自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

手動(dòng)擼一個(gè) Redis 分布式鎖

數(shù)據(jù)庫(kù) Redis
這個(gè)代碼,其實(shí)是我很久之前寫(xiě)的,因?yàn)楫?dāng)時(shí) Go 沒(méi)有開(kāi)源的分布式鎖,但是我又需要通過(guò)單機(jī)去執(zhí)行某個(gè)任務(wù),所以就自己手動(dòng)擼了一個(gè),后來(lái)在線(xiàn)上跑了 2 年,一直都沒(méi)有問(wèn)題。

大家好呀,我是樓仔。

今天第一天開(kāi)工,收拾心情,又要開(kāi)始好好學(xué)習(xí),好好工作了。

對(duì)于使用 Java 的小伙伴,其實(shí)我們完全不用手動(dòng)擼一個(gè)分布式鎖,直接使用 Redisson 就行。

但是因?yàn)檫@些封裝好的組建,讓我們?cè)絹?lái)越懶。

我們使用一些封裝好的開(kāi)源組建時(shí),可以了解其中的原理,或者自己動(dòng)手寫(xiě)一個(gè),可以更好提升你的技術(shù)水平。

今天我就教大家用原生的 Redis,手動(dòng)擼一個(gè) Redis 分布式鎖,很有意思。

01 問(wèn)題引入

其實(shí)通過(guò) Redis 實(shí)現(xiàn)分布式鎖,經(jīng)常會(huì)有面試官會(huì)問(wèn),很多同學(xué)都知道用 SetNx() 去獲取鎖,解決并發(fā)問(wèn)題。

SetNx() 是什么?我簡(jiǎn)單解答一下。

Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在時(shí),為 key 設(shè)置指定的值。

對(duì)于下面 2 種問(wèn)題,你知道如何解決么?

  • 如果獲取鎖的機(jī)器掛掉,如何處理?
  • 當(dāng)鎖超時(shí)時(shí),A、B 兩個(gè)線(xiàn)程同時(shí)獲取鎖,可能導(dǎo)致鎖被同時(shí)獲取,如何解決?

這個(gè)就是我們實(shí)現(xiàn) Redis 分布式鎖時(shí),需要重點(diǎn)解決的 2 個(gè)問(wèn)題。

02 理論知識(shí)

剛才說(shuō)過(guò),通過(guò) SetNx() 去獲取鎖,可以解決并發(fā)問(wèn)題。

當(dāng)獲取到鎖,處理完業(yè)務(wù)邏輯后,會(huì)將鎖釋放。

圖片圖片

但當(dāng)機(jī)器宕機(jī),或者重啟時(shí),沒(méi)有執(zhí)行 Del() 刪除鎖操作,會(huì)導(dǎo)致鎖一直沒(méi)有釋放。

所以,我們還需要記錄鎖的超時(shí)時(shí)間,判斷鎖是否超時(shí)。

圖片圖片

這里我們通過(guò) GetKey() 獲取鎖的超時(shí)時(shí)間 A,通過(guò)和當(dāng)前時(shí)間比較,判斷鎖是否超時(shí)。

如果鎖未超時(shí),直接返回,如果鎖超時(shí),重新設(shè)置鎖的超時(shí)時(shí)間,成功獲取鎖。

還有其它問(wèn)題么?當(dāng)然!

因?yàn)樵诓l(fā)場(chǎng)景下,會(huì)存在 A、B 兩個(gè)線(xiàn)程同時(shí)執(zhí)行 SetNx(),導(dǎo)致兩個(gè)線(xiàn)程同時(shí)獲取到鎖。

那如何解決呢?將 SetNx() 用 GetSet() 替換。

圖片圖片

GetSet() 是什么?我簡(jiǎn)單解答一下。

Redis Getset 命令用于設(shè)置指定 key 的值,并返回 key 的舊值。

這里不太好理解,我舉個(gè)例子。

假如 A、B 兩個(gè)線(xiàn)程,A 先執(zhí)行,B 后執(zhí)行:

  • 對(duì)于線(xiàn)程 A 和 B,通過(guò) GetKey 獲取的超時(shí)時(shí)間都是 T1 = 100;
  • 對(duì)于線(xiàn)程 A,將超時(shí)時(shí)間 Ta = 200 通過(guò) GetSet() 設(shè)置,返回 T2 = 100,此時(shí)滿(mǎn)足條件 “T1 == T2”,獲取鎖成功;
  • 對(duì)于線(xiàn)程 B,將超時(shí)時(shí)間 Tb = 201 通過(guò) GetSet() 設(shè)置,由于鎖超時(shí)時(shí)間已經(jīng)被 A 重新設(shè)置,所以返回 T2 = 200,此時(shí)不滿(mǎn)足條件 “T1 == T2”,獲取鎖失敗。

可能有同學(xué)會(huì)繼續(xù)問(wèn),之前設(shè)置的超時(shí)是 Ta = 200,現(xiàn)在變成了 Tb = 201,延長(zhǎng)或縮短了鎖的超時(shí)時(shí)間,不會(huì)有問(wèn)題么?

其實(shí)在現(xiàn)實(shí)并發(fā)場(chǎng)景中,能走到這一步,基本是“同時(shí)”進(jìn)來(lái)的,兩者的時(shí)間差非常小,可以忽略此影響。

03 代碼實(shí)戰(zhàn)

這里給出 Go 代碼,注釋都寫(xiě)得非常詳細(xì),即使你不會(huì) Go,讀注釋也能讀懂。

// 獲取分布式鎖,需要考慮以下情況:
// 1. 機(jī)器A獲取到鎖,但是在未釋放鎖之前,機(jī)器掛掉或者重啟,會(huì)導(dǎo)致其它機(jī)器全部hang住,這時(shí)需要根據(jù)鎖的超時(shí)時(shí)間,判斷該鎖是否需要重置;
// 2. 當(dāng)鎖超時(shí)時(shí),需要考慮兩臺(tái)機(jī)器同時(shí)去獲取該鎖,需要通過(guò)GETSET方法,讓先執(zhí)行該方法的機(jī)器獲取鎖,另外一臺(tái)繼續(xù)等待。
func GetDistributeLock(key string, expireTime int64) bool {

 currentTime := time.Now().Unix()
 expires := currentTime + expireTime
 redisAlias := "jointly"

 // 1.獲取鎖,并將value值設(shè)置為鎖的超時(shí)時(shí)間
 redisRet, err := redis.SetNx(redisAlias, key, expires)
 if nil == err && utils.MustInt64(1) == redisRet {
  // 成功獲取到鎖
  return true
 }

 // 2.當(dāng)獲取到鎖的機(jī)器突然重啟&掛掉時(shí),就需要判斷鎖的超時(shí)時(shí)間,如果鎖超時(shí),新的機(jī)器可以重新獲取鎖
 // 2.1 獲取鎖的超時(shí)時(shí)間
 currentLockTime, err := redis.GetKey(redisAlias, key)
 if err != nil {
  return false
 }

 // 2.2 當(dāng)"鎖的超時(shí)時(shí)間"大于等于"當(dāng)前時(shí)間",證明鎖未超時(shí),直接返回
 if utils.MustInt64(currentLockTime) >= currentTime {
  return false
 }

 // 2.3 將最新的超時(shí)時(shí)間,更新到鎖的value值,并返回舊的鎖的超時(shí)時(shí)間
 oldLockTime, err := redis.GetSet(redisAlias, key, expires)
 if err != nil {
  return false
 }

 // 2.4 當(dāng)鎖的兩個(gè)"舊的超時(shí)時(shí)間"相等時(shí),證明之前沒(méi)有其它機(jī)器進(jìn)行GetSet操作,成功獲取鎖
 // 說(shuō)明:這里存在并發(fā)情況,如果有A和B同時(shí)競(jìng)爭(zhēng),A會(huì)先GetSet,當(dāng)B再去GetSet時(shí),oldLockTime就等于A設(shè)置的超時(shí)時(shí)間
 if utils.MustString(oldLockTime) == currentLockTime {
  return true
 }
 return false
}

刪除鎖邏輯:

// 刪除分布式鎖
// @return bool true-刪除成功;false-刪除失敗
func DelDistributeLock(key string) bool {
 redisAlias := "jointly"
 redisRet := redis.Del(redisAlias, key)
 if redisRet != nil {
  return false
 }
 return true
}

業(yè)務(wù)邏輯:

func DoProcess(processId int) {

 fmt.Printf("啟動(dòng)第%d個(gè)線(xiàn)程\n", processId)

 redisKey := "redis_lock_key"
 for {
  // 獲取分布式鎖
  isGetLock := GetDistributeLock(redisKey, 10)
  if isGetLock {
   fmt.Printf("Get Redis Key Success, id:%d\n", processId)
   time.Sleep(time.Second * 3)
   // 刪除分布式鎖
   DelDistributeLock(redisKey)
  } else {
   // 如果未獲取到該鎖,為了避免redis負(fù)載過(guò)高,先睡一會(huì)
   time.Sleep(time.Second * 1)
  }
 }
}

最后起個(gè) 10 個(gè)多線(xiàn)程,去執(zhí)行這個(gè) DoProcess():

func main() {
 // 初始化資源
 var group string = "group"
 var name string = "name"
 var host string

 // 初始化資源
 host = "http://ip:port"
 _, err := xrpc.NewXRpcDefault(group, name, host)
 if err != nil {
  panic(fmt.Sprintf("initRpc when init rpc  failed, err:%v", err))
 }
 redis.SetRedis("louzai", "redis_louzai")

 // 開(kāi)啟10個(gè)線(xiàn)程,去搶Redis分布式鎖
 for i := 0; i <= 9; i ++ {
  go DoProcess(i)
 }

 // 避免子線(xiàn)程退出,主線(xiàn)程睡一會(huì)
 time.Sleep(time.Second * 100)
 return
}

程序跑了100 s,我們可以看到,每次都只有 1 個(gè)線(xiàn)程獲取到鎖,分別是 2、1、5、9、3,執(zhí)行結(jié)果如下:

啟動(dòng)第0個(gè)線(xiàn)程
啟動(dòng)第6個(gè)線(xiàn)程
啟動(dòng)第9個(gè)線(xiàn)程
啟動(dòng)第4個(gè)線(xiàn)程
啟動(dòng)第5個(gè)線(xiàn)程
啟動(dòng)第2個(gè)線(xiàn)程
啟動(dòng)第1個(gè)線(xiàn)程
啟動(dòng)第8個(gè)線(xiàn)程
啟動(dòng)第7個(gè)線(xiàn)程
啟動(dòng)第3個(gè)線(xiàn)程
Get Redis Key Success, id:2
Get Redis Key Success, id:2
Get Redis Key Success, id:1
Get Redis Key Success, id:5
Get Redis Key Success, id:5
Get Redis Key Success, id:5
Get Redis Key Success, id:5
Get Redis Key Success, id:5
Get Redis Key Success, id:5
Get Redis Key Success, id:5
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:9
Get Redis Key Success, id:3
Get Redis Key Success, id:3
Get Redis Key Success, id:3
Get Redis Key Success, id:3
Get Redis Key Success, id:3

04 后記

這個(gè)代碼,其實(shí)是我很久之前寫(xiě)的,因?yàn)楫?dāng)時(shí) Go 沒(méi)有開(kāi)源的分布式鎖,但是我又需要通過(guò)單機(jī)去執(zhí)行某個(gè)任務(wù),所以就自己手動(dòng)擼了一個(gè),后來(lái)在線(xiàn)上跑了 2 年,一直都沒(méi)有問(wèn)題。

不過(guò)期間也遇到過(guò)一個(gè)坑,就是我們服務(wù)遷移時(shí),忘了將舊機(jī)器的分布式鎖停掉,導(dǎo)致鎖經(jīng)常被舊機(jī)器搶占,當(dāng)時(shí)覺(jué)得很奇怪,我的鎖呢?

寫(xiě)這篇文章時(shí),又讓我想到當(dāng)時(shí)工作的場(chǎng)景。

最后再切回正題,本文由淺入深,詳細(xì)講解了 Redis 實(shí)現(xiàn)的詳細(xì)過(guò)程,以及鎖超時(shí)、并發(fā)場(chǎng)景下,如何保證鎖能正常釋放,且只有一個(gè)線(xiàn)程去獲取鎖。

責(zé)任編輯:武曉燕 來(lái)源: 樓仔
相關(guān)推薦

2020-07-30 09:35:09

Redis分布式鎖數(shù)據(jù)庫(kù)

2021-11-01 12:25:56

Redis分布式

2022-11-11 08:19:03

redis分布式

2024-05-08 10:20:00

Redis分布式

2019-06-19 15:40:06

分布式鎖RedisJava

2022-09-29 08:28:57

SpringRedis分布式

2022-09-22 13:28:34

Redis分布式鎖

2024-07-15 08:25:07

2022-04-14 07:56:30

公平鎖Java線(xiàn)程

2023-03-06 08:14:48

MySQLRedis場(chǎng)景

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2023-09-21 22:22:51

開(kāi)發(fā)分布式鎖

2022-12-18 20:07:55

Redis分布式

2019-03-21 09:45:20

IM即時(shí)通訊CIM

2024-10-07 10:07:31

2020-11-16 12:55:41

Redis分布式鎖Zookeeper

2022-09-19 08:17:09

Redis分布式

2019-07-16 09:22:10

RedisZookeeper分布式鎖
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)