四萬多字徹底瞭解Redis

- Redis 簡介

<code>   Redis 是完全開源免費的,用C語言編寫的,遵守BSD開源協議,是一個高性能的支持網絡、基於內存、(key/value)分佈式內存數據庫,並支持持久化的NoSQL內存型數據庫,並提供多種語言的API支持。/<code> 

- Redis 數據結構及底層內部編碼

Redis支持多種數據結構,包括String(字符串)、Hash(哈希表)、List(鏈表)、Set(集合)、Zset(有序集合)、bitmap(位圖)、GEO、Hyperloglogs、Streams(Redis5.0引入)等。我們之前經常說的這幾種結構知識對外的編碼,對外的表現形式,實際上以上的每種數據結構都對應著底層的內部編碼實現,有的是一種結構對應著多種編碼實現,這些底層編碼實現包括SDS簡單動態字符串(int、raw、embstr )、intset(整數集合)、skiplist(跳錶)、ziplist(壓縮列表)、hashtable等內部編碼。如下圖所示

四萬多字徹底瞭解Redis

Redis為什麼這樣設計呢?或者說他這樣設計有什麼好處?1:這樣可以在不改變外部結構和操作命令的情況下,改變內部編碼結構對用戶無感,假如後期有更加優秀的內部編碼的時候直接替換即可,說到這裡你是否想到一種設計模式呢?對的就是門面設計模式。2:一種數據結構對應多種內部編碼,這樣可以用戶存的數據進行選擇合適的內部編碼結構,這樣可以讓Redis發揮出最大的性能,在不同場景下發揮各自的優勢。

  • String的應用場景及內部編碼應用場景:可以對 String 進行自增自減運算,從而實現計數器功能。Redis 這種內存型數據庫的讀寫性能非常高,很適合存儲頻繁讀寫的計數量。這也是我們經常用到的類型之一,存儲簡單的鍵值對信息。同時也適合最簡單的k-v存儲,類似於memcached的存儲結構,短信驗證碼,配置信息等,就用這種類型來存儲。內部編碼:由上圖可知,String的3種內部編碼分別是:int、embstr、raw。int類型很好理解,當一個key的value是整型時,Redis就將其編碼為int類型(另外還有一個條件:把這個value當作字符串來看,它的長度不能超過20)。如下所示。這種編碼類型為了節省內存。Redis默認會緩存10000個整型值(#define OBJ_SHARED_INTEGERS 10000),這就意味著,如果有10個不同的KEY,其value都是10000以內的值,事實上全部都是共享同一個對象:
<code>[root@honest bin]# ./redis-cli 127.0.0.1:6379> set test '1234'OK127.0.0.1:6379> object encoding test"int"127.0.0.1:6379> set test 1234OK127.0.0.1:6379> object encoding test"int"127.0.0.1:6379> /<code>

接下來就是int和ebmstr兩種內部編碼的長度界限,請看下面的測試:

四萬多字徹底瞭解Redis

從圖上的測試結果上來int和embstr的界限是20,當長度在1-19且存儲的是數字類型的話編碼是int。當存儲的是字符數據的時候編碼為embstr,那麼ebmstr和raw的長度界限又在哪裡呢?接下來就是ebmstr和raw兩種內部編碼的長度界限,請看下面的源碼:

<code>#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44robj *createStringObject(const char *ptr, size_t len) {    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)        return createEmbeddedStringObject(ptr,len);    else        return createRawStringObject(ptr,len);}/<code>

也就是說,embstr和raw編碼的長度界限是44,我們可以做如下驗證。長度超過44以後,就是raw編碼類型,不會有任何優化,是多長,就要消耗多少內存:

<code>127.0.0.1:6379> set name "a1234567890123456789012345678901234567890123"OK127.0.0.1:6379> object encoding name"embstr"127.0.0.1:6379> set name "a12345678901234567890123456789012345678901234"OK127.0.0.1:6379> object encoding name"raw"/<code>

那麼為什麼有embstr編碼呢?它相比raw的優勢在哪裡?embstr編碼將創建字符串對象所需的空間分配的次數從raw編碼的兩次降低為一次。因為embstr編碼的字符串對象的所有數據都保存在一塊連續的內存裡面,所以這種編碼的字符串對象比起raw編碼的字符串對象能更好地利用緩存帶來的優勢。並且釋放embstr編碼的字符串對象只需要調用一次內存釋放函數,而釋放raw編碼對象的字符串對象需要調用兩次內存釋放函數。如下圖所示,左邊是embstr編碼,右邊是raw編碼:

四萬多字徹底瞭解Redis

  • ziplist內部編碼

List,Hash,Zset的內部編碼均都有 ziplist,他們分別在什麼情況下會選用ziplist呢?

以Hash為例,我們首先看一下什麼條件下它的內部編碼是ziplist:

<code>當哈希類型元素個數小於hash-max-ziplist-entries配置(默認512個);所有值都小於hash-max-ziplist-value配置(默認64個字節);/<code>

如果是sorted set的話,同樣需要滿足兩個條件:

<code>元素個數小於zset-max-ziplist-entries配置,默認128;所有值都小於zset-max-ziplist-value配置,默認64。/<code>

其中ziplist本質上是一本表(list)而不是一個鏈表(linked list),他和普通鏈表不一樣,他會整體佔用一大塊內存,這樣避免了內存碎片效率更為高效。ziplist的源碼在ziplist.c這個文件中,其中有一段這樣的描述 – The general layout of the ziplist is as follows::

<code><zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>/<entry>/<entry>/<entry>/<zllen>/<zltail>/<zlbytes>/<code>

zlbytes:表示這個ziplist佔用了多少空間,或者說佔了多少字節,這其中包括了zlbytes本身佔用的4個字節;zltail:表示到ziplist中最後一個元素的偏移量,有了這個值,pop操作的時間複雜度就是O(1)了,即不需要遍歷整個ziplist;zllen:表示ziplist中有多少個entry,即保存了多少個元素。由於這個字段佔用16個字節,所以最大值是2^16-1,也就意味著,如果entry的數量超過2的16次方-1時,需要遍歷整個ziplist才知道entry的數量;entry:真正保存的數據,有它自己的編碼;zlend:專門用來表示ziplist尾部的特殊字符,佔用8個字節,值固定為255,即8個字節每一位都是1。

如下就是一個真實的ziplist編碼,包含了2和5兩個元素:

四萬多字徹底瞭解Redis

  • linkedlist內部編碼這個他的底層就是用java的LinkedList實現,採用雙向鏈表的數據結構,這裡簡單講一下LinkedList的特點,它允許插入所有元素,包括null,同時,它是線程不同步的,也就是現場不安全。他的結構如下圖:雙向鏈表每個結點除了數據域之外,還有一個前指針和後指針,分別指向前驅結點和後繼結點(如果有前驅/後繼的話)。另外,雙向鏈表還有一個first指針,指向頭節點,和last指針,指向尾節點。相比數組,鏈表的特點就是在指定位置插入和刪除元素的效率較高,但是查找的效率就不如數組那麼高了。
  • skiplist內部編碼這個他的底層就是用java的跳錶實現,跳錶(SkipList),是一種可以快速查找的數據結構,類似於平衡樹。它們都可以對元素進行快速的查找。因為跳錶是基於鏈表的(具體結構等下會將),因此,它的插入和刪除效率比較高。因此在高併發的環境下,如果是平衡樹,你需要一個全局鎖來保證整個樹的線程安全,而對於跳錶,你只需要局部鎖來控制即可。對於查詢而言,通過"空間來換取時間"的一個算法,建立多級索引,實現以二分查找遍歷一個有序鏈表。時間複雜度等同於紅黑樹,O(log n)。但實現卻遠遠比紅黑樹要簡單。
  • hashtable內部編碼這個他的底層就是用java的HashMap實現,HashMap 的大致結構如下圖所示,其中哈希表是一個數組,我們經常把數組中的每一個節點稱為一個桶,哈希表中的每個節點都用來存儲一個鍵值對。在插入元素時,如果發生衝突(即多個鍵值對映射到同一個桶上)的話,就會通過鏈表的形式來解決衝突。因為一個桶上可能存在多個鍵值對,所以在查找的時候,會先通過key的哈希值先定位到桶,再遍歷桶上的所有鍵值對,找出key相等的鍵值對,從而來獲取value。
四萬多字徹底瞭解Redis

intset內部編碼

Set特殊內部編碼,當滿足下面的條件時Set的內部編碼就是intset而不是hashtable:

<code>Set集合中必須是64位有符號的十進制整型;元素個數不能超過set-max-intset-entries配置,默認512;/<code>

驗證如下:

<code>127.0.0.1:6379> sadd scores 135(integer) 0127.0.0.1:6379> sadd scores 128(integer) 1127.0.0.1:6379> object encoding scores"intset"/<code>

那麼intset編碼到底是個什麼東西呢?看它的源碼定義如下,很明顯,就是整型數組,並且是一個有序的整型數組。它在內存分配上與ziplist有些類似,是連續的一整塊內存空間,而且對於大整數和小整數採取了不同的編碼,儘量對內存的使用進行了優化。這樣的數據結構,如果執行SISMEMBER命令,即查看某個元素是否在集合中時,事實上使用的是二分查找法:

<code>typedef struct intset {    uint32_t encoding;    uint32_t length;    int8_t contents[];} intset;// intset編碼查找方法源碼(人為簡化),標準的二分查找法:static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {    int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;    int64_t cur = -1;    while(max >= min) {        mid = ((unsigned int)min + (unsigned int)max) >> 1;        cur = _intsetGet(is,mid);        if (value > cur) {            min = mid+1;        } else if (value < cur) {            max = mid-1;        } else {            break;        }    }    if (value == cur) {        if (pos) *pos = mid;        return 1;    } else {        if (pos) *pos = min;        return 0;    }}#define INTSET_ENC_INT16 (sizeof(int16_t))#define INTSET_ENC_INT32 (sizeof(int32_t))#define INTSET_ENC_INT64 (sizeof(int64_t))/<code>
  • bitmap內部編碼

這是Redis實現的一個布隆過濾器,bitmap並不是一種真實的數據結構,它本質上是String數據結構,只不過操作的粒度變成了位,即bit。因為String類型最大長度為512MB,所以bitmap最多可以存儲2^32個bit。假設已經有3個元素a、b和c,分別通過3個hash算法h1()、h2()和h2()計算然後對一個bit進行賦值,接下來假設需要判斷d是否已經存在,那麼也需要使用3個hash算法h1()、h2()和h2()對d進行計算,然後得到3個bit的值,恰好這3個bit的值為1,這就能夠說明:d可能存在集合中。再判斷e,由於h1(e)算出來的bit之前的值是0,那麼說明:e一定不存在集合中,如下圖

四萬多字徹底瞭解Redis

  • GEO內部編碼Geo本身不是一種數據結構,它本質上還是藉助於Sorted Set(ZSET),並且使用GeoHash技術進行填充。Redis中將經緯度使用52位的整數進行編碼,放進zset中,score就是GeoHash的52位整數值。在使用Redis進行Geo查詢時,其內部對應的操作其實就是zset(skiplist)的操作。通過zset的score進行排序就可以得到座標附近的其它元素,通過將score還原成座標值就可以得到元素的原始座標。GEO數據結構可以在Redis中存儲地理座標,並且座標有限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 規定如下:有效的經度從-180度到180度。有效的緯度從-85.05112878度到85.05112878度。
<code>redis> GEOADD city 114.031040 22.324386 "shenzhen" 112.572154 22.267832 "guangzhou"(integer) 2redis> GEODIST city shenzhen guangzhou"150265.8106"/<code>

當座標位置超出上述指定範圍時,該命令將會返回一個錯誤。添加地理位置命令如下:總之,Redis中處理這些地理位置座標點的思想是:二維平面座標點 --> 一維整數編碼值 --> zset(score為編碼值) --> zrangebyrank(獲取score相近的元素)、zrangebyscore --> 通過score(整數編碼值)反解座標點 --> 附近點的地理位置座標。

  • Streams內部編碼streams底層的數據結構是radix tree(基數樹),事實上就幾乎相同是傳統的二叉樹。僅僅是在尋找方式上,以一個unsigned int類型數為例,利用這個數的每個比特位作為樹節點的推斷。能夠這樣說,比方一個數10001010101010110101010,那麼依照Radix 樹的插入就是在根節點,假設遇到0,就指向左節點,假設遇到1就指向右節點,在插入過程中構造樹節點,在刪除過程中刪除樹節點。在Redis源碼的rax.h文件中有一段這樣的描述,這樣看起來是不是就比較直觀了:
  • HyperLogLog

Redis HyperLogLog 是用來做基數統計的算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。比如數據集 {1, 3, 5, 7, 5, 7, 8}, 那麼這個數據集的基數集為 {1, 3, 5 ,7, 8}, 基數(不重複元素)為5。 基數估計就是在誤差可接受的範圍內,快速計算基數。

- Redis 的事務機制和管道技術Pipelining

事務的四大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)事務的屬性:傳播行為、隔離級別、只讀和事務超時個人見解:這裡小僧認為事務的特性和屬性不是一個定西,特性側重於說明特點,二屬性則側重於說明本身就有的東西,這裡舉個例子人有鼻子 腿 眼睛 耳朵 這是屬性只要是正常人都有這些東西,但是這個人長得帥 騷氣 這屬於特性,有特點。關於事務的一些特性和屬性的一些具體解釋小僧這裡就不重複複述了,不懂的自行百度即可。Redis的事務屬性主要通過5個命令來操作的,分別是 multi、exec、watch、unwatch、discard。下面分別介紹下這屋命令的意思:watch key1 key2 … : 監視一或多個key,如果在事務執行之前,被監視的key被其他命令改動,則事務被打斷 ( 類似樂觀鎖 )

multi : 標記一個事務塊的開始( queued )

exec : 執行所有事務塊的命令 ( 一旦執行exec後,之前加的監控鎖都會被取消掉 )

discard : 取消事務,放棄事務塊中的所有命令

unwatch : 取消watch對所有key的監控下面用多種案例實際操作下:(1)正常執行的情況下,multi開啟事務 然後輸入一個或者多個命令這是所有命名沒有執行,當執行exec操作後是開始執行

四萬多字徹底瞭解Redis

(2)放棄事務,multi開啟事務 然後輸入一個或者多個命令這是所有命名沒有執行,當執行discard操作後怎放棄執行,不作任何操作,在上一步中把name 的值設置為了ergou,所以get的時候獲得ergou

四萬多字徹底瞭解Redis

(3)若在事務隊列中存在命令性錯誤(類似於java編譯性錯誤),則執行EXEC命令時,所有命令都不會執行

四萬多字徹底瞭解Redis

(4)若在事務隊列中存在語法性錯誤(類似於java的1/0的運行時異常),則執行EXEC命令時,其他正確命令會被執行,錯誤命令拋出異常。

四萬多字徹底瞭解Redis

(5)使用watch檢測餘額balance,事務期間balance數據未變動,事務執行成功

四萬多字徹底瞭解Redis

(6)使用watch檢測balance,在開啟事務後(一窗口),在2窗口執行的操作,更改balance的值,模擬其他客戶端在事務執行期間更改watch監控的數據,然後再執行一窗口後命令,執行EXEC後,事務未成功執行。

四萬多字徹底瞭解Redis

四萬多字徹底瞭解Redis

一但執行 EXEC 開啟事務的執行後,無論事務使用執行成功, WARCH 對變量的監控都將被取消。故當事務執行失敗後,需重新執行WATCH命令對變量進行監控,並開啟新的事務進行操作。WARCH 指令類似於樂觀鎖(你有沒有想到java的CAS呢?),在事務提交時,如果watch監控的多個KEY中任何KEY的值已經被其他客戶端更改,則使用EXEC執行事務時,事務隊列將不會被執行,同時返回Nullmulti-bulk應答以通知調用者事務執行失敗。這裡你應該問Redis為什麼不支持回滾操作呢?解答:在正常編程中上面案例(3)幾乎是不可能出現的,不過案例(4)怎有可能出現,但是Redis作者認為既然使用Redis他會默認你的操作都是正確的,為了保證高性能就不支持回滾操作了。

  • 管道PipeliningRedis是一種基於客戶端-服務端模型以及請求/響應協議的TCP服務。這意味著通常情況下一個請求會遵循以下步驟:客戶端向服務端發送一個查詢請求,並監聽Socket返回,通常是以阻塞模式,等待服務端響應。服務端處理命令,並將結果返回給客戶端。redis客戶端執行一條命令分4個過程: 發送命令-〉命令排隊-〉命令執行-〉返回結果這個過程稱為Round trip time(簡稱RTT, 往返時間),Redis提供許多批量操作的命令,如MSET/MGET/HMSET/HMGET等等,這些命令存在的意義是減少維護網絡連接和傳輸數據所消耗的資源和時間。但大部分命令(如hgetall,並沒有mhgetall)不支持批量操作,需要消耗N次RTT(Round trip time往返時間) ,這個時候需要pipeline來解決這個問題。

然而,如果客戶端要連續執行的多次操作無法通過Redis命令組合在一起,例如:SET a "abc"INCR bHSET c name "hi"

此時便可以使用Redis提供的pipelining功能來實現在一次交互中執行多條命令。使用pipelining時,只需要從客戶端一次向Redis發送多條命令(以\\r\\n)分隔,Redis就會依次執行這些命令,並且把每個命令的返回按順序組裝在一起一次返回,比如:$ (printf "PING\\r\\nPING\\r\\nPING\\r\\n"; sleep 1) | nc localhost 6379+PONG+PONG+PONG大部分的Redis客戶端都對Pipelining提供支持,所以開發者通常並不需要自己手工拼裝命令列表。Pipelining的優勢管道技術最顯著的優勢是提高了 redis 服務的性能。Pipelining的侷限性Pipelining只能用於執行連續且無相關性的命令,當某個命令的生成需要依賴於前一個命令的返回時,就無法使用Pipelining了。

- Redis 的分佈式鎖

在瞭解Redi的分佈式鎖之前先來看一下線程鎖和進程鎖、分佈式鎖的區別線程鎖:主要用來給方法、代碼塊加鎖。當某個方法或代碼使用鎖,在同一時刻僅有一個線程執行該方法或該代碼段。線程鎖只在同一JVM中有效果,因為線程鎖的實現在根本上是依靠線程之間共享內存實現的,比如synchronized是共享對象頭,顯示鎖Lock是共享某個變量(state)。

進程鎖:為了控制同一操作系統中多個進程訪問某個共享資源,因為進程具有獨立性,各個進程無法訪問其他進程的資源,因此無法通過synchronized等線程鎖實現進程鎖。

分佈式鎖:當多個進程不在同一個系統中,用分佈式鎖控制多個進程對資源的訪問。

  • 分佈式鎖解決了什麼問題呢?

隨著業務越來越複雜,應用服務都會朝著分佈式、集群方向部署,而分佈式CAP原則告訴我們,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可得兼。實踐中一般滿足CP或AP。很多場景中,需要使用分佈式事務、分佈式鎖等技術來保證數據最終一致性。有的時候,我們需要保證某一方法同一時刻只能被一個線程執行。在單機環境中,多個線程對共享變量進行訪問時,我們可以簡單的通過Java自身的同步操作來協調同一時刻對變量的串行訪問。然而在分佈式環境中,進程的獨立性,進程之間無法訪問相互之間的資源,無法像之前那樣的方式實現進程鎖,故需要一個獨立的中心節點,以協調多個系統對共享變量的訪問,所有進程在訪問該變量時,都從同一個地方進行取值並控制,從而實現在類似於單機環境中同步控制的效果。

  • 怎麼實現?分佈式鎖的三個主要元素:加鎖,解鎖,鎖超時。1:使用setnx、expire兩個命令實現在redis2.6.12版本之前,分佈式鎖常使用setnx來實現,加鎖的時候使用 setnx(key,value)命令 ,這個命令setnx(set if not exists)的意思是也就是當值不存在時,才可以創建成功返回1,否則返回0。但是,setnx無法在插入值的同時設置超時時間,setnx 與 expire 是兩條獨立的語句,這樣加鎖操作就是非原子性的,那麼就會帶來問題。(比如,當setnx成功後,準備執行expire前,程序突然出現錯誤,則添加的數據就無法清除了,因為沒有超時時間,不會自動清除)2:使用 set key random_value [EX seconds] [PX milliseconds] [NX|XX]random_value 是客戶端生成的唯一的字符串。NX 代表只在鍵不存在時,才對鍵進行設置操作。EX seconds 設置鍵的過期時間為seconds 毫秒。PX milliseconds 設置鍵的過期時間為milliseconds毫秒。

在redis2.6.12版本之後,redis支持通過set在設置值得同時設置超時時間,此操作是原子操作。

<code>// 設置lock的值為123,存在6秒127.0.0.1:6379> set lock 123 EX 6 NXOK// 6秒內,重複設置lock的值為123,返回nil(也就是null)127.0.0.1:6379> set lock 123 EX 6 NX(nil)// 6秒內,獲取值,能夠獲取到127.0.0.1:6379> get lock"123"// 6秒後,獲取值,獲取為nil,又可以重新set值了127.0.0.1:6379> get lock(nil)/<code>

解鎖的過程就是將Key鍵刪除。但也不能亂刪,不能說客戶端1的請求將客戶端2的鎖給刪除掉。這時候random_value的作用就體現出來。刪除的時候比較一下value的值是否一樣。也可以將value的值設置為當前線程的ID,del數據之前,增加鎖判斷機制:判斷要刪除的鎖是否屬於本線程。操作流程:  (1)加鎖:set(id, threadId,expire),其中value為當前線程ID;  (2)解鎖:執行del命令時,根據id和threadId數據判斷該鎖是否仍屬於本線程。是,則刪除。這套redis加解鎖機制看起來很完美,然而有一個無法避免的硬傷,就是過期時間如何設置。如果客戶端在操作共享資源的過程中,因為長期阻塞的原因,導致鎖過期,那麼接下來訪問共享資源就不安全。業務場景:我們有一個內容修改頁面,為了避免出現多個客戶端修改同一個頁面的請求,採用分佈式鎖。只有獲得鎖的客戶端,才能修改頁面。那麼正常修改一次頁面的流程如下圖所示

四萬多字徹底瞭解Redis

注意看,上面的步驟(3)–>步驟(4.1)並不是原子性操作。也就說,你可能出現在步驟(3)的時候返回的是有效這個標誌位,但是在傳輸過程中,因為延時等原因,在步驟(4.1)的時候,鎖已經超時失效了。那麼,這個時候鎖就會被另一個客戶端鎖獲得。就出現了兩個客戶端共同操作共享資源的情況。大家可以思考一下,無論你如何採用任何補償手段,你都只能降低多個客戶端操作共享資源的概率,而無法避免。例如,你在步驟(4.1)的時候也可能發生長時間GC停頓,然後在停頓的時候,鎖超時失效,從而鎖也有可能被其他客戶端獲得。這些大家可以自行思考推敲。在集群情況下為了redis的高可用,一般都會給redis的節點掛一個slave,然後採用哨兵模式進行主備切換。但由於Redis的主從複製(replication)是異步的,這可能會出現在數據同步過程中,master宕機,slave來不及同步數據就被選為master,從而數據丟失。具體流程如下所示:

<code>(1)客戶端1從Master獲取了鎖。(2)Master宕機了,存儲鎖的key還沒有來得及同步到Slave上。(3)Slave升級為Master。(4)客戶端2從新的Master獲取到了對應同一個資源的鎖。/<code>

為了應對這個情形, redis的作者antirez提出了RedLock算法,步驟如下(該流程出自官方文檔),假設我們有N個master節點(官方文檔裡將N設置成5,其實大等於3就行)(1)獲取當前時間(單位是毫秒)。(2)輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裡,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點。(3)客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裡是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認為是獲取成功了。(4)如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。(5)如果鎖獲取失敗了,不管是因為獲取成功的鎖不超過一半(N/2+1)還是因為總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認為沒有獲取成功的鎖分析:RedLock算法細想一下還存在下面的問題節點崩潰重啟,會出現多個客戶端持有鎖假設一共有5個Redis節點:A, B, C, D, E。設想發生瞭如下的事件序列:(1)客戶端1成功鎖住了A, B, C,獲取鎖成功(但D和E沒有鎖住)。(2)節點C崩潰重啟了,但客戶端1在C上加的鎖沒有持久化下來,丟失了。(3)節點C重啟後,客戶端2鎖住了C, D, E,獲取鎖成功。這樣,客戶端1和客戶端2同時獲得了鎖(針對同一資源)。

為了應對節點重啟引發的鎖失效問題,redis的作者antirez提出了延遲重啟的概念,即一個節點崩潰後,先不立即重啟它,而是等待一段時間再重啟,等待的時間大於鎖的有效時間。採用這種方式,這個節點在重啟前所參與的鎖都會過期,它在重啟後就不會對現有的鎖造成影響。這其實也是通過人為補償措施,降低不一致發生的概率。時間跳躍問題(1)假設一共有5個Redis節點:A, B, C, D, E。設想發生瞭如下的事件序列:(2)客戶端1從Redis節點A, B, C成功獲取了鎖(多數節點)。由於網絡問題,與D和E通信失敗。(3)節點C上的時鐘發生了向前跳躍,導致它上面維護的鎖快速過期。客戶端2從Redis節點C, D, E成功獲取了同一個資源的鎖(多數節點)。客戶端1和客戶端2現在都認為自己持有了鎖。

為了應對始終跳躍引發的鎖失效問題,redis的作者antirez提出了應該禁止人為修改系統時間,使用一個不會進行"跳躍"式調整系統時鐘的ntpd程序。這也是通過人為補償措施,降低不一致發生的概率。超時導致鎖失效問題RedLock算法並沒有解決,操作共享資源超時,導致鎖失效的問題。回憶一下RedLock算法的過程,如下圖所示

四萬多字徹底瞭解Redis

如圖所示,我們將其分為上下兩個部分。對於上半部分框圖裡的步驟來說,無論因為什麼原因發生了延遲,RedLock算法都能處理,客戶端不會拿到一個它認為有效,實際卻失效的鎖。然而,對於下半部分框圖裡的步驟來說,如果發生了延遲導致鎖失效,都有可能使得客戶端2拿到鎖。因此,RedLock算法並沒有解決該問題。至於Redis分佈式鎖的代碼實現,可以自行百度即可 這裡只進行說明了實現的方式以及存在的問題。

- Redis 的持久化 RDB、AOF

Redis是基於內存型的數據庫,在提供高性能的同時也需要對內存裡的數據做持久化操作,Redis的持久化分為RDM、AOF兩種持久化方案。這兩種方案各有優點和缺點,可以互相配合使用。這主要是看你的業務場景對數據的容忍程度,根據業務數據來選擇採用哪種方案。

  • RDB(Redis DataBase)持久化這種持久化的方式是將數據庫的數據以快照(二進制)的形式由內存保存到磁盤中,RDB方式的持久化幾乎不損耗Redis本身的性能,在進行RDB持久化時,Redis主進程唯一需要做的事情就是fork出一個子進程,所有持久化工作都由子進程完成。下面看一下RDB在配置文件的配置信息:RDB默認是開啟的,圖中的 save [seconds] [changes] 意思為在 [seconds]秒的時間裡數據發生了 [changes] 變化的話就進行一次RDB快照操作,同樣的Redis可以配置多條save指令,讓Redis執行多級的快照保存策略。save 900 1 #Redis每900秒檢查一次數據變更情況,如果發生了1次或以上的數據變更,則進行RDB快照保存save 300 10 #Redis每300秒檢查一次數據變更情況,如果發生了10次或以上的數據變更,則進行RDB快照保存save 60 10000 #Redis每60秒檢查一次數據變更情況,如果發生了10000次或以上的數據變更,則進行RDB快照保存當然也可以進行手工觸發Redis執行RDB快照,通過執行BGSAVE命令。下面看一下RDB相關的其他命令如下圖:  1、stop-writes-on-bgsave-error :默認值為yes。當啟用了RDB且最後一次後臺保存數據失敗,Redis是否停止接收數據。這會讓用戶意識到數據沒有正確持久化到磁盤上,否則沒有人會注意到災難(disaster)發生了。如果Redis重啟了,那麼又可以重新開始接收數據了 2、rdbcompression ;默認值是yes。對於存儲到磁盤中的快照,可以設置是否進行壓縮存儲。如果是的話,redis會採用LZF算法進行壓縮。如果你不想消耗CPU來進行壓縮的話,可以設置為關閉此功能,但是存儲在磁盤上的快照會比較大。 3、rdbchecksum :默認值是yes。在存儲快照後,我們還可以讓redis使用CRC64算法來進行數據校驗,但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關閉此功能。 4、dbfilename :設置快照的文件名,默認是 dump.rdb 5、dir:設置快照文件的存放路徑,這個配置項一定是個目錄,而不能是文件名。使用上面的 dbfilename 作為保存的文件名。 總的來說RDB文件適合數據的容災備份與恢復,通過RDB文件恢復數據庫耗時較短,可以快速恢復數據。但是RDB持久化只會週期性的保存數據,在未觸發下一次存儲時服務宕機,就會丟失增量數據。當數據量較大的情況下,fork子進程這個操作很消耗cpu,可能會發生長達秒級別的阻塞情況。RDB持久化分為SAVE、BGSAVE兩種方式:SAVE是阻塞式持久化,執行命令時Redis主進程把內存數據寫入到RDB文件中直到創建完畢,期間Redis不能處理任何命令。BGSAVE屬於非阻塞式持久化,創建一個子進程把內存中數據寫入RDB文件裡同時主進程處理命令請求。

BGSAVE實現細節

RDB方式的持久化是通過快照實現的,符合條件時Redis會自動將內存數據進行快照並存儲在硬盤上,以BGSAVE為例,一次完整數據快照的過程:1、Redis使用fork函數創建子進程;2、父進程繼續接收並處理命令請求,子進程將內存數據寫入臨時文件;3、子進程寫入所有數據後會用臨時文件替換舊RDB文件;

執行fork的時OS會使用寫時拷貝策略,對子進程進行快照過程優化。Redis在進行快照過程中不會修改RDB文件,只有快照結束後才會將舊的文件替換成新的,也就是任何時候RDB文件都是完整的。我們可以通過定時備份RDB文件來實現Redis數據庫備份,RDB文件是經過壓縮的,佔用的空間會小於內存中的數據大小。除了自動快照還可以手動發送SAVE或BGSAVE命令讓Redis執行快照。通過RDB方式實現持久化,由於RDB保存頻率的限制,如果數據很重要則考慮使用AOF方式進行持久化。優點:1、只有一個文件 dump.rdb,方便持久化。2、容災性好,一個文件可以保存到安全的磁盤。3、性能最大化,fork 子進程來完成寫操作,讓主進程繼續處理命令,所以是 IO 最大化。使用單獨子進程來進行持久化,主進程不會進行任何 IO 操作,保證了 redis 的高性能4.相對於數據集大時,比 AOF 的啟動效率更高。

缺點:1 數據的完整性和一致性不高,因為RDB可能在最後一次備份時宕機了。如果持久化之間 redis 發生故障,會發生數據丟失。所以這種方式更適合數據要求不嚴謹的時候2 備份時佔用內存,因為Redis 在備份時會獨立創建一個子進程,將數據寫入到一個臨時文件(此時內存中的數據是原來的兩倍哦),最後再將臨時文件替換之前的備份文件。所以Redis 的持久化和數據的恢復要選擇在夜深人靜的時候執行是比較合理的。

  • AOF(Append Only File)持久化AOF持久化是默認沒有開啟的,他的出現主要解決RDB數據丟失的現象,他會將Redis的每一條寫命令通過寫函數記錄到appendonly.aof(默認文件名)文件中,當做數據恢復的時候Redis會將appendonly.aof的寫命令從前到後重新執行一遍,完成數據的回覆工作,AOF的功能實現主要通過命令追加(append),)、文件寫入( write )、文件同步( sync )、文件重寫(rewrite)和重啟加載(load)。AOF的流程如下:1:將所有的寫命令追加到對應的AOF緩衝區中。2:AOF根據配置文件中選定的策略去將AOF緩衝區的數據同步到磁盤文件。3:定期的對AOF文件進行重寫操作,以此來達到減少AOF的大小,避免了許多無用的過期的數據。4:當需要數據恢復或者重啟的時候會加載AOF文件重新執行對應的寫命令進行恢復數據。
四萬多字徹底瞭解Redis

從圖中可以看出,AOF的默認文件名字為 appendonly.aof ,AOF功能默認關閉,緩衝區數據同步達到AOF文件有三個值可以選擇 always、everysec、no 默認的是everysec,下面介紹下這三個值代表的不同含義:always:每個事件循環都會講AOF的全部數據寫入到AOF文件中,這樣最大程度的保證數據的完整性,但是損失了性能,三種策略性能最差的一個,在極端情況下會損失一個事件循環的數據。everysec:每隔一秒就會在子線程中將數據進行AOF文件寫入,這種事默認的策略,同時兼顧了性能和數據完整性,但是同樣在極端情況下也會產生丟失1秒的數據可能。no :不做AOF文件寫入,將數據交給操作系統來處理依賴於系統調度,緩存區也空間寫滿或者達到週期時間,速度最快,但是同樣數據安全性和完整最差。當出現宕機或者嚴重故障的時候可能丟失數據更多。

四萬多字徹底瞭解Redis

no-appendfsync-on-rewrite:在aof重寫或者寫入rdb文件的時候,會執行大量IO,此時對於everysec和always的aof模式來說,執行fsync會造成阻塞過長時間,no-appendfsync-on-rewrite字段設置為默認設置為no。如果對延遲要求很高的應用,這個字段可以設置為yes,否則還是設置為no,這樣對持久化特性來說這是更安全的選擇。 設置為yes表示rewrite期間對新寫操作不fsync,暫時存在內存中,等rewrite完成後再寫入,默認為no,建議yes。Linux的默認fsync策略是30秒。可能丟失30秒數據。默認值為no。

auto-aof-rewrite-percentage:默認值為100。aof自動重寫配置,當目前aof文件大小超過上一次重寫的aof文件大小的百分之多少進行重寫,即當aof文件增長到一定大小的時候,Redis能夠調用bgrewriteaof對日誌文件進行重寫。當前AOF文件大小是上次日誌重寫得到AOF文件大小的二倍(設置為100)時,自動啟動新的日誌重寫過程。

auto-aof-rewrite-min-size:64mb。設置允許重寫的最小aof文件大小,避免了達到約定百分比但尺寸仍然很小的情況還要重寫。

aof-load-truncated:aof文件可能在尾部是不完整的,當redis啟動的時候,aof文件的數據被載入內存。重啟可能發生在redis所在的主機操作系統宕機後,尤其在ext4文件系統沒有加上data=ordered選項,出現這種現象 redis宕機或者異常終止不會造成尾部不完整現象,可以選擇讓redis退出,或者導入儘可能多的數據。如果選擇的是yes,當截斷的aof文件被導入的時候,會自動發佈一個log給客戶端然後load。如果是no,用戶必須手動redis-check-aof修復AOF文件才可以。默認值為 yes。

Redis數據恢復1:如果只配置 AOF ,重啟時加載 AOF 文件恢復數據;2:如果同時配置了 RDB 和 AOF ,啟動只加載 AOF 文件恢復數據;3:如果只配置 RDB,啟動將加載 dump 文件恢復數據。AOF數據恢復詳情在進行AOF數據恢復的時候,其實就是模仿一遍客戶端的寫操作,需要創建一個沒有網絡連接的偽客戶端,將AOF的寫命令逐個讀入然後逐個執行,直到處理完所有命令為止。AOF重寫當AOF文件記錄客戶端的寫命令是會產生一種現象 比如 我操作了很多次set name yichen 或者執行 set age 21 ->set age 24 ->set age 21,其實左後一次是需要的,但是AOF文件都記錄下來了,再比如AOF記錄了一些過期數據,或者有新的數據進行寫入操作,當數據文件達到一定大小,或者達到增長的百分比,這時候就需要對AOF進行重寫操作,由於AOF重寫操作會進行大量的寫操作,當進行重寫的時候會進行阻塞操作,所以會用子進程來進行AOF重寫操作,步驟如下1:子進程進行 AOF 重寫期間,Redis 進程可以繼續處理客戶端命令請求。2:子進程帶有父進程的內存數據拷貝副本(這裡你有沒有想到java的CopyAndWriteArrayList呢?),在不適用鎖的情況下,也可以保證數據的安全性。這裡有一個問題,在子進程進行重寫期間,如果有新的客戶端進行寫命令操作,這樣就會導致當前的數據與AOF文件裡的不一致,為了解決這個問題Redis專門設置了一個AOF重寫緩衝區,當子進程創建完成後這個緩衝區開始使用,在執行AOF重寫期間如果有客戶端進行寫操作的時候會將寫命令同時發送給AOF緩衝區和AOF重寫緩衝區。當子進程完成 AOF 重寫工作之後,它會向父進程發送一個信號,父進程在接收到該信號之後,會調用一個信號處理函數,並執行以下工作:1:將 AOF 重寫緩衝區中的所有內容寫入到新的 AOF 文件中。2:對新的 AOF 文件進行改名,原子地覆蓋現有 AOF 文件,完成新舊文件的替換。3:繼續處理客戶端請求命令。在整個 AOF 後臺重寫過程中,只有信號處理函數執行時會對 Redis 主進程造成阻塞,在其他時候,AOF 後臺重寫都不會阻塞主進程。新型混合持久化RDB和AOF都有各自的缺點:1: RDB是每隔一段時間持久化一次, 故障時就會丟失宕機時刻與上一次持久化之間的數據,無法保證數據完整性2: AOF存儲的是指令序列, 恢復重放時要花費很長時間並且文件更大

Redis 4.0 提供了更好的混合持久化選項: 創建出一個同時包含 RDB 數據和 AOF 數據的 AOF 文件, 其中 RDB 數據位於 AOF 文件的開頭, 它們儲存了服務器開始執行重寫操作時的數據庫狀態,至於那些在重寫操作執行之後執行的 Redis 命令, 則會繼續以 AOF 格式追加到 AOF 文件的末尾, 也即是 RDB 數據之後。

四萬多字徹底瞭解Redis

(引自:後端技術指南)

- Redis內存管理與數據淘汰機制

我們在使用Redis的時候應對數據空間做一個基本的預估,併為Redis設定最大使用的內存。否則在64位OS中Redis會無限制地佔用內存(當物理內存被佔滿後會使用swap空間),容易引發各種各樣的問題。

四萬多字徹底瞭解Redis

從圖中可以看出 redis默認沒有內存限制,可以無限使用內存,當內存用完的時候會根據設置的內存淘汰策略進行淘汰數據釋放空間,如果沒有設置淘汰策略會返回一個錯誤寫操作錯誤,但是讀操作可以正常進行。對應的淘汰策略以下幾種:1)volatile-lru 利用LRU算法移除設置過過期時間的key (LRU:最近使用 Least Recently Used )

2)allkeys-lru 利用LRU算法移除任何key

3)volatile-random 移除設置過過期時間的隨機key

4)allkeys-random 移除隨機ke

5)volatile-ttl 移除即將過期的key(minor TTL)

6)noeviction noeviction 不移除任何key,只是返回一個寫錯誤 ,默認選項

maxmemory-samples :LRU 和 minimal TTL 算法都不是精準的算法,但是相對精確的算法(為了節省內存)。隨意你可以選擇樣本大小進行檢,redis默認選擇3個樣本進行檢測,你可以通過maxmemory-samples進行設置樣本數。

- Redis 的單機模式,主從模式 哨兵模式 集群優缺點

  • 單機模式

Redis 單副本,採用單個 Redis 節點部署架構,沒有備用節點實時同步數據,不提供數據持久化和備份策略,適用於數據可靠性要求不高的純緩存業務場景。

優點:1、架構簡單,部署方便。2、高性價比:緩存使用時無需備用節點(單實例可用性可以用 supervisor 或 crontab 保證),當然為了滿足業務的高可用 性,也可以犧牲一個備用節點,但同時刻只有一個實例對外提供服務。3、高性能。

缺點:1、不保證數據的可靠性。2、在緩存使用,進程重啟後,數據丟失,即使有備用的節點解決高可用性,但是仍然不能解決緩存預熱問題,因此不適用於數據可靠性要求高的業務。3、 高性能受限於單核 CPU 的處理能力(Redis 是單線程機制),CPU 為主要瓶頸,所以適合操作命令簡單,排序、計算較少的場景。也可以考慮用 Memcached 替代。

  • 主從模式

Redis 採用主從(可以多從)部署結構,相較於單副本而言最大的特點就是主從實例間數據實時同步,並且提供數據持久化和備份策略。主從實例部署在不同的物理服務器上,根據公司的基礎環境配置,可以實現同時對外提供服務和讀寫分離策略。

優點:高可靠性:一方面,採用雙機主備架構,能夠在主庫出現故障時自動進行主備切換,從庫提升為主庫提供服務,保證服務平穩運行;另一方面,開啟數據持久化功能和配置合理的備份策略,能有效的解決數據誤操作和數據異常丟失的問題。讀寫分離策略:從節點可以擴展主庫節點的讀能力,有效應對大併發量的讀操作。

缺點:故障恢復複雜,如果沒有 RedisHA 系統(需要開發),當主庫節點出現故障時,需要手動將一個從節點晉升為主節點,同時需要通知業務方變更配置,並且需要讓其它從庫節點去複製新主庫節點,整個過程需要人為干預,比較繁瑣。主庫的寫能力受到單機的限制,可以考慮分片。主庫的存儲能力受到單機的限制,可以考慮 Pika。原生複製的弊端在早期的版本中也會比較突出,如:Redis 複製中斷後,Slave 會發起 psync,此時如果同步不成功,則會進行全量同步,主庫執行全量備份的同時可能會造成毫秒或秒級的卡頓;又由於 COW 機制,導致極端情況下的主庫內存溢出,程序異常退出或宕機;主庫節點生成備份文件導致服務器磁盤 IO 和 CPU(壓縮)資源消耗;發送數 GB 大小的備份文件導致服務器出口帶寬暴增,阻塞請求,建議升級到最新版本。

  • 哨兵模式

Redis Sentinel 是 2.8 版本後推出的原生高可用解決方案,其部署架構主要包括兩部分:Redis Sentinel 集群和 Redis 數據集群。其中 Redis Sentinel 集群是由若干 Sentinel 節點組成的分佈式集群,可以實現故障發現、故障自動轉移、配置中心和客戶端通知。Redis Sentinel 的節點數量要滿足 2n+1(n>=1)的奇數個。

優點:Redis Sentinel 集群部署簡單。能夠解決 Redis 主從模式下的高可用切換問題。很方便實現 Redis 數據節點的線形擴展,輕鬆突破 Redis 自身單線程瓶頸,可極大滿足 Redis 大容量或高性能的業務需求。可以實現一套 Sentinel 監控一組 Redis 數據節點或多組數據節點。

缺點:部署相對 Redis 主從模式要複雜一些,原理理解更繁瑣。資源浪費,Redis 數據節點中 slave 節點作為備份節點不提供服務。Redis Sentinel 主要是針對 Redis 數據節點中的主節點的高可用切換,對 Redis 的數據節點做失敗判定分為主觀下線和客觀下線兩種,對於 Redis 的從節點有對節點做主觀下線操作,並不執行故障轉移。不能解決讀寫分離問題,實現起來相對複雜。

集群模式

Redis Cluster 是 3.0 版後推出的 Redis 分佈式集群解決方案,主要解決 Redis 分佈式方面的需求,比如,當遇到單機內存,併發和流量等瓶頸的時候,Redis Cluster 能起到很好的負載均衡的目的。Redis Cluster 集群節點最小配置 6 個節點以上(3 主 3 從),其中主節點提供讀寫操作,從節點作為備用節點,不提供請求,只作為故障轉移使用。Redis Cluster 採用虛擬槽分區,所有的鍵根據哈希函數映射到 0~16383 個整數槽內,每個節點負責維護一部分槽以及槽所印映射的鍵值數據。

優點:無中心架構。數據按照 slot 存儲分佈在多個節點,節點間數據共享,可動態調整數據分佈。可擴展性:可線性擴展到 1000 多個節點,節點可動態添加或刪除。高可用性:部分節點不可用時,集群仍可用。通過增加 Slave 做 standby 數據副本,能夠實現故障自動 failover,節點之間通過 gossip 協議交換狀態信息,用投票機制完成 Slave 到 Master 的角色提升。降低運維成本,提高系統的擴展性和可用性。

缺點:Client 實現複雜,驅動要求實現 Smart Client,緩存 slots mapping 信息並及時更新,提高了開發難度,客戶端的不成熟影響業務的穩定性。目前僅 JedisCluster 相對成熟,異常處理部分還不完善,比如常見的"max redirect exception"。節點會因為某些原因發生阻塞(阻塞時間大於 clutser-node-timeout),被判斷下線,這種 failover 是沒有必要的。數據通過異步複製,不保證數據的強一致性。多個業務使用同一套集群時,無法根據統計區分冷熱數據,資源隔離性較差,容易出現相互影響的情況。Slave 在集群中充當"冷備",不能緩解讀壓力,當然可以通過 SDK 的合理設計來提高 Slave 資源的利用率。Key 批量操作限制,如使用 mset、mget 目前只支持具有相同 slot 值的 Key 執行批量操作。對於映射為不同 slot 值的 Key 由於 Keys 不支持跨 slot 查詢,所以執行 mset、mget、sunion 等操作支持不友好。Key 事務操作支持有限,只支持多 key 在同一節點上的事務操作,當多個 Key 分佈於不同的節點上時無法使用事務功能。Key 作為數據分區的最小粒度,不能將一個很大的鍵值對象如 hash、list 等映射到不同的節點。不支持多數據庫空間,單機下的 redis 可以支持到 16 個數據庫,集群模式下只能使用 1 個數據庫空間,即 db 0。複製結構只支持一層,從節點只能複製主節點,不支持嵌套樹狀複製結構。避免產生 hot-key,導致主庫節點成為系統的短板。避免產生 big-key,導致網卡撐爆、慢查詢等。重試時間應該大於 cluster-node-time 時間。Redis Cluster 不建議使用 pipeline 和 multi-keys 操作,減少 max redirect 產生的場景。

- Redis 緩存穿透 緩存擊穿 緩存雪崩的解決方案

  • 緩存雪崩

緩存雪崩是指我們緩存採用了同樣的失效時間,緩存同一時間大面積的失效,所以,後面的請求都會落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。

解決方案1: 緩存數據的過期時間設置隨機,防止同一時間大量數據過期現象發生。2:一般併發量不是特別多的時候,使用最多的解決方案是加鎖排隊。3:給每一個緩存數據增加相應的緩存標記,記錄緩存的是否失效,如果緩存標記失效,則更新數據緩存。

  • 緩存穿透

緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。

解決方案1:接口層增加校驗,如用戶鑑權校驗,id做基礎校驗,id<=0的直接攔截;2:從緩存取不到的數據,在數據庫中也沒有取到,這時也可以將key-value對寫為key-null,緩存有效時間可以設置短點,如30秒(設置太長會導致正常情況也沒法使用)。這樣可以防止攻擊用戶反覆用同一個id暴力攻擊3:採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的 bitmap 中,一個一定不存在的數據會被這個 bitmap 攔截掉,從而避免了對底層存儲系統的查詢壓力

  • 緩存擊穿

緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由於併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力瞬間增大,造成過大壓力。和緩存雪崩不同的是,緩存擊穿指併發查同一條數據,緩存雪崩是不同數據都過期了,很多數據都查不到從而查數據庫。解決方案1:設置熱點數據永遠不過期。(1) 從redis上看,確實沒有設置過期時間,這就保證了,不會出現熱點key過期問題,也就是"物理"不過期。(2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value裡,如果發現要過期了,通過一個後臺的異步線程進行緩存的構建,也就是"邏輯"過期2:加互斥鎖,這種解決方案思路比較簡單,就是隻讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據就可以了(如下圖)

四萬多字徹底瞭解Redis

如果是單機,可以用synchronized或者lock來處理,如果是分佈式環境可以用分佈式鎖來解決問題

- Redis 的性能調優

儘管Redis是一個非常快速的內存數據存儲媒介,也並不代表Redis不會產生性能問題。前文中提到過,Redis採用單線程模型,所有的命令都是由一個線程串行執行的,所以當某個命令執行耗時較長時,會拖慢其後的所有命令,這使得Redis對每個任務的執行效率更加敏感。

針對Redis的性能優化,主要從下面幾個層面入手:最初的也是最重要的,確保沒有讓Redis執行耗時長的命令使用pipelining將連續執行的命令組合執行操作系統的Transparent huge pages功能必須關閉:

echo never > /sys/kernel/mm/transparent_hugepage/enabled如果在虛擬機中運行Redis,可能天然就有虛擬機環境帶來的固有延遲。可以通過./redis-cli --intrinsic-latency 100命令查看固有延遲。同時如果對Redis的性能有較高要求的話,應儘可能在物理機上直接部署Redis。檢查數據持久化策略考慮引入讀寫分離機制

  • 長耗時命令

Redis絕大多數讀寫命令的時間複雜度都在O(1)到O(N)之間,通常來說,O(1)的命令是安全的,O(N)命令在使用時需要注意,如果N的數量級不可預知,則應避免使用。例如對一個field數未知的Hash數據執行HGETALL/HKEYS/HVALS命令,通常來說這些命令執行的很快,但如果這個Hash中的field數量極多,耗時就會成倍增長。又如使用SUNION對兩個Set執行Union操作,或使用SORT對List/Set執行排序操作等時,都應該嚴加註意。

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

<code>不要把List當做列表使用,僅當做隊列來使用通過機制嚴格控制Hash、Set、Sorted Set的大小可能的話,將排序、並集、交集等操作放在客戶端執行絕對禁止使用KEYS命令避免一次性遍歷集合類型的所有成員,而應使用SCAN類的命令進行分批的,遊標式的遍歷/<code> 

Redis提供了SCAN命令,可以對Redis中存儲的所有key進行遊標式的遍歷,避免使用KEYS命令帶來的性能問題。同時還有SSCAN/HSCAN/ZSCAN等命令,分別用於對Set/Hash/Sorted Set中的元素進行遊標式遍歷。

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

slowlog-log-slower-than xxxms #執行時間慢於xxx毫秒的命令計入Slow Logslowlog-max-len xxx #Slow Log的長度,即最大紀錄多少條Slow Log

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

  • 網絡引發的延遲儘可能使用長連接或連接池,避免頻繁創建銷燬連接客戶端進行的批量數據操作,應使用Pipeline特性在一次交互中完成。
  • 數據持久化引發的延遲

Redis的數據持久化工作本身就會帶來延遲,需要根據數據的安全級別和性能要求制定合理的持久化策略:AOF + fsync every second是比較好的折中方案,每秒fsync一次AOF + fsync never會提供AOF持久化方案下的最優性能使用RDB持久化通常會提供比使用AOF更高的性能,但需要注意RDB的策略配置每一次RDB快照和AOF Rewrite都需要Redis主進程進行fork操作。fork操作本身可能會產生較高的耗時,與CPU和Redis佔用的內存大小有關。根據具體的情況合理配置RDB快照和AOF Rewrite時機,避免過於頻繁的fork帶來的延遲AOF + fsync always的設置雖然能夠絕對確保數據安全,但每個操作都會觸發一次fsync,會對Redis的性能有比較明顯的影響Redis在fork子進程時需要將內存分頁表拷貝至子進程,以佔用了24GB內存的Redis實例為例,共需要拷貝24GB / 4kB * 8 = 48MB的數據。在使用單Xeon 2.27Ghz的物理機上,這一fork操作耗時216ms。可以通過INFO命令返回的latest_fork_usec字段查看上一次fork操作的耗時(微秒)

  • Swap引發的延遲

當Linux將Redis所用的內存分頁移至swap空間時,將會阻塞Redis進程,導致Redis出現不正常的延遲。Swap通常在物理內存不足或一些進程在進行大量I/O操作時發生,應儘可能避免上述兩種情況的出現。

/proc//smaps文件中會保存進程的swap記錄,通過查看這個文件,能夠判斷Redis的延遲是否由Swap產生。如果這個文件中記錄了較大的Swap size,則說明延遲很有可能是Swap造成的。

  • 數據淘汰引發的延遲

當同一秒內有大量key過期時,也會引發Redis的延遲。在使用時應儘量將key的失效時間錯開。

  • 引入讀寫分離機制

Redis的主從複製能力可以實現一主多從的多節點架構,在這一架構下,主節點接收所有寫請求,並將數據同步給多個從節點。在這一基礎上,我們可以讓從節點提供對實時性要求不高的讀請求服務,以減小主節點的壓力。尤其是針對一些使用了長耗時命令的統計類任務,完全可以指定在一個或多個從節點上執行,避免這些長耗時命令影響其他請求的響應。

- Redis 大廠面試題

  • 什麼是Redis

Redis(Remote Dictionary Server) 是一個使用 C 語言編寫的,開源的(BSD許可)高性能非關係型(NoSQL)的鍵值對數據庫。

Redis 可以存儲鍵和五種不同類型的值之間的映射。鍵的類型只能為字符串,值支持五種數據類型:字符串、列表、集合、散列表、有序集合。

與傳統數據庫不同的是 Redis 的數據是存在內存中的,所以讀寫速度非常快,因此 redis 被廣泛應用於緩存方向,每秒可以處理超過 10萬次讀寫操作,是已知性能最快的Key-Value DB。另外,Redis 也經常用來做分佈式鎖。除此之外,Redis 支持事務 、持久化、LUA腳本、LRU驅動事件、多種集群方案。Redis有哪些優缺點

優點讀寫性能優異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。支持數據持久化,支持AOF和RDB兩種持久化方式。支持事務,Redis的所有操作都是原子性的,同時Redis還支持對幾個操作合併後的原子性執行。數據結構豐富,除了支持string類型的value外還支持hash、set、zset、list等數據結構。支持主從複製,主機會自動將數據同步到從機,可以進行讀寫分離。

缺點數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此Redis適合的場景主要侷限在較小數據量的高性能操作和運算上。Redis 不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復。主機宕機,宕機前有部分數據未能及時同步到從機,切換IP後還會引入數據不一致的問題,降低了系統的可用性。Redis 較難支持在線擴容,在集群容量達到上限時在線擴容會變得很複雜。為避免這一問題,運維人員在系統上線時必須確保有足夠的空間,這對資源造成了很大的浪費。

  • 為什麼要用 Redis /為什麼要用緩存

主要從"高性能"和"高併發"這兩點來看待這個問題。

高性能:

假如用戶第一次訪問數據庫中的某些數據。這個過程會比較慢,因為是從硬盤上讀取的。將該用戶訪問的數據存在數緩存中,這樣下一次再訪問這些數據的時候就可以直接從緩存中獲取了。操作緩存就是直接操作內存,所以速度相當快。如果數據庫中的對應數據改變的之後,同步改變緩存中相應的數據即可!

高併發:

直接操作緩存能夠承受的請求是遠遠大於直接訪問數據庫的,所以我們可以考慮把數據庫中的部分數據轉移到緩存中去,這樣用戶的一部分請求會直接到緩存這裡而不用經過數據庫。

  • 為什麼要用 Redis 而不用 map/guava 做緩存?

緩存分為本地緩存和分佈式緩存。以 Java 為例,使用自帶的 map 或者 guava 實現的是本地緩存,最主要的特點是輕量以及快速,生命週期隨著 jvm 的銷燬而結束,並且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具有一致性。

使用 redis 或 memcached 之類的稱為分佈式緩存,在多實例的情況下,各實例共用一份緩存數據,緩存具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程序架構上較為複雜。

  • Redis為什麼這麼快

1、完全基於內存,絕大部分請求是純粹的內存操作,非常快速。數據存在內存中,類似於 HashMap,HashMap 的優勢就是查找和操作的時間複雜度都是O(1);

2、數據結構簡單,對數據操作也簡單,Redis 中的數據結構是專門進行設計的;

3、採用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;

4、使用多路 I/O 複用模型,非阻塞 IO;

5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣,Redis 直接自己構建了 VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;

  • Redis有哪些數據類型

Redis主要有5種數據類型,包括String,List,Set,Zset,Hash,滿足大部分的使用要求

Redis的應用場景

總結一

計數器

可以對 String 進行自增自減運算,從而實現計數器功能。Redis 這種內存型數據庫的讀寫性能非常高,很適合存儲頻繁讀寫的計數量。

緩存

將熱點數據放到內存中,設置內存的最大使用量以及淘汰策略來保證緩存的命中率。

會話緩存

可以使用 Redis 來統一存儲多臺應用服務器的會話信息。當應用服務器不再存儲用戶的會話信息,也就不再具有狀態,一個用戶可以請求任意一個應用服務器,從而更容易實現高可用性以及可伸縮性。

全頁緩存(FPC)

除基本的會話token之外,Redis還提供很簡便的FPC平臺。以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存後端。此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。

查找表

例如 DNS 記錄就很適合使用 Redis 進行存儲。查找表和緩存類似,也是利用了 Redis 快速的查找特性。但是查找表的內容不能失效,而緩存的內容可以失效,因為緩存不作為可靠的數據來源。

消息隊列(發佈/訂閱功能)

List 是一個雙向鏈表,可以通過 lpush 和 rpop 寫入和讀取消息。不過最好使用 Kafka、RabbitMQ 等消息中間件。

分佈式鎖實現

在分佈式場景下,無法使用單機環境下的鎖來對多個節點上的進程進行同步。可以使用 Redis 自帶的 SETNX 命令實現分佈式鎖,除此之外,還可以使用官方提供的 RedLock 分佈式鎖實現。

其它

Set 可以實現交集、並集等操作,從而實現共同好友等功能。ZSet 可以實現有序性操作,從而實現排行榜等功能。

總結二

Redis相比其他緩存,有一個非常大的優勢,就是支持多種數據類型。

數據類型說明string字符串,最簡單的k-v存儲hashhash格式,value為field和value,適合ID-Detail這樣的場景。list簡單的list,順序列表,支持首位或者末尾插入數據set無序list,查找速度快,適合交集、並集、差集處理sorted set有序的set

其實,通過上面的數據類型的特性,基本就能想到合適的應用場景了。

string——適合最簡單的k-v存儲,類似於memcached的存儲結構,短信驗證碼,配置信息等,就用這種類型來存儲。

hash——一般key為ID或者唯一標示,value對應的就是詳情了。如商品詳情,個人信息詳情,新聞詳情等。

list——因為list是有序的,比較適合存儲一些有序且數據相對固定的數據。如省市區表、字典表等。因為list是有序的,適合根據寫入的時間來排序,如:最新的***,消息隊列等。

set——可以簡單的理解為ID-List的模式,如微博中一個人有哪些好友,set最牛的地方在於,可以對兩個set提供交集、並集、差集操作。例如:查找兩個人共同的好友等。

Sorted Set——是set的增強版本,增加了一個score參數,自動會根據score的值進行排序。比較適合類似於top 10等不根據插入的時間來排序的數據。

如上所述,雖然Redis不像關係數據庫那麼複雜的數據結構,但是,也能適合很多場景,比一般的緩存數據結構要多。瞭解每種數據結構適合的業務場景,不僅有利於提升開發效率,也能有效利用Redis的性能。

  • 什麼是Redis持久化?

持久化就是把內存的數據寫到磁盤中去,防止服務宕機了內存數據丟失。Redis 的持久化機制是什麼?各自的優缺點?

Redis 提供兩種持久化機制 RDB(默認) 和 AOF 機制:

RDB:是Redis DataBase縮寫快照

RDB是Redis默認的持久化方式。按照一定的時間將內存的數據以快照的形式保存到硬盤中,對應產生的數據文件為dump.rdb。通過配置文件中的save參數來定義快照的週期。

優點:1、只有一個文件 dump.rdb,方便持久化。2、容災性好,一個文件可以保存到安全的磁盤。3、性能最大化,fork 子進程來完成寫操作,讓主進程繼續處理命令,所以是 IO 最大化。使用單獨子進程來進行持久化,主進程不會進行任何 IO 操作,保證了 redis 的高性能4.相對於數據集大時,比 AOF 的啟動效率更高。缺點:1、數據安全性低。RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生數據丟失。所以這種方式更適合數據要求不嚴謹的時候)2、AOF(Append-only file)持久化方式:是指所有的命令行記錄以 redis 命令請 求協議的格式完全持久化存儲)保存為 aof 文件。AOF:持久化久化(即Append Only File持久化),則是將Redis執行的每次寫命令記錄到單獨的日誌文件中,當重啟Redis會重新將持久化的日誌中文件恢復數據。

當兩種方式同時開啟時,數據恢復Redis會優先選擇AOF恢復。

優點:1、數據安全,aof 持久化可以配置 appendfsync 屬性,有 always,每進行一次 命令操作就記錄到 aof 文件中一次。2、通過 append 模式寫文件,即使中途服務器宕機,可以通過 redis-check-aof 工具解決數據一致性問題。3、AOF 機制的 rewrite 模式。AOF 文件沒被 rewrite 之前(文件過大時會對命令 進行合併重寫),可以刪除其中的某些命令(比如誤操作的 flushall)

缺點:1、AOF 文件比 RDB 文件大,且恢復速度慢。2、數據集大的時候,比 rdb 啟動效率低。

  • 優缺點是什麼?AOF文件比RDB更新頻率高,優先使用AOF還原數據。AOF比RDB更安全也更大RDB性能比AOF好如果兩個都配了優先加載AOF
  • 如何選擇合適的持久化方式一般來說, 如果想達到足以媲美PostgreSQL的數據安全性,你應該同時使用兩種持久化功能。在這種情況下,當 Redis 重啟的時候會優先載入AOF文件來恢復原始的數據,因為在通常情況下AOF文件保存的數據集要比RDB文件保存的數據集要完整。如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失,那麼你可以只使用RDB持久化。有很多用戶都只使用AOF持久化,但並不推薦這種方式,因為定時生成RDB快照(snapshot)非常便於進行數據庫備份, 並且 RDB 恢復數據集的速度也要比AOF恢復的速度要快,除此之外,使用RDB還可以避免AOF程序的bug。如果你只希望你的數據在服務器運行的時候存在,你也可以不使用任何持久化方式。
  • Redis持久化數據和緩存怎麼做擴容?如果Redis被當做緩存使用,使用一致性哈希實現動態擴容縮容。如果Redis被當做一個持久化存儲使用,必須使用固定的keys-to-nodes映射關係,節點的數量一旦確定不能變化。否則的話(即Redis節點需要動態變化的情況),必須使用可以在運行時進行數據再平衡的一套系統,而當前只有Redis集群可以做到這樣。
  • Redis的過期鍵的刪除策略

我們都知道,Redis是key-value數據庫,我們可以設置Redis中緩存的key的過期時間。Redis的過期策略就是指當Redis中緩存的key過期了,Redis如何處理。

過期策略通常有以下三種:

定時過期:每個設置過期時間的key都需要創建一個定時器,到過期時間就會立即清除。該策略可以立即清除過期的數據,對內存很友好;但是會佔用大量的CPU資源去處理過期的數據,從而影響緩存的響應時間和吞吐量。惰性過期:只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。該策略可以最大化地節省CPU資源,卻對內存非常不友好。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,佔用大量內存。定期過期:每隔一定的時間,會掃描一定數量的數據庫的expires字典中一定數量的key,並清除其中已過期的key。該策略是前兩者的一個折中方案。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和內存資源達到最優的平衡效果。(expires字典會保存所有設置了過期時間的key的過期時間數據,其中,key是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis集群中保存的所有鍵。)

Redis中同時使用了惰性過期和定期過期兩種過期策略。

  • Redis key的過期時間和永久有效分別怎麼設置?

EXPIRE和PERSIST命令。

  • 我們知道通過expire來設置key 的過期時間,那麼對過期的數據怎麼處理呢?

除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的緩存淘汰,常見的策略有兩種:定時去清理過期的緩存;當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據並更新緩存。

兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較複雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。內存相關

  • MySQL裡有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據

redis內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。Redis的內存淘汰策略有哪些

Redis的內存淘汰策略是指在Redis的用於緩存的內存不足時,怎麼處理需要新寫入且需要申請額外空間的數據。

全局的鍵空間選擇性移除noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。(這個是最常用的)allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。

設置過期時間的鍵空間選擇性移除volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除。

總結

Redis的內存淘汰策略的選取並不會影響過期的key的處理。內存淘汰策略用於處理內存不足時的需要申請額外空間的數據;過期策略用於處理過期的緩存數據。Redis主要消耗什麼物理資源?

  • Redis的內存用完了會發生什麼?

如果達到設置的上限,Redis的寫命令會返回錯誤信息(但是讀命令還可以正常返回。)或者你可以配置內存淘汰機制,當Redis達到內存上限時會沖刷掉舊的內容。

Redis如何做內存優化?

可以好好利用Hash,list,sorted set,set等集合類型數據,因為通常情況下很多小的Key-Value可以用更緊湊的方式存放到一起。儘可能使用散列表(hashes),散列表(是說散列表裡面存儲的數少)使用的內存非常小,所以你應該儘可能的將你的數據模型抽象到一個散列表裡面。比如你的web系統中有一個用戶對象,不要為這個用戶的名稱,姓氏,郵箱,密碼設置單獨的key,而是應該把這個用戶的所有信息存儲到一張散列表裡面線程模型

  • Redis線程模型

Redis基於Reactor模式開發了網絡事件處理器,這個處理器被稱為文件事件處理器(file event handler)。它的組成結構為4部分:多個套接字、IO多路複用程序、文件事件分派器、事件處理器。因為文件事件分派器隊列的消費是單線程的,所以Redis才叫單線程模型。文件事件處理器使用 I/O 多路複用(multiplexing)程序來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。當被監聽的套接字準備好執行連接應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時, 與操作相對應的文件事件就會產生, 這時文件事件處理器就會調用套接字之前關聯好的事件處理器來處理這些事件。

雖然文件事件處理器以單線程方式運行, 但通過使用 I/O 多路複用程序來監聽多個套接字, 文件事件處理器既實現了高性能的網絡通信模型, 又可以很好地與 redis 服務器中其他同樣以單線程方式運行的模塊進行對接, 這保持了 Redis 內部單線程設計的簡單性。

  • 什麼是事務?

事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。Redis事務的概念

Redis 事務的本質是通過MULTI、EXEC、WATCH等一組命令的集合。事務支持一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序串行化執行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

總結說:redis事務就是一次性、順序性、排他性的執行一個隊列中的一系列命令。Redis事務的三個階段事務開始 MULTI命令入隊事務執行 EXEC

事務執行過程中,如果服務端收到有EXEC、DISCARD、WATCH、MULTI之外的請求,將會把請求放入隊列中排隊Redis事務相關命令

Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的

Redis會將一個事務中的所有命令序列化,然後按順序執行。redis 不支持回滾,"Redis 在事務失敗時不進行回滾,而是繼續執行餘下的命令", 所以 Redis 的內部可以保持簡單且快速。如果在一個事務中的命令出現錯誤,那麼所有的命令都不會執行;如果在一個事務中出現運行錯誤,那麼正確的命令會被執行WATCH 命令是一個樂觀鎖,可以為 Redis 事務提供 check-and-set (CAS)行為。可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。MULTI命令用於開啟一個事務,它總是返回OK。MULTI執行之後,客戶端可以繼續向服務器發送任意多條命令,這些命令不會立即被執行,而是被放到一個隊列中,當EXEC命令被調用時,所有隊列中的命令才會被執行。EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列。當操作被打斷時,返回空值 nil 。通過調用DISCARD,客戶端可以清空事務隊列,並放棄執行事務, 並且客戶端會從事務狀態中退出。UNWATCH命令可以取消watch對所有key的監控。

事務管理(ACID)概述

原子性(Atomicity)

原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。

一致性(Consistency)

事務前後數據的完整性必須保持一致。

隔離性(Isolation)

多個事務併發執行時,一個事務的執行不應影響其他事務的執行

持久性(Durability)

持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響

Redis的事務總是具有ACID中的一致性和隔離性,其他特性是不支持的。當服務器運行在AOF持久化模式下,並且appendfsync選項的值為always時,事務也具有耐久性。Redis事務支持隔離性嗎

Redis 是單進程程序,並且它保證在執行事務時,不會對事務進行中斷,事務可以運行直到執行完所有事務隊列中的命令為止。因此,Redis 的事務是總是帶有隔離性的。Redis事務保證原子性嗎,支持回滾嗎

Redis中,單條命令是原子性執行的,但事務不保證原子性,且沒有回滾。事務中任意命令執行失敗,其餘的命令仍會被執行。Redis事務其他實現基於Lua腳本,Redis可以保證腳本內的命令一次性、按順序地執行,其同時也不提供事務運行錯誤的回滾,執行過程中如果部分命令運行錯誤,剩下的命令還是會繼續運行完基於中間標記變量,通過另外的標記變量來標識事務是否執行完成,讀取數據時先讀取該標記變量判斷是否事務執行完成。但這樣會需要額外寫代碼實現,比較繁瑣

  • 哨兵的介紹

sentinel,中文名是哨兵。哨兵是 redis 集群機構中非常重要的一個組件,主要有以下功能:

<code>集群監控:負責監控 redis master 和 slave 進程是否正常工作。消息通知:如果某個 redis 實例有故障,那麼哨兵負責發送消息作為報警通知給管理員。故障轉移:如果 master node 掛掉了,會自動轉移到 slave node 上。配置中心:如果故障轉移發生了,通知 client 客戶端新的 master 地址。/<code>

哨兵用於實現 redis 集群的高可用,本身也是分佈式的,作為一個哨兵集群去運行,互相協同工作。故障轉移時,判斷一個 master node 是否宕機了,需要大部分的哨兵都同意才行,涉及到了分佈式選舉的問題。

即使部分哨兵節點掛掉了,哨兵集群還是能正常工作的,因為如果一個作為高可用機制重要組成部分的故障轉移系統本身是單點的,那就很坑爹了。

哨兵的核心知識

<code>哨兵至少需要 3 個實例,來保證自己的健壯性。哨兵 + redis 主從的部署架構,是不保證數據零丟失的,只能保證 redis 集群的高可用性。對於哨兵 + redis 主從這種複雜的部署架構,儘量在測試環境和生產環境,都進行充足的測試和演練。/<code>

官方Redis Cluster 方案(服務端路由查詢)

edis 集群模式的工作原理能說一下麼?在集群模式下,redis 的 key 是如何尋址的?分佈式尋址都有哪些算法?瞭解一致性 hash 算法嗎?

簡介

Redis Cluster是一種服務端Sharding技術,3.0版本開始正式提供。Redis Cluster並沒有使用一致性hash,而是採用slot(槽)的概念,一共分成16384個槽。將請求發送到任意節點,接收到請求的節點會將查詢請求發送到正確的節點上執行

方案說明

<code>通過哈希的方式,將數據分片,每個節點均分存儲一定哈希槽(哈希值)區間的數據,默認分配了16384 個槽位每份數據分片會存儲在多個互為主從的多節點上數據寫入先寫主節點,再同步到從節點(支持配置為阻塞同步)同一分片多個節點間的數據不保持一致性讀取數據時,當客戶端操作的key沒有分配在該節點上時,redis會返回轉向指令,指向正確的節點擴容時時需要需要把舊節點的數據遷移一部分到新節點/<code> 

在 redis cluster 架構下,每個 redis 要放開兩個端口號,比如一個是 6379,另外一個就是 加1w 的端口號,比如 16379。

16379 端口號是用來進行節點間通信的,也就是 cluster bus 的東西,cluster bus 的通信,用來進行故障檢測、配置更新、故障轉移授權。cluster bus 用了另外一種二進制的協議,gossip 協議,用於節點間進行高效的數據交換,佔用更少的網絡帶寬和處理時間。

節點間的內部通信機制

基本通信原理

集群元數據的維護有兩種方式:集中式、Gossip 協議。redis cluster 節點間採用 gossip 協議進行通信。

分佈式尋址算法

<code>hash 算法(大量緩存重建)一致性 hash 算法(自動緩存遷移)+ 虛擬節點(自動負載均衡)redis cluster 的 hash slot 算法/<code>

優點

<code>無中心架構,支持動態擴容,對業務透明具備Sentinel的監控和自動Failover(故障轉移)能力客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可高性能,客戶端直連redis服務,免去了proxy代理的損耗/<code>

缺點

<code>運維也很複雜,數據遷移需要人工干預只能使用0號數據庫不支持批量操作(pipeline管道操作)分佈式邏輯和存儲模塊耦合等/<code>

基於客戶端分配

簡介

Redis Sharding是Redis Cluster出來之前,業界普遍使用的多Redis實例集群方法。其主要思想是採用哈希算法將Redis數據的key進行散列,通過hash函數,特定的key會映射到特定的Redis節點上。Java redis客戶端驅動jedis,支持Redis Sharding功能,即ShardedJedis以及結合緩存池的ShardedJedisPool

優點

優勢在於非常簡單,服務端的Redis實例彼此獨立,相互無關聯,每個Redis實例像單服務器一樣運行,非常容易線性擴展,系統的靈活性很強

缺點由於sharding處理放到客戶端,規模進一步擴大時給運維帶來挑戰。

客戶端sharding不支持動態增刪節點。服務端Redis實例群拓撲結構有變化時,每個客戶端都需要更新調整。連接不能共享,當應用規模增大時,資源浪費制約優化

基於代理服務器分片

簡介

客戶端發送請求到一個代理組件,代理解析客戶端的數據,並將請求轉發至正確的節點,最後將結果回覆給客戶端

特徵

<code>透明接入,業務程序不用關心後端Redis實例,切換成本低Proxy 的邏輯和存儲的邏輯是隔離的代理層多了一次轉發,性能有所損耗/<code>

業界開源方案

<code>Twtter開源的Twemproxy豌豆莢開源的Codis/<code>

Redis 主從架構

單機的 redis,能夠承載的 QPS 大概就在上萬到幾萬不等。對於緩存來說,一般都是用來支撐讀高併發的。因此架構做成主從(master-slave)架構,一主多從,主負責寫,並且將數據複製到其它的 slave 節點,從節點負責讀。所有的讀請求全部走從節點。這樣也可以很輕鬆實現水平擴容,支撐讀高併發。

redis replication -> 主從架構 -> 讀寫分離 -> 水平擴容支撐讀高併發

redis replication 的核心機制redis 採用異步方式複製數據到 slave 節點,不過 redis2.8 開始,slave node 會週期性地確認自己每次複製的數據量;一個 master node 是可以配置多個 slave node 的;slave node 也可以連接其他的 slave node;slave node 做複製的時候,不會 block master node 的正常工作;slave node 在做複製的時候,也不會 block 對自己的查詢操作,它會用舊的數據集來提供服務;但是複製完成的時候,需要刪除舊數據集,加載新數據集,這個時候就會暫停對外服務了;slave node 主要用來進行橫向擴容,做讀寫分離,擴容的 slave node 可以提高讀的吞吐量。

注意,如果採用了主從架構,那麼建議必須開啟 master node 的持久化,不建議用 slave node 作為 master node 的數據熱備,因為那樣的話,如果你關掉 master 的持久化,可能在 master 宕機重啟的時候數據是空的,然後可能一經過複製, slave node 的數據也丟了。

另外,master 的各種備份方案,也需要做。萬一本地的所有文件丟失了,從備份中挑選一份 rdb 去恢復 master,這樣才能確保啟動的時候,是有數據的,即使採用了後續講解的高可用機制,slave node 可以自動接管 master node,但也可能 sentinel 還沒檢測到 master failure,master node 就自動重啟了,還是可能導致上面所有的 slave node 數據被清空。

  • redis 主從複製的核心原理

當啟動一個 slave node 的時候,它會發送一個 PSYNC 命令給 master node。

如果這是 slave node 初次連接到 master node,那麼會觸發一次 full resynchronization 全量複製。此時 master 會啟動一個後臺線程,開始生成一份 RDB 快照文件,

同時還會將從客戶端 client 新收到的所有寫命令緩存在內存中。RDB 文件生成完畢後, master 會將這個 RDB 發送給 slave,slave 會先寫入本地磁盤,然後再從本地磁盤加載到內存中,

接著 master 會將內存中緩存的寫命令發送到 slave,slave 也會同步這些數據。

slave node 如果跟 master node 有網絡故障,斷開了連接,會自動重連,連接之後 master node 僅會複製給 slave 部分缺少的數據。

過程原理

<code>當從庫和主庫建立MS關係後,會向主數據庫發送SYNC命令主庫接收到SYNC命令後會開始在後臺保存快照(RDB持久化過程),並將期間接收到的寫命令緩存起來當快照完成後,主Redis會將快照文件和所有緩存的寫命令發送給從Redis從Redis接收到後,會載入快照文件並且執行收到的緩存的命令之後,主Redis每當接收到寫命令時就會將命令發送從Redis,從而保證數據的一致/<code>

缺點

所有的slave節點數據的複製和同步都由master節點來處理,會照成master節點壓力太大,使用主從從結構來解決

  • Redis集群的主從複製模型是怎樣的?

為了使在部分節點失敗或者大部分節點無法通信的情況下集群仍然可用,所以集群使用了主從複製模型,每個節點都會有N-1個複製品

  • 生產環境中的 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 左右的請求量。

其實大型的公司,會有基礎架構的 team 負責緩存集群的運維。

  • 說說Redis哈希槽的概念?

Redis集群沒有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384個哈希槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽,集群的每個節點負責一部分hash槽。Redis集群會有寫操作丟失嗎?為什麼?

Redis並不能保證數據的強一致性,這意味這在實際中集群在特定的條件下可能會丟失寫操作。Redis集群之間是如何複製的?

異步複製

  • Redis集群最大節點個數是多少?

16384個

  • Redis集群如何選擇數據庫?

Redis集群目前無法做數據庫選擇,默認在0數據庫。

  • Redis是單線程的,如何提高多核CPU的利用率?

可以在同一個服務器部署多個Redis的實例,並把他們當作不同的服務器來使用,在某些時候,無論如何一個服務器是不夠的, 所以,如果你想使用多個CPU,你可以考慮一下分片(shard)。

  • 為什麼要做Redis分區?

分區可以讓Redis管理更大的內存,Redis將可以使用所有機器的內存。如果沒有分區,你最多隻能使用一臺機器的內存。分區使Redis的計算能力通過簡單地增加計算機得到成倍提升,Redis的網絡帶寬也會隨著計算機和網卡的增加而成倍增長。

  • 你知道有哪些Redis分區實現方案?

客戶端分區就是在客戶端就已經決定數據會被存儲到哪個redis節點或者從哪個redis節點讀取。大多數客戶端已經實現了客戶端分區。代理分區 意味著客戶端將請求發送給代理,然後代理決定去哪個節點寫數據或者讀數據。代理根據分區規則決定請求哪些Redis實例,然後根據Redis的響應結果返回給客戶端。redis和memcached的一種代理實現就是Twemproxy查詢路由(Query routing) 的意思是客戶端隨機地請求任意一個redis實例,然後由Redis將請求轉發給正確的Redis節點。Redis Cluster實現了一種混合形式的查詢路由,但並不是直接將請求從一個redis節點轉發到另一個redis節點,而是在客戶端的幫助下直接redirected到正確的redis節點。

  • Redis分區有什麼缺點?涉及多個key的操作通常不會被支持。例如你不能對兩個集合求交集,因為他們可能被存儲到不同的Redis實例(實際上這種情況也有辦法,但是不能直接使用交集指令)。同時操作多個key,則不能使用Redis事務.分區使用的粒度是key,不能使用一個非常長的排序key存儲一個數據集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)當使用分區的時候,數據處理會非常複雜,例如為了備份你必須從不同的Redis實例和主機同時收集RDB / AOF文件。分區時動態擴容或縮容可能非常複雜。Redis集群在運行時增加或者刪除Redis節點,能做到最大程度對用戶透明地數據再平衡,但其他一些客戶端分區或者代理分區方法則不支持這種特性。然而,有一種預分片的技術也可以較好的解決這個問題。
  • Redis實現分佈式鎖

Redis為單進程單線程模式,採用隊列模式將併發訪問變成串行訪問,且多客戶端對Redis的連接並不存在競爭關係Redis中可以使用SETNX命令實現分佈式鎖。

當且僅當 key 不存在,將 key 的值設為 value。若給定的 key 已經存在,則 SETNX 不做任何動作

SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。

返回值:設置成功,返回 1 。設置失敗,返回 0 。

使用SETNX完成同步鎖的流程及事項如下:

使用SETNX命令獲取鎖,若返回0(key已存在,鎖已存在)則獲取失敗,反之獲取成功

為了防止獲取鎖後程序出現異常,導致其他線程/進程調用SETNX命令總是返回0而進入死鎖狀態,需要為該key設置一個"合理"的過期時間

釋放鎖,使用DEL命令將鎖數據刪除如何解決 Redis 的併發競爭 Key 問題

所謂 Redis 的併發競爭 Key 的問題也就是多個系統同時對一個 key 進行操作,但是最後執行的順序和我們期望的順序不同,這樣也就導致了結果的不同!

推薦一種方案:分佈式鎖(zookeeper 和 redis 都可以實現分佈式鎖)。(如果不存在 Redis 的併發競爭 Key 問題,不要使用分佈式鎖,這樣會影響性能)

基於zookeeper臨時有序節點可以實現的分佈式鎖。大致思想為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。完成業務流程後,刪除對應的子節點釋放鎖。

在實踐中,當然是從以可靠性為主。所以首推Zookeeper。

  • 分佈式Redis是前期做還是後期規模上來了再做好?為什麼?

既然Redis是如此的輕量(單實例只使用1M內存),為防止以後的擴容,最好的辦法就是一開始就啟動較多實例。即便你只有一臺服務器,你也可以一開始就讓Redis以分佈式的方式運行,使用分區,在同一臺服務器上啟動多個實例。

一開始就多設置幾個Redis實例,例如32或者64個實例,對大多數用戶來說這操作起來可能比較麻煩,但是從長久來看做這點犧牲是值得的。

這樣的話,當你的數據不斷增長,需要更多的Redis服務器時,你需要做的就是僅僅將Redis實例從一臺服務遷移到另外一臺服務器而已(而不用考慮重新分區的問題)。一旦你添加了另一臺服務器,你需要將你一半的Redis實例從第一臺機器遷移到第二臺機器。

  • 什麼是 RedLock

Redis 官方站提出了一種權威的基於 Redis 實現分佈式鎖的方式名叫 Redlock,此種方式比原先的單節點的方法更安全。它可以保證以下特性:安全特性:互斥訪問,即永遠只有一個 client 能拿到鎖避免死鎖:最終 client 都可能拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的 client crash 了或者出現了網絡分區容錯性:只要大部分 Redis 節點存活就可以正常提供服務

  • 緩存預熱

緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就可以避免在用戶請求的時候,先查詢數據庫,然後再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!

解決方案

<code>直接寫個緩存刷新頁面,上線時手工操作一下;數據量不大,可以在項目啟動的時候自動進行加載;定時刷新緩存;/<code>
  • 緩存降級

當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。

緩存降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。

在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日誌級別設置預案:一般:比如有些服務偶爾因為網絡抖動或者服務正在上線而超時,可以自動降級;警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;錯誤:比如可用率低於90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;嚴重錯誤:比如因為特殊原因數據錯誤了,此時需要緊急人工降級。

服務降級的目的,是為了防止Redis服務故障,導致數據庫跟著一起發生雪崩問題。因此,對於不重要的緩存數據,可以採取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去數據庫查詢,而是直接返回默認值給用戶。熱點數據和冷數據

熱點數據,緩存才有價值

對於冷數據而言,大部分數據可能還沒有再次訪問到就已經被擠出內存,不僅佔用內存,而且價值不大。頻繁修改的數據,看情況考慮使用緩存

對於熱點數據,比如我們的某IM產品,生日祝福模塊,當天的壽星列表,緩存以後可能讀取數十萬次。再舉個例子,某導航產品,我們將導航信息,緩存以後可能讀取數百萬次。

數據更新前至少讀取兩次,緩存才有意義。這個是最基本的策略,如果緩存還沒有起作用就失效了,那就沒有太大價值了。

那存不存在,修改頻率很高,但是又不得不考慮緩存的場景呢?有!比如,這個讀取接口對數據庫的壓力很大,但是又是熱點數據,這個時候就需要考慮通過緩存手段,減少數據庫的壓力,比如我們的某助手產品的,點贊數,收藏數,分享數等是非常典型的熱點數據,但是又不斷變化,此時就需要將數據同步保存到Redis緩存,減少數據庫壓力。

  • 緩存熱點key

緩存中的一個Key(比如一個促銷商品),在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現緩存過期一般都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。

解決方案

對緩存查詢加鎖,如果KEY不存在,就加鎖,然後查DB入緩存,然後解鎖;其他進程如果發現有鎖就等待,然後等解鎖後返回數據或者進入DB查詢常用工具

  • Redis支持的Java客戶端都有哪些?官方推薦用哪個?

Redisson、Jedis、lettuce等等,官方推薦使用Redisson。

  • Redis和Redisson有什麼關係?

Redisson是一個高級的分佈式協調Redis客服端,能幫助用戶在分佈式環境中輕鬆實現一些Java的對象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

  • Jedis與Redisson對比有什麼優缺點?

Jedis是Redis的Java實現的客戶端,其API提供了比較全面的Redis命令的支持;Redisson實現了分佈式和可擴展的Java數據結構,和Jedis相比,功能較為簡單,不支持字符串操作,不支持排序、事務、管道、分區等Redis特性。Redisson的宗旨是促進使用者對Redis的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。其他問題

  • Redis與Memcached的區別

兩者都是非關係型內存鍵值數據庫,現在公司一般都是用 Redis 來實現緩存,而且 Redis 自身也越來越強大了!Redis 與 Memcached 主要有以下不同:

(1) memcached所有的值均是簡單的字符串,redis作為其替代者,支持更為豐富的數據類型

(2) redis的速度比memcached快很多

(3) redis可以持久化其數據

  • 如何保證緩存與數據庫雙寫時的數據一致性?

你只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問題,那麼你如何解決一致性問題?

一般來說,就是如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,緩存可以稍微的跟數據庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求串行化,串到一個內存隊列裡去,這樣就可以保證一定不會出現不一致的情況

串行化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。

還有一種方式就是可能會暫時產生不一致的情況,但是發生的幾率特別小,就是先更新數據庫,然後再刪除緩存。

  • Redis常見性能問題和解決方案?

Master最好不要做任何持久化工作,包括內存快照和AOF日誌文件,特別是不要啟用內存快照做持久化。如果數據比較關鍵,某個Slave開啟AOF備份數據,策略為每秒同步一次。為了主從複製的速度和連接的穩定性,Slave和Master最好在同一個局域網內。儘量避免在壓力較大的主庫上增加從庫Master調用BGREWRITEAOF重寫AOF文件,AOF在重寫的時候會佔大量的CPU和內存資源,導致服務load過高,出現短暫服務暫停現象。為了Master的穩定性,主從複製不要用圖狀結構,用單向鏈表結構更穩定,即主從關係為:Master

  • Redis官方為什麼不提供Windows版本?

因為目前Linux版本已經相當穩定,而且用戶量很大,無需開發windows版本,反而會帶來兼容性等問題。

  • 一個字符串類型的值能存儲最大容量是多少?

512M

  • Redis如何做大量數據插入?

Redis2.6開始redis-cli支持一種新的被稱之為pipe mode的新模式用於執行大量數據插入工作。

  • 假如Redis裡面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如果將它們全部找出來?

使用keys指令可以掃出指定模式的key列表。

  • 對方接著追問:如果這個redis正在給線上的業務提供服務,那使用keys指令會有什麼問題?

這個時候你要回答redis關鍵的一個特性:redis的單線程的。keys指令會導致線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重複概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。

  • 使用Redis做過異步隊列嗎,是如何實現的

使用list類型保存數據信息,rpush生產消息,lpop消費消息,當lpop沒有消息時,可以sleep一段時間,然後再檢查有沒有信息,如果不想sleep的話,可以使用blpop, 在沒有信息的時候,會一直阻塞,直到信息的到來。redis可以通過pub/sub主題訂閱模式實現一個生產者,多個消費者,當然也存在一定的缺點,當消費者下線時,生產的消息會丟失。Redis如何實現延時隊列

使用sortedset,使用時間戳做score, 消息內容作為key,調用zadd來生產消息,消費者使用zrangbyscore獲取n秒之前的數據做輪詢處理。

  • Redis回收進程如何工作的?一個客戶端運行了新的命令,添加了新的數據。Redis檢查內存使用情況,如果大於maxmemory的限制, 則根據設定好的策略進行回收。一個新的命令被執行,等等。所以我們不斷地穿越內存限制的邊界,通過不斷達到邊界然後不斷地回收回到邊界以下。

如果一個命令的結果導致大量內存被使用(例如很大的集合的交集保存到一個新的鍵),不用多久內存限制就會被這個內存使用量超越。

  • Redis回收使用的是什麼算法?

LRU算法

- 參考文獻redis實戰redis設計與實現


分享到:


相關文章: