87.Go Redis实现可重入、自动续期分布式锁

本文详细介绍了如何使用Go语言和Redis实现分布式锁,包括基本的Lock和Unlock功能,以及添加了可重入性和自动续期的改进版本。
摘要由CSDN通过智能技术生成

86.分布式锁理论分析 中我们介绍了分布式锁的原理、"坑"以及解决办法。本文就给一下代码示例:

一、Redis实现分布式锁

package main

import (
  "fmt"
  "github.com/go-redis/redis"
  "time"
)

var client = redis.NewClient(&redis.Options{
  Addr:     "localhost:6379",
  Password: "", 
  DB:       0,  
})

func Lock(key string, value string, expiration time.Duration) (bool, error) {
  return client.SetNX(key, value, expiration).Result()
}

func Unlock(key string, value string) error {
  val, err := client.Get(key).Result()
  if err != nil {
    return err
  }
  // 避免释放它人的锁
  if val == value {
    return client.Del(key).Err()
  }
  return fmt.Errorf("key %s is locked by another value %s", key, val)
}

func main() {
  // my-value应该保证全局唯一,如uuid,雪花算法产生的ID等
  ok, err := Lock("my-key", "my-value", 10*time.Second)
  if err != nil {
    // handle error
  }
  if !ok {
    fmt.Println("Lock failed")
  } else {
    fmt.Println("Lock success")
    defer Unlock("my-key", "my-value")
    // do your job
   
  }
}

在上面的代码中,我们定义了两个函数:LockUnlockLock函数会尝试在Redis中设置一个键值对,并设置一个过期时间。如果这个键值对已经被其他地方设置了,那么SetNX函数会返回false,否则返回true

Unlock函数则尝试删除这个键值对,但是在删除之前,会检查这个键值对的值是否符合输入的值,如果不符合,那么认为这个锁已经被其他地方获取,这时就不应该删除这个键值对。

二、可重入与自动续期

上面例子,锁缺失两个重要的性质:一个是可重入,一个是如何实现到期自动续期逻辑。

package main

import (
  "sync"
  "time"

  "github.com/go-redis/redis"
  "github.com/satori/go.uuid"
)

var client = redis.NewClient(&redis.Options{
  Addr:     "localhost:6379",
  Password: "", // no password set
  DB:       0,  // use default DB
})

type Lock struct {
  key        string
  value      string
  expiration time.Duration
  mu         sync.Mutex
  isLocked   bool
  count      int
}

func NewLock(key string, expiration time.Duration) *Lock {
  return &Lock{
    key:        key,
    value:      uuid.NewV4().String(), // 这里使用uuid作为value
    expiration: expiration,
  }
}

func (l *Lock) Lock() (bool, error) {
  l.mu.Lock()
  defer l.mu.Unlock()

  if l.isLocked {
    l.count++
    return true, nil
  }

  ok, err := client.SetNX(l.key, l.value, l.expiration).Result()
  if err != nil || !ok {
    return false, err
  }

  l.isLocked = true
  l.count++

  go l.renew()
  return true, nil
}

func (l *Lock) Unlock() error {
  l.mu.Lock()
  defer l.mu.Unlock()

  if !l.isLocked {
    return nil
  }

  l.count--

  if l.count > 0 {
    return nil
  }

  val, err := client.Get(l.key).Result()
  if err != nil {
    return err
  }
  // 保证释放的是自己的锁
  if val != l.value {
    return nil
  }

  l.isLocked = false
  return client.Del(l.key).Err()
}

func (l *Lock) renew() {
  ticker := time.NewTicker(l.expiration / 2)
  for range ticker.C {
    l.mu.Lock()

    if !l.isLocked {
      ticker.Stop()
      l.mu.Unlock()
      break
    }

    client.Expire(l.key, l.expiration)
    l.mu.Unlock()
  }
}

func main() {
  lock := NewLock("my-key", 10*time.Second)
  locked, err := lock.Lock()
  if err != nil {
    panic(err)
  }
  if !locked {
    return
  }
  defer lock.Unlock()
  // do something
}

这段代码中,Lock结构体中新加入了muisLockedcount字段,分别表示互斥锁、是否已经锁定还有重入次数。当再次获取锁的时候,已经锁定则重入次数增加,否则尝试获取锁。在unlock时,如果重入次数大于零,则直接减少重入次数而不释放锁。

同时加入了renew函数,这个函数会每过一段时间检查这个锁是否已经被释放,未被释放则续期,并在锁释放后停止续期。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
重入分布式锁是指一个线程可以多次获取同一把锁而不会造成死锁。在分布式环境下,需要使用分布式锁来保证多个节点之间的数据同步。Redis可以通过SETNX命令实现分布式锁,但是没有原生支持可重入性。下面是一个使用Redis实现重入分布式锁的示例代码: ```python import redis class RedisReentrantLock: def __init__(self, redis_client, lock_key): self.redis_client = redis_client self.lock_key = lock_key self.thread_local = threading.local() self.lock_count = 0 def acquire(self): current_thread = threading.current_thread() if getattr(self.thread_local, 'lock_owner', None) == current_thread: self.lock_count += 1 return True else: if self.redis_client.setnx(self.lock_key, current_thread.ident): self.thread_local.lock_owner = current_thread self.lock_count = 1 return True else: return False def release(self): current_thread = threading.current_thread() if getattr(self.thread_local, 'lock_owner', None) == current_thread: if self.lock_count > 1: self.lock_count -= 1 else: self.redis_client.delete(self.lock_key) del self.thread_local.lock_owner self.lock_count = 0 return True else: return False ``` 这个类实现了acquire和release方法来获取和释放锁。如果一个线程已经获取了锁,再次获取锁时,acquire方法会增加锁计数器。当锁计数器减至0时,会释放锁。这样实现了可重入性。同时,使用了Python的threading.local类来确保每个线程都有自己独立的锁计数器。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值