一、分布式锁到底是个啥玩意?
咱们先打个比方。想象一下你们公司只有一个厕所,但有好几百号人。这时候要是没有排队机制,那场面绝对乱成一锅粥。分布式锁就是解决类似问题的——在分布式系统里,多个服务实例要访问共享资源时,得有套机制来保证同一时间只有一个能进去"上厕所"。
举个实际场景:电商秒杀系统。10000个人同时抢10台iPhone,如果不加锁,很可能出现超卖。这时候就需要分布式锁来协调各个微服务实例的操作顺序。
二、Redis实现方案:轻量又快速
Redis实现分布式锁最常见的就是用SETNX命令(现在推荐用SET命令带NX参数)。来看个Go语言示例:
// Go语言示例 - 使用go-redis客户端
func acquireLock(client *redis.Client, lockKey string, requestId string, expireTime time.Duration) bool {
// 关键参数说明:
// NX - 只有key不存在时才设置
// EX - 设置过期时间(秒)
result, err := client.SetNX(lockKey, requestId, expireTime).Result()
if err != nil {
log.Printf("获取锁异常: %v", err)
return false
}
return result
}
func releaseLock(client *redis.Client, lockKey string, requestId string) bool {
// 使用Lua脚本保证原子性
script := redis.NewScript(`
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`)
result, err := script.Run(client, []string{lockKey}, requestId).Int64()
return err == nil && result == 1
}
优点:
- 性能炸裂,单节点轻松上10万QPS
- 实现简单,几行代码就能搞定
- 客户端支持丰富,所有语言都能用
坑点提醒:
- 一定要设置过期时间!否则服务崩溃会导致死锁
- 推荐用UUID当requestId,防止误删别人的锁
- 主从切换可能导致锁丢失(Redlock算法可以缓解)
三、ZooKeeper方案:可靠但有点重
ZooKeeper通过临时顺序节点实现锁,天然支持等待队列。Java示例走起:
// Java示例 - 使用Curator框架
public class ZkDistributedLock {
private final InterProcessMutex lock;
public ZkDistributedLock(CuratorFramework client, String lockPath) {
this.lock = new InterProcessMutex(client, lockPath);
}
public boolean tryLock(long timeout, TimeUnit unit) throws Exception {
return lock.acquire(timeout, unit);
}
public void unlock() throws Exception {
lock.release();
}
}
// 使用示例
try {
if (zkLock.tryLock(3, TimeUnit.SECONDS)) {
// 业务代码
}
} finally {
zkLock.unlock();
}
核心优势:
- 强一致性保证,绝对不会出现脑裂问题
- 自带Watch机制,能实现阻塞等待锁
- 临时节点特性,客户端断开自动释放
劝退点:
- 写性能捉急,每秒几千次操作就到头了
- 需要维护ZooKeeper集群,运维成本高
- 客户端使用相对复杂(Curator帮了大忙)
四、etcd方案:中庸之道
etcd的锁基于Raft共识算法,比Redis可靠,比ZK轻量。Python示例:
# Python示例 - 使用etcd3客户端
import etcd3
def etcd_lock():
client = etcd3.client()
lock = client.lock('shop_lock', ttl=30)
try:
# 获取锁(最多等待5秒)
if lock.acquire(timeout=5):
print("抢到锁了!")
# 业务处理...
finally:
lock.release()
闪光点:
- 性能介于Redis和ZK之间(约1万QPS)
- 自带租约(Lease)机制,自动续期很方便
- 数据持久化可靠,适合金融级场景
注意事项:
- 需要合理设置TTL,太短会导致频繁续约
- 相比Redis内存消耗更大
- 客户端API设计有点反人类
五、三大方案PK:该选哪个?
性能排行榜: Redis > etcd > ZooKeeper
可靠性排名: ZooKeeper ≈ etcd > Redis
选型指南:
- 秒杀、缓存类场景 → Redis
- 金融交易、配置中心 → ZooKeeper
- 容器调度、服务发现 → etcd
特殊场景:
- 需要公平锁 → ZooKeeper
- 需要读写锁 → etcd
- 超高并发 → Redis
六、高级玩家技巧
锁重入问题:可以在Redis里用Hash结构记录线程信息和重入次数
-- Redis Lua脚本实现可重入锁 local key = KEYS[1] local threadId = ARGV[1] local releaseTime = ARGV[2] if (redis.call('exists', key) == 0) then redis.call('hset', key, threadId, '1') redis.call('expire', key, releaseTime) return 1 end if (redis.call('hexists', key, threadId) == 1) then redis.call('hincrby', key, threadId, '1') redis.call('expire', key, releaseTime) return 1 end return 0锁续约方案:对于长时间任务,可以起个后台线程定期续期
// Java线程池实现锁续约 ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> { if (lock.isAcquiredInThisProcess()) { client.refreshLockTime(lockKey, requestId, 30); } }, 10, 10, TimeUnit.SECONDS);
七、终极总结
- 没有银弹!根据业务特点选择最合适的方案
- Redis适合大多数互联网场景,但要处理好可靠性问题
- 分布式锁不是万能的,某些场景可以用乐观锁或CAS替代
- 一定要做好监控!包括锁等待时间、获取失败率等指标
记住:分布式系统的黄金法则——任何可能出错的地方最终都会出错。你的锁方案必须能应对网络分区、进程暂停、时钟漂移等各种异常情况。
评论