Node.jsとRedis
Node.jsとRedisは自然な組み合わせです。このレッスンでは、Node.jsでのRedisの使用について解説します。
RedisのNode.jsクライアント
主要なNode.js Redisクライアント:
- ioredis:機能豊富、クラスター、センチネル、Luaスクリプトをサポート
- redis(node-redis):公式クライアント、使いやすい
- redis(v3):旧バージョン、広く使用されている
💡 推奨: 新しいプロジェクトではioredisまたはnode-redis v4+を使用してください。
ioredis
インストール
BASH
npm install ioredis
基本接続
JAVASCRIPT
const Redis = require('ioredis');
// Redisに接続
const redis = new Redis({
host: 'localhost',
port: 6379,
// password: 'your_password',
db: 0
});
// 接続をテスト
redis.ping().then(result => {
console.log(result); // 'PONG'
});
文字列操作
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
// SETとGET
async function stringDemo() {
// SET
await redis.set('name', 'Alice');
// GET
const name = await redis.get('name');
console.log(name); // 'Alice'
// 有効期限付きSET
await redis.set('session', 'data', 'EX', 3600); // 1時間後に期限切れ
// MSETとMGET
await redis.mset('key1', 'value1', 'key2', 'value2');
const values = await redis.mget('key1', 'key2');
console.log(values); // ['value1', 'value2']
// INCR
await redis.set('counter', 0);
const count = await redis.incr('counter');
console.log(count); // 1
// DEL
await redis.del('name');
}
stringDemo();
ハッシュ操作
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function hashDemo() {
// HSETとHGET
await redis.hset('user:1', 'name', 'Alice');
await redis.hset('user:1', 'age', 25);
const name = await redis.hget('user:1', 'name');
console.log(name); // 'Alice'
// HMSET
await redis.hmset('user:2', {
name: 'Bob',
age: 30,
city: 'Beijing'
});
// HGETALL
const user = await redis.hgetall('user:2');
console.log(user); // { name: 'Bob', age: '30', city: 'Beijing' }
// HKEYSとHVALS
const keys = await redis.hkeys('user:2');
const values = await redis.hvals('user:2');
// HDEL
await redis.hdel('user:2', 'city');
// HINCRBY
await redis.hincrby('user:2', 'age', 1);
}
hashDemo();
リスト操作
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function listDemo() {
// LPUSHとRPUSH
await redis.lpush('mylist', 'value1', 'value2');
await redis.rpush('mylist', 'value3');
// LRANGE
const list = await redis.lrange('mylist', 0, -1);
console.log(list); // ['value2', 'value1', 'value3']
// LPOPとRPOP
const left = await redis.lpop('mylist');
const right = await redis.rpop('mylist');
// LLEN
const length = await redis.llen('mylist');
}
listDemo();
セット操作
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function setDemo() {
// SADD
await redis.sadd('myset', 'a', 'b', 'c');
// SMEMBERS
const members = await redis.smembers('myset');
console.log(members); // Set { 'a', 'b', 'c' }
// SISMEMBER
const exists = await redis.sismember('myset', 'a');
console.log(exists); // 1 (true)
// SREM
await redis.srem('myset', 'a');
// SINTER、SUNION、SDIFF
await redis.sadd('set1', 'a', 'b', 'c');
await redis.sadd('set2', 'b', 'c', 'd');
const inter = await redis.sinter('set1', 'set2');
console.log(inter); // Set { 'b', 'c' }
}
setDemo();
ソート済みセット操作
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function zsetDemo() {
// ZADD
await redis.zadd('leaderboard', 100, 'player1', 200, 'player2', 150, 'player3');
// ZRANGE
const top = await redis.zrange('leaderboard', 0, -1, 'WITHSCORES');
console.log(top); // ['player1', '100', 'player3', '150', 'player2', '200']
// ZREVRANGE(高い方から低い方へ)
const topDesc = await redis.zrevrange('leaderboard', 0, 2, 'WITHSCORES');
// ZSCORE
const score = await redis.zscore('leaderboard', 'player1');
console.log(score); // '100'
// ZINCRBY
await redis.zincrby('leaderboard', 50, 'player1');
// ZREM
await redis.zrem('leaderboard', 'player1');
}
zsetDemo();
Pub/Sub
JAVASCRIPT
const Redis = require('ioredis');
// サブスクライバー
const subscriber = new Redis();
subscriber.subscribe('news', 'sports');
subscriber.on('message', (channel, message) => {
console.log(`Channel: ${channel}, Message: ${message}`);
});
// パブリッシャー
const publisher = new Redis();
setInterval(() => {
publisher.publish('news', `News message ${Date.now()}`);
}, 1000);
パイプライン
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function pipelineDemo() {
const pipeline = redis.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.set('key3', 'value3');
pipeline.get('key1');
const results = await pipeline.exec();
console.log(results);
// [ [ null, 'OK' ], [ null, 'OK' ], [ null, 'OK' ], [ null, 'value1' ] ]
}
pipelineDemo();
トランザクション
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function transactionDemo() {
const multi = redis.multi();
multi.set('key1', 'value1');
multi.set('key2', 'value2');
multi.incr('counter');
const results = await multi.exec();
console.log(results);
}
transactionDemo();
接続管理
ioredisは組み込みのコネクションプールを備えており、追加設定は不要です:
JAVASCRIPT
const Redis = require('ioredis');
// 単一接続(自動プール)
const redis = new Redis({
host: 'localhost',
port: 6379,
maxRetriesPerRequest: 3,
enableReadyCheck: true
});
// クラスターモード
const cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: 7000 },
{ host: '127.0.0.1', port: 7001 },
{ host: '127.0.0.1', port: 7002 }
]);
node-redis(公式クライアント)
インストール
BASH
npm install redis
基本的な使用方法
JAVASCRIPT
const { createClient } = require('redis');
async function main() {
// クライアントを作成
const client = createClient({
url: 'redis://localhost:6379'
});
// 接続
await client.connect();
// SETとGET
await client.set('key', 'value');
const value = await client.get('key');
console.log(value); // 'value'
// ハッシュ操作
await client.hSet('user:1', 'name', 'Alice');
const name = await client.hGet('user:1', 'name');
// リスト操作
await client.lPush('mylist', 'value1');
const list = await client.lRange('mylist', 0, -1);
// セット操作
await client.sAdd('myset', 'a', 'b', 'c');
const members = await client.sMembers('myset');
// 接続を閉じる
await client.quit();
}
main();
Pub/Sub
JAVASCRIPT
const { createClient } = require('redis');
async function pubsub() {
const subscriber = createClient();
await subscriber.connect();
// サブスクライブ
await subscriber.subscribe('news', (message) => {
console.log(`Received: ${message}`);
});
const publisher = createClient();
await publisher.connect();
// 公開
await publisher.publish('news', 'Hello World');
}
pubsub();
実践的な例
例1:キャッシュデコレーター
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
function cache(expire = 3600) {
return function(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args) {
const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
// キャッシュから取得を試みる
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 元のメソッドを実行
const result = await originalMethod.apply(this, args);
// キャッシュに保存
await redis.set(cacheKey, JSON.stringify(result), 'EX', expire);
return result;
};
return descriptor;
};
}
class UserService {
@cache(300)
async getUser(userId) {
console.log(`Querying database: userId=${userId}`);
return { id: userId, name: `User${userId}` };
}
}
例2:分散ロック
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
const { v4: uuidv4 } = require('uuid');
class DistributedLock {
constructor(key, expire = 10) {
this.key = `lock:${key}`;
this.expire = expire;
this.identifier = uuidv4();
}
async acquire() {
const result = await redis.set(
this.key,
this.identifier,
'NX',
'EX',
this.expire
);
return result === 'OK';
}
async release() {
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
const result = await redis.eval(script, 1, this.key, this.identifier);
return result === 1;
}
}
// ロックを使用
async function withLock(key, callback) {
const lock = new DistributedLock(key);
try {
while (!(await lock.acquire())) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return await callback();
} finally {
await lock.release();
}
}
// 例
await withLock('resource', async () => {
console.log('Executing business logic');
});
例3:レートリミッター
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
async function rateLimit(key, limit = 10, period = 60) {
const current = Math.floor(Date.now() / 1000);
const windowStart = current - period;
const results = await redis
.multi()
.zremrangebyscore(key, 0, windowStart)
.zcard(key)
.zadd(key, current, `${current}`)
.expire(key, period)
.exec();
const count = results[1][1];
return count < limit;
}
// レートリミッターの使用
async function apiHandler(userId) {
const allowed = await rateLimit(`api:${userId}`, 10, 60);
if (allowed) {
console.log('Request allowed');
return { success: true };
} else {
console.log('Request denied');
return { success: false, error: 'Rate limit exceeded' };
}
}
例4:メッセージキュー
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
class MessageQueue {
constructor(name) {
this.name = `queue:${name}`;
}
async push(message) {
await redis.rpush(this.name, JSON.stringify(message));
}
async pop(timeout = 0) {
const result = await redis.blpop(this.name, timeout);
if (result) {
return JSON.parse(result[1]);
}
return null;
}
async size() {
return await redis.llen(this.name);
}
}
// プロデューサー
const queue = new MessageQueue('tasks');
await queue.push({ task: 'send_email', to: 'user@example.com' });
// コンシューマー
while (true) {
const task = await queue.pop(5);
if (task) {
console.log('Processing task:', task);
} else {
break;
}
}
エラーハンドリング
JAVASCRIPT
const Redis = require('ioredis');
const redis = new Redis();
redis.on('error', (error) => {
console.error('Redis error:', error);
});
redis.on('connect', () => {
console.log('Redis connected');
});
redis.on('ready', () => {
console.log('Redis ready');
});
redis.on('close', () => {
console.log('Redis connection closed');
});
❓ よくある質問
Q ioredisとnode-redisの選び方は?
A ioredisはより多機能(クラスター、センチネル)です。node-redisは公式クライアントです。ioredisが推奨されます。
Q ioredisはスレッドセーフですか?
A はい。ioredisは組み込みのコネクションプールを備えており、モジュール間で共有できます。
Q 切断を処理するにはどうすればよいですか?
A ioredisは自動的に再接続します。errorイベントとconnectイベントをリッスンできます。
Q 非同期操作はどのように機能しますか?
A すべてのioredis操作はPromiseを返します。async/awaitまたは.then()を使用します。
Q コネクションプールを実装するにはどうすればよいですか?
A ioredisは組み込みのコネクションプールを備えており、追加設定は不要です。
📖 まとめ
- ioredis:機能豊富、クラスター、センチネル、Luaスクリプトをサポート
- node-redis:公式クライアント、使いやすい
- すべての操作は非同期で、Promiseを返す
- パイプライン、トランザクション、Pub/Subをサポート
- 組み込みのコネクションプールと自動再接続
- 実践的な例:キャッシュ、分散ロック、レート制限、メッセージキュー
📝 練習問題
- 基本操作: ioredisを使用して文字列、ハッシュ、リストの操作をしましょう
- パイプライン: パイプラインを使用して1000個のキーをバッチ設定し、パフォーマンスを比較しましょう
- Pub/Sub: シンプルなPub/Subメッセージングシステムを実装しましょう
- 実践: シンプルな分散ロックまたはレートリミッターを実装しましょう
おめでとうございます!
Redisチュートリアルの全26レッスンを完了しました!次のステップ:
- 実践:実際のプロジェクトでRedisを活用しましょう
- 深化:Redisクラスター、センチネル、Luaスクリプトについて学びましょう
- 最適化:Redisのパフォーマンス最適化とベストプラクティスを学びましょう



