文章目錄
- 前言
- 一、Redis相關命令詳解及原理
- 1.1 string、set、zset、list、hash
- 1.1.1 string
- 1.1.2 list
- 1.1.3 hash
- 1.1.4 set
- 1.1.5 zset
- 1.2 分布式鎖的實現
- 1.3 lua腳本解決ACID原子性
- 1.4 Redis事務的ACID性質分析
- 二、Redis協議與異步方式
- 2.1 Redis協議解析
- 2.1.1 redis pipeline
- 2.1.2 Redis協議圖
- 2.2 特殊協議操作-訂閱發布
- 2.3 異步redis協議
- 2.3.1 hiredis + libevent
- 總結
前言
本文介紹了Redis相關命令以及Redis當中的一些概念(協議)。
一、Redis相關命令詳解及原理
內存是稀缺資源,所以:
- 當數據量少時,存儲效率高為主
- 當數據量多時,運行速度快為主
1.1 string、set、zset、list、hash
- string 是一個安全的二進制字符串(兼容’\0’作為分隔符,安全指按長度);
- 雙端隊列 (鏈表) list :有序(插入有序);
- 散列表 hash:對順序不關注,field 是唯一的;
- 無序集合 set:對順序不關注,里面的值都是唯一的;
- 有序集合 zset :對順序是關注的,里面的值是唯一的;根據 member 來確定唯一;根據 score 來 確定有序;
1.1.1 string
set key_test 1000get key_test# 原子減一
decr key_test
decrby key_test decrement(一個數字)# 原子加一
incr key_test
incrby key_test increment# set Not exist,當key_test存在時,什么也不做,否則等同于set
setnx key_test value
del key_test----------
#
setbit key_test offset value
# 第offset位設置為value
getbit key_test offset
# 統計字符串被設置為1的bit數
bitcount key_test
----------
應用
- 對象存儲:set,get
- 累加器:incr
- 分布式鎖:setnx
- 位運算:setbit,getbit,bitcount
1.1.2 list
雙向鏈表,首尾操作時間復雜度O(1);中間元素操作O(n)
- list.size < 48 不壓縮
- 元素壓縮前后長度差不超過8,不壓縮
為什么壓縮?如何壓縮的?
# 從隊列左側入隊
lpush key value ...
lpop key# 從隊列右側入隊
rpush key value ...
rpop key # 尾索引
lrange key start end# 從存于 key 的列表里移除前 count 次出現的值為 value 的元素
lrem key count value# rpop的阻塞版本
brpop key timeout
應用
- 棧:lpush + lpop
- 隊列:lpush + rpop
- 阻塞隊列:lpush + brpop
- 異步消息隊列
- 操作和隊列一樣,但是在不同系統間;生產者和消費者;
- 獲取固定窗口記錄
ltrim key 0 4
保留最近5條記錄
1.1.3 hash
散列表;C++ unordered_map
(節點數量 > 512 || 所有字符串長度 > 64) 采用dict
(節點數量 <= 512 || 所有字符串長度 < 64) 采用ziplist
hget key field
hgetallhset key field value
# 設置多個鍵值對
hmset key field1 value1 field2 value2 field3 value3 ... fieldn valuenhmget key field1 field2 ...hincrby key field increment
# 獲取有多少個鍵值對
hlen keyhdel key field
應用
- 存儲對象
- 購物車:商品列表用list,其中屬性用hash
1.1.4 set
無序集合
(元素都為整數 && 節點數量 <= 512) 采用整數數組存儲
(元素不全為整數 || 節點數量 > 512) 采用字典存儲
# 添加一個或多個
sadd key member ...
# 計算集合元素個數
scard keysmembers key
# 返回成員member是否為key的成員
sismember key member
# 隨機返回key集合中的一個或多個元素
srandmember key [count]
# 移除一個隨機元素
spop key [count]
# 返回差集
sdiff key [key...]
# 返回交集
sinter key [key...]
# 返回并集
sunion key [key...]
應用
- 抽獎:
srandmember
- 共同關注:
sdiff
;sinter
;sunion
1.1.5 zset
有序集合;實現排行榜;有序唯一
zadd key
# 從key中刪除member的鍵值對
zrem key member [member...]
# 返回有序集key中member的score值
zscore key member
# 成員member的score值加上增量
zincrby key increment member
# 返回個數
zcard key
# 返回排名
zrank key member
# 返回指定范圍的元素
zrange key start stop
# 返回指定范圍的元素(逆序)
zrevrange key start stop
應用
- 百度熱榜
- 延時隊列
- 分布式定時器
- 時間窗口限流
1.2 分布式鎖的實現
釋放鎖操作:事務操作
鎖:誰持有,誰釋放
get dislock
-- 釋放鎖
local uuid = redis.call("get", KEYS[1])
if uuid == KEYS[2] thenredis.call("del", KEYS[1])
end
1.3 lua腳本解決ACID原子性
# 開啟事務
multi# 提交事務
exec# 取消事務
discard# 檢測key的變動
watch實際中是使用lua腳本
- redis 中加載了一個 lua 虛擬機;用來執行 redis lua 腳本;
- redis lua 腳本的執行是原子性的;
- 當某個腳本正在執行的時候,不會有其他命令或者腳本被執行;
- lua 腳本當中的命令會直接修改數據狀態;
- lua 腳本 mysql 存儲區別:MySQL存儲過程不具備事務性,所以也不具備原子性;
注意:如果項目中使用了 lua 腳本,不需要使用上面的事務命令
1.4 Redis事務的ACID性質分析
-
A 原子性;事務是一個不可分割的工作單位,事務中的操作要么全部成功,要么全部失敗;redis 不支持回滾;即使事務隊列中的某個命令在執行期間出現了錯誤,整個事務也會繼續執行下去,直 到將事務隊列中的所有命令都執行完畢為止。
-
C 一致性;事務的前后,所有的數據都保持一個一致的狀態,不能違反數據的一致性檢測;這里 的一致性是指預期的一致性而不是異常后的一致性;所以 redis 也不滿足;這個爭議很大:redis 能 確保事務執行前后的數據的完整約束;但是并不滿足業務功能上的一致性;比如轉賬功能,一個扣 錢一個加錢;可能出現扣錢執行錯誤,加錢執行正確,那么最終還是會加錢成功;系統憑空多了 錢;
set zhang 1000
lpush zhang 1 3 4 #error
get mark
-
I 隔離性;各個事務之間互相影響的程度;redis 是單線程執行,天然具備隔離性;
-
D 持久性;redis 只有在 aof 持久化策略的時候,并且需要在 appendfsync=always 才具備持久性;實際項目中幾乎不會使用 redis.conf 中 aof 持久化策略;
-
面試時候回答:lua 腳本滿足原子性和隔離性;一致性和持久性不滿足;
get zhang ==>100set zhang 200
如果這兩個命令沒有作為一個整體,那么可以會有另一條連接set。這將導致數據不一致。
什么時候探討事務?多條并發連接
什么時候探討原子操作?多核
二、Redis協議與異步方式
2.1 Redis協議解析
2.1.1 redis pipeline
redis pipeline 是一個客戶端提供的機制,而不是服務端提供的;
注意:pipeline 不具備事務性;
目的:節約網絡傳輸時間;
通過一次發送多次請求命令,從而減少網絡傳輸的時間。
2.1.2 Redis協議圖
上圖描述了如何界定數據包:
- 長度 + 二進制流
- 二進制流 + 特殊分割符
2.2 特殊協議操作-訂閱發布
為了支持消息的多播機制,redis引入了發布訂閱:發送者發送消息,訂閱者接收消息。
# 訂閱頻道
subscribe `channel`
# 訂閱模式頻道
psubscribe `channel`
# 取消訂閱頻道
unsubscribe `channel`
# 發布具體頻道或模式頻道的內容
publish `channel` `message`
# 客戶端接收具體頻道內容
message `specificChannel` `message`
# 客戶端接收模式頻道內容
pmessage
應用:
- 發布訂閱可以收到redis主動推送的內容
- 項目中支持發布訂閱,需要另開一條連接
缺點:
- 生產者傳遞來一條消息,redis找到相應的消費者并傳遞過去,如果沒有消費者,消息丟棄;
- 如果有兩個消費者,此時其中一個消費者掛掉了,重連上來將不會接收到該消息;
- redis停機重啟,發布訂閱的消息不會持久化。
2.3 異步redis協議
同步連接方案采用阻塞IO來實現:
優點:代碼書寫是同步的,業務邏輯不割裂
缺點:阻塞當前線程,直到返回結果,通常需要多個線程來實現線程池來解決效率問題。
異步連接方案采用非阻塞IO來實現:
優點:不阻塞當前線程,redis沒有返回,可以繼續向redis發送命令
缺點:代碼書寫是異步的,業務邏輯割裂,可以通過協程解決(skynet,openresty)
2.3.1 hiredis + libevent
我們需要做的事情:
適配:
- 事件對象
- 事件操作函數
hiredis需要做:
- 協議解析
- 讀寫事件
- 緩沖區操作
- 協議加密等
適配事件對象和函數。
static int redisAttach(reactor_t *r, redisAsyncContext *ac) {redisContext *c = &(ac->c);redis_event_t *re;/* Nothing should be attached when something is already attached */if (ac->ev.data != NULL)return REDIS_ERR;/* Create container for ctx and r/w events */re = (redis_event_t*)hi_malloc(sizeof(*re));if (re == NULL)return REDIS_ERR;re->ctx = ac;re->e.fd = c->fd;re->e.r = r;// dont use event buffer, using hiredis's bufferre->e.in = NULL;re->e.out = NULL;re->mask = 0;ac->ev.addRead = redisAddRead;ac->ev.delRead = redisDelRead;ac->ev.addWrite = redisAddWrite;ac->ev.delWrite = redisDelWrite;ac->ev.cleanup = redisCleanup;ac->ev.data = re;return REDIS_OK;
}
總結
本文介紹了Redis的基本命令以及Redis協議中的部分內容。Redis是內存型數據庫,圍繞著內存的特性,Redis結合了lua腳本,分布式鎖(最快的),異步連接等一系列特性。
參考鏈接:
https://github.com/0voice