成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

怎么用Go和Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖

這篇文章主要介紹了怎么用Go和redis實(shí)現(xiàn)分布式互斥鎖和紅鎖的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇怎么用Go和Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

創(chuàng)新互聯(lián)公司專(zhuān)注于福清網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供福清營(yíng)銷(xiāo)型網(wǎng)站建設(shè),福清網(wǎng)站制作、福清網(wǎng)頁(yè)設(shè)計(jì)、福清網(wǎng)站官網(wǎng)定制、成都小程序開(kāi)發(fā)服務(wù),打造福清網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供福清網(wǎng)站排名全網(wǎng)營(yíng)銷(xiāo)落地服務(wù)。

互斥鎖

Redis里有一個(gè)設(shè)置如果不存在的命令,我們可以通過(guò)這個(gè)命令來(lái)實(shí)現(xiàn)互斥鎖功能,在Redis官方文檔里面推薦的標(biāo)準(zhǔn)實(shí)現(xiàn)方式是SET resource_name my_random_value NX PX 30000這串命令,其中:

  • resource_name表示要鎖定的資源

  • NX表示如果不存在則設(shè)置

  • PX 30000表示過(guò)期時(shí)間為30000毫秒,也就是30秒

  • my_random_value這個(gè)值在所有的客戶(hù)端必須是唯一的,所有同一key的鎖競(jìng)爭(zhēng)者這個(gè)值都不能一樣。

值必須是隨機(jī)數(shù)主要是為了更安全的釋放鎖,釋放鎖的時(shí)候使用腳本告訴Redis:只有key存在并且存儲(chǔ)的值和我指定的值一樣才能告訴我刪除成功,避免錯(cuò)誤釋放別的競(jìng)爭(zhēng)者的鎖。

由于涉及到兩個(gè)操作,因此我們需要通過(guò)Lua腳本保證操作的原子性:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

舉個(gè)不用Lua腳本的例子:客戶(hù)端A取得資源鎖,但是緊接著被一個(gè)其他操作阻塞了,當(dāng)客戶(hù)端A運(yùn)行完畢其他操作后要釋放鎖時(shí),原來(lái)的鎖早已超時(shí)并且被Redis自動(dòng)釋放,并且在這期間資源鎖又被客戶(hù)端B再次獲取到。

因?yàn)榕袛嗪蛣h除是兩個(gè)操作,所以有可能A剛判斷完鎖就過(guò)期自動(dòng)釋放了,然后B就獲取到了鎖,然后A又調(diào)用了Del,導(dǎo)致把B的鎖給釋放了。

TryLock和Unlock實(shí)現(xiàn)

TryLock其實(shí)就是使用SET resource_name my_random_value NX PX 30000加鎖,這里使用UUID作為隨機(jī)值,并且在加鎖成功時(shí)把隨機(jī)值返回,這個(gè)隨機(jī)值會(huì)在Unlock時(shí)使用;

Unlock解鎖邏輯就是執(zhí)行前面說(shuō)到的lua腳本。

func (l *Lock) TryLock(ctx context.Context) error {
   success, err := l.client.SetNX(ctx, l.resource, l.randomValue, ttl).Result()
   if err != nil {
      return err
   }
   // 加鎖失敗
   if !success {
      return ErrLockFailed
   }
   // 加鎖成功
   l.randomValue = randomValue
   return nil
}

func (l *Lock) Unlock(ctx context.Context) error {
   return l.script.Run(ctx, l.client, []string{l.resource}, l.randomValue).Err()
}
Lock實(shí)現(xiàn)

Lock是阻塞的獲取鎖,因此在加鎖失敗的時(shí)候,需要重試。當(dāng)然也可能出現(xiàn)其他異常情況(比如網(wǎng)絡(luò)問(wèn)題,請(qǐng)求超時(shí)等),這些情況則直接返回error。

步驟如下:

  • 嘗試加鎖,加鎖成功直接返回

  • 加鎖失敗則不斷循環(huán)嘗試加鎖直到成功或出現(xiàn)異常情況

func (l *Lock) Lock(ctx context.Context) error {
  // 嘗試加鎖
  err := l.TryLock(ctx)
  if err == nil {
    return nil
  }
  if !errors.Is(err, ErrLockFailed) {
    return err
  }
  // 加鎖失敗,不斷嘗試
  ticker := time.NewTicker(l.tryLockInterval)
  defer ticker.Stop()
  for {
    select {
    case <-ctx.Done():
      // 超時(shí)
      return ErrTimeout
    case <-ticker.C:
      // 重新嘗試加鎖
      err := l.TryLock(ctx)
      if err == nil {
        return nil
      }
      if !errors.Is(err, ErrLockFailed) {
        return err
      }
    }
  }
}

實(shí)現(xiàn)看門(mén)狗機(jī)制

我們前面的例子中提到的互斥鎖有一個(gè)小問(wèn)題,就是如果持有鎖客戶(hù)端A被阻塞,那么A的鎖可能會(huì)超時(shí)被自動(dòng)釋放,導(dǎo)致客戶(hù)端B提前獲取到鎖。

為了減少這種情況的發(fā)生,我們可以在A持有鎖期間,不斷地延長(zhǎng)鎖的過(guò)期時(shí)間,減少客戶(hù)端B提前獲取到鎖的情況,這就是看門(mén)狗機(jī)制。

當(dāng)然,這沒(méi)辦法完全避免上述情況的發(fā)生,因?yàn)槿绻蛻?hù)端A獲取鎖之后,剛好與Redis的連接關(guān)閉了,這時(shí)候也就沒(méi)辦法延長(zhǎng)超時(shí)時(shí)間了。

看門(mén)狗實(shí)現(xiàn)

加鎖成功時(shí)啟動(dòng)一個(gè)線(xiàn)程,不斷地延長(zhǎng)鎖地過(guò)期時(shí)間;在Unlock時(shí)關(guān)閉看門(mén)狗線(xiàn)程。

看門(mén)狗流程如下:

  • 加鎖成功,啟動(dòng)看門(mén)狗

  • 看門(mén)狗線(xiàn)程不斷延長(zhǎng)鎖的過(guò)程時(shí)間

  • 解鎖,關(guān)閉看門(mén)狗

func (l *Lock) startWatchDog() {
  ticker := time.NewTicker(l.ttl / 3)
  defer ticker.Stop()
  for {
    select {
    case <-ticker.C:
      // 延長(zhǎng)鎖的過(guò)期時(shí)間
      ctx, cancel := context.WithTimeout(context.Background(), l.ttl/3*2)
      ok, err := l.client.Expire(ctx, l.resource, l.ttl).Result()
      cancel()
      // 異常或鎖已經(jīng)不存在則不再續(xù)期
      if err != nil || !ok {
        return
      }
    case <-l.watchDog:
      // 已經(jīng)解鎖
      return
    }
  }
}

TryLock:?jiǎn)?dòng)看門(mén)狗

func (l *Lock) TryLock(ctx context.Context) error {
  success, err := l.client.SetNX(ctx, l.resource, l.randomValue, l.ttl).Result()
  if err != nil {
    return err
  }
  // 加鎖失敗
  if !success {
    return ErrLockFailed
  }
  // 加鎖成功,啟動(dòng)看門(mén)狗
  go l.startWatchDog()
  return nil
}

Unlock:關(guān)閉看門(mén)狗

func (l *Lock) Unlock(ctx context.Context) error {
  err := l.script.Run(ctx, l.client, []string{l.resource}, l.randomValue).Err()
  // 關(guān)閉看門(mén)狗
  close(l.watchDog)
  return err
}

紅鎖

由于上面的實(shí)現(xiàn)是基于單Redis實(shí)例,如果這個(gè)唯一的實(shí)例掛了,那么所有請(qǐng)求都會(huì)因?yàn)槟貌坏芥i而失敗,為了提高容錯(cuò)性,我們可以使用多個(gè)分布在不同機(jī)器上的Redis實(shí)例,并且只要拿到其中大多數(shù)節(jié)點(diǎn)的鎖就能加鎖成功,這就是紅鎖算法。它其實(shí)也是基于上面的單實(shí)例算法的,只是我們需要同時(shí)對(duì)多個(gè)Redis實(shí)例獲取鎖。

加鎖實(shí)現(xiàn)

在加鎖邏輯里,我們主要是對(duì)每個(gè)Redis實(shí)例執(zhí)行SET resource_name my_random_value NX PX 30000獲取鎖,然后把成功獲取鎖的客戶(hù)端放到一個(gè)channel里(這里因?yàn)槭嵌嗑€(xiàn)程并發(fā)獲取鎖,使用slice可能有并發(fā)問(wèn)題),同時(shí)使用sync.WaitGroup等待所有獲取鎖操作結(jié)束。

然后判斷成功獲取到的鎖的數(shù)量是否大于一半,如果沒(méi)有得到一半以上的鎖,說(shuō)明加鎖失敗,釋放已經(jīng)獲得的鎖。

如果加鎖成功,則啟動(dòng)看門(mén)狗延長(zhǎng)鎖的過(guò)期時(shí)間。

func (l *RedLock) TryLock(ctx context.Context) error {
  randomValue := gofakeit.UUID()
  var wg sync.WaitGroup
  wg.Add(len(l.clients))
  // 成功獲得鎖的Redis實(shí)例的客戶(hù)端
  successClients := make(chan *redis.Client, len(l.clients))
  for _, client := range l.clients {
    go func(client *redis.Client) {
      defer wg.Done()
      success, err := client.SetNX(ctx, l.resource, randomValue, ttl).Result()
      if err != nil {
        return
      }
      // 加鎖失敗
      if !success {
        return
      }
      // 加鎖成功,啟動(dòng)看門(mén)狗
      go l.startWatchDog()
      successClients <- client
    }(client)
  }
  // 等待所有獲取鎖操作完成
  wg.Wait()
  close(successClients)
  // 如果成功加鎖得客戶(hù)端少于客戶(hù)端數(shù)量的一半+1,表示加鎖失敗
  if len(successClients) < len(l.clients)/2+1 {
    // 就算加鎖失敗,也要把已經(jīng)獲得的鎖給釋放掉
    for client := range successClients {
      go func(client *redis.Client) {
        ctx, cancel := context.WithTimeout(context.Background(), ttl)
        l.script.Run(ctx, client, []string{l.resource}, randomValue)
        cancel()
      }(client)
    }
    return ErrLockFailed
  }

  // 加鎖成功,啟動(dòng)看門(mén)狗
  l.randomValue = randomValue
  l.successClients = nil
  for successClient := range successClients {
    l.successClients = append(l.successClients, successClient)
  }

  return nil
}
看門(mén)狗實(shí)現(xiàn)

我們需要延長(zhǎng)所有成功獲取到的鎖的過(guò)期時(shí)間。

func (l *RedLock) startWatchDog() {
  l.watchDog = make(chan struct{})
  ticker := time.NewTicker(resetTTLInterval)
  defer ticker.Stop()
  for {
    select {
    case <-ticker.C:
      // 延長(zhǎng)鎖的過(guò)期時(shí)間
      for _, client := range l.successClients {
        go func(client *redis.Client) {
          ctx, cancel := context.WithTimeout(context.Background(), ttl-resetTTLInterval)
          client.Expire(ctx, l.resource, ttl)
          cancel()
        }(client)
      }
    case <-l.watchDog:
      // 已經(jīng)解鎖
      return
    }
  }
}
解鎖實(shí)現(xiàn)

我們需要解鎖所有成功獲取到的鎖。

func (l *RedLock) Unlock(ctx context.Context) error {
   for _, client := range l.successClients {
      go func(client *redis.Client) {
         l.script.Run(ctx, client, []string{l.resource}, l.randomValue)
      }(client)
   }
   // 關(guān)閉看門(mén)狗
   close(l.watchDog)
   return nil
}

關(guān)于“怎么用Go和Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“怎么用Go和Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

新聞標(biāo)題:怎么用Go和Redis實(shí)現(xiàn)分布式互斥鎖和紅鎖
分享路徑:http://jinyejixie.com/article20/gpgdco.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)云服務(wù)器、網(wǎng)站導(dǎo)航、網(wǎng)站營(yíng)銷(xiāo)網(wǎng)站設(shè)計(jì)公司、做網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)
长岛县| 中江县| 诸城市| 习水县| 文化| 准格尔旗| 连平县| 洪江市| 绩溪县| 如皋市| 宜兰县| 玉田县| 南川市| 即墨市| 东丰县| 安化县| 肃宁县| 衡山县| 连江县| 华蓥市| 侯马市| 乐业县| 河西区| 弋阳县| 雷州市| 贵阳市| 仲巴县| 柞水县| 弥渡县| 堆龙德庆县| 荥阳市| 绍兴市| 鄂尔多斯市| 碌曲县| 巴彦淖尔市| 黑龙江省| 泰安市| 阳新县| 新化县| 武城县| 武宁县|