redis分布式锁
import java.util.UUID;
@Slf4j
@Component
public class RedisLockUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 获得锁
*
* @param lockName 锁的名词
* @param lockTimeout 锁本身的过期时间
* @return
*/
public String acquireLock(String lockName, long lockTimeout) {
String identifier = UUID.randomUUID().toString();//保证释放锁的时候是同一个持有锁的人
String lockKey = "lock:" + lockName;
int lockExpire = (int) (lockTimeout / 1000);
return (String) redisTemplate.execute((RedisCallback<String>) redisConnection -> {
Object nativeConnection = redisConnection.getNativeConnection();
if (nativeConnection instanceof JedisCluster) {
JedisCluster jedisCluster = (JedisCluster) nativeConnection;
//获取锁的限定时间
if (jedisCluster.setnx(lockKey, identifier) == 1) { //设置值成功
jedisCluster.expire(lockKey, lockExpire); //设置超时时间
return identifier; //获得锁成功
}
if (jedisCluster.ttl(lockKey) == -1) {
jedisCluster.expire(lockKey, lockExpire); //设置超时时间
}
}
// 单机模式
else if (nativeConnection instanceof Jedis) {
Jedis jedis = (Jedis) nativeConnection;
//获取锁的限定时间
if (jedis.setnx(lockKey, identifier) == 1) { //设置值成功
jedis.expire(lockKey, lockExpire); //设置超时时间
return identifier; //获得锁成功
}
if (jedis.ttl(lockKey) == -1) {
jedis.expire(lockKey, lockExpire); //设置超时时间
}
}
return null;
});
}
/**
* 释放锁
*
* @param lockName
* @param identifier
* @return
*/
public boolean releaseLockWithLua(String lockName, String identifier) {
log.info(lockName + "开始释放锁:" + identifier);
return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Long rs = 0l;
String lockKey = "lock:" + lockName;
String lua = "if redis.call(\"get\",KEYS[1])==ARGV[1] then " + "return redis.call(\"del\",KEYS[1]) " + "else return 0 end";
Object nativeConnection = redisConnection.getNativeConnection();
// 集群模式
if (nativeConnection instanceof JedisCluster) {
JedisCluster jedisCluster = (JedisCluster) nativeConnection;
rs = (Long) jedisCluster.eval(lua, 1, new String[]{lockKey, identifier});
}
// 单机模式
else if (nativeConnection instanceof Jedis) {
Jedis jedis = (Jedis) nativeConnection;
rs = (Long) jedis.eval(lua, 1, new String[]{lockKey, identifier});
}
if (rs.intValue() > 0) {
return true;
}
return false;
});
}
}
com.tencent.tsf spring-cloud-tsf-dependencies 1.14.1-Finchley-RELEASE
<properties>
<commons.lang3.version>3.4</commons.lang3.version>
<targetJavaProject>${basedir}/src/main/java</targetJavaProject>
<!-- XML生成路径 -->
<targetResourcesProject>${basedir}/src/main/resources</targetResourcesProject>
<targetXMLPackage>mapper</targetXMLPackage>
<!-- 依赖版本 -->
<mapper.version>3.4.0</mapper.version>
<mybatis.version>3.3.1</mybatis.version>
<mybatis.spring.version>1.2.4</mybatis.spring.version>
<pagehelper.version>4.1.1</pagehelper.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-consul-config</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-consul-discovery</artifactId>
</dependency>
<!-- <dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-auth</artifactId>
</dependency>-->
<!-- TSF Rate Limit 限流 -->
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-ratelimit</artifactId>
</dependency>
<!-- TSF Ribbon 客户端路由 -->
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-route</artifactId>
</dependency>
<!-- TSF Logger 日志 -->
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-logger</artifactId>
</dependency>
<!-- TSF swagger -->
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-swagger</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.tsf</groupId>
<artifactId>spring-cloud-tsf-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-data-mongodb</artifactId>-->
<!--</dependency>-->
<!-- 使用分布式配置自动刷新功能,需要显示添加actuator的依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.3</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!--用于测试的,本例可省略-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-base</artifactId>
<version>r03</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-collections</artifactId>
<version>r03</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Apache Commons upload -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.3</version>
</dependency>
<!-- 模板引擎 给mybatisplus自动生成用的-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<!--导出文件-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!--ui-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-ui</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
这里用的 腾讯云的 1.14.1-Finchley-RELEASE的版本
redis的配置
@Bean
CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
System.out.println(connectionFactory.getClass());
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//设置CacheManager的值序列化方式为JdkSerializationRedisSerializer,但其实RedisCacheConfiguration默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value,所以以下注释代码为默认实现
//ClassLoader loader = this.getClass().getClassLoader();
//JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader);
//RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);
//RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//设置默认超过期时间是30秒
defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
//初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
return cacheManager;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
System.out.println(factory.getClass());
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
这样写其实有问题的
1.问题是腾讯云默认会封装 redis的RedisConnectionFactory
扒腾讯的源码发现这离使用注解,替换原来默认的 org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
这里getclass发现就是腾讯的封装类AsyncTracingRedisCommands而不是jedis]
com.tencent.tsf.sleuth.instrument.redis.lettuceStandalone.AsyncTracingRedisCommands
解决办法 自己定义RedisConnectionFactory
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import com.tencent.tsf.sleuth.instrument.redis.lettuceStandalone.AsyncTracingRedisCommands;
import java.time.Duration;
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.pool.timeout}")
private int redisTimeout;
@Value("${spring.redis.password}")
private String redisAuth;
@Value("${spring.redis.database}")
private int redisDb;
@Value("${spring.redis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.pool.max-wait}")
private int maxWait;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.min-idle}")
private int minIdle;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxWaitMillis(maxWait);
poolConfig.setMinIdle(minIdle);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(false);
poolConfig.setTestWhileIdle(true);
JedisClientConfiguration clientConfig = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and().readTimeout(Duration.ofMillis(redisTimeout)).build();
// 单点redis
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
// 哨兵redis
// RedisSentinelConfiguration redisConfig = new RedisSentinelConfiguration();
// 集群redis
// RedisClusterConfiguration redisConfig = new RedisClusterConfiguration();
redisConfig.setHostName(redisHost);
redisConfig.setPassword(RedisPassword.of(redisAuth));
redisConfig.setPort(redisPort);
redisConfig.setDatabase(redisDb);
return new JedisConnectionFactory(redisConfig, clientConfig);
}
@Bean
CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
System.out.println(connectionFactory.getClass());
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//设置CacheManager的值序列化方式为JdkSerializationRedisSerializer,但其实RedisCacheConfiguration默认就是使用StringRedisSerializer序列化key,JdkSerializationRedisSerializer序列化value,所以以下注释代码为默认实现
//ClassLoader loader = this.getClass().getClassLoader();
//JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader);
//RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);
//RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//设置默认超过期时间是30秒
defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
//初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
return cacheManager;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
System.out.println(factory.getClass());
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}