redis mysql 解决超卖_Redis 分布式锁解决超卖问题
Redis 分布式鎖解決超賣問題
1,Redis 事物介紹
1. Redis 事物是可以一次執行多個命令, 本質是一組命令的集合.
2. 一個事務中的所有命令都會序列化, 按順序串行化的執行而不會被其他命令插入
作用: 一個隊列中, 一次性, 順序性, 排他性的執行一系列命令
2,multi 指令基本使用
1. 下面指令演示了一個完整的事物過程, 所有指令在 exec 前不執行, 而是緩存在服務器的一個事物隊列中
2. 服務器一旦收到 exec 指令才開始執行事物隊列, 執行完畢后一次性返回所有結果
3. 因為 Redis 是單線程的, 所以不必擔心自己在執行隊列是被打斷, 可以保證這樣的 "原子性"
注: Redis 事物在遇到指令失敗后, 后面的指令會繼續執行# Multi 命令用于標記一個事務塊的開始事務塊內的多條命令會按照先后順序被放進一個隊列當中, 最后由 EXEC 命令原子性 ( atomic ) 地執行
>multi(開始一個Redis事物)
incr books
incr books
>exec(執行事物)
>discard(丟棄事物)
[[emailprotected]~]#Redis-cli
127.0.0.1:6379>multi
OK
127.0.0.1:6379>settest123
QUEUED
127.0.0.1:6379>exec
1)OK
127.0.0.1:6379>gettest
"123"
127.0.0.1:6379>multi
OK
127.0.0.1:6379>settest456
QUEUED
127.0.0.1:6379>discard
OK
127.0.0.1:6379>gettest
"123"
127.0.0.1:6379>
在命令行測試 Redis 事物
#! /usr/bin/env python
# -*- coding: utf-8 -*-
importRedis
r=Redis.Redis(host='127.0.0.1')
pipe=r.pipeline()
pipe.multi()#開啟事務
pipe.set('key2',4)#存儲子命令
pipe.execute()#執行事務
print(r.get('key2'))
使用 python 測試 Redis 事物
注: MySQL 的 rollback 與 Redis 的 discard 的區別
1. MySQL 回滾為 sql 全部成功才執行, 一條 sql 失敗則全部失敗, 執行 rollback 后所有語句造成的影響消失
2. Redis 的 discard 只是結束本次事務, 正確命令造成的影響仍然還在.
1)Redis 如果在一個事務中的命令出現錯誤, 那么所有的命令都不會執行;
2)Redis 如果在一個事務中出現運行錯誤, 那么正確的命令會被執行.
3,watch 指令作用
實質: WATCH 只會在數據被其他客戶端搶先修改了的情況下通知執行命令的這個客戶端 (通過 WatchError 異常) 但不會阻止其他客戶端對數據的修改
1.watch 其實就是 Redis 提供的一種樂觀鎖, 可以解決并發修改問題
2. watch 會在事物開始前盯住一個或多個關鍵變量, 當服務器收到 exec 指令要順序執行緩存中的事物隊列時, Redis 會檢查關鍵變量自 watch 后是否被修改
3. WATCH 只會在數據被其他客戶端搶先修改了的情況下通知執行命令的這個客戶端 (通過 WatchError 異常) 但不會阻止其他客戶端對數據的修改
1.2 setnx(Redis 分布式鎖)
1, 分布式鎖
1. 分布式鎖本質是占一個坑, 當別的進程也要來占坑時發現已經被占, 就會放棄或者稍后重試
2. 占坑一般使用 setnx(set if not exists)指令, 只允許一個客戶端占坑
3. 先來先占, 用完了在調用 del 指令釋放坑>setnxlock:codeholetrue
....dosomething critical....
>dellock:codehole
4. 但是這樣有一個問題, 如果邏輯執行到中間出現異常, 可能導致 del 指令沒有被調用, 這樣就會陷入死鎖, 鎖永遠無法釋放
5. 為了解決死鎖問題, 我們拿到鎖時可以加上一個 expire 過期時間, 這樣即使出現異常, 當到達過期時間也會自動釋放鎖>setnxlock:codeholetrue
>expirelock:codehole5
....dosomething critical....
>dellock:codehole
6. 這樣又有一個問題, setnx 和 expire 是兩條指令而不是原子指令, 如果兩條指令之間進程掛掉依然會出現死鎖
7. 為了治理上面亂象, 在 Redis 2.8 中加入了 set 指令的擴展參數, 使 setnx 和 expire 指令可以一起執行>setlock:codeholetrueex5nx
'''do something'''
>dellock:codehole
1.3 Redis 解決超賣問題
1, 使用 reids 的 watch + multi 指令實現
#! /usr/bin/env python
# -*- coding: utf-8 -*-
importRedis
defsale(rs):
whileTrue:
withrs.pipeline()asp:
try:
p.watch('apple')# 監聽 key 值為 apple 的數據數量改變
count=int(rs.get('apple'))
print('拿取到了蘋果的數量: %d'%count)
p.multi()# 事務開始
ifcount>0:# 如果此時還有庫存
p.set('apple',count-1)
p.execute()# 執行事務
p.unwatch()
break# 當庫存成功減一或沒有庫存時跳出執行循環
exceptExceptionase:# 當出現 watch 監聽值出現修改時, WatchError 異常拋出
print('[Error]: %s'%e)
continue# 繼續嘗試執行
rs=Redis.Redis(host='127.0.0.1',port=6379)# 連接 Redis
rs.set('apple',1000)# # 首先在 Redis 中設置某商品 apple 對應數量 value 值為 1000
sale(rs)
watch+multi 解決超賣問題
1)原理
1. 當用戶購買時, 通過 WATCH 監聽用戶庫存, 如果庫存在 watch 監聽后發生改變, 就會捕獲異常而放棄對庫存減一操作
2. 如果庫存沒有監聽到變化并且數量大于 1, 則庫存數量減一, 并執行任務
2)弊端
1. Redis 在嘗試完成一個事務的時候, 可能會因為事務的失敗而重復嘗試重新執行
2. 保證商品的庫存量正確是一件很重要的事情, 但是單純的使用 WATCH 這樣的機制對服務器壓力過大
2, 使用 reids 的 watch + multi + setnx 指令實現
1)為什么要自己構建鎖
1. 雖然有類似的 SETNX 命令可以實現 Redis 中的鎖的功能, 但他鎖提供的機制并不完整
2. 并且 setnx 也不具備分布式鎖的一些高級特性, 還是得通過我們手動構建
2)創建一個 Redis 鎖
1. 在 Redis 中, 可以通過使用 SETNX 命令來構建鎖: rs.setnx(lock_name, uuid 值)
2. 而鎖要做的事情就是將一個隨機生成的 128 位 UUID 設置位鍵的值, 防止該鎖被其他進程獲取
3)釋放鎖
1. 鎖的刪除操作很簡單, 只需要將對應鎖的 key 值獲取到的 uuid 結果進行判斷驗證
2. 符合條件 (判斷 uuid 值) 通過 delete 在 Redis 中刪除即可, pipe.delete(lockname)
3. 此外當其他用戶持有同名鎖時, 由于 uuid 的不同, 經過驗證后不會錯誤釋放掉別人的鎖
4)解決鎖無法釋放問題
1. 在之前的鎖中, 還出現這樣的問題, 比如某個進程持有鎖之后突然程序崩潰, 那么會導致鎖無法釋放
2. 而其他進程無法持有鎖繼續工作, 為了解決這樣的問題, 可以在獲取鎖的時候加上鎖的超時功能
#! /usr/bin/env python
# -*- coding: utf-8 -*-
importRedis
importuuid
importtime
# 1. 初始化連接函數
defget_conn(host,port=6379):
rs=Redis.Redis(host=host,port=port)
returnrs
# 2. 構建 Redis 鎖
defacquire_lock(rs,lock_name,expire_time=10):
'''
rs: 連接對象
lock_name: 鎖標識
acquire_time: 過期超時時間
return -> False 獲鎖失敗 or True 獲鎖成功
'''
identifier=str(uuid.uuid4())
end=time.time()+expire_time
whiletime.time()
# 當獲取鎖的行為超過有效時間, 則退出循環, 本次取鎖失敗, 返回 False
ifrs.setnx(lock_name,identifier):# 嘗試取得鎖
returnidentifier
time.sleep(.001)
returnFalse
# 3. 釋放鎖
defrelease_lock(rs,lockname,identifier):
'''
rs: 連接對象
lockname: 鎖標識
identifier: 鎖的 value 值, 用來校驗
'''
pipe=rs.pipeline(True)
try:
pipe.watch(lockname)
ifrs.get(lockname).decode()==identifier:# 防止其他進程同名鎖被誤刪
pipe.multi()# 開啟事務
pipe.delete(lockname)
pipe.execute()
returnTrue# 刪除鎖
pipe.unwatch()# 取消事務
exceptExceptionase:
pass
returnFalse# 刪除失敗
'''在業務函數中使用上面的鎖'''
defsale(rs):
start=time.time()# 程序啟動時間
withrs.pipeline()asp:
'''
通過管道方式進行連接
多條命令執行結束, 一次性獲取結果
'''
whileTrue:
lock=acquire_lock(rs,'lock')
ifnotlock:# 持鎖失敗
continue
try:
count=int(rs.get('apple'))# 取量
p.set('apple',count-1)# 減量
p.execute()
print('當前庫存量: %s'%count)
break
finally:
release_lock(rs,'lock',lock)
print('[time]: %.2f'%(time.time()-start))
rs=Redis.Redis(host='127.0.0.1',port=6379)# 連接 Redis
rs.set('apple',1000)# # 首先在 Redis 中設置某商品 apple 對應數量 value 值為 1000
sale(rs)
setnx+watch+multi 解決超賣問題
defacquire_expire_lock(rs,lock_name,expire_time=10,locked_time=10):
'''
rs: 連接對象
lock_name: 鎖標識
acquire_time: 過期超時時間
locked_time: 鎖的有效時間
return -> False 獲鎖失敗 or True 獲鎖成功
'''
identifier=str(uuid.uuid4())
end=time.time()+expire_time
whiletime.time()
# 當獲取鎖的行為超過有效時間, 則退出循環, 本次取鎖失敗, 返回 False
ifrs.setnx(lock_name,identifier):# 嘗試取得鎖
# print('鎖已設置: %s' % identifier)
rs.expire(lock_name,locked_time)
returnidentifier
time.sleep(.001)
returnFalse
優化: 給分布式鎖加超時時間防止死鎖
來源: http://www.bubuko.com/infodetail-3475503.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的redis mysql 解决超卖_Redis 分布式锁解决超卖问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python语言结构_Python语言表
- 下一篇: shell在二级python_在pyth