Node
模块Nodejs
模块之ioredis
模块的简单使用
ioredis
是Nodejs的一个Redis
模块,有着与Redis
一模一样的 API 操作。
redis是单线程作业,所以不管查询任务是由一个链接发来的还是多个链接发来的,redis是串行的执行。并通过当前的链接返回客户端。nodejs接受redis的返回后,不管是不是并行,都要等主线程空闲下来才能一个个处理服务器返回的数据。
它有几个特点:
- 全功能。它支持 Cluster,Sentinel,Pipelining 以及 Lua 脚本和 Pub / Sub(在二进制消息的支持下)。
- 高性能。
- 令人愉快的 API 它适用于 Node 回调和 Promise。
- 转换命令参数和回复。
- 透明键前缀。
- 抽象 Lua 脚本,允许您定义自定义命令。
- 支持二进制数据。
- 支持 TLS。
- 支持离线队列和准备检查。
- 支持 ES6 类型,如 Map 和 Set。
- 支持 GEO 命令(Redis 3.2 Unstable)。
- 复杂的错误处理策略。
安装:
0 |
npm install --save ioredis |
基本使用
下面介绍,它的基本使用,其实使用起来非常简单。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
const Redis = require('ioredis'); const redis = new Redis(); // 计算时间 const timing = async (name = 'test', cb) => { console.time(name); typeof cb === 'function' && await cb(); console.timeEnd(name); } // set timing('set', _ => { redis.set('foo', 'bar'); redis.get('foo').then(res => { console.log(res); }); }); // del timing('del', _ => { redis.del('foo').then(res => { console.log(res); }); }); // sadd [set 集合] timing('sadd', _ => { redis.sadd('seta', 1, 3, 5, 7).then(res => console.log(res)); redis.sadd('setb', [1, 3, 5, 7]).then(res => console.log(res)); }); // lpush [list 列表] timing('lpush', _ => { redis.lpush('list', [1, 2, 3]).then(res => console.log(res)); redis.lrange('list', [0, -1]).then(res => console.log(res)); }); // 选择数据库 [序号] timing('select', _ => { // 选择 1 号数据库,然后查询 1 号数据库的 keys redis.select(1).then(res => { redis.set('name', 'xiaoming'); redis.keys('*').then(res => console.log(res)); }) }); // 事务 timing('basic', async () => { redis.multi().set('a', 1).set('b', 2).exec(); }); // 管道 redis.pipeline().get('a').get('b').exec().then(res => { console.log(res); }); |
输出:
0 1 2 3 4 5 6 7 8 9 10 |
set: 9.000ms del: 9.557ms sadd: 10.212ms lpush: 10.100ms bar 1 0 0 3 1 [ 'name' ] |
可以看出,在 redis 命令行里怎样使用,在这里就怎样使用,API 基本是一致。
连接 Redis
连接Redis
,默认是连接到http://127.0.0.1:6379
。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
new Redis() // Connect to 127.0.0.1:6379 new Redis(6380) // 127.0.0.1:6380 new Redis(6379, '192.168.1.1') // 192.168.1.1:6379 new Redis('/tmp/redis.sock') new Redis({ port: 6379, // Redis port host: '127.0.0.1', // Redis host family: 4, // 4 (IPv4) or 6 (IPv6) password: 'auth', db: 0, // 数据库号 }) // 使用 redis 协议 new Redis('redis://:authpassword@127.0.0.1:6380/4') |
发布与订阅
以下是发布/订阅 API 的简单示例。以下程序打开两个客户端连接。它使用一个连接订阅一个频道,并与另一个发布到该频道:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
timing('sub/pub', _ => { // 发布 const pub = new Redis(); // 订阅 news 与 music redis.subscribe('news', 'music', (err, count) => { // 现在我们订阅了'新闻'和'音乐'通道 // count 表示我们当前订阅的频道数 pub.publish('news', 'Hello world!'); pub.publish('music', 'Hello again!'); }); // 监听 message 事件 redis.on('message', (channel, message) => { // 接收信息世界,你好!从频道新闻 // 再次收到消息,你好!从声道音乐 console.log('Receive message %s from channel %s', message, channel); // 把 news 退订了 redis.unsubscribe('news'); // redis.unsubscribe('news', 'music'); }); // 还有一个名为'messageBuffer'的事件,它与'message'相同 // 它返回缓冲区而不是字符串 redis.on('messageBuffer', (channel, message) => { // Both `channel` and `message` are buffers. }); }); |
相应的有:
0 1 2 3 4 |
// 订阅与给定模式相匹配的所有频道 redis.psubscribe('pat?ern', function (err, count) {}); redis.punsubscribe(...arg); redis.on('pmessage', function (pattern, channel, message) {}); redis.on('pmessageBuffer', function (pattern, channel, message) {}); |
当客户端发出 subscribe
或 psubscribe
时,该连接被置于“订户”模式。在这一点上,只有修改订阅集的命令是有效的。(其他命令无效)
订阅集为空时,连接将恢复为常规模式。
处理二进制数据
参考下面代码:
0 |
redis.set('buf', new Buffer('buf')); |
拿到缓存中的数据:
0 1 2 3 |
redis.getBuffer('buf', function (err, result) { // result is a buffer. }); |
管道 Pipelining
发送5条以上的命令,就可以使用管道的将命令放进内存的队列中,然后一起发送至redis,这个表现能提升百分之50-300.
这些命令在内存的队列李阿敏,并配合exec
方法进行执行。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var pipeline = redis.pipeline(); pipeline.set('foo', 'bar'); pipeline.del('cc'); pipeline.exec(function (err, results) { // `err` 总是null, and `results` 响应的数组 // corresponding to the sequence of queued commands. // Each response follows the format `[err, result]`. }); // 甚至能够链式调用命令 redis.pipeline().set('foo', 'bar').del('cc').exec(function (err, results) { }); // `exec` 也是返回的promise var promise = redis.pipeline().set('foo', 'bar').get('foo').exec(); promise.then(function (result) { // result === [[null, 'OK'], [null, 'bar']] }); |
每个链式命令也可以有一个回调,当命令得到回复时将调用它:
0 1 2 3 4 5 |
redis.pipeline().set('foo', 'bar').get('foo', function (err, result) { // result === 'bar' }).exec(function (err, result) { // result[1][1] === 'bar' }); |
除了单独向管道队列添加命令外,还可以将一组命令和参数传递给构造函数:
0 1 2 3 4 |
redis.pipeline([ ['set', 'foo', 'bar'], ['get', 'foo'] ]).exec(function () { /* ... */ }); |
length的属性:
0 1 2 |
const length = redis.pipeline().set('foo', 'bar').get('foo').length; // length === 2 |
事务:
0 1 2 3 |
redis.multi().set('foo', 'bar').get('foo').exec(function (err, results) { // results === [[null, 'OK'], [null, 'bar']] }); |
如果事务的命令链中存在语法错误(例如,参数数量错误,命令名称错误等),则不会执行任何命令,并返回错误:
0 1 2 3 4 5 6 7 8 9 10 11 12 |
redis.multi().set('foo').set('foo', 'new value').exec(function (err, results) { // err: // { [ReplyError: EXECABORT Transaction discarded because of previous errors.] // name: 'ReplyError', // message: 'EXECABORT Transaction discarded because of previous errors.', // command: { name: 'exec', args: [] }, // previousErrors: // [ { [ReplyError: ERR wrong number of arguments for 'set' command] // name: 'ReplyError', // message: 'ERR wrong number of arguments for \'set\' command', // command: [Object] } ] } }); |
就接口而言,multi与管道的不同之处在于,在为每个链接命令指定回调时,排队状态将传递给回调而不是命令的结果:
0 1 2 3 4 5 6 |
redis.multi({ pipeline: false }); redis.set('foo', 'bar'); redis.get('foo'); redis.exec(function (err, result) { // result === [[null, 'OK'], [null, 'bar']] }); |
0 1 2 3 4 |
redis.multi([ ['set', 'foo', 'bar'], ['get', 'foo'] ]).exec(function () { /* ... */ }); |
管道支持内联事务,这意味着您可以将管道中的命令子集分组到事务中:
redis.pipeline().get('foo').multi().set('foo', 'bar').get('foo').exec().get('foo').exec();
Lua脚本
ioredis支持所有脚本命令,例如EVAL,EVALSHA和SCRIPT。
ioredis公开了一个defineCommand方法,使脚本编写更容易使用:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var redis = new Redis(); // This will define a command echo: redis.defineCommand('echo', { numberOfKeys: 2, lua: 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}' }); // Now `echo` can be used just like any other ordinary command, // and ioredis will try to use `EVALSHA` internally when possible for better performance. redis.echo('k1', 'k2', 'a1', 'a2', function (err, result) { // result === ['k1', 'k2', 'a1', 'a2'] }); // `echoBuffer` is also defined automatically to return buffers instead of strings: redis.echoBuffer('k1', 'k2', 'a1', 'a2', function (err, result) { // result[0] equals to Buffer.from('k1'); }); // And of course it works with pipeline: redis.pipeline().set('foo', 'bar').echo('k1', 'k2', 'a1', 'a2').exec(); |
如果在定义命令时无法确定键的数量,则可以省略numberOfKeys属性,并在调用命令时将键数作为第一个参数传递:
0 1 2 3 4 5 6 7 8 9 |
redis.defineCommand('echoDynamicKeyNumber', { lua: 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}' }); // Now you have to pass the number of keys as the first argument every time // you invoke the `echoDynamicKeyNumber` command: redis.echoDynamicKeyNumber(2, 'k1', 'k2', 'a1', 'a2', function (err, result) { // result === ['k1', 'k2', 'a1', 'a2'] }); |
Redis规范
cluster
构造函数接受两个参数,
- 第一个参数是连接到的集群节点列表,不需要枚举所有的集群节点,但是如果一个节点无法访问客户端将会尝试下一个节点,并且客户端将至少连接一个节点时自动发现其他节点。
- 第二个参数是选项
clusterRetryStrategy
:当没有启动任何节点可访问时,clusterRetryStrategy
将调用,返回一个数字,ioredis将尝试在指定的延迟(毫秒为单位)后从头开始重新连接到启动节点,否则将返回无启动节点可用错误。
012345// 选项的默认值function (times) {var delay = Math.min(100 + times * 2, 2000);return delay;}enableOfflineQueue
:类似于类的enableOfflineQueue
选项Redis
。enableReadyCheck
:启用后,只有在cluster info
报告集群准备好处理命令的命令时才会发出“就绪”事件。否则,它将在“connect”发出后立即发出。scaleReads
:配置发送读取查询的位置。maxRedirections
:当收到与集群相关的错误(例如MOVED
,等)时,客户端将命令重定向到另一个节点。此选项限制发送命令时允许的最大重定向。默认值为。ASK
CLUSTERDOWN
16
retryDelayOnFailover
:如果在发送命令时目标节点断开连接,ioredis将在指定的延迟后重试。默认值为100
。您应该确保retryDelayOnFailover * maxRedirections > cluster-node-timeout
在故障转移期间确保没有命令失败。retryDelayOnClusterDown
:当集群关闭时,所有命令都将被拒绝,错误为CLUSTERDOWN
。如果此选项是数字(默认情况下为100
),则客户端将在指定时间(以毫秒为单位)后重新发送命令retryDelayOnTryAgain
:如果此选项是一个数字(默认情况下是100
),则客户端将TRYAGAIN
在指定时间(以毫秒为单位)后重新发送被拒绝的命令。redisOptions
:Redis
连接到节点时传递给构造函数的默认选项。slotsRefreshTimeout
:从集群刷新插槽时发生超时前的毫秒数(默认值1000
)slotsRefreshInterval
:每个自动插槽刷新之间的毫秒数(默认5000
)
集群模式下的发布和订阅:(和独立模式下的发布订阅一致)
01234567sub.on('message', function (channel, message) {console.log(channel, message);});sub.subscribe('news', function () {pub.publish('news', 'highlights');});
参考:
https://www.dazhuanlan.com/2019/10/20/5dab4f18bc9ae/
https://blog.csdn.net/qq_33589252/article/details/85535890