1.1 总结
Redis
整体是多线程的,至于平时所说的 Redis
单线程是指 Redis的网络io 和 键值对读写 是在同一个线程中完成
1.2 一次 Redis 请求过程
- 监听客户端请求(
bind/listen
) - 等待客户端的连接(
accept
) - 从
socket
中读取数据(recv
) - 解析客户端发送的内容(
parse
) - 根据解析出来的内容进行内存操作(
get/set
) - 执行完成命令之后给客户端响应结果,即向
socket
中写数据(send
)
graph TB
subgraph A[redis线程]
subgraph B[网络IO处理]
B1[bind/listen]
B2[accept]
B3[recv]
B4[parse]
end
subgraph C[键值对读写]
C1[get/set]
end
subgraph D[网络IO处理]
D1[send]
end
end
B-->C-->D
- 网络 io 处理过程中,
listen
和recv
这两个步骤是可能阻塞的,即当没有连接或客户端没有发送命令时,服务器处理这两个环节时会阻塞,但是socket网络模型自身就支持非阻塞的形式 对于多个客户端的请求流程如下图所示
1.3 Redis4 版本中的多线程
- 在
Redis4
中主要是增加了异步删除 机制,比如UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC
等非阻塞的删除操作。 - 引入异步删除的原因是现在服务器的内存原来越大,
Redis
中可能会存在bigKey
, 对于这些bigKey
的删除如果使用阻塞的方式,会导致服务器的响应时间变长,所以引入了异步删除的机制,即当删除bigKey
的时候,会将删除操作放入一个异步队列中,由一个单独的线程来负责异步删除,这样就可以提高服务器的响应时间
1.4 Redis6 版本的多线程
在 redis6
中有几个重要特性,比如
- 面向网络处理 的多
IO
线程 - 客户端缓存
- 细粒度的权限控制
本节主要讲解 Redis6
中的多线程模型,在 Redis6
之前,从网络io处理到键值对读写都是在同一个线程中完成的 , 随着网络硬件的性能提升,Redis
的性能瓶颈有时会出现在网络io
的处理上,即 单个主线程处理网络请求的速度跟不上底层网络硬件的速度 ,所以在 Redis6
中使用多个网络io
线程来处理网络请求,提高网络请求处理并行的能力, 需要注意的是 Redis6的多线程模型只是针对网络io部分,Redis的数据读写依然是在一个线程中完成的
1.4.1 Redis6中主线程和io线程协作流程
graph TB
subgraph A["redis主线程(接收请求)"]
direction TB
A1["接收建立连接请求,获取socket"]
A2["将socket放入全局等待队列"]
A3["以轮询方式将socket连接分配给IO线程, 主线程开始阻塞-----"]
A1-->A2-->A3
end
subgraph B["redis网络IO线程(解析请求)"]
direction TB
B1["将socket和线程绑定"]
B2["读取socket中的请求并解析"]
B3["请求解析完成"]
B1-->B2-->B3
end
subgraph BB["redis网络IO线程(解析请求)"]
direction TB
BB1["将socket和线程绑定"]
BB2["读取socket中的请求并解析"]
BB3["请求解析完成"]
BB1-->BB2-->BB3
end
A--"io线程开始执行"-->B & BB
subgraph C["redis主线程(命令读写)"]
direction TB
C1["执行请求的命令操作"]
C2["请求的命令操作执行完成"]
C3["将结果数据写入缓冲区"]
C4["等待io线程完成数据回写socket,主线程阻塞---"]
C1-->C2-->C3-->C4
end
B-->C
BB-->C
subgraph D["redis网络IO线程(结果响应)"]
direction TB
D1["将结果数据回写socket(这一步是并行处理)"]
D2["socket回写完成"]
D1-->D2
end
subgraph DD["redis网络IO线程(结果响应)"]
direction TB
DD1["将结果数据回写socket(这一步是并行处理)"]
DD2["socket回写完成"]
DD1-->DD2
end
C-->D & DD
D-->E["主线程清空等待队列,等待后续请求"]
DD-->E["主线程清空等待队列,等待后续请求"]
1.4.2 阶段1
- 服务端和客户端连接
socket
连接,并将socket
放入全局等待队列 - 主线程通过轮询的方式,将
socket
连接分配给io
线程 - 主线程进入阻塞状态
1.4.3 阶段2
io
线程完成客户端请求读取和解析io
线程有多个,是并发操作的, 所以会很快
1.4.4 阶段3
- 等待
io
x线程将命令解析完成后,主线程会以单线程的方式执行这个命令操作
1.4.5 阶段4
- 主线程执行完请求操作后,将需要返回的结果写入缓冲区,然会,主线程会阻塞等待
io
线程把这些结果写回到socket
中,并返回给客户端 io
线程回写socket
之后,主线程会清空全局队列,等待客户端的后续请求
1.5 Redis6 多线程模型的启用
- 在
Redis6
中,多线程模块默认是关闭的 - 可以在
redis.conf
中设置io-threads-do-reads yes
来开启多线程 - 可以在
redis.conf
中设置线程个数,比如io-threads 4
, 一般建议线程个数小于redis
实例所在机器的cpu
核个数
1.6 疑问解答
Redis6.0
增加了IO
线程来处理网络请求,如果客户端先发送了一个set key1 val1
写命令,紧接着发送一个get key1
读命令。由于IO线程是多线程处理的,是否会导致get key1
读命令 先于set key1 val1
写命令执行呢?结果客户端读到了key1
的旧值的情况发生?
如果这两条命令是同一个客户端发送的,有明显的先后顺序,就不会出现这种情况,因为接收请求的还是主线程,主线程接收请求后会将命令入队列,这个是可以保证顺序的,虽然后续是交给多线程来解析请求,但是命令读写的顺序还是会按照队列中命令先后出现的顺序,而且命令读写是通过主线程单线程操作的。
如果这里说的是多个客户端发送的命令,那就需要看哪个客户端发送的请求先到达服务端,先到达的命令会先执行