> **Redis - 事務及鎖應用**
## 說明 ##
**Redis 中也存在簡單的事務處理。并且利用watch指令可以實現樂觀鎖的相關操作,常用于秒殺場景中。**
----------
##事務的概念##
Redis 事務的本質是一組命令的集合。事務支持一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序串行化執行隊列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
總結說:redis事務就是一次性、順序性、排他性的執行一個隊列中的一系列命令。
##相關命令##
watch key1 key2 ... : 監視一或多個key,如果在事務執行之前,被監視的key被其他命令改動,則事務被打斷 ( 類似樂觀鎖 )
multi : 標記一個事務塊的開始( queued )
exec : 執行所有事務塊的命令 ( 一旦執行exec后,之前加的監控鎖都會被取消掉 )
discard : 取消事務,放棄事務塊中的所有命令
unwatch : 取消watch對所有key的監控
##與mysql事務的區別##
redis 支持簡單的事務,以下是跟mysql的簡單區別。
![請輸入圖片描述][1]
Discard 是放棄本次事務的意思。跟MySQL回滾是有些不一樣的
當出現錯誤語法時,exec執行也會有問題。整個事務對列不會執行。
當操作語法正確,但操作類型錯誤時,整個事務隊列還是會被執行,只有操作類型錯誤的那個語句不會執行。所以跟mysql的回滾機制不一樣,日常開發中要注意
##事務簡單實現##
<h5>正常使用</h5>
1用戶有1000塊錢,2用戶有500塊錢,1向2轉賬操作,實現1減100,2加100。
127.0.0.1:6379> set 1 1000
OK
127.0.0.1:6379> set 2 500
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby 1 100
QUEUED
127.0.0.1:6379> incrby 2 100
QUEUED
127.0.0.1:6379> exec
1) (integer) 900
2) (integer) 600
<h5>錯誤語法</h5>
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby 1 100
QUEUED
127.0.0.1:6379> incrby1 2 100
(error) ERR unknown command 'incrby1'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
當出現錯誤語法時,exec執行也會有問題。
<h5>語法正確,對象錯誤</h5>
127.0.0.1:6379> decrby 1 100
QUEUED
127.0.0.1:6379> sadd 2 100
QUEUED
127.0.0.1:6379> set 3 300
QUEUED
127.0.0.1:6379> exec
1) (integer) 700
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
當操作語法正確,但操作類型錯誤時,整個事務隊列還是會被執行,只有操作類型錯誤的那個語句不會執行。所以跟mysql的回滾機制不一樣,日常開發中要注意。
##鎖機制##
Redis 中提供了WATCH命令用于監控數據的變化,當數據產生變化時,在此之后的事務都將會被取消,這種特性特別適合用來用作樂觀鎖的實現。
WATCH:監控一個或者多個key,如果這些key在提交事務(EXEC)之前被其他用戶修改過,那么事務將執行失敗,需要重新獲取最新數據重頭操作(類似于樂觀鎖)。
UNWATCH:取消WATCH命令對多有key的監控,所有監控鎖將會被取消。
##鎖機制演示##
<h5>正常操作</h5>
模擬將余額100提現20到提現賬戶。
監聽的數據沒有變化時,事務可以正常提交。
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby new_balance 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
<h5>并發演示</h5>
此時另外一個進程模擬新的余額到賬
監聽的數據發生了變化,事務不可以提交。
1.進行轉賬操作
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby new_balance 20
2.此時又有余額到賬
在另外一個窗口中模擬到賬
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> incrby balance 100
(integer) 180
127.0.0.1:6379>
3.事務不可以提交,發現數據發生了變化
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get balance
"180"
<h5>整體的操作步驟</h5>
![請輸入圖片描述][2]
##基于樂觀鎖的秒殺場景簡單實現##
<h5>設置秒殺商品</h5>
設置秒殺商品,模擬代理商創建秒殺商品,添加商品1為秒殺商品,秒殺數量為10,秒殺價格為100
sk:h:1 gid 1 num 10 price 100 //秒殺商品Hash數據
Redis::hset("sk:h:1","gid","1"); //秒殺商品id
Redis::hset("sk:h:1","gum","10");//秒殺數量
Redis::hset("sk:h:1","price","100");//秒殺金額
----------
對應在redis里的數據
127.0.0.1:6379> keys *
1) "sk:h:1"
127.0.0.1:6379> hgetall sk:h:1
1) "gid"
2) "1"
3) "gum"
4) "10"
5) "price"
6) "100"
<h5>對列+樂觀鎖實現秒殺</h5>
//監聽已搶購的數量
Redis::watch("sk:1:num"); //已經秒殺完的商品數量
$skNum = Redis::hget("sk:h:1",'gum'); //秒殺商品Hash信息
$isSkNum = (int)Redis::get("sk:1:num");
if($isSkNum < $skNum ){
$uid = $this->rand(5);//隨機生成用戶id
// 暫時用setnx 跟 expire 處理限購 問題是并發情況下會不止10個人進來,但是不影響限購啊
// 沒有考慮到更好的解決方案,先這么處理吧
// Redis::set("sk:su:1", 1 , 'NX', 'EX', "1000"); 不清楚predis下set 同時設置 NX和EX為什么老是不生效,暫時用下邊的方法處理
if(Redis::setnx("sk:su:".$uid,$uid)){
Redis::expire("sk:su:".$uid,10); //設置過期時間,保證10秒內一個用戶只能秒殺成功一次
}else{
Rddis::incr('fail');
echo "10秒內允許搶購一次。";
}
//上述代碼不能放在multi之內。 否則if(Redis::setnx("sk:su:".$uid,$uid)) 會報錯
//放在multi當中,相當于未執行,結果不會返回,所以會一直報錯
Redis::multi();
Redis::incr('sk:1:num');
Redis::lpush("sk:l:1",$uid);
Redis::exec();
}else{
Redis::incr('fail');
echo "不好意思,秒殺已經結束了。";
}
}
----------
//php生成不重復隨機字符串
//https://www.cnblogs.com/gorey/p/5647664.html
function rand($len)
{
$chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$string=time();
for(;$len>=1;$len--)
{
$position=rand()%strlen($chars);
$position2=rand()%strlen($string);
$string=substr_replace($string,substr($chars,$position,1),$position2,0);
}
return $string;
}
<h5>ab 壓測</h5>
項目在自己本地的電腦上,就簡單的模擬了以下。
1000 個請求100的并發量。
C:\Users\Depp\Desktop\Apache24\bin>ab -n 1000 -c 100 http://blog.com/admin/redis
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking blog.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests
Server Software: nginx/1.15.11
Server Hostname: blog.com
Server Port: 80
Document Path: /admin/redis
Document Length: 0 bytes
Concurrency Level: 100
Time taken for tests: 20.847 seconds
Complete requests: 1000
Failed requests: 172
(Connect: 0, Receive: 0, Length: 172, Exceptions: 0)
Non-2xx responses: 297
Total transferred: 973697 bytes
HTML transferred: 27176 bytes
Requests per second: 47.97 [#/sec] (mean)
Time per request: 2084.699 [ms] (mean)
Time per request: 20.847 [ms] (mean, across all concurrent requests)
Transfer rate: 45.61 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.4 0 1
Processing: 155 1967 1724.6 991 5855
Waiting: 145 1967 1724.6 990 5855
Total: 155 1967 1724.6 991 5855
Percentage of the requests served within a certain time (ms)
50% 991
66% 2467
75% 3971
80% 4222
90% 4702
95% 4836
98% 5154
99% 5339
100% 5855 (longest request)
<h5>搶購結果</h5>
127.0.0.1:6379> keys *
1) "sk:h:1" //以Hash類型存儲的秒殺商品信息
2) "fail" //沒有搶購到的人 實際場景中可以不用記錄
3) "sk:l:1" //搶購到的list用戶隊列
4) "sk:1:num" //秒殺商品的總庫存 之后清楚或者過期都可以
----------
127.0.0.1:6379> get sk:1:num
"10"
127.0.0.1:6379> lrange sk:l:1 0 -1
1) "t106A0502910239"
2) "160A50HR21k23R9"
3) "160i7O502f12Y39"
4) "160j5021W23UzZ9"
5) "Wd160c5i0212539"
6) "1Pw60I502Ae1239"
7) "146050RgG213239"
8) "1605p0aL212p3u9"
9) "16050wyAa2123Y9"
10) "16050fi2012f3j9"
## 結尾 ##
<p style="background-image: -webkit-linear-gradient(left, #3498db, #f47920 10%, #d71345 20%, #f7acbc 30%,#ffd400 40%, #3498db 50%, #f47920 60%, #d71345 70%, #f7acbc 80%, #ffd400 90%, #3498db);color: transparent;-webkit-text-fill-color: transparent;-webkit-background-clip: text;text-align:center;">
心如花木,向陽而生。
</p>
[1]: https://blog.zxliu.cn/usr/uploads/2020/11/1618088135.png
[2]: https://blog.zxliu.cn/usr/uploads/2020/11/3034037858.png