Redis原理和高可用場景實踐總結

擊水三千里發表於2018-12-26

目錄

1.Redis基礎原理和知識

2.Redis持久化:RDB,AOF

3.部署redis企業級資料備份方案

4.如何通過讀寫分離來承載讀請求QPS超過10萬+(master+slave)

5 redis哨兵架構的相關基礎知識的講解

6 哨兵主備切換的資料丟失問題:非同步複製、叢集腦裂

7 redis的叢集架構(企業級常用架構)

8.redis cluster的hash slot演算法

9.Redis節點間的內部通訊機制

10.高可用性與主備切換原理

11 .jedis cluster api與redis cluster叢集互動的一些基本原理

12.redis在實踐中的一些常見問題以及優化思路

13.redis的雪崩和穿透

14、Redis的併發競爭問題該如何解決

15、關於redis生產分散式叢集的常見實現方案

16、你們生產環境中的redis是怎麼部署的

17、Redis Java客戶端的選擇


1.Redis基礎原理和知識

1.1 redis和memcached有啥區別

1)Redis支援伺服器端的資料操作:Redis相比Memcached來說,擁有更多的資料結構和並支援更豐富的資料操作,通常在Memcached裡,你需要將資料拿到客戶端來進行類似的修改再set回去。這大大增加了網路IO的次數和資料體積。在Redis中,這些複雜的操作通常和一般的GET/SET一樣高效。所以,如果需要快取能夠支援更復雜的結構和操作,那麼Redis會是不錯的選擇。

2)叢集模式:memcached沒有原生的叢集模式,需要依靠客戶端來實現往叢集中分片寫入資料;但是redis目前是原生支援cluster模式的,redis官方就是支援redis cluster叢集模式的,比memcached來說要更好

1.2 redis的執行緒模型

redis單執行緒模型

 

1.2.1基礎概念鋪墊

redis基於reactor模式開發了網路事件處理器,這個處理器叫做檔案事件處理器

檔案事件處理器是單執行緒模式執行的,但是通過IO多路複用機監聽多個socket,可以實現高效能的網路通訊模型,又可以跟內部其他單執行緒的模組進行對接,保證了redis內部的執行緒模型的簡單性。

 

檔案事件處理器的結構包含4個部分:多個socket,IO多路複用程式,檔案事件分派器,事件處理器(命令請求處理器、命令回覆處理器、連線應答處理器,等等)

檔案事件處理器的結構

 

多個socket可能併發的產生不同的操作,每個操作對應不同的檔案事件,但是IO多路複用程式會監聽多個socket,但是會將socket放入一個佇列中排隊,每次從佇列中取出一個socket給事件分派器,事件分派器把socket給對應的事件處理器。

 

當socket變得可讀時,socket就會產生一個AE_READABLE事件。

當socket變得可寫的時候,socket會產生一個AE_WRITABLE事件。

IO多路複用程式可以同時監聽AE_REABLE和AE_WRITABLE兩種事件,要是一個socket同時產生了AE_READABLE和AE_WRITABLE兩種事件,那麼檔案事件分派器優先處理AE_REABLE事件,然後才是AE_WRITABLE事件。

 

如果是客戶端要連線redis,那麼會為socket關聯連線應答處理器

如果是客戶端要寫資料到redis,那麼會為socket關聯命令請求處理器

如果是客戶端要從redis讀資料,那麼會為socket關聯命令回覆處理器

 

1.2.2客戶端與redis通訊的一次流程

在redis啟動初始化的時候,redis會將連線應答處理器跟AE_READABLE事件關聯起來,接著如果一個客戶端跟redis發起連線,此時會產生一個AE_READABLE事件,然後由連線應答處理器來處理跟客戶端建立連線,建立客戶端對應的socket,同時將這個socket的AE_READABLE事件跟命令請求處理器關聯起來。

當客戶端向redis發起請求的時候(不管是讀請求還是寫請求,都一樣),首先就會在socket產生一個AE_READABLE事件,然後由對應的命令請求處理器來處理。這個命令請求處理器就會從socket中讀取請求相關資料,然後進行執行和處理。

接著redis這邊準備好了給客戶端的響應資料之後,就會將socket的AE_WRITABLE事件跟命令回覆處理器關聯起來,當客戶端這邊準備好讀取響應資料時,就會在socket上產生一個AE_WRITABLE事件,會由對應的命令回覆處理器來處理,就是將準備好的響應資料寫入socket,供客戶端來讀取。

命令回覆處理器寫完之後,就會刪除這個socket的AE_WRITABLE事件和命令回覆處理器的關聯關係。

 

1.3為啥redis單執行緒模型也能效率這麼高

1)純記憶體操作(主要)

2)核心是基於非阻塞的IO多路複用機制(主要)

3)單執行緒反而避免了多執行緒的頻繁上下文切換問題

1s內可以處理幾萬請求

 

1.4 redis的基礎資料型別

型別

用途舉例

string

 
hash  
list    比如可以通過lrange命令,就是從某個元素開始讀取多少個元素,可以基於list實現分頁查詢
set  
sorted_set  排行榜:將每個使用者以及其對應的什麼分數寫入進去,zadd board score username,接著zrevrange board 0 99,就可以獲取排名前100的使用者;zrank board username,可以看到使用者在排行榜裡的排名

 

1.5、Pipelining

客戶端要連續執行的多次操作無法通過Redis命令組合在一起,例如:

SET a "abc"

INCR b

HSET c name "hi"

此時便可以使用Redis提供的pipelining功能來實現在一次互動中執行多條命令。

使用示例:

mysql  -uroot -p123456.. -Dds_0 --default-character-set=utf8 --skip-column-names --raw < syns_data.sql | redis-cli -a 123456.. --pipe

Pipelining的侷限性

Pipelining只能用於執行連續且無相關性的命令,當某個命令的生成需要依賴於前一個命令的返回時,就無法使用Pipelining了。

 

1.6、事務與Scripting

Pipelining能夠讓Redis在一次互動中處理多條命令,然而在一些場景下,我們可能需要在此基礎上確保這一組命令是連續執行的。

比如獲取當前累計的PV數並將其清0

> GET vCount

12384

> SET vCount 0

OK

如果在GET和SET命令之間插進來一個INCR vCount,就會使客戶端拿到的vCount不準確。

Redis的事務可以確保複數命令執行時的原子性。也就是說Redis能夠保證:一個事務中的一組命令是絕對連續執行的,在這些命令執行完成之前,絕對不會有來自於其他連線的其他命令插進去執行。

通過MULTI和EXEC命令來把這兩個命令加入一個事務中:

> MULTI

OK

> GET vCount

QUEUED

> SET vCount 0

QUEUED

> EXEC

1) 12384

2) OK

 

Redis在接收到MULTI命令後便會開啟一個事務,這之後的所有讀寫命令都會儲存在佇列中但並不執行,直到接收到EXEC命令後,Redis會把佇列中的所有命令連續順序執行,並以陣列形式返回每個命令的返回結果。

可以使用DISCARD命令放棄當前的事務,將儲存的命令佇列清空。

需要注意的是,Redis事務不支援回滾

 

2.Redis持久化:RDB,AOF

2.1、RDB和AOF兩種持久化機制的介紹

RDB持久化機制,對redis中的資料執行週期性的持久化

AOF機制對每條寫入命令作為日誌,以append-only的模式寫入一個日誌檔案中,在redis重啟的時候,可以通過回放AOF日誌中的寫入指令來重新構建整個資料集

如果我們想要redis僅僅作為純記憶體的快取來用,那麼可以禁止RDB和AOF所有的持久化機制

通過RDB或AOF,都可以將redis記憶體中的資料給持久化到磁碟上面來,然後可以將這些資料備份到別的地方去,比如說阿里雲,雲服務

如果redis掛了,伺服器上的記憶體和磁碟上的資料都丟了,可以從雲服務上拷貝回來之前的資料,放到指定的目錄中,然後重新啟動redis,redis就會自動根據持久化資料檔案中的資料,去恢復記憶體中的資料,繼續對外提供服務

如果同時使用RDB和AOF兩種持久化機制,那麼在redis重啟的時候,會使用AOF來重新構建資料,因為AOF中的資料更加完整

 

2.2、RDB持久化機制的優點

(1)RDB會生成多個資料檔案,每個資料檔案都代表了某一個時刻中redis的資料,這種多個資料檔案的方式,非常適合做冷備,可以將這種完整的資料檔案傳送到一些遠端的安全儲存上去,比如說Amazon的S3雲服務上去,在國內可以是阿里雲的ODPS分散式儲存上,以預定好的備份策略來定期備份redis中的資料

(2)RDB對redis對外提供的讀寫服務,影響非常小,可以讓redis保持高效能,因為redis主程式只需要fork一個子程式,讓子程式執行磁碟IO操作來進行RDB持久化即可

(3)相對於AOF持久化機制來說,直接基於RDB資料檔案來重啟和恢復redis程式,更加快速

 

2.3、RDB持久化機制的缺點

(1)如果想要在redis故障時,儘可能少的丟失資料,那麼RDB沒有AOF好。一般來說,RDB資料快照檔案,都是每隔5分鐘,或者更長時間生成一次,這個時候就得接受一旦redis程式當機,那麼會丟失最近5分鐘的資料

(2)RDB每次在fork子程式來執行RDB快照資料檔案生成的時候,如果資料檔案特別大,可能會導致對客戶端提供的服務暫停數毫秒,或者甚至數秒


 

2.4、AOF持久化機制的優點

(1)AOF可以更好的保護資料不丟失,一般AOF會每隔1秒,通過一個後臺執行緒執行一次fsync操作,最多丟失1秒鐘的資料

(2)AOF日誌檔案以append-only模式寫入,所以沒有任何磁碟定址的開銷,寫入效能非常高,而且檔案不容易破損,即使檔案尾部破損,也很容易修復

(3)AOF日誌檔案即使過大的時候,出現後臺重寫操作,也不會影響客戶端的讀寫。因為在rewrite log的時候,會對其中的指導進行壓縮,建立出一份需要恢復資料的最小日誌出來。再建立新日誌檔案的時候,老的日誌檔案還是照常寫入。當新的merge後的日誌檔案ready的時候,再交換新老日誌檔案即可。

(4)AOF日誌檔案的命令通過非常可讀的方式進行記錄,這個特性非常適合做災難性的誤刪除的緊急恢復。比如某人不小心用flushall命令清空了所有資料,只要這個時候後臺rewrite還沒有發生,那麼就可以立即拷貝AOF檔案,將最後一條flushall命令給刪了,然後再將該AOF檔案放回去,就可以通過恢復機制,自動恢復所有資料

 

2.5、AOF持久化機制的缺點

(1)對於同一份資料來說,AOF日誌檔案通常比RDB資料快照檔案更大

(2)AOF開啟後,支援的寫QPS會比RDB支援的寫QPS低,因為AOF一般會配置成每秒fsync一次日誌檔案,當然,每秒一次fsync,效能也還是很高的

(3)以前AOF發生過bug,就是通過AOF記錄的日誌,進行資料恢復的時候,沒有恢復一模一樣的資料出來。所以說,類似AOF這種較為複雜的基於命令日誌/merge/回放的方式,比基於RDB每次持久化一份完整的資料快照檔案的方式,更加脆弱一些,容易有bug。不過AOF就是為了避免rewrite過程導致的bug,因此每次rewrite並不是基於舊的指令日誌進行merge的,而是基於當時記憶體中的資料進行指令的重新構建,這樣健壯性會好很多。

 

2.6、RDB和AOF到底該如何選擇

(1)不要僅僅使用RDB,因為那樣會導致你丟失很多資料

(2)也不要僅僅使用AOF,因為那樣有兩個問題,第一,你通過AOF做冷備,沒有RDB做冷備,來的恢復速度更快; 第二,RDB每次簡單粗暴生成資料快照,更加健壯,可以避免AOF這種複雜的備份和恢復機制的bug

(3)綜合使用AOF和RDB兩種持久化機制,用AOF來保證資料不丟失,作為資料恢復的第一選擇; 用RDB來做不同程度的冷備,在AOF檔案都丟失或損壞不可用的時候,還可以使用RDB來進行快速的資料恢復

 

3.部署redis企業級資料備份方案

3.1、企業級的持久化的配置策略

在企業中,RDB的生成策略,用預設的也差不多

save 60 10000:60s內資料更改達到10000就save

10000->生成RDB,1000->RDB,這個根據你自己的應用和業務的資料量,你自己去決定

AOF一定要開啟,fsync,everysec

auto-aof-rewrite-percentage 100: 就是當前AOF大小膨脹到超過上次100%,上次的兩倍
auto-aof-rewrite-min-size 64mb: 根據你的資料量來定,16mb,32mb

3.2、企業級的資料備份方案

RDB非常適合做冷備,每次生成之後,就不會再有修改了

資料備份方案

(1)寫crontab定時排程指令碼去做資料備份
(2)每小時都copy一份rdb的備份,到一個目錄中去,僅僅保留最近48小時的備份
(3)每天都保留一份當日的rdb的備份,到一個目錄中去,僅僅保留最近1個月的備份
(4)每次copy備份的時候,都把太舊的備份給刪了
(5)每天晚上將當前伺服器上所有的資料備份,傳送一份到遠端的雲服務上去

/usr/local/redis

每小時copy一次備份,刪除48小時前的資料

crontab -e

0 * * * * sh /usr/local/redis/copy/redis_rdb_copy_hourly.sh

redis_rdb_copy_hourly.sh

#!/bin/sh 

cur_date=`date +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=`date -d -48hour +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$del_date

每天copy一次備份

crontab -e

0 0 * * * sh /usr/local/redis/copy/redis_rdb_copy_daily.sh

redis_rdb_copy_daily.sh

#!/bin/sh 

cur_date=`date +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=`date -d -1month +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$del_date

每天一次將所有資料上傳一次到遠端的雲伺服器上去

3.3、資料恢復方案

3.3.1如果redis當前最新的AOF和RDB檔案出現了丟失/損壞,那麼可以嘗試基於該機器上當前的某個最新的RDB資料副本進行資料恢復

當前最新的AOF和RDB檔案都出現了丟失/損壞到無法恢復,一般不是機器的故障,人為把/var/redis/6379下的檔案給刪除了

在資料安全丟失的情況下,基於rdb冷備,如何完美的恢復資料,同時還保持aof和rdb的雙開?

停止redis,關閉aof,拷貝rdb備份,重啟redis,確認資料恢復,直接在命令列熱修改redis配置,開啟aof,這個redis就會將記憶體中的資料對應的日誌,寫入aof檔案中

此時aof和rdb兩份資料檔案的資料就同步了

redis config set熱修改配置引數,可配置檔案中的實際的引數沒有被持久化的修改,再次停止redis,手動修改配置檔案,開啟aof的命令,再次重啟redis

為什麼要按照上面的步驟操作:

appendonly.aof 和 dump.rdb都開啟的情況表下,優先用appendonly.aof去恢復資料,雖然你刪除了appendonly.aof,但是因為開啟了aof持久化,redis就一定會優先基於aof去恢復,即使檔案不在,那就建立一個新的空的aof檔案,停止redis,暫時在配置中關閉aof,然後拷貝一份rdb過來,再重啟redis,資料能不能恢復過來,可以恢復過來。腦子一熱,再關掉redis,手動修改配置檔案,開啟aof,再重啟redis,資料又沒了,空的aof檔案,所有資料又沒了

3.3.2 如果當前機器上的所有RDB檔案全部損壞,那麼從遠端的雲服務上拉取最新的RDB快照回來恢復資料

3.3.3 如果是發現有重大的資料錯誤,比如某個小時上線的程式一下子將資料全部汙染了,資料全錯了,那麼可以選擇某個更早的時間點,對資料進行恢復

舉個例子,12點上線了程式碼,發現程式碼有bug,導致程式碼生成的所有的快取資料,寫入redis,全部錯了

找到一份11點的rdb的冷備,然後按照上面的步驟,去恢復到11點的資料,不就可以了嗎
 

4.如何通過讀寫分離來承載讀請求QPS超過10萬+(master+slave)

redis單機qps可支撐幾萬,一般來說,對快取,一般都是用來支撐讀高併發的,寫的請求是比較少的,可能寫請求也就一秒鐘幾千,大量的請求都是讀,一秒鐘二十萬次讀

 

4.1 redis replication(複製)的核心機制

(1)redis採用非同步方式複製資料到slave節點,不過redis 2.8開始,slave node會週期性地確認自己每次複製的資料量
(2)一個master node是可以配置多個slave node的
(3)slave node也可以連線其他的slave node
(4)slave node做複製的時候,是不會block master node的正常工作的
(5)slave node在做複製的時候,也不會block對自己的查詢操作,它會用舊的資料集來提供服務; 但是複製完成的時候,需要刪除舊資料集,載入新資料集,這個時候就會暫停對外服務了
(6)slave node主要用來進行橫向擴容,做讀寫分離,擴容的slave node可以提高讀的吞吐量

 

4.2、master持久化對於主從架構的安全保障的意義

如果採用了主從架構,那麼建議必須開啟master node的持久化!

不建議用slave node作為master node的資料熱備,因為那樣的話,如果你關掉master的持久化,可能在master當機重啟的時候資料是空的,然後可能一經過複製,salve node資料也丟了

master -> RDB和AOF都關閉了 -> 全部在記憶體中

master當機,重啟,是沒有本地資料可以恢復的,然後就會直接認為自己IDE資料是空的

master就會將空的資料集同步到slave上去,所有slave的資料全部清空,100%的資料丟失

 

5 redis哨兵架構的相關基礎知識的講解

5.1、哨兵的介紹

sentinal,中文名是哨兵

哨兵是redis叢集架構中非常重要的一個元件,主要功能如下

(1)叢集監控,負責監控redis master和slave程式是否正常工作
(2)訊息通知,如果某個redis例項有故障,那麼哨兵負責傳送訊息作為報警通知給管理員
(3)故障轉移,如果master node掛掉了,會自動轉移到slave node上
(4)配置中心,如果故障轉移發生了,通知client客戶端新的master地址

哨兵本身也是分散式的,作為一個哨兵叢集去執行,互相協同工作

(1)故障轉移時,判斷一個master node是當機了,需要大部分的哨兵都同意才行,涉及到了分散式選舉的問題
(2)即使部分哨兵節點掛掉了,哨兵叢集還是能正常工作的,因為如果一個作為高可用機制重要組成部分的故障轉移系統本身是單點的,那就很坑爹了

目前採用的是sentinal 2版本,sentinal 2相對於sentinal 1來說,重寫了很多程式碼,主要是讓故障轉移的機制和演算法變得更加健壯和簡單

5.2、哨兵的核心知識

(1)哨兵至少需要3個例項,來保證自己的健壯性
(2)哨兵 + redis主從的部署架構,是不會保證資料零丟失的,只能保證redis叢集的高可用性
(3)對於哨兵 + redis主從這種複雜的部署架構,儘量在測試環境和生產環境,都進行充足的測試和演練

5.3、為什麼redis哨兵叢集只有2個節點無法正常工作

哨兵叢集必須部署2個以上節點

如果哨兵叢集僅僅部署了個2個哨兵例項,quorum=1

+----+         +----+
| M1 |---------| R1 |
| S1 |         | S2 |
+----+         +----+

Configuration: quorum = 1

master當機,s1和s2中只要有1個哨兵認為master當機就可以還行切換,同時s1和s2中會選舉出一個哨兵來執行故障轉移

同時這個時候,需要majority,也就是大多數哨兵都是執行的,2個哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2個哨兵都執行著,就可以允許執行故障轉移

但是如果整個M1和S1執行的機器當機了,那麼哨兵只有1個了,此時就沒有majority來允許執行故障轉移,雖然另外一臺機器還有一個R1,但是故障轉移不會執行

5.4、經典的3節點哨兵叢集

       +----+
       | M1 |
       | S1 |
       +----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+

Configuration: quorum = 2,majority

如果M1所在機器當機了,那麼三個哨兵還剩下2個,S2和S3可以一致認為master當機,然後選舉出一個來執行故障轉移

同時3個哨兵的majority是2,所以還剩下的2個哨兵執行著,就可以允許執行故障轉移

5.5、sdown和odown轉換機制

sdown是主觀當機,就一個哨兵如果自己覺得一個master當機了,那麼就是主觀當機

odown是客觀當機,如果quorum數量的哨兵都覺得一個master當機了,那麼就是客觀當機

5.6、slave->master選舉演算法

如果一個master被認為odown了,而且majority哨兵都允許了主備切換,那麼某個哨兵就會執行主備切換操作,此時首先要選舉一個slave來

會考慮slave的一些資訊

(1)跟master斷開連線的時長
(2)slave優先順序
(3)複製offset
(4)run id

如果一個slave跟master斷開連線已經超過了down-after-milliseconds的10倍,外加master當機的時長,那麼slave就被認為不適合選舉為master

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

接下來會對slave進行排序

(1)按照slave優先順序進行排序,slave priority越低,優先順序就越高
(2)如果slave priority相同,那麼看replica offset,哪個slave複製了越多的資料,offset越靠後,優先順序就越高
(3)如果上面兩個條件都相同,那麼選擇一個run id比較小的那個slave

5.7、哨兵的生產環境部署

daemonize yes
logfile /var/log/sentinal/5000

mkdir -p /var/log/sentinal/5000


6 哨兵主備切換的資料丟失問題:非同步複製、叢集腦裂

6.1、兩種資料丟失的情況

主備切換的過程,可能會導致資料丟失

(1)非同步複製導致的資料丟失

因為master -> slave的複製是非同步的,所以可能有部分資料還沒複製到slave,master就當機了,此時這些部分資料就丟失了

(2)腦裂導致的資料丟失

腦裂,也就是說,某個master所在機器突然脫離了正常的網路,跟其他slave機器不能連線,但是實際上master還執行著

此時哨兵可能就會認為master當機了,然後開啟選舉,將其他slave切換成了master

這個時候,叢集裡就會有兩個master,也就是所謂的腦裂

此時雖然某個slave被切換成了master,但是可能client還沒來得及切換到新的master,還繼續寫向舊master的資料可能也丟失了

因此舊master再次恢復的時候,會被作為一個slave掛到新的master上去,自己的資料會清空,重新從新的master複製資料
 

6.2、解決非同步複製和腦裂導致的資料丟失

min-slaves-to-write 1     要求至少有1個slave,資料複製和同步的延遲不能超過10秒
min-slaves-max-lag 10  一旦所有的slave,資料複製和同步的延遲都超過了10秒鐘,那麼這個時候,master就不會再接收任何請求了

上面兩個配置可以減少非同步複製和腦裂導致的資料丟失

(1)減少非同步複製的資料丟失

有了min-slaves-max-lag這個配置,就可以確保說,一旦slave複製資料和ack延時太長,就認為可能master當機後損失的資料太多了,那麼就拒絕寫請求,這樣可以把master當機時由於部分資料未同步到slave導致的資料丟失降低的可控範圍內

非同步複製導致資料丟失如何降低損失

(2)減少腦裂的資料丟失

如果一個master出現了腦裂,跟其他slave丟了連線,那麼上面兩個配置可以確保說,如果不能繼續給指定數量的slave傳送資料,而且slave超過10秒沒有給自己ack訊息,那麼就直接拒絕客戶端的寫請求,因此在腦裂場景下,最多就丟失10秒的資料

 

7 redis的叢集架構(企業級常用架構)

7.1、什麼是redis cluster

1能夠自動將資料分散在多個節點上

2當訪問的key不在當前分片上時,能夠自動將請求轉發至正確的分片

3當叢集中部分節點失效時仍能提供服務

 其中第三點是基於主從複製來實現的,Redis Cluster的每個資料分片都採用了主從複製的結構,原理和前文所述的主從複製完全一致,唯一的區別是省去了Redis Sentinel這一額外的元件,由Redis Cluster負責進行一個分片內部的節點監控和自動failover

在redis cluster架構下,每個redis要放開兩個埠號,比如一個是6379,另外一個就是加10000的埠號,比如16379

16379埠號是用來進行節點間通訊的,也就是cluster bus的東西,叢集匯流排。cluster bus的通訊,用來進行故障檢測,配置更新,故障轉移授權

cluster bus用了另外一種二進位制的協議(gossip協議),主要用於節點間進行高效的資料交換,佔用更少的網路頻寬和處理時間

 

7.2、redis cluster vs. replication + sentinal

從各個方面看,Redis Cluster都是優於主從複製的方案

Redis Cluster能夠解決單節點上資料量過大的問題

Redis Cluster能夠解決單節點訪問壓力過大的問題

Redis Cluster包含了主從複製的能力

軟體架構永遠不是越複雜越好,複雜的架構在帶來顯著好處的同時,一定也會帶來相應的弊端。採用Redis Cluster的弊端包括:

維護難度增加

客戶端資源消耗增加。

效能優化難度增加

事務和LUA Script的使用成本增加

7.2.1 如果你的資料量很少,主要是承載高併發高效能的場景,比如你的快取一般就幾個G,單機足夠了

replication,一個mater,多個slave,要幾個slave跟你的要求的讀吞吐量有關係,然後自己搭建一個sentinal叢集,去保證redis主從架構的高可用性,就可以了

7.2.2 redis cluster,主要是針對海量資料+高併發+高可用的場景,海量資料,如果你的資料量很大,那麼建議就用redis cluster

 

8.redis cluster的hash slot演算法

Redis Cluster的Hash slot演算法

工作原理如下

1、客戶端與Redis節點直連,不需要中間Proxy層,直接連線任意一個Master節點

2、根據公式HASH_SLOT=CRC16(key) mod 16384,計算出對映到哪個分片上,然後Redis會去相應的節點進行操作

具有如下優點:
(1)無需Sentinel哨兵監控,如果Master掛了,Redis Cluster內部自動將Slave切換Master
(2)可以進行水平擴容
(3)支援自動化遷移,當出現某個Slave當機了,那麼就只有Master了,這時候的高可用性就無法很好的保證了,萬一Master也當機了,咋辦呢? 針對這種情況,如果說其他Master有多餘的Slave ,叢集自動把多餘的Slave遷移到沒有Slave的Master 中。

缺點:
(1)批量操作是個坑
(2)資源隔離性較差,容易出現相互影響的情況

 

9.Redis節點間的內部通訊機制

9.1、基礎通訊原理

(1)redis cluster節點間採取gossip協議進行通訊

跟集中式不同,不是將叢集後設資料(節點資訊,故障,等等)集中儲存在某個節點上,而是互相之間不斷通訊,保持整個叢集所有節點的資料是完整的

維護叢集的後設資料一種叫做集中式,一種叫做gossip

集中式:好處在於,後設資料的更新和讀取,時效性非常好,一旦後設資料出現了變更,立即就更新到集中式的儲存中,其他節點讀取的時候立即就可以感知到; 不好在於,所有的後設資料的跟新壓力全部集中在一個地方,可能會導致後設資料的儲存有壓力

集中式的叢集後設資料儲存和維護

 

gossip:好處在於,後設資料的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新,有一定的延時,降低了壓力; 缺點,後設資料更新有延時,可能導致叢集的一些操作會有一些滯後

gossip協議維護叢集後設資料

 

(2)10000埠

每個節點都有一個專門用於節點間通訊的埠,就是自己提供服務的埠號+10000,比如7001,那麼用於節點間通訊的就是17001埠

每隔節點每隔一段時間都會往另外幾個節點傳送ping訊息,同時其他幾點接收到ping之後返回pong

(3)交換的資訊

故障資訊,節點的增加和移除,hash slot資訊,等等

9.2、gossip協議

gossip協議包含多種訊息,包括ping,pong,meet,fail,等等

meet: 某個節點傳送meet給新加入的節點,讓新節點加入叢集中,然後新節點就會開始與其他節點進行通訊

redis-trib.rb add-node

其實內部就是傳送了一個gossip meet訊息,給新加入的節點,通知那個節點去加入我們的叢集

ping: 每個節點都會頻繁給其他節點傳送ping,其中包含自己的狀態還有自己維護的叢集後設資料,互相通過ping交換後設資料

每個節點每秒都會頻繁傳送ping給其他的叢集,ping,頻繁的互相之間交換資料,互相進行後設資料的更新

pong: 返回ping和meet,包含自己的狀態和其他資訊,也可以用於資訊廣播和更新

fail: 某個節點判斷另一個節點fail之後,就傳送fail給其他節點,通知其他節點,指定的節點當機了

9.3、ping訊息深入

ping很頻繁,而且要攜帶一些後設資料,所以可能會加重網路負擔

每個節點每秒會執行10次ping,每次會選擇5個最久沒有通訊的其他節點

當然如果發現某個節點通訊延時達到了cluster_node_timeout / 2,那麼立即傳送ping,避免資料交換延時過長,落後的時間太長了

比如說,兩個節點之間都10分鐘沒有交換資料了,那麼整個叢集處於嚴重的後設資料不一致的情況,就會有問題

所以cluster_node_timeout可以調節,如果調節比較大,那麼會降低傳送的頻率

每次ping,一個是帶上自己節點的資訊,還有就是帶上1/10其他節點的資訊,傳送出去,進行資料交換

至少包含3個其他節點的資訊,最多包含總節點-2個其他節點的資訊

 

10.高可用性與主備切換原理

redis cluster的高可用的原理,幾乎跟哨兵是類似的

10.1、判斷節點當機

如果一個節點認為另外一個節點當機,那麼就是pfail,主觀當機

如果多個節點都認為另外一個節點當機了,那麼就是fail,客觀當機,跟哨兵的原理幾乎一樣,sdown,odown

在cluster-node-timeout內,某個節點一直沒有返回pong,那麼就被認為pfail

如果一個節點認為某個節點pfail了,那麼會在gossip ping訊息中,ping給其他節點,如果超過半數的節點都認為pfail了,那麼就會變成fail

10.2、從節點過濾

對當機的master node,從其所有的slave node中,選擇一個切換成master node

檢查每個slave node與master node斷開連線的時間,如果超過了cluster-node-timeout * cluster-slave-validity-factor,那麼就沒有資格切換成master

這個也是跟哨兵是一樣的,從節點超時過濾的步驟

10.3、從節點選舉

哨兵:對所有從節點進行排序,slave priority,offset,run id

每個從節點,都根據自己對master複製資料的offset,來設定一個選舉時間,offset越大(複製資料越多)的從節點,選舉時間越靠前,優先進行選舉

所有的master node開始slave選舉投票,給要進行選舉的slave進行投票,如果大部分master node(N/2 + 1)都投票給了某個從節點,那麼選舉通過,那個從節點可以切換成master

從節點執行主備切換,從節點切換為主節點

10.4、與哨兵比較

整個流程跟哨兵相比,非常類似,所以說,redis cluster功能強大,直接整合了replication和sentinal的功能

 

11 .jedis cluster api與redis cluster叢集互動的一些基本原理

11.1、基於重定向的客戶端

redis-cli -c,自動重定向

(1)請求重定向

(2)計算hash slot

(3)hash slot查詢

11.2、smart jedis

(1)什麼是smart jedis

基於重定向的客戶端,很消耗網路IO,因為大部分情況下,可能都會出現一次請求重定向,才能找到正確的節點

所以大部分的客戶端,比如java redis客戶端,就是jedis,都是smart的

本地維護一份hashslot -> node的對映表,快取,大部分情況下,直接走本地快取就可以找到hashslot -> node,不需要通過節點進行moved重定向

(2)JedisCluster的工作原理

在JedisCluster初始化的時候,就會隨機選擇一個node,初始化hashslot -> node對映表,同時為每個節點建立一個JedisPool連線池

每次基於JedisCluster執行操作,首先JedisCluster都會在本地計算key的hashslot,然後在本地對映表找到對應的節點

如果那個node正好還是持有那個hashslot,那麼就ok; 如果說進行了reshard這樣的操作,可能hashslot已經不在那個node上了,就會返回moved, 如果JedisCluter API發現對應的節點返回moved,那麼利用該節點的後設資料,更新本地的hashslot -> node對映表快取

重複上面幾個步驟,直到找到對應的節點,如果重試超過5次,那麼就報錯,JedisClusterMaxRedirectionException

 

12.redis在實踐中的一些常見問題以及優化思路

12.1、fork耗時導致高併發請求延時

RDB和AOF的時候,其實會有生成RDB快照,AOF rewrite,耗費磁碟IO的過程,主程式fork子程式

fork的時候,子程式是需要拷貝父程式的空間記憶體頁表的,也是會耗費一定的時間的

一般來說,如果父程式記憶體有1個G的資料,那麼fork可能會耗費在20ms左右,如果是10G~30G,那麼就會耗費20 * 10,甚至20 * 30,也就是幾百毫秒的時間

info stats中的latest_fork_usec,可以看到最近一次form的時長

redis單機QPS一般在幾萬,fork可能一下子就會拖慢幾萬條操作的請求時長,從幾毫秒變成1秒

優化思路

fork耗時跟redis主程式的記憶體有關係,一般控制redis的記憶體在10GB以內,slave -> master,全量複製

12.2、AOF的阻塞問題

redis將資料寫入AOF緩衝區,單獨開一個現場做fsync操作,每秒一次

但是redis主執行緒會檢查兩次fsync的時間,如果距離上次fsync時間超過了2秒,那麼寫請求就會阻塞

everysec,最多丟失2秒的資料

一旦fsync超過2秒的延時,整個redis就被拖慢

優化思路

優化硬碟寫入速度,建議採用SSD,不要用普通的機械硬碟,SSD,大幅度提升磁碟讀寫的速度

12.3、主從複製延遲問題

主從複製可能會超時嚴重,這個時候需要良好的監控和報警機制

在info replication中,可以看到master和slave複製的offset,做一個差值就可以看到對應的延遲量

如果延遲過多,那麼就進行報警

12.4、主從複製風暴問題

如果一下子讓多個slave從master去執行全量複製,一份大的rdb同時傳送到多個slave,會導致網路頻寬被嚴重佔用

如果一個master真的要掛載多個slave,那儘量用樹狀結構,不要用星型結構

12.5、vm.overcommit_memory

0: 檢查有沒有足夠記憶體,沒有的話申請記憶體失敗(預設)
1: 允許使用記憶體直到用完為止
2: 記憶體地址空間不能超過swap + 50%

如果是0的話,可能導致類似fork等操作執行失敗,申請不到足夠的記憶體空間

cat /proc/sys/vm/overcommit_memory
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1

12.6、swapiness

cat /proc/version,檢視linux核心版本

如果linux核心版本<3.5,那麼swapiness設定為0,這樣系統寧願swap也不會oom killer(殺掉程式)
如果linux核心版本>=3.5,那麼swapiness設定為1,這樣系統寧願swap也不會oom killer

保證redis不會在記憶體不充足時被殺掉

echo 0 > /proc/sys/vm/swappiness
echo vm.swapiness=0 >> /etc/sysctl.conf

12.7、最大開啟檔案控制程式碼

ulimit -n 10032 10032

自己去上網搜一下,不同的作業系統,版本,設定的方式都不太一樣

12.8、tcp backlog

cat /proc/sys/net/core/somaxconn
echo 511 > /proc/sys/net/core/somaxconn

12.9、長耗時命令

Redis提供了Slow Log功能,可以自動記錄耗時較長的命令。相關的配置引數有兩個:

slowlog-log-slower-than xxxms  #執行時間慢於xxx毫秒的命令計入Slow Log

slowlog-max-len xxx  #Slow Log的長度,即最大紀錄多少條Slow Log

使用SLOWLOG GET [number]命令,可以輸出最近進入Slow Log的number條命令。
使用SLOWLOG RESET命令,可以重置Slow Log

避免在使用這些O(N)命令時發生問題主要有幾個辦法:

1、不要把List當做列表使用,僅當做佇列來使用

2、通過機制嚴格控制Hash、Set、Sorted Set的大小

3、可能的話,將排序、並集、交集等操作放在客戶端執行

4、絕對禁止使用KEYS命令

5、避免一次性遍歷集合型別的所有成員,而應使用SCAN類的命令進行分批的,遊標式的遍歷

Redis提供了SCAN命令,可以對Redis中儲存的所有key進行遊標式的遍歷,避免使用KEYS命令帶來的效能問題。同時還有SSCAN/HSCAN/ZSCAN等命令,分別用於對Set/Hash/Sorted Set中的元素進行遊標式遍歷。SCAN類命令的使用請參考官方文件:https://redis.io/commands/scan
 

13.redis的雪崩和穿透

13.1快取雪崩

快取雪崩現象
如何解決快取雪崩

  

    事前:redis高可用,主從+哨兵,redis cluster,避免全盤崩潰
    事中:本地ehcache快取 + hystrix限流&降級,避免MySQL被打死
    事後:redis持久化,快速恢復快取資料

13.2快取穿透

快取穿透現象以及解決方案

 

解決方案:每次從資料庫裡沒查到資料,就寫一個空值到快取裡 (或則利用布隆過濾器判斷key是否存在)

 注:快取穿透要麼是自己系統出bug,要麼就是被黑客攻擊

 

14、Redis的併發競爭問題該如何解決

這個也是線上非常常見的一個問題,就是多客戶端同時併發寫一個key,可能本來應該先到的資料後到了,導致資料版本錯了。或者是多客戶端同時獲取一個key,修改值之後再寫回去,只要順序錯了,資料就錯了。

Redis有一系列的命令,特點是以NX結尾,NX是Not Exists的縮寫,如SETNX命令就應該理解為:SET if Not Exists。這系列的命令非常有用,可以使用SETNX來實現分散式鎖。 

如果在公司裡落地生產環境用分散式鎖的時候,一定是會用開源類庫的,比如Redis分散式鎖,一般就是用Redisson框架就好了,非常的簡便易用。 

 

Redis分散式鎖實現原理

 

 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * 分散式鎖
 * 效能待測試
 * @author lzhcode
 *
 */
public class RedisLock {

	  private static Logger logger = LoggerFactory.getLogger(RedisLock.class);

	    private RedisTemplate redisTemplate;

	    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

	    /**
	     * Lock key path.
	     */
	    private String lockKey;

	    /**
	     * 鎖超時時間,防止執行緒在入鎖以後,無限的執行等待
	     */
	    private int expireMsecs = 60 * 1000;

	    /**
	     * 鎖等待時間,防止執行緒飢餓
	     */
	    private int timeoutMsecs = 10 * 1000;

	    private volatile boolean locked = false;

	    /**
	     * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
	     *
	     * @param lockKey lock key (ex. account:1, ...)
	     */
	    public RedisLock(RedisTemplate redisTemplate, String lockKey) {
	        this.redisTemplate = redisTemplate;
	        this.lockKey = lockKey + "_lock";
	    }

	    /**
	     * Detailed constructor with default lock expiration of 60000 msecs.
	     *
	     */
	    public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs) {
	        this(redisTemplate, lockKey);
	        this.timeoutMsecs = timeoutMsecs;
	    }

	    /**
	     * Detailed constructor.
	     *
	     */
	    public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
	        this(redisTemplate, lockKey, timeoutMsecs);
	        this.expireMsecs = expireMsecs;
	    }

	    /**
	     * @return lock key
	     */
	    public String getLockKey() {
	        return lockKey;
	    }

	    private String get(final String key) {
	        Object obj = null;
	        try {
	            obj = redisTemplate.execute(new RedisCallback<Object>() {
	                @Override
	                public Object doInRedis(RedisConnection connection) throws DataAccessException {
	                    StringRedisSerializer serializer = new StringRedisSerializer();
	                    byte[] data = connection.get(serializer.serialize(key));
	                    connection.close();
	                    if (data == null) {
	                        return null;
	                    }
	                    return serializer.deserialize(data);
	                }
	            });
	        } catch (Exception e) {
	            logger.error("get redis error, key : {}", key);
	        }
	        return obj != null ? obj.toString() : null;
	    }

	    private boolean setNX(final String key, final String value) {
	        Object obj = null;
	        try {
	            obj = redisTemplate.execute(new RedisCallback<Object>() {
	                @Override
	                public Object doInRedis(RedisConnection connection) throws DataAccessException {
	                    StringRedisSerializer serializer = new StringRedisSerializer();
	                    Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
	                    connection.close();
	                    return success;
	                }
	            });
	        } catch (Exception e) {
	            logger.error("setNX redis error, key : {}", key);
	        }
	        return obj != null ? (Boolean) obj : false;
	    }

	    private String getSet(final String key, final String value) {
	        Object obj = null;
	        try {
	            obj = redisTemplate.execute(new RedisCallback<Object>() {
	                @Override
	                public Object doInRedis(RedisConnection connection) throws DataAccessException {
	                    StringRedisSerializer serializer = new StringRedisSerializer();
	                    byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
	                    connection.close();
	                    return serializer.deserialize(ret);
	                }
	            });
	        } catch (Exception e) {
	            logger.error("setNX redis error, key : {}", key);
	        }
	        return obj != null ? (String) obj : null;
	    }

	    /**
	     * 獲得 lock.
	     * 實現思路: 主要是使用了redis 的setnx命令,快取了鎖.
	     * reids快取的key是鎖的key,所有的共享, value是鎖的到期時間(注意:這裡把過期時間放在value了,沒有時間上設定其超時時間)
	     * 執行過程:
	     * 1.通過setnx嘗試設定某個key的值,成功(當前沒有這個鎖)則返回,成功獲得鎖
	     * 2.鎖已經存在則獲取鎖的到期時間,和當前時間比較,超時的話,則設定新的值
	     *
	     * @return true if lock is acquired, false acquire timeouted
	     * @throws InterruptedException in case of thread interruption
	     */
	    public   boolean lock() throws InterruptedException {
	        int timeout = timeoutMsecs;
	        while (timeout >= 0) {
	            long expires = System.currentTimeMillis() + expireMsecs + 1;
	            String expiresStr = String.valueOf(expires); //鎖到期時間
	            if (this.setNX(lockKey, expiresStr)) {
	                // lock acquired
	                locked = true;
	                return true;
	            }

	            String currentValueStr = this.get(lockKey); //redis裡的時間
	            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
	                //判斷是否為空,不為空的情況下,如果被其他執行緒設定了值,則第二個條件判斷是過不去的
	                // lock is expired

	                String oldValueStr = this.getSet(lockKey, expiresStr);
	                //獲取上一個鎖到期時間,並設定現在的鎖到期時間,
	                //只有一個執行緒才能獲取上一個線上的設定時間,因為jedis.getSet是同步的
	                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
	                    //防止誤刪(覆蓋,因為key是相同的)了他人的鎖——這裡達不到效果,這裡值會被覆蓋,但是因為什麼相差了很少的時間,所以可以接受

	                    //[分散式的情況下]:如過這個時候,多個執行緒恰好都到了這裡,但是隻有一個執行緒的設定值和當前值相同,他才有權利獲取鎖
	                    // lock acquired
	                    locked = true;
	                    return true;
	                }
	            }
	            timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;

	            /*
	                延遲100 毫秒,  這裡使用隨機時間可能會好一點,可以防止飢餓程式的出現,即,當同時到達多個程式,
	                只會有一個程式獲得鎖,其他的都用同樣的頻率進行嘗試,後面有來了一些進行,也以同樣的頻率申請鎖,這將可能導致前面來的鎖得不到滿足.
	                使用隨機的等待時間可以一定程度上保證公平性
	             */
	            Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);

	        }
	        return false;
	    }


	    /**
	     * Acqurired lock release.
	     */
	    public   void unlock() {
	        if (locked) {
	            redisTemplate.delete(lockKey);
	            locked = false;
	        }
	    }

}

和RedisLock和Redisson表面來看,這個方案似乎很管用,但是這裡存在一個問題:在我們的系統架構裡存在一個單點故障,如果Redis的master節點當機了怎麼辦呢?有人可能會說:加一個slave節點!在master當機時用slave就行了!但是其實這個方案明顯是不可行的,因為這種方案無法保證第1個安全互斥屬性,因為Redis的複製是非同步的。 總的來說,這個方案裡有一個明顯的競爭條件(race condition),舉例來說:

  • 客戶端A在master節點拿到了鎖。
  • master節點在把A建立的key寫入slave之前當機了。
  • slave變成了master節點
  • B也得到了和A還持有的相同的鎖(因為原來的slave裡還沒有A持有鎖的資訊)

下面的表格總結了Zookeeper和Redis分散式鎖的優缺點:

 

15、關於redis生產分散式叢集的常見實現方案

15.1基於客戶端的分片(不常用)

現在專案中用的是Sharded jedis,提供了一致性hash和md5雜湊兩種hash演算法,預設使用一致性hash演算法。並且為了使得請求能均勻的落在不同的節點上,Sharded jedis會使用節點的名稱(如果節點沒有名稱使用預設名稱)虛擬化出160個虛擬節點。也可以根據不同節點的weight,虛擬化出160*weight個節點。

當客戶端訪問redis時,首先根據key計算出其落在哪個節點上,然後找到節點的ip和埠進行連線訪問。

15.2基於代理的redis分片(Proxy+Replication+Sentinel)

proxy,客戶端通過proxy與redis伺服器進行互動,客戶端並不知道proxy後方的redis伺服器的架構部署,對客戶端來說,proxy服務就相當於一臺單獨的redis服務,也就說對於客戶端來說,redis的叢集架構以及後期的擴容遷移等操作都是透明的,redis服務的叢集分片、架構變化都是由代理服務proxy來維護的。

這裡以Twemproxy為例說明,如下圖所示

Proxy+Replication+Sentinel

 

工作原理如下

1、前端使用Twemproxy+KeepAlived做代理,將其後端的多臺Redis例項分片進行統一管理與分配

2、每一個分片節點的Slave都是Master的副本且只讀

3、Sentinel持續不斷的監控每個分片節點的Master,當Master出現故障且不可用狀態時,Sentinel會通知/啟動自動故障轉移等動作

4、Sentinel 可以在發生故障轉移動作後觸發相應指令碼(通過 client-reconfig-script 引數配置 ),指令碼獲取到最新的Master來修改Twemproxy配置

     缺陷:
    (1)部署結構超級複雜
    (2)可擴充套件性差,進行擴縮容需要手動干預
    (3)運維不方便

15.3基於redis伺服器的分片(Redis Cluster)

基於redis伺服器的分片,又可稱之為“查詢路由”,就是客戶端隨機連線redis叢集中的一個節點,向其傳送讀寫請求,如果這個請求不能夠被當前節點處理,則這個節點會將請求轉發給正確的節點來處理(這一點很像zookeeper中的主從寫請求機制),有的實現並不會由當前節點轉發給其他節點,而是當前節點響應給客戶端一個正確節點的資訊,由客戶端再次向正確的節點發出請求。

 

16、你們生產環境中的redis是怎麼部署的

redis cluster,10臺機器,5臺機器部署了redis主例項,另外5臺機器部署了redis的從例項,每個主例項掛了一個從例項,5個節點對外提供讀寫服務,每個節點的讀寫高峰qps可能可以達到每秒5萬,5臺機器最多是25萬讀寫請求/s。

機器是什麼配置?32G記憶體+8核CPU+1T磁碟,但是分配給redis程式的是10g記憶體,一般線上生產環境,redis的記憶體儘量不要超過10g,超過10g可能會有問題。

5臺機器對外提供讀寫,一共有50g記憶體。

因為每個主例項都掛了一個從例項,所以是高可用的,任何一個主例項當機,都會自動故障遷移,redis從例項會自動變成主例項繼續提供讀寫服務

你往記憶體裡寫的是什麼資料?每條資料的大小是多少?商品資料,每條資料是10kb。100條資料是1mb,10萬條資料是1g。常駐記憶體的是200萬條商品資料,佔用記憶體是20g,僅僅不到總記憶體的50%。

目前高峰期每秒就是3500左右的請求量

 

17、Redis Java客戶端的選擇

Redis的Java客戶端很多,官方推薦的有三種:Jedis、Redisson和lettuce。

在這裡對Jedis和Redisson進行對比介紹

Jedis:

輕量,簡潔,便於整合和改造

支援連線池

支援pipelining、事務、LUA Scripting、Redis Sentinel、Redis Cluster

不支援讀寫分離,需要自己實現

文件差(真的很差,幾乎沒有……)

Redisson:

基於Netty實現,採用非阻塞IO,效能高

支援非同步請求

支援連線池

支援pipelining、LUA Scripting、Redis Sentinel、Redis Cluster

不支援事務,官方建議以LUA Scripting代替事務

支援在Redis Cluster架構下使用pipelining

支援讀寫分離,支援讀負載均衡,在主從複製和Redis Cluster架構下都可以使用

內建Tomcat Session Manager,為Tomcat 6/7/8提供了會話共享功能

可以與Spring Session整合,實現基於Redis的會話共享

文件較豐富,有中文文件

相關文章