一. Redis6.0 新特性
1. 多線程IO
redis6.0引入多線程IO,只是用來處理網絡數據的讀寫和協議的解析,而執行命令依舊是單線程,所以不需要去考慮set/get、事務、lua等的並發問題。(詳細的線程模型見后面)
多線程IO的性能提升測試可參考:https://zhuanlan.zhihu.com/p/76788470 (相對權威) 自己測試:https://www.cnblogs.com/yaopengfei/p/13922295.html
2. ACL精細化權限控制
在Redis 5版本之前,Redis 安全規則只有密碼控制 還有通過rename 來調整高危命令比如 flushdb
, KEYS*
, shutdown
等。
Redis 6 則提供ACL的功能對用戶進行更細粒度的權限控制 ,支持對客戶端的權限控制,實現對不同的key授予不同的操作權限:
(1)接入權限: 用戶名和密碼 (2)可以執行的命令 (3)可以操作的 KEY
相關指令如下:
3. 支持RESP3
RESP(Redis Serialization Protocol)是 Redis 服務端與客戶端之間通信的協議。
Redis 5 使用的是 RESP2,而 Redis 6 開始在兼容 RESP2 的基礎上,開始支持 RESP3。推出RESP3的目的:一是因為希望能為客戶端提供更多的語義化響應,以開發使用舊協議難以實現的功能;另一個原因是實現 Client-side-caching(客戶端緩存)功能。
4. 重新設計了客戶端緩存
基於 RESP3 協議實現的客戶端緩存功能。為了進一步提升緩存的性能,將客戶端經常訪問的數據cache到客戶端,減少TCP網絡交互,提升RT。
5. 提升了RDB的加載速度
根據文件的實際組成(較大或較小的值),可以預期20/30%的改進。當有很多客戶機連接時,信息也更快了,這是一個老問題,現在終於解決了。
6. 支持SSL
連接支持SSL,更加安全。
7. Redis Cluster proxy(集群代理)
antirez開發了 Proxy 功能,讓 Cluster 擁有像單實例一樣的接入方式,降低大家使用cluster的門檻。不過需要注意的是代理不改變 Cluster 的功能限制,不支持的命令還是不會支持,比如跨 slot 的多Key操作。
8. Modules API
Redis 6中模塊API開發進展非常大,因為Redis Labs為了開發復雜的功能,從一開始就用上Redis模塊。Redis可以變成一個框架,利用Modules來構建不同系統,而不需要從頭開始寫然后還要BSD許可。Redis一開始就是一個向編寫各種系統開放的平台。
Disque作為一個Redis Module使用足以展示Redis的模塊系統的強大。集群消息總線API、屏蔽和回復客戶端、計時器、模塊數據的AOF和RDB等等。
參考:http://antirez.com/latest/0 (redis作者)
二. 剖析線程模型
1. 靈魂拷問
(1). 為什么說redis6.0之前是單線程的?
redis執行客戶端命令的請求從: 獲取 (socket 讀)→解析→執行→內容返回 (socket 寫) 等等都是由一個線程處理,所有操作是一個個挨着串行執行的 (主線程),這就是稱redis是單線程的原因。
但是:redis從4.0開始,也有后台線程在工作,處理一些較為緩慢的操作,例如無用連接的釋放、大 key 的刪除等等,嚴格意義上來說,redis6.0之前也不完全是單線程的。
(2). 單線程非常快的原因是什么?
A. 純內存操作,避免大量訪問數據庫,減少直接讀取磁盤數據,redis將數據儲存在內存里面,讀寫數據的時候都不會受到硬盤 I/O 速度的限制,所以速度快.
B. 單線程操作,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗.
C. 采用了非阻塞I/O 多路復用機制.
(3). redis在6.0之前為什么一直堅持單線程?
官方曾做過類似問題的回復:使用Redis時,幾乎不存在CPU成為瓶頸的情況, Redis主要受限於內存和網絡。例如在一個普通的Linux系統上,Redis通過使用pipelining每秒可以處理100萬個請求,所以如果應用程序主要使用O(N)或O(log(N))的命令,它幾乎不會占用太多CPU。
使用了單線程后,可維護性高。多線程模型雖然在某些方面表現優異,但是它卻引入了程序執行順序的不確定性,帶來了並發讀寫的一系列問題,增加了系統復雜度、同時可能存在線程切換、甚至加鎖解鎖、死鎖造成的性能損耗。Redis通過AE事件模型以及IO多路復用等技術,處理性能非常高,因此沒有必要使用多線程。單線程機制使得 Redis 內部實現的復雜度大大降低,Hash 的惰性 Rehash、Lpush 等等 “線程不安全” 的命令都可以無鎖進行。
(4). redis6.0引入多線程機制的背景是什么?
從Redis自身角度來說,因為讀寫網絡的read/write系統調用占用了Redis執行期間大部分CPU時間,瓶頸主要在於網絡的 IO 消耗, 優化主要有兩個方向:
(1). 提高網絡 IO 性能,典型的實現比如使用 DPDK 來替代內核網絡棧的方式
(2). 使用多線程充分利用多核,典型的實現比如 Memcached。
協議棧優化的這種方式跟 Redis 關系不大,支持多線程是一種最有效最便捷的操作方式。所以總結起來,redis支持多線程主要就是兩個原因:
(1). 可以充分利用服務器 CPU 資源,目前主線程只能利用一個核
(2). 多線程任務可以分攤 Redis 同步 IO 讀寫負荷
2. redis單線程模型(6.0之前)
Redis客戶端對服務端的每次調用都經歷了發送命令,執行命令,返回結果三個過程。其中執行命令階段,由於Redis是單線程來處理命令的,所有每一條到達服務端的命令不會立刻執行,所有的命令都會進入一個隊列中,然后逐個被執行。並且多個客戶端發送的命令的執行順序是不確定的。但是可以確定的是不會有兩條命令被同時執行,不會產生並發問題,這就是Redis的單線程基本模型。
PS:可以修改redis的最大鏈接數,默認為10000,如下圖,如果要修改的話,直接修改配置文件重點maxclients即可。
(1). 什么是非阻塞IO?
非阻塞 IO 在 Socket 對象上提供了一個選項Non_Blocking
,當這個選項打開時,讀寫方法不會阻塞,而是能讀多少讀多少,能寫多少寫多少。
能讀多少取決於內核為 Socket 分配的讀緩沖區的大小,能寫多少取決於內核為 Socket 分配的寫緩沖區的剩余空間大小。讀方法和寫方法都會通過返回值來告知程序實際讀寫了多少字節數據。
有了非阻塞 IO 意味着線程在讀寫 IO 時可以不必再阻塞了,讀寫可以瞬間完成然后線程可以繼續干別的事了。
補充阻塞IO概念:
當我們調用 Scoket 的讀寫方法,默認它們是阻塞的。
read() 方法要傳遞進去一個參數 n,表示讀取這么多字節后再返回,如果沒有讀夠 n 字節線程就會阻塞,直到新的數據到來或者連接關閉了, read 方法才可以返回,線程才能繼續處理。
write() 方法會首先把數據寫到系統內核為 Scoket 分配的寫緩沖區中,當寫緩存區滿溢,即寫緩存區中的數據還沒有寫入到磁盤,就有新的數據要寫道寫緩存區時,write() 方法就會阻塞,直到寫緩存區中有空閑空間。
(2). 什么是IO多路復用?
背景:
非阻塞 IO 有個問題,那就是單個線程要處理多個讀寫請求,處理某個客戶端的的讀數據的請求,結果讀了一部分就返回了,線程如何知道什么時候才應該繼續讀數據。處理寫請求的時候,如果緩沖區滿了,寫不完,剩下的數據何時才應該繼續寫?在什么時候處理什么請求?redis 單線程處理多個IO請求時就用到了IO多路復用技術。
原理:
如下圖,redis 需要處理 3 個 IO 請求,同時把 3 個請求的結果返回給客戶端,所以總共需要處理 6 個 IO 事件,由於 redis 是單線程模型,同一時間只能處理一個 IO 事件,於是 redis 需要在合適的時間暫停對某個 IO 事件的處理,轉而去處理另一個 IO 事件,這樣 redis 就好比一個開關,當開關撥到哪個 IO 事件這個電路上,就處理哪個 IO 事件,其他 IO 事件就暫停處理了。這就是IO多路復用技術。
以上是大致的理解下 IO 多路復用技術,在系統底層,IO 多路復用有 3 種實現機制:select、poll、epoll。
(3). 什么是文件處理器?
A. Redis 基於 Reactor 模式開發了自己的網絡事件處理器: 這個處理器被稱為文件事件處理器(file event handler)
B. 文件事件處理器使用 I/O 多路復用(multiplexing)程序來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。
C. 當被監聽的套接字准備好執行連接應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時, 與操作相對應的文件事件就會產生, 這時文件事件處理器就會調用套接字之前關聯好的事件處理器來處理這些事件。
D. 文件事件處理器以單線程方式運行, 但通過使用 I/O 多路復用程序來監聽多個套接字, 文件事件處理器既實現了高性能的網絡通信模型, 又可以很好地與 redis 服務器中其他同樣以單線程方式運行的模塊進行對接, 這保持了 Redis 內部單線程設計的簡單性。
3. redis多線程模型(6.0開始)
(1). 流程如下:(如下圖)
- 主線程獲取 socket 放入等待列表
-
將 socket 分配給各個 IO 線程(並不會等列表滿)
-
主線程阻塞等待 IO 線程(多線程)讀取 socket 完畢
-
主線程執行命令 - 單線程(如果命令沒有接收完畢,會等 IO 下次繼續)
-
主線程阻塞等待 IO 線程(多線程)將數據回寫 socket 完畢(一次沒寫完,會等下次再寫)
-
解除綁定,清空等待隊列
(2). 特點如下:
- IO 線程要么同時在讀 socket,要么同時在寫,不會同時讀或寫
- IO 線程只負責讀寫 socket 解析命令,不負責命令處理(主線程串行執行命令)
- IO 線程數可自行配置
參考:https://ruby-china.org/topics/38957%EF%BC%89
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。