分散式服務資料一致性-redis篇

Yoger發表於2022-02-11

go-redis分散式鎖:github.com/go-redsync/redsync

client := goredislib.NewClient(&goredislib.Options{
   Addr: "10.211.55.6:6379",
})
pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
rs := redsync.New(pool)

//根據需求設定鎖名稱
mutexName := "goods-1"
var wg sync.WaitGroup
wg.Add(20)
//根據需求設定鎖
mutexName := "goods-1"
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
   go func() {
      defer wg.Done()
      var goods Goods
      mutex := rs.NewMutex(mutexName)
      fmt.Println("開始獲取鎖")
      if err := mutex.Lock(); err != nil {
         fmt.Println("獲取鎖異常")
         panic(err)
      }
      fmt.Println("獲取鎖成功")
      db.Where(Goods{ProductId: 1}).First(&goods)
      result := db.Model(&Goods{}).Where("product_id=?", 1).Updates(Goods{Inventory: goods.Inventory - 1})
      if result.RowsAffected == 0 {
         fmt.Println("更新失敗")
      }
      fmt.Println("開始釋放鎖")
      if ok, err := mutex.Unlock(); !ok || err != nil {
         panic("unlock failed")
      }
      fmt.Println("鎖釋放成功")
   }()
}
wg.Wait()

redsync原始碼解讀

使用Redis Setnx 命令:

在指定的 key 不存在時,為 key 設定指定的值。設定成功,返回 1 。 設定失敗,返回 0 。(將獲取和設定值變成原子性操作)

redis 127.0.0.1:6379> SETNX KEY_NAME VALUE

設定過期時間:

避免執行過程服務掛掉,釋放鎖失敗,出現死鎖。

func (m *Mutex) acquire(ctx context.Context, pool redis.Pool, value string) (bool, error) {
   conn, err := pool.Get(ctx)
   if err != nil {
      return false, err
   }
   defer conn.Close()
   reply, err := conn.SetNX(m.name, value, m.expiry)  //過期時間為8秒
   if err != nil {
      return false, err
   }
   return reply, nil
}

業務還沒有執行完,時間就過期了?

1.過期前重新整理一下過期時間
2.需要自己啟動協程完成延時工作,避免服務hung住一直申請延長時間,導致其他服務拿不到鎖

var touchScript = redis.NewScript(1, `
   if redis.call("GET", KEYS[1]) == ARGV[1] then
      return redis.call("PEXPIRE", KEYS[1], ARGV[2])
   else
      return 0
   end
`)

分散式鎖需要解決的問題:–lua指令碼實現

1.互斥性 -setnx
2.避免死鎖 -過期時間
3.安全性 -鎖只能被持有者刪除,不能被其他使用者刪除。透過value值去判斷,只有當前g知道value的值,刪除的時候取出值對比。

Redlock(紅鎖) 演算法

在分散式版本的演算法裡我們假設我們有N個Redis master節點,這些節點都是完全獨立的,我們不用任何複製或者其他隱含的分散式協調演算法。我們已經描述瞭如何在單節點環境下安全地獲取和釋放鎖。因此我們理所當然地應當用這個方法在每個單節點裡來獲取和釋放鎖。在我們的例子裡面我們把N設成5,這個數字是一個相對比較合理的數值,因此我們需要在不同的計算機或者虛擬機器上執行5個master節點來保證他們大多數情況下都不會同時當機。一個客戶端需要做如下操作來獲取鎖:
1.獲取當前時間(單位是毫秒)。
2.輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裡,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點。
3.客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裡是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認為是獲取成功了。
4.如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。
5.如果鎖獲取失敗了,不管是因為獲取成功的鎖不超過一半(N/2+1)還是因為總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認為沒有獲取成功的鎖。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章