node.js后台应用在开始时往往不会搞得太复杂,使用单实例的redis,一般都会使用官方推荐的模块 node_redis 。
访问单实例node.js
在 node_redis 的基础上稍作封装,主要是避免并行访问时意外创建多个redis连接。
database.js
var redis = require('redis'); function Database() { var self = this; self._redis_host = '127.0.0.1'; self._redis_port = 6379; self._redis_db = 2; self._redis = null; self._redis_selected = false; } Database.prototype.redis = function(callback) { var self = this; if (self._redis && self._redis_selected) { return callback(null, self._redis); } if (! self._redis) { self._redis = redis.createClient(self._redis_port, self._redis_host); } self._redis.select(self._redis_db, function (err) { if (err) { return callback(err); } self._redis_selected = true; return callback(null, self._redis); }); }; module.exports = new Database();
client.js
使用 database.js
var db = require('./database'); db.redis(function (err, client) { if(err) { console.error(err.toString()); return process.exit(1); } client.set('hello', 'world', function (err) { if(err) { console.error(err.toString()); return process.exit(1); } setTimeout(function () { client.get('hello', function (err, value) { if(err) { console.error(err.toString()); return process.exit(1); } console.log('hello: ' + value); process.exit(0); }); }, 20*1000); }); });
访问主备Redis
但是随着服务的成功,用户量开始增加,另外对稳定性、可靠性有了一定要求,确保数据的安全性成了重中之重,redis由单机转向主/备(Replication)甚至集群(Cluster),本文只关注使用 Sentinel
管理的主/备(Replication)Redis。
这就意味着Redis客户端应用不能直接连接Redis实例,而是需要先连接 Sentinel
,根据 Sentinel
提供的 Master
或 Slave
地址连接Redis实例,还要接收 Sentinel
的 Master
Slave
变动通知,重连Redis实例。
不幸的是,node_redis 模块只支持单实例redis,基于 node_redis 实现与 Sentinel
的交互工作量比较大。
所幸的是 ioredis (现已成为redis官方推荐模块)出现了,它支持 Sentinel
,而且大部分API跟 node_redis 是兼容的:
ioredis is a robust, full-featured Redis client that is used in the world's biggest online commerce company Alibaba and many other awesome companies.
- Full-featured. It supports Cluster, Sentinel, Pipelining and of course Lua scripting & Pub/Sub (with the support of binary messages).
- High performance.
- Delightful API. It works with Node callbacks and Bluebird promises.
- Transformation of command arguments and replies.
- Transparent key prefixing.
- Abstraction for Lua scripting, allowing you to define custom commands.
- Support for binary data.
- Support for TLS.
- Support for offline queue and ready checking.
- Support for ES6 types, such as Map and Set.
- Support for GEO commands (Redis 3.2 Unstable).
- Sophisticated error handling strategy.
ioredis 提供了从 node_redis 进行迁移的文档 Migrating from node_redis 。
但也要注意 ioredis 的不同之处,如连接 Redis
失败时, node_redis 默认是不重连,而 ioredis 会重连,在 redis
故障时可能导致 node.js
积压大量请求耗尽内存。
参考文档 ioredis Sentinel ,将上一节 访问单实例node.js
中redis封装代码改成支持 Sentinel
database.js
var Redis = require('ioredis'); function Database() { var self = this; self._redis_options = { name: 'mymaster', sentinels: [{ host: '127.0.0.1', port: 5000 }, { host: '127.0.0.1', port: 5001 }], db: 2 }; self._redis = null; } Database.prototype.redis = function(callback) { var self = this; if (! self._redis) { self._redis = new Redis(self._redis_options); } return callback(null, self._redis); }; module.exports = new Database();
和使用 node_redis 的 database.js
对照一下,可以发现 ioredis 连接 redis
时,支持 db
选项,可以省去调用 select
。
值得注意的是 node_redis 最近开始支持连接 redis
时指定 db
选项,见 Parse redis url just like IANA · NodeRedis/node_redis@a4285c1 ,使用该特性时请安装最新版的 node_redis 。
db: null; If set, client will run redis select command on connect. This is not recommended.
启动 Redis 及 Sentinel
启动上一篇文章《 redis高可用性方案:Sentinel 》配置好的 Sentinel
Master
redis-server ./redis-master.conf
Slave
redis-server ./redis-slave.conf
Sentinel a
redis-sentinel ./redis-sentinel-a.conf
Sentinel b
redis-sentinel ./redis-sentinel-b.conf
Sentinel c
redis-sentinel ./redis-sentinel-c.conf
演示运行
运行演示脚本
1: node client.js & 2: sleep 1 3: redis-cli -p 6380 -n 2 get hello 4: redis-cli -p 6379 debug sleep 30 &
演示脚本运行结果
5: "world" 6: OK 7: hello: world