lockx

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package lockx 定义分布式阻塞与非阻塞锁契约,提供获取、执行和释放的通用流程。 业务依赖 Locker(等待获取)或 TryLocker(单次尝试),初始化时选择具体后端。 当前由 redislock 提供固定租期的阻塞与非阻塞锁。

需要复用配置时通过 New(backend) 绑定为 Executor,调用 executor.Do/TryDo 无需再次传入后端。 NewNamed(name, backend) 创建命名执行器,NewDefault(name, backend) 同时将其设为默认; Default() 与对应的 Named(name) 返回同一个实例,New 和 NewNamed 不隐式设置默认实例。 空白名称、重复名称、第二个默认实例和 nil 后端返回错误,失败不占用名称或默认位置; 获取未登记的实例会 panic。启动阶段完成全部登记后再运行业务,不支持配置热切换。 执行器不拥有后端资源,也不提供 Close;应用须先停止业务,再关闭共享客户端。 名称仅选择配置,不参与 key 构造;同一 Redis 数据库的相同 key 在不同执行器之间仍然互斥。 使用 biz/bootstrap 时可由 MustRegisterRedisLock 按代码声明,在 Redis 就绪后统一装配。

以下示例需导入 context 和 github.com/bpcoder16/pixiu/biz/lockx; locker 由启动流程注入,confirm 为应用提供的业务函数:

func confirmOrder(
    ctx context.Context,
    locker lockx.Locker,
    orderID string,
    confirm func(context.Context) error,
) error {
    return lockx.Do(ctx, locker, "orders:confirm:"+orderID, confirm)
}

使用 TryLocker 时通过 TryDo 调用:

func tryConfirmOrder(
    ctx context.Context,
    locker lockx.TryLocker,
    confirm func(context.Context) error,
) (bool, error) {
    return lockx.TryDo(ctx, locker, "orders:confirm:123", confirm)
}

批量非阻塞执行使用 TryDoMany 或 executor.TryDoMany,Backend 已要求实现 TryManyLocker:

func tryConfirmBatch(
    ctx context.Context,
    keys []string,
    confirm func(context.Context) error,
) (bool, error) {
    return lockx.Default().TryDoMany(ctx, keys, confirm)
}

keys 必须非空且不能包含空白 key;复制并按原值去重,不修剪、不改大小写。 整批获取成功后才执行一次回调;任一冲突时整批失败。单 key 与批量使用相同 key 时互斥。 Backend 内嵌 Locker、TryLocker 和 TryManyLocker,三种能力在编译期约束,执行器直接调用。 仅增加非阻塞批量能力,不提供 DoMany / LockMany。应用应限制单次 key 数量,避免过大批次占用后端。 TryDo / TryDoMany 的 false、nil 表示竞争失败,true 只表示进入过回调,不代表业务已提交。 上述执行函数同步执行回调最多一次,工作期限取请求与 Lock.Deadline 的较早者。 获取阶段等待预算不会带入工作;内置 Redis 租期锁不自动续租。 回调返回或 panic 后使用独立的 1 秒期限尽力释放;正常返回合并错误,保留原始 panic。

手工调用 Lock/TryLock 时应立即登记释放,租期句柄的 Deadline 包含成功尝试耗时。 获取 context 取消不会自动释放已交付的句柄。Unlock 原子校验持有身份, 内置实现对不再持有、到期或重复释放幂等返回 nil,真实下游错误仍返回。 释放的 nil 不代表仍持有锁,业务本身的租期超时仍会返回;取消不能强制停止业务。

不同后端或协议的同名锁互不排斥;同一资源的竞争者必须使用相同实现。 迁移前先停止旧竞争者并完成旧临界区。锁不替代幂等、事务或数据端版本约束, 租期过期后的旧任务仍可能执行。

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotHeld = errors.New("lockx: lock not held")

ErrNotHeld 表示实现确认本次锁已过期、已释放或已由其他执行者持有。 保留用于兼容旧后端;内置实现的 Unlock 将这些状态作为幂等完成并返回 nil。

Functions

func Do

func Do(ctx context.Context, locker Locker, key string, fn func(context.Context) error) error

Do 等待获取锁,同步执行 fn 并释放;获取失败不执行 fn。 工作期限取 ctx 与句柄期限的较早者,取消不能强制终止 fn。 释放脱离请求取消并独立限时 1 秒;正常路径合并错误,panic 时继续原始 panic。

Example
package main

import (
	"context"

	"github.com/bpcoder16/pixiu/biz/lockx"
)

func main() {
	confirm := func(ctx context.Context, locker lockx.Locker, fn func(context.Context) error) error {
		return lockx.Do(ctx, locker, "orders:confirm:123", fn)
	}
	_ = confirm
}

func TryDo

func TryDo(ctx context.Context, locker TryLocker, key string, fn func(context.Context) error) (bool, error)

TryDo 尝试获取锁,成功后同步执行 fn 并释放;竞争失败返回 false、nil。 executed 表示是否进入过 fn,不表示业务是否提交成功;其余语义与 Do 相同。

Example
package main

import (
	"context"

	"github.com/bpcoder16/pixiu/biz/lockx"
)

func main() {
	// 业务接收统一接口,初始化位置决定 Redis 或其他实现。
	tryConfirm := func(
		ctx context.Context,
		locker lockx.TryLocker,
		orderID string,
		confirm func(context.Context) error,
	) (bool, error) {
		return lockx.TryDo(ctx, locker, "orders:lock:confirm:"+orderID, confirm)
	}
	_ = tryConfirm
}

func TryDoMany

func TryDoMany(ctx context.Context, locker TryManyLocker, keys []string, fn func(context.Context) error) (bool, error)

TryDoMany 只尝试一次整批获取,全部成功后同步执行 fn;竞争失败不执行回调。 keys 须非空且不能包含空白 key;按原值复制去重,不修剪或改变大小写。 executed 只表示进入过回调;租期、取消、错误合并与 panic 清理沿用 TryDo。

Types

type Backend

type Backend interface {
	Locker
	TryLocker
	TryManyLocker
}

Backend 同时支持单 key 阻塞、单 key 非阻塞及整批非阻塞获取;共享使用时须支持并发调用。

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor 绑定可复用的锁后端,调用时只需传入请求 context、业务 key 和回调。 不保存单次锁句柄,不创建或关闭后端资源;零值不可用,须通过 New 系列函数构造。

func Default

func Default() *Executor

Default 返回显式登记的默认执行器,与对应 Named(name) 是同一实例;未登记时 panic。 登记表不拥有后端资源,调用方须在关闭后端之前停止使用执行器。

func Named

func Named(name string) *Executor

Named 返回指定命名执行器;名称不存在时 panic,不自动创建或回退默认实例。

func New

func New(backend Backend) (*Executor, error)

New 创建独立执行器,不登记默认或命名实例;nil(包括带类型的 nil)后端返回错误。 后端的配置与资源由调用方管理,发布共享执行器后不应修改后端配置。

func NewDefault

func NewDefault(name string, backend Backend) (*Executor, error)

NewDefault 创建并按 name 登记执行器,同时设为默认;已有默认实例时返回错误。 创建失败不占用名称或默认位置;其余约束与 NewNamed 相同。

func NewNamed

func NewNamed(name string, backend Backend) (*Executor, error)

NewNamed 创建并登记命名执行器;名称不能为空白,重复名称返回错误且不替换已有实例。 应在启动阶段完成全部登记后再运行业务;后端的资源始终由调用方关闭。

Example
package main

import (
	"context"

	"github.com/bpcoder16/pixiu/biz/lockx"
)

func main() {
	// 后端由启动流程创建;两个后端可以共享 Redis 并持有不同的租期配置。
	configure := func(normal, longTask lockx.Backend) error {
		if _, err := lockx.NewDefault("default", normal); err != nil {
			return err
		}
		_, err := lockx.NewNamed("long-task", longTask)
		return err
	}
	run := func(ctx context.Context, key string, work func(context.Context) error) error {
		return lockx.Named("long-task").Do(ctx, key, work)
	}
	_ = configure
	_ = run
}

func (*Executor) Do

func (e *Executor) Do(ctx context.Context, key string, fn func(context.Context) error) error

Do 等待获取锁,同步执行 fn 并释放;沿用包级 Do 的期限、错误与 panic 语义。

func (*Executor) TryDo

func (e *Executor) TryDo(ctx context.Context, key string, fn func(context.Context) error) (bool, error)

TryDo 只尝试一次;竞争失败返回 false、nil,executed 只表示是否进入过 fn。 其余语义沿用包级 TryDo;执行器名称不参与 key 的构造。

func (*Executor) TryDoMany

func (e *Executor) TryDoMany(ctx context.Context, keys []string, fn func(context.Context) error) (bool, error)

TryDoMany 通过绑定的 Backend 整批获取,沿用包级 TryDoMany 的执行与清理语义。

Example

批量竞争只尝试一次;业务 key 由应用按资源构造。

package main

import (
	"context"

	"github.com/bpcoder16/pixiu/biz/lockx"
)

func main() {
	publish := func(ctx context.Context, keys []string, work func(context.Context) error) (bool, error) {
		return lockx.Default().TryDoMany(ctx, keys, work)
	}
	_ = publish
}

type Lock

type Lock interface {
	// Deadline 返回保守的本地工作期限,不包含获取前的竞争等待。
	// 没有固定期限时返回零值、false;有限租期包含成功尝试的耗时。
	Deadline() (time.Time, bool)
	// Unlock 原子校验本次身份,允许并发调用,不得删除其他持有者的锁。
	// 已过期、不再持有或重复释放作为幂等完成,返回 nil。
	// 参数、取消和真实下游错误仍须按实现契约返回。
	// 幂等释放成功不代表业务仍在有效租期内。
	Unlock(ctx context.Context) error
}

Lock 是一次独立获取的锁句柄,持有身份由实现保存。

type Locker

type Locker interface {
	Lock(ctx context.Context, key string) (Lock, error)
}

Locker 定义分布式阻塞锁接口。 锁被占用时等待其他持有者释放;等待上限由实现配置与 ctx 共同约束。

type TryLocker

type TryLocker interface {
	// TryLock 成功返回句柄、true、nil;确认竞争失败返回 nil、false、nil。
	// 取消、下游错误和结果不确定返回 nil、false、error。
	TryLock(ctx context.Context, key string) (Lock, bool, error)
}

TryLocker 定义分布式非阻塞锁接口。 只尝试一次,锁被占用时返回竞争失败,不等待其他持有者释放。 非阻塞仅针对锁竞争,连接池与网络操作仍可能等待。

type TryManyLocker

type TryManyLocker interface {
	TryLockMany(ctx context.Context, keys []string) (Lock, bool, error)
}

TryManyLocker 提供整批非阻塞获取,是 Backend 必须实现的能力。 只有全部 key 获取成功才交付一个管理整批占用的 Lock;竞争返回 nil、false、nil。 错误或不确定结果不能交付句柄;后端负责清理已确认的占用,异常遗留由租期回收。

Directories

Path Synopsis
internal
lockctx
Package lockctx 为 lockx 公共流程及其后端统一检查 context 取消与期限。
Package lockctx 为 lockx 公共流程及其后端统一检查 context 取消与期限。
Package redislock 通过 infra/redisx 实现单机 Redis 上的阻塞与非阻塞锁。
Package redislock 通过 infra/redisx 实现单机 Redis 上的阻塞与非阻塞锁。

Jump to

Keyboard shortcuts

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