1 基于 Redis 实现的分布式锁
如果要使用更复杂精细的功能,可以使用redis官方提供的go实现的分布式锁redsync(github.com/go-redsync/… ),其特性有如下几点:
- value值的随机性,唯一性验证,防误删
- 预估业务可执行时间,防获取无效锁
- 重试机制,提高获取锁的效率
- 多redis节点支持,保证高可用性(Redlock)
缺点:不支持可重入性
2 基于数据库实现的分布式锁
特点:
- 互斥,同一时间内只有一个线程持有
- 超时机制,线程获取锁后异常退出,超时后将释放
- 防止误释放,锁会记录持有者
基本使用:
m := sql.NewMutex(db)
// 不记录持有者
m.Lock(key, time.Second*3)
m.Unlock(key)
// 记录持有者
m.LockWithHolder(key, "uuid", time.Second)
m.UnlockWithHolder(key, "uuid")
实现:
- 锁的结构
type Lock struct {
Id uint `gorm:"column:id;primaryKey"`
Key string `gorm:"column:key;type:varchar(255);unique_index"`
Holder string `gorm:"column:holder;type:varchar(255)"`
ExpireAt time.Time `gorm:"column:expire_at;type:datetime"`
}
type Mutex struct {
db *gorm.DB
}
func NewMutex(db *gorm.DB) *Mutex {
return &Mutex{
db: db,
}
}
- 加锁
func (m *Mutex) Lock(key string, expiration time.Duration) error {
return m.LockWithHolder(key, "default", expiration)
}
func (m *Mutex) LockWithHolder(key string, holder string, expiration time.Duration) error {
err := m.db.Transaction(func(tx *gorm.DB) error {
now := time.Now()
expireAt := now.Add(expiration)
lock := &Lock{}
// 查询锁是否存在
err := tx.Where("`key` = ?", key).First(lock).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if lock.Id == 0 {
// 锁不存在则创建
err = tx.Create(&Lock{Key: key, ExpireAt: expireAt, Holder: holder}).Error
if err != nil {
return err
}
return nil
} else if lock.ExpireAt.Before(now) {
// 锁存在并且过期
err = tx.Model(&Lock{}).Where("`key` = ?", key).Updates(map[string]interface{}{"expire_at": expireAt, "holder": holder}).Error
if err != nil {
return err
}
return nil
}
return errors.New("locked")
})
if err != nil {
fmt.Println(err)
}
return err
}
- 解锁
func (m *Mutex) Unlock(key string) error {
return m.UnlockWithHolder(key, "default")
}
func (m *Mutex) UnlockWithHolder(key string, holder string) error {
err := m.db.Transaction(func(tx *gorm.DB) error {
lock := &Lock{}
now := time.Now()
err := tx.Where("`key` = ?", key).First(lock).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}
if lock.ExpireAt.Before(now) {
err := tx.Where("`key` = ?", key).Delete(&Lock{}).Error
if err != nil {
return err
}
return nil
} else if lock.Holder == holder {
err := tx.Where("`key` = ?", key).Delete(&Lock{}).Error
if err != nil {
return err
}
return nil
}
return errors.New("lock is held by other")
})
if err != nil {
fmt.Println("Unlock", err)
}
return nil
}
3 基于 etcd 实现的分布式锁
由于etcd官方已经提供了基于etcd的分布式锁的实现,所以可以直接使用,具体实现在下面的包中。
github.com/etcd-io/etcd/client/v3/concurrency
基本使用:
// 初始化客户端
cli, _ := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 5 * time.Second,
})
// 新建一次会话,内部逻辑会申请一笔租约,并进行续约
session, _ := concurrency.NewSession(cli)
// 新建一个锁
m := concurrency.NewMutex(session, "key")
// 阻塞加锁
m.Lock(context.TODO())
// 非阻塞加锁
m.TryLock(context.TODO())
// 解锁
m.Unlock(context.TODO())
4 方案对比
分布式锁特性 | code-go现有 | redsync | 数据库 | etcd |
---|---|---|---|---|
互斥 | 支持 | 支持 | 支持 | 支持 |
超时机制 | 支持 | 支持 | 支持 | 支持 |
完备锁接口(支持阻塞与非阻塞) | 只支持非阻塞 | 支持 | 只支持非阻塞 | 支持 |
可重入性 | 无 | 无 | 无 | 支持 |
公平性 | 无 | 无 | 无 | 支持 |
单节点故障容错 | 无 | 支持 | 无 | 支持 |