通过mget批量执行指令可以节约网络连接和数据传输开销,在高并发场景下可以节约大量系统资源。本文中,我们更进一步,比较一下redis提供的几种批量执行指令的性能。

1. 为什么需要批量执行redis指令

众所周知,Redis协议采取的是客户端-服务器方式,即在一次round trip中,客户端发送一条指令,服务端解析指令并执行,然后向客户端返回结果。这是一种典型的tcp交互方式。

粗略的分,客户端发起一次Redis请求主要有如下开销:

  • socket IO导致的上下文切换开销 (严重读写系统开销

熟悉OS/Linux的童鞋都知道,一次redis请求在客户端和服务端分别至少会存在一次read()和一次write(),作为系统调用,read/write的成本高于普通的函数调用,因此,在单个命令重复调用场景下,大量的read/write系统调用会产生明显的系统开销。

  • 指令执行开销 (轻微指令开销

Redis采用C实现,使用了轻量级的hash表、skipList跳表等数据结构实现了高效的缓存。因此,单条执行大多数指令的成本非常低。因此,相对而言,IO的开销显得更加无法忽略。

  • (高并发下)资源竞争和系统调度调度开销 (Redis竞争抖动

客户端的影响非常明显。在高压力下,如果采用循环(loop)方式调用多次指令来完成某个服务请求,那么在高并发下,多个请求会在多个线程中同时竞争redis连接资源多次,导致连接池压力增加,线程上下文切换更加频发,最终会导致请求RTT(round-trip time)急剧恶化。如果每个请求只抢占一次redis连接并通过批量执行的方式一次处理多个请求,则单次请求的RTT会有显著提升。

在服务端,因为我们通常将redis绑定到CPU(不管是通过物理机还是通过docker),因此一般而言不存在系统调度/资源竞争的开销。但是由于redis对QPS敏感,如果因为客户端使用不合理而造成QPS放大效应,则redis可能更早触及性能瓶颈而导致系统响应严重下降。

笔者曾经在一次性能调优中发现,每次服务请求访问redis次数高达数十次,使得redis请求次数达到服务QPS的数十倍,触发了redis服务器的极限(大概5~10万QPS)而导致服务性能低下,多个请求对redis连接池进行了激烈竞争,并且由于redis响应速度的下降导致大量线程在获取连接处阻塞并频繁进行线程切换。在改进实现采用了批量指令处理后,服务性能瞬间达到了数十倍的提升。因此,如果每次服务掉用需要触发多次redis请求,合理地适用批量执行技术,可以使系统运行更加有效,数据吞吐得到明显提升。

2. redis批量指令介绍

2.1. 批量命令即redis对应的命令

  • mget(适用于string类型)
  • mset(适用于string类型)
  • hmget(适用于hash类型)
  • hmset(适用于hash类型)

严格来说上述命令不属于批量操作,而是在一个指令中处理多个key。

  • 优势:性能优异,因为是单条指令操作,因此性能略优于其他批量操作指令
  • 劣势:批量命令不保证原子性,存在部分成功部分失败的情况,需要应用程序解析返回的结果并做相应处理   批量命令在key数目巨大时存在RRT与key数目成比例放大的性能衰减,会导致单实例响应性能(RRT)严重下降

2.2 管道 pipeline

管道(pipelining)方式意味着客户端可以在一次请求中发送多个命令。

  • 优势:
  1. 通过管道,可以将多个redis指令聚合到一个redis请求中批量执行
  2. 可以使用各种redis命令,使用更灵活
  3. 客户端一般会将命令打包,并控制每个包的大小,在执行大量命令的场景中,可以有效提升运行效率
  4. 由于所有命令被分批次发送到服务器端执行,因此相比较事务类型的操作先逐批发送,再一次执行(或取消),管道拥有微弱的性能优势
  • 劣势:
  1. 没有任何事务保证,其他client的命令可能会在本pipeline的中间被执行

2.3 事务操作

事务(Transactions)操作允许在一步中执行一组redis操作,并对这一组redis命令有如下保证:

  1. 同一个事务中的所有命令会被串行地逐一执行。不可能出现有任何来自其他client的命令在这组命令中间被执行。
  2. 单个事务的所有命令,或者被全部执行,或者一个也不会被执行,因此事务保证了redis操作的原子性。命令EXEC触发事务中所有命令的执行,因此如果一个client在事务上下文中丢失了连接,那么不会有任何一条命令被执行;相反如果client已经调用了EXEC,那么所有命令都会被执行。

  3. 当使用append-only文件时,Redis保证仅使用一个write(2)系统调用来将事务结果写入磁盘。然而如果Redis server崩溃或者被系统管理员使用hard方式kill了进程,那么还是有可能只写入了部分操作。Redis在重启时可以检测到这一问题,并以error退出。这时,可以使用redis-check-aof工具来对append-only文件进行修复,它将会删除部分写入的事务这样server就可以启动了。

  • 优势:
  1. 事务的执行具备原子性,即全部被执行或全部不执行,并且在持久化时也具备原子性
  2. 可以使用WATCH提供的乐观锁机制保证命令执行的排他性
  • 劣势:
  1. 事务的所有命令会分批发送给redis实例,redis返回+QUEUED,表示命令已入列,但是不会执行任何命令。在收到EXEC命令时,一次执行本事务的所有命令。因此事务的性能略低于pipeline,但是相差不多。

  2. 在keys竞争激烈时,WATCH提供的乐观锁由于竞争过多而性能低下,应该尽量避免。

2.4 基于管道的事务

在Redis中,管道是通过RESP,即redis协议来实现的,它允许在一个消息包中按照指定格式传递多个命令。而事务是通过命令实现的,因此管道和事务之间并不冲突,事务可以承载与管道之上。在某些场景,需要在一次请求处理中发起多次事务的场景下,通过引入管道,可以获得略高于单独执行多次事务的性能,但是两者的差距非常小,小到可以忽略。

3. 压测用例分析

针对上述4种批量操作,设计如下case:

  • 条件:在本地单机redis中创建1,000,000对key-value,key长8字节,value长5字节
  • 测试过程:
  1. 使用set/mset/pipeline/transaction/transaction in pipeline这五种方式分别重新设置所有key的值,记录各自的运行时长
  2. 使用get/mget/pipeline/transaction/transaction in pipeline这五种方式分别遍历所有key的值,记录各自的运行时长

单位:ms

3.1 SET性能压测结果

3.2 GET性能压测结果

3.3 结论

从上述测试结果中可以看出,不同的处理方式,最终性能曲线基本一致。

  • mset性能最好,吞吐量最高,因为mset是作为单条命令执行,在命令解析和执行上都更有效率
  • pipeline好于transaction in pipeline,因为事务会导致命令入列和出列会稍许浪费cpu时间
  • transaction in pipeline微弱领先于transaction,但是几乎没有区别,可以理解为pipeline在命令传输上更有效率。
  • 总得来说,在批量模式下,四种操作都比普通的get/set性能上有几大的提升。
  • 在当前生产环境中使用较多的Redis Cluster环境中,上述四种批量操作的使用场景都比较有限,其中transaction不支持,pipeline建议仅用于单slot且目前支持的客户端很少,mget/mset也仅仅可以操作于单slot中的key。

Redis批量操作详解及性能分析相关推荐

  1. UIWebView、WKWebView使用详解及性能分析

    一.整体介绍 UIWebView自iOS2就有,WKWebView从iOS8才有,毫无疑问WKWebView将逐步取代笨重的UIWebView.通过简单的测试即可发现UIWebView占用过多内存,且 ...

  2. mysql show profile详解_SQL 性能分析利器 show profile

    本文首发个人公众号<andyqian>, 期待你的关注- 前言 在之前的文章中,我们提到过一些慢SQL优化的步骤.其中就包括:使用 explain 关键字来查看执行计划,是否命中索引. 通 ...

  3. Mysql进阶优化篇01——四万字详解数据库性能分析工具(深入、全面、详细,收藏备用)

    前 言 ?? 作者简介:,长跑型选手,立志坚持写10年博客,专注于java后端 ?? 专栏简介:mysql进阶,主要讲解mysql数据库进阶知识,包括索引.数据库调优.分库分表等 ?? 文章简介:本文 ...

  4. NSLog使用详解与性能分析

    NSLog 使用 在XCode做开发调试时往往需要打印一些调试信息做debug用 NSLog 性能问题 它的运行会占用时间和设备资源.当打印信息的地方多了之后在模拟器上跑可能不会有什么问题,因为模拟器 ...

  5. vmstat命令详解——linux性能分析

    版本信息: procps version 3.2.8 基本使用: vmstat [-a] [-n] [-t] [-S unit] [delay [ count]] [-a]:显示活跃和非活跃内存 [- ...

  6. 怎么往integer型数组添加数据_用户日活月活怎么统计 - Redis HyperLogLog 详解

    HyperLogLog 是一种概率数据结构,用来估算数据的基数.数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID. 基数就是指一个集合中不同值的数目,比如 a, b, c, d 的 ...

  7. redis 数据类型详解 以及 redis适用场景场合

    redis 数据类型详解 以及 redis适用场景场合 1. MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访 ...

  8. redis数据结构详解之Hash(四)

    原文:redis数据结构详解之Hash(四) 序言 Hash数据结构累似c#中的dictionary,大家对数组应该比较了解,数组是通过索引快速定位到指定元素的,无论是访问数组的第一个元素还是最后一个 ...

  9. 并发编程五:java并发线程池底层原理详解和源码分析

    文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...

最新文章

  1. powerdesigner生成数据库文档
  2. JS循环绑定对象或变量
  3. 在maven引入一个maven仓库中不存在的jar,安装本地底仓库
  4. Gblfy 专栏设立服务大家,共享资源
  5. 解决“在eclipse中配置Tomcat时,出现Cannot create a server using the selected type的错误”的问题...
  6. JSON——JavaScript 中的使用
  7. 5G协议标准化及进展
  8. java excel导入导出案例
  9. qpsk频谱图matlab,QPSK调制得出的调制信号频谱波形多了一个点频信号
  10. 【问题求教】mapgis67文件转换失败
  11. JAVA调用WebService的三种方法
  12. 深信服技术认证之容灾与备份(二)
  13. JAVA 清理垃圾文件简单实现
  14. Minecraft Server 搭建手账
  15. 【Unity开发小技巧】Unity日志输出存储
  16. ios 下拉菜单Menu
  17. 软件工程概念总结-期末重点-(简单中文+英文关键词)-第一部分软件过程(第1-6章)-罗杰S普莱斯曼
  18. 仿QQ好友列表,QListWidget!
  19. 任务调度 的常用的基本方式
  20. 使用iTEXT生成PDF

热门文章

  1. LINUX下软件包的安装与使用
  2. Nginx技巧:灵活的server_name,Nginx配置一个服务器多个站点 和 一个站点多个二级域名...
  3. Bootstrap – 1.认识
  4. ExtJs 3 自定义combotree
  5. jQuery 2.0.3 源码分析 事件体系结构
  6. ACL在路由器上设置例子
  7. jsp简单练习-简单的下拉表单
  8. Spring常问的面试
  9. 授于某个用户有写作业和调度作业的权限
  10. AutoMapper入门使用