我们在分布式应用中,经常都会用到分布式锁,分布式锁的实现可以有很多种,除了我们常用的redis之外,这里主要是介绍一下consul的锁的实现与原理和自己写的一个基于consul的demo
consul介绍
Consul 是 Go 实现的一个轻量级,支持服务发现 、KV存储 的工具,它通过强一致性的KV存储实现了简易的 分布式锁 ,下面我们根据源码看下 Consul 是怎么解决以上分布式锁的难点的。
// api/lock.go
// Lock 分布式锁数据结构
type Lock struct {
c *Client // 提供访问consul的API客户端
opts *LockOptions // 分布式锁的可选项
isHeld bool // 该锁当前是否已经被持有
sessionRenew chan struct{} // 通知锁持有者需要更新session
locksession string // 锁持有者的session
l sync.Mutex // 锁变量的互斥锁
}
// LockOptions 提供分布式锁的可选项参数
type LockOptions struct {
Key string // 锁的 Key,必填项,且必须有 KV 的写权限
Value []byte // 锁的内容,以下皆为选填项
Session string // 锁的session,用于判断锁是否被创建
SessionOpt *SessionEntry // 自定义创建session条目,用于创建session,避免惊群
SessionName string // 自定义锁的session名称,默认为 "Consul API Lock"
SessionTTL string // 自定义锁的TTL时间,默认为 "15s"
MonitorRetries int // 自定义监控的重试次数,避免脑裂问题
MonitorRetryTime time.Duration // 自定义监控的重试时长,避免脑裂问题
LockWaitTime time.Duration // 自定义锁的等待市场,避免死锁问题
LockTryOnce bool // 是否只重试一次,默认为false,则为无限重试
}
// 提供的接口:
// LockOpts 通过传入锁的参数,返回一个可用的锁
// 必须注意的是 opts.Key 必须在 KV 中有写权限
func (c *Client) LockOpts(opts *LockOptions) (*Lock, error)
// Lock尝试获取一个可用的锁,可以通过一个非空的 stopCh 来提前终止获取
// 如果返回的锁发生异常,则返回一个被关闭的 chan struct ,应用程序必须要处理该情况
func (l *Lock) Lock(stopCh <-chan struct) (<-chan struct{}, error)
// Unlock 尝试释放 consul 分布式锁,如果发生异常则返回 error
func (l *Lock) Unlock() error
从上面的结构可以看出,LockOptions 是所有可能的选项的容器,可以用于设置键和值、 定制会话或设置TTL。
demo:
package dao
import (
"errors"
"github.com/hashicorp/consul/api"
)
const (
host = "127.0.0.1:8500"
)
var (
Client *api.Client
Lockers map[string]*api.Lock
)
//初始化consul
func initConsul() {
client, err := api.NewClient(&api.Config{Address: host})
if err == nil {
Client = client
}
}
//Lock 上锁
func Lock(key, sessionTTL string) error {
opts := &api.LockOptions{
Key: key,
Value: []byte("set by sender 1"),
SessionTTL: sessionTTL,
//SessionTTL: "10s",
SessionOpts: &api.SessionEntry{
Checks: []string{"check1", "check2"},
Behavior: "release",
},
}
lock, err := Client.LockOpts(opts)
if err != nil {
return errors.New("failed to created lock")
}
Lockers[key] = lock
_, err = lock.Lock(nil)
if err != nil {
return errors.New("failed to accquired lock")
}
return nil
}
//Unlock 解锁
func Unlock(key string) error {
if l, ok := Lockers[key]; ok && l != nil {
err := l.Unlock()
if err != nil {
return errors.New("failed to unlock")
}
return nil
}
return errors.New("not exist locker")
}