Redis的9種數(shù)據(jù)類型用法解讀
在具體描述這幾種數(shù)據(jù)類型之前,我們先通過一張圖了解下 Redis 內(nèi)部內(nèi)存管理中是如何描述這些不同數(shù)據(jù)類型的:

首先Redis內(nèi)部使用一個(gè)redisObject對象來表示所有的key和value,redisObject最主要的信息如上圖所示:type代表一個(gè)value對象具體是何種數(shù)據(jù)類型,encoding是不同數(shù)據(jù)類型在redis內(nèi)部的存儲方式,
比如:type=string代表value存儲的是一個(gè)普通字符串,那么對應(yīng)的encoding可以是raw或者是int,如果是int則代表實(shí)際redis內(nèi)部是按數(shù)值型類存儲和表示這個(gè)字符串的,當(dāng)然前提是這個(gè)字符串本身可以用數(shù)值表示,比如:"123" "456"這樣的字符串。
這需要特殊說明一下vm字段,只有打開了Redis的虛擬內(nèi)存功能,此字段才會真正的分配內(nèi)存,該功能默認(rèn)是關(guān)閉狀態(tài)的,該功能會在后面具體描述。
通過上圖我們可以發(fā)現(xiàn)Redis使用redisObject來表示所有的key/value數(shù)據(jù)是比較浪費(fèi)內(nèi)存的,當(dāng)然這些內(nèi)存管理成本的付出主要也是為了給Redis不同數(shù)據(jù)類型提供一個(gè)統(tǒng)一的管理接口,實(shí)際作者也提供了多種方法幫助我們盡量節(jié)省內(nèi)存使用,我們隨后會具體討論。
redis支持豐富的數(shù)據(jù)類型
不同的場景使用合適的數(shù)據(jù)類型可以有效的優(yōu)化內(nèi)存數(shù)據(jù)的存放空間:
- string:最基本的數(shù)據(jù)類型,二進(jìn)制安全的字符串,最大512M。
- list:按照添加順序保持順序的字符串列表。
- set:無序的字符串集合,不存在重復(fù)的元素。
- sorted set:已排序的字符串集合。
- hash:key-value對的一種集合。
- bitmap:更細(xì)化的一種操作,以bit為單位。
- hyperloglog:基于概率的數(shù)據(jù)結(jié)構(gòu)。 # 2.8.9新增
- Geo:地理位置信息儲存起來, 并對這些信息進(jìn)行操作 # 3.2新增
- 流(Stream)# 5.0新增
String 字符串
常用命令:
setnx,set,get,decr,incr,mget 等。
應(yīng)用場景
字符串是最常用的數(shù)據(jù)類型,他能夠存儲任何類型的字符串,當(dāng)然也包括二進(jìn)制、JSON化的對象、甚至是Base64編碼之后的圖片。
在Redis中一個(gè)字符串最大的容量為512MB,可以說是無所不能了。redis的key和string類型value限制均為512MB。
雖然Key的大小上限為512M,但是一般建議key的大小不要超過1KB,這樣既可以節(jié)約存儲空間,又有利于Redis進(jìn)行檢索
- 緩存,熱點(diǎn)數(shù)據(jù)
- 分布式session
- 分布式鎖
- INCR計(jì)數(shù)器
- 文章的閱讀量,微博點(diǎn)贊數(shù),允許一定的延遲,先寫入 Redis 再定時(shí)同步到數(shù)據(jù)庫
- 全局ID
- INT 類型,INCRBY,利用原子性
- INCR 限流
- 以訪問者的 IP 和其他信息作為 key,訪問一次增加一次計(jì)數(shù),超過次數(shù)則返回 false。
- setbit 位操作
內(nèi)部編碼
- int:8 個(gè)字節(jié)的長整型(long,2^63-1)
- embstr:小于等于44個(gè)字節(jié)的字符串,embstr格式的SDS(Simple Dynamic String)
- raw:SDS大于 44 個(gè)字節(jié)的字符串
接下來就是ebmstr和raw兩種內(nèi)部編碼的長度界限,請看下面的源碼
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}通過下圖可以直觀感受一下字符串類型和哈希類型的區(qū)別:

redis 為什么要自己寫一個(gè)SDS的數(shù)據(jù)類型,主要是為了解決C語言 char[] 的四個(gè)問題
- 字符數(shù)組必須先給目標(biāo)變量分配足夠的空間,否則可能會溢出
- 查詢字符數(shù)組長度 時(shí)間復(fù)雜度O(n)
- 長度變化,需要重新分配內(nèi)存
- 通過從字符串開始到結(jié)尾碰到的第一個(gè)\0來標(biāo)記字符串的結(jié)束,因此不能保存圖片、音頻、視頻、壓縮文件等二進(jìn)制(bytes)保存的內(nèi)容,二進(jìn)制不安全
redis SDS
- 不用擔(dān)心內(nèi)存溢出問題,如果需要會對 SDS 進(jìn)行擴(kuò)容
- 因?yàn)槎x了 len 屬性,查詢數(shù)組長度時(shí)間復(fù)雜度O(1) 固定長度
- 空間預(yù)分配,惰性空間釋放
- 根據(jù)長度 len來判斷是結(jié)束,而不是 \0

為什么要有embstr編碼呢?他比raw的優(yōu)勢在哪里?
embstr編碼將創(chuàng)建字符串對象所需的空間分配的次數(shù)從raw編碼的兩次降低為一次。
因?yàn)閑mstr編碼字符串的素有對象保持在一塊連續(xù)的內(nèi)存里面,所以那個(gè)編碼的字符串對象比起raw編碼的字符串對象能更好的利用緩存。
并且釋放embstr編碼的字符串對象只需要調(diào)用一次內(nèi)存釋放函數(shù),而釋放raw編碼對象的字符串對象需要調(diào)用兩次內(nèi)存釋放函數(shù),如圖所示,左邊emstr編碼,右邊是raw編碼:

Hash 哈希表
常用命令:
hget,hsetnx,hset,hvals,hgetall,hmset,hmget 等。
redis> HSET phone myphone nokia-1110 (integer) 1 redis> HEXISTS phone myphone (integer) 1 redis> HSET people jack "Jack Sparrow" (integer) 1 redis> HSET people gump "Forrest Gump" (integer) 1 redis> HGETALL people 1) "jack" # 域 2) "Jack Sparrow" # 值 3) "gump" 4) "Forrest Gump"
應(yīng)用場景
我們簡單舉個(gè)實(shí)例來描述下 Hash 的應(yīng)用場景,比如我們要存儲一個(gè)用戶信息對象數(shù)據(jù),包含以下信息:用戶 ID 為查找的 key,存儲的 value 用戶對象包含姓名,年齡,生日等信息,如果用普通的 key/value 結(jié)構(gòu)來存儲,主要有以下2種存儲方式:

第一種方式將用戶 ID 作為查找 key,把其他信息封裝成一個(gè)對象以序列化的方式存儲,這種方式的缺點(diǎn)是,增加了序列化/反序列化的開銷,并且在需要修改其中一項(xiàng)信息時(shí),需要把整個(gè)對象取回,并且修改操作需要對并發(fā)進(jìn)行保護(hù),引入CAS等復(fù)雜問題。

第二種方法是這個(gè)用戶信息對象有多少成員就存成多少個(gè) key-value 對兒,用用戶 ID +對應(yīng)屬性的名稱作為唯一標(biāo)識來取得對應(yīng)屬性的值,雖然省去了序列化開銷和并發(fā)問題,但是用戶 ID 為重復(fù)存儲,如果存在大量這樣的數(shù)據(jù),內(nèi)存浪費(fèi)還是非??捎^的。
那么 Redis 提供的 Hash 很好的解決了這個(gè)問題,Redis 的 Hash 實(shí)際是內(nèi)部存儲的 Value 為一個(gè) HashMap,并提供了直接存取這個(gè) Map 成員的接口,如下圖:

也就是說,Key 仍然是用戶 ID,value 是一個(gè) Map,這個(gè) Map 的 key 是成員的屬性名,value 是屬性值,這樣對數(shù)據(jù)的修改和存取都可以直接通過其內(nèi)部 Map 的 Key(Redis 里稱內(nèi)部 Map 的 key 為 field),也就是通過 key(用戶 ID) + field(屬性標(biāo)簽)就可以操作對應(yīng)屬性數(shù)據(jù)了,既不需要重復(fù)存儲數(shù)據(jù),也不會帶來序列化和并發(fā)修改控制的問題。很好的解決了問題。
這里同時(shí)需要注意,Redis 提供了接口(hgetall)可以直接取到全部的屬性數(shù)據(jù),但是如果內(nèi)部 Map 的成員很多,那么涉及到遍歷整個(gè)內(nèi)部 Map 的操作,由于 Redis 單線程模型的緣故,這個(gè)遍歷操作可能會比較耗時(shí),而另其它客戶端的請求完全不響應(yīng),這點(diǎn)需要格外注意。
購物車


內(nèi)部編碼
- ziplist(壓縮列表):當(dāng)哈希類型中元素個(gè)數(shù)小于 hash-max-ziplist-entries 配置(默認(rèn) 512 個(gè)),同時(shí)所有值都小于 hash-max-ziplist-value 配置(默認(rèn) 64 字節(jié))時(shí),Redis 會使用 ziplist 作為哈希的內(nèi)部實(shí)現(xiàn)。
- hashtable(哈希表):當(dāng)上述條件不滿足時(shí),Redis 則會采用 hashtable 作為哈希的內(nèi)部實(shí)現(xiàn)。
下面我們通過以下命令來演示一下 ziplist 和 hashtable 這兩種內(nèi)部編碼。
當(dāng) field 個(gè)數(shù)比較少并且 value 也不是很大時(shí)候 Redis 哈希類型的內(nèi)部編碼為 ziplist:

當(dāng) value 中的字節(jié)數(shù)大于 64 字節(jié)時(shí)(可以通過 hash-max-ziplist-value 設(shè)置),內(nèi)部編碼會由 ziplist 變成 hashtable。

當(dāng) field 個(gè)數(shù)超過 512(可以通過 hash-max-ziplist-entries 參數(shù)設(shè)置),內(nèi)部編碼也會由 ziplist 變成 hashtable
List 列表
常用命令:
lpush,rpush,lpop,rpop,lrange等。
127.0.0.1:6379> lpush list one # 將一個(gè)值或者多個(gè)值,插入到列表的頭部(左)(integer) 1 127.0.0.1:6379> lpush list two (integer) 2 127.0.0.1:6379> lpush list three (integer) 3 127.0.0.1:6379> lrange list 0 -1 # 查看全部元素 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> lrange list 0 1 # 通過區(qū)間獲取值 1) "three" 2) "two" 127.0.0.1:6379> rpush list right # 將一個(gè)值或者多個(gè)值,插入到列表的尾部(右)(integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "right" 127.0.0.1:6379>
列表(list)用來存儲多個(gè)有序的字符串,每個(gè)字符串稱為元素;一個(gè)列表可以存儲2^32-1個(gè)元素。Redis中的列表支持兩端插入和彈出,并可以獲得指定位置(或范圍)的元素,可以充當(dāng)數(shù)組、隊(duì)列、棧等
應(yīng)用場景
比如 twitter 的關(guān)注列表,粉絲列表等都可以用 Redis 的 list 結(jié)構(gòu)來實(shí)現(xiàn),可以利用lrange命令,做基于Redis的分頁功能,性能極佳,用戶體驗(yàn)好。
消息隊(duì)列
列表類型可以使用 rpush 實(shí)現(xiàn)先進(jìn)先出的功能,同時(shí)又可以使用 lpop 輕松的彈出(查詢并刪除)第一個(gè)元素,所以列表類型可以用來實(shí)現(xiàn)消息隊(duì)列

發(fā)紅包的場景
在發(fā)紅包的場景中,假設(shè)發(fā)一個(gè)10元,10個(gè)紅包,需要保證搶紅包的人不會多搶到,也不會少搶到

下面我們通過下圖來看一下 Redis 中列表類型的插入和彈出操作:

下面我們看一下 Redis 中列表類型的獲取與刪除操作:

Redis 列表類型的特點(diǎn)如下:
- 列表中所有的元素都是有序的,所以它們是可以通過索引獲取的lindex 命令。并且在 Redis 中列表類型的索引是從 0 開始的。
- 列表中的元素是可以重復(fù)的,也就是說在 Redis 列表類型中,可以保存同名元素
內(nèi)部編碼
- ziplist(壓縮列表):當(dāng)列表中元素個(gè)數(shù)小于 512(默認(rèn))個(gè),并且列表中每個(gè)元素的值都小于 64(默認(rèn))個(gè)字節(jié)時(shí),Redis 會選擇用 ziplist 來作為列表的內(nèi)部實(shí)現(xiàn)以減少內(nèi)存的使用。當(dāng)然上述默認(rèn)值也可以通過相關(guān)參數(shù)修改:list-max-ziplist-entried(元素個(gè)數(shù))、list-max-ziplist-value(元素值)。
- linkedlist(鏈表):當(dāng)列表類型無法滿足 ziplist 條件時(shí),Redis 會選擇用 linkedlist 作為列表的內(nèi)部實(shí)現(xiàn)。
Set 集合
常用命令:
sadd,spop,smembers,sunion,scard,sscan,sismember等。
# 初始化 sadd poker T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 TJ TQ TK X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 XJ XQ XK M1 M2 M3 M4 M5 M6 M7 M8 M9 M10 MJ MQ MK F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 FJ FQ FK XW DW # 數(shù)量 scard poker # 復(fù)用撲克牌 sunionstore pokernew poker # 成員 smembers poker # 是否包含 sismember poker T1 # 隨機(jī)讀取 spop poker # 設(shè)置 sadd user1tag tagID1 tagID2 tagID3 sadd user2tag tagID2 tagID3 sadd user3tag tagID2 tagID4 tagID5 # 獲取共同擁有的tag(交集) sinter user1tag user2tag user3tag # 獲取擁有的所有tag(并集) sunion user1tag user2tag user3tag # 獲取兩個(gè)之間的區(qū)別(差集) sdiff user2tag user3tag
應(yīng)用場景
Redis set 對外提供的功能與 list 類似是一個(gè)列表的功能,特殊之處在于 set 是可以自動排重的,當(dāng)你需要存儲一個(gè)列表數(shù)據(jù),又不希望出現(xiàn)重復(fù)數(shù)據(jù)時(shí),set 是一個(gè)很好的選擇,并且 set 提供了判斷某個(gè)成員是否在一個(gè) set 集合內(nèi)的重要接口,這個(gè)也是 list 所不能提供的。
1知乎點(diǎn)贊數(shù)

2京東的商品篩選

127.0.0.1:6379> sadd brand:apple iPhone11 (integer) 1 127.0.0.1:6379> sadd brand:ios iPhone11 (integer) 1 127.0.0.1:6379> sadd screensize:6.0-6.24 iPhone11 (integer) 1 127.0.0.1:6379> sadd memorysize:256GB iPhone11 (integer) 1 127.0.0.1:6379> sinter brand:apple brand:ios screensize:6.0-6.24 memorysize:256GB 1) "iPhone11"
篩選商品,蘋果,IOS,屏幕6.0-6.24,內(nèi)存大小256G
sinter brand:apple brand:ios screensize:6.0-6.24 memorysize:256GB
3.存儲社交關(guān)系
用戶(編號user001)關(guān)注
sadd focus:user001 user003
sadd focus:user002 user003 user004
相互關(guān)注
sadd focus:user001 user002
sadd focus:user002 user001
#判斷用戶2是否關(guān)注了用戶1 127.0.0.1:6379> SISMEMBER focus:user002 user001 (integer) 1
我關(guān)注得到人也關(guān)注了他(共同關(guān)注)
#獲取關(guān)注的交集 127.0.0.1:6379> sinter focus:user001 focus:user002 1) "user003"
可能認(rèn)識的人
#將所有的人存放到allusers集合 127.0.0.1:6379> SUNIONSTORE alluser:user001 focus:user001 focus:user002 (integer) 4 127.0.0.1:6379> SDIFF alluser:user001 focus:user001 1) "user004" 2) "user001" #剔除掉自己 127.0.0.1:6379> SREM alluser:user001 user001 (integer) 1 127.0.0.1:6379> SDIFF alluser:user001 focus:user001 1) "user004"
實(shí)現(xiàn)方式:
set 的內(nèi)部實(shí)現(xiàn)是一個(gè) value 永遠(yuǎn)為 null 的 HashMap,實(shí)際就是通過計(jì)算 hash 的方式來快速排重的,這也是 set 能提供判斷一個(gè)成員是否在集合內(nèi)的原因。
Redis 中的集合類型,也就是 set。在 Redis 中 set 也是可以保存多個(gè)字符串的,經(jīng)常有人會分不清 list 與 set,下面我們重點(diǎn)介紹一下它們之間的不同:
- set 中的元素是不可以重復(fù)的,而 list 是可以保存重復(fù)元素的。
- set 中的元素是無序的,而 list 中的元素是有序的。
- set 中的元素不能通過索引下標(biāo)獲取元素,而 list 中的元素則可以通過索引下標(biāo)獲取元素。
- 除此之外 set 還支持更高級的功能,例如多個(gè) set 取交集、并集、差集等
為什么 Redis 要提供 sinterstore、sunionstore、sdiffstore 命令來將集合的交集、并集、差集的結(jié)果保存起來呢?這是因?yàn)?Redis 在進(jìn)行上述比較時(shí),會比較耗費(fèi)時(shí)間,所以為了提高性能可以將交集、并集、差集的結(jié)果提前保存起來,這樣在需要使用時(shí),可以直接通過 smembers 命令獲取。
內(nèi)部編碼
- intset(整數(shù)集合):當(dāng)集合中的元素都是整數(shù),并且集合中的元素個(gè)數(shù)小于set-max-intset-entries 參數(shù)時(shí),默認(rèn)512,Redis 會選用 intset 作為底層內(nèi)部實(shí)現(xiàn)。
- hashtable(哈希表):當(dāng)上述條件不滿足時(shí),Redis 會采用 hashtable 作為底層實(shí)現(xiàn)。
Sorted set 有序集合
常用命令:
zadd,zrange,zrem,zcard,zscore,zcount,zlexcount等
redis> ZRANGE salary 0 -1 WITHSCORES # 測試數(shù)據(jù) 1) "tom" 2) "2000" 3) "peter" 4) "3500" 5) "jack" 6) "5000" redis> ZSCORE salary peter # 注意返回值是字符串 "3500" redis> ZCOUNT salary 2000 5000 # 計(jì)算薪水在 2000-5000 之間的人數(shù) (integer) 3 # rank:key 100: 分?jǐn)?shù) u1: ID # 初始化 時(shí)間復(fù)雜度: O(log(N)) zadd rank 100 u1 zadd rank 200 u2 zadd rank 300 u3 zadd rank 400 u4 zadd rank 500 u5 # 數(shù)量 zcard rank # 內(nèi)容(正序、倒序)時(shí)間復(fù)雜度: O(log(N)+M) zrange rank 0 -1 zrange rank 0 -1 withscores zrevrange rank 0 -1 withscores zrevrange rank 0 -2 withscores zrevrange rank 0 -1 # 獲取用戶分?jǐn)?shù)(不存在返回空) 時(shí)間復(fù)雜度: O(1) zscore rank u1 # 修改分?jǐn)?shù) zadd rank 800 u3 # 修改分?jǐn)?shù)(累加:新增或減少,返回修改后的分?jǐn)?shù)) zincrby rank 100 u3 zincrby rank -100 u3 # 查詢分?jǐn)?shù)范圍內(nèi)容(正序、倒序) zrangebyscore rank (200 800 withscores zrevrangebyscore rank (200 800 withscores # 移除元素 zrem rank u3
下面先看一下列表、集合、有序集合三種數(shù)據(jù)類型之間的區(qū)別:

內(nèi)部編碼
- ziplist(壓縮列表):當(dāng)有序集合的元素個(gè)數(shù)小于 128 個(gè)(默認(rèn)設(shè)置),同時(shí)每個(gè)元素的值都小于 64 字節(jié)(默認(rèn)設(shè)置),Redis 會采用 ziplist 作為有序集合的內(nèi)部實(shí)現(xiàn)。
- skiplist(跳躍表):當(dāng)上述條件不滿足時(shí),Redis 會采用 skiplist 作為內(nèi)部編碼。

備注:上述中的默認(rèn)值,也可以通過以下參數(shù)設(shè)置:zset-max-ziplist-entries 和 zset-max-ziplist-value。
應(yīng)用場景
Redis sorted set 的使用場景與 set 類似,區(qū)別是 set 不是自動有序的,而 sorted set 可以通過用戶額外提供一個(gè)優(yōu)先級(score)的參數(shù)來為成員排序,并且是插入有序的,即自動排序。
當(dāng)你需要一個(gè)有序的并且不重復(fù)的集合列表,那么可以選擇 sorted set 數(shù)據(jù)結(jié)構(gòu),比如 twitter 的 public timeline 可以以發(fā)表時(shí)間作為 score 來存儲,這樣獲取時(shí)就是自動按時(shí)間排好序的。點(diǎn)擊數(shù)做出排行榜。
1.商品的評價(jià)標(biāo)簽,可以記錄商品的標(biāo)簽,統(tǒng)計(jì)標(biāo)簽次數(shù),增加標(biāo)簽次數(shù),按標(biāo)簽的分值進(jìn)行排序

#添加商品(編號i5001)的標(biāo)簽tag和對應(yīng)標(biāo)簽的評價(jià)次數(shù) 127.0.0.1:6379> zadd goods_tag:i5001 442 tag1 265 tag2 264 tag3 (integer) 3 #不帶分?jǐn)?shù) 127.0.0.1:6379> zrange goods_tag:i5001 0 -1 1) "tag3" 2) "tag2" 3) "tag1" #帶分?jǐn)?shù) 127.0.0.1:6379> zrange goods_tag:i5001 0 -1 withscores 1) "tag3" 2) "264" 3) "tag2" 4) "265" 5) "tag1" 6) "442"
2.百度搜索熱點(diǎn)

#維護(hù)2020年1月21號的熱點(diǎn)新聞 127.0.0.1:6379> zadd hotspot:20200121 520 pot1 263 pot2 244 pot3 (integer) 3 127.0.0.1:6379> zrange hotspot:20200121 0 -1 withscores 1) "pot3" 2) "244" 3) "pot2" 4) "263" 5) "pot1" 6) "520" #增加點(diǎn)擊次數(shù) 127.0.0.1:6379> ZINCRBY hotspot 1 pot1 "521"
3.反spam系統(tǒng)
作為一個(gè)電商網(wǎng)站被各種spam攻擊是少不免(垃圾評論、發(fā)布垃圾商品、廣告、刷自家商品排名等)針對這些spam制定一系列anti-spam規(guī)則,其中有些規(guī)則可以利用redis做實(shí)時(shí)分析
譬如:1分鐘評論不得超過2次、5分鐘評論少于5次等
#獲取5秒內(nèi)操作記錄
$res = $redis->zRangeByScore('user:1000:comment', time() - 5, time());
#判斷5秒內(nèi)不能評論
if (!$res) {
$redis->zAdd('user:1000:comment', time(), '評論內(nèi)容');
} else {
echo '5秒之內(nèi)不能評論';
}
#5秒內(nèi)評論不得超過2次
if($redis->zRangeByScore('user:1000:comment',time()-5 ,time())==1)
echo '5秒之內(nèi)不能評論2次';
#5秒內(nèi)評論不得少于2次
if(count($redis->zRangeByScore('user:1000:comment',time()-5 ,time()))<2)
echo '5秒之內(nèi)不能評論2次';BitMap 位圖
在應(yīng)用場景中,有一些數(shù)據(jù)只有兩個(gè)屬性,比如是否是學(xué)生,是否是黨員等等,對于這些數(shù)據(jù),最節(jié)約內(nèi)存的方式就是用bit去記錄,以是否是學(xué)生為例,1代表是學(xué)生,0代表不是學(xué)生。那么1000110就代表7個(gè)人中3個(gè)是學(xué)生,這就是BitMaps的存儲需求。
Bitmaps是一個(gè)可以對位進(jìn)行操作的字符串,我們可以把Bitmaps想象成是一串二進(jìn)制數(shù)字,每個(gè)位置只存儲0和1。下標(biāo)是Bitmaps的偏移量。
BitMap 就是通過一個(gè) bit 位來表示某個(gè)元素對應(yīng)的值或者狀態(tài), 其中的 key 就是對應(yīng)元素本身,實(shí)際上底層也是通過對字符串的操作來實(shí)現(xiàn)。Redis從2.2.0版本開始新增了setbit,getbit,bitcount等幾個(gè)bitmap相關(guān)命令。
雖然是新命令,但是并沒有新增新的數(shù)據(jù)類型,因?yàn)?code>setbit等命令只不過是在set上的擴(kuò)展。
使用場景一:用戶簽到
很多網(wǎng)站都提供了簽到功能(這里不考慮數(shù)據(jù)落地事宜),并且需要展示最近一個(gè)月的簽到情況
<?php
$redis = new Redis();
$redis->connect('127.0.0.1');
//用戶uid
$uid = 1;
//記錄有uid的key
$cacheKey = sprintf("sign_%d", $uid);
//開始有簽到功能的日期
$startDate = '2017-01-01';
//今天的日期
$todayDate = '2017-01-21';
//計(jì)算offset
$startTime = strtotime($startDate);
$todayTime = strtotime($todayDate);
$offset = floor(($todayTime - $startTime) / 86400);
echo "今天是第{$offset}天" . PHP_EOL;
//簽到
//一年一個(gè)用戶會占用多少空間呢?大約365/8=45.625個(gè)字節(jié),好小,有木有被驚呆?
$redis->setBit($cacheKey, $offset, 1);
//查詢簽到情況
$bitStatus = $redis->getBit($cacheKey, $offset);
echo 1 == $bitStatus ? '今天已經(jīng)簽到啦' : '還沒有簽到呢';
echo PHP_EOL;
//計(jì)算總簽到次數(shù)
echo $redis->bitCount($cacheKey) . PHP_EOL;
/**
* 計(jì)算某段時(shí)間內(nèi)的簽到次數(shù)
* 很不幸啊,bitCount雖然提供了start和end參數(shù),但是這個(gè)說的是字符串的位置,而不是對應(yīng)"位"的位置
* 幸運(yùn)的是我們可以通過get命令將value取出來,自己解析。并且這個(gè)value不會太大,上面計(jì)算過一年一個(gè)用戶只需要45個(gè)字節(jié)
* 給我們的網(wǎng)站定一個(gè)小目標(biāo),運(yùn)行30年,那么一共需要1.31KB(就問你屌不屌?)
*/
//這是個(gè)錯(cuò)誤的計(jì)算方式
echo $redis->bitCount($cacheKey, 0, 20) . PHP_EOL;使用場景二:統(tǒng)計(jì)活躍用戶
使用時(shí)間作為cacheKey,然后用戶ID為offset,如果當(dāng)日活躍過就設(shè)置為1
那么我該如果計(jì)算某幾天/月/年的活躍用戶呢(暫且約定,統(tǒng)計(jì)時(shí)間內(nèi)只有有一天在線就稱為活躍),有請下一個(gè)redis的命令
命令 BITOP operation destkey key [key ...]
說明:對一個(gè)或多個(gè)保存二進(jìn)制位的字符串 key 進(jìn)行位元操作,并將結(jié)果保存到 destkey 上。
說明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 這四種操作中的任意一種參數(shù)
bitop and destKey key1 key2.... //交 bitop or destKey key1 key2.... //并 bitop not destKey key1 key2.... //非 bitop xor destKey key1 key2.... //異或
<?php
//日期對應(yīng)的活躍用戶
$data = array(
'2017-01-10' => array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),?
'2017-01-11' => array(1, 2, 3, 4, 5, 6, 7, 8),?
'2017-01-12' => array(1, 2, 3, 4, 5, 6),?
'2017-01-13' => array(1, 2, 3, 4),
'2017-01-14' => array(1, 2)?
);
//批量設(shè)置活躍狀態(tài)?
foreach ($data as $date => $uids) {
$cacheKey = sprintf("stat_%s", $date);
foreach ($uids as $uid) {
$redis->setBit($cacheKey, $uid, 1);
}
}
$redis->bitOp('AND', 'stat', 'stat_2017-01-10', 'stat_2017-01-11', 'stat_2017-01-12') . PHP_EOL;
//總活躍用戶:6?
echo "總活躍用戶:" . $redis->bitCount('stat') . PHP_EOL;
$redis->bitOp('AND', 'stat1', 'stat_2017-01-10', 'stat_2017-01-11', 'stat_2017-01-14') . PHP_EOL;
?
//總活躍用戶:2?
echo "總活躍用戶:" . $redis->bitCount('stat1') . PHP_EOL;
$redis->bitOp('AND', 'stat2', 'stat_2017-01-10', 'stat_2017-01-11') . PHP_EOL; //總活躍用戶:8?
echo "總活躍用戶:" . $redis->bitCount('stat2') . PHP_EOL;使用場景三:用戶在線狀態(tài)
前段時(shí)間開發(fā)一個(gè)項(xiàng)目,對方給我提供了一個(gè)查詢當(dāng)前用戶是否在線的接口。不了解對方是怎么做的,自己考慮了一下,使用bitmap是一個(gè)節(jié)約空間效率又高的一種方法,只需要一個(gè)key,然后用戶ID為offset,如果在線就設(shè)置為1,不在線就設(shè)置為0,和上面的場景一樣,5000W用戶只需要6MB的空間。
<?php
//批量設(shè)置在線狀態(tài)
$uids = range(1, 500000);
foreach ($uids as $uid) {
$redis->setBit('online', $uid, $uid % 2);
}
//一個(gè)一個(gè)獲取狀態(tài)
$uids = range(1, 500000);
$startTime = microtime(true);
foreach ($uids as $uid) {
echo $redis->getBit('online', $uid) . PHP_EOL;
}
$endTime = microtime(true);
//在我的電腦上,獲取50W個(gè)用戶的狀態(tài)需要25秒?
echo "total:" . ($endTime - $startTime) . "s";內(nèi)部編碼
這個(gè)就是Redis實(shí)現(xiàn)的BloomFilter,BloomFilter非常簡單,如下圖所示,假設(shè)已經(jīng)有3個(gè)元素a、b和c,分別通過3個(gè)hash算法h1()、h2()和h2()計(jì)算然后對一個(gè)bit進(jìn)行賦值,接下來假設(shè)需要判斷d是否已經(jīng)存在,那么也需要使用3個(gè)hash算法h1()、h2()和h2()對d進(jìn)行計(jì)算,然后得到3個(gè)bit的值,恰好這3個(gè)bit的值為1,這就能夠說明:d可能存在集合中。再判斷e,由于h1(e)算出來的bit之前的值是0,那么說明:e一定不存在集合中:

需要說明的是,bitmap并不是一種真實(shí)的數(shù)據(jù)結(jié)構(gòu),它本質(zhì)上是String數(shù)據(jù)結(jié)構(gòu),只不過操作的粒度變成了位,即bit。因?yàn)镾tring類型最大長度為512MB,所以bitmap最多可以存儲2^32個(gè)bit。
HyperLogLog 基數(shù)統(tǒng)計(jì)
HyperLogLog算法時(shí)一種非常巧妙的近似統(tǒng)計(jì)大量去重元素?cái)?shù)量的算法,它內(nèi)部維護(hù)了16384個(gè)桶來記錄各自桶的元素?cái)?shù)量,當(dāng)一個(gè)元素過來,它會散列到其中一個(gè)桶。
當(dāng)元素到來時(shí),通過 hash 算法將這個(gè)元素分派到其中的一個(gè)小集合存儲,同樣的元素總是會散列到同樣的小集合。這樣總的計(jì)數(shù)就是所有小集合大小的總和。
使用這種方式精確計(jì)數(shù)除了可以增加元素外,還可以減少元素

一個(gè)HyperLogLog實(shí)際占用的空間大約是 13684 * 6bit / 8 = 12k 字節(jié)。但是在計(jì)數(shù)比較小的時(shí)候,大多數(shù)桶的計(jì)數(shù)值都是零。如果 12k 字節(jié)里面太多的字節(jié)都是零,那么這個(gè)空間是可以適當(dāng)節(jié)約一下的。Redis 在計(jì)數(shù)值比較小的情況下采用了稀疏存儲,稀疏存儲的空間占用遠(yuǎn)遠(yuǎn)小于 12k 字節(jié)。相對于稀疏存儲的就是密集存儲,密集存儲會恒定占用 12k 字節(jié)。
內(nèi)部編碼
HyperLogLog 整體的內(nèi)部結(jié)構(gòu)就是 HLL 對象頭 加上 16384 個(gè)桶的計(jì)數(shù)值位圖。它在 Redis 的內(nèi)部結(jié)構(gòu)表現(xiàn)就是一個(gè)字符串位圖。你可以把 HyperLogLog 對象當(dāng)成普通的字符串來進(jìn)行處理。
應(yīng)用場景
Redis 的基數(shù)統(tǒng)計(jì),這個(gè)結(jié)構(gòu)可以非常省內(nèi)存的去統(tǒng)計(jì)各種計(jì)數(shù),比如注冊 IP 數(shù)、每日訪問 IP 數(shù)、頁面實(shí)時(shí)UV)、在線用戶數(shù)等。但是它也有局限性,就是只能統(tǒng)計(jì)數(shù)量,而沒辦法去知道具體的內(nèi)容是什么。當(dāng)然用集合也可以解決這個(gè)問題。但是一個(gè)大型的網(wǎng)站,每天 IP 比如有 100 萬,粗算一個(gè) IP 消耗 15 字節(jié),那么 100 萬個(gè) IP 就是 15M。而 HyperLogLog 在 Redis 中每個(gè)鍵占用的內(nèi)容都是 12K,理論存儲近似接近 2^64 個(gè)值,不管存儲的內(nèi)容是什么,它一個(gè)基于基數(shù)估算的算法,只能比較準(zhǔn)確的估算出基數(shù),可以使用少量固定的內(nèi)存去存儲并識別集合中的唯一元素。而且這個(gè)估算的基數(shù)并不一定準(zhǔn)確,是一個(gè)帶有 0.81% 標(biāo)準(zhǔn)錯(cuò)誤的近似值。
HyperLogLog 主要的應(yīng)用場景就是進(jìn)行基數(shù)統(tǒng)計(jì)。這個(gè)問題的應(yīng)用場景其實(shí)是十分廣泛的。例如:對于 Google 主頁面而言,同一個(gè)賬戶可能會訪問 Google 主頁面多次。于是,在諸多的訪問流水中,如何計(jì)算出 Google 主頁面每天被多少個(gè)不同的賬戶訪問過就是一個(gè)重要的問題。那么對于 Google 這種訪問量巨大的網(wǎng)頁而言,其實(shí)統(tǒng)計(jì)出有十億 的訪問量或者十億零十萬的訪問量其實(shí)是沒有太多的區(qū)別的,因此,在這種業(yè)務(wù)場景下,為了節(jié)省成本,其實(shí)可以只計(jì)算出一個(gè)大概的值,而沒有必要計(jì)算出精準(zhǔn)的值
這個(gè)數(shù)據(jù)結(jié)構(gòu)的命令有三個(gè):PFADD、PFCOUNT、PFMERGE
redis> PFADD databases "Redis" "MongoDB" "MySQL" (integer) 1 redis> PFADD databases "Redis" # Redis 已經(jīng)存在,不必對估計(jì)數(shù)量進(jìn)行更新 (integer) 0 redis> PFCOUNT databases (integer) 3
Geo 地理位置
Redis 的 GEO 特性在 Redis 3.2 版本中推出, 這個(gè)功能可以將用戶給定的地理位置信息儲存起來, 并對這些信息進(jìn)行操作。
GEO的數(shù)據(jù)結(jié)構(gòu)總共有六個(gè)命令:geoadd、geopos、geodist、georadius、georadiusbymember、gethash,GEO使用的是國際通用坐標(biāo)系WGS-84。
- 1.GEOADD:添加地理位置
- 2.GEOPOS:查詢地理位置(經(jīng)緯度),返回?cái)?shù)組
- 3.GEODIST:計(jì)算兩位位置間的距離
- 4.GEORADIUS:以給定的經(jīng)緯度為中心, 返回鍵包含的位置元素當(dāng)中, 與中心的距離不超過給定最大距離的所有位置元素。
- 5.GEORADIUSBYMEMBER:以給定的地理位置為中心, 返回鍵包含的位置元素當(dāng)中, 與中心的距離不超過給定最大距離的所有位置元素。
127.0.0.1:6379> geoadd kcityGeo 116.405285 39.904989 "beijing"
(integer) 1
127.0.0.1:6379> geoadd kcityGeo 121.472644 31.231706 "shanghai"
(integer) 1
127.0.0.1:6379> geodist kcityGeo beijing shanghai km
"1067.5980"
127.0.0.1:6379> geopos kcityGeo beijing
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
127.0.0.1:6379> geohash kcityGeo beijing
1) "wx4g0b7xrt0"
127.0.0.1:6379> georadiusbymember kcityGeo beijing 1200 km withdist withcoord asc count 5
1) 1) "beijing"
2) "0.0000"
3) 1) "116.40528291463851929"
2) "39.9049884229125027"
2) 1) "shanghai"
2) "1067.5980"
3) 1) "121.47264629602432251"
2) "31.23170490709807012"內(nèi)部編碼
但是,需要說明的是,Geo本身不是一種數(shù)據(jù)結(jié)構(gòu),它本質(zhì)上還是借助于Sorted Set(ZSET),并且使用GeoHash技術(shù)進(jìn)行填充。Redis中將經(jīng)緯度使用52位的整數(shù)進(jìn)行編碼,放進(jìn)zset中,score就是GeoHash的52位整數(shù)值。在使用Redis進(jìn)行Geo查詢時(shí),其內(nèi)部對應(yīng)的操作其實(shí)就是zset(skiplist)的操作。通過zset的score進(jìn)行排序就可以得到坐標(biāo)附近的其它元素,通過將score還原成坐標(biāo)值就可以得到元素的原始坐標(biāo)。
總之,Redis中處理這些地理位置坐標(biāo)點(diǎn)的思想是:二維平面坐標(biāo)點(diǎn) --> 一維整數(shù)編碼值 --> zset(score為編碼值) --> zrangebyrank(獲取score相近的元素)、zrangebyscore --> 通過score(整數(shù)編碼值)反解坐標(biāo)點(diǎn) --> 附近點(diǎn)的地理位置坐標(biāo)。
應(yīng)用場景
比如現(xiàn)在比較火的直播業(yè)務(wù),我們需要檢索附近的主播,那么GEO就可以很好的實(shí)現(xiàn)這個(gè)功能。
- 一是主播開播的時(shí)候?qū)懭胫鞑d的經(jīng)緯度
- 二是主播關(guān)播的時(shí)候刪除主播Id元素,這樣就維護(hù)了一個(gè)具有位置信息的在線主播集合提供給線上檢索
Streams 流
這是Redis5.0引入的全新數(shù)據(jù)結(jié)構(gòu),用一句話概括Streams就是Redis實(shí)現(xiàn)的內(nèi)存版kafka。支持多播的可持久化的消息隊(duì)列,用于實(shí)現(xiàn)發(fā)布訂閱功能,借鑒了 kafka 的設(shè)計(jì)。
Redis Stream的結(jié)構(gòu)有一個(gè)消息鏈表,將所有加入的消息都串起來,每個(gè)消息都有一個(gè)唯一的ID和對應(yīng)的內(nèi)容。消息是持久化的,Redis重啟后,內(nèi)容還在。

每個(gè)Stream都有唯一的名稱,它就是Redis的key,在我們首次使用xadd指令追加消息時(shí)自動創(chuàng)建。
每個(gè)Stream都可以掛多個(gè)消費(fèi)組,每個(gè)消費(fèi)組會有個(gè)游標(biāo)last_delivered_id在Stream數(shù)組之上往前移動,表示當(dāng)前消費(fèi)組已經(jīng)消費(fèi)到哪條消息了。每個(gè)消費(fèi)組都有一個(gè)Stream內(nèi)唯一的名稱,消費(fèi)組不會自動創(chuàng)建,它需要單獨(dú)的指令xgroup create進(jìn)行創(chuàng)建,需要指定從Stream的某個(gè)消息ID開始消費(fèi),這個(gè)ID用來初始化last_delivered_id變量。
每個(gè)消費(fèi)組(Consumer Group)的狀態(tài)都是獨(dú)立的,相互不受影響。也就是說同一份Stream內(nèi)部的消息會被每個(gè)消費(fèi)組都消費(fèi)到。
同一個(gè)消費(fèi)組(Consumer Group)可以掛接多個(gè)消費(fèi)者(Consumer),這些消費(fèi)者之間是競爭關(guān)系,任意一個(gè)消費(fèi)者讀取了消息都會使游標(biāo)last_delivered_id往前移動。每個(gè)消費(fèi)者者有一個(gè)組內(nèi)唯一名稱。
消費(fèi)者(Consumer)內(nèi)部會有個(gè)狀態(tài)變量pending_ids,它記錄了當(dāng)前已經(jīng)被客戶端讀取的消息,但是還沒有ack。如果客戶端沒有ack,這個(gè)變量里面的消息ID會越來越多,一旦某個(gè)消息被ack,它就開始減少。這個(gè)pending_ids變量在Redis官方被稱之為PEL,也就是Pending Entries List,這是一個(gè)很核心的數(shù)據(jù)結(jié)構(gòu),它用來確??蛻舳酥辽傧M(fèi)了消息一次,而不會在網(wǎng)絡(luò)傳輸?shù)闹型緛G失了沒處理。
- 消息ID:消息ID的形式是timestampInMillis-sequence,例如1527846880572-5,它表示當(dāng)前的消息在毫米時(shí)間戳1527846880572時(shí)產(chǎn)生,并且是該毫秒內(nèi)產(chǎn)生的第5條消息。消息ID可以由服務(wù)器自動生成,也可以由客戶端自己指定,但是形式必須是整數(shù)-整數(shù),而且必須是后面加入的消息的ID要大于前面的消息ID。
- 消息內(nèi)容:消息內(nèi)容就是鍵值對,形如hash結(jié)構(gòu)的鍵值對,這沒什么特別之處。
127.0.0.1:6379> XADD mystream * field1 value1 field2 value2 field3 value3
"1588491680862-0"
127.0.0.1:6379> XADD mystream * username lisi age 18
"1588491854070-0"
127.0.0.1:6379> xlen mystream
(integer) 2
127.0.0.1:6379> XADD mystream * username lisi age 18
"1588491861215-0"
127.0.0.1:6379> xrange mystream - +
1) 1) "1588491680862-0"
2) 1) "field1"
2) "value1"
3) "field2"
4) "value2"
5) "field3"
6) "value3"
2) 1) "1588491854070-0"
2) 1) "username"
2) "lisi"
3) "age"
4) "18"
3) 1) "1588491861215-0"
2) 1) "username"
2) "lisi"
3) "age"
4) "18"
127.0.0.1:6379> xdel mystream 1588491854070-0
(integer) 1
127.0.0.1:6379> xrange mystream - +
1) 1) "1588491680862-0"
2) 1) "field1"
2) "value1"
3) "field2"
4) "value2"
5) "field3"
6) "value3"
2) 1) "1588491861215-0"
2) 1) "username"
2) "lisi"
3) "age"
4) "18"
127.0.0.1:6379> xlen mystream
(integer) 2內(nèi)部編碼
streams底層的數(shù)據(jù)結(jié)構(gòu)是radix tree:Radix Tree(基數(shù)樹) 事實(shí)上就幾乎相同是傳統(tǒng)的二叉樹。僅僅是在尋找方式上,以一個(gè)unsigned int類型數(shù)為例,利用這個(gè)數(shù)的每個(gè)比特位作為樹節(jié)點(diǎn)的推斷。
能夠這樣說,比方一個(gè)數(shù)10001010101010110101010,那么依照Radix 樹的插入就是在根節(jié)點(diǎn),假設(shè)遇到0,就指向左節(jié)點(diǎn),假設(shè)遇到1就指向右節(jié)點(diǎn),在插入過程中構(gòu)造樹節(jié)點(diǎn),在刪除過程中刪除樹節(jié)點(diǎn)。如下是一個(gè)保存了7個(gè)單詞的Radix Tree:

應(yīng)用場景總結(jié)
實(shí)際上,所謂的應(yīng)用場景,其實(shí)就是合理的利用Redis本身的數(shù)據(jù)結(jié)構(gòu)的特性來完成相關(guān)業(yè)務(wù)功能

常用內(nèi)存優(yōu)化手段與參數(shù)
通過我們上面的一些實(shí)現(xiàn)上的分析可以看出 redis 實(shí)際上的內(nèi)存管理成本非常高,即占用了過多的內(nèi)存,作者對這點(diǎn)也非常清楚,所以提供了一系列的參數(shù)和手段來控制和節(jié)省內(nèi)存,我們分別來討論下。
首先最重要的一點(diǎn)是不要開啟 Redis 的 VM 選項(xiàng),即虛擬內(nèi)存功能,這個(gè)本來是作為 Redis 存儲超出物理內(nèi)存數(shù)據(jù)的一種數(shù)據(jù)在內(nèi)存與磁盤換入換出的一個(gè)持久化策略,但是其內(nèi)存管理成本也非常的高,并且我們后續(xù)會分析此種持久化策略并不成熟,所以要關(guān)閉 VM 功能,請檢查你的 redis.conf 文件中 vm-enabled 為 no。
其次最好設(shè)置下 redis.conf 中的 maxmemory 選項(xiàng),該選項(xiàng)是告訴 Redis 當(dāng)使用了多少物理內(nèi)存后就開始拒絕后續(xù)的寫入請求,該參數(shù)能很好的保護(hù)好你的 Redis 不會因?yàn)槭褂昧诉^多的物理內(nèi)存而導(dǎo)致 swap,最終嚴(yán)重影響性能甚至崩潰。
另外 Redis 為不同數(shù)據(jù)類型分別提供了一組參數(shù)來控制內(nèi)存使用,我們在前面詳細(xì)分析過 Redis Hash 是 value 內(nèi)部為一個(gè) HashMap,如果該 Map 的成員數(shù)比較少,則會采用類似一維線性的緊湊格式來存儲該 Map,即省去了大量指針的內(nèi)存開銷,這個(gè)參數(shù)控制對應(yīng)在 redis.conf 配置文件中下面2項(xiàng):
hash-max-ziplist-entries 64 hash-max-zipmap-value 512
含義是當(dāng) value 這個(gè) Map 內(nèi)部不超過多少個(gè)成員時(shí)會采用線性緊湊格式存儲,默認(rèn)是64,即 value 內(nèi)部有64個(gè)以下的成員就是使用線性緊湊存儲,超過該值自動轉(zhuǎn)成真正的 HashMap。
hash-max-zipmap-value 含義是當(dāng) value 這個(gè) Map 內(nèi)部的每個(gè)成員值長度不超過多少字節(jié)就會采用線性緊湊存儲來節(jié)省空間。
以上2個(gè)條件任意一個(gè)條件超過設(shè)置值都會轉(zhuǎn)換成真正的 HashMap,也就不會再節(jié)省內(nèi)存了,那么這個(gè)值是不是設(shè)置的越大越好呢,答案當(dāng)然是否定的,HashMap 的優(yōu)勢就是查找和操作的時(shí)間復(fù)雜度都是 O(1) 的,而放棄 Hash 采用一維存儲則是 O(n) 的時(shí)間復(fù)雜度,如果成員數(shù)量很少,則影響不大,否則會嚴(yán)重影響性能,所以要權(quán)衡好這個(gè)值的設(shè)置,總體上還是最根本的時(shí)間成本和空間成本上的權(quán)衡。
同樣類似的參數(shù)還有
list-max-ziplist-entries 512
說明:list 數(shù)據(jù)類型多少節(jié)點(diǎn)以下會采用去指針的緊湊存儲格式。
list-max-ziplist-value 64
說明:list 數(shù)據(jù)類型節(jié)點(diǎn)值大小小于多少字節(jié)會采用緊湊存儲格式。
set-max-intset-entries 512
說明:set 數(shù)據(jù)類型內(nèi)部數(shù)據(jù)如果全部是數(shù)值型,且包含多少節(jié)點(diǎn)以下會采用緊湊格式存儲。
最后想說的是 Redis 內(nèi)部實(shí)現(xiàn)沒有對內(nèi)存分配方面做過多的優(yōu)化,在一定程度上會存在內(nèi)存碎片,不過大多數(shù)情況下這個(gè)不會成為 Redis 的性能瓶 頸,不過如果在 Redis 內(nèi)部存儲的大部分?jǐn)?shù)據(jù)是數(shù)值型的話,Redis 內(nèi)部采用了一個(gè) shared integer 的方式來省去分配內(nèi)存的開銷,即在系統(tǒng)啟動時(shí)先分配一個(gè)從 1~n 那么多個(gè)數(shù)值對象放在一個(gè)池子中,如果存儲的數(shù)據(jù)恰好是這個(gè)數(shù)值范圍內(nèi)的數(shù)據(jù),則直接從池子里取出該對象,并且通過引用計(jì)數(shù)的方式來共享,這樣在系統(tǒng)存儲了大量數(shù)值下,也能一定程度上節(jié)省內(nèi)存并且提高性能,這個(gè)參數(shù)值 n 的設(shè)置需要修改源代碼中的一行宏定義 REDIS_SHARED_INTEGERS,該值 默認(rèn)是 10000,可以根據(jù)自己的需要進(jìn)行修改,修改后重新編譯就可以了。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redis分布式鎖如何實(shí)現(xiàn)續(xù)期
這篇文章主要介紹了Redis分布式鎖如何實(shí)現(xiàn)續(xù)期的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
redis+lua實(shí)現(xiàn)分布式限流的示例
本文主要介紹了redis+lua實(shí)現(xiàn)分布式限流的示例,可以實(shí)現(xiàn)復(fù)雜的限流邏輯,如滑動窗口限流,并且避免了多步操作導(dǎo)致的并發(fā)問題,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03
通過redis的腳本lua如何實(shí)現(xiàn)搶紅包功能
這篇文章主要給大家介紹了關(guān)于通過redis的腳本lua如何實(shí)現(xiàn)搶紅包功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
基于Redis實(shí)現(xiàn)共享Session登錄的實(shí)現(xiàn)
本文主要介紹了基于Redis實(shí)現(xiàn)共享Session登錄的實(shí)現(xiàn),包括發(fā)送短信驗(yàn)證碼、短信驗(yàn)證碼登錄和注冊、以及登錄狀態(tài)校驗(yàn)的流程,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03

