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)“喧賓奪主”的意思羊娃。
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)程拭,不做記錄定踱。
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é)省內(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。
在統(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 中。
回到剛剛的問(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é)
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
但問(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)緯度信息
問(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é)果:
最后組合兩次編碼的 GeoHash 編碼悬荣,得到1110011101表示該經(jīng)緯度的權(quán)重分值,規(guī)則如下:
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 編碼值基本也是接近的羡疗。
所以,使用 Sorted Set 范圍查詢得到的相近編碼值别洪,在實(shí)際的地理空間上叨恨,也是相鄰的方格,這就可以實(shí)現(xiàn) LBS 應(yīng)用“搜索附近的人或物”的功能了挖垛。
當(dāng)然也有反例:相同條件分別四分區(qū)痒钝,得到16方格:
所以秉颗,為了避免查詢不準(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ù)類型支持的命令操作。