分布式锁是微服务架构中保证跨进程互斥访问的核心机制。当多个服务实例同时操作共享资源(库存扣减、订单去重、定时任务防重执行)时,本地锁无法跨进程生效。Redis和Zookeeper是两种主流的分布式锁实现方案,在一致性保证、性能特性和故障处理上各有取舍。本文通过代码实现对比两种方案的原理与适用场景。
Redis分布式锁:SET NX EX与Lua脚本解锁
Redis单实例分布式锁基于SET key value NX EX timeout命令,NX保证互斥(Key不存在才设置),EX设置过期时间防止死锁:
// Redis单实例分布式锁
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.UUID;
public class RedisDistributedLock {
private final Jedis jedis;
private static final String LOCK_PREFIX = "lock:";
private static final int LOCK_EXPIRE = 30; // 锁过期时间(秒)
public RedisDistributedLock(Jedis jedis) {
this.jedis = jedis;
}
public String tryLock(String lockKey, String requestId) {
String value = UUID.randomUUID().toString();
SetParams params = SetParams.setParams().nx().ex(LOCK_EXPIRE);
String result = jedis.set(LOCK_PREFIX + lockKey, value, params);
return "OK".equals(result) ? value : null;
}
public boolean unlock(String lockKey, String requestId) {
// 使用Lua脚本保证"判断+删除"原子性
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Object result = jedis.eval(luaScript,
1, LOCK_PREFIX + lockKey, requestId);
return Long.valueOf(1).equals(result);
}
}
解锁操作必须使用Lua脚本保证原子性。先GET判断value是否匹配,再DEL删除Key。如果不用Lua脚本,GET和DEL之间存在时间窗口,锁可能被其他线程误删。
requestId使用UUID标识锁的持有者,防止A线程获取锁后超时自动释放,B线程获取锁后,A线程的解锁操作误删B线程的锁。
Redlock算法:多实例投票提升可靠性
Redlock解决Redis单点故障问题。在多个独立Redis实例上同时获取锁,多数节点(N/2+1)获取成功即认为锁获取成功:
// Redisson Redlock实现
import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
public class RedlockExample {
public void executeWithLock() {
RLock lock1 = redisson1.getLock("lock:order:123");
RLock lock2 = redisson2.getLock("lock:order:123");
RLock lock3 = redisson3.getLock("lock:order:123");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
if (redLock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 执行业务逻辑
doBusinessLogic();
}
} finally {
redLock.unlock();
}
}
}
Redlock在3个Redis实例上同时执行SET NX,至少2个成功才算获取锁。释放锁时向所有实例发送DEL。Redisson框架内置Redlock实现,支持锁续期(Watchdog机制)——后台线程每1/3过期时间自动续期,防止业务执行时间超过锁过期时间导致锁提前释放。
Zookeeper分布式锁:临时顺序节点方案
Zookeeper通过临时顺序节点实现分布式锁,天然解决锁过期和可重入问题:
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
public class ZkDistributedLock {
private final CuratorFramework client;
private final InterProcessMutex lock;
public ZkDistributedLock(CuratorFramework client, String lockPath) {
this.client = client;
this.lock = new InterProcessMutex(client, lockPath);
}
public void executeWithLock(Runnable task) throws Exception {
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
task.run();
} finally {
lock.release();
}
}
} catch (Exception e) {
throw new RuntimeException("获取分布式锁失败", e);
}
}
}
InterProcessMutex是Curator框架提供的可重入锁实现。底层原理:
- 在锁路径下创建临时顺序节点
/lock/node-00001 - 获取所有子节点,检查自己是否为序号最小的节点
- 如果是最小节点,获取锁成功
- 如果不是,监听前一个节点的删除事件
- 前一个节点删除(对应客户端断开或主动释放锁)时,收到通知后重新检查
临时节点的特性:客户端连接断开时,Zookeeper自动删除该节点。这保证了锁持有者崩溃时锁会自动释放,不需要设置过期时间。
两种方案对比与选型建议
- 一致性:Redis是AP模型,锁可能因主从切换失效;Zookeeper是CP模型,Zab协议保证强一致性
- 性能:Redis TPS 10万+,延迟<1ms;Zookeeper TPS 1万左右,延迟2-5ms
- 可靠性:Redlock提升可靠性但非强一致;Zookeeper临时节点自动释放,可靠性高
- 复杂度:Redis实现简单;Zookeeper需部署集群
选型建议:
- 电商秒杀库存扣减:Redis分布式锁 + Redisson,高并发场景下性能优先,配合库存预扣减兜底
- 金融账户转账:Zookeeper分布式锁,强一致性保证,宁可牺牲性能也不允许锁失效
- 定时任务防重执行:Redis分布式锁,简单高效,失效时重复执行影响可控
- 分布式事务协调:Zookeeper分布式锁,与事务回滚配合保证数据一致
分布式锁不是万能的。使用前评估是否可以用幂等设计、乐观锁、队列串行化等方案替代。数据库层面的SELECT ... FOR UPDATE或乐观锁版本号机制,在单库场景下比分布式锁更可靠且无需额外中间件。技术选型的核心不在于哪种方案更”高级”,而在于业务对一致性和性能的真实需求。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-suo-shi-xian-fang-an-dui-bi-redisredlock-yu/