日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis事物分布式锁

發布時間:2025/3/21 数据库 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis事物分布式锁 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

redis事物介紹

1. redis事物是可以一次執行多個命令,本質是一組命令的集合。

2. 一個事務中的所有命令都會序列化,按順序串行化的執行而不會被其他命令插入

作用:一個隊列中,一次性、順序性、排他性的執行一系列命令

exec指令的作用:

如果關鍵變量被人改動過,exec指令就會返回null回復告知客戶端事物執行失敗,這個時候客戶端會選擇重試

multi 指令基本使用

1. 下面指令演示了一個完整的事物過程,所有指令在exec前不執行,而是緩存在服務器的一個事物隊列中

2. 服務器一旦收到exec指令才開始執行事物隊列,執行完畢后一次性返回所有結果

3. 因為redis是單線程的,所以不必擔心自己在執行隊列是被打斷,可以保證這樣的“原子性”

注:redis事物在遇到指令失敗后,后面的指令會繼續執行

      # Multi 命令用于標記一個事務塊的開始事務塊內的多條命令會按照先后順序被放進一個隊列當中,最后由 EXEC 命令原子性( atomic )地執行 > multi(開始一個redis事物) incr books incr books > exec (執行事物) > discard (丟棄事物)

在命令行 測試redis事物

[root@redis ~]# redis-cli 127.0.0.1:6379> multi OK 127.0.0.1:6379> set test 123 QUEUED 127.0.0.1:6379> exec 1) OK 127.0.0.1:6379> get test "123" 127.0.0.1:6379> multi OK 127.0.0.1:6379> set test 456 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get test "123" 127.0.0.1:6379>

使用Python測試redis事物

#! /usr/bin/env python # -*- coding: utf-8 -*- import redis r = redis.Redis(host='127.0.0.1') pipe = r.pipeline() pipe.multi() #開啟事務 pipe.set('key2', 4) #存儲子命令 pipe.execute() #執行事務 print(r.get('key2'))

注:mysql的rollback與redis的discard的區別

  • mysql回滾為sql全部成功才執行,一條sql失敗則全部失敗,執行rollback后所有語句造成的影響消失
  • redis的discard只是結束本次事務,正確命令造成的影響仍然還在.
    1)redis如果在一個事務中的命令出現錯誤,那么所有的命令都不會執行;
    2)redis如果在一個事務中出現運行錯誤,那么正確的命令會被執行。
  • watch 指令作用

    實質:WATCH 只會在數據被其他客戶端搶先修改了的情況下通知執行命令的這個客戶端(通過 WatchError 異常)但不會阻止其他客戶端對數據的修改

  • watch其實就是redis提供的一種樂觀鎖,可以解決并發修改問題
  • watch會在事物開始前盯住一個或多個關鍵變量,當服務器收到exec指令要順序執行緩存中的事物隊列時,redis會檢查關鍵變量自watch后是否被修改
  • WATCH 只會在數據被其他客戶端搶先修改了的情況下通知執行命令的這個客戶端(通過 WatchError 異常)但不會阻止其他客戶端對數據的修改
  • 分布式鎖

    1. 分布式鎖本質是占一個坑,當別的進程也要來占坑時發現已經被占,就會放棄或者稍后重試

    2. 占坑一般使用 setnx(set if not exists)指令,只允許一個客戶端占坑

    3. 先來先占,用完了在調用del指令釋放坑

    > setnx lock:codehole true .... do something critical .... > del lock:codehole

    4. 但是這樣有一個問題,如果邏輯執行到中間出現異常,可能導致del指令沒有被調用,這樣就會陷入死鎖,鎖永遠無法釋放

    5. 為了解決死鎖問題,我們拿到鎖時可以加上一個expire過期時間,這樣即使出現異常,當到達過期時間也會自動釋放鎖

    > setnx lock:codehole true > expire lock:codehole 5 .... do something critical .... > del lock:codehole

    6. 這樣又有一個問題,setnx和expire是兩條指令而不是原子指令,如果兩條指令之間進程掛掉依然會出現死鎖

    7. 為了治理上面亂象,在redis 2.8中加入了set指令的擴展參數,使setnx和expire指令可以一起執行

    > set lock:codehole true ex 5 nx ''' do something ''' > del lock:codehole

    redis解決超賣問題

    1、使用reids的 watch + multi 指令實現
    watch+multi解決超賣問題

    #! /usr/bin/env python # -*- coding: utf-8 -*- import redis def sale(rs):while True:with rs.pipeline() as p:try:p.watch('apple') # 監聽key值為apple的數據數量改變count = int(rs.get('apple'))print('拿取到了蘋果的數量: %d' % count)p.multi() # 事務開始if count> 0 : # 如果此時還有庫存p.set('apple', count - 1)p.execute() # 執行事務p.unwatch()break # 當庫存成功減一或沒有庫存時跳出執行循環except Exception as e: # 當出現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)

    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. 而其他進程無法持有鎖繼續工作,為了解決這樣的問題,可以在獲取鎖的時候加上鎖的超時功能

    watch + multi + setnx解決超賣問題

    #! /usr/bin/env python # -*- coding: utf-8 -*- import redis import uuid import time# 1.初始化連接函數 def get_conn(host,port=6379):rs = redis.Redis(host=host, port=port)return rs# 2. 構建redis鎖 def acquire_lock(rs, lock_name, expire_time=10):'''rs: 連接對象lock_name: 鎖標識acquire_time: 過期超時時間return -> False 獲鎖失敗 or True 獲鎖成功'''identifier = str(uuid.uuid4())end = time.time() + expire_timewhile time.time() < end:# 當獲取鎖的行為超過有效時間,則退出循環,本次取鎖失敗,返回Falseif rs.setnx(lock_name, identifier): # 嘗試取得鎖return identifiertime.sleep(.001)return False# 3. 釋放鎖 def release_lock(rs, lockname, identifier):'''rs: 連接對象lockname: 鎖標識identifier: 鎖的value值,用來校驗'''pipe = rs.pipeline(True)try:pipe.watch(lockname)if rs.get(lockname).decode() == identifier: # 防止其他進程同名鎖被誤刪pipe.multi() # 開啟事務pipe.delete(lockname)pipe.execute()return True # 刪除鎖pipe.unwatch() # 取消事務except Exception as e:passreturn False # 刪除失敗'''在業務函數中使用上面的鎖''' def sale(rs):start = time.time() # 程序啟動時間with rs.pipeline() as p:'''通過管道方式進行連接多條命令執行結束,一次性獲取結果'''while True:lock = acquire_lock(rs, 'lock')if not lock: # 持鎖失敗continuetry:count = int(rs.get('apple')) # 取量p.set('apple', count-1) # 減量p.execute()print('當前庫存量: %s' % count)breakfinally: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)

    優化:給分布式鎖加超時時間防止死鎖

    def acquire_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_timewhile time.time() < end:# 當獲取鎖的行為超過有效時間,則退出循環,本次取鎖失敗,返回Falseif rs.setnx(lock_name, identifier): # 嘗試取得鎖# print('鎖已設置: %s' % identifier)rs.expire(lock_name, locked_time)return identifiertime.sleep(.001)return False

    來自于原址參考

    總結

    以上是生活随笔為你收集整理的Redis事物分布式锁的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。