<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis 進(jìn)階筆記

          共 5358字,需瀏覽 11分鐘

           ·

          2021-05-09 13:07

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


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


          1. String

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


          2. List

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

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


          3. Set

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

          可運用場景:活動抽獎去重。

          4. Hash

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

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


          5. Zset

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

          可運用場景:榜單,總榜,熱榜。


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



          1. 布隆過濾器


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

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

          (1)命令使用


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

          (2)原理



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


          2. 分布式鎖


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

          命令


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

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

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


          三、深入原理



          1. IO模型


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

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


          Redis IO 處理模型


          2. 通信協(xié)議


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


          3. 持久化


          (1)RDB


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


          (2)AOF


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

          (3)混合持久化


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

          4. 主從同步


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

          同步過程:


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


          5. Sentinel


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


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

          6. Redis集群工作原理


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


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

          set key value

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


          集群節(jié)點同步:

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

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


          四、Redis為什么變慢了



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

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


          原因分析


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


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


          五、Redis安全


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

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

          瀏覽 23
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲黄色性爱视频 | 色 高清在线 | 青草福利在线视频 | 日韩免费一级 | 日韩一级黄 |