本文主要是介绍牛皮了!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() 方法流程如图所示。
在意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顺序排列,而在发生时间的维度上是乱序的。
文件事件可以看作I/O引起的事件,客户端发送命令会让服务端产生一个读I/O,对应一个读事件;同样当客户端等待服务端消息的时候需要变得可写,让服务端写入内容,因此会对应一个写事件。 AE_READABLE 事件会在客户
这篇关于牛皮了!Redis 6.0 如何实现大幅度的性能提升!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!