Redis批量操作详解及性能分析

2023-05-16

通过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。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis批量操作详解及性能分析 的相关文章

  • Spring Data Redis JedisConnectionException:流意外结束

    雷迪斯3 0 5Spring数据Redis 1 3 6绝地武士2 6 3 我们的 Web 应用程序通过 pub sub 从 Redis 接收数据 还以键 值对的形式在 Redis 上执行数据读 写 读 写发生在监听线程 独立监控线程和htt
  • 如何将 ActionController::Live 与 Resque + Redis 一起使用(用于聊天应用程序)

    我正在尝试为我的 Rails 应用程序构建聊天功能 我在用ActionController Live Puma Resque Redis为了这 所以基本上在这种情况下 redissubscribe方法正在后台运行 使用resque 到目前为
  • 通过 StackExchange.Redis 连接到 Redis Servier

    我尝试使用以下方法制作一个测试项目Redis https redis io服务器 通过 Virtual Box 安装在 Linux Ubuntu 虚拟机上 Linux 机器通过 Virtual Box 的桥接适配器与本地网络连接 Virtu
  • 在 aws-elasticache 上使用 memcached 或 Redis

    我正在 AWS 上开发一个应用程序 并使用 AWS elasticache 进行缓存 我对使用 memcached 或 redis 感到困惑 我阅读了有关 redis 3 0 2 更新以及它现在如何等同于 memchached 的文章 ht
  • Redis SYNC 套接字上的错误情况:连接被拒绝

    在我的 django 应用程序中使用 celery 和 redis 一切都工作正常 直到我遇到了问题 redis 文件的位置已更改 redis 无法访问它们 经过查找 原来这是由于网络随机攻击造成的 需要添加confg 我添加文件后 一段时
  • 使用 Redis 命令 incr 和 expire 时的竞争条件

    根据redis文档 http redis io commands incr http redis io commands incr 在段落模式 速率限制器 2 较短的版本代码 value INCR ip IF value 1 THEN EX
  • 有没有办法在 ruby​​ 中重新定义 []=+

    我正在尝试编写一个简单的 DSL 针对 Redis 并且我想自己定义 I have def key val redis zadd name val key end 我想定义 def key val redis zincrby name va
  • 如何设置和获取Redis中存储的对象?

    我试图在 redis 中存储一个对象 当我获取该对象时 它似乎不起作用 I tried u User new u name blankman redis set test u x redis get test x name error 我想
  • 使用 Sentinels 升级 Redis 的最佳实践?

    我有 3 个 Redis 节点 由 3 个哨兵监视 我进行了搜索 文档似乎不清楚如何最好地升级此类配置 我目前使用的是 3 0 6 版本 我想升级到最新的 5 0 5 我对这方面的程序有几个疑问 升级两个大版本可以吗 我在我们的暂存环境中执
  • redis - 使用哈希

    我正在使用 redis 为我的 Web 应用程序实现社交流和通知系统 我是 redis 的新手 我对哈希值及其效率有一些疑问 我读过这篇很棒的文章Instagram 帖子 http instagram engineering tumblr
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • 在 Redis 上为 Django 和 Express.js 应用程序共享会话存储

    我想创建一个包含一些登录用户的 Django 应用程序 另一方面 由于我想要一些实时功能 所以我想使用 Express js 应用程序 现在的问题是 我不希望身份不明的用户访问 Express js 应用程序的日期 因此 我必须在 Expr
  • Redis+Docker+Django - 错误 111 连接被拒绝

    我正在尝试使用 Redis 作为使用 Docker Compose 的 Django 项目的 Celery 代理 我无法弄清楚我到底做错了什么 但尽管控制台日志消息告诉我 Redis 正在运行并接受连接 事实上 当我这样做时 docker
  • Amazon Elasticache Redis 集群 - 无法获取端点

    我需要获取 Amazon Elasticache 中 Redis 集群的终端节点 以下代码适用于 Memcached 集群 但不适用于 Redis import com amazonaws auth AWSCredentials impor
  • 创建 C++ Redis 模块 - “不导出 RedisModule_OnLoad() 符号”

    我在加载 Redis 模块时遇到一些问题 我只是复制来自的示例https redis io topics modules intro https redis io topics modules intro 但我把它剥下来了 include
  • 在 Spring 4 中干掉通用的 RedisTemplate

    我读到你可以拥有 Autowired从 Spring 4 开始泛型 这太棒了 我有一个摘要RedisService我想参加的课程 Autowired一个通用的 RestTemplate 如下所示 public abstract class
  • 如何使用redis发布/订阅

    目前我正在使用node js和redis来构建应用程序 我使用redis的原因是因为发布 订阅功能 该应用程序只是在用户进入用户或离开房间时通知经理 function publishMsg channel mssage redisClien
  • 使用 Redis 中的键

    我是 Redis 和键值数据库的新手 你能告诉我如何在redis中正确实现这种关系方法吗 我有一个关系表 其中两个键对应一个值 master id slave id 价值 Example 主站 ID 从属ID 价值 1 1 值1 2 1 值
  • redis能完全取代mysql吗?

    简单的问题 我是否可以使用 redis 而不是 mysql 来处理各种 Web 应用程序 社交网络 地理位置服务等 IT 领域没有什么是不可能的 但有些事情可能会变得极其复杂 将键值存储用于全文搜索之类的事情可能会非常痛苦 另外 据我所知
  • memcache、redis 和 ehcache 作为分布式缓存框架的比较 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我需要做出的决定之一是在我的系统中使用什么缓存框架 有这么多可供选择 我目前正在研究 redis ehcache 和 memcached

随机推荐