在redis版本6之前,網絡IO和鍵值對讀寫都是由一個線程來完成的。而redis的其他功能,比如持久化、異步刪除、集群數據同步等,是由其他線程完成的。
為什么采用單線程
多線程有助于提升吞吐率(系統同時處理的請求數),但處理共享資源時,會帶來額外的開銷。設計有問題時,采用多線程甚至會造成性能下降。為了減少并發訪問控制問題,redis直接采用單線程模式。
redis的大部分操作都是在內存上完成的,而且redis采用哈希表、跳表等性能良好的數據結構,以及多路復用機制,使得redis在單線程模式下也能實現高性能和高吞吐率。
linux的IO多路復用機制
Linux的IO多路復用機制指的是一個線程處理多個IO流,即select/epoll
機制。內核一直監聽套接字的請求,一旦有相關請求,就交給redis處理,從而實現redis線程處理多個IO流的效果。
為了在請求到達時能通知到Redis線程,select/epoll
提供了基于事件的回調機制,即針對不同事件的發生,調用相應的處理函數。相關事件被放到一個事件隊列,redis單線程對該事件隊列不斷處理,避免一直輪詢是否有請求發生造成CPU資源浪費。因為redis一直在處理事件隊列,所以能及時響應請求。
redis單線程處理IO請求瓶頸
- 任意一個請求在server中一旦耗時較長,就會影響整個server的性能,也就是說,后面的請求都要等前面的先處理完。耗時操作包括以下幾種:
-
- 操作big key:寫入一個big key在分配內存時需要耗費一定時間。刪除一個big key去釋放內存也需要一定時間。
- 操作大量數據的命令。類似于mysql的
select * from xxx
的操作會占用大量時間。 - 大量key集中過期。
- 淘汰策略。內存不夠用時,每次寫入都要淘汰一些key,淘汰會耗時較長。
- AOF開啟always機制,每次寫入都將操作進行刷盤,拖慢redis性能
- 主從全量同步生成RDB:雖然采用fork子進程生成快照,但fork的一瞬間也會阻塞整個線程,實例越大,阻塞越久。
- 并發量非常大時,雖然采用了IO多路復用,但讀寫客戶端數據依舊是同步IO,只能單線程依次讀取客戶端數據,無法利用CPU的多核。
redis6.0的多線程
redis6.0引入了多線程,但只是多線程處理網絡IO請求,對于讀寫命令,redis仍然使用單線程處理。redis 6.0中,多線程機制默認是關閉的,相關配置:
# 啟用多線程
io-threads-do-reads yes
# 設置線程數。官方建議小于主機CPU核數
io-threads 6