首頁  >  文章  >  資料庫  >  redis的高階特性介紹

redis的高階特性介紹

王林
王林轉載
2021-01-13 10:11:342062瀏覽

redis的高階特性介紹

Redis(Remote Dictionary Server ),即遠端字典服務,是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key- Value資料庫,並提供多種語言的API。

(學習影片分享:redis影片教學

1.redis發布訂閱模式

Redis除了提供像list的這種的訊息佇列模式,還提供了一組命令實現發布/訂閱模式。例如微博,公眾號等都是可以由此實現。

redis的高階特性介紹

1.2 訂閱頻道

發布者需要將訊息傳送到一個地方,讓訂閱者可以訂閱訊息,而這個地方就是頻道(channel)。訂閱者可以訂閱一個或多個頻道,所有訂閱了這個頻道的訂閱者都會受到這則訊息。

開啟兩個客戶端進行測試

客户端1 订阅channel1
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1

客户端2 发布一则消息
127.0.0.1:6379> publish channel1 test
(integer) 1

客户端1 订阅消息
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "test"

1.2 依規則訂閱

#支援?和*佔位符。 ?代表一個字符,*代表 0 個或多個字符。

啟動四個redis-cli,一個作為訊息的發布者,另外三個作為訂閱者。
訂閱者1:訂閱體育相關

psubscribe *sport

訂閱者2:訂閱新聞相關

psubscribe news*

訂閱者3:訂閱天氣相關

psubscribe new weather*

發布者:

publish news-sport Kobe
publish news-music jaychou
publish news-weather rain

此時訂閱者1將會收到Kobe,訂閱者2將會收到全部訊息,訂閱者3將會收到rain。

redis的高階特性介紹

2.redis事務

2.1 為什麼要用交易

我們都是知道redis的單一指令是原子性的,但是如果需要用多個指令作為一個不可分割的操作序列,就需要用到交易。
例如使用setnx實現分散式鎖,我們一般先set,然後對key設定expire,防止del發生異常時候鎖不會釋放,業務處理完之後在del,這三個操作我們就希望作為一組命令執行。
redis交易有兩個特點:

  • 依照進入佇列的順序執行

  • 不會受到其他客戶端請求影響

redis的交易設計四個指令:multi(開啟交易),exec(執行交易),dicard(取消交易),watch(監視)

2.2 交易的用法

轉帳場景A和B各有100元,A向B轉帳10元,A減10元,B加10元

127.0.0.1:6379> set A 100
OK
127.0.0.1:6379> set B 100
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby A 10
QUEUED
127.0.0.1:6379> incrby B 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 90
2) (integer) 110
127.0.0.1:6379> get A
"90"
127.0.0.1:6379> get B
"110"

透過multi指令開啟交易。事務不能嵌套,多個multi指令效果一樣的
使用multi開啟事務之後,客戶端向伺服器發送多條指令,這些指令並不會立即被執行,而是會被放到一個佇列中,當exec指令呼叫之後,佇列中的命令才會被執行。
我們可以使用discard來清空事務佇列。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)

當我們執行事務的時候出現了問題會回滾嗎?

exec之前發生錯誤(如指令語法錯誤)

127.0.0.1:6379> clear
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name test
QUEUED
127.0.0.1:6379> hset user lisi
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name
(nil)

exec之後發生錯誤(對同一個key使用不同資料類型的命令)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> hset k1 a b
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get k1
"1"

透過上面操作,我們可以知道當交易在exec前發生錯誤,將會回滾所有操作;如果在exec後發生錯誤,只有錯誤的指令不會被執行。
為什麼redis在一個交易中存在錯誤不進行回滾呢?
我們從上面操作可以看出,redis只有在指令語法錯誤的時候進行回滾,而指令操作錯誤是有開發人員導致的bug,例如:你對一個int型別進行1,然後不小心2,或對一個string類型進行1,回滾是不適用的

2.3 watch指令

它可以為Redis事務提供CAS樂觀鎖定操作,也就是多個執行緒更新某個變數的時候,會讓舊值跟記憶體位址比較,如果相等,則更新為新值。
我們可以用watch監視一個或多個key,如果開啟事務之後,至少有一個被監視的key在exec執行之前被修改,則會取消整個事務。

首先client 1執行watch監視money這個key,並開啟事務對money進行增加100

127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby money 100
QUEUED

在事務結束之前,在client 2對money進行減少100

127.0.0.1:6379> decrby money 100
(integer) 900

此時client 1結束事務,money的值並沒有被增加,反而減少,說明事務的修改失效

127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get money
"900"

3. Lua腳本

Lua腳本是一種輕量級腳本語言,C語言寫的,跟預存過程有點類似。為啥要用lua腳本呢?

一次發送多個命令,減少網路開銷Redis會將腳本作為一個整體執行,確保原子性(可用此方式替換事務)腳本復用,以便於多個客戶端完成相同的邏輯。

3.1 使用

我們可以使用以下指令進行呼叫lua腳本

eval script numkeys [key1 key2 key3 ....] [arg1 arg2 arg3 ....]

eval 執行lua腳本

script 代表lua腳本的內容

numkeys key的數量

[key1 key2 key3 ....] 键名参数,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。

[arg1 arg2 arg3 ....] 全局变量,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)

来个简单的例子

127.0.0.1:6379> eval "return {KEYS[1],ARGV[1],KEYS[2],ARGV[2]}" 2 key1 key2 val1 val1
1) "key1"
2) "val1"
3) "key2"
4) "val1"
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 val1 val1
1) "key1"
2) "key2"
3) "val1"
4) "val1"

在lua脚本如何调用redis命令呢?
我们可以使用 redis.call(command, key [param1, param2…])进行操作

commond redis的命令,如set,get等key 被操作的键[param1, param2…]表示给key的参数

127.0.0.1:6379> eval "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 name age lisi 18
(nil)
127.0.0.1:6379> mget name age
1) "lisi"
2) "18

以上命令等价于 mset name lisi age 18, key的数量为2,2 后面两个值为key,在之后就是args

直接在redis-cli中写lua脚本不够方便,通常我们会把脚本放在文件中,然后执行这个文件
我们在一个目录下新建一个test.lua的脚本,填写以下内容后执行。

root@VM-0-5-centos src]# mkdir testlua
[root@VM-0-5-centos src]# cd testlua/
[root@VM-0-5-centos testlua]# ll
total 0
[root@VM-0-5-centos testlua]# touch test.lua
[root@VM-0-5-centos testlua]# vim test.lua
redis.call('set',KEYS[1],ARGV[1])
return redis.call('get',KEYS[1])
[root@VM-0-5-centos testlua]# redis-cli --eval test.lua 1 myname , Armin
"Armin"

值得注意的是key和arg之间需要加上空格逗号空格(myname , Armin)

3.2 缓存lua脚本

之所以需要缓存lua脚本,这是因为每次调用的时候都将整个脚本传给redis服务端,会产生较大的网络开销。为了解决这个问题,Redis提供了evalsha命令,让开发人员通过脚本内容的SHA1摘要执行脚本。

那么怎么将生成这个SHA1并将脚本内容加载到缓存呢,这就用到script load命令去计算脚本的SHA1摘要并记录脚本到缓存中,执行evalsha时,redis会根据提供的摘要去脚本缓存找到对应脚本内容,如果找到则执行,否则返回错误提示: “NOSCRIPT No matching script. Please use EVAL”

127.0.0.1:6379> script load "return 'Hey boy'"
"3760855b303510c83f0be2e8acfb0be64113ae6e"
127.0.0.1:6379> evalsha 3760855b303510c83f0be2e8acfb0be64113ae6e 0
"Hey boy"
127.0.0.1:6379> script exists 3760855b303510c83f0be2e8acfb0be64113ae6e //判断是否存在
1) (integer)

Redis还给lua脚本的执行提供了超时时间,默认的超时时间为5s,超过5s之后redis会接受其他命令但是会返回一个"BUSY"的错误
可在redis.conf中修改指定参数

lua-time-limit 5000

Redis提供了个script kill的命令来终止正在运行的脚本

127.0.0.1:6379> set name lisi
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
127.0.0.1:6379> script kill
OK
127.0.0.1:6379> set name lisi
OK

如果数据进行了修改操作,将无法使用script kill终止脚本,因为违反了原子性。此时只能通过shutdown nosave来强行终止redis。
shutdown nosave 和 shutdown 的区别在于 shutdown nosave 不会进行持久化 操作,意味着发生在上一次快照后的数据库修改都会丢失。

相关推荐:redis数据库教程

以上是redis的高階特性介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除