1.1 单机Redis锁的局限性
// 单机Redis锁示例 (SETNX + EXPIRE)
Jedis jedis = new Jedis("localhost", 6379);
String lockKey = "my_lock";
String lockValue = UUID.randomUUID().toString();
if ("OK".equals(jedis.set(lockKey, lockValue, "NX", "PX", 30000))) {
// 获取锁成功
try {
// 执行业务逻辑
} finally {
// 释放锁 (需要Lua脚本保证原子性)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(lockValue));
}
}
// 问题:Redis主节点故障导致锁丢失
1.2 Redlock算法的设计目标
2.1 算法步骤拆解
2.2 Redis源码中的锁操作
// Redis SETNX 命令实现 (redis.c)
void setCommand(client *c) {
robj *key = c->argv[1];
robj *val = c->argv[2];
if (lookupKeyWriteOrReply(c,key,shared.ok) == NULL) return;
if (dbAdd(c->db,key,val) == C_OK) {
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
server.dirty++;
addReply(c,shared.ok);
} else {
addReply(c,shared.czero); // key已存在
}
}
// Redis PEXPIRE 命令实现 (redis.c)
void pexpireCommand(client *c) {
robj *key = c->argv[1];
long long milliseconds;
if (getLongLongFromObjectOrReply(c, c->argv[2], &milliseconds, NULL) != C_OK)
return;
if (milliseconds <= 0) {
addReplyError(c,"invalid expire time in PEXPIRE");
return;
}
if (lookupKeyWrite(c->db,key) == NULL) {
addReply(c,shared.czero);
return;
}
setExpire(c->db,key,mstime()+milliseconds);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"pexpire",key,c->db->id);
server.dirty++;
}
2.3 Java版Redlock客户端模拟
public class Redlock {
private List<Jedis> clients;
public boolean lock(String resource, long leaseTime) {
long startTime = System.currentTimeMillis();
List<String> acquiredLocks = new ArrayList<>();
for (Jedis client : clients) {
try {
String identifier = UUID.randomUUID().toString();
if ("OK".equals(client.set(resource, identifier, "NX", "PX", leaseTime))) {
acquiredLocks.add(identifier);
}
} catch (Exception e) {
// 处理网络错误
}
}
long elapsedTime = System.currentTimeMillis() - startTime;
if (acquiredLocks.size() > clients.size() / 2 && elapsedTime < leaseTime) {
return true;
}
// 释放已获取的锁
unlock(resource, acquiredLocks);
return false;
}
private void unlock(String resource, List<String> identifiers) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
for (Jedis client : clients) {
try {
for(String identifier : identifiers){
client.eval(script, Collections.singletonList(resource),
Collections.singletonList(identifier));
}
} catch (Exception e) {
// 处理网络错误
}
}
}
}
3.1 Redisson的Redlock封装
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://192.168.1.100:7000", "redis://192.168.1.101:7001")
.addNodeAddress("redis://192.168.1.102:7002");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");
try {
lock.lock();
// 业务逻辑
} finally {
lock.unlock();
}
3.2 WatchDog机制与自动续期
// Redisson 看门狗机制,默认锁有效期30秒,每10秒续期
config.setLockWatchdogTimeout(30000);
RLock lock = redisson.getLock("myLock");
lock.lock();
// 假设业务逻辑执行超过30秒,看门狗会自动续期,防止锁过期
Thread.sleep(40000);
lock.unlock();
4.1 Martin Kleppmann的批评
4.2 antirez的回应与辩护
5.1 Redlock适用场景
5.2 Redlock替代方案
6.1 模拟高并发场景
// 使用JMeter模拟并发请求
// 设置线程组参数:线程数、循环次数、Ramp-Up时间
// 添加HTTP请求:请求目标服务器的加锁接口
// 添加监听器:查看吞吐量、响应时间等指标
6.2 性能指标分析
6.3 优化策略
7.1 Redlock核心要点回顾
7.2 未来发展与展望
RedLock
命令提供官方支持7.3 学习资源推荐
7.4 实践建议
通过本文的学习,相信读者已经对Redlock算法有了更深入的理解,能够在实际项目中更好地应用和优化分布式锁。 Remember that choosing the right tool for the job is crucial, and Redlock, while powerful, isn’t a one-size-fits-all solution. Continuously evaluating and adapting your approach to distributed locking will ensure the robustness and scalability of your applications.