Redis(2)九大資料型別及落地案例
九大資料型別
redis命令不區分大小寫,但key是區分大小寫的
Tips help @型別名詞 可以獲得該型別的所有操作命令
string
string型別是二進位制安全的,redis的string可以包含任何資料,如影象、序列化物件。一個鍵最多能儲存512MB。
- 二進位制安全是指,在傳輸資料的時候,能保證二進位制資料的資訊保安,也就是不會被篡改、破譯;如果被攻擊,能夠及時檢測出來
命令 | 含義 |
---|---|
set key value | 命令不區分大小寫,但是key區分大小寫 |
setnx key value | 當key不存在時設定key的值。(SET if Not eXists),分散式鎖的問題 |
setex key_name time key_value | 建立一個key,並且設定他的過期時間 |
psetex | 建立並設定一個key存活的有效期(毫秒) |
get key_name | |
getrange key start end | 獲取key中字串的子字串,從start開始,end結束 |
setrange key offset value | 設定從offset往後的值(覆蓋) |
mget key1 [key2 …] | 獲取多個key |
mset | 設定多個key |
msetnx | 僅當要設定的多個key都不存在時候才被設定值 否則均不會被新增 |
getset key_name value: | 返回key的舊值,並設定key的值。當key不存在,返回nil |
strlen key | 返回key所儲存的字串的長度 |
append key_name value | 字串拼接,追加至末尾,如果不存在,為其賦值 |
incr key_name | INCR命令key中儲存的值+1,如果不存在key,則key中的值話先被初始化為0再加1 |
incrby key_name step | 按步長增量 |
incrbyfloat | |
decr key_name | key中的值自減1 |
decrby key_name step | 按步長減 |
應用場景
-
String通常用於儲存單個字串或JSON字串資料
-
因為String是二進位制安全的,所以可以把保密要求高的圖片檔案內容作為字串來儲存
-
計數器:常規Key-Value快取應用,如微博數、粉絲數。INCR本身就具有原子性特性,所以不會有執行緒安全問題
-
介面被呼叫次數
-
分散式鎖
- 比如抖音無限點贊某個視訊或者商品,點一下加一次
中小廠可用此方法,高QPS不可用
-
閱讀數
只要點選了rest地址,直接可以使用incr key 命令增加一個數字1,完成記錄數字。
hash
Map<String,Map<Object,Object>>
Redis hash是一個string型別的field和value的對映表,hash特別適用於儲存物件。每個hash可以儲存232 - 1(40億左右)鍵值對。可以看成KEY和VALUE的MAP容器。相比於JSON,hash佔用很少的記憶體空間。
命令 | 含義 |
---|---|
hset key_name field value | 為指定的key設定field和value |
hmset key field value[field1,value1] | 為指定的key一次性設定多個field和其對應的value |
hsetnx | 當不存在才建立該field |
hget key field | 獲取指定key指定欄位的值 |
hmget key field[field1] | 返回指定key的指定多個欄位 |
hgetall key | 返回hash表中所有欄位和值 |
hkeys key | 獲取hash表指定key的所有欄位 |
hvals key | 獲取hash表制定key的所有欄位對應的值 |
hlen key | 獲取hash表中的欄位數量 |
hdel key field [field1] | 刪除一個或多個hash表的欄位 |
hexists | 在key裡面是否存在指定的field |
hincrby key field step | 增加某個field的值 |
應用場景
Hash的應用場景,通常用來儲存一個使用者資訊的物件資料。
-
相比於儲存物件的string型別的json串,json串修改單個屬性需要將整個值取出來。而hash不需要。
-
相比於多個key-value儲存物件,hash節省了很多記憶體空間
-
如果hash的屬性值被刪除完,那麼hash的key也會被redis刪除
-
JD購物車早期 設計目前不再採用,當前小中廠可用
行為 命令 新增商品 hset shopcar:uid1024 334488 1 新增商品 hset shopcar:uid1024 334477 1 增加商品數量 hincrby shopcar:uid1024 334477 1 商品總數 hlen shopcar:uid1024 全部選擇 hgetall shopcar:uid1024
list
類似於Java中的LinkedList。一個雙端連結串列的結構, 容量是232個元素,大概40多億,主要功能有push/pop等,一般用在棧、佇列、訊息佇列等場景。
它是一個字串連結串列,left、right都可以插入新增;
如果鍵不存在,建立新的連結串列;
如果鍵已存在,新增內容;
如果值全移除,對應的鍵也就消失了。
連結串列的操作無論是頭和尾效率都極高,但假如是對中間元素進行操作,效率就很慘淡了。
命令 | 作用 |
---|---|
lpush key value1 [value2] | 從左側插入,右邊的先出,相當於一個棧 lpush list 1 2 3 lrange list 0 -1 輸出:3 2 1 |
rpush key value1 [value2]: | 從右側插入,左邊的先出 rpush list 1 2 3 lrange list 0 -1 輸出:1 2 3 |
lpop key | 返回並從左側移除第一個元素 |
rpop key | 返回並從右側移除第一個元素 |
llen key | 獲取列表長度 |
lindex key index | 獲取列表指定索引的元素,從零開始 |
lrange key start stop | 獲取列表指定範圍的元素 |
lrem key num value | 從索引0往後直至刪除指定個數的同一元素 |
lrem list 2 3 刪掉了集合中的兩個3 | |
ltrim key start stop | 對列表進行修改,讓列表只保留指定區間的元素,不在指定區間的元素就會被刪除 list1中元素1 2 3 4 5 ltrim list1 2 3 list1剩餘元素:3 4 |
lset key index value | 修改指定索引的值(索引必須存在) |
linsert key before|after value value | 在列表元素前或則後插入元素 |
lpushx key value | 從左側插入值,如果list不存在,則不操作 |
rpushx key value | 從右側插入值,如果list不存在,則不操作 |
blpop key [key1] timeout | 移除並獲取列表第一個元素,如果列表沒有元素會阻塞列表到等待超時或發現可彈出元素為止 |
brpop key [key1] timeout | 移除並獲取列表最後一個元素,如果列表沒有元素會阻塞列表到等待超時或發現可彈出元素為止 |
rpoplpush list1 list2 | 移除list1最後一個元素,並將該元素新增到list2並返回此元素 用此命令可以實現訂單下單流程、使用者系統登入註冊簡訊等。 |
應用場景
-
對資料大的集合資料刪減
列表顯示、關注列表、粉絲列表、留言評價…分頁、熱點新聞等 -
任務佇列
list通常用來實現一個訊息佇列,而且可以確保先後順序,不必像MySQL那樣通過order by來排序補充:
rpoplpush list1 list2 此命令可以實現訂單下單流程、使用者系統登入註冊簡訊等。
-
公眾號訂閱的訊息
- 大V作者李永樂老師和CSDN釋出了文章分別是 11 和 22
- MMMM關注了他們兩個,只要他們釋出了新文章,就會安裝進我的List
- lpush likearticle:MMMM 11 22
- 檢視MMMM自己的號訂閱的全部文章,類似分頁,下面0~10就是一次顯示10條
- lrange likearticle:MMMM 0 9
-
商品評論列表
需求1:使用者針對某一商品釋出評論,一個商品會被不同的使用者進行評論,儲存商品評論時,要按時間順序排序
需要2:使用者在前端頁面查詢該商品的評論,需要按照時間順序降序排序
使用list儲存商品評論資訊,key是該商品的id,value是商品評論資訊商品編號為1001的商品評論key【items:comment:1001】
lpush items:comment:1001 {"id":1001,"name":"huawei","date":1600484283054,"content":"lasjfdljsa;fdlkajsd;lfjsa;ljf;lasjf;lasjfdlsad"}
Set
唯一、無序
命令 | 含義 |
---|---|
sadd key value1[value2] | 向集合新增成員 |
scard key | 返回集合成員數量 |
smembers key | 返回集合中所有成員 |
sismember key member | 判斷memeber元素是否是集合key成員的成員 |
srandmember key [count] | 返回集合中count個隨機數 |
srem key member1 [member2] | 移除集合中一個或多個成員 |
spop key | 移除並返回集合中的一個隨機元素 |
smove source destination member: | 將member元素從source集合移動到destination集合 |
sdiff key1 [key2]: | 返回給定的第一個集合和其他集合的差集(即在key1中的值而在其他key中找不到) |
sdiffstore destination key1[key2]: | 返回給定的第一個集合與其他的集合的差集並存儲在destination中 set1:1 2 3 set2:3 4 5 6 sdiffstore set3 set1 set2 smembers set3 result:1 2 |
sinter key1 [key2]: | 返回所有集合的交集 |
sunion key1 [key2]: | 返回所有集合的並集 |
應用場景
-
對兩個集合間的資料[計算]進行交集、並集、差集運算
-
- 以非常方便的實現如共同關注、共同喜好、二度好友等功能。對上面的所有集合操作,你還可以使用不同的命令選擇將結果返回給客戶端還是儲存到一個新的集合中。
- 利用唯一性,可以統計訪問網站的所有獨立IP
-
微信抽獎小程式
節點 | 命令 |
---|---|
1 使用者ID,立即參與按鈕 | sadd key 使用者ID |
2 顯示已經有多少人蔘與了,上圖23208人蔘加 | SCARD key |
3 抽獎(從set中任意選取N箇中獎人) | SRANDMEMBER key 2 隨機抽獎2個人,元素不刪除 SPOP key 3 隨機抽獎3個人,元素會刪除 |
-
微信朋友圈點贊
行為 | 命令 |
---|---|
1 新增點贊 | sadd pub:msgID 點贊使用者ID1 點贊使用者ID2 |
2 取消點贊 | srem pub:msgID 點贊使用者ID |
3 展現所有點贊過的使用者 | SMEMBERS pub:msgID |
4 點贊使用者數統計,就是常見的點贊紅色數字 | scard pub:msgID |
5 判斷某個朋友是否對樓主點贊過 | SISMEMBER pub:msgID 使用者ID |
-
微博好友關注社交關係
-
共同關注的人 取交集
sadd s1 1 2 3 4 5 6
sadd s2 3 5 9 11
sinter s1 s2--------------------->3 5
-
我關注的人(s1,s2)也關注他)(3)(大家愛好相同)
sismember s1 3
sismember s2 3
-
QQ內推可能認識的人
sadd s1 1 2 3 4 5
sadd s2 3 4 5 6 7
sinter s1 s2 ——————————3 4 5
sdiff s1 s2 -------------------1 2
sdiff s2 s1-------------------6 7
-
zset
有序且不重複。每個元素都會關聯一個double型別的分數,Redis通過分數進行從小到大的排序。分數可以重複
命令 | 作用 |
---|---|
zadd key score1 member1 score2 member2 | 向有序集合中加入一個元素和該元素的分數 |
zcard key : | 獲取集合中的元素數量 |
zcount key min max | 計算在有序集合中指定區間分數的成員數 |
zrange key start stop 【withscores】 | 指定輸出索引範圍內的成員,輸出結果按照分數大小排序 |
zrangebyscore key min max | 指定輸出score區間內的成員 |
zrank key member: | 返回有序集合指定成員的索引 |
zrevrange key start stop : | 返回有序集中指定區間內的成員,通過索引,分數從高到低 |
zincrby key score member | 給某一個元素加分 |
zrem key member [member …] | 移除有序集合中的一個或多個成員 |
zremrangebyrank key start stop | 移除有序集合中給定的索引區間的所有成員(第一名是0)(低到高排序) |
zremrangebyscore key min max | 移除有序集合中給定的分數區間的所有成員 |
zscore | 顯示某一元素的分數 |
應用場景
-
常用於排行榜
- 如推特可以以發表時間作為score來儲存
- 儲存成績
- 還可以用zset來做帶權重的佇列,讓重要的任務先執行
-
根據商品銷售對商品進行排序顯示
定義商品銷售排行榜(sorted set集合),key為goods:sellsort,分數為商品銷售數量。
行為 命令 商品編號1001的銷量是9,商品編號1002的銷量是15 zadd goods:sellsort 9 1001 15 1002 有一個客戶又買了2件商品1001,商品編號1001銷量加2 zincrby goods:sellsort 2 1001 求商品銷量前10名 ZRANGE goods:sellsort 0 10 withscores -
抖音熱搜
行為 命令 1 點選視訊 ZINCRBY hotvcr:20200919 1 八佰
ZINCRBY hotvcr:20200919 15 八佰 2 花木蘭2 展示當日排行前10條 ZREVRANGE hotvcr:20200919 0 9 withscore
新三大資料型別出現的時代背景
新需求
手機App中的每天的⽤⼾登入資訊:1天對應1系列⽤⼾ID或移動裝置ID;
電商⽹站上商品的⽤⼾評論列表:1個商品對應了1系列的評論;
⽤⼾在⼿機App上的簽到打卡資訊:1天對應1系列⽤⼾的簽到記錄;
應⽤⽹站上的⽹⻚訪問資訊:1個⽹⻚對應1系列的訪問點選。
記錄對集合中的資料進行統計
在移動應用中,需要統計每天的新增使用者數和第2天的留存使用者數;
在電商網站的商品評論中,需要統計評論列表中的最新評論;
在簽到打卡中,需要統計一個月內連續打卡的使用者數;
在網頁訪問記錄中,需要統計獨立訪客(UniqueVisitor,UV)量。
痛點:
類似今日頭條、抖音、淘寶這樣的額使用者訪問級別都是億級的,請問如何處理?
換句話說,以上的需求的痛點
億級資料的收集+統計,要求存的進,取得快,多統計
而真正有價值的是統計
case:滴滴打車 司機id:經緯度 由於車在動,需要五秒鐘更新一次車的經緯度位置,高頻度更新資料來源,MySQL顯然無法勝任
億級系統常見的四種統計
大資料下使用者畫像的獲取進而推進廣告+直播賣貨
聚合統計
統計多個集合元素的聚合結果,就是前面講解過的交差並等集合統計
交併差集和聚合函式的應用
排序統計
-
抖音視訊最新評論留言的場景,請你設計一個展現列表。
考察你的資料結構和設計思路
-
list
每個商品評價對應一個List集合,這個List包含了對這個商品的所有評論,而且會按照評論時間儲存這些評論,每來一個新評論就用LPUSH命令把它插入List的隊頭。但是,如果在演示第二頁前,又產生了一個新評論, 第2頁的評論不一樣了。原因:
List是通過元素在List中的位置來排序的,當有一個新元素插入時,原先的元素在List中的位置都後移了一位, 原來在第1位的元素現在排在了第2位,當LRANGE讀取時,就會讀到舊元素。
-
zset
在⾯對需要展示最新列表、排行榜等場景時,如果資料更新頻繁或者需要分頁顯示,建議使⽤ZSet
-
二值統計
集合元素的取值就只有0和1兩種。
在釘釘上班簽到打卡的場景中,我們只用記錄有簽到(1)或沒簽到(0)——————————————bitmap應運而生
基數統計
指統計⼀個集合中不重複的元素個數————————————————————hyperloglog應運而生
bitmap
——本質是string型別(點陣圖)
Bit arrays(or simply bitmaps,我們可以稱之為點陣圖),由0和1狀態表現的二進位制位的bit陣列
一個位元組(1 Byte)=8位,上圖中每一個小格子就是一個個的bit,只能放1或0,用來判斷Y/N狀態
用於狀態統計,Y,N類似AtomicBoolean
命令 | 作用 | 時間複雜度 |
---|---|---|
setbit key offset val | 給指定key的值的第offset賦值val | O(1) |
getbit key offset | 獲取指定key的第offset位 | O(1) |
bitcount key start end | 返回指定key中【start,end】中為1的數量 | O(n) |
bitop operation destkey key | 對不同的二進位制儲存資料進行位運算(AND,OR,NOT,XOR) | O(n) |
bitmap的偏移量從零開始算的
-
按年去儲存一個使用者的簽到情況,365 天只需要 365 / 8 ≈ 46 Byte,1000W 使用者量一年也只需要 44 MB 就足夠了。
-
假如是億級的系統, 每天使用1個1億位的Bitmap約佔12MB的記憶體 (10^8/8/1024/1024),10天的Bitmap的記憶體開銷約為120MB,記憶體壓力不算太高。在實際使用時,最好對Bitmap設定過期時間,讓Redis自動刪除不再需要的簽到記錄以節省記憶體開銷。
本質是string型別,實質上是二進位制的ASCII編碼對應
兩個setbit命令對k1進行設定後,對應的二進位制串就是0100 0001,二進位制串就是0100 0001對應的10進位制就是65
擴容機制
不是字串長度而是佔據幾個位元組,超過8位後自己按照8位一組一byte再擴容
一年365天,全年天天登陸佔用多少位元組
bitop
連續2天都簽到的使用者
假如某個網站或者系統,它的使用者有1000W,做個使用者id和位置的對映
比如0號位對應使用者id:uid-092iok-lkj
比如1號位對應使用者id:uid-7388c-xxx
應用場景
需求:
-
使用者是否登陸過,Y,N比如京東每日簽到送京豆
-
電影、廣告是否被點贊播放過
-
釘釘打卡上班,簽到統計
-
vcr使用者是否點選:setbit vcr:01 [userid] 1
案例
日活統計,連續簽到打卡,最近一週的活躍使用者,統計指定使用者一年之中的登入天數,某使用者按照一年365天,哪幾天登陸過?哪幾天沒有登陸過?全年中登入的天數共計多少?
京東簽到領京東
使用mysql明顯難以落地實現
簽到使用者量較小時這麼設計能行,但京東這個體量的使用者(估算3000W簽到使用者,一天一條資料,一個月就是9億資料) 對於京東這樣的體量,如果一條簽到記錄對應著當日用記錄,那會很恐怖...... 如何解決這個痛點?
1 一條簽到記錄對應一條記錄,會佔據越來越大的空間。
2 一個月最多31天,剛好我們的int型別是32位,那這樣一個int型別就可以搞定一個月,32位大於31天,當天來了位是1沒來就是0。
3 一條資料直接儲存一個月的簽到記錄,不再是儲存一天的簽到記錄。
在簽到統計時,每個使用者一天的簽到用1個bit位就能表示,
一個月(假設是31天)的簽到情況用31個bit位就可以,一年的簽到也只需要用365個bit位,根本不用太複雜的集合型別
HyperLogLog(統計)
名詞介紹
-
UV—Unique View 獨立訪客,一般理解為客戶IP 需要去重
-
PV—Page View 頁面瀏覽量,不用去重(Bitmap)
-
DAU—Daily Active User 日活躍使用者量,登入或者使用了某個產品的使用者數(去重複登入的使用者),常用於反應網站、網際網路應用或者網路遊戲的運營情況
-
MAU—Month Active User 月活躍使用者量
產生背景需求
-
統計某個網站的UV,統計某個文章的UV
-
使用者搜尋網站關鍵詞的數量
-
統計使用者每天搜尋不同詞條個數
去重複統計功能的基數[1]估計演算法就是HyperLogLog,Redis在2.8.9版本中添加了HyperLoglog結構。它是用來做基數統計的演算法,Hyperloglog的優點是在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的,並且是很小的。
在redis裡面,每個Hyperloglog鍵只需要花費12KB記憶體[2]就可以計算接近264個不同元素的基數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。
但是,因為HyperLogLog只會根據輸入元素來計算基數,而不會存輸入元素本身,所以HyperLogLog不能像集合那樣,返回輸入的各個元素。
對於去重,我們可能首先會想到的是
-
hashset,redis的set,hash
但是在實際使用上對於億級的統計,HashSet由於Hash衝突以及佔用的記憶體大小,肯定是不能採用的,redis的set同理,佔用體積過大。redis——hash = <keyDay,<ip,1>> 按照ipv4的結構來說明,每個ipv4的地址最多是15個位元組(ip = "192.168.111.1",最多xxx.xxx.xxx.xxx) ,某一天的1.5億 * 15個位元組= 2G,一個月60G,redis死定了
-
bitmap,將ip與偏移量一一對應,似乎可以,但是
如果資料顯較大億級統計,使用bitmaps同樣會有這個問題。
bitmap是通過用位bit陣列來表示各元素是否出現,每個元素對應一位,所需的總記憶體為N個bit。
基數計數則將每一個元素對應到bit陣列中的其中一位,比如bit陣列010010101(按照從零開始下標,有的就是1、4、6、8)。
新進入的元素只需要將已經有的bit陣列和新加入的元素進行按位或計算就行。這個方式能大大減少記憶體佔用且位操作迅速。But,假設一個樣本案例就是一億個基數位值資料,一個樣本就是一億,如果要統計1億個資料的基數位值,大約需要記憶體100000000/8/1024/1024約等於12M,記憶體減少佔用的效果顯著。
這樣得到統計一個物件樣本的基數值需要12M。
如果統計10000個物件樣本(1w個億級),就需要117.1875G將近120G,可見使用bitmaps還是不適用大資料量下(億級)的基數計數場景,
但是bitmaps方法是精確計算的。
-
結論
樣本元素越多記憶體消耗急劇增大,難以管控+各種慢,對於億級統計不太合適,大資料害死人,於是HyperLogLog登場,通過犧牲準確率來換取空間,對於不要求絕對準確率的場景下可以使用,因為概率演算法不直接儲存資料本身,通過一定的概率統計方法預估基數值,同時保證誤差在一定範圍內,由於又不儲存資料故此可以大大節約記憶體。HyperLogLog就是一種概率演算法的實現。
原理
只是進行不重複的基數統計,不是集合也不儲存資料,只記錄數量而不是具體內容。有誤差,非精確統計,犧牲準確率來換取空間,誤差僅僅只是0.81%左右 Redis new data structure: the HyperLogLog -
為什麼redis叢集的最大槽數是16384個?
Redis叢集並沒有使用一致性hash而是引入了雜湊槽的概念。 Redis 叢集有16384個雜湊槽 ,每個key通過CRC16校驗後對16384取模來決定放置哪個槽,叢集的每個節點負責一部分hash槽。但為什麼雜湊槽的數量是16384(214)個呢?
CRC16演算法產生的hash值有16bit,該演算法可以產生216=65536個值。
換句話說值是分佈在0~65535之間。那作者在做mod運算的時候,為什麼不mod65536,而選擇mod16384?
why redis-cluster use 16384 slots? · Issue #2576 · redis/redis (github.com)
正常的心跳資料包帶有節點的完整配置,可以用冪等方式用舊的節點替換舊節點,以便更新舊的配置。
這意味著它們包含原始節點的插槽配置,該節點使用2k的空間和16k的插槽,但是會使用8k的空間(使用65k的插槽)。
同時,由於其他設計折衷,Redis叢集不太可能擴充套件到1000個以上的主節點。
因此16k處於正確的範圍內,以確保每個主機具有足夠的插槽,最多可容納1000個矩陣,但數量足夠少,可以輕鬆地將插槽配置作為原始點陣圖傳播。請注意,在小型群集中,點陣圖將難以壓縮,因為當N較小時,點陣圖將設定的slot / N位佔設定位的很大百分比。
-
如果槽位為65536,傳送心跳資訊的訊息頭達8k,傳送的心跳包過於龐大。
在訊息頭中最佔空間的是myslots[CLUSTER_SLOTS/8]。 當槽位為65536時,這塊的大小是: 65536÷8÷1024=8kb 因為每秒鐘,redis節點需要傳送一定數量的ping訊息作為心跳包,如果槽位為65536,這個ping訊息的訊息頭太大了,浪費頻寬。
-
redis的叢集主節點數量基本不可能超過1000個。
叢集節點越多,心跳包的訊息體內攜帶的資料越多。如果節點過1000個,也會導致網路擁堵。因此redis作者不建議redis cluster節點數量超過1000個。 那麼,對於節點數在1000以內的redis cluster叢集,16384個槽位夠用了。沒有必要拓展到65536個。
-
槽位越小,節點少的情況下,壓縮比高,容易傳輸
Redis主節點的配置資訊中它所負責的雜湊槽是通過一張bitmap的形式來儲存的,在傳輸過程中會對bitmap進行壓縮,但是如果bitmap的填充率slots / N很高的話(N表示節點數),bitmap的壓縮率就很低。 如果節點數很少,而雜湊槽數量很多的話,bitmap的壓縮率就很低。
命令
命令 | 作用 |
---|---|
pfadd key element ... | 將所有元素新增到key中 |
pfcount key | 統計key的估算值(不精確) |
pgmerge new_key k1 k2 | 合併key到新key中 |
The API is constituted of three new commands:
PFADD var elment element ... element
PFCOUNT var
PFMERGE dst src src src src ... src
The commands prefix is "PF" in honor of Philippe Flajolet
應用場景
UV的統計需要去重,一個使用者一天內的多次訪問只能算作一次
淘寶、天貓首頁的UV,平均每天是1~1.5個億左右
每天存1.5個億的IP,訪問者來了後先去查是否存在,不存在加入
GEO(地理)
本質使用zset
引言
移動網際網路時代LBS應用越來越多,交友軟體中附近的小姐姐、外賣軟體中附近的美食店鋪、打車軟體附近的車輛等等,那這種附近各種形形色色的XXX地址位置選擇是如何實現的?
地球上的地理位置是使用二維的經緯度表示,經度範圍 (-180, 180],緯度範圍 (-90, 90],只要我們確定一個點的經緯度就可以確定他在地球的位置 。
例如滴滴打車,最直觀的操作就是實時記錄更新各個車的位置, 然後當我們要找車時,在資料庫中查詢距離我們(座標x0,y0)附近r公里範圍內部的車輛,使用如下sql即可:select taxi from position where x0-r < x < x0 + r and y0-r < y < y0+r
,但是這樣做會有什麼問題呢?
-
查詢效能問題,如果併發高,資料量大這種查詢是要搞垮資料庫的
-
這個查詢的是一個矩形訪問,而不是以我為中心r公里為半徑的圓形訪問。
-
精準度的問題,我們知道地球不是平面座標系,而是一個圓球,這種矩形計算在長距離計算時會有很大誤差
命令
-
GEOADD——多個經度(longitude)、緯度(latitude)、位置名稱(member)新增到指定的 key 中
用於儲存指定的地理空間位置,可以將一個或多個經度(longitude),緯度(latitude),位置名稱(member)新增到指定的key中,語法如下
GEOADD key longitude latitude member [longitude latitude member ...] ex: > GEOADD city 116.403963 39.915119 "天安門" 116.403414 39.924091 "故宮" 116.024067 40.362639 "長城" > type city zset > zrange city 0 -1
-
GEOPOS 從鍵裡面返回所有給定位置元素的位置(經度和緯度)
用於從給定的key裡面返回所有指定名稱(member)的位置(經度和緯度),不存在返回nil,語法如下:
GEOPOS key member [member ...] ex: > GEOPOS city 天安門 故宮 116.403963 39.915119 116.403414 39.924091
-
GEODIST 返回兩個給定位置之間的距離,語法如下
GEODIST key member1 member2 [m|km|ft|mi] ex: > GEODIST city 天安門 長城 km 59.3390 > GEODIST city 天安門 長城 m 59338.9814
後面引數是距離單位:m 米 km 千米 ft 英尺 mi 英里
-
GEORADIUS 以給定的經緯度為中心, 返回與中心的距離不超過給定最大距離的所有位置元素。
應用場景:以半徑為中心,查詢附近的XXX
GEORADIUS 以給定的經緯度為中心, 返回鍵包含的位置元素當中,與中心的距離不超過給定最大距離的所有位置元素。
ex: GEORADIUS city 116.418017 39.914402 10 km withdist withcoord count 10 withhash desc
-
WITHDIST: 在返回位置元素的同時, 將位置元素與中心之間的距離也一併返回。 距離的單位和使用者給定的範圍單位保持一致。
-
WITHCOORD: 將位置元素的經度和維度也一併返回。
-
WITHHASH: 以 52 位有符號整數的形式, 返回位置元素經過原始 geohash 編碼的有序集合分值。 這個選項主要用於底層應用或者除錯, 實際中的作用並不大
-
COUNT 限定返回的記錄數。
-
-
GEORADIUSBYMEMBER
跟GEORADIUS類似,找出位於指定範圍內的元素,中心點是由給定位置的元素決定
-
GEOHASH返回一個或多個位置元素的 Geohash 表示
使用GEOHASH來儲存地理位置的座標,用於獲取一個或多個位置元素的geohash值。語法格式如下
GEOHASH key member [member ...] > GEOHASH city 天安門 故宮 長城 wx4gOf6f2vO wx4gOgfqsjO wx4t85y1ktO
核心思想就是將球體轉換為平面,區塊轉換為一點 ,主要分為三步
-
將三維的地球變為二維的座標
-
在將二維的座標轉換為一維的點塊
-
最後將一維的點塊轉換為二進位制再通過base32編碼
-
應用場景
美團地圖位置的酒店推送
-
微信附近的人或者一公里內的各種營業廳,加油站,理髮店,超市
-
找單車
-
附近的酒店
Stream
Redis Stream 是 Redis 5.0 版本新增加的資料結構。 Redis Stream 主要用於訊息佇列(MQ,Message Queue),
Redis 本身是有一個 Redis 釋出訂閱 (pub/sub) 來實現訊息佇列的功能,但它有個缺點就是訊息無法持久化,如果出現網路斷開、Redis 宕機等,訊息就會被丟棄。簡單來說釋出訂閱 (pub/sub) 可以分發訊息,但無法記錄歷史訊息。 而 Redis Stream 提供了訊息的持久化和主備複製功能,可以讓任何客戶端訪問任何時刻的資料,並且能記住每一個客戶端的訪問位置,還能保證訊息不丟失。它算是redis自己訊息功能的補充。
但是, 一般主流MQ都固定了(Kafka/RabbitMQ/RocketMQ/Pulsar) 。