20條Redis知識匯總

前言

1. 什么是Redis侯养?它主要用來什么的拍鲤?

Redis剩檀,英文全稱是Remote Dictionary Server(遠程字典服務)憋沿,是一個開源的使用ANSI C語言編寫、支持網(wǎng)絡沪猴、可基于內(nèi)存亦可持久化的日志型辐啄、Key-Value數(shù)據(jù)庫,并提供多種語言的API运嗜。

與MySQL數(shù)據(jù)庫不同的是壶辜,Redis的數(shù)據(jù)是存在內(nèi)存中的。它的讀寫速度非车W猓快砸民,每秒可以處理超過10萬次讀寫操作。因此redis被廣泛應用于緩存奋救,另外岭参,Redis也經(jīng)常用來做分布式鎖。除此之外尝艘,Redis支持事務演侯、持久化、LUA 腳本背亥、LRU 驅(qū)動事件秒际、多種集群方案。

2.說說Redis的基本數(shù)據(jù)結(jié)構(gòu)類型

大多數(shù)小伙伴都知道隘梨,Redis有以下這五種基本類型:

  • String(字符串)

  • Hash(哈希)

  • List(列表)

  • Set(集合)

  • zset(有序集合)
    它還有三種特殊的數(shù)據(jù)結(jié)構(gòu)類型

  • Geospatial

  • Hyperloglog

  • Bitmap

2.1 Redis 的五種基本數(shù)據(jù)類型

String(字符串)

  • 簡介:String是Redis最基礎的數(shù)據(jù)結(jié)構(gòu)類型程癌,它是二進制安全的,可以存儲圖片或者序列化的對象轴猎,值最大存儲為512M
  • 簡單使用舉例: set key value嵌莉、get key等
  • 應用場景:共享session、分布式鎖捻脖,計數(shù)器锐峭、限流。
  • 內(nèi)部編碼有3種可婶,int(8字節(jié)長整型)/embstr(小于等于39字節(jié)字符串)/raw(大于39個字節(jié)字符串)

C語言的字符串是char[]實現(xiàn)的拇砰,而Redis使用SDS(simple dynamic string) 封裝,sds源碼如下:

struct sdshdr{
  unsigned int len; // 標記buf的長度
  unsigned int free; //標記buf中未使用的元素個數(shù)
  char buf[]; // 存放元素的坑
}

SDS 結(jié)構(gòu)圖如下:

Redis為什么選擇SDS結(jié)構(gòu)枷莉,而C語言原生的char[]不香嗎颅停?

舉例其中一點惫搏,SDS中,O(1)時間復雜度蚕涤,就可以獲取字符串長度筐赔;而C 字符串,需要遍歷整個字符串揖铜,時間復雜度為O(n)

Hash(哈希)

  • 簡介:在Redis中茴丰,哈希類型是指v(值)本身又是一個鍵值對(k-v)結(jié)構(gòu)
  • 簡單使用舉例:hset key field value 、hget key field
  • 內(nèi)部編碼:ziplist(壓縮列表) 天吓、hashtable(哈希表)
  • 應用場景:緩存用戶信息等贿肩。
  • 注意點:如果開發(fā)使用hgetall,哈希元素比較多的話龄寞,可能導致Redis阻塞汰规,可以使用hscan。而如果只是獲取部分field萄焦,建議使用hmget控轿。

字符串和哈希類型對比如下圖:


List(列表)

  • 簡介:列表(list)類型是用來存儲多個有序的字符串,一個列表最多可以存儲2^32-1個元素拂封。
  • 簡單實用舉例:lpush key value [value ...] 茬射、lrange key start end
  • 內(nèi)部編碼:ziplist(壓縮列表)、linkedlist(鏈表)
  • 應用場景:消息隊列冒签,文章列表
    一圖看懂list類型的插入與彈出:



    list應用場景參考以下:

  • lpush+lpop=Stack(棧)
  • lpush+rpop=Queue(隊列)
  • lpsh+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息隊列)

Set(集合)

  • 簡介:集合(set)類型也是用來保存多個的字符串元素在抛,但是不允許重復元素
  • 簡單使用舉例:sadd key element [element ...]、smembers key
  • 內(nèi)部編碼:intset(整數(shù)集合)萧恕、hashtable(哈希表)
  • 注意點:smembers和lrange刚梭、hgetall都屬于比較重的命令,如果元素過多存在阻塞Redis的可能性票唆,可以使用sscan來完成朴读。
  • 應用場景:用戶標簽,生成隨機數(shù)抽獎、社交需求走趋。

有序集合(zset)

  • 簡介:已排序的字符串集合衅金,同時元素不能重復
  • 簡單格式舉例:zadd key score member [score member ...],zrank key member
  • 底層內(nèi)部編碼:ziplist(壓縮列表)簿煌、skiplist(跳躍表)
  • 應用場景:排行榜氮唯,社交需求(如用戶點贊)。

2.2 Redis 的三種特殊數(shù)據(jù)類型

  • Geo:Redis3.2推出的姨伟,地理位置定位惩琉,用于存儲地理位置信息,并對存儲的信息進行操作夺荒。
  • HyperLogLog:用來做基數(shù)統(tǒng)計算法的數(shù)據(jù)結(jié)構(gòu)瞒渠,如統(tǒng)計網(wǎng)站的UV良蒸。
  • Bitmaps :用一個比特位來映射某個元素的狀態(tài),在Redis中在孝,它的底層是基于字符串類型實現(xiàn)的诚啃,可以把bitmaps成作一個以比特位為單位的數(shù)組

3. Redis為什么這么快?

3.1 基于內(nèi)存存儲實現(xiàn)

我們都知道內(nèi)存讀寫是比在磁盤快很多的私沮,Redis基于內(nèi)存存儲實現(xiàn)的數(shù)據(jù)庫,相對于數(shù)據(jù)存在磁盤的MySQL數(shù)據(jù)庫和橙,省去磁盤I/O的消耗仔燕。

3.2 高效的數(shù)據(jù)結(jié)構(gòu)

我們知道,Mysql索引為了提高效率魔招,選擇了B+樹的數(shù)據(jù)結(jié)構(gòu)晰搀。其實合理的數(shù)據(jù)結(jié)構(gòu),就是可以讓你的應用/程序更快办斑。先看下Redis的數(shù)據(jù)結(jié)構(gòu)&內(nèi)部編碼圖:


SDS簡單動態(tài)字符串

  • 字符串長度處理:Redis獲取字符串長度外恕,時間復雜度為O(1),而C語言中乡翅,需要從頭開始遍歷鳞疲,復雜度為O(n);
  • 空間預分配:字符串修改越頻繁的話,內(nèi)存分配越頻繁蠕蚜,就會消耗性能尚洽,而SDS修改和空間擴充,會額外分配未使用的空間靶累,減少性能損耗腺毫。
  • 惰性空間釋放:SDS 縮短時,不是回收多余的內(nèi)存空間挣柬,而是free記錄下多余的空間潮酒,后續(xù)有變更,直接使用free中記錄的空間邪蛔,減少分配急黎。
  • 二進制安全:Redis可以存儲一些二進制數(shù)據(jù),在C語言中字符串遇到'\0'會結(jié)束店溢,而 SDS中標志字符串結(jié)束的是len屬性叁熔。

字典
Redis 作為 K-V 型內(nèi)存數(shù)據(jù)庫,所有的鍵值就是用字典來存儲床牧。字典就是哈希表荣回,比如HashMap,通過key就可以直接獲取到對應的value戈咳。而哈希表的特性心软,在O(1)時間復雜度就可以獲得對應的值壕吹。
跳躍表

  • 跳躍表是Redis特有的數(shù)據(jù)結(jié)構(gòu),就是在鏈表的基礎上删铃,增加多級索引提升查找效率耳贬。
  • 跳躍表支持平均 O(logN),最壞 O(N)復雜度的節(jié)點查找,還可以通過順序性操作批量處理節(jié)點猎唁。

3.3 合理的數(shù)據(jù)編碼

Redis 支持多種數(shù)據(jù)數(shù)據(jù)類型咒劲,每種基本類型,可能對多種數(shù)據(jù)結(jié)構(gòu)诫隅。什么時候,使用什么樣數(shù)據(jù)結(jié)構(gòu)腐魂,使用什么樣編碼,是redis設計者總結(jié)優(yōu)化的結(jié)果逐纬。

  • String:如果存儲數(shù)字的話蛔屹,是用int類型的編碼;如果存儲非數(shù)字,小于等于39字節(jié)的字符串豁生,是embstr兔毒;大于39個字節(jié),則是raw編碼甸箱。
  • List:如果列表的元素個數(shù)小于512個育叁,列表每個元素的值都小于64字節(jié)(默認),使用ziplist編碼摇肌,否則使用linkedlist編碼
  • Hash:哈希類型元素個數(shù)小于512個擂红,所有值小于64字節(jié)的話,使用ziplist編碼,否則使用hashtable編碼围小。
  • Set:如果集合中的元素都是整數(shù)且元素個數(shù)小于512個昵骤,使用intset編碼,否則使用hashtable編碼肯适。
  • Zset:當有序集合的元素個數(shù)小于128個变秦,每個元素的值小于64字節(jié)時,使用ziplist編碼框舔,否則使用skiplist(跳躍表)編碼

3.4 合理的線程模型

I/O 多路復用

圖片

多路I/O復用技術(shù)可以讓單個線程高效的處理多個連接請求蹦玫,而Redis使用用epoll作為I/O多路復用技術(shù)的實現(xiàn)。并且刘绣,Redis自身的事件處理模型將epoll中的連接樱溉、讀寫、關閉都轉(zhuǎn)換為事件纬凤,不在網(wǎng)絡I/O上浪費過多的時間福贞。

什么是I/O多路復用?

  • I/O :網(wǎng)絡 I/O
  • 多路 :多個網(wǎng)絡連接
  • 復用:復用同一個線程停士。
  • IO多路復用其實就是一種同步IO模型挖帘,它實現(xiàn)了一個線程可以監(jiān)視多個文件句柄完丽;一旦某個文件句柄就緒,就能夠通知應用程序進行相應的讀寫操作拇舀;而沒有文件句柄就緒時,就會阻塞應用程序逻族,交出cpu。

單線程模型

  • Redis是單線程模型的骄崩,而單線程避免了CPU不必要的上下文切換和競爭鎖的消耗聘鳞。也正因為是單線程,如果某個命令執(zhí)行過長(如hgetall命令)要拂,會造成阻塞搁痛。Redis是面向快速執(zhí)行場景的數(shù)據(jù)庫。宇弛,所以要慎用如smembers和lrange、hgetall等命令源请。
  • Redis 6.0 引入了多線程提速枪芒,它的執(zhí)行命令操作內(nèi)存的仍然是個單線程。

3.5 虛擬內(nèi)存機制

Redis直接自己構(gòu)建了VM機制 谁尸,不會像一般的系統(tǒng)會調(diào)用系統(tǒng)函數(shù)處理舅踪,會浪費一定的時間去移動和請求。
Redis的虛擬內(nèi)存機制是啥呢良蛮?

虛擬內(nèi)存機制就是暫時把不經(jīng)常訪問的數(shù)據(jù)(冷數(shù)據(jù))從內(nèi)存交換到磁盤中抽碌,從而騰出寶貴的內(nèi)存空間用于其它需要訪問的數(shù)據(jù)(熱數(shù)據(jù))。通過VM功能可以實現(xiàn)冷熱數(shù)據(jù)分離决瞳,使熱數(shù)據(jù)仍在內(nèi)存中货徙、冷數(shù)據(jù)保存到磁盤。這樣就可以避免因為內(nèi)存不足而造成訪問速度下降的問題皮胡。

4. 什么是緩存擊穿痴颊、緩存穿透、緩存雪崩屡贺?

4.1 緩存穿透問題

先來看一個常見的緩存使用方式:讀請求來了蠢棱,先查下緩存,緩存有值命中甩栈,就直接返回泻仙;緩存沒命中,就去查數(shù)據(jù)庫量没,然后把數(shù)據(jù)庫的值更新到緩存玉转,再返回。


緩存穿透:指查詢一個一定不存在的數(shù)據(jù)允蜈,由于緩存是不命中時需要從數(shù)據(jù)庫查詢冤吨,查不到數(shù)據(jù)則不寫入緩存蒿柳,這將導致這個不存在的數(shù)據(jù)每次請求都要到數(shù)據(jù)庫去查詢,進而給數(shù)據(jù)庫帶來壓力漩蟆。

通俗點說垒探,讀請求訪問時,緩存和數(shù)據(jù)庫都沒有某個值怠李,這樣就會導致每次對這個值的查詢請求都會穿透到數(shù)據(jù)庫圾叼,這就是緩存穿透。

緩存穿透一般都是這幾種情況產(chǎn)生的:

  • 業(yè)務不合理的設計捺癞,比如大多數(shù)用戶都沒開守護夷蚊,但是你的每個請求都去緩存,查詢某個userid查詢有沒有守護髓介。
  • 業(yè)務/運維/開發(fā)失誤的操作惕鼓,比如緩存和數(shù)據(jù)庫的數(shù)據(jù)都被誤刪除了。
  • 黑客非法請求攻擊唐础,比如黑客故意捏造大量非法請求箱歧,以讀取不存在的業(yè)務數(shù)據(jù)。
    如何避免緩存穿透呢一膨? 一般有三種方法呀邢。
  • 1.如果是非法請求,我們在API入口豹绪,對參數(shù)進行校驗价淌,過濾非法值。
  • 2.如果查詢數(shù)據(jù)庫為空瞒津,我們可以給緩存設置個空值蝉衣,或者默認值。但是如有有寫請求進來的話仲智,需要更新緩存哈买乃,以保證緩存一致性,同時钓辆,最后給緩存設置適當?shù)倪^期時間剪验。(業(yè)務上比較常用,簡單有效)
  • 3.使用布隆過濾器快速判斷數(shù)據(jù)是否存在前联。即一個查詢請求過來時功戚,先通過布隆過濾器判斷值是否存在,存在才繼續(xù)往下查似嗤。

布隆過濾器原理:它由初始值為0的位圖數(shù)組和N個哈希函數(shù)組成啸臀。一個對一個key進行N個hash算法獲取N個值,在比特數(shù)組中將這N個值散列后設定為1,然后查的時候如果特定的這幾個位置都為1乘粒,那么布隆過濾器判斷該key存在豌注。

4.2 緩存雪崩問題

緩存雪崩: 指緩存中數(shù)據(jù)大批量到過期時間,而查詢數(shù)據(jù)量巨大灯萍,請求都直接訪問數(shù)據(jù)庫轧铁,引起數(shù)據(jù)庫壓力過大甚至down機。

  • 緩存雪崩一般是由于大量數(shù)據(jù)同時過期造成的旦棉,對于這個原因齿风,可通過均勻設置過期時間解決,即讓過期時間相對離散一點绑洛。如采用一個較大固定值+一個較小的隨機值救斑,5小時+0到1800秒醬紫。
  • Redis 故障宕機也可能引起緩存雪奔真屯。這就需要構(gòu)造Redis高可用集群啦脸候。

4.3 緩存擊穿問題

緩存擊穿: 指熱點key在某個時間點過期的時候,而恰好在這個時間點對這個Key有大量的并發(fā)請求過來绑蔫,從而大量的請求打到db纪他。

緩存擊穿看著有點像,其實它兩區(qū)別是晾匠,緩存雪奔是指數(shù)據(jù)庫壓力過大甚至down機,緩存擊穿只是大量并發(fā)請求到了DB數(shù)據(jù)庫層面梯刚×构荩可以認為擊穿是緩存雪奔的一個子集吧。有些文章認為它倆區(qū)別亡资,是區(qū)別在于擊穿針對某一熱點key緩存澜共,雪奔則是很多key。

解決方案就有兩種:

  • 1.使用互斥鎖方案锥腻。緩存失效時嗦董,不是立即去加載db數(shù)據(jù),而是先使用某些帶成功返回的原子操作命令瘦黑,如(Redis的setnx)去操作京革,成功的時候,再去加載db數(shù)據(jù)庫數(shù)據(jù)和設置緩存幸斥。否則就去重試獲取緩存匹摇。
  • 2. “永不過期”。 是指沒有設置過期時間甲葬,但是熱點數(shù)據(jù)快要過期時廊勃,異步線程去更新和設置過期時間。

5. 什么是熱Key問題经窖,如何解決熱key問題

什么是熱Key呢坡垫?在Redis中梭灿,我們把訪問頻率高的key,稱為熱點key冰悠。
如果某一熱點key的請求到服務器主機時堡妒,由于請求量特別大,可能會導致主機資源不足屿脐,甚至宕機涕蚤,從而影響正常的服務。

圖片

而熱點Key是怎么產(chǎn)生的呢的诵?主要原因有兩個:

  • 用戶消費的數(shù)據(jù)遠大于生產(chǎn)的數(shù)據(jù)万栅,如秒殺、熱點新聞等讀多寫少的場景西疤。
  • 請求分片集中烦粒,超過單Redi服務器的性能,比如固定名稱key代赁,Hash落入同一臺服務器扰她,瞬間訪問量極大,超過機器瓶頸芭碍,產(chǎn)生熱點Key問題徒役。

那么在日常開發(fā)中,如何識別到熱點key呢窖壕?

  • 憑經(jīng)驗判斷哪些是熱Key忧勿;
  • 客戶端統(tǒng)計上報;
  • 服務代理層上報

如何解決熱key問題瞻讽?

  • Redis集群擴容:增加分片副本鸳吸,均衡讀流量;
  • 將熱key分散到不同的服務器中速勇;
  • 使用二級緩存晌砾,即JVM本地緩存,減少Redis的讀請求。

6. Redis 過期策略和內(nèi)存淘汰策略

圖片

6.1 Redis的過期策略

我們在set key的時候烦磁,可以給它設置一個過期時間养匈,比如expire key 60尾抑。指定這key60s后過期蒙畴,60s后导盅,redis是如何處理的嘛矾瑰?我們先來介紹幾種過期策略:

定時過期

每個設置過期時間的key都需要創(chuàng)建一個定時器瓶摆,到過期時間就會立即對key進行清除浓利。該策略可以立即清除過期的數(shù)據(jù)鲸拥,對內(nèi)存很友好帆锋;但是會占用大量的CPU資源去處理過期的數(shù)據(jù),從而影響緩存的響應時間和吞吐量逐虚。

惰性過期

只有當訪問一個key時聋溜,才會判斷該key是否已過期,過期則清除叭爱。該策略可以最大化地節(jié)省CPU資源撮躁,卻對內(nèi)存非常不友好。極端情況可能出現(xiàn)大量的過期key沒有再次被訪問买雾,從而不會被清除把曼,占用大量內(nèi)存。

定期過期

每隔一定的時間漓穿,會掃描一定數(shù)量的數(shù)據(jù)庫的expires字典中一定數(shù)量的key嗤军,并清除其中已過期的key。該策略是前兩者的一個折中方案晃危。通過調(diào)整定時掃描的時間間隔和每次掃描的限定耗時叙赚,可以在不同情況下使得CPU和內(nèi)存資源達到最優(yōu)的平衡效果。

expires字典會保存所有設置了過期時間的key的過期時間數(shù)據(jù)僚饭,其中震叮,key是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間鳍鸵。鍵空間是指該Redis集群中保存的所有鍵苇瓣。

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

  • 假設Redis當前存放30萬個key偿乖,并且都設置了過期時間钓简,如果你每隔100ms就去檢查這全部的key,CPU負載會特別高汹想,最后可能會掛掉。
  • 因此撤蚊,redis采取的是定期過期古掏,每隔100ms就隨機抽取一定數(shù)量的key來檢查和刪除的。
  • 但是侦啸,最后可能會有很多已經(jīng)過期的key沒被刪除槽唾。這時候,redis采用惰性刪除光涂。在你獲取某個key的時候庞萍,redis會檢查一下,這個key如果設置了過期時間并且已經(jīng)過期了忘闻,此時就會刪除钝计。

如果定期刪除漏掉了很多過期的key,然后也沒走惰性刪除。就會有很多過期key積在內(nèi)存內(nèi)存私恬,直接會導致內(nèi)存爆的债沮。或者有些時候本鸣,業(yè)務量大起來了疫衩,redis的key被大量使用,內(nèi)存直接不夠了荣德,運維也忘記加大內(nèi)存了闷煤。難道redis直接這樣掛掉?不會的涮瞻!Redis用8種內(nèi)存淘汰策略保護自己~

6.2 Redis 內(nèi)存淘汰策略

  • volatile-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時鲤拿,從設置了過期時間的key中使用LRU(最近最少使用)算法進行淘汰;
  • allkeys-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時饲宛,從所有key中使用LRU(最近最少使用)算法進行淘汰皆愉。
  • volatile-lfu:4.0版本新增,當內(nèi)存不足以容納新寫入數(shù)據(jù)時艇抠,在過期的key中幕庐,使用LFU算法進行刪除key。
  • allkeys-lfu:4.0版本新增家淤,當內(nèi)存不足以容納新寫入數(shù)據(jù)時异剥,從所有key中使用LFU算法進行淘汰;
  • volatile-random:當內(nèi)存不足以容納新寫入數(shù)據(jù)時絮重,從設置了過期時間的key中冤寿,隨機淘汰數(shù)據(jù);青伤。
  • allkeys-random:當內(nèi)存不足以容納新寫入數(shù)據(jù)時督怜,從所有key中隨機淘汰數(shù)據(jù)。
  • volatile-ttl:當內(nèi)存不足以容納新寫入數(shù)據(jù)時狠角,在設置了過期時間的key中号杠,根據(jù)過期時間進行淘汰,越早過期的優(yōu)先被淘汰丰歌;
  • noeviction:默認策略姨蟋,當內(nèi)存不足以容納新寫入數(shù)據(jù)時,新寫入操作會報錯立帖。

7.說說Redis的常用應用場景

之前的文章有介紹眼溶,這里不做詳細描述Redis 16大應用場景 - 簡書 (jianshu.com)

8. Redis 的持久化機制有哪些?優(yōu)缺點說說

Redis是基于內(nèi)存的非關系型K-V數(shù)據(jù)庫晓勇,既然它是基于內(nèi)存的堂飞,如果Redis服務器掛了灌旧,數(shù)據(jù)就會丟失。為了避免數(shù)據(jù)丟失了酝静,Redis提供了持久化节榜,即把數(shù)據(jù)保存到磁盤。

Redis提供了RDB和AOF兩種持久化機制别智,它持久化文件加載流程如下:


8.1 RDB

RDB宗苍,就是把內(nèi)存數(shù)據(jù)以快照的形式保存到磁盤上。

什么是快照?可以這樣理解薄榛,給當前時刻的數(shù)據(jù)讳窟,拍一張照片,然后保存下來敞恋。

RDB持久化丽啡,是指在指定的時間間隔內(nèi),執(zhí)行指定次數(shù)的寫操作硬猫,將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤中补箍,它是Redis默認的持久化方式。執(zhí)行完操作后啸蜜,在指定目錄下會生成一個dump.rdb文件坑雅,Redis 重啟的時候,通過加載dump.rdb文件來恢復數(shù)據(jù)衬横。RDB觸發(fā)機制主要有以下幾種:

RDB 的優(yōu)點

  • 適合大規(guī)模的數(shù)據(jù)恢復場景裹粤,如備份,全量復制等

RDB缺點

  • 沒辦法做到實時持久化/秒級持久化蜂林。
  • 新老版本存在RDB格式兼容問題

AOF
AOF(append only file) 持久化遥诉,采用日志的形式來記錄每個寫操作,追加到文件中噪叙,重啟時再重新執(zhí)行AOF文件中的命令來恢復數(shù)據(jù)矮锈。它主要解決數(shù)據(jù)持久化的實時性問題。默認是不開啟的睁蕾。

AOF的工作流程如下:


AOF的優(yōu)點

  • 數(shù)據(jù)的一致性和完整性更高

AOF的缺點

  • AOF記錄的內(nèi)容越多苞笨,文件越大,數(shù)據(jù)恢復變慢惫霸。

9.怎么實現(xiàn)Redis的高可用?

我們在項目中使用Redis葱弟,肯定不會是單點部署Redis服務的壹店。因為,單點部署一旦宕機芝加,就不可用了硅卢。為了實現(xiàn)高可用射窒,通常的做法是,將數(shù)據(jù)庫復制多個副本以部署在不同的服務器上将塑,其中一臺掛了也可以繼續(xù)提供服務脉顿。Redis 實現(xiàn)高可用有三種部署模式:主從模式,哨兵模式点寥,集群模式艾疟。

9.1 主從模式

主從模式中,Redis部署了多臺機器敢辩,有主節(jié)點蔽莱,負責讀寫操作,有從節(jié)點戚长,只負責讀操作盗冷。從節(jié)點的數(shù)據(jù)來自主節(jié)點,實現(xiàn)原理就是主從復制機制

主從復制包括全量復制同廉,增量復制兩種仪糖。一般當slave第一次啟動連接master,或者認為是第一次連接迫肖,就采用全量復制锅劝,全量復制流程如下:


  • 1.slave發(fā)送sync命令到master。
  • 2.master接收到SYNC命令后咒程,執(zhí)行bgsave命令鸠天,生成RDB全量文件。
  • 3.master使用緩沖區(qū)帐姻,記錄RDB快照生成期間的所有寫命令稠集。
  • 4.master執(zhí)行完bgsave后,向所有slave發(fā)送RDB快照文件饥瓷。
  • 5.slave收到RDB快照文件后剥纷,載入、解析收到的快照呢铆。
  • 6.master使用緩沖區(qū)晦鞋,記錄RDB同步期間生成的所有寫的命令。
  • 7.master快照發(fā)送完畢后棺克,開始向slave發(fā)送緩沖區(qū)中的寫命令;
  • 8.salve接受命令請求悠垛,并執(zhí)行來自master緩沖區(qū)的寫命令

redis2.8版本之后,已經(jīng)使用psync來替代sync娜谊,因為sync命令非常消耗系統(tǒng)資源确买,psync的效率更高。
slave與master全量同步之后纱皆,master上的數(shù)據(jù)湾趾,如果再次發(fā)生更新芭商,就會觸發(fā)增量復制。

當master節(jié)點發(fā)生數(shù)據(jù)增減時搀缠,就會觸發(fā)replicationFeedSalves()函數(shù)铛楣,接下來在 Master節(jié)點上調(diào)用的每一個命令會使用replicationFeedSlaves()來同步到Slave節(jié)點。執(zhí)行此函數(shù)之前呢艺普,master節(jié)點會判斷用戶執(zhí)行的命令是否有數(shù)據(jù)更新簸州,如果有數(shù)據(jù)更新的話,并且slave節(jié)點不為空衷敌,就會執(zhí)行此函數(shù)勿侯。這個函數(shù)作用就是:把用戶執(zhí)行的命令發(fā)送到所有的slave節(jié)點,讓slave節(jié)點執(zhí)行缴罗。流程如下:

9.2 哨兵模式

主從模式中助琐,一旦主節(jié)點由于故障不能提供服務,需要人工將從節(jié)點晉升為主節(jié)點面氓,同時還要通知應用方更新主節(jié)點地址兵钮。顯然,多數(shù)業(yè)務場景都不能接受這種故障處理方式舌界。Redis從2.8開始正式提供了Redis Sentinel(哨兵)架構(gòu)來解決這個問題掘譬。

哨兵模式,由一個或多個Sentinel實例組成的Sentinel系統(tǒng)呻拌,它可以監(jiān)視所有的Redis主節(jié)點和從節(jié)點葱轩,并在被監(jiān)視的主節(jié)點進入下線狀態(tài)時,自動將下線主服務器屬下的某個從節(jié)點升級為新的主節(jié)點藐握。但是呢靴拱,一個哨兵進程對Redis節(jié)點進行監(jiān)控,就可能會出現(xiàn)問題(單點問題)猾普,因此袜炕,可以使用多個哨兵來進行監(jiān)控Redis節(jié)點,并且各個哨兵之間還會進行監(jiān)控初家。

圖片

簡單來說偎窘,哨兵模式就三個作用:

  • 發(fā)送命令,等待Redis服務器(包括主服務器和從服務器)返回監(jiān)控其運行狀態(tài)溜在;
  • 哨兵監(jiān)測到主節(jié)點宕機陌知,會自動將從節(jié)點切換成主節(jié)點,然后通過發(fā)布訂閱模式通知其他的從節(jié)點掖肋,修改配置文件仆葡,讓它們切換主機;
  • 哨兵之間還會相互監(jiān)控培遵,從而達到高可用浙芙。

故障切換的過程是怎樣的呢

假設主服務器宕機,哨兵1先檢測到這個結(jié)果籽腕,系統(tǒng)并不會馬上進行 failover 過程嗡呼,僅僅是哨兵1主觀的認為主服務器不可用,這個現(xiàn)象成為主觀下線皇耗。當后面的哨兵也檢測到主服務器不可用南窗,并且數(shù)量達到一定值時,那么哨兵之間就會進行一次投票郎楼,投票的結(jié)果由一個哨兵發(fā)起万伤,進行 failover 操作。切換成功后呜袁,就會通過發(fā)布訂閱模式敌买,讓各個哨兵把自己監(jiān)控的從服務器實現(xiàn)切換主機,這個過程稱為客觀下線阶界。這樣對于客戶端而言虹钮,一切都是透明的。

哨兵的工作模式如下:
1膘融、每個Sentinel以每秒鐘一次的頻率向它所知的Master芙粱,Slave以及其他Sentinel實例發(fā)送一個 PING命令。
2氧映、如果一個實例(instance)距離最后一次有效回復 PING 命令的時間超過 down-after-milliseconds 選項所指定的值春畔, 則這個實例會被 Sentinel標記為主觀下線。
3岛都、如果一個Master被標記為主觀下線律姨,則正在監(jiān)視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態(tài)。
4疗绣、當有足夠數(shù)量的 Sentinel(大于等于配置文件指定的值)在指定的時間范圍內(nèi)確認Master的確進入了主觀下線狀態(tài)线召, 則Master會被標記為客觀下線。
5多矮、在一般情況下缓淹, 每個 Sentinel 會以每10秒一次的頻率向它已知的所有Master,Slave發(fā)送 INFO 命令塔逃。
6讯壶、當Master被 Sentinel 標記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 發(fā)送 INFO 命令的頻率會從 10 秒一次改為每秒一次
7湾盗、若沒有足夠數(shù)量的 Sentinel同意Master已經(jīng)下線伏蚊, Master的客觀下線狀態(tài)就會被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回復格粪, Master 的主觀下線狀態(tài)就會被移除躏吊。

9.3 Cluster集群模式

哨兵模式基于主從模式氛改,實現(xiàn)讀寫分離,它還可以自動切換比伏,系統(tǒng)可用性更高胜卤。但是它每個節(jié)點存儲的數(shù)據(jù)是一樣的,浪費內(nèi)存赁项,并且不好在線擴容葛躏。因此,Cluster集群應運而生悠菜,它在Redis3.0加入的舰攒,實現(xiàn)了Redis的分布式存儲。對數(shù)據(jù)進行分片悔醋,也就是說每臺Redis節(jié)點上存儲不同的內(nèi)容摩窃,來解決在線擴容的問題。并且芬骄,它也提供復制和故障轉(zhuǎn)移的功能偶芍。

Cluster集群節(jié)點的通訊

一個Redis集群由多個節(jié)點組成,各個節(jié)點之間是怎么通信的呢德玫?通過Gossip協(xié)議匪蟀!

Redis Cluster集群通過Gossip協(xié)議進行通信,節(jié)點之前不斷交換信息宰僧,交換的信息內(nèi)容包括節(jié)點出現(xiàn)故障材彪、新節(jié)點加入、主從節(jié)點變更信息琴儿、slot信息等等段化。常用的Gossip消息分為4種,分別是:ping造成、pong显熏、meet、fail晒屎。

圖片
  • meet消息:通知新節(jié)點加入喘蟆。消息發(fā)送者通知接收者加入到當前集群,meet消息通信正常完成后鼓鲁,接收節(jié)點會加入到集群中并進行周期性的ping蕴轨、pong消息交換。
  • ping消息:集群內(nèi)交換最頻繁的消息骇吭,集群內(nèi)每個節(jié)點每秒向多個其他節(jié)點發(fā)送ping消息橙弱,用于檢測節(jié)點是否在線和交換彼此狀態(tài)信息。
  • pong消息:當接收到ping、meet消息時棘脐,作為響應消息回復給發(fā)送方確認消息正常通信斜筐。pong消息內(nèi)部封裝了自身狀態(tài)數(shù)據(jù)。節(jié)點也可以向集群內(nèi)廣播自身的pong消息來通知整個集群對自身狀態(tài)進行更新蛀缝。
  • fail消息:當節(jié)點判定集群內(nèi)另一個節(jié)點下線時奴艾,會向集群內(nèi)廣播一個fail消息,其他節(jié)點接收到fail消息之后把對應節(jié)點更新為下線狀態(tài)内斯。

特別的,每個節(jié)點是通過集群總線(cluster bus) 與其他的節(jié)點進行通信的像啼。通訊時俘闯,使用特殊的端口號,即對外服務端口號加10000忽冻。例如如果某個node的端口號是6379真朗,那么它與其它nodes通信的端口號是 16379。nodes 之間的通信采用特殊的二進制協(xié)議僧诚。

Hash Slot插槽算法

既然是分布式存儲遮婶,Cluster集群使用的分布式算法是一致性Hash嘛?并不是湖笨,而是Hash Slot插槽算法旗扑。

插槽算法把整個數(shù)據(jù)庫被分為16384個slot(槽),每個進入Redis的鍵值對慈省,根據(jù)key進行散列臀防,分配到這16384插槽中的一個。使用的哈希映射也比較簡單边败,用CRC16算法計算出一個16 位的值袱衷,再對16384取模。數(shù)據(jù)庫中的每個鍵都屬于這16384個槽的其中一個笑窜,集群中的每個節(jié)點都可以處理這16384個槽致燥。

集群中的每個節(jié)點負責一部分的hash槽,比如當前集群有A排截、B嫌蚤、C個節(jié)點,每個節(jié)點上的哈希槽數(shù) =16384/3断傲,那么就有:

  • 節(jié)點A負責0~5460號哈希槽
  • 節(jié)點B負責5461~10922號哈希槽
  • 節(jié)點C負責10923~16383號哈希槽

Redis Cluster集群

Redis Cluster集群中搬葬,需要確保16384個槽對應的node都正常工作,如果某個node出現(xiàn)故障艳悔,它負責的slot也會失效急凰,整個集群將不能工作。

因此為了保證高可用,Cluster集群引入了主從復制抡锈,一個主節(jié)點對應一個或者多個從節(jié)點疾忍。當其它主節(jié)點 ping 一個主節(jié)點 A 時,如果半數(shù)以上的主節(jié)點與 A 通信超時床三,那么認為主節(jié)點 A 宕機了一罩。如果主節(jié)點宕機時,就會啟用從節(jié)點撇簿。

在Redis的每一個節(jié)點上聂渊,都有兩個玩意尚卫,一個是插槽(slot)祠挫,它的取值范圍是0~16383。另外一個是cluster问潭,可以理解為一個集群管理的插件找蜜。當我們存取的key到達時饼暑,Redis 會根據(jù)CRC16算法得出一個16 bit的值,然后把結(jié)果對16384取模洗做。醬紫每個key都會對應一個編號在 0~16383 之間的哈希槽弓叛,通過這個值,去找到對應的插槽所對應的節(jié)點诚纸,然后直接自動跳轉(zhuǎn)到這個對應的節(jié)點上進行存取操作撰筷。

雖然數(shù)據(jù)是分開存儲在不同節(jié)點上的,但是對客戶端來說畦徘,整個集群Cluster闭专,被看做一個整體【缮眨客戶端端連接任意一個node影钉,看起來跟操作單實例的Redis一樣。當客戶端操作的key沒有被分配到正確的node節(jié)點時掘剪,Redis會返回轉(zhuǎn)向指令平委,最后指向正確的node,這就有點像瀏覽器頁面的302 重定向跳轉(zhuǎn)夺谁。

故障轉(zhuǎn)移

Redis集群實現(xiàn)了高可用廉赔,當集群內(nèi)節(jié)點出現(xiàn)故障時,通過故障轉(zhuǎn)移匾鸥,以保證集群正常對外提供服務蜡塌。

redis集群通過ping/pong消息,實現(xiàn)故障發(fā)現(xiàn)勿负。這個環(huán)境包括主觀下線和客觀下線馏艾。

主觀下線: 某個節(jié)點認為另一個節(jié)點不可用,即下線狀態(tài),這個狀態(tài)并不是最終的故障判定琅摩,只能代表一個節(jié)點的意見铁孵,可能存在誤判情況。

圖片

客觀下線: 指標記一個節(jié)點真正的下線房资,集群內(nèi)多個節(jié)點都認為該節(jié)點不可用蜕劝,從而達成共識的結(jié)果。如果是持有槽的主節(jié)點故障轰异,需要為該節(jié)點進行故障轉(zhuǎn)移岖沛。

  • 假如節(jié)點A標記節(jié)點B為主觀下線,一段時間后搭独,節(jié)點A通過消息把節(jié)點B的狀態(tài)發(fā)到其它節(jié)點婴削,當節(jié)點C接受到消息并解析出消息體時,如果發(fā)現(xiàn)節(jié)點B的pfail狀態(tài)時戳稽,會觸發(fā)客觀下線流程;
  • 當下線為主節(jié)點時期升,此時Redis Cluster集群為統(tǒng)計持有槽的主節(jié)點投票惊奇,看投票數(shù)是否達到一半,當下線報告統(tǒng)計數(shù)大于一半時播赁,被標記為客觀下線狀態(tài)颂郎。

流程如下:

故障恢復:故障發(fā)現(xiàn)后,如果下線節(jié)點的是主節(jié)點容为,則需要在它的從節(jié)點中選一個替換它乓序,以保證集群的高可用。流程如下:

圖片
  • 資格檢查:檢查從節(jié)點是否具備替換故障主節(jié)點的條件坎背。
  • 準備選舉時間:資格檢查通過后替劈,更新觸發(fā)故障選舉時間。
  • 發(fā)起選舉:到了故障選舉時間得滤,進行選舉陨献。
  • 選舉投票:只有持有槽的主節(jié)點才有票,從節(jié)點收集到足夠的選票(大于一半)懂更,觸發(fā)替換主節(jié)點操作

10. 使用過Redis分布式鎖嘛眨业?有哪些注意點呢?

分布式鎖沮协,是控制分布式系統(tǒng)不同進程共同訪問共享資源的一種鎖的實現(xiàn)龄捡。秒殺下單、搶紅包等等業(yè)務場景慷暂,都需要用到分布式鎖聘殖,我們項目中經(jīng)常使用Redis作為分布式鎖。

選了Redis分布式鎖的幾種實現(xiàn)方法,大家來討論下就斤,看有沒有啥問題哈悍募。

  • 命令setnx + expire分開寫
  • setnx + value值是過期時間
  • set的擴展命令(set ex px nx)
  • set ex px nx + 校驗唯一隨機值,再刪除

10.1 命令setnx + expire分開寫

if(jedis.setnx(key,lock_value) == 1){ //加鎖
    expire(key,100); //設置過期時間
    try {
        do something  //業(yè)務請求
    }catch(){
  }
  finally {
       jedis.del(key); //釋放鎖
    }
}

如果執(zhí)行完setnx加鎖洋机,正要執(zhí)行expire設置過期時間時坠宴,進程crash掉或者要重啟維護了,那這個鎖就“長生不老”了绷旗,別的線程永遠獲取不到鎖啦喜鼓,所以分布式鎖不能這么實現(xiàn)。

10.2 setnx + value值是過期時間

long expires = System.currentTimeMillis() + expireTime; //系統(tǒng)時間+設置的過期時間
String expiresStr = String.valueOf(expires);

// 如果當前鎖不存在衔肢,返回加鎖成功
if (jedis.setnx(key, expiresStr) == 1) {
        return true;
} 
// 如果鎖已經(jīng)存在庄岖,獲取鎖的過期時間
String currentValueStr = jedis.get(key);

// 如果獲取到的過期時間,小于系統(tǒng)當前時間角骤,表示已經(jīng)過期
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {

     // 鎖已過期隅忿,獲取上一個鎖的過期時間,并設置現(xiàn)在鎖的過期時間(不了解redis的getSet命令的小伙伴邦尊,可以去官網(wǎng)看下哈)
    String oldValueStr = jedis.getSet(key_resource_id, expiresStr);
    
    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
         // 考慮多線程并發(fā)的情況背桐,只有一個線程的設置值和當前值相同,它才可以加鎖
         return true;
    }
}
        
//其他情況蝉揍,均返回加鎖失敗
return false;
}

筆者看過有開發(fā)小伙伴是這么實現(xiàn)分布式鎖的链峭,但是這種方案也有這些缺點:

  • 過期時間是客戶端自己生成的,分布式環(huán)境下又沾,每個客戶端的時間必須同步弊仪。
  • 沒有保存持有者的唯一標識,可能被別的客戶端釋放/解鎖杖刷。
  • 鎖過期的時候励饵,并發(fā)多個客戶端同時請求過來,都執(zhí)行了jedis.getSet()滑燃,最終只能 有一個客戶端加鎖成功曲横,但是該客戶端鎖的過期時間,可能被別的客戶端覆蓋不瓶。

10.3:set的擴展命令(set ex px nx)(注意可能存在的問題)

if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加鎖
    try {
        do something  //業(yè)務處理
    }catch(){
  }
  finally {
       jedis.del(key); //釋放鎖
    }
}

這個方案可能存在這樣的問題:

  • 鎖過期釋放了禾嫉,業(yè)務還沒執(zhí)行完。
  • 鎖被別的線程誤刪蚊丐。

10.4 set ex px nx + 校驗唯一隨機值,再刪除

if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加鎖
    try {
        do something  //業(yè)務處理
    }catch(){
  }
  finally {
       //判斷是不是當前線程加的鎖,是才釋放
       if (uni_request_id.equals(jedis.get(key))) {
        jedis.del(key); //釋放鎖
        }
    }
}

在這里熙参,判斷當前線程加的鎖和釋放鎖是不是一個原子操作。如果調(diào)用jedis.del()釋放鎖的時候麦备,可能這把鎖已經(jīng)不屬于當前客戶端孽椰,會解除他人加的鎖昭娩。


圖片

一般也是用lua腳本代替。lua腳本如下:

if redis.call('get',KEYS[1]) == ARGV[1] then 
   return redis.call('del',KEYS[1]) 
else
   return 0
end;

這種方式比較不錯了黍匾,一般情況下栏渺,已經(jīng)可以使用這種實現(xiàn)方式。但是存在鎖過期釋放了锐涯,業(yè)務還沒執(zhí)行完的問題(實際上磕诊,估算個業(yè)務處理的時間,一般沒啥問題了)纹腌。

11. 使用過Redisson嘛霎终?說說它的原理

分布式鎖可能存在鎖過期釋放,業(yè)務沒執(zhí)行完的問題升薯。有些小伙伴認為莱褒,稍微把鎖過期時間設置長一些就可以啦。其實我們設想一下涎劈,是否可以給獲得鎖的線程广凸,開啟一個定時守護線程,每隔一段時間檢查鎖是否還存在蛛枚,存在則對鎖的過期時間延長谅海,防止鎖過期提前釋放。

當前開源框架Redisson就解決了這個分布式鎖問題坤候。我們一起來看下Redisson底層原理是怎樣的吧:


圖片

只要線程一加鎖成功胁赢,就會啟動一個watch dog看門狗企蹭,它是一個后臺線程白筹,會每隔10秒檢查一下,如果線程1還持有鎖谅摄,那么就會不斷的延長鎖key的生存時間徒河。因此,Redisson就是使用Redisson解決了鎖過期釋放送漠,業(yè)務沒執(zhí)行完問題顽照。

12. 什么是Redlock算法

Redis一般都是集群部署的,假設數(shù)據(jù)在主從同步過程闽寡,主節(jié)點掛了代兵,Redis分布式鎖可能會有哪些問題呢?一起來看些這個流程圖:

圖片

如果線程一在Redis的master節(jié)點上拿到了鎖爷狈,但是加鎖的key還沒同步到slave節(jié)點植影。恰好這時,master節(jié)點發(fā)生故障涎永,一個slave節(jié)點就會升級為master節(jié)點思币。線程二就可以獲取同個key的鎖啦鹿响,但線程一也已經(jīng)拿到鎖了,鎖的安全性就沒了谷饿。

為了解決這個問題惶我,Redis作者 antirez提出一種高級的分布式鎖算法:Redlock。Redlock核心思想是這樣的:

搞多個Redis master部署博投,以保證它們不會同時宕掉绸贡。并且這些master節(jié)點是完全相互獨立的,相互之間不存在數(shù)據(jù)同步贬堵。同時恃轩,需要確保在這多個master實例上,是與在Redis單實例黎做,使用相同方法來獲取和釋放鎖叉跛。

我們假設當前有5個Redis master節(jié)點,在5臺服務器上面運行這些Redis實例蒸殿。

圖片

RedLock的實現(xiàn)步驟:如下

  • 1.獲取當前時間筷厘,以毫秒為單位。
  • 2.按順序向5個master節(jié)點請求加鎖宏所∷盅蓿客戶端設置網(wǎng)絡連接和響應超時時間,并且超時時間要小于鎖的失效時間爬骤。(假設鎖自動失效時間為10秒充石,則超時時間一般在5-50毫秒之間,我們就假設超時時間是50ms吧)。如果超時霞玄,跳過該master節(jié)點骤铃,盡快去嘗試下一個master節(jié)點。
  • 3.客戶端使用當前時間減去開始獲取鎖時間(即步驟1記錄的時間)坷剧,得到獲取鎖使用的時間惰爬。當且僅當超過一半(N/2+1,這里是5/2+1=3個節(jié)點)的Redis master節(jié)點都獲得鎖惫企,并且使用的時間小于鎖失效時間時撕瞧,鎖才算獲取成功。(如上圖狞尔,10s> 30ms+40ms+50ms+4m0s+50ms)
  • 如果取到了鎖丛版,key的真正有效時間就變啦,需要減去獲取鎖所使用的時間偏序。
  • 如果獲取鎖失斠称琛(沒有在至少N/2+1個master實例取到鎖,有或者獲取鎖時間已經(jīng)超過了有效時間)禽车,客戶端要在所有的master節(jié)點上解鎖(即便有些master節(jié)點根本就沒有加鎖成功寇漫,也需要解鎖刊殉,以防止有些漏網(wǎng)之魚)。

簡化下步驟就是:

  • 按順序向5個master節(jié)點請求加鎖
  • 根據(jù)設置的超時時間來判斷州胳,是不是要跳過該master節(jié)點记焊。
  • 如果大于等于三個節(jié)點加鎖成功,并且使用的時間小于鎖的有效期栓撞,即可認定加鎖成功啦遍膜。
  • 如果獲取鎖失敗,解鎖瓤湘!

13. Redis的跳躍表

圖片
  • 跳躍表是有序集合zset的底層實現(xiàn)之一
  • 跳躍表支持平均O(logN),最壞 O(N)復雜度的節(jié)點查找瓢颅,還可以通過順序性操作批量處理節(jié)點。
  • 跳躍表實現(xiàn)由zskiplist和zskiplistNode兩個結(jié)構(gòu)組成弛说,其中zskiplist用于保存跳躍表信息(如表頭節(jié)點挽懦、表尾節(jié)點、長度)木人,而zskiplistNode則用于表示跳躍表節(jié)點信柿。
  • 跳躍表就是在鏈表的基礎上,增加多級索引提升查找效率醒第。

14. MySQL與Redis 如何保證雙寫一致性

  • 緩存延時雙刪
  • 刪除緩存重試機制
  • 讀取biglog異步刪除緩存

14.1 延時雙刪渔嚷?

什么是延時雙刪呢?流程圖如下:

圖片
  1. 先刪除緩存
  2. 再更新數(shù)據(jù)庫
  3. 休眠一會(比如1秒)稠曼,再次刪除緩存形病。

這個休眠一會,一般多久呢霞幅?都是1秒漠吻?

這個休眠時間 = 讀業(yè)務邏輯數(shù)據(jù)的耗時 + 幾百毫秒。為了確保讀請求結(jié)束蝗岖,寫請求可以刪除讀請求可能帶來的緩存臟數(shù)據(jù)侥猩。

這種方案還算可以榔至,只有休眠那一會(比如就那1秒)抵赢,可能有臟數(shù)據(jù),一般業(yè)務也會接受的唧取。但是如果第二次刪除緩存失敗呢铅鲤?緩存和數(shù)據(jù)庫的數(shù)據(jù)還是可能不一致,對吧枫弟?給Key設置一個自然的expire過期時間邢享,讓它自動過期怎樣?那業(yè)務要接受過期時間內(nèi)淡诗,數(shù)據(jù)的不一致咯骇塘?還是有其他更佳方案呢伊履?

14.2 刪除緩存重試機制

因為延時雙刪可能會存在第二步的刪除緩存失敗,導致的數(shù)據(jù)不一致問題款违√破伲可以使用這個方案優(yōu)化:刪除失敗就多刪除幾次呀,保證刪除緩存成功就可以了呀~ 所以可以引入刪除緩存重試機制

圖片
  1. 寫請求更新數(shù)據(jù)庫
  2. 緩存因為某些原因,刪除失敗
  3. 把刪除失敗的key放到消息隊列
  4. 消費消息隊列的消息插爹,獲取要刪除的key
  5. 重試刪除緩存操作

14.3 讀取biglog異步刪除緩存

重試刪除緩存機制還可以吧哄辣,就是會造成好多業(yè)務代碼入侵。其實赠尾,還可以這樣優(yōu)化:通過數(shù)據(jù)庫的binlog來異步淘汰key力穗。

圖片

以mysql為例吧

  • 可以使用阿里的canal將binlog日志采集發(fā)送到MQ隊列里面
  • 然后通過ACK機制確認處理這條更新消息,刪除緩存气嫁,保證數(shù)據(jù)緩存一致性

15. 為什么Redis 6.0 之后改多線程呢当窗?

  • Redis6.0之前,Redis在處理客戶端的請求時寸宵,包括讀socket超全、解析、執(zhí)行邓馒、寫socket等都由一個順序串行的主線程處理嘶朱,這就是所謂的“單線程”。
  • Redis6.0之前為什么一直不使用多線程光酣?使用Redis時疏遏,幾乎不存在CPU成為瓶頸的情況, Redis主要受限于內(nèi)存和網(wǎng)絡救军。例如在一個普通的Linux系統(tǒng)上财异,Redis通過使用pipelining每秒可以處理100萬個請求,所以如果應用程序主要使用O(N)或O(log(N))的命令唱遭,它幾乎不會占用太多CPU戳寸。

redis使用多線程并非是完全摒棄單線程,redis還是使用單線程模型來處理客戶端的請求拷泽,只是使用多線程來處理數(shù)據(jù)的讀寫和協(xié)議解析疫鹊,執(zhí)行命令還是使用單線程。

這樣做的目的是因為redis的性能瓶頸在于網(wǎng)絡IO而非CPU司致,使用多線程能提升IO讀寫的效率拆吆,從而整體提高redis的性能。

16. 聊聊Redis 事務機制

Redis通過MULTI脂矫、EXEC枣耀、WATCH等一組命令集合,來實現(xiàn)事務機制庭再。事務支持一次執(zhí)行多個命令捞奕,一個事務中所有命令都會被序列化牺堰。在事務執(zhí)行過程,會按照順序串行化執(zhí)行隊列中的命令颅围,其他客戶端提交的命令請求不會插入到事務執(zhí)行命令序列中萌焰。

簡言之,Redis事務就是順序性谷浅、一次性扒俯、排他性的執(zhí)行一個隊列中的一系列命令。

Redis執(zhí)行事務的流程如下:

  • 開始事務(MULTI)
  • 命令入隊
  • 執(zhí)行事務(EXEC)一疯、撤銷事務(DISCARD )
命令 描述
EXEC 執(zhí)行所有事務塊內(nèi)的命令
DISCARD 取消事務撼玄,放棄執(zhí)行事務塊內(nèi)的所有命令
MULTI 標記一個事務塊的開始
UNWATCH 取消 WATCH 命令對所有 key 的監(jiān)視。
WATCH 監(jiān)視key 墩邀,如果在事務執(zhí)行之前掌猛,該key 被其他命令所改動,那么事務將被打斷眉睹。

17. Redis的Hash 沖突怎么辦

Redis 作為一個K-V的內(nèi)存數(shù)據(jù)庫荔茬,它使用用一張全局的哈希來保存所有的鍵值對。這張哈希表竹海,有多個哈希桶組成慕蔚,哈希桶中的entry元素保存了key和value指針,其中key指向了實際的鍵斋配,value指向了實際的值孔飒。

圖片

哈希表查找速率很快的,有點類似于Java中的HashMap艰争,它讓我們在O(1) 的時間復雜度快速找到鍵值對坏瞄。首先通過key計算哈希值,找到對應的哈希桶位置甩卓,然后定位到entry鸠匀,在entry找到對應的數(shù)據(jù)。

什么是哈希沖突逾柿?

哈希沖突:通過不同的key缀棍,計算出一樣的哈希值,導致落在同一個哈希桶中鹿寻。

Redis為了解決哈希沖突睦柴,采用了鏈式哈希诽凌。鏈式哈希是指同一個哈希桶中毡熏,多個元素用一個鏈表來保存,它們之間依次用指針連接侣诵。

圖片

有些讀者可能還會有疑問:哈希沖突鏈上的元素只能通過指針逐一查找再操作痢法。當往哈希表插入數(shù)據(jù)很多狱窘,沖突也會越多,沖突鏈表就會越長财搁,那查詢效率就會降低了蘸炸。

為了保持高效,Redis 會對哈希表做rehash操作尖奔,也就是增加哈希桶搭儒,減少沖突。為了rehash更高效提茁,Redis還默認使用了兩個全局哈希表淹禾,一個用于當前使用,稱為主哈希表茴扁,一個用于擴容铃岔,稱為備用哈希表

18. 在生成 RDB期間峭火,Redis 可以同時處理寫請求么毁习?

可以的,Redis提供兩個指令生成RDB卖丸,分別是save和bgsave纺且。

  • 如果是save指令,會阻塞稍浆,因為是主線程執(zhí)行的隆檀。
  • 如果是bgsave指令,是fork一個子進程來寫入RDB文件的粹湃,快照持久化完全交給子進程來處理恐仑,父進程則可以繼續(xù)處理客戶端的請求。

19. Redis底層为鳄,使用的什么協(xié)議?

RESP裳仆,英文全稱是Redis Serialization Protocol,它是專門為redis設計的一套序列化協(xié)議. 這個協(xié)議其實在redis的1.2版本時就已經(jīng)出現(xiàn)了,但是到了redis2.0才最終成為redis通訊協(xié)議的標準。

RESP主要有實現(xiàn)簡單孤钦、解析速度快歧斟、可讀性好等優(yōu)點。

20. 布隆過濾器

應對緩存穿透問題偏形,我們可以使用布隆過濾器静袖。布隆過濾器是什么呢?

布隆過濾器是一種占用空間很小的數(shù)據(jù)結(jié)構(gòu)俊扭,它由一個很長的二進制向量和一組Hash映射函數(shù)組成队橙,它用于檢索一個元素是否在一個集合中,空間效率和查詢時間都比一般的算法要好的多,缺點是有一定的誤識別率和刪除困難捐康。

布隆過濾器原理是仇矾?假設我們有個集合A,A中有n個元素解总。利用k個哈希散列函數(shù)贮匕,將A中的每個元素映射到一個長度為a位的數(shù)組B中的不同位置上,這些位置上的二進制數(shù)均設置為1花枫。如果待檢查的元素刻盐,經(jīng)過這k個哈希散列函數(shù)的映射后,發(fā)現(xiàn)其k個位置上的二進制數(shù)全部為1劳翰,這個元素很可能屬于集合A隙疚,反之,一定不屬于集合A磕道。

來看個簡單例子吧供屉,假設集合A有3個元素,分別為{d1,d2,d3}溺蕉。有1個哈希函數(shù)伶丐,為Hash1。現(xiàn)在將A的每個元素映射到長度為16位數(shù)組B疯特。

圖片

我們現(xiàn)在把d1映射過來哗魂,假設Hash1(d1)= 2,我們就把數(shù)組B中漓雅,下標為2的格子改成1录别,如下:

圖片

我們現(xiàn)在把d2也映射過來,假設Hash1(d2)= 5邻吞,我們把數(shù)組B中组题,下標為5的格子也改成1,如下:

圖片

接著我們把d3也映射過來抱冷,假設Hash1(d3)也等于 2崔列,它也是把下標為2的格子標1:

圖片

因此阀捅,我們要確認一個元素dn是否在集合A里溉苛,我們只要算出Hash1(dn)得到的索引下標败潦,只要是0菱属,那就表示這個元素不在集合A,如果索引下標是1呢省咨?那該元素可能是A中的某一個元素腮敌。因為你看竖瘾,d1和d3得到的下標值鸣剪,都可能是1组底,還可能是其他別的數(shù)映射的丈积,布隆過濾器是存在這個缺點的:會存在hash碰撞導致的假陽性,判斷存在誤差斤寇。

如何減少這種誤差呢桶癣?

  • 搞多幾個哈希函數(shù)映射拥褂,降低哈希碰撞的概率
  • 同時增加B數(shù)組的bit長度娘锁,可以增大hash函數(shù)生成的數(shù)據(jù)的范圍,也可以降低哈希碰撞的概率

我們又增加一個Hash2哈希映射函數(shù)饺鹃,假設Hash2(d1)=6,Hash2(d3)=8,它倆不就不沖突了嘛莫秆,如下:

圖片

即使存在誤差,我們可以發(fā)現(xiàn)悔详,布隆過濾器并沒有存放完整的數(shù)據(jù)镊屎,它只是運用一系列哈希映射函數(shù)計算出位置,然后填充二進制向量茄螃。如果數(shù)量很大的話缝驳,布隆過濾器通過極少的錯誤率,換取了存儲空間的極大節(jié)省归苍,還是挺劃算的用狱。

目前布隆過濾器已經(jīng)有相應實現(xiàn)的開源類庫啦,如Google的Guava類庫拼弃,Twitter的 Algebird 類庫夏伊,信手拈來即可,或者基于Redis自帶的Bitmaps自行實現(xiàn)設計也是可以的吻氧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末溺忧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盯孙,更是在濱河造成了極大的恐慌鲁森,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件振惰,死亡現(xiàn)場離奇詭異刀森,居然都是意外死亡,警方通過查閱死者的電腦和手機报账,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門研底,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人透罢,你說我怎么就攤上這事榜晦。” “怎么了羽圃?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵乾胶,是天一觀的道長。 經(jīng)常有香客問我,道長识窿,這世上最難降的妖魔是什么斩郎? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喻频,結(jié)果婚禮上缩宜,老公的妹妹穿的比我還像新娘。我一直安慰自己甥温,他們只是感情好锻煌,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姻蚓,像睡著了一般宋梧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狰挡,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天捂龄,我揣著相機與錄音,去河邊找鬼加叁。 笑死倦沧,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的殉农。 我是一名探鬼主播刀脏,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼超凳!你這毒婦竟也來了愈污?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤轮傍,失蹤者是張志新(化名)和其女友劉穎暂雹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體创夜,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡杭跪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了驰吓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涧尿。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖檬贰,靈堂內(nèi)的尸體忽然破棺而出姑廉,到底是詐尸還是另有隱情,我是刑警寧澤翁涤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布桥言,位于F島的核電站萌踱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏号阿。R本人自食惡果不足惜并鸵,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扔涧。 院中可真熱鬧园担,春花似錦、人聲如沸扰柠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卤档。三九已至,卻和暖如春程剥,著一層夾襖步出監(jiān)牢的瞬間劝枣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工织鲸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舔腾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓搂擦,卻偏偏與公主長得像稳诚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瀑踢,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容