原文: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