Redis擴展數據類型詳解

      網友投稿 712 2022-05-30

      在Redis中有5種基本數據類型,分別是String, List, Hash, Set, Zset。除此之外,Redis中還有一些實用性很高的擴展數據類型,下面來介紹一下這些擴展數據類型以及它們的使用場景。

      Geo

      GEO在Redis 3.2版本后被添加,可以說是針對LBS(Location-Based Service)產生的一種數據類型,主要用于存儲地理位置信息,并可以對存儲的信息進行一系列的計算操作。

      geoadd:存儲指定的地理空間位置:

      # 語法格式: GEOADD key longitude latitude member [longitude latitude member ...] # 測試: > GEOADD locations 116.419217 39.921133 beijing > GEOADD locations 120.369557 36.094406 qingdao

      來看一下geo數據在Redis中的存儲方式,可以看到是以zset格式進行存儲的,因此geo是zset的一個擴展:

      geopos:返回指定地理位置的經緯度坐標:

      # 語法格式: GEOPOS key member [member ...] # 測試: > GEOPOS locations beijing qingdao 116.41921967267990112 39.92113206197632991 120.36955565214157104 36.09440522913565275

      也可以使用zrange返回所有的位置元素而不帶經緯度信息:

      > ZRANGE locations 0 -1 qingdao beijing

      geodist:計算指定位置間的距離,并可以指定返回的距離單位:

      # 語法格式: GEODIST key member1 member2 [m|km|ft|mi] # 測試: > GEODIST locations beijing qingdao km 548.5196

      georadiusbymember:找出以給定位置為中心,返回key包含的元素中,與中心的距離不超過給定最大距離的所有位置元素:

      # 語法格式: GEORADIUSBYMEMBER key member radius [m|km|ft|mi] # 測試: > GEORADIUSBYMEMBER locations beijing 150 km beijing # 擴大范圍 > GEORADIUSBYMEMBER locations beijing 600 km qingdao beijing

      georadius與georadiusbymember類似,不過是以指定的經緯度為中心:

      # 語法格式: GEORADIUS key longitude latitude radius [m|km|ft|mi] # 測試: > GEORADIUS locations 116.4192 39.9211 10 km beijing

      geo并沒有提供刪除指令,但根據其底層是zset實現,我們可以使用zrem對數據進行刪除:

      > ZREM locations beijing

      基于geo,可以很簡單的存儲人或物關聯的經緯度信息,并對這些地理信息進行處理,例如基于查詢相鄰的經緯度范圍,能簡單實現類似“附近的人”等功能。

      Bitmap

      Bitmap 也被稱為位圖,是以 String 類型作為底層數據結構實現的一種統計二值狀態的數據類型。其中每一個bit都只能是0或1,所以通常用來表示一個對應于數組下標的數據是否存在。Bitmap 提供了一系列api,主要用于對 bit 位進行讀寫、計算、統計等操作。

      setbit:對key所存儲的字符串值,設置或清除指定偏移量上的位(bit):

      # 語法格式: SETBIT key offset value # 測試: > SETBIT key 100 1 > SETBIT key 128 1

      getbit:對key所存儲的字符串值,獲取指定偏移量上的位(bit):

      # 語法格式: GETBIT key offset # 測試: > GETBIT key 100 1

      bitcount:可以統計bit 數組中指定范圍內所有 1 的個數,如果不指定范圍,則獲取所有:

      # 語法格式: BITCOUNT key [start end] # 測試: > BITCOUNT key 2

      bitpos:計算 bit 數組中指定范圍第一個偏移量對應的的值等于targetBit的位置:

      # 語法格式: BITPOS key tartgetBit [start end] # 測試: > BITPOS key 1 100

      bitop:做多個bit 數組的and(交集)、or(并集)、not(非)、xor(異或)。例如對key和key2做交集操作,并將結果保存在key:and:key2中:

      # 語法格式: BITOP op destKey key1 [key2...] # 測試: > BITOP and key:and:key2 key key2 17

      Bitmap底層使用String實現,value的值最大能存儲512M字節,可以表示 512 * 1024 * 1024*8=4294967296個位,已經能夠滿足我們絕大部分的使用場景。再看一下底層存儲數據的格式,以剛剛存儲的key為例:

      \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x80

      將16進制的數據轉化為2進制數據,如下圖所示,第100位和第128位為1,其他為0:

      此外,由于Redis在存儲string類型的時候存儲形式為二進制,所以也可以通過操作bit位來對string類型進行操作,在下面的例子中,通過直接操作bit,將string類型的abc變成了bbc。

      > set key2 abc > setbit key2 6 1 > setbit key2 7 0 > get key2 bbc

      另外,可以通過bitfield命令實現類似的效果:

      > set key3 a > BITFIELD key3 get u8 0 97 > BITFIELD key3 set u8 0 98 97 > get key3 b

      使用bitfield 命令可以返回指定位域的bit值,并將它轉化為整形,有符號整型需在位數前加 i,無符號在位數前加u。上面我們將8位轉化為無符號整形,正好是a的ASCII碼,再對ASCII碼進行修改,可以直接改變字符串的值。

      Redis擴展數據類型詳解

      Bitmap的應用非常廣泛,例如在緩存三大問題中我們介紹過使用Bitmap作為布隆過濾器應對緩存穿透的問題,此外布隆過濾器也被廣泛用于郵件系統中攔截垃圾郵件的地址。另外,常用的用戶簽到、朋友圈等功能也可以用它來實現。

      以實現用戶簽到功能為例,可以將每個用戶按月存儲為一條數據,key的格式可以定義為 sign:userId:yyyyMM ,如果簽到了就將對應的位置改為1,未簽到為0,這樣最多只需要31個bit位就可以存儲一個月的數據,轉換為字節的話也只要4個字節就已經足夠。

      # 1月10日簽到,因為offset從0起始,所以將天數減1 > SETBIT sign:6666:202101 9 1 0 # 查看1月10日是否簽到 > GETBIT sign:6666:202101 9 1 # 統計簽到天數 > BITCOUNT sign:6666:202101 1 # 查看首次簽到的日期 > BITPOS sign:6666:202101 1 9 # 提取整月的簽到數據 > BITFIELD sign:6666:202101 get u31 0 2097152

      注意在使用bitfield指令時,有符號整型最大支持64位,而無符號整型最大支持63位。如果位數超過限制,會報如下錯誤:

      > bitfield key3 get u64 0 ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.

      所以在存儲簽到數據時,如果按月存儲的話在之后提取數據時會比較方便,如果按年存儲數據,在提取整年的簽到數據時可能需要進行分段。

      HyperLogLog

      Redis 在 2.8.9 版本添加了 HyperLogLog 結構,它是一種用于基數統計的數據集合類型。它的最大優勢就在于,當集合元素數量非常多時,它計算基數所需的空間總是固定的,而且還很小。

      pfadd:向HyperLogLog中添加數據:

      # 語法格式: PFADD key element [element ...] # 測試: > PFADD index.html uuid1 uuid2 uuid3 uuid4

      pfcount:返回HyperLogLog的基數統計結果:

      # 語法格式: PFCOUNT key [key ...] # 測試: > PFCOUNT index.html 4

      pfmerge:將多個 HyperLogLog 合并為一個 HyperLogLog ,合并后的 HyperLogLog 的基數估算值是通過對所有 給定 HyperLogLog 進行并集計算得出的。

      # 語法格式: PFMERGE destkey sourcekey [sourcekey ...] # 測試: > PFMERGE index.html home.html OK > PFCOUNT index.html 6

      例如在上面的例子中,使用HyperLogLog 可以很方便的統計網頁的UV。在官方文檔中指明,Redis 中每個 HyperLogLog 只需要花費 12 KB 內存,就可以對 2^64 個數據完成基數統計。盡管使用Set或Hash等結構也能實現基數統計,但這些數據結構都會消耗大量的內存。而使用HyperLogLog 時,和其他數據結構計算基數時,元素越多耗費內存就越多形成了鮮明對比。

      需要注意的是,HyperLogLog是一種算法,并非是Redis獨有的,并且HyperLogLog 的統計規則是基于概率完成的,所以它給出的統計結果是有一定誤差的,官方給出的標準誤算率是 0.81%。 HyperLogLog 只會根據輸入元素來計算基數,而不會存儲輸入的元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。

      針對以上這些特性,可以總結出,HyperLogLog適用于大數據量的基數統計,但是它也存在局限性,它只能夠實現統計基數的數量,但無法知道具體的原數據是什么。如果需要原數據的話,我們可以將 Bitmap 和 HyperLogLog 配合使用,例如在統計網站UV時,使用Bitmap 標識哪些用戶屬于活躍用戶,使用 HyperLogLog 實現基數統計。

      Stream

      Stream是Redis 5.0版本之后新增加的數據結構,實現了消息隊列的功能,并且實現消息的持久化和主備復制功能,可以讓任何客戶端訪問任何時刻的數據,并且能記住每一個客戶端的訪問位置,保證消息不丟失,下面我們看一下具體的指令。

      xadd:向隊列添加消息

      # 語法格式: XADD key ID field value [field value ...] # 測試: > XADD stream1 * phone 88888888 name Hydra "1614316213565-0" > XADD stream1 * key1 value1 key2 value2 key3 value3 "1614317444558-0"

      添加消息是生成的 1614316213565-0,是生成消息的id,由時間戳加序號組成,時間戳是Redis的服務器時間,如果在同一個時間戳內,序號會遞增來標識不同的消息。并且為了保證消息的有序性,生成的消息id是保持自增的。可以使用可視化工具查看數據,消息是以json格式被存儲:

      這里因為是不同時間戳,所以序號都是從0開始。我們可以通過redis的事務添加消息進行測試:

      > MULTI "OK" > XADD stream * msg 1 "QUEUED" > XADD stream * msg 2 "QUEUED" > XADD stream * msg 3 "QUEUED" > XADD stream * msg 4 "QUEUED" > XADD stream * msg 5 "QUEUED" > EXEC 1) "OK" 2) "1614319042782-0" 3) "OK" 4) "1614319042782-1" 5) "OK" 6) "1614319042782-2" 7) "OK" 8) "1614319042782-3" 9) "OK" 10) "1614319042782-4" 11) "OK"

      通過上面的例子,可以看見同一時間戳內,序號會不斷遞增。

      xrange:獲取消息列表,會自動過濾刪除的消息

      # 語法格式: XRANGE key start end [COUNT count] # 測試: > XRANGE stream1 - + count 5 1) 1) "1614316213565-0" 2) 1) "phone" 2) "88888888" 3) "name" 4) "Hydra" 2) 1) "1614317444558-0" 2) 1) "key1" 2) "value1" 3) "key2" 4) "value2" 5) "key3" 6) "value3"

      xread:以阻塞或非阻塞方式獲取消息列表

      # 語法格式: XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...] # 測試: > XREAD count 1 STREAMS stream1 0-1 1) 1) "stream1" 2) 1) 1) "1614316213565-0" 2) 1) "phone" 2) "88888888" 3) "name" 4) "Hydra"

      xdel:刪除消息

      # 語法格式: XDEL key ID [ID ...] # 測試: > XDEL stream1 1614317444558-0 "1"

      除了上面消息隊列的基本操作外,還可以創建消費者組對消息進行消費。首先使用xgroup create 創建消費者組:

      # 語法格式: XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername] # 創建一個隊列,從頭開始消費: > XGROUP CREATE stream1 consumer-group-1 0-0 # 創建一個隊列,從尾部開始消費,只接收新消息: > XGROUP CREATE stream1 consumer-group-2 $

      下面使用消費者組消費消息:

      # 語法格式 XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]

      注意這里消費消息的對象是 consumer消費者,而不是消費者組。在消費消息時,不需要預先創建消費者,在消費過程中直接指定就可以。接下來再向stream中發送一條消息,比較兩個消費者組的消費順序差異:

      # 重新發送一條消息 > XADD stream1 * newmsg hi "1614318022661-0" # 使用消費者組1消費: > XREADGROUP GROUP consumer-group-1 consumer1 COUNT 1 STREAMS stream1 > 1) 1) "stream1" 2) 1) 1) "1614316213565-0" 2) 1) "phone" 2) "88888888" 3) "name" 4) "Hydra" # 使用消費者組2消費: > XREADGROUP GROUP consumer-group-2 consumer2 COUNT 1 STREAMS stream1 > 1) 1) "stream1" 2) 1) 1) "1614318022661-0" 2) 1) "newmsg" 2) "hi"

      可以看到,消費者組1從stream的頭部開始消費,而消費者組2從創建消費者組后的最新消息開始消費。在消費者組2內使用新的消費者再次進行消費:

      > XREADGROUP GROUP consumer-group-2 consumer4 COUNT 1 STREAMS stream1 > > XADD stream1 * newmsg2 hi2 "1614318706162-0" > XREADGROUP GROUP consumer-group-2 consumer4 COUNT 1 STREAMS stream1 > 1) 1) "stream1" 2) 1) 1) "1614318706162-0" 2) 1) "newmsg2" 2) "hi2"

      在上面的例子中,可以看到在一個消費者組中,存在互斥原則,即一條消息被一個消費者消費過后,其他消費者就不能再消費這條消息了。

      xpending:等待列表用于記錄讀取但并未處理完畢的消息,可以使用它來獲取未處理完畢的消息。

      > XPENDING stream1 consumer-group-2 1) "2" # 2條已讀取但未處理的消息 2) "1614318022661-0" # 起始消息ID 3) "1614318706162-0" # 結束消息ID 4) 1) 1) "consumer2" # 消費者2有1個 2) "1" 2) 1) "consumer4" # 消費者4有1個 2) "1"

      在 xpending 命令后添加start end count參數可以獲取詳細信息:

      > XPENDING stream1 consumer-group-2 - + 10 1) 1) "1614318022661-0" # 消息ID 2) "consumer2" # 消費者 3) "1867692" # 從讀取到現在經歷的毫秒數 4) "1" #消息被讀取次數 2) 1) "1614318706162-0" 2) "consumer4" 3) "1380323" 4) "1"

      xack:告知消息被處理完成,移出pending列表

      > XACK stream1 consumer-group-2 1614318022661-0 "1"

      再次查看pending列表,可以看到1614318022661-0 已被移除:

      > XPENDING stream1 consumer-group-2 1) "1" 2) "1614318706162-0" 3) "1614318706162-0" 4) 1) 1) "consumer4" 2) "1"

      基于以上功能,如果我們的系統中已經使用了redis,甚至可以移除掉不需要的其他消息隊列中間件,來達到精簡應用系統的目的。并且,Redis Stream提供了消息的持久化和主從復制,能夠很好的保證消息的可靠性。

      最后

      如果覺得對您有所幫助,小伙伴們可以點個贊啊,非常感謝~

      公眾號碼農參上,一個熱愛分享的公眾號,有趣、深入、直接,與你聊聊技術。歡迎來加Hydra好友,做個之交。

      Redis 數據結構

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:淺談對象關系映射框架Hibernate
      下一篇:如何搭建自己的個人博客
      相關文章
      亚洲国产美女福利直播秀一区二区| 亚洲AV成人精品网站在线播放 | 国产AV无码专区亚洲Av| 亚洲第一视频在线观看免费| 小说区亚洲自拍另类| 久久亚洲精品11p| 国产成人亚洲综合无| 在线观看亚洲免费视频| 在线观看亚洲电影| mm1313亚洲精品国产| 亚洲国产成人五月综合网 | 亚洲最大天堂无码精品区| ASS亚洲熟妇毛茸茸PICS| 亚洲人成77777在线播放网站不卡 亚洲人成77777在线观看网 | 日韩va亚洲va欧洲va国产| 国产成人精品日本亚洲网站| 亚洲av无码一区二区乱子伦as| 亚洲国产精品无码久久久不卡| 久久久久亚洲AV成人无码| 亚洲AV本道一区二区三区四区| 91嫩草私人成人亚洲影院| 亚洲国产精品成人综合久久久| 亚洲不卡在线观看| 亚洲综合国产成人丁香五月激情| 亚洲国产精品美女久久久久| 日韩精品亚洲专区在线观看| 亚洲午夜无码片在线观看影院猛| 国产亚洲AV夜间福利香蕉149| 亚洲色偷偷偷鲁综合| 亚洲影院在线观看| 亚洲国产精品人久久电影| 日韩亚洲人成在线| 成人亚洲网站www在线观看| 久久亚洲av无码精品浪潮| 好看的亚洲黄色经典| 亚洲精品韩国美女在线| 亚洲欧洲日本在线观看| MM1313亚洲精品无码久久| 亚洲精品线路一在线观看| 亚洲AV无码第一区二区三区| 亚洲日本在线观看网址|