Redis阻塞操作实现原理(转)
原文:https://www.jianshu.com/p/xsMzfn
作者:Haiger
最近一位朋友问到:既然Redis是单线程的工作模式,那像BLPOP这样的阻塞操作又是然后实现的呢?
接下来分别从服务端和客户端来阐述这一逻辑的实现原理。
Redis Server:
redis实现了一套事件触发模型,主要处理两种事件:I/O事件(文件事件)和定时事件。而处理它们的就靠一个EventLoop线程。同时redis还提供了丰富的数据结构,今天我们要分析的主要是List数据结构中的阻塞命令。
先来看看BLPOP的源码(做了精简,只看主要的部分,详细的可以看文尾提供的参考链接):
t_list.c
既然是list数据结构,当然有push数据的操作:
同样是t_list.c
从上面的分析来看,主要是blocking_keys和ready_keys的作用,那何时才会处理它们呢? 我们知道redis全靠EventLoop来处理所以的I/O事件,我们来看看所以命令的处理入口:
redis.c
这样一来整个流程就清晰了。redis就是通过blocking_keys和ready_keys两个数据结构来实现的阻塞操作。但整个阻塞并没有阻塞EventLoop本身,从而实现命令的快速响应。算是一个典型的空间换时间的设计思路。
接下来再看看客户端如何实现一个阻塞的I/O请求。
Client:
这里我们分两种I/O模型来阐述:阻塞I/O(BIO)和非阻塞I/O(NIO)。
BIO,以Jedis为例。
BinaryJedis.java
注意:这里的链接是被独享的,不然会有数据干扰。
NIO的实现就稍微复杂一些,这里分两种情况(以netty为例):
不带RquestID的实现方式
伪代码如下:
由于NIO的特性read和write是两个I/O事件,要分别等待selector来触发,所以不能像BIO那样连续发起两次I/O操作。再加上没有requesID,当read到数据时无法找到之前对应的发起者,所以这里的链接也必须是独享的,同时由一个只能包含一个元素的阻塞队列LinkedBlockingQueue来实现阻塞的效果。
带RequestID的实现方式
伪代码如下:
这里因为reqeust和response数据结构里都有带上了requestId,并且在链接对象上缓存了requestId和响应future的对应关系,因此链接可以不用独享。
到处,整个阻塞的实现原理分析完毕。
参考链接:
带注解的redis源码:
https://github.com/huangz1990/annotated_redis_source/blob/unstable/src/t_list.c https://github.com/huangz1990/annotated_redis_source/blob/unstable/src/redis.c
IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
http://blog.csdn.net/historyasamirror/article/details/5778378