手机版

提高node.js中redis性能的问题及解决方案

时间:2021-08-29 来源:互联网 编辑:宝哥软件园 浏览:

问题首先出现了

基于node.js开发的业务系统提供了dubbo服务,可以对第三方进行缓存和查询,设置多个业务数据,聚合操作结果。当QPS达到800(两台虚拟机,每台机器有4个4Core8G4node进程)时,监控平台出现大量慢rt警告,平均接口响应达到60 ms,请求告警率达到80%。

为了找到导致服务吞吐量低的罪魁祸首,业务人员在请求日志中做了所有的查询缓存操作,结果显示每个请求的查询缓存时间在50-100毫秒之间跳跃。查询redis-server的监控数据后发现,服务器端没有慢查询,服务器端的处理时间在整个监控区间内徘徊在40us左右,消除了Redis-server处理能力不足的原因;

通过登录内网机器测试相应redis服务器机器的端到端时延,内部局域网的带宽、时延、抖动都足够正常,不是造成这个问题的原因。

因此,错误的原因在于调用redis客户端的业务代码和redis客户端的I/O性能。

文中提到的node redis客户端采用了基于node-redis封装的双方包,因此问题排查也是基于node-redis模块。

瓶颈在哪里

为了在本地模拟在线环境的并发性,我们可以做一个不太严格的测试:

async()={ let DD=date . now())let arr=[]for(let I=0;i200i ){ arr.push(new Promise((res,rej)={ let HR time=process . HR time();client.send_command('get ',['key'],function(e,r){ let diff=process . HR time(HR time);让成本=(diff[0]* NS _ PER _ SEC diff[1])/1000000;console . log(` final : $ { cost } ms `)RES();});}));} wait promise . all(arr)console . log(' ops/sec : ',200*1000/(Date.now() - dd),date . now()-DD);}您会发现每个请求的rt都会比上一个请求的rt大

最后一次请求的rt实际上达到了257 ms!虽然像示例代码这样在一个节点的单个进程中并发执行200个get请求是非常罕见和愚蠢的(示例代码的优化将在下一节中介绍),但是对于这个示例,必须找到请求延迟增加的原因。

为此,继续分析,redis客户端采用单连接方式,底层采用无阻塞网络I/O,socket.recv()是通过在节点级监控socket的数据事件来完成的,所以先分析redis client的读取性能:

上图中每个日志的含义如下:

-数据事件触发时间:套接字数据事件触发时间-数据事件开始于预防事件3360数据事件时间间隔自上次触发-数据事件执行时间(毫秒):此事件处理程序的执行时间

上图只截取了初始请求日志,发现第六次触发数据事件时,距离上一次触发事件还有35ms,后续请求中还会重复

由于这种现象,当有200个并发查询请求时,每个请求的rt会增加,一些响应之间的间隔会是30 ms。

显而易见的问题是redis-server发送的响应不是一个数据块,而是多个数据块,导致触发socket的数据事件太多,数据事件抖动太大,导致响应之间突然变化30毫秒(数据事件不能同时触发两次,执行完每个数据事件处理程序后才能触发下一个数据事件);当然,它也可能与套接字写入(即发送请求)有关,例如缓存请求。若要继续探测,请监视与套接字写入相关的接口**_write()**并记录每次写入套接字数据时距上次写入的时间间隔:

因此,当使用redis-client发送请求时,write方法不是瓶颈。

使用同样的方法,对socket的push()进行监控(触发socket的数据事件),发现socket的数据到达间隔非常抖动:

因此,redis-client并发请求导致的响应rt抖动与单个连接下响应数据到达本地的时间有关,这可能与底层libuv的缓存策略有关(我没有进一步探究)。

在一个节点实例中,当它通过单个连接与redis服务器通信时,在高并发下会有排队等待响应的情况,可能会出现响应rt的雪崩效应(如上面的演示所示)。因此,有必要尽可能减少或缓存客户端请求的数量,并分批发送。

调整

1.流水线(涉及写入模式和时序)2。脚本

对于管道模式,默认情况下支持redis服务器。通俗地说,pipeline可以将一系列请求组合起来,一次性发送出去,一次性得到这些请求对应的结果。因此,这种方法可以有效地减少响应的数量,从而减少套接字触发的数据事件的数量,并尽快获得响应者。

需要强调的是,在node中,通过底层socket的**_writev**一次发送多个redis命令,而_writev也称为聚合写入,支持通过一次系统调用将不同缓冲区的多个数据写入目标流,因此性能比一次从单个缓冲区写入单个数据要好得多。在节点的可写对象中,有科克和非科克方法,通过这两种方法,多段数据可以缓存在节点写流中,并通过_writev发送一次。

关于_writev的数据结构

获取数据后,redis根据resp协议解析命令集并缓存在队列中,直到收到exec命令,开始批量执行命令集,并将所有的命令执行结果转换为数组返回给redis collection。这样,一次写一次读就可以实现高性能的I/O。

async()={ let DD=date . now()}让batch=wait client . batch();for(设I=0;i200I){ batch . get(' vdWeex _ com . koudai . weidian . buyer _ 1 ');}让rt=wait batch . exec();process . exit();}对于script方法,redis客户端传入script命令,在服务器端执行script逻辑,批量执行命令,并返回结果。也是写读一次。

收获

1.默认情况下,节点套接字由writev set写入。2.独立的批处理请求由pipeline3.evalscript. 4解决。redis的高性能体现在服务器的处理能力上,但瓶颈往往出现在客户端。因此,增强客户端的I/O能力,将多个客户端并行化是高并发的解决方案。

版权声明:提高node.js中redis性能的问题及解决方案是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。