Redisをインストールするには

「7つのデータベース 7つの世界」第8章の学習のために、Redisをインストールします。

radish(大根)を連想してしまうよ...

7つのデータベース 7つの世界

  • PotgreSQL
  • Riak
  • HBase
  • MongoDB
  • CouchDB
  • Neo4j
  • Redis
Redis - The Real-time Data Platform
Developers love Redis. Unlock the full potential of the Redis database with Redis Enterprise and start building blazing ...

書籍で使っているRedisのバージョンは、2.4

最新バージョンは、6.0.4。

インストール

Windows 10

公式のWindowsバイナリはないようです。

githubにmicrosoftarchivesの3.2.100が残っています。msiインストーラとzipファイルをダウンロードできます。

Releases · microsoftarchive/redis
Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are...

zipファイル

ここでは、zipファイルをダウンロードして、Windowsサービスではなくフォアグラウンドでredis-serverを起動してみます。

エクスプローラで、ダウンロードしたzipファイルを選択して、右クリックして、「すべて展開...」を選びます。Redis-x64-3.2.100フォルダに展開します。

コマンドプロンプトまたはPoweShellを起動して、展開したフォルダに移動します。

C:> cd Downloads\Redis-x64-3.2.100Code language: Bash (bash)

バージョンを確認します。

C:> redis-cli --version
redis-cli 3.2.100

C:> redis-server --version
Redis server v=3.2.100 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=dd26f1f93c5130eeCode language: Bash (bash)

redis-cliを起動すると、「not connected」プロンプトになってしまいました。quitで終了します。

C:> redis-cli
Could not connect to Redis at 127.0.0.1:6379: 対象のコンピューターによって拒否されたため、接続できませんでした。
Could not connect to Redis at 127.0.0.1:6379: 対象のコンピューターによって拒否されたため、接続できませんでした。
not connected> quitCode language: Bash (bash)

redis-serverを起動します。confファイルは、ファイル名に「-service」がつかない「redis.windows.conf」を指定します。

redis-serverを停止するには、Ctrl+Cを押します。

C:> redis-server redis.windows.conf
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 3.2.100 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 22904
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

[22904] 29 May 09:09:34.136 # Server started, Redis version 3.2.100
[22904] 29 May 09:09:34.139 * DB loaded from disk: 0.000 seconds
[22904] 29 May 09:09:34.140 * The server is now ready to accept connections on port 6379Code language: Bash (bash)

別のコマンドプロンプトを起動して、redis-cliを起動します。

C:> cd Downloads\Redis-x64-3.2.100

C:> redis-cli
127.0.0.1:6379>Code language: Bash (bash)

msiインストーラ

msiインストーラで、Windowsサービスとしてインストールする手順は、次の記事を参考にしてください。
https://weblabo.oscasierra.net/redis-windows-install/
https://qiita.com/okoi/items/3bb5ae26ad559e4f39a0

Mac OS X

homebrewで6.0.4を、homebrew caskで4.0.2をインストールできます。

ここでは、homebrewの6.0.4をインストールしました。

$ brew install redis
(省略)
==> Caveats
To have launchd start redis now and restart at login:
  brew services start redis
Or, if you don't want/need a background service you can just run:
  redis-server /usr/local/etc/redis.conf
(省略)Code language: Bash (bash)
$ redis-server --version
Redis server v=6.0.4 sha=00000000:0 malloc=libc bits=64 build=992d36c7134f1fc8

$ redis-cli --version
redis-cli 6.0.4
Code language: Bash (bash)

バージョンを確認します。

$ redis-server --version
Redis server v=6.0.4 sha=00000000:0 malloc=libc bits=64 build=992d36c7134f1fc8

$ redis-cli --version
redis-cli 6.0.4
Code language: Bash (bash)

起動方法は2種類あります。サービスとして起動する方法、フォアグラウンドで起動する方法です。

サービスとして起動する方法

$ brew services start redis
==> Successfully started `redis` (label: homebrew.mxcl.redis)

$ redis-cli
127.0.0.1:6379> quit

$ brew services stop redis
Stopping `redis`... (might take a while)
==> Successfully stopped `redis` (label: homebrew.mxcl.redis)
Code language: Bash (bash)

フォアグラウンドで起動する方法。別のターミナルを開いて、redis-cliを起動します。フォアグラウンドのredis-serverをストップするには、Ctrl+Cを押します。

$ redis-server /usr/local/etc/redis.conf
75019:C 29 May 2020 08:52:25.243 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
75019:C 29 May 2020 08:52:25.243 # Redis version=6.0.4, bits=64, commit=00000000, modified=0, pid=75019, just started
75019:C 29 May 2020 08:52:25.243 # Configuration loaded
75019:M 29 May 2020 08:52:25.245 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.4 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 75019
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

75019:M 29 May 2020 08:52:25.246 # Server initialized
75019:M 29 May 2020 08:52:25.247 * Loading RDB produced by version 6.0.4
75019:M 29 May 2020 08:52:25.247 * RDB age 8 seconds
75019:M 29 May 2020 08:52:25.247 * RDB memory usage when created 0.95 Mb
75019:M 29 May 2020 08:52:25.247 * DB loaded from disk: 0.000 seconds
75019:M 29 May 2020 08:52:25.247 * Ready to accept connections

Code language: Bash (bash)

Ubuntu 18.04

aptでインストールできます。

$ sudo apt install redisCode language: Bash (bash)

バージョンは、4.0.9 でした。

$ redis-server --version
Redis server v=4.0.9 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=9435c3c2879311f3

$ redis-cli --version
redis-cli 4.0.9Code language: Bash (bash)

サービスとしてインストールされます。インストール後、すでにサービスがスタートしていました。

$ sudo service redis stop

$ sudo service redis startCode language: Bash (bash)

試しに、サービスをストップして、redis-cliを起動してみました。redis-cliのプロンプトが「not connected>」と表示されました。

$ redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> quitCode language: Bash (bash)

サービスがスタートしていれば、redis-cliのプロンプトは「127.0.0.1:6379>」と表示されました。

$ redis-cli
127.0.0.1:6379>Code language: Bash (bash)

書籍内コードほか

p261 データダンプ

http://download.freebase.com/datadumps/latest/browse/book/isbn.tsv は404エラー。

http://freebase-easy.cs.uni-freiburg.de/ から、freebase-easy-14-04-14.zip をダウンロードして、"isbn-13"でgrepして、列を入れ替えて、isbn.tsv を作りました。

isbn.tsv.gz 15MB、69万件

p262のisbn.rbでインポートしたところ、28.45秒。isbn_pipelined.rbでインポートしたところ、9.4秒。パイプラインで改善できるとの説明どおり、処理時間は約1/3になりました。

p270 データの投入

http://download.freebase.com/datadumps/latest/browse/music/group_membership.tsv は404エラー

http://freebase-easy.cs.uni-freiburg.de/ から、freebase-easy-14-04-14.zip をダウンロードして、もろもろ処理をして、group_membership.tsv を作りました。

group_membership.tsv 2MB、15万件

p271 フェーズ1: データ変換

最新のnodejsで動くように、pre_populate.jsの一部を書き直しました。

/***
 * Excerpted from "Seven Databases in Seven Weeks",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/rwdata for more book information.
***/

var
  // The band data file name in tab-seperated form
  tsvFileName = 'group_membership.tsv',
  // track how many file lines we've processed
  processedLines = 0,

  // standard libraries
  fs = require('fs'),
  csv = require('csv'),
  redis = require('redis'),

  // database clients
  redis_client = redis.createClient(6379);

/**
 * A helper function that splits up the comma-seperated list of roles and
 * converts it to an array. If no valid roles exist, return an empty array.
 * @param string the CSV to split into a role array
 */
function buildRoles( string ) {
  var roles = string.split(',');
  if(roles.length === 1 && roles[0] === '') roles = [];
  return roles;
};

/**
 * Utility function that increments the total number
 * of lines (artists) processed and outputs every 1000.
 */
function trackLineCount() {
  if( ++processedLines % 1000 === 0 )
    console.log('Lines Processed: ' + processedLines);
}

/**
 * Does all heavy lifting. Loops through the CSV file
 * and populate Redis with the given values.
 */
function processData(data) {
  var artist = data[1];
  var band =   data[2];
  var roles = buildRoles(data[3]);

  if ( band === '' || artist === '') {
    trackLineCount();
    return true;
  }

  redis_client.sadd('band:' + band, artist);
  
  roles.forEach(function(role) {
    redis_client.sadd('artist:' + band + ':' + artist, role);
  });

  trackLineCount();
}

function populateRedis() {
  const parser = csv.parse({
    delimiter: "\t",
    quote: false
  });

  parser.on('error', (err) => {
    console.log(err.message);
  });

  parser.on('readable', () => {
    var data;

    while (data = parser.read()) {
      processData(data);
    }
  });
  
  parser.on('end', () => {
    console.log('Total Lines Processed: ' + processedLines);
    redis_client.quit();
  });

  fs.createReadStream(tsvFileName).pipe(parser);
};

populateRedis();
Code language: JavaScript (javascript)

populate_couch.js

/***
 * Excerpted from "Seven Databases in Seven Weeks",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/rwdata for more book information.
***/

var
  // how many bands we expect to process
  totalBands = null,
  // and keep track of how many bands we have processed
  processedBands = 0,
  // The name of the couch database
  couchDBpath = '/bands',

  // standard libraries
  http = require('http'),
  redis = require('redis'),

  // database clients
  redisClient = redis.createClient(6379);

/**
 * A helper function that builds a good CouchDB key
 * @param string the unicode string being keyified
 */
function couchKeyify(string) {
  // remove bad chars, and disallow starting with an underscore
  return string.
    replace(/[\t \?\#\\\-\+\.\,'"()*&!\/]+/g, '_').
    replace(/^_+/, '');
};

/*
 * Keep track of the number of bands processed, output every 1000 loaded,
 * and close the Redis client when we've loaded them all.
 */
function trackLineCount( increment ) {
  processedBands += increment;

  // output once every 1000 lines
  if (processedBands % 1000 === 0) {
    console.log('Bands Loaded: ' + processedBands);
  }

  // close the Redis Client when complete
  if (totalBands <= processedBands) {
    console.log('Total Bands Loaded: ' + processedBands);
    redisClient.quit();
  }
};

/*
 * Post one or more documents into CouchDB.
 * @param url is where we POST to.
 * @param docString a stringified JSON document.
 * @param count the number of documents being inserted.
 */
function postDoc(path, docsString, count) {
  console.log("postDoc " + path + " " + count);

  const options = {
    host:   '127.0.0.1',
    port:   5984,
    method: 'POST',
    path:   path,
    headers: {
      'Content-Type' : 'application/json'
    }
  };

  const req = http.request(options, (res) => {
    res.on('data', (chunk) => {
      trackLineCount(count);
    });

    res.on('error', (err) => {
      console.log('response.error: ' + err.message);
    });
  });

  req.on('error', (err) => {
    console.log('request.error: ' + err.message);
  });
  req.write(docsString);
  req.end();
};

/*
 * Loop through all of the bands populated in Redis. We expect
 * the format of each key to be 'band:Band Name' having the value
 * as a set of artist names. The artists each have the list of roles
 * they play in each band, keyed by 'artist:Band Name:Artist Name'.
 * The band name, set of artists, and set of roles each artist plays
 * populates the CouchDB documents. eg:
  {
    name:"Nirvana",
    artists:[{
      name: "Kurt Cobain",
      roles:["Lead Vocals", "Guitar"]
    },...]
  }
 */
function populateBands() {

  // First, create the couch database
  const options = {
    host:   '127.0.0.1',
    port:   5984,
    method: 'PUT',
    path:   couchDBpath,
  };
  const req = http.request(options).end();

  redisClient.keys('band:*', (error, bandKeys) => {

    totalBands = bandKeys.length;
    var 
      readBands = 0,
      bandsBatch = [];
	  
    bandKeys.forEach((bandKey) => {
      // substring of 'band:'.length gives us the band name
      var bandName = bandKey.substring(5);
      redisClient.smembers(bandKey, (error, artists) => {

        // batch the Redis calls to get all artists' information at once
        var roleBatch = [];
        artists.forEach((artistName) => {
          roleBatch.push([
            'smembers',
            'artist:' + bandName + ':' + artistName
          ]);
        });

        // batch up each band member to find the roles they play
        redisClient
          .multi(roleBatch)
          .exec((err, roles) => {
            var
              i = 0,
              artistDocs = [];

            // build the artists sub-documents
            artists.forEach( function(artistName) {
              artistDocs.push({ name: artistName, role : roles[i++] });
            });
			
            // add this new band document to the batch to be executed later
            bandsBatch.push({
              _id: couchKeyify( bandName ),
              name: bandName,
              artists: artistDocs
            });
            // keep track of the total number of bands read
            readBands += 1;

            // upload batches of 50 values to couch, or the remaining values left
            if (bandsBatch.length >= 50 || totalBands - readBands == 0) {
              postDoc(
                couchDBpath + '/_bulk_docs',
                JSON.stringify({ docs : bandsBatch }),
                bandsBatch.length
                );

              // empty out the batch array to be filled again
              bandsBatch = [];
            }
          });
      });
    });
  });
 };

// expose couchKeyify function
exports.couchKeyify = couchKeyify;

// start populating bands if running as main script
if(!module.parent) {
  populateBands();
}

Code language: JavaScript (javascript)

bands.js

/***
 * Excerpted from "Seven Databases in Seven Weeks",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/rwdata for more book information.
***/

var
  port = 8080,
  jsonHeader = {'Content-Type':'application/json'},

  // standard libraries
  http = require('http'),
  redis = require('redis'),
  bricks = require('bricks'),
  mustache = require('mustache'),
  fs = require('fs'),

  // custom libraries
  couchUtil = require('./populate_couch.js'),
  neo4j = require('./neo4j_caching_client.js'),

  // database clients
  //couchClient = http.createClient(5984, 'localhost'),
  neo4jClient = neo4j.createClient(),
  redisClient = redis.createClient(6379);

var
  gremlin = neo4jClient.runGremlin;

/**
 * A convenience function for wrapping the
 * reading of JSON reponse data chunks.
 * @param response A Node HTTP response object.
 * @param callback the function to populate and call on completion.
 */
function processBuffer( response, callback )
{
  var buffer = '';
  response.on('data', function(chunk) {
    buffer += chunk;
  });
  response.on('end', function() {
    if(buffer === '') buffer = 'null';
    callback( JSON.parse(buffer) );
  });
};

/*
 * Post one or more documents into CouchDB.
 * @param url is where we POST to.
 * @param docString a stringified JSON document.
 * @param count the number of documents being inserted.
 */
function getCouchDoc( path, httpResponse, callback )
{
  var options = {
    hsot: "127.0.0.1",
    port: 5984,
    method: 'GET',
    path: path,
    headers: {
      jsonHeader 
    }
  }
  var request = http.request(options);
  request.end();
  request.on('response', function( response ) {
    if( response.statusCode != 200 ) {
      writeTemplate( httpResponse, '', { message: "Value not found" } );
    } else {
      processBuffer( response, function( couchObj ) {
        callback( couchObj );
      });
    }
  }).
  on('error', function(e) {
    console.log('postDoc Got error: ' + e.message);
  });
};

/**
 * Wraps a block of HTML with a standard template. HTML lives in template.html.
 * @innerHtml populates the body of the template
 */
function htmlTemplate( innerHtml )
{
  var file_data = fs.readFileSync( 'template.html', 'utf8' );
  return file_data.replace("[[YIELD]]", innerHtml);
};

function writeTemplate( response, innerHtml, values )
{
  response.write( mustache.render(htmlTemplate(innerHtml), values));
  response.end();
}


// A Nodejs web app utility setup
appServer = new bricks.appserver();

// attach request plugin to easily extract params
appServer.addRoute("^/", appServer.plugins.request);

/*
 * Just display a blank form if no band is given.
 */
appServer.addRoute("^/$", function(req, res) {
  writeTemplate(res, '', { message: "Find a band" });
});

/*
 * Accepts a band name and displays all artists in the band.
 * Also displays a list of suggested bands where at least
 * one artist has played at one time.
 */
appServer.addRoute("^/band$", function(req, res) {
  var
    bandName = req.param('name'),
    bandNodePath = '/bands/' + couchUtil.couchKeyify( bandName ),
    membersQuery = 'g.V[[name:"'+bandName+'"]]'
                 + '.out("member").in("member").uniqueObject.name';

  getCouchDoc( bandNodePath, res, function( couchObj ) {
    gremlin( membersQuery, function(graphData) {
      var artists = couchObj && couchObj['artists'];
      var values = { band: bandName, artists: artists, bands: graphData };
      var body = '<h2>{{band}} Band Members</h2>';
      body += '<ul>{{#artists}}';
      body += '<li><a href="/artist?name={{name}}">{{name}}</a></li>';
      body += '{{/artists}}</ul>';
      body += '<h3>You may also like</h3>';
      body += '<ul>{{#bands}}';
      body += '<li><a href="/band?name={{.}}">{{.}}</a></li>';
      body += '{{/bands}}</ul>';
      writeTemplate( res, body, values );
    });
  });
});

/*
 * Accepts an artist name and displays band and role information
 */
appServer.addRoute("^/artist$", function(req, res) {
  var
    artistName = req.param('name'),
    rolesQuery = 'g.V[[name:"'+artistName+'"]].out("plays").role.uniqueObject',
    bandsQuery = 'g.V[[name:"'+artistName+'"]].in("member").name.uniqueObject';
  gremlin( rolesQuery, function(roles) {
    gremlin( bandsQuery, function(bands) {
      var values = { artist: artistName, roles: roles, bands: bands };
      var body = '<h3>{{artist}} Performs these Roles</h3>';
      body += '<ul>{{#roles}}';
      body += '<li>{{.}}</li>';
      body += '{{/roles}}</ul>';
      body += '<h3>Play in Bands</h3>';
      body += '<ul>{{#bands}}';
      body += '<li><a href="/band?name={{.}}">{{.}}</a></li>';
      body += '{{/bands}}</ul>';
      writeTemplate( res, body, values );
    });
  });
});

/*
 * A band name search. Used for autocompletion.
 */
appServer.addRoute("^/search$", function(req, res) {
  var query = req.param('term');

  redisClient.keys("band-name:"+query+"*", function(error, keys) {
    var bands = [];
    keys.forEach(function(key){
      bands.push(key.replace("band-name:", ''));
    });
    res.write( JSON.stringify(bands) );
    res.end();
  });
});

// catch all unknown routes with a 404
appServer.addRoute(".+", appServer.plugins.fourohfour);
appServer.addRoute(".+", appServer.plugins.loghandler, { section: "final" });

// start up the server
console.log("Starting Server on port " + port);
appServer.createServer().listen(port);
Code language: JavaScript (javascript)
タイトルとURLをコピーしました