文章目录
Redis学习笔记-高性能IO模型&Redis6.0多线程
前面的文章简单介绍了 Redis
的底层数据结构,合理地使用底层数据结构可以提升 Redis
读写速度,而 Redis
很快的原因主要有 内存(大部分操作是在内存完成的)
、数据结构
、IO多路复用机制
,这篇文章主要探讨一下 Redis
的 高性能IO模型
,为什么单线程 Redis
能每秒处理数十万级的数据,以及 Redis6.0
的多线程解决是什么问题。
1.笔记图
2.Redis 单线程含义
Redis
的网络IO
和键值对读写是由一个线程来完成的Redis
其他功能,如持久化
、异步删除
、集群数据同步
等其实都是由额外线程执行
3.Redis单线程设计
3.1 多线程需要解决的问题
- 多线程设计合理会增加吞吐量(每秒处理的请求),若设计不良好甚至可能会出现吞吐率下降的情况
- 系统中通常会存在被多线程共享访问的资源
- 多线程编程模式面临共享资源并发访问控制问题
3.2 单线程 Redis 为什么那么快?
- 通常单线程处理能力要比多线程差很多,但
Redis
却能使用单线程模型有每秒处理数十万数据级别的能力 Redis
大部分操作是在内存完成的Redis
底层数据结构,如哈希表
、跳表
、双向链表
等- 多路复用机制
4.多路复用机制
Redis
多路复用机制在网络 IO
操作中能并发处理大量的客户端请求,实现高吞吐率(每秒处理的请求数)。
4.1 IO 模型
- 监听客户端请求
(bind/listen)
- 和客户端建立连接
(accept)
- 从socket中读取请求
(recv)
- 解析客户端发送请求
(parse)
- 根据请求类型读取键值数据
(get)
- 返回给客户端
(send)
4.2 潜在阻塞点
accept
:当Redis
监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞其他客户端和Redis
建立连接recv
:当Redis
通过recv()
从一个客户端读取数据时,如果一直没有到达,Redis
也会一直阻塞在recv()
4.3 socket网络模型非阻塞模式
- socket()方法:
socket()
方法会返回主动套接字,然后调用listen()
方法 - listen()方法:将主动套接字转化为监听套接字,此时可以用来监听来自客户端的连接请求,可设置
accept()
非阻塞模式 - accept()方法:最后调用
accept()
方法接收到达的客户端连接,并返回连接套接字,可设置send()/recv()
非阻塞模式
4.4 基于多路复用的高性能 IO 模型 select/epoll
- 虽然
Redis
线程可以不用继续等待,但是总得有机制继续在监听套接字上等待后续连接请求,并在有请求时通知Redis
- 该机制允许内核中同时存在多个监听套接字和已连接套接字
Redis
网络框架调用epoll
机制,让内核监听这些套接字Redis
线程不会阻塞在某一个特定的监听或已连接的套接字上(即不会阻塞在某个请求)- 为了在请求到达时能通知到
Redis
线程,select/epoll
提供了事件的回调机制,针对不同事件的发生调用相应的处理函数 - 避免
Redis
轮询是否有请求,避免造成CPU资源浪费,select/epoll
一旦监测到FD
上有请求到达时,就会触发相应的事件
5.Redis 6.0 多线程
5.1 使用多线程原因
- 随着网络硬件的性能提升,
Redis
的性能瓶颈有时会出现在网络IO
的处理上 - 单个主线程处理网络请求的速度跟不上底层网络硬件的速度
5.2 对应网络 IO 瓶颈方法
-
用户态网络协议栈(例如 DPDK)取代内核网络协议栈,让网络请求的处理不用在内核里执行,直接在用户态完成处理就行,该方法需要修改网络源码,可能引入新BUG,导致不稳定,该方法没有采用
-
采用多个
IO
线程处理网络请求 -
阶段一:服务端和客户端建立
Socket
连接,并分配处理线程 -
阶段二:
IO
线程读取并解析请求(有多个IO
线程在并行处理) -
阶段三:主线程执行请求操作
-
阶段四:
IO
线程回写Socket
和主线程清空全局队列
5.3 Redis6.0 多线程开启方式
- 需要在
redis.conf
中设置io-thread-do-reads
配置项为yes
,表示启用多线程 - 需要在
redis.conf
中设置线程个数。一般来说,线程个数要小于Redis
实例所在机器的CPU
核个数,例如,对于一个8
核的机器来说,Redis
官方建议配置6
个IO
线程
5.4 优化建议
如果你在实际应用中,发现 Redis
实例的 CPU
开销不大,吞吐量却没有提升,可以考虑使用 Redis 6.0
的多线程机制,加速网络处理,进而提升实例的吞吐量。
扫码关注