成都网站建设方案服务,赣州网站建设百家号,哪些平台可以发广告,WordPress在线转义分布式锁的问题
基于setnx的分布式锁实现起来并不复杂#xff0c;不过却存在一些问题。
锁误删问题
第一个问题就是锁误删问题#xff0c;目前释放锁的操作是基于DEL#xff0c;但是在极端情况下会出现问题。
例如#xff0c;有线程1获取锁成功#xff0c;并且执行完任…分布式锁的问题
基于setnx的分布式锁实现起来并不复杂不过却存在一些问题。
锁误删问题
第一个问题就是锁误删问题目前释放锁的操作是基于DEL但是在极端情况下会出现问题。
例如有线程1获取锁成功并且执行完任务正准备释放锁
总结一下误删的原因归根结底是因为什么 超时释放 判断锁标示、删除锁两个动作不是原子操作
超时释放问题
除了上述问题以外分布式锁还会碰到一些其它问题 锁的重入问题同一个线程多次获取锁的场景目前不支持可能会导致死锁 锁失败的重试问题获取锁失败后要不要重试目前是直接失败不支持重试 Redis主从的一致性问题由于主从同步存在延迟当线程在主节点获取锁后从节点可能未同步锁信息。如果此时主宕机会出现锁失效情况。此时会有其它线程也获取锁成功。从而出现并发安全问题。 ... 当然上述问题并非无法解决只不过会比较麻烦。例如 原子性问题可以利用Redis的LUA脚本来编写锁操作确保原子性 超时问题利用WatchDog看门狗机制获取锁成功时开启一个定时任务在锁到期前自动续期避免超时释放。而当服务宕机后WatchDog跟着停止运行不会导致死锁。 锁重入问题可以模拟Synchronized原理放弃setnx而是利用Redis的Hash结构来记录锁的持有者以及重入次数获取锁时重入次数1释放锁是重入次数-1次数为0则锁删除 主从一致性问题可以利用Redis官网推荐的RedLock机制来解决
快速入门
首先引入依赖
!--redisson--
dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactId
/dependency
然后配置 Configurationpublic class RedisConfig {Beanpublic RedissonClient redissonClient() {// 配置类Config config new Config();// 添加redis地址这里添加了单点的地址也可以使用config.useClusterServers()添加集群地址 config.useSingleServer().setAddress(redis://192.168.150.101:6379).setPassword(123321);// 创建客户端return Redisson.create(config);}}
使用
看门狗机制不能设置失效时间设置失效时间看门狗会失效
lock.unlock() 虽然看起来简单但是底层释放的是当前锁不会释放其他的锁 Autowiredprivate RedissonClient redissonClient;Testvoid testRedisson() throws InterruptedException {// 1.获取锁对象指定锁名称RLock lock redissonClient.getLock(anyLock);try {// 2.尝试获取锁参数waitTime、leaseTime、时间单位// 设置失效时间看门狗会失效boolean isLock lock.tryLock();if (!isLock) {// 获取锁失败处理 ..} else {// 获取锁成功处理}} finally {// 4.释放锁lock.unlock();}}
利用Redisson获取锁时可以传3个参数 waitTime获取锁的等待时间。当获取锁失败后可以多次重试直到waitTime时间耗尽。waitTime默认-1即失败后立刻返回不重试。 leaseTime锁超时释放时间。默认是30同时会利用WatchDog来不断更新超时时间。需要注意的是如果手动设置leaseTime值会导致WatchDog失效。 TimeUnit时间单位
集成
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.tianji.common.autoconfigure.redisson.aspect.LockAspect;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.time.Duration;
import java.util.ArrayList;
import java.util.List;Slf4j
ConditionalOnClass({RedissonClient.class, Redisson.class})
Configuration
EnableConfigurationProperties(RedisProperties.class)
public class RedissonConfig {private static final String REDIS_PROTOCOL_PREFIX redis://;private static final String REDISS_PROTOCOL_PREFIX rediss://;BeanConditionalOnMissingBeanpublic LockAspect lockAspect(RedissonClient redissonClient){return new LockAspect(redissonClient);}BeanConditionalOnMissingBean // 当spring容器中没有redissionClient 对象才会创建public RedissonClient redissonClient(RedisProperties properties){log.debug(尝试初始化RedissonClient);// 1.读取Redis配置RedisProperties.Cluster cluster properties.getCluster();RedisProperties.Sentinel sentinel properties.getSentinel();String password properties.getPassword();int timeout 3000;Duration d properties.getTimeout();if(d ! null){timeout Long.valueOf(d.toMillis()).intValue();}// 2.设置Redisson配置Config config new Config();if(cluster ! null !CollectionUtil.isEmpty(cluster.getNodes())){// 集群模式config.useClusterServers().addNodeAddress(convert(cluster.getNodes())).setConnectTimeout(timeout).setPassword(password);}else if(sentinel ! null !StrUtil.isEmpty(sentinel.getMaster())){// 哨兵模式config.useSentinelServers().setMasterName(sentinel.getMaster()).addSentinelAddress(convert(sentinel.getNodes())).setConnectTimeout(timeout).setDatabase(0).setPassword(password);}else{// 单机模式config.useSingleServer().setAddress(String.format(redis://%s:%d, properties.getHost(), properties.getPort())).setConnectTimeout(timeout).setDatabase(0).setPassword(password);}// 3.创建Redisson客户端return Redisson.create(config);}private String[] convert(ListString nodesObject) {ListString nodes new ArrayList(nodesObject.size());for (String node : nodesObject) {if (!node.startsWith(REDIS_PROTOCOL_PREFIX) !node.startsWith(REDISS_PROTOCOL_PREFIX)) {nodes.add(REDIS_PROTOCOL_PREFIX node);} else {nodes.add(node);}}return nodes.toArray(new String[0]);}
}几个关键点 这个配置上添加了条件注解ConditionalOnClass({RedissonClient.class, Redisson.class}) 也就是说引用了Redisson依赖这套配置就会生效。不引入Redisson依赖配置自然不会生效从而实现按需引入。 RedissonClient的配置无需自定义Redis地址而是直接基于SpringBoot中的Redis配置即可。而且不管是Redis单机、Redis集群、Redis哨兵模式都可以支持 所以在微服务中应用的步骤 引入Redisson依赖 注入RedissonClient使用分布式锁