Redis6.0 多线程无锁I/O设计精髓
mhr18 2024-11-28 08:44 17 浏览 0 评论
因为我们的主题是多线程,所以不会过多涉及单线程。
推荐视频:
为什么C/C++程序员一定要阅读redis源码?腾讯面试教你做人
谈redis,memcached,nginx三大网络服务器模型的对比
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
1. 单线程模式-并非CPU瓶颈
咱们都知道单线程的程序是没法利用服务器的多核CPU的,那么早期的Redis为何还要使用单线程呢?咱们不妨先看一下Redis官方给出的回答:
核心意思是:CPU并非制约Redis性能表现的瓶颈所在,更多状况下是受到内存大小和网络I/O的限制,因此Redis核心网络模型使用单线程并无什么问题,若是你想要使用服务的多核CPU,能够在一台服务器上启动多个实例或者采用分片集群的方式。
咱们知道Redis的I/O线程除了在等待事件,其它的事件都是非阻塞的,没有浪费任何的CPU时间,这也是Redis可以提供高性能服务的缘由。
2. 多线程网络模型-多线程Reactor模式
Redis在 6.0 版本以后正式在核心网络模型中引入了多线程,它的工做模式是这样的:
区别于单 Reactor 模式,这种模式再也不是单线程的事件循环,而是有多个线程(Sub Reactors)各自维护一个独立的事件循环,由 Main Reactor 负责接收新链接并分发给 Sub Reactors 去独立处理,最后 Sub Reactors 回写响应给客户端。
Multiple Reactors 模式一般也能够等同于 Master-Workers 模式,好比Nginx等就是采用这种多线程模型,虽然不一样的项目实现细节略有区别,但整体来讲模式是一致的。
2.1 多线程工作流程
- Redis 服务器启动,开启主线程事件循环(Event Loop),注册 acceptTcpHandler 链接应答处理器到用户配置的监听端口对应的文件描述符,等待新链接到来;
- 客户端和服务端创建网络链接;
- acceptTcpHandler 被调用,主线程使用 AE 的 API 将 readQueryFromClient 命令读取处理器绑定到新链接对应的文件描述符上,并初始化一个 client 绑定这个客户端链接;
- 客户端发送请求命令,触发读就绪事件,服务端主线程不会经过 socket 去读取客户端的请求命令,而是先将 client 放入一个 LIFO 队列 clients_pending_read;
- 在事件循环(Event Loop)中,主线程执行 beforeSleep -->handleClientsWithPendingReadsUsingThreads,利用 Round-Robin 轮询负载均衡策略,把 clients_pending_read队列中的链接均匀地分配给 I/O 线程各自的本地 FIFO 任务队列 io_threads_list[id] 和主线程本身,并且用io_threads_pending[id]来记录每个线程的分配任务数量,因为线程需要读取这个io_threads_pending[id]这个数量来消费任务,消费完成会初始化为0。I/O 线程经过 socket 读取客户端的请求命令(是通过io_threads_op这个变量来判断是读(IO_THREADS_OP_READ)还是写(IO_THREADS_OP_WRITE), 这里是io_threads_op == IO_THREADS_OP_READ),存入 client->querybuf 并解析第一个命令,但不执行命令,主线程忙轮询,等待全部 I/O 线程完成读取任务;
- 主线程和全部 I/O 线程都完成了读取任务(通过遍历io_threads_pending[id],把每个线程的分配任务数量累加起来如果和等于0代表多线程已经消费完了任务),主线程结束忙轮询,遍历 clients_pending_read 队列,执行全部客户端链接的请求命令,先调用 processCommandAndResetClient 执行第一条已经解析好的命令,而后调用 processInputBuffer 解析并执行客户端链接的全部命令,在其中使用 processInlineBuffer 或者 processMultibulkBuffer 根据 Redis 协议解析命令,最后调用 processCommand 执行命令;
- 根据请求命令的类型(SET, GET, DEL, EXEC 等),分配相应的命令执行器去执行,最后调用 addReply 函数族的一系列函数将响应数据写入到对应 client 的写出缓冲区:client->buf 或者 client->reply ,client->buf 是首选的写出缓冲区,固定大小 16KB,通常来讲能够缓冲足够多的响应数据,可是若是客户端在时间窗口内须要响应的数据很是大,那么则会自动切换到 client->reply 链表上去,使用链表理论上可以保存无限大的数据(受限于机器的物理内存),最后把 client 添加进一个 LIFO 队列 clients_pending_write;
- 在事件循环(Event Loop)中,主线程执行 beforeSleep --> handleClientsWithPendingWritesUsingThreads,利用 Round-Robin 轮询负载均衡策略,把 clients_pending_write 队列中的链接均匀地分配给 I/O 线程各自的本地 FIFO 任务队列 io_threads_list[id] 和主线程本身,并且用io_threads_pending[id]来记录每个线程的分配任务数量,因为线程需要读取这个io_threads_pending[id]这个数量来消费任务,消费完成会置为0。I/O 线程经过调用 writeToClient(io_threads_op == IO_THREADS_OP_WRITE)把 client 的写出缓冲区里的数据回写到客户端,主线程忙轮询,等待全部 I/O 线程完成写出任务;
- 主线程和全部 I/O 线程都完成了写出任务(通过遍历io_threads_pending[id],把每个线程的分配任务数量累加起来如果和等于0代表多线程已经消费完了任务), 主线程结束忙轮询,遍历 clients_pending_write 队列,若是 client 的写出缓冲区还有数据遗留,则注册 sendReplyToClient 到该链接的写就绪事件,等待客户端可写时在事件循环中再继续回写残余的响应数据。
这里大部分逻辑和以前的单线程模型是一致的,变更的地方仅仅是把读取客户端请求命令和回写响应数据的逻辑异步化了,交给 I/O 线程去完成,这里须要特别注意的一点是:I/O 线程仅仅是读取和解析客户端命令而不会真正去执行命令,客户端命令的执行最终仍是要回到主线程上完成。
2.2 图解详细流程
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg,大厂面试题 等)
3. I/O多线程lock-free(无锁设计)
主线程和 I/O 线程之间共享的变量有三个:io_threads_pending 计数器、io_threads_op I/O 标识符和 io_threads_list 线程本地任务队列。
- io_threads_pending
- 统计每个线程分配任务的数量,主线程在分配的时候,子线程是被换新的,它一直在执行100w次的CPU空转即自旋操作;当主线程把任务分配好之后,子线程会判断io_threads_pending[id]不为0就去消费任务,消费完成会置为0。
- io_threads_op
- IO_THREADS_OP_WRITE socket可写
- IO_THREADS_OP_READ socket可读
- io_threads_list
- 线程本地任务队列(链表)
lock-free无锁设计核心:
1. 原子变量,不需要加锁保护
- io_threads_pending变量在声明的时候加上了_Atomic限定符:_Atomic unsigned long; _Atomic是C11标准中引入的原子操作:被_Atomic修饰的变量被认为是原子变量,对原子变量的操作是不可分割的(Atomicity),且操作结果对其他线程可见,执行的顺序也不能被重排。所以io_threads_pending是属于线程安全的变量。
2. 交错访问来规避共享数据竞争
- io_threads_op 和 io_threads_list 这两个变量则是经过控制主线程和 I/O 线程交错访问来规避共享数据竞争问题。
- I/O 线程启动以后会经过忙轮询和锁休眠等待主线程的信号,在这以前它不会去访问本身的本地任务队列 io_threads_list[id],而主线程会在分配完全部任务到各个 I/O 线程的本地队列以后才去唤醒 I/O 线程开始工做,而且主线程以后在 I/O 线程运行期间只会访问本身的本地任务队列 io_threads_list[0] 而不会再去访问 I/O 线程的本地队列,这也就保证了主线程永远会在 I/O 线程以前访问 io_threads_list 而且以后再也不访问,保证了交错访问。
- io_threads_op 同理,主线程会在唤醒 I/O 线程以前先设置好 io_threads_op 的值,而且在 I/O 线程运行期间不会再去访问这个变量,这也就变相保证了原子性。在源码src/server.h中 : extern int io_threads_op;
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)