目录
介绍
现在项目一般都是使用分布式集群部署,对后台业务数据的某些操作需要考虑加锁的问题,而jdk的synchronize加锁机制已经不适合做集群部署的操作,因为synchronize关键字只是针对于单体部署的单台虚拟机有用。考虑到现在系统使用redis做缓存比较高效,此处推荐使用redis下的分布式锁redisson进行加锁操作。而Redisson解决了Redis基于 setnx 实现的分布式锁存在的一些问题:
不可重入
:同一个线程无法多次获取同一把锁(eg:方法A调用方法B,在方法A中先获取锁,然后去调用方法B,方法B也需要获取同一把锁,这种情况下如果锁不可重入,方法B显然获取不到锁,会出现死锁的情况)不可重试
:获取锁只尝试一次就返回 false,没有重试机制超时释放
:超时释放虽然能够避免死锁,但如果业务执行执行时间较长导致锁释放,会存在安全隐患主从一致性
:主从数据同步存在延迟,比如:线程在主节点获取了锁,尚未同步给从节点时主节点宕机,此时会选一个从节点作为新的主节点,这个从节点由于没有完成同步不存在锁的标识,此时其他线程可以趁虚而入拿到锁,这就造成多个线程同时拿到锁,就会出现安全问题)
引入redis、redisson依赖
<!--redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.3.2.RELEASE</version> </dependency> <!--redisson依赖--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.4</version> </dependency> <!--使用redis时需要此jar包--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
配置redis连接信息
spring: redis: database: 0 host: xx.xx.xx.xx port: 1316 password: xxxx timeout: 3000 lettuce: pool: max-active: 20 max-idle: 10 max-wait: -1 min-idle: 0
配置RedisConfig
@Configuration public class RedisConfig { Logger logger = LoggerFactory.getLogger(RedisConfig.class); @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory) { logger.debug("redisTemplate实例化 {}"); RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<Object>(Object.class); // key的序列化采用StringRedisSerializer redisTemplate.setKeySerializer(new StringRedisSerializer()); // value值的序列化采用fastJsonRedisSerializer redisTemplate.setValueSerializer(fastJsonRedisSerializer); // hash的key也采用String的序列化方式 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // hash的value序列化方式采用fastJsonRedisSerializer redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
配置RedissonConfig
配置RedissonClient客户端,用于加锁操作,使用@Bean注解,当程序启动时加载到spring容器中供后期使用,配置客户端需要根据redis服务的模式配置,有集群、主从、哨兵等模式,具体配置参考官网:2. 配置方法 · redisson/redisson Wiki · GitHub;此处使用的单节点模式配置。
@Configuration public class RedissonConfig { //redis相关配置 @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private String redisPort; @Value("${spring.redis.database}") private int database; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.timeout}") private int timeout; //创建redisson客户端,此时默认使用单节点 @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer().setAddress("redis://"+redisHost+":"+redisPort); config.useSingleServer().setDatabase(database); config.useSingleServer().setPassword(password); config.useSingleServer().setTimeout(timeout); RedissonClient redisson = Redisson.create(config); return redisson; } }
加解锁锁操作
操作特别简单,通过RedissonClient获取锁,然后调用lock即可加锁,解锁使用unlock即可。
//在需要使用分布式锁的类里面注入RedissonClient客户端 @Autowired RedissonClient redissonClient; //根据锁名称获取锁 RLock lock = redissonClient.getLock("anyLock"); //加锁 // 最常见的使用方法 lock.lock(); // 加锁以后10秒钟自动解锁 lock.lock(10, TimeUnit.SECONDS); // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { try { ... } finally { //解锁 lock.unlock(); } }