1. Redis 進(jìn)階筆記

        共 5358字,需瀏覽 11分鐘

         ·

        2021-04-22 16:50

        導(dǎo)語 | Redis 大家用的不少,但是我們大多數(shù)人可能都只是關(guān)注業(yè)務(wù)本身,對于底層的細(xì)節(jié)則經(jīng)常忽略,久而久之,對個(gè)人的成長幫助甚少。本文為大家總結(jié)了關(guān)于 Redis 常見用法的進(jìn)階指南,希望幫助大家加深對這門技術(shù)的理解。文章作者:何永康,騰訊 CSIG 后臺(tái)研發(fā)工程師。


        一、Redis 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)


        1. String

        Redis 里的字符串是動(dòng)態(tài)字符串,會(huì)根據(jù)實(shí)際情況動(dòng)態(tài)調(diào)整。類似于 Go 里面的切片-slice,如果長度不夠則自動(dòng)擴(kuò)容。至于如何擴(kuò)容,方法大致如下:當(dāng) length 小于 1M 的時(shí)候,擴(kuò)容規(guī)則將目前的字符串翻倍;如果 length 大于 1M 的話,則每次只會(huì)擴(kuò)容 1M,直到達(dá)到 512M。


        2. List

        Redis 里的 List 是一個(gè)鏈表,由于鏈表本身插入和刪除比較塊,但是查詢的效率比較低,所以常常被用做異步隊(duì)列。Redis 里的 List 設(shè)計(jì)非常牛,當(dāng)數(shù)據(jù)量比較小的時(shí)候,數(shù)據(jù)結(jié)構(gòu)是壓縮鏈表,而當(dāng)數(shù)據(jù)量比較多的時(shí)候就成為了快速鏈表。

        可運(yùn)用的場景:在業(yè)務(wù)中異步隊(duì)列使用 rpush/lpush 操作隊(duì)列,使用 lpop 和 rpop 出隊(duì)列,具體結(jié)構(gòu)如下圖所示:


        3. Set

        Redis 中的 set 是一個(gè)無序 Map,由于 Go 中沒有 set 結(jié)構(gòu),所以這里只能類比 Java 中的 HashSet 概念。Redis 的 set 底層也是一個(gè) Map 結(jié)構(gòu),不同于 Java 的是:alue 是一個(gè) NULL。由于 set 的特性,它可以用于去重邏輯,這一點(diǎn)在 Java 中也經(jīng)常使用。

        可運(yùn)用場景:活動(dòng)抽獎(jiǎng)去重。

        4. Hash

        Redis 中的字典類型大家不陌生,也許其他語言都有這種結(jié)構(gòu)(python,Java,Go), hash 的擴(kuò)容 rehash 過程和 Go 里面的設(shè)計(jì)頗有類似,也就是維護(hù)了兩個(gè) hash 結(jié)構(gòu),如果需要擴(kuò)容的時(shí)候,就把新的數(shù)據(jù)寫入新字典中,然后后端起一個(gè)線程來逐步遷移,總體上來說就是采用了空間換時(shí)間的思想。

        可運(yùn)用場景:記錄業(yè)務(wù)中的不同用戶/不同商品/不同場景的信息:如某個(gè)用戶的名稱,或者用戶的歷史行為。


        5. Zset

        Redis 中的 zset 是一個(gè)比較特殊的數(shù)據(jù)結(jié)構(gòu)(跳躍列表),也就是我們了解到的跳表,底層由于 set 的特性保證了 value 唯一,同時(shí)也給了 value 一個(gè)得分,所謂的有序其實(shí)就是根據(jù)這個(gè)得分來排序。至于跳躍表如何插入,其實(shí)內(nèi)部采用了一個(gè)隨機(jī)策略:L0:100%-L2:50%-L3:25%-....Ln:(n-1)value/2%。

        可運(yùn)用場景:榜單,總榜,熱榜。


        二、Redis 進(jìn)階使用



        1. 布隆過濾器


        Redis 在 4.0 以后支持布隆過濾(準(zhǔn)確的來說是支持了布隆過濾器的插件),給 Redis 提供了強(qiáng)大的去重功能。在業(yè)務(wù)中,我們可能需要查詢數(shù)據(jù)庫判斷歷史數(shù)據(jù)是否存在,如果數(shù)據(jù)庫的并發(fā)能力有限,這個(gè)時(shí)候我們可以采用 Redis 的 set 做去重。如果緩存的數(shù)據(jù)過大,這個(gè)時(shí)候就需要遍歷所有緩存數(shù)據(jù),另外如果我們的歷史數(shù)據(jù)緩存寫不下了,終究要去查詢數(shù)據(jù)庫,這個(gè)時(shí)候就可以使用布隆過濾器。

        當(dāng)然布隆過濾器精確度不是 100% 準(zhǔn)確(如果對數(shù)據(jù)準(zhǔn)確度要求很高的話,這里不建議使用),因?yàn)閷τ诖嬖诘臄?shù)據(jù)也許這個(gè)值不一定存在,當(dāng)然如果不存在,那肯定 100% 不存在了。

        (1)命令使用


        bf.add #添加元素bf.exists #判斷元素是否存在bf.madd #批量添加bf.mexists #批量判斷是否存在
         

        (2)原理



        布隆過濾的組成可以當(dāng)作一個(gè)位數(shù)組和幾個(gè)計(jì)算結(jié)果比較均勻的 hash 函數(shù),每次添加 key 的時(shí)候,會(huì)把 key 通過多次 hash 來計(jì)算所得到的位置,如果當(dāng)前位置不是 0 則表示存在。可以看到,這樣的計(jì)算存在一定誤差,這也正是它的不準(zhǔn)確性問題的由來。


        2. 分布式鎖


        大家對分布式鎖也許也不會(huì)陌生,現(xiàn)在市面上主流的實(shí)現(xiàn)分布鎖的技術(shù)有 ZK 和 Redis;下文為大家簡單介紹一下 Redis 如何實(shí)現(xiàn)分布式鎖。

        命令


        setnx lock:mutex ture #加鎖del lock:mutex #刪除鎖

        實(shí)現(xiàn)分布式鎖的核心就是:請求的時(shí)候 Set 這個(gè) key,如果其他請求設(shè)置失敗的時(shí)候,即拿不到鎖。但是存在一個(gè)問題:如果業(yè)務(wù) panic 或者忘記調(diào)用 del 的話,就會(huì)產(chǎn)生死鎖,這個(gè)時(shí)候大家很容易能想到:我們可以 expire 一個(gè)過期時(shí)間,這樣就可以保證請求不會(huì)一直獨(dú)占鎖且無法釋放鎖的邏輯了。

        但是假設(shè)業(yè)務(wù)存在這樣一種情況:A 請求在獲取鎖后處理邏輯,由于邏輯過長,這個(gè)時(shí)候鎖到期釋放了,A 這個(gè)時(shí)候剛剛處理完成,而 B 又去改了這個(gè)數(shù)據(jù),這就存在一個(gè)鎖失效的問題。解決這種問題參考 CAS 的方式,對鎖設(shè)置一個(gè)隨機(jī)數(shù),可以理解為版本號,如果釋放的時(shí)候版本號不一致,則表示數(shù)字已經(jīng)在釋放那一刻改掉了。


        三、深入原理



        1. IO模型


        Redis 是單線程模型(這里的單線程指的是 IO 和鍵值對的讀寫是一個(gè)線程完成的),當(dāng)然如果嚴(yán)謹(jǐn)?shù)膩碚f還是可以理解為是多線程,不過這樣的多線程不過是在數(shù)據(jù)備份的時(shí)候會(huì) fork 一個(gè)子進(jìn)程對數(shù)據(jù)進(jìn)行從磁盤讀取數(shù)據(jù)并組裝 RDB,然后同步給 slaver 節(jié)點(diǎn)的操作,當(dāng)然包括備份和持久化也都是通過另外起線程完成的,所以我們可以把 Redis 認(rèn)作為一個(gè)單線程模型。

        那么問題來了,為什么單線程的模型能這么快?原因很簡單,因?yàn)?Redis 本身就是在內(nèi)存中運(yùn)算,而對于上游的客戶端請求,采用了多路復(fù)用的原理。Redis 會(huì)給每一個(gè)客戶端套接字都關(guān)聯(lián)一個(gè)指令隊(duì)列,客戶端的指令隊(duì)列通過隊(duì)列排隊(duì)來進(jìn)行順序處理,同時(shí) Reids 給每一個(gè)客戶端的套件字關(guān)聯(lián)一個(gè)響應(yīng)隊(duì)列,Redis 服務(wù)器通過響應(yīng)隊(duì)列來將指令的接口返回給客戶端。


        Redis IO 處理模型


        2. 通信協(xié)議


        Redis 采用了 Gossip 協(xié)議作為通信協(xié)議。Gossip 是一種傳播消息的方式,可以類比為瘟疫或者流感的傳播方式,使用 Gossip 協(xié)議的有:Redis Cluster、Consul、Apache Cassandra 等。Gossip 協(xié)議類似病毒擴(kuò)散的方式,將信息傳播到其他的節(jié)點(diǎn),這種協(xié)議效率很高,只需要廣播到附近節(jié)點(diǎn),然后被廣播的節(jié)點(diǎn)繼續(xù)做同樣的操作即可。當(dāng)然這種協(xié)議也有一個(gè)弊端就是:會(huì)存在浪費(fèi),哪怕一個(gè)節(jié)點(diǎn)之前被通知到了,下次被廣播后仍然會(huì)重復(fù)轉(zhuǎn)發(fā)。


        3. 持久化


        (1)RDB


        RDB 是對當(dāng)前 Redis 的存儲(chǔ)數(shù)據(jù)進(jìn)行一次快照(具體原理和如何做,限于篇幅這里不做過多復(fù)述了)。


        (2)AOF


        日志只記錄 Redis 對內(nèi)存修改的指令記錄,Redis 提供了一個(gè) bgrewriteaif 的指令對 AOF 進(jìn)行壓縮。原理就是:開辟一個(gè)子進(jìn)程對內(nèi)存進(jìn)行遍歷后,轉(zhuǎn)換成一系列對 Redis 的操作指令,序列化到一個(gè)新的 AOF 日志文件中。系列化完成后再將發(fā)送的增量 AOF 日志追加到這個(gè)新的 AOF 日志中,追加完成后用新的 AOF 日志代替舊的。

        (3)混合持久化


        由于單純 RDB 的話,可能存在數(shù)據(jù)的丟失,而頻繁的 AOF 又會(huì)影響了性能,在 Redis 4.0 之后,支持了混合持久化,也就是每次啟動(dòng)時(shí)候通過 RDB+增量的 AOF 文件來進(jìn)行回復(fù),由于增量的 AOF 僅記錄了開始持久化到持久化結(jié)束期間發(fā)生的增量,這樣日志不會(huì)太大,性能相對較高。

        4. 主從同步


        Redis 的同步方式有:主從同步、從從同步(由于全部都由 master 同步的話,會(huì)損耗性能,所以部分的 slave 會(huì)通過 slave 之間進(jìn)行同步)。

        同步過程:


        • 建立連接,然后從庫告訴主庫:“我要同步啦,你給我準(zhǔn)備好”,然后主庫跟從庫說:“收到”。
        • 從庫拿到數(shù)據(jù)后,要把數(shù)據(jù)保存到庫里。這個(gè)時(shí)候就會(huì)在本地完成數(shù)據(jù)的加載,會(huì)用到 RDB 。
        • 主庫把新來的數(shù)據(jù) AOF 同步給從庫。


        5. Sentinel


        Redis 的主從切換是通過哨兵來解決的。這里哨兵主要解決的問題就是:當(dāng) master 掛了的情況下,如果在短時(shí)間內(nèi)重新選舉出一個(gè)新的 master 。


        Sentinel 集群是一個(gè)由 3-5 個(gè)(可以更多)節(jié)點(diǎn)組成的,用來監(jiān)聽整個(gè) Redis 的集群,如果發(fā)現(xiàn) master 不可用的時(shí)候,會(huì)關(guān)閉和斷開全部的與 master 相連的舊鏈接。這個(gè)時(shí)候 Sentinel 會(huì)完成選舉和故障轉(zhuǎn)移,新的請求則會(huì)轉(zhuǎn)到新到 master 中。

        6. Redis集群工作原理


        Redis 集群通過槽指派機(jī)制來決定寫命令應(yīng)該被分配到那個(gè)節(jié)點(diǎn)。整個(gè)集群對應(yīng)的槽是由 16384 大小的二進(jìn)制數(shù)組組成,集群中每個(gè)主節(jié)點(diǎn)分配一部分槽,每條寫命令落到二進(jìn)制數(shù)組中的某個(gè)位置,該位置被分配給了哪個(gè)節(jié)點(diǎn),則對應(yīng)的命令就由該節(jié)點(diǎn)去執(zhí)行。槽指派對應(yīng)的二進(jìn)制數(shù)組如下圖所示:


        從上圖可以看到:節(jié)點(diǎn) 1 只負(fù)責(zé) 執(zhí)行 0 - 4999 的槽位,而節(jié)點(diǎn) 2 負(fù)責(zé)執(zhí)行 5000 - 9999,節(jié)點(diǎn) 3 執(zhí)行 9999- 16383 。當(dāng)進(jìn)行寫的時(shí)候:

        set key value

        命令通過 CRC16(key) & 16383 = 6789(假設(shè)結(jié)果),由于節(jié)點(diǎn) 2 負(fù)責(zé) 5000~9999 的槽位,則該命令的結(jié)果 6789 最終由節(jié)點(diǎn) 2 執(zhí)行。當(dāng)然如果在節(jié)點(diǎn) 2 執(zhí)行一條命令時(shí),假設(shè)通過 CRC 計(jì)算后得到的值為 567,則其應(yīng)該由節(jié)點(diǎn) 1 執(zhí)行,此時(shí)命令會(huì)進(jìn)行轉(zhuǎn)向操作,將要執(zhí)行的命令流轉(zhuǎn)到節(jié)點(diǎn) 1 上去執(zhí)行。


        集群節(jié)點(diǎn)同步:

        集群中每個(gè)主節(jié)點(diǎn)都會(huì)定時(shí)發(fā)送信息到其他主節(jié)點(diǎn)進(jìn)行同步,如果其他主節(jié)點(diǎn)在規(guī)定時(shí)間內(nèi)響應(yīng)了發(fā)送消息的主節(jié)點(diǎn),則發(fā)送消息的主節(jié)點(diǎn)認(rèn)為響應(yīng)了消息的主節(jié)點(diǎn)正常,反之則認(rèn)為響應(yīng)消息的主節(jié)點(diǎn)疑似下線,則發(fā)送消息的主節(jié)點(diǎn)在其節(jié)點(diǎn)上將其標(biāo)記“似下線”。

        當(dāng)集群中超過一半以上的節(jié)點(diǎn)認(rèn)為某個(gè)主節(jié)點(diǎn)被標(biāo)記為“疑似下線”,則其中某個(gè)主節(jié)點(diǎn)將疑似下線節(jié)點(diǎn)標(biāo)記為下線狀態(tài),并向集群廣播一條下線消息,當(dāng)下線節(jié)點(diǎn)對應(yīng)的從節(jié)點(diǎn)接收到該消息時(shí),則從從節(jié)點(diǎn)中選舉出一個(gè)節(jié)點(diǎn)作為主節(jié)點(diǎn)繼續(xù)對外提供服務(wù)。


        四、Redis為什么變慢了



        業(yè)務(wù)場景中,不知道大家是否碰到過 Redis 變慢的情況:

        • 執(zhí)行 SET、DEL 命令耗時(shí)也很久;
        • 偶現(xiàn)卡頓,之后又恢復(fù)正常了;
        • 在某個(gè)時(shí)間點(diǎn),突然開始變慢了。


        原因分析


        查看慢查詢,由于筆者本身機(jī)器沒有慢查詢,所以這里看到是空(實(shí)在尷尬,這里沒有可用的例子~~)


        • 由于 Redis 在 IO 操作和對鍵值對的操作是單線程的,所以直接在客戶端 Redis-cli 上執(zhí)行的 Redis 命令有可能會(huì)導(dǎo)致操作延遲變大;
        • 使用復(fù)雜的命令會(huì)讓 Redis的處理變慢,以及CPU過高,例如 SORT、SUNION、ZUNIONSTORE 聚合類命令(時(shí)間負(fù)責(zé)度O(N) );
        • 查詢的數(shù)據(jù)量過大,使得更多時(shí)間花費(fèi)在數(shù)據(jù)協(xié)議的組裝和網(wǎng)絡(luò)傳輸過程中;
        • 大 key 查詢,比如對于一個(gè)很大的 hash、zset 等,這樣的對象對 Redis 的集群數(shù)據(jù)遷移帶來了很大的問題,因?yàn)樵诩涵h(huán)境下,如果某個(gè) key 太大,會(huì)導(dǎo)致數(shù)據(jù)遷移卡頓;
        • 另外在內(nèi)存分配上,如果一個(gè) key 太大,那么當(dāng)它需要擴(kuò)容時(shí),會(huì)一次性申請更大的一塊內(nèi)存,這也會(huì)導(dǎo)致卡頓。如果這個(gè)大 key 被刪除,內(nèi)存會(huì)一次性回收,卡頓現(xiàn)象會(huì)再一次產(chǎn)生。
        • 集中過期,變慢的時(shí)間統(tǒng)一,所以業(yè)務(wù)中的 Key 過期時(shí)間盡量在統(tǒng)一的一個(gè)時(shí)間點(diǎn)加上一個(gè)隨機(jī)數(shù)時(shí)間;
        • 內(nèi)存使用達(dá)到上限,當(dāng)內(nèi)存達(dá)到內(nèi)存上限的時(shí)候,就不許淘汰一些數(shù)據(jù),這個(gè)時(shí)候也可能導(dǎo)致 Redis 查詢效率低;
        • 碎片整理,Redis 在 4.0 版本后會(huì)自動(dòng)整理碎片(由于內(nèi)存回收過程中存在大量的碎片空間,不整理會(huì)導(dǎo)致 Redis 的空間少量浪費(fèi)),而在整理碎片的過程中會(huì)消耗 CPU 的資源,從而影響了請求得到性能;
        • 網(wǎng)絡(luò)帶寬,Redis 集群和業(yè)務(wù)混部,或者并發(fā)量過大以及每次返回的數(shù)據(jù)也很大,網(wǎng)卡帶寬跑滿的情況容易導(dǎo)致網(wǎng)絡(luò)阻塞;
        • AOF 的頻率過高,由于 AOF 需要將全部的寫命令同步,如果同步的間隔比較短,也會(huì)影響到 Redis 的性能;
        • Redis 提供了 flushdb 和 flushall 指令,用來清空數(shù)據(jù)庫,這也是導(dǎo)致 Redis 緩慢的操作。


        五、Redis安全


        默認(rèn)會(huì)監(jiān)聽 6379 端口,最好在 Redis 的配置文件中指定監(jiān)聽的 IP 地址,更進(jìn)一步還可以增加 Redis 的 ACL 訪問控制,對客戶指定群組,并限限制用戶對數(shù)據(jù)的讀寫權(quán)限。

        訪問 Redis 盡量走公司代理,由于 Redis 本身不支持 SSL 的鏈接,所以走公司代理可以保證安全。客戶端登陸 Redis 必須設(shè)置 Auth 秘密登陸。

        瀏覽 56
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 国产精品久久久免费无码 | 巴西性极品videos | 操屄网 | 99热大香蕉 | 午夜电影理伦片2023在线观看 |