一、前言
本篇主要使用StackExchangeRedis在.Net Core中使用Redis,使用基礎見:點擊此處。
二、五種基礎數據結構
1.字符串類型String
字符串類型是Redis中最基本的數據類型,它能存儲任何形式的字符串,包括二進制數據。你可以用其存儲用戶的郵箱、JSON化的對象甚至是一張圖片。一個字符串類型鍵允許存儲地得數據的最大容量是512MB。
字符串類型是其他4種數據類型的基礎,其他數據類型和字符串類型的差別從某種角度來說只是組織字符串的形式不同。例如,列表類型是以列表的形式組織字符串,二集合類型是以集合的形式組織字符串。
以下為StackExchangeRedis中字符串常用方法及其命令:
(1)賦值與取值
//方法 redisConnection.GetDatabase().StringSetAsync(key, value); redisConnection.GetDatabase().StringGetAsync(key); //命令 127.0.0.1:6379> set stringKey stringValue OK 127.0.0.1:6379> get stringKey "stringValue"
(2)返回 key 中字符串值的子字符
//方法 redisConnection.GetDatabase().StringGetRangeAsync(key, start, end); //命令 127.0.0.1:6379> getrange stringKey 6 10 "Value"
(3)將給定 key 的值設為 value ,並返回 key 的舊值(old value)
//方法 var oldvalue = await redisConnection.GetDatabase().StringGetSetAsync(key, oldkey); //命令 127.0.0.1:6379> getset stringKey newValue "stringValue"
(4)如果 key 已經存在並且是一個字符串, APPEND 命令將指定的 value 追加到該 key 原來值(value)的末尾
//方法 redisConnection.GetDatabase().StringAppendAsync(key, appendValue); //命令 127.0.0.1:6379> append stringKey append (integer) 14
2.散列類型Hash
Redis是采用字典結構以鍵值對的形式存儲數據的,而Hash的鍵值也是一種字典結構,其存儲了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他數據類型,不能嵌套其他數據類型(其他數據類型同理)。
散列類型適合存儲對象:使用對象類別和ID構成鍵名,使用字段表示對象的屬性,而字段值則存儲屬性值。例如要存儲ID為2的汽車對象,可以分別使用名為color、name和price的三個字段來存儲該輛汽車的顏色、名稱和價格。
以下為StackExchangeRedis中散列類型常用方法及其命令:
(1)賦值
//方法 HashEntry[] hashEntry = new HashEntry[] { new HashEntry("id",2), new HashEntry("color","red"), new HashEntry("price",200), }; redisConnection.GetDatabase().HashSetAsync("youCar", hashEntry); //命令 127.0.0.1:6379> hset myCar price 200 (integer) 1 127.0.0.1:6379> hset mCar color blue (integer) 1 127.0.0.1:6379> hset mCar name tractor (integer) 1
(2)取值
//方法 redisConnection.GetDatabase().HashGetAsync("youCar", "color"); redisConnection.GetDatabase().HashGetAllAsync("youCar"); //命令 127.0.0.1:6379> hget youCar color "red" 127.0.0.1:6379> hgetall youCar 1) "id" 2) "2" 3) "color" 4) "red" 5) "price" 6) "200"
(3)獲取所有散列類型中的字段
//方法 var keys = redisConnection.GetDatabase().HashKeys("youCar"); //命令 127.0.0.1:6379> hkeys youCar 1) "id" 2) "color" 3) "price"
3.列表List
列表類型(List)可以存儲一個有序的字符串列表,常用的操作是向列表兩端添加元素,或者獲得列表的一個片段。
列表類型內部是使用雙向鏈表(double linked list)實現的,所以向列表兩段添加元素的時間復雜都為O(1),獲取越接近兩端的元素速度就越快。這意味着即使是一個由幾千萬個元素的列表,獲取頭部或者尾部的10條記錄也是極快的。
不過使用鏈表的代價是通過索引訪問元素比較慢。於是,列表類型很適合於如社交網站的新鮮事、記錄日志、消息隊列等,類似獲取最前幾條的數據、從列表尾部插入等場景。
以下為StackExchangeRedis中列表類型常用方法及其命令:
(1)向列表兩端增加元素
//方法 redisConnection.GetDatabase().ListLeftPushAsync("myList","head1"); redisConnection.GetDatabase().ListRightPush("myList","bottom1"); //命令 127.0.0.1:6379> lpush myList head1 (integer) 1127.0.0.1:6379> rpush myList bottom1 (integer) 2
(2)從列表兩端移除並獲取一個元素
//方法 var value = redisConnection.GetDatabase().ListLeftPopAsync("myList"); var value = redisConnection.GetDatabase().ListRightPopAsync("myList"); //命令 127.0.0.1:6379> lpop myList "head1" 127.0.0.1:6379> rpop myList "bottom1" 127.0.0.1:6379> rpop myList (nil)
(3)獲取列表中的元素片段(不刪除只獲取)
//方法 var value = redisConnection.GetDatabase().ListRangeAsync("myList",0,2); //命令 127.0.0.1:6379> lrange myList 0 2 1) "1" 2) "2" 3) "3"
4.集合Set
Redis 的 Set 是 String 類型的無序集合。集合成員是唯一的,這就意味着集合中不能出現重復的數據。常用的操作是向集合加入或者刪除元素、判斷某個元素是否存在等,由於Redis 中集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是 O(1)。
最方便的是多個集合類型鍵之間還可以進行並集、交集和差集運算。
可以運用在共同好友、喜好和好友推薦(交集超過閾值)等可以運用交差並集操作的場景。
以下為StackExchangeRedis中集合類型常用方法及其命令:
(1)增加/刪除元素(可多個,相同元素自動忽略)
//方法 redisConnection.GetDatabase().SetAddAsync("mySet", new RedisValue[] { "a", "b" }); redisConnection.GetDatabase().SetRemoveAsync("mySet", "a"); //命令 127.0.0.1:6379> sadd mySet a (integer) 1 127.0.0.1:6379> sadd mySet a b c (integer) 2 127.0.0.1:6379> srem mySet c (integer) 1 127.0.0.1:6379> srem mySet b a (integer) 2
(2)獲取交集
//方法 var value = redisConnection.GetDatabase().SetCombine(SetOperation.Intersect, "mySet1", "mySet2"); //命令 127.0.0.1:6379> sadd mySet1 a b c (integer) 3 127.0.0.1:6379> sadd mySet2 b c d (integer) 3 127.0.0.1:6379> sinter mySet1 mySet2 1) "c" 2) "b"
5.有序集合Sort Set
從有序集合的名字就可以看出它和集合的區別就是”有序“二字。
在集合類型的基礎尚有序集合類型為集合中的每個元素都關聯了一個分數,按分數大小進行從小到大的排序,這使得我們不僅可以完成插入、刪除和判斷元素是否在等集合類型支持的操作,還能夠獲得分數最高(或最低)的前N個元素、獲得指定分數范圍內的元素等與分數有關的操作。雖然集合中每個元素都是不同的,但是它們的分數卻可以相同。
有序集合類型在某方面和列表類型有些相似
-
二者都是有序。
-
二者都可以獲取某一范圍的元素
但是二者由這很大的區別,這使得他們的應用場景有所不同。
-
列表是通過鏈表實現的,獲取靠近兩端的數據速度極快,而當元素增多后,訪問中間元素的速度會較慢,所以它更加適合實現如“新鮮事”或“日志”這樣很少訪問中間元素的應用。
-
有序集合是使用散列表和跳表(skip list)實現的,所以即使讀取文娛中間部分的數據速度也快(時間復雜度是O(log(N)))。
-
列表中不能簡單地調整元素的位置,但有序集合可以(通過更改元素的分數),所以可以用於排行榜、權重應用(有權重的消息隊列)。
-
有序集合要比類別更耗內存。
以下為StackExchangeRedis中有序集合類型常用方法及其命令:
(1)增加元素
//方法 SortedSetEntry[] sortedSetEntry = new SortedSetEntry[] { new SortedSetEntry("tom",10), new SortedSetEntry("peter",20), new SortedSetEntry("david",30), }; redisConnection.GetDatabase().SortedSetAddAsync("mySs", sortedSetEntry); //命令 127.0.0.1:6379> zadd mySs 10 tom 20 peter 30 david (integer) 3
(2)獲取排名在某個分數范圍的元素列表
//方法 var value = redisConnection.GetDatabase().SortedSetRemoveRangeByScoreAsync("mySs", 10, 20); //命令 127.0.0.1:6379> zrangebyscore mySs 10 20 1) "tom" 2) "peter"
(3)增加某個元素的分數
//方法 var sorce = redisConnection.GetDatabase().SortedSetIncrementAsync("mySs", "top", 3); //命令 127.0.0.1:6379> zincrby mySs 3 tom "13"
6.小結
上面各類型所舉得例子有限,基本所有的Redis命令在StackExchangeRedis中都有相應的異步和同步的方法,大家可以參考https://redis.io/commands。
三、其他數據結構
1.HyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 結構(其本身也是一種算法)。
Redis HyperLogLog 是用來做基數統計的算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。
在 Redis 里面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。
但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。
所以主要應用於:
-
統計訪問IP數量;
-
統計搜索關鍵詞數量;
-
統計用戶在線數;
-
等等大數量統計。
以下為StackExchangeRedis中常用方法及其命令:
(1)添加指定元素到 HyperLogLog 中
//方法 RedisValue[] redisValue = new RedisValue[] { 110,120,130,130 }; redisConnection.GetDatabase().HyperLogLogAddAsync("key", redisValue); //命令 127.0.0.1:6379> pfadd hyperlogKy 110 120 130 130 (integer) 1
(2)返回給定 HyperLogLog 的基數估算值。
//方法 var count = redisConnection.GetDatabase().HyperLogLogLength("key"); //命令 127.0.0.1:6379> pfcount hyperlogKy (integer) 3
2.geo
Redis在3.2版本之后新增了一個geo(地理位置),其數據結構為有序集合sort set。
geo可以將地理位置信息(經緯度)儲存起來,並計算兩個地理坐標之間的位置、返回以指定位置為圓心指定半徑內的所有地理位置信息等。
像是我們平時的打車、租房地圖等功能中就可以用到。
既然前面說到geo是有序集合那它的score怎么來?是我們自己輸入嘛?其實是通過我們存儲的經緯度通過geohash計算后的base32編碼字符串。geohash原理點擊此處。
以下為StackExchangeRedis中常用方法及其命令:
(1)添加地理位置
//方法 GeoEntry[] geoEntry = new GeoEntry[] { new GeoEntry(120.20000, 30.26667, "hangzhou"), new GeoEntry(116.41667, 39.91667, "beijing"), new GeoEntry(121.47, 31.23, "shanghai"), }; redisConnection.GetDatabase().GeoAdd("city", geoEntry); //命令 127.0.0.1:6379> geoadd city 120.20000 30.26667 hangzhou 116.41667 39.91667 beijing 121.47 31.23 shanghai (integer) 3
(2)獲取geohash
//方法 var geohash = redisConnection.GetDatabase().GeoHash("city", "hanghzou"); //命令 127.0.0.1:6379> geohash city hangzhou 1) "wtmkpjyuph0"
(3)獲取指定元素范圍的地理信息位置集合
//方法 var geo = redisConnection.GetDatabase().GeoRadius("city", "hanghzou", 300, GeoUnit.Kilometers, 10, Order.Ascending, GeoRadiusOptions.WithGeoHash); //命令 127.0.0.1:6379> georadiusbymember city hangzhou 300 km withcoord withdist withhash asc count 10 1) 1) "hangzhou" 2) "0.0000" 3) (integer) 4054134205316048 4) 1) "120.20000249147415" 2) "30.266670658987586" 2) 1) "shanghai" 2) "161.9183" 3) (integer) 4054803462927619 4) 1) "121.47000163793564" 2) "31.229999039757836"
在給定以下可選項時, 命令會返回額外的信息:
-
WITHDIST : 在返回位置元素的同時, 將位置元素與中心之間的距離也一並返回。 距離的單位和用戶給定的范圍單位保持一致。
-
WITHCOORD : 將位置元素的經度和維度也一並返回。
-
WITHHASH : 以 52 位有符號整數的形式, 返回位置元素經過原始 geohash 編碼的有序集合分值。
-
ASC : 根據中心的位置, 按照從近到遠的方式返回位置元素。DESC : 根據中心的位置, 按照從遠到近的方式返回位置元素。
它還有個類似的georadius命令,區別是由給定的經緯度為圓心。
//方法 var geo = redisConnection.GetDatabase().GeoDistance("city", "hanghzou","beijing"); //命令 127.0.0.1:6379> geodist city hangzhou beijing km "1126.8937"
3.發布訂閱pub/sub
"發布/訂閱"模式中包含兩種角色,分別是發布者和訂閱者。訂閱者可以訂閱一個或者若干個頻道(channel),而發布者可以向指定的頻道發送消息,所有訂閱此頻道的訂閱者都會收到此消息。
發布者通過publish命令發送消息:
//命令 127.0.0.1:6379> publish channel_1 hi (integer) 0 //方法 var count = redisConnection.GetSubscriber().Publish("channel_1", "hi");
消息是發送出去了,publish命令的返回值表示接收到這條消息的訂閱者數量。因為當前沒有訂閱這訂閱這個頻道,所以返回0。
注意,發送出去的消息是不會持久化的,訂閱者只能收到訂閱之后發布者發送的消息。
訂閱者通過subscribe命令訂閱一個或者多個頻道:
//命令 127.0.0.1:6379> subscribe channel_1 channel_2 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channel_1" 3) (integer) 1 1) "subscribe" 2) "channel_2" 3) (integer) 2 1) "message" 2) "channel_1" 3) "hi" //方法 while (true) { redisConnection.GetSubscriber().Subscribe("channel_1", (channel, message) => { var msg = message;//收到的消息 var chan = channel;//頻道名稱 }); }
執行subscribe命令之后進入訂閱狀態,可能收到三種類型的回復。每種類型的回復都包含三個值,第一個值是消息的類型,根據消息類型的不同,第二、三個值得含義也不同。消息類型可能得取值有以下三個:
-
subscribe:表示訂閱成功得反饋信息。第二個值是訂閱成功得頻道名稱,第三個值是當前客戶端訂閱得頻道數量。
-
message:這個類型得回復是我們最關心得,它表示接收到得消息。第二個值表示禪師消息的頻道名稱,第三個值是消息的內容。
-
unsubscribe:表示成功取消訂閱某個頻道。第二個值是對應頻道的名稱,第三個值是當前客戶端訂閱的頻道數量。
StackExchangeRedis中用到message和unsubscribe這兩種。
除了通過頻道名之外,還可以使用psubscribe命令通過規則訂閱頻道。如約定規則為channel_?*則可以匹配channel_為開頭的頻道,如channel_1、channel_2等。
//命令 127.0.0.1:6379> psubscribe cahnnel_?* Reading messages... (press Ctrl-C to quit) (integer) 1 1) "psubscribe" 2) "cahnnel_?*" 3) (integer) 1 //方法 while (true) { redisConnection.GetSubscriber().Subscribe("channel_?*", (channel, message) => { var msg = message;//收到的消息 var chan = channel;//頻道名稱 }); }
解除訂閱通過命令unsubscribe,如果不指定頻道則取消全部訂閱。在StackExchangeRedis中分別時方法Unsubscribe()和UnsubscribeAll()。