溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

redis奇葩數(shù)據(jù)類型與集群知識(shí)有哪些

發(fā)布時(shí)間:2022-01-06 12:57:56 來(lái)源:億速云 閱讀:143 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

本篇文章為大家展示了redis奇葩數(shù)據(jù)類型與集群知識(shí)有哪些,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

    多樣的數(shù)據(jù)類型

    string 類型簡(jiǎn)單方便,支持空間預(yù)分配,也就是每次會(huì)多分配點(diǎn)空間,這樣 string 如果下次變長(zhǎng)的話,就不需要額外的申請(qǐng)空了,當(dāng)然前提是剩余的空間夠用。

    List 類型可以實(shí)現(xiàn)簡(jiǎn)單的消息隊(duì)列,但是注意可能存在消息丟失哦,它并不持 ACK 模式。

    Hash 表有點(diǎn)像關(guān)系型數(shù)據(jù)庫(kù),但是當(dāng) hash 表越來(lái)越大的時(shí)候,請(qǐng)注意,避免使用 hgetall 之類的語(yǔ)句,因?yàn)檎?qǐng)求大量的數(shù)據(jù)會(huì)導(dǎo)致redis阻塞,這樣后面的兄弟們就得等待了。

    set 集合類型可以幫你做一些統(tǒng)計(jì),比如你要統(tǒng)計(jì)某天活躍的用戶,可以直接把用戶ID扔到集合里,集合支持一些騷操作,比如 sdiff 可以獲取集合之間的差集,sunion 可以獲取集合之間的并集,功能很多,但是一定需要謹(jǐn)慎,因?yàn)榕1频墓δ苁怯写鷥r(jià)的,這些操作需要耗費(fèi)一些 CPU 和IO 資源,可能會(huì)導(dǎo)致阻塞,因此大集合之間的騷操作要慎用,

    zset 可以說(shuō)是最閃耀的星,可以做排序,因?yàn)榭梢耘判?,因此?yīng)用場(chǎng)景挺多,比如點(diǎn)贊前xx名用戶,延時(shí)隊(duì)列等等。

    bitmap 位圖的好處就是在于節(jié)省空間,特別在做一些統(tǒng)計(jì)類的方面,比如要統(tǒng)計(jì)某一天有多少個(gè)用戶簽到了并且某個(gè)用戶是否簽到了,如果不用bitmap的話,你可能會(huì)想到用set。

    SADD day 1234//簽到就添加到集合
    SISMEMBER day 1234//判斷1234是否簽到
    SCARD day   //有多少個(gè)簽到的

    set 在功能上可以滿足,但是相比bitmap的話,set要更耗費(fèi)存儲(chǔ)空間,set的底層主要是由整數(shù)集合或者 hashtable 組成,整數(shù)集合只有在數(shù)據(jù)量非常小的情況下才會(huì)使用,一般是小于512個(gè)元素,同時(shí)元素必須都是整數(shù),對(duì)于set來(lái)說(shuō),整數(shù)集合的數(shù)據(jù)更加緊湊,他們?cè)趦?nèi)存是上連續(xù)的,查詢的話只能是二分查找了,時(shí)間復(fù)雜度是O(logN),而 hashtable 就不同了,這里的 hashtable 和 redis 的5大數(shù)據(jù)類型中的hash是一樣的,只不過(guò)沒(méi)有 value 而已,value 指向個(gè) null,同時(shí)也不存在沖突,因?yàn)檫@里是集合,但是需要考慮 rehash 相關(guān)問(wèn)題。ok,扯的有點(diǎn)遠(yuǎn),我們說(shuō)的用戶簽到問(wèn)題,在用戶非常多的情況下,set 的話肯定會(huì)用到 hashtable,hashtable 的話,其實(shí)每個(gè)元素都是個(gè) dictEntry 結(jié)構(gòu)體

    typedef struct dictEntry {
        // 鍵
        void *key;
        // 值
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;
        // 指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
        struct dictEntry *next;
    } dictEntry;

    從這個(gè)結(jié)構(gòu)體可以看到什么呢?首先雖然值 union(沒(méi)有 value)和 next(沒(méi)有沖突)是空的,但是結(jié)構(gòu)體本身需要空間,還需要加上個(gè) key,這個(gè)占用空間是實(shí)打?qū)嵉模绻?bitmap 的話,一個(gè)bit位就可以代表一個(gè)數(shù)字,很省空間,我們來(lái)看看 bitmap 的方式如何設(shè)置和統(tǒng)計(jì)。

    SETBIT day 1234 1//簽到
    GETBIT day 1234//判斷1234是否簽到
    BITCOUNT day//有多少個(gè)簽到的

    bf 這是 redis4.0 之后支持的布隆過(guò)濾器 RedisBloom,但是需要單獨(dú)加載對(duì)應(yīng)的 module,當(dāng)然我們也可以基于上述的 bitmap 來(lái)實(shí)現(xiàn)自己的布隆過(guò)濾器,不過(guò)既然 redis 已經(jīng)支持了,通過(guò) RedisBloom 可以減少我們的開(kāi)發(fā)時(shí)間,布隆過(guò)濾器是干嘛的,我這里就不贅述了,直接來(lái)看看 RedisBloom 相關(guān)的用法吧。

    # 可以通過(guò)docker的方式快速拉取鏡像來(lái)玩耍
    docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
    docker exec -it redis-redisbloom bash
    redis-cli
    # 相關(guān)操作
    bf.reserve sign 0.001 10000
    bf.add sign 99 //99這個(gè)用戶加入
    bf.add exists 99//判斷99這個(gè)用戶是否存在

    因?yàn)椴悸∵^(guò)濾器是存在誤判的,所有 bf 支持自定義誤判率,0.001就代表誤判率,10000 代表布隆過(guò)濾器可以存儲(chǔ)的元素個(gè)數(shù),當(dāng)實(shí)際存儲(chǔ)的元素個(gè)數(shù)超過(guò)這個(gè)值的時(shí)候,誤判率會(huì)提高。

    HyperLogLog 可以用于統(tǒng)計(jì),它的優(yōu)點(diǎn)就是占用的存儲(chǔ)空間極小,只需要 12KB 的內(nèi)存就可以統(tǒng)計(jì) 2^64 個(gè)元素,那它主要統(tǒng)計(jì)什么呢?其實(shí)主要就是基數(shù)統(tǒng)計(jì),比如像 UV 這種,從功能上來(lái)說(shuō) UV 可以用 set 或者 hash 來(lái)存儲(chǔ),但是缺點(diǎn)就是耗費(fèi)存儲(chǔ),容易使之變成大 key,如果想要節(jié)省空間,bitmap 也可以,12KB 空間的 bitmap 只能統(tǒng)計(jì) 12*1024*8=98304個(gè)元素,而 HyperLogLog 卻可以統(tǒng)計(jì) 2^64 個(gè)元素,但是這么牛逼的技術(shù)其實(shí)是有誤差的,HyperLogLog 是基于概率來(lái)統(tǒng)計(jì)的,標(biāo)準(zhǔn)誤算率是 0.81%,在統(tǒng)計(jì)海量數(shù)據(jù)并且對(duì)精度要求不那么高的場(chǎng)景下,HyperLogLog 在節(jié)省空間這塊還是很優(yōu)秀的。

    PFADD uv 1 2 3 //1 2 3是活躍用戶
    PFCOUNT uv //統(tǒng)計(jì)

    GEO 是可以應(yīng)用在地理位置的業(yè)務(wù)上,比如微信附近的人或者附近的車輛等等,先來(lái)看一下如果沒(méi)有GEO 這種數(shù)據(jù)結(jié)構(gòu),你如何知道你附近的人?首先得上報(bào)自己的地理位置信息吧,比如經(jīng)度 116.397128,緯度 39.916527,此時(shí)可以用 string、hash 數(shù)據(jù)類型存儲(chǔ),但是如果要查找你附近的人,string 和 hash 這種就無(wú)能為例了,你不可能每次都要遍歷全部的數(shù)據(jù)來(lái)判斷,這樣太耗時(shí)了,當(dāng)然你也不可能通過(guò) zset 這種數(shù)據(jù)結(jié)構(gòu)來(lái)把經(jīng)緯度信息當(dāng)成權(quán)重,但是如果我們能把經(jīng)緯度信息通過(guò)某種方式轉(zhuǎn)換成一個(gè)數(shù)字,然后當(dāng)成權(quán)重好像也可以,這時(shí)我們只需通過(guò)zrangebyscore key v1 v2也可以找到附近的人。真的需要這么麻煩嗎?于是 GEO 出現(xiàn)了,GEO 轉(zhuǎn)換經(jīng)緯度為數(shù)字的方法是“二分區(qū)間,區(qū)間編碼”,這是什么意思呢?以經(jīng)度為例,它的范圍是[-180,180],如果要采用3位編碼值,那么就是需要二分3次,二分后落在左邊的用0表示,右邊的用1表示,以經(jīng)度是121.48941 來(lái)說(shuō),第一次是在[0,180]這個(gè)區(qū)間,因此記1,第二次是在[90,180],因此再記1,第三次是在[90,135],因此記0。緯度也是同樣的邏輯,假設(shè)此時(shí)對(duì)應(yīng)的緯度編碼后是010,最后把經(jīng)緯度合并在一起,需要注意的是經(jīng)度的每個(gè)值在偶數(shù)位,緯度的每個(gè)值在奇數(shù)位。

    1 1 0   //經(jīng)度
     0 1 0  //緯度
    ------------
    101100 //經(jīng)緯度對(duì)應(yīng)的數(shù)值

    原理是這樣,我們?cè)賮?lái)看看 redis 如何使用 GEO:

    GEOADD location 112.123456 41.112345 99 //上報(bào)用戶99的地理位置信息
    GEORADIUS location  112.123456 41.112345 1 km ASC COUNT 10 //獲取附近1KM的人

    搞懂集群

    生產(chǎn)環(huán)境用單實(shí)例 redis 的應(yīng)該比較少,單實(shí)例的風(fēng)險(xiǎn)在于:

    1. 單點(diǎn)故障即服務(wù)故障,沒(méi)有backup

    2. 單實(shí)例壓力大,又要提供讀,又要提供寫

    于是我們首先想到的就是經(jīng)典的主從模式,而且往往是一主多從,這是因?yàn)榇蟛糠謶?yīng)用都是讀多寫少的情況,我們的主負(fù)責(zé)更新,從負(fù)責(zé)提供讀,就算我們的主宕機(jī)了,我們也可以選擇一個(gè)從來(lái)充當(dāng)主,這樣整個(gè)應(yīng)用依然可以提供服務(wù)。

    復(fù)制過(guò)程的細(xì)節(jié)

    當(dāng)一個(gè) redis 實(shí)例首次成為某個(gè)主的從的時(shí)候,這時(shí)主得把數(shù)據(jù)發(fā)給它,也就是 rdb 文件,這個(gè)過(guò)程 master 是要 fork 一個(gè)子進(jìn)程來(lái)處理的,這個(gè)子進(jìn)程會(huì)執(zhí)行 bgsave 把當(dāng)前的數(shù)據(jù)重新保存一下,然后準(zhǔn)備發(fā)給新來(lái)的從,bgsave 的本質(zhì)是讀取當(dāng)前內(nèi)存中的數(shù)據(jù)然后保存到 rdb 文件中,這個(gè)過(guò)程涉及大量的 IO,如果直接在主進(jìn)程中來(lái)處理的話,大概率會(huì)阻塞正常的請(qǐng)求,因此使用個(gè)子進(jìn)程是個(gè)明智的選擇。

    那 fork 的子進(jìn)程在 bgsave 過(guò)程中如果有新的變更請(qǐng)求會(huì)怎么辦?

    嚴(yán)格來(lái)說(shuō)子進(jìn)程出來(lái)的一瞬間,要保存的數(shù)據(jù)應(yīng)該就是當(dāng)時(shí)那個(gè)點(diǎn)的快照數(shù)據(jù),所以是直接把當(dāng)時(shí)的內(nèi)存再?gòu)?fù)制一份嗎?不復(fù)制的話,如果這期間又有變更改怎么辦?其實(shí)這要說(shuō)到寫實(shí)復(fù)制(COW)機(jī)制,首先從表象上來(lái)看內(nèi)存是一整塊空間,其實(shí)這不太好維護(hù),因此操作系統(tǒng)會(huì)把內(nèi)存分成一小塊一小塊的,也就是內(nèi)存分頁(yè)管理,一頁(yè)的大小一般是4K、8K或者16K等等,redis 的數(shù)據(jù)都是分布在這些頁(yè)面上的,出于效率問(wèn)題,fork 出來(lái)的子進(jìn)程是和主進(jìn)程是共享同一塊的內(nèi)存的,并不會(huì)復(fù)制內(nèi)存,如果這期間主進(jìn)程有數(shù)據(jù)變更,那么為了區(qū)分,這時(shí)最快捷的做法就是把對(duì)應(yīng)的數(shù)據(jù)頁(yè)重新復(fù)制一下,然后主的變更就在這個(gè)新的數(shù)據(jù)頁(yè)上修改,并不會(huì)修改來(lái)的數(shù)據(jù)頁(yè),這樣就保證了子進(jìn)程處理的還是當(dāng)時(shí)的快照。

    以上說(shuō)的變更是從快照的角度來(lái)考慮的,如果從數(shù)據(jù)的一致性來(lái)說(shuō),當(dāng)快照的 rdb 被從庫(kù)應(yīng)用之后,這期間的變更該如何同步給從庫(kù)?答案是緩沖區(qū),這個(gè)緩沖區(qū)叫做 replication buffer,主庫(kù)在收到需要同步的命令之后,會(huì)把期間的變更都先保存在這個(gè)緩沖區(qū)中,這樣在把 rdb 發(fā)給從庫(kù)之后,緊接著會(huì)再把 replication buffer 的數(shù)據(jù)也發(fā)給從庫(kù),最終主從就保持了一致。

    replication buffer不是萬(wàn)能的補(bǔ)給劑

    我們來(lái)看看 replication buffer 持續(xù)寫入的時(shí)間有多長(zhǎng)。

    1. 我們知道主從同步的時(shí)候,主庫(kù)會(huì)執(zhí)行 fork 來(lái)讓子進(jìn)程完成相應(yīng)地工作,因此子進(jìn)程從開(kāi)始執(zhí)行 bgsave 到執(zhí)行完畢這期間,變更是要寫入 replication buffer 的。

    2. rdb 生成好之后,需要把它發(fā)送給從庫(kù),這個(gè)網(wǎng)絡(luò)傳輸是不是也需要耗點(diǎn)時(shí)間,這期間也是要寫入 replication buffer 的。

    3. 從庫(kù)在收到 rdb 之后需要把 rdb 應(yīng)用到內(nèi)存里,這期間從庫(kù)是阻塞的,無(wú)法提供服務(wù),因此這期間也是要寫入 replication buffer 的。

    replication buffer 既然是個(gè) buffer,那么它的大小就是有限的,如果說(shuō)上面3個(gè)步驟中,只要有一個(gè)耗時(shí)長(zhǎng),就會(huì)導(dǎo)致 replication buffer 快速增長(zhǎng)(前提是有正常的寫入),當(dāng) replication buffer 超過(guò)了限制之后就會(huì)導(dǎo)致主庫(kù)和從庫(kù)之間的連接斷開(kāi),斷開(kāi)之后如果從庫(kù)再次連接上來(lái)就會(huì)導(dǎo)致重新開(kāi)始復(fù)制,然后重復(fù)同樣的漫長(zhǎng)的復(fù)制步驟,因此這個(gè) replication buffer 的大小還是很關(guān)鍵的,一般需要根據(jù)寫入的速度、每秒寫入的量和網(wǎng)絡(luò)傳輸?shù)乃俣鹊纫蛩貋?lái)綜合判斷。

    從庫(kù)網(wǎng)絡(luò)不好和主庫(kù)斷了該怎么辦?

    正常來(lái)說(shuō),只要主從之間的連接建立好了,后面主庫(kù)的變更可以直接發(fā)給從庫(kù),讓從庫(kù)直接回放,但是我們并不能保證網(wǎng)絡(luò)環(huán)境是百分百的通暢的,因此也要考慮從庫(kù)和主庫(kù)之間的斷聯(lián)問(wèn)題。

    應(yīng)該是在 redis2.8 以前,只要從庫(kù)斷聯(lián),哪怕只有很短的時(shí)間,后面從庫(kù)再次連接上來(lái)的時(shí)候,主庫(kù)也會(huì)直接無(wú)腦的進(jìn)行全量同步。在 2.8 版本及以后,開(kāi)始支持增量復(fù)制了,增量復(fù)制的原理就是得有個(gè)緩沖區(qū)來(lái)保存變更的記錄,這里這個(gè)緩沖區(qū)叫做repl_backlog_buffer,這個(gè)緩沖區(qū)從邏輯上來(lái)說(shuō)是個(gè)環(huán)形緩沖區(qū),寫滿了就會(huì)從頭開(kāi)始覆蓋,所以也有大小限制。在從庫(kù)重新連接上來(lái)的時(shí)候,從庫(kù)會(huì)告訴主庫(kù):“我當(dāng)前已經(jīng)復(fù)制到了xx位置”,主庫(kù)收到從庫(kù)的消息之后開(kāi)始查看xx位置的數(shù)據(jù)是否還在 repl_backlog_buffer 中,如果在的話,直接把xx后面的數(shù)據(jù)發(fā)給從庫(kù)即可,如果不在的話,那無(wú)能為力了,只能再次進(jìn)行全量同步。

    需要一個(gè)管理者

    在主從模式下,如果主庫(kù)掛了,我們可以把一個(gè)從庫(kù)升級(jí)成主庫(kù),但是這個(gè)過(guò)程是手動(dòng)的,靠人力來(lái)操作,不能使損失降到最低,還是需要一套自動(dòng)管理和選舉的機(jī)制,這就是哨兵,哨兵它本身也是個(gè)服務(wù),只不過(guò)它不處理數(shù)據(jù)的讀寫而已,它只負(fù)責(zé)管理所有的 redis 實(shí)例,哨兵每隔一段時(shí)間會(huì)和各個(gè) redis 通信(ping 操作),每個(gè) redis 實(shí)例只要在規(guī)定的時(shí)間內(nèi)及時(shí)回復(fù),就可以表明自己的立場(chǎng)。當(dāng)然哨兵本身也可能存在宕機(jī)或者網(wǎng)絡(luò)不通的情況,因此一般哨兵也會(huì)搭建個(gè)哨兵集群,這個(gè)集群的個(gè)數(shù)最好是奇數(shù),比如3個(gè)或者5這個(gè)這種,奇數(shù)的目的主要就是為了選舉(少數(shù)服從多數(shù))。

    當(dāng)某個(gè)哨兵在發(fā)起 ping 后沒(méi)有及時(shí)收到 pong,那么就會(huì)把這個(gè) redis 實(shí)例標(biāo)記下線,此時(shí)它還是不是真正的下線,這時(shí)其他的哨兵也會(huì)判定當(dāng)前這個(gè)哨兵是不是真正的下線,當(dāng)大多數(shù)哨兵都認(rèn)定這個(gè) redis 是下線狀態(tài),那么就會(huì)把它從集群中踢出去,如果下線的是從庫(kù),那么還好,直接踢出去就ok,如果是主庫(kù)還要觸發(fā)選舉,選舉也不是盲目選舉,肯定是要選出最合適的那個(gè)從來(lái)充當(dāng)新的主庫(kù)。這個(gè)最合適充當(dāng)主庫(kù)的庫(kù),一般會(huì)按照以下優(yōu)先級(jí)來(lái)確定:

    1. 權(quán)重,每個(gè)從庫(kù)其實(shí)都可以設(shè)置一個(gè)權(quán)重,權(quán)重越高的從庫(kù)會(huì)被優(yōu)先選擇

    2. 復(fù)制的進(jìn)度,每個(gè)從庫(kù)復(fù)制的進(jìn)度可能是不一樣的,優(yōu)先選擇當(dāng)前和主庫(kù)數(shù)據(jù)差距最小的那個(gè)

    3. 服務(wù)的 ID,其實(shí)每個(gè) redis 實(shí)例都有自己的 ID,如果以上條件都一樣,那么會(huì)選擇 ID 最小的那個(gè)庫(kù)來(lái)充當(dāng)主庫(kù)

    更強(qiáng)的橫向伸縮性

    主從模式解決了單點(diǎn)故障問(wèn)題,同時(shí)讀寫分離技術(shù)使得應(yīng)用支撐能力更強(qiáng),哨兵模式可以自動(dòng)監(jiān)管集群,實(shí)現(xiàn)自動(dòng)選主,自動(dòng)剔除故障節(jié)點(diǎn)的能力。

    正常來(lái)說(shuō)只要讀的壓力越來(lái)越大,我們可以添加從庫(kù)來(lái)緩解,那如果主庫(kù)壓力很大怎么辦?這就得提到接下來(lái)要說(shuō)的分片技術(shù)了,我們只需要把主庫(kù)切成幾片,部署到不同的機(jī)器上即可。這個(gè)分片就是 redis 中的槽概念了,當(dāng)分片的時(shí)候,redis 會(huì)默認(rèn)分成 0~16383 也就是一共 16384 個(gè)槽,然后把這些槽平均分到每個(gè)分片節(jié)點(diǎn)上就可以起到負(fù)載均衡的作用了。每個(gè) key 具體該分到哪個(gè)槽中,主要是先 CRC16 得到一個(gè) 16bit 的數(shù)字,然后這個(gè)數(shù)字再對(duì) 16384 取模即可:

    crc16(key)%16384

    然后客戶端會(huì)緩存槽信息,這樣每當(dāng)一個(gè) key 到來(lái)時(shí),只要通過(guò)計(jì)算就知道該發(fā)給哪個(gè)實(shí)例來(lái)處理來(lái)了。但是客戶端緩存的槽信息并不是一成不變的,比如在增加實(shí)例的時(shí)候,這時(shí)候會(huì)導(dǎo)致重新分片,那么原來(lái)客戶端緩存的信息就會(huì)不準(zhǔn)確,一般這時(shí)候會(huì)發(fā)生兩個(gè)常見(jiàn)的錯(cuò)誤,嚴(yán)格來(lái)說(shuō)也不是錯(cuò)誤,更像一種信息,一個(gè)叫做MOVED,一個(gè)叫做ASK。moved的意思就說(shuō),原來(lái)是實(shí)例A負(fù)責(zé)的數(shù)據(jù),現(xiàn)在被遷移到了實(shí)例B,MOVED 代表的是遷移完成的,但是 ASK 代表的是正在遷移過(guò)程中,比如原來(lái)是實(shí)例A負(fù)責(zé)的部分?jǐn)?shù)據(jù),現(xiàn)在被遷移到了實(shí)例B,剩下的還在等待遷移中,當(dāng)數(shù)據(jù)遷移完畢之后 ASK 就會(huì)變成 MOVED,然后客戶端收到 MOVED 信息之后就會(huì)再次更新下本地緩存,這樣下次就不會(huì)出現(xiàn)這兩個(gè)錯(cuò)誤了。

    上述內(nèi)容就是redis奇葩數(shù)據(jù)類型與集群知識(shí)有哪些,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI