【后端开发】goland分布式锁的几种实现方式(mysql,redis,etcd,zookeeper,mq,s3)
【后端开发】goland分布式锁的几种实现方式(mysql,redis,etcd,zookeeper,mq,s3)
文章目录
- 1、分布式锁实现方案对比(mysql,redis,etcd,zookeeper,mq,s3)
- 2、锁的常见策略(乐观/悲观,可重入/不可重入,公平/非公平,自旋锁, 读写锁)
- 3、基于redis,mysql的实现(缓存,AP-高性能)
- 4、基于etcd,zk的实现(分布式协调系统,CP-强一致性)
- 5、基于mq,s3等其他中间件的实现(特殊CAP)
1、分布式锁实现方案对比(mysql,redis,etcd,zookeeper,mq,s3)
技术 | 实现复杂度 | 性能 | 可靠性 | 适用场景 | 主要特点 |
---|---|---|---|---|---|
Redis | 低 | 高 | 中 | 高性能、高并发场景 | 基于内存,高性能;需要处理锁续期问题;可能出现脑裂问题 |
Etcd | 中 | 中高 | 高 | 强一致性要求的场景 | 基于Raft协议,强一致性;自带租约机制;适合云原生环境 |
ZooKeeper | 中高 | 中 | 高 | 复杂协调场景 | 基于ZAB协议,强一致性;watch机制完善;但运维成本较高 |
MySQL | 低 | 低 | 中 | 低频、简单场景 | 实现简单;性能瓶颈明显;不适合高并发 |
MQ | 中 | 中高 | 中高 | 异步、解耦场景 | 通过消息排他性实现;天然支持分布式;但不如专用锁工具直观 |
S3 | 高 | 低 | 高 | 特殊场景(如跨云) | 基于对象存储的原子操作;延迟高;适合低频跨地域场景 |
分布式锁适用场景(排序)
-
Redis - 最适合大多数场景,性能与功能平衡 (无脑选择)
- 推荐库: redsync
- 适用: 电商秒杀、缓存更新等高频场景
-
Etcd - 强一致性要求的云原生场景首选 (云原生+刚好有etcd)
- 推荐库: 官方concurrency包
- 适用: K8s环境、服务注册发现相关锁需求
-
ZooKeeper - 已有ZK基础设施的复杂协调场景 (刚好有…雾)
- 推荐库: curator(Java)或go-zookeeper
- 适用: Hadoop生态、传统分布式系统
-
MQ - 已使用MQ且需要弱化锁概念的场景 (刚好只有…雾)
- 推荐: RabbitMQ排他队列/Kafka partition
- 适用: 异步任务调度、事件驱动架构
-
MySQL - 简单低频场景的快速实现 (不咋用,刚好只有mysql)
- 推荐: GET_LOCK函数
- 适用: 低频管理操作、小型系统
-
S3 - 特殊跨云/跨地域需求 (刚好只有S3)
- 推荐: S3原子操作
- 适用: 多云环境下的协调
特殊场景建议
- 超高频场景(10万+/秒): Redis集群+Redlock算法
- 金融级强一致: Etcd或ZooKeeper
- 云原生环境: 首选Etcd
- 已有MQ基础设施: 可考虑利用MQ特性实现简化架构
- 简单低频系统: MySQL实现最方便
参考资料:1, 2
2、锁的常见策略(乐观/悲观,可重入/不可重入,公平/非公平,自旋锁, 读写锁)
CAP定理(一致性Consistency、可用性Availability、分区容错性Partition Tolerance)
用锁的策略考虑
- 争用程度:高争用时应减少锁粒度
- 持有时间:长时间持有应使用可重入锁
- 公平性需求:平衡吞吐量与公平性
悲观锁
- 特点
默认认为并发冲突会发生
访问资源前先加锁
阻塞其他线程直到锁释放
实现简单但并发性能较低 - 适用场景:
冲突频率高的场景
临界区执行时间较长
需要强一致性保证
乐观锁
- 特点:
默认认为冲突不会发生
不上锁直接操作,提交时检查版本
非阻塞,冲突时回滚或重试
高并发性能好 - 适用场景:
读多写少的环境
冲突概率低的场景
需要高吞吐量的系统
--- 乐观锁
--- 初始数据
id | name | version
1 | 张三 | 1场景1:无并发冲突
T1: 服务A读取数据 (version=1)
T2: 服务A更新数据 (version=1 -> 2)
T3: 服务B读取数据 (version=2)
T4: 服务B更新数据 (version=2 -> 3)场景2:有并发冲突
T1: 服务A读取数据 (version=1)
T2: 服务B读取数据 (version=1)
T3: 服务B更新数据 (version=1 -> 2)
T4: 服务A尝试更新数据 (version=1 -> 2) 失败!
分布式锁
- 特性:
跨进程/跨机器互斥
高可用性(避免单点故障)
自动释放(防死锁)
可重入性(可选) - 实现对比:
Redis:AP系统,高性能但不保证强一致
Zookeeper/Etcd:CP系统,强一致但性能较低
可重入锁(递归锁)
- 特点:
同一线程可重复获取已持有的锁
需要记录持有线程和重入次数
避免自死锁 - 实现:
etcd默认 concurrency包
不可重入锁
- 特点:
严格互斥,即使同一线程也不能重复获取
实现简单但易引发死锁
死锁
- 四大条件与预防
1 互斥条件, 破坏互斥
2 请求与保持,破坏请求保持,资源有序分配法
3 不可剥夺,允许资源抢占
4 循环等待, 破坏循环等待 - 实现措施
设置超时时间(Redis锁的expire)
资源有序申请(按固定顺序获取锁)
使用tryLock而非lock
公平锁
- 特点
按申请顺序分配锁
避免线程饥饿
性能较低(需要维护队列)
非公平锁
- 特点
允许插队获取锁
吞吐量更高
可能导致线程饥饿
自旋锁
- 适用场景:临界区非常小且CPU资源充足
// C++原子操作实现自旋锁
std::atomic_flag lock = ATOMIC_FLAG_INIT;void lock() {while(lock.test_and_set(std::memory_order_acquire));
}void unlock() {lock.clear(std::memory_order_release);
}
读写锁
- 特点:读共享,写互斥
// Java读写锁
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();// 读操作
rwLock.readLock().lock();
try {// 并发读
} finally {rwLock.readLock().unlock();
}// 写操作
rwLock.writeLock().lock();
try {// 独占写
} finally {rwLock.writeLock().unlock();
}
3、基于redis,mysql的实现(缓存,AP-高性能)
Redis实现
- AP系统:高可用和分区容忍优先,牺牲强一致性(如Redis集群可能出现脑裂)
- 高性能:基于内存操作,TPS可达10万+级别
- Redis集群脑裂是指在一个Redis集群中,由于网络分区导致集群被分裂成两个或多个部分,每个部分都认为自己是主节点(Master)的情况。
- redsync/v4(Redsync)
Redis 官方推荐的分布式锁库,基于 Redlock 算法 实现,支持单节点、多节点 Redis 模式,具备自动续期、容错处理等特性。 - 特点
支持单实例和 多实例(Redlock) 模式,多实例模式下通过多数节点加锁提升可靠性。
自动处理锁的续期(Renewal),避免业务执行时间超过锁有效期导致的误释放。
提供 Lock 和 RLock(可重入锁)接口,支持上下文取消(context.Context)。 Redlock参考:1, 2, 3
// Redlock算法示例(多节点Redis)
err := redsync.New(redisPools).NewMutex("resource_lock").Lock()// 使用 SET NX 命令
lockKey := "sync_lock"
lockValue := "server_id"
success, err := redis.SetNX(ctx, lockKey, lockValue, 5*time.Minute).Result()
手动实现
package mainimport ("context""log""math/rand""time""github.com/go-redis/redis/v9"
)type DistLock struct {rdb *redis.Clientkey stringvalue string // 唯一标识(如 UUID)expiration time.Durationctx context.Context
}// 新建分布式锁
func NewDistLock(rdb *redis.Client, key string, expiration time.Duration) *DistLock {return &DistLock{rdb: rdb,key: key,value: rand.String(16), // 生成随机 UUID(示例简化)expiration: expiration,ctx: context.Background(),}
}// 加锁
func (l *DistLock) Lock() (bool, error) {ok, err := l.rdb.SetNX(l.ctx, l.key, l.value, l.expiration).Result()if err != nil {return false, err}return ok, nil
}// 释放锁
func (l *DistLock) Unlock() (bool, error) {script := `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end`result, err := l.rdb.Eval(l.ctx, script, []string{l.key}, l.value).Result()if err != nil {return false, err}return result.(int64) == 1, nil
}func main() {rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379",})lock := NewDistLock(rdb, "my-lock", 5*time.Second)// 尝试加锁ok, err := lock.Lock()if err != nil || !ok {log.Fatal("获取锁失败")return}defer lock.Unlock() // 确保释放锁// 模拟业务执行(可能超过锁过期时间,需手动处理续期)log.Println("执行业务...")time.Sleep(6 * time.Second) // 超过 5s 锁过期时间,可能导致其他进程获取锁
}
MySQL实现
- 基于唯一索引或乐观锁(CAS)
- 简单但性能低(TPS通常<1K)
- 适用场景:低频管理操作、传统单体架构过渡期。
-- 悲观锁
SELECT * FROM table WHERE id=1 FOR UPDATE;-- 乐观锁
UPDATE table SET stock=stock-1, version=version+1
WHERE id=1 AND version=5;
4、基于etcd,zk的实现(分布式协调系统,CP-强一致性)
基于etcd的分布式锁实现
- etcd是一个分布式、高可用的键值存储系统,主要用于共享配置和服务发现。由CoreOS开发,现为CNCF(云原生计算基金会)项目。
etcd采用Raft一致性算法保证数据一致性,支持分布式锁、观察者模式、TTL(生存时间)等特性,广泛应用于Kubernetes等云原生系统中。 - etcd实现锁的核心是通过Create操作在KV存储中创建临时键值对。如果创建成功即获取锁,否则监听该键变化。锁释放时自动删除键值对。 1 2
- 优点:
强一致性
自动过期
适合分布式系统
// etcd 手动实现
// etcd客户端初始化
cli, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"},DialTimeout: 5 * time.Second,
})// 尝试获取锁
lease := clientv3.NewLease(cli)
leaseGrantResp, err := lease.Grant(context.TODO(), 10) // 10秒租约
_, err = cli.Put(context.TODO(), "/lock/key", "", clientv3.WithLease(leaseGrantResp.ID))// 锁续约
keepAliveCh, err := lease.KeepAlive(context.TODO(), leaseGrantResp.ID)// 释放锁
lease.Revoke(context.TODO(), leaseGrantResp.ID)
// etcd的concurrency包
func main() {// 创建一个etcd客户端client, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"},DialTimeout: 5 * time.Second,})if err != nil {panic(err)}defer client.Close()// 创建一个分布式锁mutex := concurrency.NewMutex(client, "/lock/key")// 尝试获取锁err = mutex.Lock(context.Background())if err != nil {panic(err)}defer mutex.Unlock(context.Background())// 执行需要互斥访问的代码// ...
}
etcdctl工具 1
brew install etcd
etcdctl get --prefix foo# 创建租约(TTL为10秒)
etcdctl lease grant 10
# 输出示例:lease 32695410dcc0ca06 granted with TTL(10s)# 使用租约持有锁(写入一个键并绑定租约)
etcdctl put --lease=32695410dcc0ca06 /lock/resource "holder1"# 续租
etcdctl lease keep-alive 32695410dcc0ca06
# 释放锁(删除键或撤销租约)
etcdctl lease revoke 32695410dcc0ca06
基于Zookeeper的分布式锁实现
// 创建连接
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);// 创建临时有序节点
String lockPath = zk.create("/lock/key-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);// 检查是否为最小节点
List<String> children = zk.getChildren("/lock", false);
Collections.sort(children);// 监听前一个节点
String watchNode = "/lock/" + children.get(Collections.binarySearch(children, lockPath)-1);
zk.exists(watchNode, new Watcher() {public void process(WatchedEvent event) {// 节点删除时重新检查锁状态}
});// 释放锁
zk.delete(lockPath, -1);
etcd和zk对比
1,
应用场景 | etcd | Zookeeper | 细节差异 |
---|---|---|---|
发布与订阅(配置中心) | 是 | 是 | etcd API 简洁轻量;Zookeeper 需处理会话超时等细节,Java 生态适配好 |
软负载均衡 | 是 | 是 | etcd 客户端主动拉取负载;Zookeeper 靠监听节点变化实现,传统架构适配性强 |
命名服务 | 是 | 是 | etcd 查询高效,适配灵活场景;Zookeeper 适合强层级结构,如传统中间件集成 |
服务发现 | 是 | 是 | etcd 天然适配云原生(如 K8s );Zookeeper 是 Dubbo 等传统框架默认依赖 |
分布式通知 / 协调 | 是 | 是 | etcd 多 Key 监听、原子性优;Zookeeper 适合复杂协调逻辑,如集群状态同步 |
集群管理与 Master 选举 | 是 | 是 | etcd 基于 Raft 算法,选主高效自动;Zookeeper 靠临时 / 有序节点,Java 生态成熟 |
分布式锁 | 是 | 是 | etcd 轻量级、无锁竞争性能优;Zookeeper 阻塞锁易实现,高并发下节点创建开销略大 |
分布式队列 | 是 | 是 | etcd 可扩展延时队列(结合 Lease );Zookeeper 天然支持阻塞队列,客户端完善 |
etcd和redis的对比 1
对比维度 | etcd | Redis |
---|---|---|
定位 | 分布式系统元数据存储、配置中心 | 内存数据库、高性能缓存、实时数据处理 |
数据存储 | 仅磁盘持久化(WAL + 快照) | 内存为主(支持 RDB/AOF 持久化) |
数据一致性 | 强一致性(Raft 共识算法) | 最终一致性(可配置强一致性) |
数据类型 | 仅简单键值对(String-Byte) | 丰富数据类型(String/Hash/List 等) |
分布式能力 | 原生支持(Raft 集群) | 需手动配置(Cluster/Sentinel) |
典型场景 | Kubernetes 集群、分布式锁、配置管理 | 缓存、排行榜、消息队列、实时分析 |
读写性能 | 低频操作(数百次 / 秒,受磁盘限制) | 高频内存操作(数万次 / 秒) |
扩展性 | 节点数建议 3-5 个(Raft 限制) | 支持动态扩缩容(数千节点) |
生态集成 | 深度集成 Kubernetes 生态 | 通用型,适配多种框架(如 Spring、Node.js) |
5、基于mq,s3等其他中间件的实现(特殊CAP)
MQ实现:
- 原理
利用消息队列的排他性消费特性
通过创建唯一队列或独占消费者实现互斥
基于消息的ACK机制实现锁释放 - 示例
RabbitMQ: 使用排他队列(exclusive queue)或单活跃消费者
Kafka: 使用partition的单一消费者机制
RocketMQ: 使用MessageGroup保证同一分组消息被同一消费者处理 - 优点:
天然分布式特性
与业务消息系统集成方便
自带重试和持久化机制 - 缺点:
没有专门为锁设计,功能不直观
锁释放依赖于消费者生命周期
性能通常低于专用锁服务
S3实现:
- 方案1:去s3持久化文件,在文件中记录当前锁的状态
- 方案2:
通过 PutObject 尝试创建一个唯一的锁文件(如以锁名称为键的对象),利用 IfNoneMatch 条件确保只有当文件不存在时才能创建成功(即获取锁)
通过 DeleteObject 删除锁文件,利用 IfMatch 条件确保只有持有锁的客户端(通过存储在元数据中的唯一标识)才能删除,避免误删其他客户端的锁 - 适用场景
轻量级分布式锁需求:如简单的任务调度、资源编排等。
跨云或多云环境:S3 兼容接口(如 MinIO)可统一不同云厂商的实现。
低成本场景:避免引入 Redis、ZooKeeper 等复杂中间件。
优点 | 缺点 |
---|---|
1. 利用 S3 原生接口,无需额外组件 | 1. 依赖 S3 服务的可用性和延迟 |
2. 支持跨区域分布式场景 | 2. 高频操作时 API 调用成本较高(需付费) |
3. 天然支持持久化(锁状态存储在 S3 中) | 3. 强一致性依赖 S3 的读一致性模型 |
4. 实现简单,适合轻量级场景 | 4. 不支持可重入锁 |