现在开发的微服务项目对分布式锁使用比较多,故此对自定实现分布式锁作一些记录。
* 实际项目使用时直接使用Redisson框架的RLock锁即可。
创建springboot redis项目
springboot-redis-demo
添加依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</exclusion>
</exclusions>
</dependency>
添加配置项application.properties
spring.cache.type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=111111
spring.redis.timeout=0
spring.redis.ssl=false
spring.redis.jedis.pool.max-active=300
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=300
spring.redis.jedis.pool.min-idle=1
#spring.redis.cluster.nodes=127.0.0.1:6080
#spring.redis.cluster.max-redirects=10
#spring.redis.sentinel.master=127.0.0.1:6080
#spring.redis.sentinel.nodes=192.168.1.248:26379,192.168.1.249:26379,192.168.1.250:26379
#spring.data.redis.repositories.enabled=true
创建redis配置类
配置类添加@EnableFeignClients注解
@Configuration
@EnableCaching
public class RediseConfig extends CachingConfigurerSupport {
@Autowired
private RedisProperties redisProperties;
@Autowired
private RedisProperties.Pool redisPropertiesPool;
@Bean
@ConditionalOnMissingBean(name = "poolConfig")
public JedisPoolConfig poolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setBlockWhenExhausted(true);
jedisPoolConfig.setTestOnBorrow(true);
jedisPoolConfig.setMaxTotal(redisPropertiesPool.getMaxActive());
jedisPoolConfig.setMaxIdle(redisPropertiesPool.getMaxIdle());
jedisPoolConfig.setMinIdle(redisPropertiesPool.getMinIdle());
jedisPoolConfig.setMaxWaitMillis(redisPropertiesPool.getMaxWait());// 100000
jedisPoolConfig.setTestOnBorrow(true);
jedisPoolConfig.setTestOnReturn(true);
//Idle时进行连接扫描
jedisPoolConfig.setTestWhileIdle(true);
//表示idle object evitor两次扫描之间要sleep的毫秒数
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
//表示idle object evitor每次扫描的最多的对象数
jedisPoolConfig.setNumTestsPerEvictionRun(10);
//表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
jedisPoolConfig.setMinEvictableIdleTimeMillis(60000);
return jedisPoolConfig;
}
@Bean
@ConditionalOnMissingBean(name = "jedisPool")
public JedisPool jedisPool(JedisPoolConfig poolConfig) {
return new JedisPool(poolConfig,
redisProperties.getHost(),
redisProperties.getPort(),
redisProperties.getTimeout().getNano(),
redisProperties.getPassword());
}
@Bean
@ConditionalOnMissingBean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig) {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
//设置redis服务器的host或者ip地址
redisStandaloneConfiguration.setHostName(redisProperties.getHost());
redisStandaloneConfiguration.setPort(redisProperties.getPort());
redisStandaloneConfiguration.setPassword(redisProperties.getPassword());
redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
//获得默认的连接池构造
//这里需要注意的是,edisConnectionFactoryJ对于Standalone模式的没有(RedisStandaloneConfiguration,JedisPoolConfig)的构造函数,对此
//我们用JedisClientConfiguration接口的builder方法实例化一个构造器,还得类型转换
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcf = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
//修改我们的连接池配置
jpcf.poolConfig(poolConfig);
//通过构造器来构造jedis客户端配置
JedisClientConfiguration jedisClientConfiguration = jpcf.build();
return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
}
@Primary
@Bean
public CacheManager cacheManager(@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
//.entryTtl(Duration.ofHours(1)) // 设置缓存有效期一小时
//.disableCachingNullValues(); // 不缓存空值
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
/**
* 实例化 RedisTemplate 对象
* @return
*/
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = this.jackson2JsonRedisSerializer();
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(redisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean(name = "stringRedisTemplate")
@ConditionalOnMissingBean(name = "stringRedisTemplate")
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = this.jackson2JsonRedisSerializer();
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(redisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
private Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
}
/**
* 对hash类型的数据操作
* @param redisTemplate
* @return
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 对redis字符串类型数据操作
* @param redisTemplate
* @return
*/
@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* 对链表类型的数据操作
* @param redisTemplate
* @return
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* 对无序集合类型的数据操作
* @param redisTemplate
* @return
*/
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* 对有序集合类型的数据操作
* @param redisTemplate
* @return
*/
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
@Bean
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
@Bean
public RedisLockRegistry redisLockRegistry(@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "redis_locks", 6000L);
}
@Bean
public Jedis jedis(@Qualifier("jedisPool") JedisPool jedisPool) {
return jedisPool.getResource();
}
}
创建RedisLockUtil工具类
@Slf4j
public class RedisLockUtil implements Lock {
private Jedis jedis = SpringUtils.getBean("jedis");
private static String REDIS_LOCK_KEY = "REDIS_LOCK_KEY_";
private static String REQUEST_ID = "redis_lock_id_";
/**
* 锁超时时间,防止线程在入锁以后,无限的执行等待
*/
private int EXPIRE_TIME_MSECS = 60 * 1000;
private static final Long RELEASE_SUCCESS = 1L;
public RedisLockUtil(String key){
this.REDIS_LOCK_KEY = this.REDIS_LOCK_KEY + key;
}
public RedisLockUtil(String key, String val){
this.REDIS_LOCK_KEY = this.REDIS_LOCK_KEY + key;
this.REQUEST_ID = this.REQUEST_ID + val;
}
@Override
public boolean tryLock() {
try {
if (jedis.exists(REDIS_LOCK_KEY)) {
Long setNx = jedis.setnx(REDIS_LOCK_KEY, REQUEST_ID);
if (setNx == 1) {
return Boolean.TRUE;
}
}
} catch (Exception e) {
log.error("redis tryLock Exception", e);
}
return Boolean.FALSE;
}
@Override
public void lock() {
try {
if (!tryLock()) {
SetParams params = new SetParams();
params.px(EXPIRE_TIME_MSECS).nx();
jedis.set(REDIS_LOCK_KEY, REQUEST_ID, params);
log.info(">>>>>>redis lock: {}", jedis.get(REDIS_LOCK_KEY));
}
} catch (Exception e) {
log.error("redis lock Exception", e);
}
}
@Override
public boolean unlock() {
try {
if (jedis.exists(REDIS_LOCK_KEY)) {
String delLock = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object result = jedis.eval(delLock, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(REQUEST_ID));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
} else {
return true;
}
} catch (Exception e) {
log.error("redis unlock Exception", e);
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
}
要确保获得锁时添加锁的自动释放时间是原子操作
SetParams params = new SetParams();
params.px(EXPIRE_TIME_MSECS).nx();
jedis.set(REDIS_LOCK_KEY, REQUEST_ID, params);
xn:意思是SET IF NOT EXIST,即当key不存在时,就进行set操作;若key已经存在,则不做任何操作;
px:意思是给key加一个EXPIRE_TIME_MSECS的过期时间。
创建unit test测试类
@SpringBootTest(classes = SpringbootRedisDemoApplication.class)
public class RedisTests {
@Autowired
private RedisUtils redisUtil;
@Test
public void redisLockUtilTest() throws InterruptedException {
RedisLockUtil redisLockUtil = new RedisLockUtil("testLock1", "1");
redisLockUtil.lock();
TimeUnit.SECONDS.sleep(10);
redisLockUtil.unlock();
}
}
测试结果
在锁定的时候redis中可以看到锁的状态
源码示例:https://gitee.com/lion123/springboot-redis-demo
* redis分布式锁过期时间到了但业务没执行完怎么办?
直接使用Redisson框架的RLock,redisson RLock锁获取成功后,就会去注册一个定时任务(一个后台线程)来每隔10秒检查一下锁的过期时间(加锁的锁key默认生存时间是30秒),如果当前线程还持有锁key,那么就会不断的延长锁key的生存时间。