dlock

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 29, 2026 License: MIT Imports: 12 Imported by: 0

README

dlock

dlock 是 Genesis 的 L2 分布式锁组件,提供统一的 Locker 接口,支持 Redis 和 Etcd 两种后端。它解决的问题不是“实现所有锁模型”,而是把任务竞选、资源互斥、跨实例串行化这类常见场景收敛成一组稳定、可预测的 API。

组件定位

  • 提供 Lock / TryLock / Unlock / Close 四个核心方法
  • 支持 Redis token 校验解锁和 Etcd lease/mutex 两种实现
  • 在锁持有期间自动续期,减少长任务执行时的锁过期风险
  • Close() 会停止续期,并尽力释放当前 Locker 已持有的锁
  • 支持通过 WithTTL(...) 覆盖单次加锁 TTL

dlock 不提供可重入锁、读写锁、公平锁、锁诊断平台或死锁检测。如果你需要非常定制化的锁协议,应该直接使用底层 Redis 或 Etcd 客户端。

快速开始

redisConn, err := connector.NewRedis(&cfg.Redis, connector.WithLogger(logger))
if err != nil {
    return err
}
defer redisConn.Close()

locker, err := dlock.New(&dlock.Config{
    Driver:        dlock.DriverRedis,
    Prefix:        "myapp:lock:",
    DefaultTTL:    10 * time.Second,
    RetryInterval: 100 * time.Millisecond,
}, dlock.WithRedisConnector(redisConn), dlock.WithLogger(logger))
if err != nil {
    return err
}
defer locker.Close()

ctx := context.Background()
if err := locker.Lock(ctx, "inventory:42"); err != nil {
    return err
}
defer locker.Unlock(ctx, "inventory:42")

// critical section

核心接口

type Locker interface {
    Lock(ctx context.Context, key string, opts ...LockOption) error
    TryLock(ctx context.Context, key string, opts ...LockOption) (bool, error)
    Unlock(ctx context.Context, key string) error
    Close() error
}

Lock 适合“拿不到锁就不能继续”的场景,内部按 RetryInterval 重试;TryLock 适合任务竞选这类“拿不到就跳过”的场景;Unlock 只允许持有者释放;Close 用于结束当前 Locker 生命周期,停止续期并清理它持有的锁。

TTL 语义

WithTTL(...) 看起来是统一选项,但两种后端的精度并不完全一样:

  • Redis 直接使用原生 time.Duration
  • Etcd 基于 lease,TTL 是秒级

因此 Etcd 的 DefaultTTLWithTTL(...) 都必须满足:

  • 至少 1*time.Second
  • 必须是整秒,例如 5*time.Second

非法 TTL 会返回 ErrInvalidTTL,不会再静默回退到默认值。

推荐场景

任务竞选
ok, err := locker.TryLock(ctx, "jobs:daily-settlement")
if err != nil {
    return err
}
if !ok {
    return nil
}
defer locker.Unlock(ctx, "jobs:daily-settlement")

runSettlement()
短事务串行化
key := fmt.Sprintf("inventory:%d", productID)
if err := locker.Lock(ctx, key, dlock.WithTTL(30*time.Second)); err != nil {
    return err
}
defer locker.Unlock(ctx, key)

return updateInventory(ctx, productID)

错误语义

常见错误包括:

  • ErrLockAlreadyHeld:当前 Locker 已在本地持有同一个 key
  • ErrLockNotHeld:尝试释放一个当前 Locker 没持有的锁
  • ErrOwnershipLost:远端锁已经不属于当前持有者
  • ErrInvalidTTL:TTL 非法,常见于 Etcd 子秒级 TTL

业务代码通常只需要区分“锁冲突”“所有权丢失”和“底层异常”三类场景。

日志与资源释放

通过 WithLogger 注入 clog.Logger 后,组件会自动附加 component=dlock 字段,并在加锁、解锁、续期失败、所有权丢失等关键事件上输出结构化日志。

dlock 不拥有底层 Redis / Etcd 连接,因此:

  • locker.Close() 负责清理当前 Locker 自己持有的锁和续期状态
  • redisConn.Close() / etcdConn.Close() 仍然由调用方负责

相关文档

Documentation

Overview

Package dlock 提供 Genesis 的分布式锁组件。

`dlock` 的定位是为常见的“跨实例互斥”场景提供一层克制、统一的锁接口, 当前支持 Redis 和 Etcd 两种后端。调用方通过 `Locker` 使用阻塞式 `Lock`、 非阻塞式 `TryLock`、显式 `Unlock` 和 `Close`,不需要直接处理 Redis 的 token 校验或 Etcd 的 session / mutex 细节。

这个组件的核心目标不是抽象出一个“万能锁框架”,而是把最常见、最稳定的 分布式锁能力收敛成少量接口,并把几个容易出错的边界收紧:

  • Redis 使用 token + Lua 脚本,避免误删别人的锁。
  • Redis 和 Etcd 都会在锁持有期间自动续期。
  • `Close()` 会停止续期,并尽力释放当前 `Locker` 已持有的锁。
  • 同一个 `Locker` 不允许本地重入同一个 key。

`dlock` 不负责可重入锁、读写锁、公平锁、锁诊断平台或死锁检测。它更适合 任务竞选、资源互斥、短事务串行化这类“需要一把简单分布式锁”的场景。

需要注意的是,Redis 与 Etcd 并不是完全等价的协议实现。尤其在 TTL 语义上, Etcd 依赖 lease,精度为秒级,因此 `DefaultTTL` 和 `WithTTL(...)` 都要求 至少 1 秒且必须是整秒;Redis 则直接使用原生 `time.Duration`。

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrConfigNil 配置为空
	ErrConfigNil = xerrors.New("dlock: config is nil")

	// ErrConnectorNil 连接器为空
	ErrConnectorNil = xerrors.New("dlock: connector is nil")

	// ErrLockNotHeld 锁未持有
	ErrLockNotHeld = xerrors.New("dlock: lock not held")

	// ErrLockAlreadyHeld 锁已在本地持有
	ErrLockAlreadyHeld = xerrors.New("dlock: lock already held locally")

	// ErrOwnershipLost 锁所有权丢失
	ErrOwnershipLost = xerrors.New("dlock: ownership lost")

	// ErrInvalidTTL TTL 配置非法
	ErrInvalidTTL = xerrors.New("dlock: invalid ttl")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// Driver 选择使用的后端 (redis | etcd)
	Driver DriverType `json:"driver" yaml:"driver"`

	// Prefix 锁 Key 的全局前缀,例如 "dlock:"
	Prefix string `json:"prefix" yaml:"prefix"`

	// DefaultTTL 默认锁超时时间
	// Redis 会启动 Watchdog 自动续期;Etcd 使用 Session KeepAlive 自动续期。
	DefaultTTL time.Duration `json:"default_ttl" yaml:"default_ttl"`

	// RetryInterval 加锁重试间隔 (仅 Lock 模式有效)
	RetryInterval time.Duration `json:"retry_interval" yaml:"retry_interval"`
}

Config 组件静态配置

type DriverType

type DriverType string

DriverType 定义支持的后端类型

const (
	DriverRedis DriverType = "redis"
	DriverEtcd  DriverType = "etcd"
)

type LockOption

type LockOption func(*lockOptions)

LockOption Lock 操作的选项函数

func WithTTL

func WithTTL(d time.Duration) LockOption

WithTTL 设置锁的 TTL(超时时间) 用于覆盖配置中的 DefaultTTL。

需要注意:

  • Redis 支持原生 time.Duration 精度
  • Etcd 的 TTL 基于 lease,必须至少 1 秒且为整秒,否则返回 ErrInvalidTTL

使用示例:

locker.Lock(ctx, "key", dlock.WithTTL(10*time.Second))

type Locker

type Locker interface {
	// Lock 阻塞式加锁
	// 成功返回 nil,失败返回错误
	// 如果上下文取消,返回 context.Canceled 或 context.DeadlineExceeded
	//
	// opts 支持的选项:
	//   - WithTTL(duration): 设置锁的超时时间
	Lock(ctx context.Context, key string, opts ...LockOption) error

	// TryLock 非阻塞式尝试加锁
	// 成功获取锁返回 true, nil
	// 锁已被占用返回 false, nil
	// 发生错误返回 false, err
	//
	// opts 支持的选项:
	//   - WithTTL(duration): 设置锁的超时时间
	TryLock(ctx context.Context, key string, opts ...LockOption) (bool, error)

	// Unlock 释放锁
	// 只有锁的持有者才能成功释放
	Unlock(ctx context.Context, key string) error

	// Close 关闭 Locker 的持有状态。
	// 它会停止自动续期,并尽力释放当前 Locker 已持有的锁。
	// 底层 Redis / Etcd 连接器仍由调用方负责关闭。
	Close() error
}

Locker 定义了分布式锁的核心行为

func New

func New(cfg *Config, opts ...Option) (Locker, error)

New 创建分布式锁组件(配置驱动)

通过 cfg.Driver 选择后端,连接器通过 Option 注入:

  • DriverRedis: WithRedisConnector
  • DriverEtcd: WithEtcdConnector

type Option

type Option func(*options)

Option DLock 组件初始化选项函数

func WithEtcdConnector

func WithEtcdConnector(conn connector.EtcdConnector) Option

WithEtcdConnector 注入 Etcd 连接器

func WithLogger

func WithLogger(l clog.Logger) Option

WithLogger 注入日志记录器 组件会自动添加 component=dlock 字段

func WithRedisConnector

func WithRedisConnector(conn connector.RedisConnector) Option

WithRedisConnector 注入 Redis 连接器

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL