【后端开发】goland分布式锁的几种实现方式(mysql,redis,etcd,zookeeper,mq,s3)
技术 | 实现复杂度 | 性能 | 可靠性 | 适用场景 | 主要特点 |
---|---|---|---|---|---|
Redis | 低 | 高 | 中 | 高性能、高并发场景 | 基于内存,高性能;需要处理锁续期问题;可能出现脑裂问题 |
Etcd | 中 | 中高 | 高 | 强一致性要求的场景 | 基于Raft协议,强一致性;自带租约机制;适合云原生环境 |
ZooKeeper | 中高 | 中 | 高 | 复杂协调场景 | 基于ZAB协议,强一致性;watch机制完善;但运维成本较高 |
MySQL | 低 | 低 | 中 | 低频、简单场景 | 实现简单;性能瓶颈明显;不适合高并发 |
MQ | 中 | 中高 | 中高 | 异步、解耦场景 | 通过消息排他性实现;天然支持分布式;但不如专用锁工具直观 |
S3 | 高 | 低 | 高 | 特殊场景(如跨云) | 基于对象存储的原子操作;延迟高;适合低频跨地域场景 |
分布式锁适用场景(排序)
Redis - 最适合大多数场景,性能与功能平衡 (无脑选择)
Etcd - 强一致性要求的云原生场景首选 (云原生+刚好有etcd)
ZooKeeper - 已有ZK基础设施的复杂协调场景 (刚好有…雾)
MQ - 已使用MQ且需要弱化锁概念的场景 (刚好只有…雾)
MySQL - 简单低频场景的快速实现 (不咋用,刚好只有mysql)
S3 - 特殊跨云/跨地域需求 (刚好只有S3)
特殊场景建议
参考资料:1, 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) 失败!
分布式锁
可重入锁(递归锁)
不可重入锁
死锁
公平锁
非公平锁
自旋锁
// 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();
}
Redis实现
// 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 main
import (
"context"
"log"
"math/rand"
"time"
"github.com/go-redis/redis/v9"
)
type DistLock struct {
rdb *redis.Client
key string
value string // 唯一标识(如 UUID)
expiration time.Duration
ctx 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实现
-- 悲观锁
SELECT * FROM table WHERE id=1 FOR UPDATE;
-- 乐观锁
UPDATE table SET stock=stock-1, version=version+1
WHERE id=1 AND version=5;
基于etcd的分布式锁实现
// 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) |
MQ实现:
S3实现:
优点 | 缺点 |
---|---|
1. 利用 S3 原生接口,无需额外组件 | 1. 依赖 S3 服务的可用性和延迟 |
2. 支持跨区域分布式场景 | 2. 高频操作时 API 调用成本较高(需付费) |
3. 天然支持持久化(锁状态存储在 S3 中) | 3. 强一致性依赖 S3 的读一致性模型 |
4. 实现简单,适合轻量级场景 | 4. 不支持可重入锁 |