Redis 數(shù)據(jù)結(jié)構(gòu)與內(nèi)存管理策略(上)

Redis 數(shù)據(jù)類型特點(diǎn)與使用場(chǎng)景

redis?為我們提供了?5?種數(shù)據(jù)類型,基本上我們使用頻率最高的就是?string?,而對(duì)其他四種數(shù)據(jù)類型使用的頻次稍弱于?string?。

一方面是由于?string?使用起來(lái)比較簡(jiǎn)單,可以方便存儲(chǔ)復(fù)雜大對(duì)象,使用場(chǎng)景比較多。還有一個(gè)原因就是由于?redis expire time?只能設(shè)置在?key?上,像?list、hashset、zset?屬于集合類型,會(huì)管理一組?item,我們無(wú)法在這些集合的?item?上設(shè)置過(guò)期時(shí)間,所以使用?expire time?來(lái)處理集合的?cache?失效會(huì)變得稍微復(fù)雜些。但是?string?使用?expire time?來(lái)管理過(guò)期策略會(huì)比較簡(jiǎn)單,因?yàn)樗捻?xiàng)少。這里說(shuō)的集合是寬泛的類似集合。

導(dǎo)致我們習(xí)慣性的使用?string?而忽視其他四種數(shù)據(jù)類型的另一個(gè)深層次原因,大多是由于我們對(duì)另外四種數(shù)據(jù)類型的使用和原理不是太了解。這個(gè)時(shí)候往往會(huì)忽視在特定場(chǎng)景下使用某種數(shù)據(jù)類型可能會(huì)比?string?性能高出很多,比如使用?hash?結(jié)構(gòu)來(lái)提高某個(gè)實(shí)體的某個(gè)項(xiàng)的修改等。

這里我們不打算羅列這?5?種數(shù)據(jù)類型的使用方法,這些資料網(wǎng)上有很多。我們主要討論這?5?種數(shù)據(jù)類型的功能特點(diǎn),這些特點(diǎn)分別適合用于處理哪些現(xiàn)實(shí)的業(yè)務(wù)場(chǎng)景,最重要的是我們?nèi)绾谓M合性的使用這?5?種數(shù)據(jù)類型來(lái)解決復(fù)雜的?cache?問(wèn)題。

String、List、Hash、Set、Zset

String

string?是?redis?提供的字符串類型。可以針對(duì)?string?類型獨(dú)立設(shè)置?expire time?。通常用來(lái)存儲(chǔ)長(zhǎng)字符串?dāng)?shù)據(jù),比如,某個(gè)對(duì)象的?json?字符串。

string?類型我們?cè)谑褂蒙献钋擅畹氖强梢詣?dòng)態(tài)拼接?key。通常我們可以將一組?id?放在?set?里,然后動(dòng)態(tài)查找?string?還是否存在,如果不存在說(shuō)明已經(jīng)過(guò)期或者由于數(shù)據(jù)修改主動(dòng)?delete?了,需要再做一次?cache?數(shù)據(jù)?load?。

雖然?set?無(wú)法設(shè)置?item?的過(guò)期時(shí)間,但是我們可以將?set item?與?string key?關(guān)聯(lián)來(lái)達(dá)到相同的效果。

上圖中的左邊是一個(gè)?key?為?set:order:ids?的?set?集合,它可能是一個(gè)全量集合,也可能是某個(gè)查詢條件獲取出來(lái)的一個(gè)集合。

有時(shí)候復(fù)雜點(diǎn)的場(chǎng)景需要多個(gè)?set?集合來(lái)支撐計(jì)算,在?redis 服務(wù)器?里可能會(huì)有很多類似這樣的集合。

這些集合我們可以稱為?功能數(shù)據(jù),這些數(shù)據(jù)是用來(lái)輔助?cache?計(jì)算的,當(dāng)進(jìn)行各種集合運(yùn)算之后會(huì)得出當(dāng)前查詢需要返回的子集,最后我們才會(huì)去獲取某個(gè)訂單真正的數(shù)據(jù)。

這些?string:order:{orderId}?字符串?key?并不一定是為了服務(wù)一種場(chǎng)景,而是整個(gè)系統(tǒng)最底層的數(shù)據(jù),各種場(chǎng)景最后都需要獲取這些數(shù)據(jù)。那些?set?集合可以認(rèn)為是查詢條件數(shù)據(jù),用來(lái)輔助查詢條件的計(jì)算。

redis?為我們提供了?TYPE?命令來(lái)查看某個(gè)?key?的數(shù)據(jù)類型,如:string?類型:

SETstring:order:100order-100TYPEstring:order:100string

List

list?在提高?throughput?的場(chǎng)景中非常適用,因?yàn)樗赜械?LPUSH、RPUSH、LPOP、RPOP?功能可以無(wú)縫的支持生產(chǎn)者、消費(fèi)者架構(gòu)模式。

這非常適合實(shí)現(xiàn)類似?Java Concurrency?Fork/Join?框架中的?work-stealing 算法 (工作竊取)?。

java fork/join?框架使用并行來(lái)提高性能,但是會(huì)帶來(lái)由于并發(fā)?take task?帶來(lái)的?race condition (競(jìng)態(tài)條件)?問(wèn)題,所以采用?work-stealing 算法?來(lái)解決由于競(jìng)爭(zhēng)問(wèn)題帶來(lái)的性能損耗。

上圖中模擬了一個(gè)典型的支付?callback?峰值場(chǎng)景。在峰值出現(xiàn)的地方一般我們都會(huì)使用加?buffer?的方式來(lái)加快請(qǐng)求處理速度,這樣才能提高并發(fā)處理能力,提高?throughput?。

支付?gateway?收到?callback?之后不做任何處理直接交給?分發(fā)器?。分發(fā)器?是一個(gè)無(wú)狀態(tài)的?cluster?,每個(gè)?node?通過(guò)向?注冊(cè)中心?pull handler queue list,也就是獲取下游處理器注冊(cè)到注冊(cè)中心里的消息通道。

每一個(gè)分發(fā)器?node?會(huì)維護(hù)一個(gè)本地?queue list?,然后順序推送消息到這些?queue list?即可。這里會(huì)有點(diǎn)小問(wèn)題,就是?支付?gateway?調(diào)用分發(fā)器的時(shí)候是如何做?load balance?,如果不是平均負(fù)載可能會(huì)有某個(gè)?queue list?高出其他?queue list?。

而分發(fā)器不需要做?soft load balance?,因?yàn)槟呐履硞€(gè)?queue list?比其他?queue list?多也無(wú)所謂,因?yàn)橄掠?message handler?會(huì)根據(jù)?work-stealing?算法來(lái)竊取其他消費(fèi)慢的?queue list?。

redis list 的?LPUSHRPUSH、LPOP、RPOP?特性確實(shí)可以在很多場(chǎng)景下提高這種橫向擴(kuò)展計(jì)算能力。

Hash

hash?數(shù)據(jù)類型很明顯是基于?hash?算法的,對(duì)于項(xiàng)的查找時(shí)間復(fù)雜度是?O(1)?的,在極端情況下可能出現(xiàn)項(xiàng)?hash?沖突問(wèn)題,redis?內(nèi)部是使用鏈表加?key?判斷來(lái)解決的。具體?redis?內(nèi)部的數(shù)據(jù)結(jié)構(gòu)我們?cè)诤竺嬗薪榻B,這里就不展開(kāi)了。

hash?數(shù)據(jù)類型的特點(diǎn)通??梢杂脕?lái)解決帶有映射關(guān)系,同時(shí)又需要對(duì)某些項(xiàng)進(jìn)行更新或者刪除等操作。如果不是某個(gè)項(xiàng)需要維護(hù),那么一般可以通過(guò)使用?string?來(lái)解決。

如果有需要對(duì)某個(gè)字段進(jìn)行修改,使用?string?很明顯是會(huì)多出很多開(kāi)銷,需要讀取出來(lái)反序列化成對(duì)象然后操作,然后再序列化寫(xiě)回?redis?,這中間可能還有并發(fā)問(wèn)題。

那我們可以使用?redis hash?提供的實(shí)體屬性?hash?存儲(chǔ)特性,我們可以認(rèn)為?hash value?是一個(gè)?hash table?,實(shí)體的每一個(gè)屬性都是通過(guò)?hash?得到屬性的最終數(shù)據(jù)索引。

上圖使用?hash?數(shù)據(jù)類型來(lái)記錄頁(yè)面的?a/b metrics?,左邊的是首頁(yè)?index?的各個(gè)區(qū)域的統(tǒng)計(jì),右邊是營(yíng)銷?marketing?的各個(gè)區(qū)域統(tǒng)計(jì)。

在程序里我們可以很方便的使用?redis?的?atomic?特性對(duì)?hash?某個(gè)項(xiàng)進(jìn)行累加操作。

HMSEThash:mall:page:ab:metrics:indextopbanner10leftbanner5rightbanner8bottombanner20productmore10topshopping8OK

HGETALLhash:mall:page:ab:metrics:index1)"topbanner"2)"10"3)"leftbanner"4)"5"5)"rightbanner"6)"8"7)"bottombanner"8)"20"9)"productmore"10)"10"11)"topshopping"12)"8"

HINCRBYhash:mall:page:ab:metrics:indextopbanner1(integer)11

使用?redis hash increment?進(jìn)行原子增加操作。HINCRBY?命令可以原子增加任何給定的整數(shù),也可以通過(guò)?HINCRBYFLOAT?來(lái)原子增加浮點(diǎn)類型數(shù)據(jù)。

Set

set?集合數(shù)據(jù)類型可以支持集合運(yùn)算,不能存儲(chǔ)重復(fù)數(shù)據(jù)。

set?最大的特點(diǎn)就是集合的計(jì)算能力,inter 交集、union 并集、diff 差集,這些特點(diǎn)可以用來(lái)做高性能的交叉計(jì)算或者剔除數(shù)據(jù)。

set?集合在使用場(chǎng)景上還是比較多和自由的。舉個(gè)簡(jiǎn)單的例子,在應(yīng)用系統(tǒng)中比較常見(jiàn)的就是商品、活動(dòng)類場(chǎng)景。用一個(gè)?set?緩存有效商品集合,再用一個(gè)?set?緩存活動(dòng)商品集合。如果商品出現(xiàn)上下架操作只需要維護(hù)有效商品?set?,每次獲取活動(dòng)商品的時(shí)候需要過(guò)濾下是否有下架商品,如果有就需要從活動(dòng)商品中剔除。

當(dāng)然,下架的時(shí)候可以直接刪除緩存的活動(dòng)商品,但是活動(dòng)是從?marketing?系統(tǒng)中?load?出來(lái)的,就算我將?cache?里的活動(dòng)商品刪除,當(dāng)下次再?gòu)?marketing系統(tǒng)中?load?活動(dòng)商品時(shí)候還是會(huì)有下架商品。當(dāng)然這只是舉例,一個(gè)場(chǎng)景有不同的實(shí)現(xiàn)方法。

上圖中左右兩邊是兩個(gè)不同的集合,左邊是營(yíng)銷域中的可用商品ids集合,右邊是營(yíng)銷域中活動(dòng)商品ids集合,中間計(jì)算出兩個(gè)集合的交集。

SADDset:marketing:product:available:ids100010010001201000130100014010001501000160

SMEMBERSset:marketing:product:available:ids1)"1000100"2)"1000120"3)"1000130"4)"1000140"5)"1000150"6)"1000160"

SADDset:marketing:activity:product:ids100010010001201000130100014010002001000300

SMEMBERSset:marketing:activity:product:ids1)"1000100"2)"1000120"3)"1000130"4)"1000140"5)"1000200"6)"1000300"

SINTERset:marketing:product:available:idsset:marketing:activity:product:ids1)"1000100"2)"1000120"3)"1000130"4)"1000140"

在一些復(fù)雜的場(chǎng)景中,也可以使用?SINTERSTORE?命令將交集計(jì)算后的結(jié)果存儲(chǔ)在一個(gè)目標(biāo)集合中。 這在使用?pipeline?命令管道中特別有用,將?SINTERSTORE?命令包裹在?pipeline?命令串中可以重復(fù)使用計(jì)算出來(lái)的結(jié)果集。

由于?redis?是?Signle-Thread 單線程模型?,基于這個(gè)特性我們就可以使用?redis?提供的?pipeline 管道?來(lái)提交一連串帶有邏輯的命令集合,這些命令在處理期間不會(huì)被其他客戶端的命令干擾。

Zset

zset?排序集合與?set?集合類似,但是?zset?提供了排序的功能。在介紹?set?集合的時(shí)候我們知道?set?集合中的成員是無(wú)序的,zset?填補(bǔ)了集合可以排序的空隙。

zset?最強(qiáng)大的功能就是可以根據(jù)某個(gè)?score 比分值?進(jìn)行排序,這在很多業(yè)務(wù)場(chǎng)景中非常急需。比如,在促銷活動(dòng)里根據(jù)商品的銷售數(shù)量來(lái)排序商品,在旅游景區(qū)里根據(jù)流入人數(shù)來(lái)排序熱門景點(diǎn)等。

基本上人們?cè)谧鋈魏问虑槎夹枰鶕?jù)某些條件進(jìn)行排序。

其實(shí)?zset?在我們應(yīng)用系統(tǒng)中能用到地方到處都是,這里我們舉一個(gè)簡(jiǎn)單的例子,在團(tuán)購(gòu)系統(tǒng)中我們通常需要根據(jù)參團(tuán)人數(shù)來(lái)排序成團(tuán)列表,大家都希望參加那些即將成團(tuán)的團(tuán)。

上圖是一個(gè)根據(jù)團(tuán)購(gòu)code創(chuàng)建的zset,score 分值?就是參團(tuán)人數(shù)累加和。

ZADDzset:marketing:groupon:group:codes5G_PXYJY9QQFA8G_4EXMT6NZJQ20G_W7BMF5QC2P10G_429DHBTGZX8G_KHZGH9U4PP

ZREVRANGEBYSCOREzset:marketing:groupon:group:codes100001)"G_W7BMF5QC2P"2)"G_ZMZ69HJUCB"3)"G_429DHBTGZX"4)"G_KHZGH9U4PP"5)"G_4EXMT6NZJQ"6)"G_PXYJY9QQFA"

ZREVRANGEBYSCOREzset:marketing:groupon:group:codes10000withscores1)"G_W7BMF5QC2P"2)"20"3)"G_ZMZ69HJUCB"4)"10"5)"G_429DHBTGZX"6)"10"7)"G_KHZGH9U4PP"8)"8"9)"G_4EXMT6NZJQ"10)"8"11)"G_PXYJY9QQFA"12)"5"

zset?本身提供了很多方法用來(lái)進(jìn)行集合的排序,如果需要?score?分值可以使用?withscore?字句帶出每一項(xiàng)的分值。

在一些比較特殊的場(chǎng)合可能需要組合排序,可能有多個(gè)?zset?分別用來(lái)對(duì)同一個(gè)實(shí)體在不同維度的排序,按時(shí)間排序、按人數(shù)排序等。這個(gè)時(shí)候就可以組合使用?zset帶來(lái)的便捷性,利用?pipeline?再結(jié)合多個(gè)?zset?最終得出組合排序集合。

案例:滬江團(tuán)購(gòu)系統(tǒng)大促?hot-top?接口?cache?設(shè)計(jì)

我們總結(jié)了?redis?提供的?5?種數(shù)據(jù)類型的各自特點(diǎn)和一般的使用場(chǎng)景。但是我們不僅僅可以分開(kāi)使用這些數(shù)據(jù)類型,我們完全可以綜合使用這些數(shù)據(jù)類型來(lái)完成復(fù)雜的?cache?場(chǎng)景。

下面我們分享一個(gè)使用多個(gè)?zset?、string?來(lái)優(yōu)化?團(tuán)購(gòu)系統(tǒng)?前臺(tái)接口的例子。由于篇幅和時(shí)間限制,這里只介紹跟本次案例相關(guān)的信息。

hot-top?接口是指熱點(diǎn)、排名接口的意思,表示它的瀏覽量、并發(fā)量比較高,一般大促的時(shí)候都會(huì)有幾個(gè)這種性能要求比較高的接口。

我們先來(lái)分析一個(gè)查詢接口所包含的常規(guī)信息。

首先一個(gè)查詢接口肯定是有?query condition 查詢條件?,然后是?sort 排序信息_ 、最后是?page 分頁(yè)信息_ 。這是一般接口所承擔(dān)的基本職責(zé),當(dāng)然,特殊場(chǎng)景下還需要支持?master/slave replication?時(shí)關(guān)于數(shù)據(jù)?session 一致性?的要求,需要提供跟蹤標(biāo)記來(lái)回?master?查詢數(shù)據(jù),這里就不展開(kāi)了。

我們可以抽象出這幾個(gè)維度的信息:

query condition

查詢條件,companyid=100,sellerid=1010101 諸如此類。

sort

排序信息,一般是默認(rèn)一個(gè)列排序,但是在復(fù)雜的場(chǎng)景下會(huì)有可能讓接口使用者定制排序字段,比如一些租戶信息列。

page

分頁(yè)信息,簡(jiǎn)單理解就是數(shù)據(jù)記錄排完序之后的第幾行到第幾行。

由于這里我們純粹用?redis?來(lái)提高?cache?能力,不涉及到有關(guān)于任何搜索的能力,所以這里忽略其他復(fù)雜查詢的情況。其實(shí)我們?cè)趶?fù)雜的地方使用了?elastcsearch?來(lái)提高搜索能力。

上述我們分析總結(jié)出了一個(gè)查詢接口的基本信息,這里還有一個(gè)有關(guān)于高并發(fā)接口的設(shè)計(jì)原則就是將?hot-top?接口和一般?search?接口分離開(kāi),因?yàn)橹挥蟹侄沃拍芊謩e根據(jù)特點(diǎn)選用不同的技術(shù)。如果我們不分職責(zé)將所有的查詢場(chǎng)景封裝在一個(gè)接口里,那么在后面優(yōu)化接口性能的時(shí)候基本就很麻煩了,有些場(chǎng)景是無(wú)法或者很難用?cache?來(lái)解決的,因?yàn)榻涌诶锺詈狭烁鞣N場(chǎng)景邏輯,就算勉強(qiáng)能實(shí)現(xiàn)性能也不會(huì)高。

前面做這些鋪墊是為了能在介紹案例的時(shí)候達(dá)成一個(gè)基本的共識(shí)?,F(xiàn)在我們來(lái)看下這個(gè)團(tuán)購(gòu)系統(tǒng)的?hot-top?接口的具體邏輯。

在大促的時(shí)候需要展現(xiàn)團(tuán)購(gòu)列表,這個(gè)接口的訪問(wèn)量是非常大的,團(tuán)購(gòu)活動(dòng)需要根據(jù)參團(tuán)人數(shù)倒序排序,并且分頁(yè)返回指定數(shù)量的團(tuán)列表。

我們假設(shè)這個(gè)接口名為?getTopGroups(getTopGroupsRequest request)

query condition 查詢條件問(wèn)題

我們來(lái)仔細(xì)分析下,首先不同的查詢條件從?DB?里查詢出來(lái)的數(shù)據(jù)是不一樣的,也就是說(shuō)查詢出來(lái)的團(tuán)列表是不一樣的,可能有?company 公司?、channel 渠道?等過(guò)濾條件。由于一個(gè)團(tuán)購(gòu)活動(dòng)下不會(huì)有太多團(tuán),頂多上百個(gè)是極限了,所以一個(gè)查詢條件出來(lái)的團(tuán)列表也頂多幾十個(gè),而且根據(jù)場(chǎng)景分析熱點(diǎn)查詢條件不會(huì)超過(guò)十個(gè),所以我們選擇將?查詢條件 hash?出一個(gè)?code?來(lái)緩存本次查詢條件的全量團(tuán)列表集合,但是這些結(jié)果集是沒(méi)有任何排序的。

sort 排序問(wèn)題

再看根據(jù)參團(tuán)人數(shù)排序問(wèn)題,我們立刻就可以想到使用?zset?來(lái)處理團(tuán)排序問(wèn)題,因?yàn)橹挥幸粋€(gè)排序維度,所以一個(gè)?zset?就夠了。我們使用一個(gè) __zset__來(lái)緩存所有團(tuán)的參團(tuán)人數(shù)集合,它是一個(gè)全量的團(tuán)排序集合。

那么我們?nèi)绾螌⒂脩舻牟樵儣l件出來(lái)的團(tuán)列表根據(jù)參團(tuán)人數(shù)排序尼,剛好可以使用?zset?的交集運(yùn)算直接計(jì)算出當(dāng)前這個(gè)集合的?zset?子集。

page 分頁(yè)問(wèn)題

通過(guò)對(duì)已經(jīng)排序之后的團(tuán)列表?zset?使用?zrange?來(lái)獲取出分頁(yè)集合。

我們來(lái)看下完整的流程,如何處理查詢、排序、分頁(yè)的。

上圖從?query condition?計(jì)算?hash code?,然后通過(guò)?DB?查詢出當(dāng)前條件全量團(tuán)列表。

zset:marketing:groupon:hottop:available:group key?表示全量團(tuán)的參團(tuán)人數(shù),用一個(gè)?zset?來(lái)緩存。接著將這兩個(gè)?zset?計(jì)算交集,就可以得出當(dāng)前查詢所需要的帶有參團(tuán)人數(shù)的?zset?,最后在使用?zrevrange?獲取分頁(yè)區(qū)間。

ZADDzset:marketing:groupon:hottop:condition:29860800G4ZD5732YZQ0G5VW3YF42UC0GF773FEJ7CC0GFW8DUEND8S0GKPKKW8XEY90GL324DGWMZM(integer)6

ZADDzset:marketing:groupon:hottop:available:group5GN7KQH36ZWK10GS7VB22AWD415GF773FEJ7CC17G5VW3YF42UC18G4ZD5732YZQ32GTYJKCEJBRR40GKPKKW8XEY945GL324DGWMZM50GFW8DUEND8S60GYTKY4ACWLT(integer)10

ZINTERSTOREzset:marketing:groupon:hottop:condition:interstore2zset:marketing:groupon:hottop:condition:2986080zset:marketing:groupon:hottop:available:group(integer)6

ZRANGEzset:marketing:groupon:hottop:condition:interstore0-1withscores1)"GF773FEJ7CC"2)"15"3)"G5VW3YF42UC"4)"17"5)"G4ZD5732YZQ"6)"18"7)"GKPKKW8XEY9"8)"40"9)"GL324DGWMZM"10)"45"11)"GFW8DUEND8S"12)"50"

ZREVRANGEzset:marketing:groupon:hottop:condition:interstore24withscores1)"GKPKKW8XEY9"2)"40"3)"G4ZD5732YZQ"4)"18"5)"G5VW3YF42UC"6)"17"

有了返回的團(tuán)?code?集合之后就可以通過(guò)?mget?來(lái)批量獲取?string?類型的團(tuán)詳情信息,這里就不貼出代碼了。

由于篇幅和時(shí)間關(guān)系,這里就不展開(kāi)太多的業(yè)務(wù)場(chǎng)景介紹了。這其中還涉及到計(jì)算?cache?過(guò)期時(shí)間的問(wèn)題,這也跟促銷活動(dòng)的運(yùn)營(yíng)規(guī)則有關(guān)系,還涉及到有可能?query condition hash?沖突問(wèn)題等,但是這些已經(jīng)不與我們本節(jié)主題相關(guān)。

Redis 內(nèi)存數(shù)據(jù)結(jié)構(gòu)與編碼

我們已經(jīng)了解了?redis?提供的?5?種數(shù)據(jù)類型,那么?redis?內(nèi)部到底是如何支持這?5?種數(shù)據(jù)類型的,也就是說(shuō)?redis?到底是使用什么樣的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)、查找我們?cè)O(shè)置在內(nèi)存中的數(shù)據(jù)。

雖然我們使用?5?種數(shù)據(jù)類型來(lái)緩存數(shù)據(jù),但是?redis?會(huì)根據(jù)我們存儲(chǔ)數(shù)據(jù)的不同而選用不同的數(shù)據(jù)結(jié)構(gòu)和編碼。

我們?nèi)粘J褂玫氖?redis?提供的?5?種數(shù)據(jù)類型,但是這?5?種數(shù)據(jù)類型在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)和編碼有很多種。隨著我們存儲(chǔ)的數(shù)據(jù)類型的不同、數(shù)據(jù)量的大小不同都會(huì)引起內(nèi)存數(shù)據(jù)結(jié)構(gòu)的動(dòng)態(tài)調(diào)整。

本節(jié)只是做數(shù)據(jù)結(jié)構(gòu)和編碼的一般性介紹,不做過(guò)多細(xì)節(jié)討論,一方面是關(guān)于?redis?源碼分析的資料網(wǎng)上有很多,還有一個(gè)原因就是?redis?每一個(gè)版本的實(shí)現(xiàn)有很大差異,一旦展開(kāi)細(xì)節(jié)討論每一個(gè)點(diǎn)每一個(gè)數(shù)據(jù)結(jié)構(gòu)都會(huì)很復(fù)雜,所以我們這里就不展開(kāi)討論這些,只是起到拋磚引玉作用。

OBJECT?encoding key、DEBUG OBJECT?key

我們知道使用?type?命令可以查看某個(gè)?key?是否是?5?種數(shù)據(jù)類型之一,但是當(dāng)我們想查看某個(gè)?key?底層是使用哪種數(shù)據(jù)結(jié)構(gòu)和編碼來(lái)存儲(chǔ)的時(shí)候可以使用?OBJECT encoding?命令。

SETstring:orderid:1010101010101010OK

OBJECT encodingstring:orderid:10101010"int"

SETstring:orderid:10101010"orderid:10101010"OK

OBJECT encodingstring:orderid:10101010"embstr"

同樣一個(gè)?key?,但是由于我們?cè)O(shè)置的值不同而?redis?選用了不同的內(nèi)存數(shù)據(jù)結(jié)構(gòu)和編碼。雖然?redis?提供的?string?數(shù)據(jù)類型,但是?redis?會(huì)自動(dòng)識(shí)別我們?cache?的數(shù)據(jù)類型是?int?還是?string?。

如果我們?cè)O(shè)置的是字符串,且這個(gè)字符串長(zhǎng)度不大于?39?字節(jié)那么將使用?embstr?來(lái)編碼,如果大于?39?字節(jié)將使用?raw?來(lái)編碼。redis 4.0?將這個(gè)閥值擴(kuò)大了?45?個(gè)字節(jié)。

除了使用?OBJECT encoding?命令外,我們還可以使用?DEBUG OBJECT?命令來(lái)查看更多詳細(xì)信息。

DEBUG OBJECTstring:orderid:10101010Valueat:0x7fd190500210refcount:1encoding:intserializedlength:5lru:6468044lru_seconds_idle:8

DEBUGOBJECTstring:orderid:10101010Valueat:0x7fd19043be60refcount:1encoding:embstrserializedlength:17lru:6465804lru_seconds_idle:1942

DEBUG OBJECT?能看到這個(gè)對(duì)象的?refcount 引用計(jì)數(shù)?、serializedlength 長(zhǎng)度?、lru_seconds_idle 時(shí)間?,這些信息決定了這個(gè)?key?緩存清除策略。

簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string)

簡(jiǎn)單動(dòng)態(tài)字符串簡(jiǎn)稱?SDS?,在?redis?中所有涉及到字符串的地方都是使用?SDS?實(shí)現(xiàn),當(dāng)然這里不包括字面量。?SDS?與傳統(tǒng)?C?字符串的區(qū)別就是?SDS?是結(jié)構(gòu)化的,它可以高效的處理分配、回收、長(zhǎng)度計(jì)算等問(wèn)題。

structsdshdr{unsignedintlen;unsignedintfree;charbuf[];};

這是?redis 3.0?版本的?sds.h?頭文件定義,3.0.0?之后變化比較大。len?表示字符串長(zhǎng)度,free?表示空間長(zhǎng)度,buf?數(shù)組表示字符串。

SDS?有很多優(yōu)點(diǎn),比如,獲取長(zhǎng)度的時(shí)間復(fù)雜度?O(1)?,不需要遍歷所有?char buf[]?組數(shù),直接返回?len?值。

staticinlinesize_t sdslen(constsds s) {structsdshdr *sh = (void*)(s-(sizeof(structsdshdr)));returnsh->len;}

當(dāng)然還有空間分配檢查、空間預(yù)分配、空間惰性釋放等,這些都是?SDS?結(jié)構(gòu)化字符串帶來(lái)的強(qiáng)大的擴(kuò)展能力。

鏈表(linked list)

鏈表數(shù)據(jù)結(jié)構(gòu)我們是比較熟悉的,最大的特點(diǎn)就是節(jié)點(diǎn)的增、刪非常靈活。redis List?數(shù)據(jù)類型底層就是基于鏈表來(lái)實(shí)現(xiàn)。這是?redis 3.0?實(shí)現(xiàn)。

typedefstructlist{listNode *head;? ? listNode *tail;void*(*dup)(void*ptr);void(*free)(void*ptr);int(*match)(void*ptr,void*key);unsignedlonglen;}list;

typedefstructlistNode{structlistNode*prev;structlistNode*next;void*value;} listNode;

在?redis 3.2.0?版本的時(shí)候引入了?quicklist?鏈表結(jié)構(gòu),結(jié)合了?linkedlist?和?ziplist?的優(yōu)勢(shì)。

typedefstructquicklist{quicklistNode *head;? ? quicklistNode *tail;unsignedlongcount;/* total count of all entries in all ziplists */unsignedintlen;/* number of quicklistNodes */intfill :16;/* fill factor for individual nodes */unsignedintcompress :16;/* depth of end nodes not to compress;0=off */} quicklist;

typedefstructquicklistNode{structquicklistNode*prev;structquicklistNode*next;unsignedchar*zl;unsignedintsz;/* ziplist size in bytes */unsignedintcount :16;/* count of items in ziplist */unsignedintencoding :2;/* RAW==1 or LZF==2 */unsignedintcontainer :2;/* NONE==1 or ZIPLIST==2 */unsignedintrecompress :1;/* was this node previous compressed? */unsignedintattempted_compress :1;/* node can't compress; too small */unsignedintextra :10;/* more bits to steal for future usage */} quicklistNode;

quicklist?提供了靈活性同時(shí)也兼顧了?ziplist?的壓縮能力,quicklist->encoding?指定了兩種壓縮算法。?quicklist->compress?表示我們可以進(jìn)行?quicklist node?的深度壓縮能力。redis 提供了兩個(gè)有關(guān)于壓縮的配置。

list-max-ziplist-size:ziplist長(zhǎng)度控制

list-compress-depth:控制鏈表兩端節(jié)點(diǎn)的壓縮個(gè)數(shù),越是靠近兩端的節(jié)點(diǎn)被訪問(wèn)的機(jī)率越大,所以可以將訪問(wèn)機(jī)率大的節(jié)點(diǎn)不壓縮,其他節(jié)點(diǎn)進(jìn)行壓縮

對(duì)比?redis 3.2?的?quicklist?與?redis 3.0?,很明顯?quicklist?提供了更加豐富的壓縮功能。redis 3.0?的版本是每個(gè)?listnode?直接緩存值,而?quicklistnode?還有強(qiáng)大的有關(guān)于壓縮能力。

LPUSHlist:products:mall100200300(integer)3

OBJECT encodinglist:products:mall"quicklist"

歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開(kāi)發(fā): 854393687

群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來(lái)學(xué)習(xí)提升自己,不要再用"沒(méi)有時(shí)間“來(lái)掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來(lái)的自己一個(gè)交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來(lái)自《Redis開(kāi)發(fā)與運(yùn)維》一書(shū)第八章,如轉(zhuǎn)載請(qǐng)聲明。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 19,060評(píng)論 2 29
  • 參考來(lái)源 Redis的內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常寶貴的資源。對(duì)于如何優(yōu)化內(nèi)存使用一直...
    秦漢郵俠閱讀 1,369評(píng)論 0 2
  • 轉(zhuǎn)自 hzlzh Best-App: 1. 付費(fèi)軟件篇 (Mac OS) Mac OS平臺(tái)下有眾多優(yōu)秀的軟件工具。...
    organnn閱讀 17,343評(píng)論 1 47
  • 一首歌 酗酒的好處就是完全不知道昨晚發(fā)生了什么,但醒來(lái)是痛苦的,頭疼,胃也難受。特別是頂著它們?nèi)ド习喔且患纯嗟?..
    七月江南閱讀 380評(píng)論 0 4
  • 一年了,宋離還在怡樂(lè)縣。 怡樂(lè)縣這個(gè)地方是我編出來(lái)的,我不知道世界上是否真的有這個(gè)地方。 我總是先寫(xiě)一句話,再否定...
    一粟小閱讀 422評(píng)論 0 1

友情鏈接更多精彩內(nèi)容