背景:基于redis实现分布式锁。客户端:lettuce-core-5.1.7.RELEASE。连接池:commons-pool2-2.6.2
很简单,单机的服务,如果服务器宕机,那么服务处于不可用状态,如何解决?增加备份,master- slave模式,master宕机后,slave升级master进行服务
单点问题解决后也引入了故障转移问题。如果master宕机时,数据还没有同步至slave怎么办?
分布式锁也同样需要解决此类问题,如果master宕机slave未完成master数据的同步,会导致两台机器同时获取到同一把分布式锁
简单讲就是写入主库的同时,等待主库数据同步至从库后再响应客户端,但是牺牲的是可用性。互联网时代,如果一主一从,那么代价就是任何一台机器有问题都会导致服务不可用,如果增加slave机器,则可用性也会降低
redis不再采用master-slave模式,而是集群模式,假设有3台机器,同时向三台机器申请分布式锁。如果有两台机器响应成功则代表请求成功,类似于选举算法的过半原则。既保障了高可用性,也解决了故障转移问题
redlock其实就是“双写”方案的变形,等同于:多写+过半机制
环境背景:集群分片+master-slave模式
底层实现使用的是redis的wait命令
wait命令的作用:
此命令将阻塞当前客户端,直到当前Session连接(master节点上)所有的写命令都被传送到指定数据量的slave节点。
如果到达超时(以毫秒为单位),则即使尚未完全传送到达指定数量的salve节点,该命令也会返回成功传送到的节点的个数。
private boolean doClusterLock(String lock, String resource, String sign, long maxWaitTime, long maxLockDuration) {
boolean masterSuccess = false;
boolean rollback = false;
long getPoolStartTime = System.currentTimeMillis();
try (StatefulConnection<String, String> conn = genericObjectPool.borrowObject()) {
long getPoolEndTime = System.currentTimeMillis();
logger.debug("the lock {} get connection time is : {}", lock, (getPoolEndTime - getPoolStartTime));
StatefulRedisClusterConnection<String, String> clusterConn = (StatefulRedisClusterConnection<String, String>) conn;
long beginTime = System.currentTimeMillis();
// 1. 获取redis key所在集群分片的master节点id
int keySlot = SlotHash.getSlot(resource);
String masterId = redisClusterClient.getPartitions().getPartitionBySlot(keySlot).getNodeId();
// 2. 操作redis master节点,获取分布式锁
RedisCommands<String, String> masterCmd = clusterConn.getConnection(masterId).sync();
String masterResult = masterCmd.set(resource, sign, SetArgs.Builder.nx().px(maxLockDuration));
masterSuccess = "OK".equals(masterResult);
if (!masterSuccess) {
return false;
}
// 3. 等待master节点同步数据至slave节点
Map<RedisClusterNode, RedisAsyncCommands<String, String>> slaveNodes =
clusterConn.async()
.slaves(node -> masterId.equals(node.getSlaveOf()))
.asMap();
int slaveCount = slaveNodes.size();
if (slaveCount > 0) {
int minReplicaCount = Math.min(slaveCount, MIN_REPLICA_COUNT);
masterCmd.waitForReplication(minReplicaCount, maxWaitTime);
}
...
}