前言
如果一个 Node 应用有多台服务器或多个进程在跑每个进程都拥有自己的内存空间各个进程之间的数据共享就显得非常重要。
使用数据库是一个解决数据共享的方案但一些临时性、高并发的数据并不太适合直接写入数据库比如 session。
引入 Redis 可以解决数据共享的问题也因为 Redis 是基于内存存储的特点有着非常高的性能可以大大降低数据库读写的压力提升应用的整体性能。
Redis 还可以用来缓存复杂的数据库查询结果做自增长统计暂存用户操作状态等功能。
最近负责的node项目在高并发的情况下性能表现非常的差rt基本会在7 80ms甚至100ms以上由于对外提供了dubbo接口所以经常导致上游应用和自己的dubbo线程池耗尽所以花了一点时间排查了一番才发现原来自己的node功力还有很长的路要走啊~之后node的文章可能会越来越多~
最蠢的方式
先来看看我最早是怎么用的呢
for(let i = 0; i < params.length; i++) {
redisKey = getKey(params.id);
let value = await redis.exec('get', redisKey);
}
这就是我最原始的调用方法就是在for循环里不断的去await结果的请求这样的结果就是每一个请求都需要等待上一个请求完成再去执行只要在高流量的时候有一部分请求rt很高就会引起雪崩的反应。
使用Promise.all优化请求
经过了一阵谷歌之后我发现可以通过Promise.all的形式来进行请求链路的优化
for(let i = 0; i < params.length; i++) {
redisKey = getKey(params.id);
arr.push(redis.exec('get', redisKey))
}
await Promise.all(arr);
上面的第一种方式被我司的node大神严重吐槽了10分钟然后告诉我使用Promise.all的方式可以很有效的优化这种连续的网络请求我赶紧将代码改完并上线。
自信满满的上线之后迎来的确实现实无情的打击在高流量的时刻报警依然不断我一边和领导说“没事我再看看”心里一边想着辞职报告该怎么写。
redis的正确使用姿势
在继续经过了一系列的谷歌之后我才发现原来的是对redis的理解太浅了针对于业务上的需求我不假思索的只知道使用最简单的set和get而redis对于set和get这样的命令是一条命令一个tcp请求在业务场景上确实不太合理于是我使用谷歌告诉我的pipeline机制去改造现有的get请求
let batch = await RedisClient.getClient().batch();
for(let i = 0; i < params.length; i++) {
batch.get(redisKey);
}
batch.exec();
对于pipeline机制大家可以看这篇文章。在使用pipeline之后便秘一下就通畅了再也没有报警过终于可以不用辞职了。
再后面的日子里我觉得认真的研究一下redis这个东西保证让上面的问题不再发生于是我发现其实还是有一种更加简单的方案的那就是使用mget
for(let i = 0; i < params.length; i++) {
redisKey = getKey(params.id);
arr.push(redisKey);
}
let value = await redis.exec('mget', arr);
使用mget进行批量的查询这是redis里比较常见的一种方式了~
总结一下
在对以上四种方式进行了对比之后得出了数据上的结论
在一个200次的循环中调用redis请求第一种最蠢的方案大概是8000ms左右第二种Promise.all的方案大概在2000ms左右而第三和第四种方案大概只需要几十ms就能完成这真的是质的飞跃啊。
这个线上血淋淋的案例让我决定真的要好好的研究一下redis不能再轻视它导致犯错。