自我系統(tǒng)學(xué)習(xí)Redis小記-05

11 | “萬(wàn)金油”的String摔癣,為什么不好用了?

1、String 類型的內(nèi)存空間消耗問(wèn)題梁丘,以及選擇節(jié)省內(nèi)存開(kāi)銷的數(shù)據(jù)類型的解決方案侵浸。

String 類型并不是適用于所有場(chǎng)合的,它有一個(gè)明顯的短板氛谜,就是它保存數(shù)據(jù)時(shí)所消耗的內(nèi)存空間較多掏觉。

內(nèi)存大-->生成rdb響應(yīng)滿-->redis響應(yīng)慢

2、為什么 String 類型內(nèi)存開(kāi)銷大值漫?

除了記錄實(shí)際數(shù)據(jù)离陶,String 類型還需要額外的內(nèi)存空間記錄數(shù)據(jù)長(zhǎng)度、空間使用等信息妄田,這些信息也叫作元數(shù)據(jù)哩陕。當(dāng)實(shí)際保存的數(shù)據(jù)較小時(shí),元數(shù)據(jù)的空間開(kāi)銷就顯得比較大了危虱,有點(diǎn)“喧賓奪主”的意思羊娃。

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

buf:字節(jié)數(shù)組,保存實(shí)際數(shù)據(jù)埃跷。為了表示字節(jié)數(shù)組的結(jié)束蕊玷,Redis 會(huì)自動(dòng)在數(shù)組最后加一個(gè)“\0”,這就會(huì)額外占用 1 個(gè)字節(jié)的開(kāi)銷弥雹。

len:占 4 個(gè)字節(jié)垃帅,表示 buf 的已用長(zhǎng)度。

alloc:也占個(gè) 4 字節(jié)剪勿,表示 buf 的實(shí)際分配長(zhǎng)度贸诚,一般大于 len。

在 SDS 中窗宦,buf 保存實(shí)際數(shù)據(jù)赦颇,而 len 和 alloc 本身其實(shí)是 SDS 結(jié)構(gòu)體的額外開(kāi)銷。

除此之外赴涵,還有一些其他的媒怯,比如RedisObject 記錄一些元數(shù)據(jù)(最后訪問(wèn)時(shí)間、被引用次數(shù))髓窜、以及?dictEntry(全局hash表的指針三個(gè)等扇苞,占用24字節(jié),分別是指向 key寄纵、value 以及下一個(gè) dictEntry)等等鳖敷、這里只需要知道String除了實(shí)際數(shù)據(jù)還存了其他東西即可,認(rèn)為不是重點(diǎn)程拭,不做記錄定踱。

redisObject
sds
dictEntry結(jié)構(gòu)體

3、用什么數(shù)據(jù)結(jié)構(gòu)可以節(jié)省內(nèi)存恃鞋?

壓縮列表(ziplist)崖媚,一種非常節(jié)省內(nèi)存的結(jié)構(gòu)

表頭有三個(gè)字段 zlbytes亦歉、zltail 和 zllen,分別表示列表長(zhǎng)度畅哑、列表尾的偏移量肴楷,以及列表中的 entry 個(gè)數(shù)。壓縮列表尾還有一個(gè) zlend荠呐,表示列表結(jié)束赛蔫。

壓縮列表結(jié)構(gòu)

壓縮列表之所以能節(jié)省內(nèi)存,就在于它是用一系列連續(xù)的 entry 保存數(shù)據(jù)泥张。

這些 entry 會(huì)挨個(gè)兒放置在內(nèi)存中呵恢,不需要再用額外的指針進(jìn)行連接,這樣就可以節(jié)省指針?biāo)加玫目臻g圾结。

每個(gè) entry 的元數(shù)據(jù)包括下面幾部分:

1)瑰剃、prev_len,表示前一個(gè) entry 的長(zhǎng)度筝野。prev_len 有兩種取值情況:1 字節(jié)或 5 字節(jié)。取值 1 字節(jié)時(shí)粤剧,表示上一個(gè) entry 的長(zhǎng)度小于 254 字節(jié)歇竟。雖然 1 字節(jié)的值能表示的數(shù)值范圍是 0 到 255,但是壓縮列表中 zlend 的取值默認(rèn)是 255抵恋,因此焕议,就默認(rèn)用 255表示整個(gè)壓縮列表的結(jié)束,其他表示長(zhǎng)度的地方就不能再用 255 這個(gè)值了弧关。所以盅安,當(dāng)上一個(gè) entry 長(zhǎng)度小于 254 字節(jié)時(shí),prev_len 取值為 1 字節(jié)世囊,否則别瞭,就取值為 5 字節(jié)。

2)株憾、len:表示自身長(zhǎng)度蝙寨,4 字節(jié);

3)嗤瞎、encoding:表示編碼方式墙歪,1 字節(jié);

4)贝奇、content:保存實(shí)際數(shù)據(jù)虹菲。

Redis 基于壓縮列表實(shí)現(xiàn)了 List、Hash 和 Sorted Set 這樣的集合類型掉瞳,最大好處是節(jié)省了?dictEntry?的開(kāi)銷毕源。String是一個(gè)健值對(duì)對(duì)應(yīng)一個(gè)dictEntry髓帽,集合是一個(gè)key對(duì)應(yīng)多個(gè)dictEntry?這樣就節(jié)省了內(nèi)存。

4脑豹、如何用集合類型保存單值的鍵值對(duì)郑藏?

在保存單值的鍵值對(duì)時(shí),可以采用基于 Hash 類型的二級(jí)編碼方法瘩欺。這里說(shuō)的二級(jí)編碼必盖,就是把一個(gè)單值的數(shù)據(jù)拆分成兩部分,前一部分作為 Hash 集合的 key俱饿,后一部分作為Hash 集合的 value歌粥,這樣一來(lái),我們就可以把單值數(shù)據(jù)保存到 Hash 集合中了拍埠。

以圖片 ID 1101000060 和圖片存儲(chǔ)對(duì)象 ID 3302000080 為例失驶,我們可以把圖片 ID 的前7 位(1101000)作為 Hash 類型的鍵,把圖片 ID 的最后 3 位(060)和圖片存儲(chǔ)對(duì)象ID 分別作為 Hash 類型值中的 key 和 value枣购。

使用String占用64字節(jié)嬉探,但是使用二級(jí)編碼只占用16字節(jié)。

注意棉圈,這里用前7位做Hash類型的key涩堤,二級(jí)編碼方法中采用的 ID 長(zhǎng)度是有講究的。

Hash類型底層使用 壓縮列表哈希表分瘾。注意什么時(shí)候用什么類型胎围!

Hash類型設(shè)置了用壓縮列表保存數(shù)據(jù)時(shí)的兩個(gè)閾值,一旦超過(guò)閾值Hash 類型就會(huì)用哈希表來(lái)保存數(shù)據(jù)了德召。

兩個(gè)閾值分別對(duì)應(yīng)兩個(gè)配置項(xiàng):

1)白魂、hash-max-ziplist-entries:表示用壓縮列表保存時(shí)哈希集合中的最大元素個(gè)數(shù)。

2)上岗、hash-max-ziplist-value:表示用壓縮列表保存時(shí)哈希集合中單個(gè)元素的最大長(zhǎng)度福荸。

如果我們往 Hash 集合中寫(xiě)入的元素個(gè)數(shù)超過(guò)了 hash-max-ziplist-entries,或者寫(xiě)入的單個(gè)元素大小超過(guò)了 hash-max-ziplist-value液茎,Redis 就會(huì)自動(dòng)把 Hash 類型的實(shí)現(xiàn)結(jié)構(gòu)由壓縮列表轉(zhuǎn)為哈希表逞姿。

一旦從壓縮列表轉(zhuǎn)為了哈希表,Hash 類型就會(huì)一直用哈希表進(jìn)行保存捆等,而不會(huì)再轉(zhuǎn)回壓縮列表了滞造。在節(jié)省內(nèi)存空間方面,哈希表就沒(méi)有壓縮列表那么高效了栋烤。

為了能充分使用壓縮列表的精簡(jiǎn)內(nèi)存布局谒养,我們一般要控制保存在 Hash 集合中的元素個(gè)數(shù)。所以,在剛才的二級(jí)編碼中买窟,我們只用圖片 ID 最后 3 位作為 Hash 集合的 key丰泊,也就保證了 Hash 集合的元素個(gè)數(shù)不超過(guò) 1000,同時(shí)始绍,我們把 hash-max-ziplist-entries 設(shè)置為 1000瞳购,這樣一來(lái),Hash 集合就可以一直使用壓縮列表來(lái)節(jié)省內(nèi)存空間了亏推。

5学赛、小結(jié)

打破了對(duì) String 的認(rèn)知誤區(qū),以前吞杭,我們認(rèn)為 String 是“萬(wàn)金油”盏浇,、

什么場(chǎng)合都適用芽狗,但是绢掰,在保存的鍵值對(duì)本身占用的內(nèi)存空間不大時(shí),

String 類型的元數(shù)據(jù)開(kāi)銷就占據(jù)主導(dǎo)了童擎,這里面包括了RedisObject 結(jié)構(gòu)滴劲、SDS 結(jié)構(gòu)、dictEntry 結(jié)構(gòu)的內(nèi)存開(kāi)銷柔昼。

針對(duì)這種情況哑芹,我們可以使用壓縮列表保存數(shù)據(jù)。當(dāng)然捕透,使用 Hash 這種集合類型保存單值鍵值對(duì)的數(shù)據(jù)時(shí),我們需要將單值數(shù)據(jù)拆分成兩部分碴萧,分別作為 Hash 集合的鍵和值乙嘀,就像剛才案例中用二級(jí)編碼來(lái)表示圖片 ID。


12 | 有一億個(gè)keys要統(tǒng)計(jì)破喻,應(yīng)該用哪種集合虎谢?

1、前言

對(duì)以下集合進(jìn)行統(tǒng)計(jì)引出問(wèn)題(訪問(wèn)量巨大曹质,百萬(wàn)婴噩、千萬(wàn)):

1)、在移動(dòng)應(yīng)用中羽德,需要統(tǒng)計(jì)每天的新增用戶數(shù)和第二天的留存用戶數(shù)几莽;

2)、在電商網(wǎng)站的商品評(píng)論中宅静,需要統(tǒng)計(jì)評(píng)論列表中的最新評(píng)論章蚣;

3)、在簽到打卡中姨夹,需要統(tǒng)計(jì)一個(gè)月內(nèi)連續(xù)打卡的用戶數(shù)纤垂;

4)矾策、在網(wǎng)頁(yè)訪問(wèn)記錄中,需要統(tǒng)計(jì)獨(dú)立訪客(Unique Visitor峭沦,UV)量贾虽。

要想選擇合適的集合,我們就得了解常用的集合統(tǒng)計(jì)模式吼鱼。

集合類型常見(jiàn)的四種統(tǒng)計(jì)模式蓬豁,包括聚合統(tǒng)計(jì)、排序統(tǒng)計(jì)蛉抓、二值狀態(tài)統(tǒng)計(jì)和基數(shù)統(tǒng)計(jì)庆尘。

2、聚合統(tǒng)計(jì)

是指統(tǒng)計(jì)多個(gè)集合元素的聚合結(jié)果巷送。包括:交集統(tǒng)計(jì)驶忌、差集統(tǒng)計(jì)、并集統(tǒng)計(jì)笑跛。

統(tǒng)計(jì)手機(jī) App 每天的新增用戶數(shù)和第二天的留存用戶數(shù)付魔,正好對(duì)應(yīng)了聚合統(tǒng)計(jì)。

完成這個(gè)統(tǒng)計(jì)任務(wù)需要記錄兩個(gè)集合飞蹂,第一個(gè)集合:記錄所有登陸過(guò)app的用戶ID几苍;第二個(gè)集合:記錄每一天登陸過(guò)app的用戶ID。

記錄所有登陸過(guò)app的用戶ID
記錄每一天登陸過(guò)app的用戶ID

在統(tǒng)計(jì)每天新增用戶時(shí)陈哑,只用計(jì)算這兩個(gè)集合的差集即可妻坝。

Set 的差集、并集和交集的計(jì)算復(fù)雜度較高惊窖,在數(shù)據(jù)量較大的情況下刽宪,如果直接執(zhí)行這些計(jì)算,會(huì)導(dǎo)致 Redis 實(shí)例阻塞界酒。所以圣拄,建議:可以從主從集群中選擇一個(gè)從庫(kù),讓它專門(mén)負(fù)責(zé)聚合計(jì)算毁欣,或者是把數(shù)據(jù)讀取到客戶端庇谆,在客戶端來(lái)完成聚合統(tǒng)計(jì),這樣就可以規(guī)避阻塞主庫(kù)實(shí)例和其他從庫(kù)實(shí)例的風(fēng)險(xiǎn)了凭疮。

3饭耳、排序統(tǒng)計(jì)

要求集合類型能對(duì)元素保序,集合中的元素可以按序排列哭尝,這種對(duì)元素保序的集合類型叫作有序集合

List 是按照元素進(jìn)入 List 的順序進(jìn)行排序的哥攘,而 Sorted Set 可以根據(jù)元素的權(quán)重來(lái)排序

場(chǎng)景:最新評(píng)論列表包含了所有評(píng)論中的最新留言

List會(huì)導(dǎo)致分頁(yè)時(shí)候出現(xiàn)問(wèn)題,如果恰好新插入一個(gè)數(shù)據(jù),會(huì)導(dǎo)致讀到舊元素逝淹。

Sorted Set不存在這種情況耕姊,因?yàn)樗歉鶕?jù)元素的實(shí)際權(quán)重來(lái)排序和獲取數(shù)據(jù)的。

//權(quán)重N栅葡,最新的權(quán)重越大茉兰,我們執(zhí)行下面的命令時(shí),就可以獲得最新的 10 條評(píng)論

ZRANGEBYSCORE comments N-9 N

所以欣簇,面對(duì)需要展示最新列表规脸、排行榜等場(chǎng)景時(shí),如果數(shù)據(jù)更新頻繁或者需要分頁(yè)熊咽,可以使用Sorted Set莫鸭。

4、二值狀態(tài)統(tǒng)計(jì)

二值狀態(tài)就是指集合元素的取值就只有 0 和 1 兩種横殴。

場(chǎng)景:簽到被因,1-已經(jīng)簽到,0-未簽到

在簽到統(tǒng)計(jì)時(shí)衫仑,每個(gè)用戶一天的簽到用 1 個(gè) bit 位就能表示梨与,一個(gè)月31bit,可以選不復(fù)雜的結(jié)構(gòu)文狱。

這時(shí)粥鞋,我們就可以選擇 Bitmap

Bitmap是 Redis 提供的擴(kuò)展數(shù)據(jù)類型瞄崇。

Bitmap介紹:

Bitmap 本身是用 String 類型作為底層數(shù)據(jù)結(jié)構(gòu)呻粹,實(shí)現(xiàn)的一種統(tǒng)計(jì)二值狀態(tài)的數(shù)據(jù)類型。

String 類型是會(huì)保存為二進(jìn)制的字節(jié)數(shù)組苏研,所以 Redis 把字節(jié)數(shù)組的每個(gè) bit 位利用起來(lái)尚猿,用來(lái)表示一個(gè)元素的二值狀態(tài)。Bitmap就相當(dāng)于一個(gè) bit 數(shù)組楣富。

Bitmap 提供了?GETBIT/SETBIT 操作,使用了一個(gè)偏移量 offset 對(duì) bit 數(shù)組的某一個(gè) bit 位進(jìn)行讀和寫(xiě)伴榔。

具體命令:SETBIT KEY_NAME OFFSET 1

Bitmap 的偏移量是從 0 開(kāi)始算的纹蝴,也就是說(shuō) offset的最小值是 0。

當(dāng)使用?SETBIT 對(duì)一個(gè) bit 位進(jìn)行寫(xiě)操作時(shí)踪少,這個(gè)bit位會(huì)被設(shè)置為1塘安。

Bitmap 還提供了 BITCOUNT 操作,用來(lái)統(tǒng)計(jì)這個(gè) bit 數(shù)組中所有 “1” 的個(gè)數(shù)援奢。

Bitmap 支持用 BITOP 命令對(duì)多個(gè) Bitmap 按位做“與”“或”“異或”的操作兼犯,操作的結(jié)果會(huì)保存到一個(gè)新的 Bitmap 中。

BITCOUNT 統(tǒng)計(jì)操作
BITOP 命令按位與操作

回到剛剛的問(wèn)題,在統(tǒng)計(jì) 1 億個(gè)用戶連續(xù) 10 天的簽到情況時(shí)切黔,你可以把每天的日期作為key砸脊,每個(gè) key 對(duì)應(yīng)一個(gè) 1 億位的 Bitmap,每一個(gè) bit 對(duì)應(yīng)一個(gè)用戶當(dāng)天的簽到情況纬霞。接下來(lái)凌埂,我們對(duì) 10 個(gè) Bitmap 做“與”操作,得到的結(jié)果也是一個(gè) Bitmap诗芜。在這個(gè)Bitmap 中瞳抓,只有 10 天都簽到的用戶對(duì)應(yīng)的 bit 位上的值才會(huì)是 1。最后伏恐,我們可以用 BITCOUNT 統(tǒng)計(jì)下 Bitmap 中的 1 的個(gè)數(shù)孩哑,這就是連續(xù)簽到 10 天的用戶總數(shù)了。

可以計(jì)算一下記錄了 10 天簽到情況后的內(nèi)存開(kāi)銷翠桦。每天使用 1 個(gè) 1 億位的 Bitmap横蜒,大約占 12MB 的內(nèi)存(10^8/8/1024/1024),10 天的 Bitmap 的內(nèi)存開(kāi)銷約為 120MB秤掌,內(nèi)存壓力不算太大愁铺。不過(guò),在實(shí)際應(yīng)用時(shí)闻鉴,最好對(duì) Bitmap 設(shè)置過(guò)期時(shí)間茵乱,讓Redis 自動(dòng)刪除不再需要的簽到記錄,以節(jié)省內(nèi)存開(kāi)銷孟岛。

所以瓶竭,如果只需要統(tǒng)計(jì)數(shù)據(jù)的二值狀態(tài),例如商品有沒(méi)有渠羞、用戶在不在等斤贰,就可以使用Bitmap,因?yàn)樗?b>只用一個(gè) bit 位就能表示 0 或 1次询。在記錄海量數(shù)據(jù)時(shí)荧恍,Bitmap 能夠有效地節(jié)省內(nèi)存空間。

5屯吊、基數(shù)統(tǒng)計(jì)

統(tǒng)計(jì)場(chǎng)景:基數(shù)統(tǒng)計(jì)送巡。基數(shù)統(tǒng)計(jì)就是指統(tǒng)計(jì)一個(gè)集合中不重復(fù)的元素個(gè)數(shù)

對(duì)應(yīng)到我們剛才介紹的場(chǎng)景中盒卸,就是統(tǒng)計(jì)網(wǎng)頁(yè)的 UV骗爆。

網(wǎng)頁(yè) UV 的統(tǒng)計(jì)有個(gè)獨(dú)特的地方,就是需要去重蔽介,一個(gè)用戶一天內(nèi)的多次訪問(wèn)只能算作一次摘投。

解決:

1)煮寡、首先想到 Redis 里面的 Set 自動(dòng)去重,后續(xù)使用 SCARD 命令返回個(gè)數(shù)犀呼。

缺點(diǎn)是數(shù)據(jù)量大的時(shí)候非常耗內(nèi)存幸撕,(某一page頁(yè)非常火爆)圆凰。

SADD page1:uv user1

2)杈帐、采用 Hash 類型記錄UV,后續(xù)使用 HLEN 命令統(tǒng)計(jì) Hash 集合中的所有元素個(gè)數(shù)

即使一個(gè)用戶多次訪問(wèn)专钉,這個(gè)值還是1挑童,缺點(diǎn)和 Set 類似,頁(yè)面很多跃须,數(shù)據(jù)量大時(shí)耗內(nèi)存站叼。

HSET page1:uv user1 1

3)、Redis 提供的 HyperLogLog

是一種用于統(tǒng)計(jì)基數(shù)的數(shù)據(jù)集合類型菇民,最大優(yōu)勢(shì)在于尽楔,當(dāng)集合元素?cái)?shù)量非常多時(shí),它計(jì)算基數(shù)所需的空間總是固定的第练,而且還很小阔馋。

在 Redis 中,每個(gè) HyperLogLog 只需要花費(fèi) 12 KB 內(nèi)存娇掏,就可以計(jì)算接近 2^64 個(gè)元素的基數(shù)

HyperLogLog 和 hash呕寝、set 比較就很節(jié)省內(nèi)存空間。

在 UV 統(tǒng)計(jì)這個(gè)場(chǎng)景婴梧,可以用 PFADD 命令把訪問(wèn)頁(yè)面的每個(gè)用戶都添加到 HyperLogLog 中

#用于向 HyperLogLog 中添加新元素

PFADD page1:uv user1 user2 user3 user4 user5

#返回 HyperLogLog 的統(tǒng)計(jì)結(jié)果

PFCOUNT page1:uv

注意一下:HyperLogLog 是基數(shù)概率統(tǒng)計(jì)下梢,有誤差,標(biāo)準(zhǔn)誤算率是0.81%塞蹭,需要精確統(tǒng)計(jì)就要用 hash 和 set孽江。

6、小結(jié)

各個(gè)數(shù)據(jù)結(jié)構(gòu)支持情況和優(yōu)缺點(diǎn)

1)番电、Set 和 Sorted Set 都支持多種聚合統(tǒng)計(jì)岗屏,不過(guò),對(duì)于差集計(jì)算來(lái)說(shuō)漱办,只有 Set支持担汤。Bitmap 也能做多個(gè) Bitmap 間的聚合計(jì)算,包括與洼冻、或和異或操作。

2)隅很、當(dāng)需要進(jìn)行排序統(tǒng)計(jì)時(shí)撞牢,List 中的元素雖然有序率碾,但是一旦有新元素插入,原來(lái)的元素在List 中的位置就會(huì)移動(dòng)屋彪,那么所宰,按位置讀取的排序結(jié)果可能就不準(zhǔn)確了。而 Sorted Set 本身是按照集合元素的權(quán)重排序畜挥,可以準(zhǔn)確地按序獲取結(jié)果仔粥,所以建議你優(yōu)先使用它。

3)蟹但、如果我們記錄的數(shù)據(jù)只有 0 和 1 兩個(gè)值的狀態(tài)躯泰,Bitmap 會(huì)是一個(gè)很好的選擇,這主要?dú)w功于 Bitmap 對(duì)于一個(gè)數(shù)據(jù)只用 1 個(gè) bit 記錄华糖,可以節(jié)省內(nèi)存麦向。

4)、對(duì)于基數(shù)統(tǒng)計(jì)來(lái)說(shuō)客叉,如果集合元素量達(dá)到億級(jí)別而且不需要精確統(tǒng)計(jì)時(shí)诵竭,我建議你使用HyperLogLog。


13 | GEO是什么兼搏?還可以定義新的數(shù)據(jù)類型嗎卵慰?

1、前言

Redis 五大基本類型:String佛呻、List裳朋、Hash、Set 和Sorted Set件相。

在海量數(shù)據(jù)統(tǒng)計(jì)方面再扭,基本類型內(nèi)存開(kāi)銷很大,而且一些場(chǎng)景還不滿足夜矗。

Redis提供三種拓展數(shù)據(jù)類型:Bitmap泛范、HyperLogLog 和 GEO

2紊撕、面向 LBS 應(yīng)用的 GEO 數(shù)據(jù)類型

基于位置信息服務(wù)(Location-Based Service罢荡,LBS)的應(yīng)用。

LBS 應(yīng)用訪問(wèn)的數(shù)據(jù)是人或物關(guān)聯(lián)的一組經(jīng)緯度信息对扶,而且要能查詢相鄰的經(jīng)緯度范圍区赵。

LBS 應(yīng)用中經(jīng)緯度的存取特點(diǎn):

1、? ?每一輛網(wǎng)約車都有一個(gè)編號(hào)(例如 33)浪南,網(wǎng)約車需要將自己的經(jīng)度信息(例如116.034579)和緯度信息(例如 39.000452 )發(fā)給叫車應(yīng)用笼才。?

2、用戶在叫車的時(shí)候络凿,叫車應(yīng)用會(huì)根據(jù)用戶的經(jīng)緯度位置(例如經(jīng)度 116.054579骡送,緯度39.030452)昂羡,查找用戶的附近車輛,并進(jìn)行匹配摔踱。

3虐先、等把位置相近的用戶和車輛匹配上以后,叫車應(yīng)用就會(huì)根據(jù)車輛的編號(hào)派敷,獲取車輛的信息蛹批,并返回給用戶。

一輛車(或一個(gè)用戶)對(duì)應(yīng)一組經(jīng)緯度篮愉,并且隨著車(或用戶)的位置移動(dòng)腐芍,相應(yīng)的經(jīng)緯度也會(huì)變化。

3潜支、GEO 的底層結(jié)構(gòu)

1)甸赃、當(dāng)有很多車或者用戶,需要用一個(gè)集合來(lái)保存冗酿,首先考慮Hash

hash存儲(chǔ)位置信息

但問(wèn)題是埠对,對(duì)于一個(gè) LBS 應(yīng)用來(lái)說(shuō),除了記錄經(jīng)緯度信息裁替,還需要根據(jù)用戶的經(jīng)緯度信息在車輛的 Hash 集合中進(jìn)行范圍查詢项玛。一旦涉及到范圍查詢,就意味著集合中的元素需要有序弱判,但 Hash 類型的元素是無(wú)序的襟沮,顯然不能滿足我們的要求

2)昌腰、Sorted Set 類型开伏,正是 GEO 底層數(shù)據(jù)結(jié)構(gòu)

key 就是 Sorted Set 中的元素,而 value 則是元素的權(quán)重分?jǐn)?shù)

更重要的是遭商,Sorted Set 可以根據(jù)元素的權(quán)重分?jǐn)?shù)排序固灵,支持范圍查詢。這就能滿足 LBS 服務(wù)中查找相鄰位置的需求了

用 Sorted Set 來(lái)保存車輛的經(jīng)緯度信息時(shí)劫流,Sorted Set 的元素是車輛 ID巫玻,元素的權(quán)重分?jǐn)?shù)是經(jīng)緯度信息

sort set存儲(chǔ)經(jīng)緯度信息

問(wèn)題:Sorted Set 元素的權(quán)重分?jǐn)?shù)是一個(gè)浮點(diǎn)數(shù)(float 類型),而一組經(jīng)緯度包含的是經(jīng)度和緯度兩個(gè)值祠汇,是沒(méi)法直接保存為一個(gè)浮點(diǎn)數(shù)的仍秤,那具體該怎么保存?

解決:GeoHash編碼

4可很、GeoHash編碼方法----“二分區(qū)間诗力,區(qū)間編碼”

為了高效經(jīng)緯度比較,業(yè)界最廣泛使用的就是GeoHash編碼我抠。

經(jīng)緯度單獨(dú)編碼過(guò)程:

經(jīng)度范圍是[-180,180]緯度的范圍是[-90姜骡,90])导坟。GeoHash 編碼會(huì)把一個(gè)經(jīng)度值編碼成一個(gè) N 位的二進(jìn)制值,我們來(lái)對(duì)經(jīng)度范圍[-180,180]做 N 次的二分區(qū)操作圈澈,其中 N可以自定義。二分后尘惧,值落在左區(qū)間取0康栈,右區(qū)間取1。

對(duì)精度116.37喷橙,緯度39.86啥么,進(jìn)行GeoHash編碼,N取5贰逾,得到如下結(jié)果:

經(jīng)度5次二分
緯度5次二分

最后組合兩次編碼的 GeoHash 編碼悬荣,得到1110011101表示該經(jīng)緯度的權(quán)重分值,規(guī)則如下:

經(jīng)疙剑、緯度組合GeoHash編碼

5氯迂、GeoHash 分區(qū)

使用 GeoHash 編碼后,我們相當(dāng)于把整個(gè)地理空間劃分成了一個(gè)個(gè)方格言缤,每個(gè)方格對(duì)應(yīng)了 GeoHash 中的一個(gè)分區(qū)嚼蚀。

[180,180]、[90,90]的經(jīng)緯度做兩次分區(qū)

分區(qū)一:[-180,0) 和[-90,0)管挟,編碼 00轿曙;

分區(qū)二:[-180,0) 和[0,90],編碼 01僻孝;

分區(qū)三:[0,180]和[-90,0)导帝,編碼 10;

分區(qū)四:[0,180]和[0,90]穿铆,編碼 11您单。

每個(gè)方格覆蓋了一定范圍內(nèi)的經(jīng)緯度值,分區(qū)越多悴务,每個(gè)方格能覆蓋到的地理空間就越小睹限,也就越精準(zhǔn)。我們把所有方格的編碼值映射到一維空間時(shí)讯檐,相鄰方格的 GeoHash 編碼值基本也是接近的羡疗。

經(jīng)緯度分別 二次分區(qū) 得到四塊

所以,使用 Sorted Set 范圍查詢得到的相近編碼值别洪,在實(shí)際的地理空間上叨恨,也是相鄰的方格,這就可以實(shí)現(xiàn) LBS 應(yīng)用“搜索附近的人或物”的功能了挖垛。

當(dāng)然也有反例:相同條件分別四分區(qū)痒钝,得到16方格:

經(jīng)緯度分別 四次分區(qū) 得到十六塊

所以秉颗,為了避免查詢不準(zhǔn)確問(wèn)題,我們可以同時(shí)查詢給定經(jīng)緯度所在的方格周圍的 4 個(gè)或8 個(gè)方格送矩。

6蚕甥、如何操作 GEO 類型?

兩個(gè)命令:分別是 GEOADD 和 GEORADIUS栋荸。

GEOADD 命令:用于把一組經(jīng)緯度信息和相對(duì)應(yīng)的一個(gè) ID 記錄到 GEO 類型集合中菇怀;

GEORADIUS 命令:會(huì)根據(jù)輸入的經(jīng)緯度位置,查找以這個(gè)經(jīng)緯度為中心的一定范圍內(nèi)的其他元素晌块。當(dāng)然爱沟,我們可以自己定義這個(gè)范圍。

# 車輛id為33匆背,經(jīng)緯度為116.034579 39.030452呼伸,加入到GEO集合中,前面為key

GEOADD cars:locations 116.034579 39.030452 33

#根據(jù)輸入的用戶的經(jīng)緯度信息(116.054579钝尸,39.030452 )括享,查找以這個(gè)經(jīng)緯度為中心的 5 公里內(nèi)的車輛信息,5 這個(gè)參數(shù)可以自己控制

#asc 表示距離中心點(diǎn)最近的車輛從近到遠(yuǎn)排序

#count 表示返回十輛車蝶怔,5km內(nèi)可能有很多車奶浦,如果都返回占用數(shù)據(jù)帶寬

GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10

7、小結(jié)

1)踢星、Redis 的擴(kuò)展數(shù)據(jù)類型 GEO澳叉。GEO 屬于 Redis 提供的擴(kuò)展數(shù)據(jù)類型。GEO 可以記錄經(jīng)緯度形式的地理位置信息沐悦,被廣泛地應(yīng)用在 LBS 服務(wù)中成洗。GEO 本身并沒(méi)有設(shè)計(jì)新的底層數(shù)據(jù)結(jié)構(gòu),而是直接使用了 Sorted Set 集合類型藏否。

2)瓶殃、GEO 類型使用 GeoHash 編碼方法實(shí)現(xiàn)了經(jīng)緯度到 Sorted Set 中元素權(quán)重分?jǐn)?shù)的轉(zhuǎn)換,這其中的兩個(gè)關(guān)鍵機(jī)制就是對(duì)二維地圖做區(qū)間劃分副签,以及對(duì)區(qū)間進(jìn)行編碼遥椿。一組經(jīng)緯度落在某個(gè)區(qū)間后,就用區(qū)間的編碼值來(lái)表示淆储,并把編碼值作為 Sorted Set 元素的權(quán)重分?jǐn)?shù)冠场。

也就是說(shuō) GEO = Sorted Set + GeoHash

3)、把經(jīng)緯度保存到 Sorted Set 中本砰,利用 Sorted Set 提供的“按權(quán)重進(jìn)行有序范圍查找”的特性碴裙,實(shí)現(xiàn) LBS 服務(wù)中頻繁使用的“搜索附近”的需求。

*4)、(了解一下)擴(kuò)展數(shù)據(jù)類型有兩種實(shí)現(xiàn)途徑:一種是基于現(xiàn)有的數(shù)據(jù)類型舔株,通過(guò)數(shù)據(jù)編碼或是實(shí)現(xiàn)新的操作的方式莺琳,來(lái)實(shí)現(xiàn)擴(kuò)展數(shù)據(jù)類型,例如基于Sorted Set 和 GeoHash 編碼實(shí)現(xiàn) GEO载慈,以及基于 String 和位操作實(shí)現(xiàn) Bitmap惭等;另一種就是開(kāi)發(fā)自定義的數(shù)據(jù)類型,具體的操作是增加新數(shù)據(jù)類型的定義办铡,實(shí)現(xiàn)創(chuàng)建和釋放函數(shù)咕缎,實(shí)現(xiàn)新數(shù)據(jù)類型支持的命令操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末料扰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子焙蹭,更是在濱河造成了極大的恐慌晒杈,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孔厉,死亡現(xiàn)場(chǎng)離奇詭異拯钻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)撰豺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)粪般,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人污桦,你說(shuō)我怎么就攤上這事亩歹。” “怎么了凡橱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵小作,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我稼钩,道長(zhǎng)顾稀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任坝撑,我火速辦了婚禮静秆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巡李。我一直安慰自己抚笔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布击儡。 她就那樣靜靜地躺著塔沃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛀柴,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天螃概,我揣著相機(jī)與錄音,去河邊找鬼鸽疾。 笑死吊洼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的制肮。 我是一名探鬼主播冒窍,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼豺鼻!你這毒婦竟也來(lái)了综液?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤儒飒,失蹤者是張志新(化名)和其女友劉穎谬莹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桩了,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡附帽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了井誉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕉扮。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖颗圣,靈堂內(nèi)的尸體忽然破棺而出喳钟,到底是詐尸還是另有隱情,我是刑警寧澤欠啤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布荚藻,位于F島的核電站,受9級(jí)特大地震影響洁段,放射性物質(zhì)發(fā)生泄漏应狱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一祠丝、第九天 我趴在偏房一處隱蔽的房頂上張望疾呻。 院中可真熱鬧,春花似錦写半、人聲如沸岸蜗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)璃岳。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铃慷,已是汗流浹背单芜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犁柜,地道東北人洲鸠。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像馋缅,于是被迫代替她去往敵國(guó)和親扒腕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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