mysql 實現分布式鎖


歡迎跳轉到本文的原文鏈接:https://honeypps.com/architect/distribute-lock-based-on-database/

參照連接 https://blog.csdn.net/u013256816/article/details/92854794

概述

在單機時代,雖然不需要分布式鎖,但也面臨過類似的問題,只不過在單機的情況下,如果有多個線程要同時訪問某個共享資源的時候,我們可以采用線程間加鎖的機制,即當某個線程獲取到這個資源后,就立即對這個資源進行加鎖,當使用完資源之后,再解鎖,其它線程就可以接着使用了。例如,在JAVA中,甚至專門提供了一些處理鎖機制的一些API(synchronize/Lock等)。

但是到了分布式系統的時代,這種線程之間的鎖機制,就沒作用了,系統可能會有多份並且部署在不同的機器上,這些資源已經不是在線程之間共享了,而是屬於進程之間共享的資源。

因此,為了解決這個問題,我們就必須引入分布式鎖。分布式鎖是指在分布式的部署環境下,通過鎖機制來讓多客戶端互斥的對共享資源進行訪問。

目前比較常見的分布式鎖實現方案有以下幾種:

  1. 基於數據庫,如MySQL
  2. 基於緩存,如Redis
  3. 基於Zookeeper、etcd等

我們在討論使用分布式鎖的時候往往首先排除掉基於數據庫的方案,本能的會覺得這個方案不夠“高級”。從性能的角度考慮,基於數據庫的方案性能確實不夠優異,整體性能對比:緩存 > Zookeeper、etcd > 數據庫。也有人提出基於數據庫的方案問題很多,不太可靠。筆者認為采用哪種方案是要基於使用場景來看的,選擇哪種方案,合適最重要。

我這里引用一下之前文章中的一個應用場景——分配任務場景。在這個場景中,由於是公司的業務后台系統,主要是用於審核人員的審核工作,並發量並不是很高,而且任務的分配規則設計成了通過審核人員每次主動的請求拉取,然后服務端從任務池中隨機的選取任務進行分配。這個場景看到這里你會覺得比較單一,但是實際的分配過程中,由於涉及到了按用戶聚類的問題,所以要比我描述的復雜,但是這里為了說明問題,大家可以把問題簡單化理解。那么在使用過程中,主要是為了避免同一個任務同時被兩個審核人員獲取到的問題。在這個場景下使用基於數據庫的方案就比較合理。

再補充一下,比如某一個服務它下游依賴數據庫來做一些數據的讀寫操作,模型如下圖所示:
在這里插入圖片描述
一般服務也是多實例部署,如果多個實例需要操作同一份數據的時候(比如前面所說的同一個任務同時被兩個審核人員獲取到的問題),自然而然的引入了分布式鎖。不過此時,我們並沒有采用數據庫的方案,而是引入了Redis,模型如下圖所示:
在這里插入圖片描述
引入Redis之后,正向的收益我就不贅述了,反向的收益是增加了系統的復雜度,對於整個服務而言,還需要多考慮1和2失效的情況。1失效是指服務模塊與Redis的交互出現了異常,這種異常不單是指無法通信的異常,也有可能是服務模塊發送請求只Redis的過程中或者Redis響應服務模塊的過程中出現的異常,整體服務需要考慮這種情況:是重試、丟棄還是采取其他措施;2失效是指Redis本身出現了異常。數據鏈路一旦變長,系統復雜度一旦變大,在出現問題的時候會阻礙故障排查以及服務恢復,從而使得服務的整體可用性下調。

反觀,如果采用數據庫的方案,那么就可以省去了這部分的復雜度,如果數據庫的方案能滿足當下場景以及可視范圍內的未來擴展,為什么還要平白地增加系統復雜度呢?大家要根據具體業務場景選擇合適的技術方案,而不是隨便找一個足夠復雜、足夠新潮的技術方案來解決業務問題。

下面我們來了解一下基於數據庫(MySQL)的方案,一般分為3類:基於表記錄、樂觀鎖和悲觀鎖。

基於表記錄

要實現分布式鎖,最簡單的方式可能就是直接創建一張鎖表,然后通過操作該表中的數據來實現了。當我們想要獲得鎖的時候,就可以在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄。

為了更好的演示,我們先創建一張數據庫表,參考如下:

class Flock(models.Model):
    id = models.AutoField(primary_key=True)
    user_id=models.IntegerField()  # 設置唯一索引
    class Meta:
        db_table='flock'
from django.db import connection
#鎖的使用
class Testlock(APIView):
    def post(self,request):
        uid=jwt_end(request.GET.get('jwt',None))
        number=request.GET.get('number')
        res=User.objects.get(pk=uid)
        if res.balance>0:
            with connection.cursor() as a:
                a.execute("INSERT INTO flock(user_id) VALUES (%s)"%res.id)
            # try:
            #     Flock.objects.create(user_id=res.id)
            # except Exception as e:
            #     return Response({'msg':"無法操作"})
            with connection.cursor() as c:
                c.execute('update user set balance=balance-%s where id=%s'%(int(number),res.id))

            with connection.cursor() as w:
                w.execute("DELETE FROM flock WHERE user_id=%s"%res.id)
            return Response({'msg':"ok"})
        else:
            return Response({'msg':"錢包為空"})

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM