單線程的redis爲何這麼快

爲何說Redis是單線程的而且這麼快redis

 

其它開源軟件採用的模型數據庫

 

Nginx:多進程單線程模型 安全

Memcached:單進程多線程模型服務器

Redis:單進程單線程網絡

 

 

單線程的redis爲何這麼快數據結構

主要是如下三點多線程

(一)純內存操做架構

數據存在內存中,相似於HashMap,HashMap的優點就是查找和操做的時間複雜度都是O(1);併發

 

(二)單線程操做避免了頻繁的上下文切換框架

避免了沒必要要的上下文切換和競爭條件,也不存在多進程或者多線程致使的切換而消耗 CPU,不用去考慮各類鎖的問題,不存在加鎖釋放鎖操做,沒有由於可能出現死鎖而致使的性能消耗;

 

多線程處理可能涉及到鎖 

多線程處理會涉及到線程切換而消耗CPU

 

多線程對同一個Key操做時,Redis服務是根據先到先做的原則,其餘排隊(可設置爲直接丟棄),由於是單線程。

修改默認的超時時間,默認2秒。可是大部份的操做都在30ms之內。

  

(三)採用了非阻塞I/O多路複用機制

內部實現採用epoll,採用了epoll+本身實現的簡單的事件框架。epoll中的讀、寫、關閉、鏈接都轉化成了事件,而後利用epoll的多路複用特性,毫不在io上浪費一點時間

 

(四)數據結構簡單,對數據操做也簡單,Redis中的數據結構是專門進行設計的;

 

 

咱們使用單線程的方式是沒法發揮多核CPU 性能,不過咱們能夠經過在單機開多個Redis 實例來完善!

警告:這裏咱們一直在強調的單線程,只是在處理咱們的網絡請求的時候只有一個線程來處理,一個正式的Redis Server運行的時候確定是不止一個線程的,這裏須要你們明確的注意一下!

例如Redis進行持久化的時候會以子進程或者子線程的方式執行(具體是子線程仍是子進程待讀者深刻研究)

 

 

Redis爲何是單線程的?

由於CPU不是Redis的瓶頸。Redis的瓶頸最有多是機器內存或者網絡帶寬。

既然單線程容易實現,並且CPU不會成爲瓶頸,那就瓜熟蒂落地採用單線程的方案了。

關於redis的性能,官方網站也有,普通筆記本輕鬆處理每秒幾十萬的請求,參見:How fast is Redis?

 

若是萬一CPU成爲你的Redis瓶頸了,或者,你就是不想讓服務器其餘核閒置,那怎麼辦?

那也很簡單,你多起幾個Redis進程就行了。Redis是keyvalue數據庫,又不是關係數據庫,數據之間沒有約束。只要客戶端分清哪些key放在哪一個Redis進程上就能夠了。redis-cluster能夠幫你作的更好。

 

單線程能夠處理高併發請求嗎?

固然能夠了,Redis都實現了。有一點概念須要澄清,併發並非並行。

(相關概念:併發性I/O流,意味着可以讓一個計算單元來處理來自多個客戶端的流請求。並行性,意味着服務器可以同時執行幾個事情,具備多個計算單元)

 

單線程處理的缺點?

沒法發揮多核CPU性能,不過能夠經過在單機開多個Redis實例來完善

    

單進程單線程的Redis如何可以高併發

採用多路 I/O 複用技術可讓單個線程高效的處理多個鏈接請求(儘可能減小網絡IO的時間消耗) 

 

Redis不存在線程安全問題? 

Redis採用了線程封閉的方式,把任務封閉在一個線程,天然避免了線程安全問題,不過對於須要依賴多個redis操做的複合操做來講,依然須要鎖,並且有多是分佈式鎖

 

 

redis的一些其餘特色:

(1)Redis是單進程單線程的

redis利用隊列技術將併發訪問變爲串行訪問,消除了傳統數據庫串行控制的開銷

 

(2)讀寫分離模型

經過增長Slave DB的數量,讀的性能能夠線性增加。爲了不Master DB的單點故障,集羣通常都會採用兩臺Master DB作雙機熱備,因此整個集羣的讀和寫的可用性都很是高。

讀寫分離架構的缺陷在於,不論是Master仍是Slave,每一個節點都必須保存完整的數據,若是在數據量很大的狀況下,集羣的擴展能力仍是受限於單個節點的存儲能力,並且對於Write-intensive類型的應用,讀寫分離架構並不適合。

 

(3)數據分片模型

爲了解決讀寫分離模型的缺陷,能夠將數據分片模型應用進來。

能夠將每一個節點當作都是獨立的master,而後經過業務實現數據分片。

結合上面兩種模型,能夠將每一個master設計成由一個master和多個slave組成的模型。

 

(4)Redis的回收策略

volatile-lru:從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

volatile-ttl:從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰

volatile-random:從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰

 

allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

 

no-enviction(驅逐):禁止驅逐數據

 

注意這裏的6種機制,volatile和allkeys規定了是對已設置過時時間的數據集淘汰數據仍是從所有數據集淘汰數據,後面的lru、ttl以及random是三種不一樣的淘汰策略,再加上一種no-enviction永不回收的策略。

 

使用策略規則:

  一、若是數據呈現冪律分佈,也就是一部分數據訪問頻率高,一部分數據訪問頻率低,則使用allkeys-lru

  二、若是數據呈現平等分佈,也就是全部的數據訪問頻率都相同,則使用allkeys-random

 

 

redis常見性能問題和解決方案:

 

(1) Master最好不要作任何持久化工做,如RDB內存快照和AOF日誌文件

(Master寫內存快照,save命令調度rdbSave函數,會阻塞主線程的工做,當快照比較大時對性能影響是很是大的,會間斷性暫停服務,因此Master最好不要寫內存快照;

AOF文件過大會影響Master重啓的恢復速度)

 

(2) 若是數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次

(3) 爲了主從複製的速度和鏈接的穩定性,Master和Slave最好在同一個局域網內

(4) 儘可能避免在壓力很大的主庫上增長從庫

(5) 主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,即:Master <- Slave1 <- Slave2 <- Slave3...

 

這樣的結構方便解決單點故障問題,實現Slave對Master的替換。若是Master掛了,能夠馬上啓用Slave1作Master,其餘不變。

 

 

1. Redis服務端是個單線程的架構,不一樣的Client雖然看似能夠同時保持鏈接,但發出去的命令是序列化執行的,這在一般的數據庫理論下是最高級別的隔離(serialize)

2. 用MULTI/EXEC 來把多個命令組裝成一次發送,達到原子性

3. 用WATCH提供的樂觀鎖功能,在你EXEC的那一刻,若是被WATCH的鍵發生過改動,則MULTI到EXEC之間的指令所有不執行,不須要rollback

4. 其餘回答中提到的DISCARD指令只是用來撤銷EXEC以前被暫存的指令,並非回滾