牛皮了!Redis 6.0 如何实现大幅度的性能提升!

牛皮了!Redis 6.0 如何实现大幅度的性能提升

 

导读:

Redis可以轻松支撑100k+ QPS,离不开基于Reactor模型的I/O Multiplexing,In-memory操作,以及单线程执行命令避免静态消耗。尽管性能已经能满足大多数应用场景,但是如何继续在迭代中继续优化,以及在多核时代利用上多线程的优势,也是大家关注的重点。我们知道性能优化在系统资源层面可以从I/O以及CPU上入手,对于Redis而言,其功能不过度依赖CPU计算能力,即不是CPU密集型的应用,而In-memory的操作也绕开了通常会拖慢性能的磁盘I/O,所以在Redis 6.0版本中,将从网络I/O入手,引入Threaded I/O辅助读写,在一些场景下实现了大幅度的性能提升。本文将介绍Redis的事件模型,分析Threaded I/O是如何帮助提升性能,以及其实现的原理。

Introduction

Redis从6.0版本开始引入了Threaded I/O,目的是为了提升执行命令前后的网络I/O性能。本文会先从Redis的主流程开始分析,讲解网络I/O发生在哪里,以及现有的网络I/O模型,然后介绍Threaded I/O的新模型、实现以及生效场景,最后会进行场景测试,对比Threaded I/O关闭与开启,以及启用Threaded I/O与在单实例上搭建集群的性能差异。如果你已经了解过Redis的循环流程,可以直接跳至浪Threaded I/O相关 的部分;如果你只关心新功能的实际提升,可以跳至 性能测试 部分查看。

Redis是如何运行的

事件循环

main

Redis的入口位于server.c下, main() 方法流程如图所示。

牛皮了!Redis 6.0 如何实现大幅度的性能提升

 

在意main() 方法中Redis首先需要做的是 初始化各种库以及服务配置 。具体举例:

  • crc64_init() 会初始化一个crc校验用的Lookup Table
  • getRandomBytes() 为 hashseed 填充随机元素作为初始化值,用作哈希表的seed
  • ...
  • initServerConfig() 中执行了大量对 server 对象属性的初始化操作:
    • 初始化 server.runid ,如 16e05f486b8d41e79593a35c8b96edaff101c194
    • 获取当前的时区信息,存放至 server.timezone 中
    • 初始化 server.next_client_id 值,使得连接进来的客户端id从1开始自增
    • ...
  • ACLInit() 是对Redis 6.0新增的ACL系统的初始化操作,包括初始化用户列表、ACL日志、默认用户等信息
  • 通过 moduleInitModulesSystem() 和 tlsInit() 初始化模块系统和SSL等
  • ...

初始化结束后,开始 读取用户的启动参数 ,和大多数配置加载过程类似,Redis也通过字符串匹配等分析用户输入的 argc 和 argv[] ,这个过程中可能会发生:

  • 获取到配置文件路径,修改 server.configfile 的值,后续用于加载配置文件
  • 获取到启动选项参数,如 loadmodule 和对应的Module文件路径,保存至 options 变量中

解析完参数之后,执行 loadServerConfig() , 读取配置文件并与命令行参数options的内容进行合并 ,组成一个 config 变量,并且逐个将name和value设置进configs列表中。对于每个config,有对应的switch-case的代码,例如对于 loadmodule ,会执行 queueLoadModule() 方法,以完成真正的配置加载:

...
        } else if (!strcasecmp(argv[0],"logfile") && argc == 2) {   
            ...         } else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
            queueLoadModule(argv[1],&argv[2],argc-2);
        } else if (!strcasecmp(argv[0],"sentinel")) {
...

回到 main 方法的流程,Redis会开始打印启动的日志,执行 initServer() 方法,服务根据配置项,继续 为 server 对象初始化内容 ,例如:

  • 创建事件循环结构体 aeEventLoop (定义在ae.h),赋值给 server.el
  • 根据配置的db数目,分配大小为 sizeof(redisDb) * dbnum 的内存空间, server.db 保存这块空间的地址指针
  • 每个db都是一个redisDb结构,将这个结构中的保存key、保存过期时间等的字典初始化为空dict
  • ...

此后就是一些根据不同运行模式的初始化,例如常规模式运行时会记录常规日志、加载磁盘持久化的数据;而在sentinel模式运行时记录哨兵日志,不加载数据等。

在所有准备操作都完成后, Redis开始陷入 aeMain() 的事件循环,在这个循环中会不断执行 aeProcessEvents() 处理发生的各种事件,直到Redis结束退出 。

两种事件

Redis中存在有两种类型的事件: 时间事件 、 文件事件 。

时间事件也就是到了一定时间会发生的事件,在Redis中它们被记录成一个链表,每次创建新的时间事件的时候,都会在链表头部插入一个 aeTimeEvent 节点,其中保存了该事件会在何时发生,需要调用什么样的方法处理。遍历整个链表我们可以知道离最近要发生的时间事件还有多久,因为链表里面的节点按照自增id顺序排列,而在发生时间的维度上是乱序的。

牛皮了!Redis 6.0 如何实现大幅度的性能提升

 

文件事件可以看作I/O引起的事件,客户端发送命令会让服务端产生一个读I/O,对应一个读事件;同样当客户端等待服务端消息的时候需要变得可写,让服务端写入内容,因此会对应一个写事件。 AE_READABLE 事件会在客户端

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值