关于node-redis中quit()的优雅退出遇到forEach的问题

在redis的node客户端中官方文档上描述客户端断开与redis的连接的方式有两种:

  • client.end()所谓的‘二话不说直接暴力断开’;官方文档上是这样描述end()方法的:

Forcibly close the connection to the Redis server. Note that this does not wait until all replies have been parsed. If you want to exit cleanly, call client.quit() as mentioned above.
You should set flush to true, if you are not absolutely sure you do not care about any other commands. If you set flush to false all still running commands will silently fail.

大体上的意思就是说end()方法不会等到所有的答复都被解析之后才断开和redis的连接,他会立刻断开与数据库的连接,若是你想要优雅的退出你应该选择quit()。

  • quit()方法在官方文档上是这样描述的:

This sends the quit command to the redis server and ends cleanly right after all running commands were properly handled. If this is called while reconnecting (and therefore no connection to the redis server exists) it is going to end the connection right away instead of resulting in further reconnections! All offline commands are going to be flushed with an error in that case.

主旨是说调用quit命令将所有运行的命令正确处理后,将quit命令发送到redis服务器,并将其完全正确地结束。就是所谓的优雅退出
如果在重新连接时调用(即redis服务器的连接不存在),则它将立即结束连接,而不会导致进一步的重新连接,在这种情况下,所有脱机命令将被刷新,并出现错误。

因为有所谓的优雅退出才会有如下的方式写法:

var redis = require("redis");
var client = redis.createClient({detect_buffers: true});
 
client.set("foo_rand000000000000", "OK");
 
// This will return a JavaScript String 
client.get("foo_rand000000000000", function (err, reply) {
    console.log(reply.toString()); // Will print `OK` 
});
 
// This will return a Buffer since original key is specified as a Buffer 
client.get(new Buffer("foo_rand000000000000"), function (err, reply) {
    console.log(reply.toString()); // Will print `<Buffer 4f 4b>` 
});
client.quit();

这段代码中的client.set()和client.get()无疑都是异步的代码,若是此处用的是client.end()替换掉client.quit()无疑会在命令未能正确处理之前就会于数据库断开从而出现错误,而quit()就会处理完所有命令回复之后才安全的断开与数据库的连接。

Redis中的quit()和end()就相应类比为在node-mysql中的两种与数据库断开的方式end()和destroy()方法。

接下来就是我在写代码的时候自以为quit优雅退出遇到的问题,原始代码如下:

/**
 * 往某一个有序集合里面增加一个成员,如果这个成员存在则将其分数加1,如果不存在则直接加入一个成员;
 * 判断members是否已经在sortedSet中存在了
 * 由于有序集合中不像简单集合中有SISMEMBER直接判断是否存在的命令
 * 这里我使用 ’zscore key memberName‘ 的方式如果返回为null则表明他不存在
 * @param {String} 键的keyName
 * @param {Array}  
 * @param {Function}
 */

redisDB.addToSortedSet = (keyName, args, callback) => {
        let client = redisDB.connect();
        args.forEach((element, index) => {
            let params = [];
            params.push(keyName, element);
            client.zscore(params, (err, results) => {
                if (err) {
                    logger.writeDebug(err);
                    callback(err);
                } else {
                    if (results) { //成员已经存在
                        let params = [];
                        params.push(keyName, 1, element);
                        client.zincrby(params, (err, res) => {
                            if (err) {
                                logger.writeDebug(err);
                                callback(err);
                            } else {
                                callback(null, res);
                            }
                        })

                    } else { //成员不存在直接插入,初始分数为0
                        let params = [];
                        params.push(keyName, 0, element);
                        client.zadd(params, (err, res) => {
                            if (err) {
                                logger.writeDebug(err);
                                callback(err);
                            } else {
                                callback(null, res);
                            }
                        })

                    }
                }
            })
        });
     client.quit();
    }

这段代码主要的结构就是我在建立了node与Redis的连接之后跟了一个对数组的forEach循环,然后再循环体中有嵌套两层的异步回调函数;(Tips:在写这段的时候我是需要去判断数组内的元素在redis的有序集合中是不是已经存在,由于有序集合不像是Set结构那样有一条命令去直接判断元素是否是Set的成员(sismember命令),所以我用了zscore key memberName 如果成员存在则会返回这个成员对应的score,如果这个成员不存在则就会返回null,以此判断是否存在)

这段代码在我测试调用插入两条数据的时候报出了这样的错误:

{ AbortError: 
Stream connection ended and command aborted.
It might have been processed.
  code: 'NR_CLOSED',
  command: 'ZINCRBY',}

说我的连接断了,执行到ZINCRBY的时候命令中断了........

原来“优雅退出”也并不是完全让人省心......(我好想抖段子。。。)

我仔细看了一下报错,我插入了两条数据库里都存在数据,应该到最后走的都是最里层ZINCRBY的那个异步分支,而报错的就是forEach中两条数数据都是执行到ZINCRBY报出得Stream connection ended and command aborted.

当时我的脑子里过的第一个单纯的想法是forEach会不会是个异步的,因为它长得像,而quit()优雅退出的是redis自己的命令回调,并不会管别的异步回调,然而forEach虽然传了一个函数但是它本身并不是异步的,测试代码如下:

let args=[2,3,4];
args.forEach( (element, index)=> {
    console.log(element);
});
console.log(1);

打印结果:

2
3
4
1

并不是想象中的异步的;必然还是要试一下forEach中只有一层异步的情况的,试完问题就必然都对上号了

redisDB.addToSortedSet = (keyName, args, callback) => {
        let client = redisDB.connect();
        args.forEach((element, index) => {
            let params = [];
            params.push(keyName, element);
            client.zscore(params, (err, results) => {
                if (err) {
                    logger.writeDebug(err);
                    callback(err);
                } else {
                    console.log(results);
                }
            })
        });
       client.quit();
    }

这一次执行之后没毛病,传进去有两个元素的args,因为存在所以就很优雅的返回了两个成员的分数,看来优雅的照顾也是有限的,我只负责你一层所有的命令全部给你结果,再嵌套一层就不会管你了:)

遂...

代码改为如下

redisDB.addToSortedSet = (keyName, args, callback) => {
        let client = redisDB.connect();
        args.forEach((element, index) => {
            let params = [];
            params.push(keyName, element);
            client.zscore(params, (err, results) => {
                if (err) {
                    logger.writeDebug(err);
                    callback(err);
                } else {
                    if (results) { //成员已经存在
                        let params = [];
                        params.push(keyName, 1, element);
                        client.zincrby(params, (err, res) => {

                            client.quit();

                            if (err) {
                                logger.writeDebug(err);
                                callback(err);
                            } else {
                                callback(null, res);
                            }
                        })

                    } else { //成员不存在直接插入,初始分数为0
                        let params = [];
                        params.push(keyName, 0, element);
                        client.zadd(params, (err, res) => {

                            client.quit();

                            if (err) {
                                logger.writeDebug(err);
                                callback(err);
                            } else {
                                callback(null, res);
                            }
                        })

                    }
                }
            })
        });
    }

这下没毛病了.......在最里层异步函数的回调里再调用client.quit(),就能保证请求都被处理完了之后再“优雅断开”
(最后很想说,第一次在博客网站上输出,可能还是跟以前在wiki上记的片面的不太一样,以后也会养成经常写博客记录的习惯,因为水平有限表达上可能也有待提高,更希望能够得到大家的批评指正,大家一起在学习路上共同进步!)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • 安全性 设置客户端连接后进行任何其他指令前需要使用的密码。 警告:因为redis 速度相当快,所以在一台比较好的服...
    OzanShareing阅读 1,653评论 1 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,510评论 18 139
  • 1.1 资料 ,最好的入门小册子,可以先于一切文档之前看,免费。 作者Antirez的博客,Antirez维护的R...
    JefferyLcm阅读 17,022评论 1 51
  • linux 启动 redis:cd /usr/local/redis-3.2.0src/redis-server ...
    晏子小七阅读 9,793评论 0 11
  • 想要一个人很奇怪吗?毕业一年后,工作不上不下,生活没着没落。小城市里,女人好像除了结婚别无选择。每当别人问起,你今...
    故乡的云啊阅读 133评论 0 0