Redis 从入门到精通【应用篇】之SpringBoot Redis 多数据源集成支持哨兵模式Cluster集群模式、单机模式
文章目录
0.前言
说明
大家都知道Redis在6.0版本之前是单线程工作的,这导致在一个项目中有大量读写操作的情况下,Redis单实例的性能被其他业务长时间占据,导致部分业务出现延迟现象,为了解决这个问题,部分公司项目选择使用多个Redis实例分别存储不同的业务数据和使用场景,比如IoT网关写入的数据,可以单独拆分一个Redis实例去使用,其他业务使用一个Redis实例。用多个Redis实例 可以提高Redis的性能。Redis是一种基于内存的缓存数据库,内存容量是其性能的瓶颈。当项目中的数据量较大时,单个Redis实例可能无法承载所有数据,导致性能下降。而使用多个Redis实例可以将数据分散到多个实例中,从而提高Redis的整体性能。
这就导致在某些业务场景下,一个项目工程,同时要使用这两个Redis实例的数据,这就是本文要解决的问题。
本文通过写一个Redis 多数据源组件 Starter 来解决上面的问题,支持Redis 多数据源,可集成配置哨兵模式、Cluster集群模式、单机模式。如果单实例配置哨兵模式,请参阅我之前的博客 《SpringBoot Redis 使用Lettuce和Jedis配置哨兵模式》
项目结构
Pom 依赖
如下,可能有多余的,根据项目具体情况删减。再就是需要使用
Springboot parent
<spring-boot-dependencies.version>2.7.12</spring-boot-dependencies.version>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
1. 配置
1.1 通用配置()
设置主redis的标识
很关键
# 示例custom.primary.redis.key=user
连接池相关配置
此配置为通用配置所有类型的链接模式都可以配置,不配置走Springboot 默认配置。
spring.redis.xxx.timeout =3000
spring.redis.xxx.maxTotal =50
spring.redis.xxx.maxIdle =50
spring.redis.xxx.minIdle =2
spring.redis.xxx.maxWaitMillis =10000
spring.redis.xxx.testOnBorrow = False
1.2 单例模式配置
# 第1个Redis 实例 用于用户体系,我们取标识为user
spring.redis.user.host =127.0.0.1
spring.redis.user.port =6380
spring.redis.user.password = 密码
spring.redis.user.database =0# 第2个Redis 实例用于IoT体系
spring.redis.iot.host =127.0.0.1
spring.redis.iot.port =6390
spring.redis.iot.password = 密码
spring.redis.iot.database =0# 第3个Redis 实例用于xxx
spring.redis.xxx.host =127.0.0.1
spring.redis.xxx.port =6390
spring.redis.xxx.password = 密码
spring.redis.xxx.database =0
1.3 哨兵模式配置
多个Redis数据库实例的情况下,将下面配置项多配置几个。
spring.redis.xxx1.sentinel.master=mymaster1
spring.redis.xxx1.sentinel.nodes=ip:端口,ip:端口
spring.redis.xxx1.password = bD945aAfeb422E22AbAdFb9D2a22bEDd
spring.redis.xxx1.database =0
spring.redis.xxx1.timeout =3000#第二个spring.redis.xxx2.sentinel.master=mymaster2
spring.redis.xxx2.sentinel.nodes=ip:端口,ip:端口
spring.redis.xxx2.password = bD945aAfeb422E22AbAdFb9D2a22bEDd
spring.redis.xxx2.database =0
spring.redis.xxx2.timeout =3000
1.4 集群模式配置(集群模式不支持设置database)
spring.redis.xxx1.cluster.nodes=ip1:端口,ip2:端口,ip3:端口,ip4:端口,ip5:端口,ip6:端口
spring.redis.xxx1.cluster.max-redirects=5
spring.redis.xxx1.password = 密码
spring.redis.xxx1.timeout =3000
2. 代码实例
2.1.CustomRedisConfig
根据配置文件配置项,创建Redis多个数据源的
RedisTemplate
。
主要思想为,
- 在服务启动过程中读取多数据源配置文件,将多数据源的配置读取到
// 定义静态Map变量redis,用于存储Redis配置参数protectedstaticMap<String,Map<String,String>> redis =newHashMap<>();
- 根据多数据源配置创建不同类型的
Configuration
privateRedisStandaloneConfigurationbuildStandaloneConfig(Map<String,String> param){//...省略}
privateRedisSentinelConfigurationbuildSentinelConfig(Map<String,String> param){//...省略}
privateRedisClusterConfigurationbuildClusterConfig(Map<String,String> param){//...省略}
- 根据 不同类型的创建
RedisConnectionFactory
publicRedisConnectionFactorybuildLettuceConnectionFactory(String redisKey,Map<String,String> param,GenericObjectPoolConfig genericObjectPoolConfig){...}
4.最后遍历上面我们配置的配置文件调用
buildCustomRedisService(k, redisTemplate, stringRedisTemplate);
将创建的不同的RedisTemplate Bean 然后注入到Spring容器中
CustomRedisConfig 源码
源码中涉及的Springboot 相关知识在此处就不做赘婿,需要了解,可以参考我的《SpringBoot 源码解析系列》
InitializingBean,
ApplicationContextAware
,
BeanPostProcessor
packagecom.iceicepip.project.common.redis;importcom.iceicepip.project.common.redis.util.AddressUtils;importcom.fasterxml.jackson.annotation.JsonAutoDetect;importcom.fasterxml.jackson.annotation.PropertyAccessor;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.apache.commons.lang3.StringUtils;importorg.apache.commons.pool2.impl.GenericObjectPoolConfig;importorg.springframework.beans.MutablePropertyValues;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.beans.factory.config.BeanPostProcessor;importorg.springframework.beans.factory.config.ConstructorArgumentValues;importorg.springframework.beans.factory.support.DefaultListableBeanFactory;importorg.springframework.beans.factory.support.GenericBeanDefinition;importorg.springframework.boot.autoconfigure.AutoConfiguration;importorg.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.context.annotation.Bean;importorg.springframework.core.env.MapPropertySource;importorg.springframework.core.env.StandardEnvironment;importorg.springframework.data.redis.connection.*;importorg.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;importorg.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;importorg.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;importjava.time.Duration;importjava.util.*;@AutoConfiguration@ConfigurationProperties(prefix ="spring")publicclassCustomRedisConfigimplementsInitializingBean,ApplicationContextAware,BeanPostProcessor{// 定义静态Map变量redis,用于存储Redis配置参数protectedstaticMap<String,Map<String,String>> redis =newHashMap<>();// 在代码中作为Redis的主数据源的标识@Value("${customer.primary.redis.key}")privateString primaryKey;@Override// 实现InitializingBean接口的方法,用于在属性被注入后初始化Redis连接工厂和Redis模板publicvoidafterPropertiesSet(){
redis.forEach((k, v)->{// 如果当前的Redis主键等于注入的主键,则将Redis配置参数加入到属性源中if(Objects.equals(k,primaryKey)){Map<String,Object> paramMap =newHashMap<>(4);
v.forEach((k1,v1)-> paramMap.put("spring.redis."+k1,v1));MapPropertySource mapPropertySource =newMapPropertySource("redisAutoConfigProperty", paramMap);((StandardEnvironment)applicationContext.getEnvironment()).getPropertySources().addLast(mapPropertySource);}// 创建Redis连接池配置对象和连接工厂对象GenericObjectPoolConfig genericObjectPoolConfig =buildGenericObjectPoolConfig(k, v);RedisConnectionFactory lettuceConnectionFactory =buildLettuceConnectionFactory(k, v, genericObjectPoolConfig);// 创建Redis模板对象和字符串模板对象,并调用方法创建自定义Redis服务对象RedisTemplate redisTemplate =buildRedisTemplate(k, lettuceConnectionFactory);StringRedisTemplate stringRedisTemplate =buildStringRedisTemplate(k, lettuceConnectionFactory);buildCustomRedisService(k, redisTemplate, stringRedisTemplate);});}// 创建Redis主数据源 RedisTemplate@BeanpublicRedisTemplate<Object,Object>redisTemplate(){Map<String,String> redisParam = redis.get(primaryKey);GenericObjectPoolConfig genericObjectPoolConfig =buildGenericObjectPoolConfig(primaryKey, redisParam);RedisConnectionFactory lettuceConnectionFactory =buildLettuceConnectionFactory(primaryKey, redisParam, genericObjectPoolConfig);RedisTemplate<Object,Object> template =newRedisTemplate();
template.setConnectionFactory(lettuceConnectionFactory);return template;}// 创建Redis主数据源 StringRedisTemplate @Bean@ConditionalOnMissingBeanpublicStringRedisTemplatestringRedisTemplate(){Map<String,String> redisParam = redis.get(primaryKey);GenericObjectPoolConfig genericObjectPoolConfig =buildGenericObjectPoolConfig(primaryKey, redisParam);RedisConnectionFactory lettuceConnectionFactory =buildLettuceConnectionFactory(primaryKey, redisParam, genericObjectPoolConfig);StringRedisTemplate template =newStringRedisTemplate();
template.setConnectionFactory(lettuceConnectionFactory);return template;}// 创建自定义Redis服务对象privatevoidbuildCustomRedisService(String k,RedisTemplate redisTemplate,StringRedisTemplate stringRedisTemplate){ConstructorArgumentValues constructorArgumentValues =newConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, stringRedisTemplate);
constructorArgumentValues.addIndexedArgumentValue(1, redisTemplate);// 将来使用的时候Redis对象的beanName,区分多个数据源setCosBean(k +"Redis",CustomRedisService.class, constructorArgumentValues);}// 创建StringRedisTemplateprivateStringRedisTemplatebuildStringRedisTemplate(String k,RedisConnectionFactory lettuceConnectionFactory){ConstructorArgumentValues constructorArgumentValues =newConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, lettuceConnectionFactory);setCosBean(k +"StringRedisTemplate",StringRedisTemplate.class, constructorArgumentValues);returngetBean(k +"StringRedisTemplate");}// 创建Redis模板对象privateRedisTemplatebuildRedisTemplate(String k,RedisConnectionFactory lettuceConnectionFactory){// 如果已经存在Redis模板对象,则直接返回该对象if(applicationContext.containsBean(k +"RedisTemplate")){returngetBean(k +"RedisTemplate");}// 创建Redis序列化器对象Jackson2JsonRedisSerializer serializer =newJackson2JsonRedisSerializer(Object.class);ObjectMapper mapper =newObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);// 创建Redis模板对象,并设置连接池配置和序列化器等属性Map original =newHashMap<>(2);
original.put("connectionFactory", lettuceConnectionFactory);
original.put("valueSerializer", serializer);
original.put("keySerializer",newStringRedisSerializer());
original.put("hashKeySerializer",newStringRedisSerializer());
original.put("hashValueSerializer", serializer);// 将来使用RedisTemplate的地方只需要用注解制定beanName 即可获取到每个Redis实例的操作工具类setBean(k +"RedisTemplate",RedisTemplate.class, original);returngetBean(k +"RedisTemplate");}}publicGenericObjectPoolConfigbuildGenericObjectPoolConfig(String redisKey,Map<String,String> param){if(applicationContext.containsBean(redisKey +"GenericObjectPoolConfig")){returngetBean(redisKey +"GenericObjectPoolConfig");}Integer maxIdle =StringUtils.isEmpty(param.get(CustomRedisConfigConstant.REDIS_MAXIDLE))?GenericObjectPoolConfig.DEFAULT_MAX_IDLE:Integer.valueOf(param.get(CustomRedisConfigConstant.REDIS_MAXIDLE));Integer minIdle =StringUtils.isEmpty(param.get(CustomRedisConfigConstant.REDIS_MINIDLE))?GenericObjectPoolConfig.DEFAULT_MIN_IDLE:Integer.valueOf(param.get(CustomRedisConfigConstant.REDIS_MINIDLE));Integer maxTotal =StringUtils.isEmpty(param.get(CustomRedisConfigConstant.REDIS_MAXTOTAL))?GenericObjectPoolConfig.DEFAULT_MAX_TOTAL:Integer.valueOf(param.get(CustomRedisConfigConstant.REDIS_MAXTOTAL));Long maxWaitMillis =StringUtils.isEmpty(param.get(CustomRedisConfigConstant.REDIS_MAXWAITMILLIS))?-1L:Long.valueOf(param.get(CustomRedisConfigConstant.REDIS_MAXWAITMILLIS));Boolean testOnBorrow =StringUtils.isEmpty(param.get(CustomRedisConfigConstant.REDIS_TESTONBORROW))?Boolean.FALSE:Boolean.valueOf(param.get(CustomRedisConfigConstant.REDIS_TESTONBORROW));Map original =newHashMap<>(8);
original.put("maxTotal", maxTotal);
original.put("maxIdle", maxIdle);
original.put("minIdle", minIdle);
original.put("maxWaitMillis",maxWaitMillis);
original.put("testOnBorrow",testOnBorrow);setBean(redisKey +"GenericObjectPoolConfig",GenericObjectPoolConfig.class, original);returngetBean(redisKey +"GenericObjectPoolConfig");}publicRedisConnectionFactorybuildLettuceConnectionFactory(String redisKey,Map<String,String> param,GenericObjectPoolConfig genericObjectPoolConfig){if(applicationContext.containsBean(redisKey +"redisConnectionFactory")){returngetBean(redisKey +"redisConnectionFactory");}String timeout =StringUtils.defaultIfEmpty(param.get(CustomRedisConfigConstant.REDIS_TIMEOUT),"3000");LettuceClientConfiguration clientConfig =LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(Long.valueOf(timeout))).poolConfig(genericObjectPoolConfig).build();Object firstArgument =null;if(this.isCluster(param)){RedisClusterConfiguration clusterConfiguration =buildClusterConfig(param);
firstArgument = clusterConfiguration;}elseif(this.isSentinel(param)){RedisSentinelConfiguration sentinelConfiguration =buildSentinelConfig(param);
firstArgument = sentinelConfiguration;}else{RedisStandaloneConfiguration standaloneConfiguration =buildStandaloneConfig(param);
firstArgument = standaloneConfiguration;}ConstructorArgumentValues constructorArgumentValues =newConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, firstArgument);
constructorArgumentValues.addIndexedArgumentValue(1, clientConfig);setCosBean(redisKey +"redisConnectionFactory",LettuceConnectionFactory.class, constructorArgumentValues);returngetBean(redisKey +"redisConnectionFactory");}/**
* 如果配置的是哨兵模式
* @return
*/privatebooleanisSentinel(Map<String,String> param){String sentinelMaster = param.get(CustomRedisConfigConstant.REDIS_SENTINEL_MASTER);String sentinelNodes = param.get(CustomRedisConfigConstant.REDIS_SENTINEL_NODES);returnStringUtils.isNotEmpty(sentinelMaster)&&StringUtils.isNotEmpty(sentinelNodes);}/**
* 如果配置的是集群模式
* @return
*/privatebooleanisCluster(Map<String,String> param){String clusterNodes = param.get(CustomRedisConfigConstant.REDIS_CLUSTER_NODES);returnStringUtils.isNotEmpty(clusterNodes);}privateRedisStandaloneConfigurationbuildStandaloneConfig(Map<String,String> param){String host = param.get(CustomRedisConfigConstant.REDIS_HOST);String port = param.get(CustomRedisConfigConstant.REDIS_PORT);String database = param.get(CustomRedisConfigConstant.REDIS_DATABASE);String password = param.get(CustomRedisConfigConstant.REDIS_PASSWORD);RedisStandaloneConfiguration standaloneConfig =newRedisStandaloneConfiguration();
standaloneConfig.setHostName(host);
standaloneConfig.setDatabase(Integer.valueOf(database));
standaloneConfig.setPort(Integer.valueOf(port));
standaloneConfig.setPassword(RedisPassword.of(password));return standaloneConfig;}privateRedisSentinelConfigurationbuildSentinelConfig(Map<String,String> param){String sentinelMaster = param.get(CustomRedisConfigConstant.REDIS_SENTINEL_MASTER);String sentinelNodes = param.get(CustomRedisConfigConstant.REDIS_SENTINEL_NODES);String database = param.get(CustomRedisConfigConstant.REDIS_DATABASE);String password = param.get(CustomRedisConfigConstant.REDIS_PASSWORD);RedisSentinelConfiguration config =newRedisSentinelConfiguration();
config.setMaster(sentinelMaster);Iterable<AddressUtils.Address> addressIterable =AddressUtils.parseAddresses(sentinelNodes);Iterable<RedisNode> redisNodes =transform(addressIterable);
config.setDatabase(Integer.valueOf(database));
config.setPassword(RedisPassword.of(password));
config.setSentinels(redisNodes);return config;}privateRedisClusterConfigurationbuildClusterConfig(Map<String,String> param){String clusterNodes = param.get(CustomRedisConfigConstant.REDIS_CLUSTER_NODES);String clusterMaxRedirects = param.get(CustomRedisConfigConstant.REDIS_CLUSTER_MAX_REDIRECTS);String password = param.get(CustomRedisConfigConstant.REDIS_PASSWORD);RedisClusterConfiguration config =newRedisClusterConfiguration();Iterable<AddressUtils.Address> addressIterable =AddressUtils.parseAddresses(clusterNodes);Iterable<RedisNode> redisNodes =transform(addressIterable);
config.setClusterNodes(redisNodes);
config.setMaxRedirects(StringUtils.isEmpty(clusterMaxRedirects)?5:Integer.valueOf(clusterMaxRedirects));
config.setPassword(RedisPassword.of(password));return config;}privateIterable<RedisNode>transform(Iterable<AddressUtils.Address> addresses){List<RedisNode> redisNodes =newArrayList<>();
addresses.forEach( address -> redisNodes.add(newRedisServer(address.getHost(), address.getPort())));return redisNodes;}privatestaticApplicationContext applicationContext;publicMap<String,Map<String,String>>getRedis(){return redis;}/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext){CustomRedisConfig.applicationContext = applicationContext;}privatestaticvoidcheckApplicationContext(){if(applicationContext ==null){thrownewIllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextUtil");}}/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/@SuppressWarnings("unchecked")publicstatic<T>TgetBean(String name){checkApplicationContext();if(applicationContext.containsBean(name)){return(T) applicationContext.getBean(name);}returnnull;}/**
* 删除spring中管理的bean
*
* @param beanName
*/publicstaticvoidremoveBean(String beanName){DefaultListableBeanFactory acf =(DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
acf.removeBeanDefinition(beanName);}/**
* 同步方法注册bean到ApplicationContext中
*
* @param beanName
* @param clazz
* @param original bean的属性值
*/publicsynchronizedvoidsetBean(String beanName,Class<?> clazz,Map<String,Object> original){checkApplicationContext();DefaultListableBeanFactory beanFactory =(DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();if(beanFactory.containsBean(beanName)){return;}GenericBeanDefinition definition =newGenericBeanDefinition();//类class
definition.setBeanClass(clazz);if(beanName.startsWith(primaryKey)){
definition.setPrimary(true);}//属性赋值
definition.setPropertyValues(newMutablePropertyValues(original));//注册到spring上下文
beanFactory.registerBeanDefinition(beanName, definition);}publicsynchronizedvoidsetCosBean(String beanName,Class<?> clazz,ConstructorArgumentValues original){checkApplicationContext();DefaultListableBeanFactory beanFactory =(DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();//这里重要if(beanFactory.containsBean(beanName)){return;}GenericBeanDefinition definition =newGenericBeanDefinition();//类class
definition.setBeanClass(clazz);if(beanName.startsWith(primaryKey)){
definition.setPrimary(true);}//属性赋值
definition.setConstructorArgumentValues(newConstructorArgumentValues(original));//注册到spring上下文
beanFactory.registerBeanDefinition(beanName, definition);}}
2.2. CustomRedisConfigConstant
定义常用配置项的键名
packagecom.iceicepip.project.common.redis;publicclassCustomRedisConfigConstant{privateCustomRedisConfigConstant(){}publicstaticfinalStringREDIS_HOST="host";publicstaticfinalStringREDIS_PORT="port";publicstaticfinalStringREDIS_TIMEOUT="timeout";publicstaticfinalStringREDIS_DATABASE="database";publicstaticfinalStringREDIS_PASSWORD="password";publicstaticfinalStringREDIS_MAXWAITMILLIS="maxWaitMillis";publicstaticfinalStringREDIS_MAXIDLE="maxIdle";publicstaticfinalStringREDIS_MINIDLE="minIdle";publicstaticfinalStringREDIS_MAXTOTAL="maxTotal";publicstaticfinalStringREDIS_TESTONBORROW="testOnBorrow";publicstaticfinalStringREDIS_SENTINEL_MASTER="sentinel.master";publicstaticfinalStringREDIS_SENTINEL_NODES="sentinel.nodes";publicstaticfinalStringREDIS_CLUSTER_NODES="cluster.nodes";publicstaticfinalStringREDIS_CLUSTER_MAX_REDIRECTS="cluster.max-redirects";publicstaticfinalStringBEAN_NAME_SUFFIX="Redis";publicstaticfinalStringINIT_METHOD_NAME="getInit";}
2.3封装一个Redis操作类 CustomRedisService
packagecom.iceicepip.project.common.redis;importcom.alibaba.ttl.TransmittableThreadLocal;importorg.apache.commons.lang3.StringUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.aop.framework.Advised;importorg.springframework.aop.support.AopUtils;importorg.springframework.beans.factory.NoSuchBeanDefinitionException;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.ApplicationContext;importorg.springframework.dao.DataAccessException;importorg.springframework.data.redis.connection.RedisConnection;importorg.springframework.data.redis.connection.RedisStringCommands;importorg.springframework.data.redis.connection.ReturnType;importorg.springframework.data.redis.connection.StringRedisConnection;importorg.springframework.data.redis.core.*;importorg.springframework.data.redis.core.types.Expiration;importjavax.annotation.Resource;importjava.nio.charset.StandardCharsets;importjava.util.*;importjava.util.concurrent.TimeUnit;importjava.util.function.Function;importjava.util.function.Supplier;publicclassCustomRedisService{privatestaticfinalLogger logger =LoggerFactory.getLogger(CustomRedisService.class);privateStringRedisTemplate stringRedisTemplate;privateRedisTemplate redisTemplate;@Value("${distribute.lock.MaxSeconds:100}")privateInteger lockMaxSeconds;privatestaticLongLOCK_WAIT_MAX_TIME=120000L;@ResourceprivateApplicationContext applicationContext;/**
* 保存锁的value
*/privateTransmittableThreadLocal<String> redisLockReentrant =newTransmittableThreadLocal<>();/**
* 解锁lua脚本
*/privatestaticfinalStringRELEASE_LOCK_SCRIPT="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";/**
* redis锁固定前缀
*/privatestaticfinalStringREDIS_LOCK_KEY_PREFIX="xxx:redisLock";/**
* redis nameSpace
*/privatestaticfinalStringREDIS_NAMESPACE_PREFIX=":";@Value("${spring.application.name}")privateString appName;publicCustomRedisService(){}publicCustomRedisService(StringRedisTemplate stringRedisTemplate,RedisTemplate redisTemplate){this.stringRedisTemplate = stringRedisTemplate;this.redisTemplate = redisTemplate;}publicStringRedisTemplategetStringRedisTemplate(){return stringRedisTemplate;}publicRedisTemplategetRedisTemplate(){return redisTemplate;}//以下是操作publicvoidsaveOrUpdate(HashMap<String,String> values)throwsException{ValueOperations<String,String> valueOps = stringRedisTemplate
.opsForValue();
valueOps.multiSet(values);}publicvoidsaveOrUpdate(String key,String value)throwsException{ValueOperations<String,String> valueOps = stringRedisTemplate
.opsForValue();
valueOps.set(key, value);}publicStringgetValue(String key)throwsException{ValueOperations<String,String> valueOps = stringRedisTemplate
.opsForValue();return valueOps.get(key);}publicvoidsetValue(String key,String value)throwsException{ValueOperations<String,String> valueOps = stringRedisTemplate
.opsForValue();
valueOps.set(key, value);}publicvoidsetValue(String key,String value,long timeout,TimeUnit unit)throwsException{ValueOperations<String,String> valueOps = stringRedisTemplate
.opsForValue();
valueOps.set(key, value, timeout, unit);}publicList<String>getValues(Collection<String> keys)throwsException{ValueOperations<String,String> valueOps = stringRedisTemplate
.opsForValue();return valueOps.multiGet(keys);}publicvoiddelete(String key)throwsException{
stringRedisTemplate.delete(key);}publicvoiddelete(Collection<String> keys)throwsException{
stringRedisTemplate.delete(keys);}publicvoidaddSetValues(String key,String... values)throwsException{SetOperations<String,String> setOps = stringRedisTemplate.opsForSet();
setOps.add(key, values);}publicSet<String>getSetValues(String key)throwsException{SetOperations<String,String> setOps = stringRedisTemplate.opsForSet();return setOps.members(key);}publicStringgetSetRandomMember(String key)throwsException{SetOperations<String,String> setOps = stringRedisTemplate.opsForSet();return setOps.randomMember(key);}publicvoiddelSetValues(String key,Object... values)throwsException{SetOperations<String,String> setOps = stringRedisTemplate.opsForSet();
setOps.remove(key, values);}publicLonggetZsetValuesCount(String key)throwsException{return stringRedisTemplate.opsForSet().size(key);}publicvoidaddHashSet(String key,HashMap<String,String> args)throwsException{HashOperations<String,String,String> hashsetOps = stringRedisTemplate
.opsForHash();
hashsetOps.putAll(key, args);}publicMap<String,String>getHashSet(String key)throwsException{HashOperations<String,String,String> hashsetOps = stringRedisTemplate
.opsForHash();return hashsetOps.entries(key);}publicMap<byte[],byte[]>getHashByteSet(String key)throwsException{RedisConnection connection =null;try{
connection = redisTemplate.getConnectionFactory().getConnection();return connection.hGetAll(key.getBytes());}catch(Exception e){thrownewException(e);}finally{if(Objects.nonNull(connection)&&!connection.isClosed()){
connection.close();}}}publicList<byte[]>getHashMSet(byte[] key,byte[][] fields)throwsException{return stringRedisTemplate.getConnectionFactory().getConnection().hMGet(key, fields);}/**
* 设备hash中的值
*
* @param key
* @param field
* @param vaule
* @return
* @throws Exception
*/publicBooleansetHashMSet(byte[] key,byte[] field,byte[] vaule)throwsException{return stringRedisTemplate.getConnectionFactory().getConnection().hSet(key, field, vaule);}/**
* 采用Pipeline方式获取多个Key的数据
*
* @param keys Key数组
* @param fields Hash对象的二级Key
* @return 结果数组,每个Object对象为List<byte[]>,使用时需判断是否为null
* @throws Exception
*/publicList<Object>getHashMSet(byte[][] keys,byte[][] fields)throwsException{if(keys ==null|| keys.length ==0|| fields ==null|| fields.length ==0){returnnull;}RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();try{
connection.openPipeline();for(byte[] key : keys){
connection.hMGet(key, fields);}return connection.closePipeline();}finally{if(!connection.isClosed()){
connection.close();}}}/**
* 采用Pipeline方式获取多个Key的数据
*
* @param keys Key数组
* @param field Hash对象的二级Key
* @return 结果数组,每个Object对象为List<byte[]>,使用时需判断是否为null
* @throws Exception
*/publicList<Object>getHashMSet(byte[][] keys,byte[] field)throwsException{if(keys ==null|| keys.length ==0|| field ==null){returnnull;}RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();try{
connection.openPipeline();for(byte[] key : keys){
connection.hGet(key, field);}return connection.closePipeline();}finally{if(!connection.isClosed()){
connection.close();}}}/**
* 采用Pipeline方式获取多个Key的数据
*
* @param keys Key数组
* @return 结果数组,每个Object对象为List<byte[]>,使用时需判断是否为null
* @throws Exception
*/publicList<Object>getHashMSet(byte[][] keys)throwsException{if(keys ==null|| keys.length ==0){returnnull;}RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();try{
connection.openPipeline();for(byte[] key : keys){
connection.hGetAll(key);}return connection.closePipeline();}finally{if(!connection.isClosed()){
connection.close();}}}/**
* 删除批量string
*
* @param keys Key数组
*/publicvoiddeleteAllStringValues(byte[][] keys){if(keys ==null|| keys.length ==0){return;}RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();try{
connection.openPipeline();for(byte[] key : keys){
connection.del(key);}
connection.closePipeline();}finally{if(!connection.isClosed()){
connection.close();}}}publicList<String>getHashMSet(String key,List<String> fields)throwsException{HashOperations<String,String,String> hashsetOps = stringRedisTemplate
.opsForHash();return hashsetOps.multiGet(key, fields);}publicList<byte[]>getHashByteMSet(String key,byte[]... fields)throwsException{// HashOperations<String, String, byte[]> hashsetOps = stringRedisTemplate// .opsForHash();// return hashsetOps.multiGet(key, fields);RedisConnection connection =null;try{
connection = redisTemplate.getConnectionFactory().getConnection();return connection.hMGet(key.getBytes(), fields);}catch(Exception e){thrownewException(e);}finally{if(Objects.nonNull(connection)&&!connection.isClosed()){
connection.close();}}}publicvoiddelHashSetValues(String key,Object... values)throwsException{HashOperations<String,String,String> hashsetOps = stringRedisTemplate
.opsForHash();
hashsetOps.delete(key, values);}publicvoidaddZset(String key,String value,double score)throwsException{ZSetOperations<String,String> zSetOps = stringRedisTemplate
.opsForZSet();
zSetOps.add(key, value, score);}publicSet<String>getZsetValues(String key)throwsException{returnnull;}publicvoiddelZsetValues(String key,Object... values)throwsException{ZSetOperations<String,String> zSetOps = stringRedisTemplate
.opsForZSet();
zSetOps.remove(key, values);}publicStringgetHashByKey(String redisKey,String mapKey)throwsException{HashOperations<String,String,String> hashsetOps = stringRedisTemplate.opsForHash();return hashsetOps.get(redisKey, mapKey);}publicbyte[]getHashByteByKey(String redisKey,String mapKey)throwsException{RedisConnection connection =null;try{
connection = redisTemplate.getConnectionFactory().getConnection();return connection.hGet(redisKey.getBytes(), mapKey.getBytes());}catch(Exception e){thrownewException(e);}finally{if(Objects.nonNull(connection)&&!connection.isClosed()){
connection.close();}}// HashOperations<String, String, byte[]> hashsetOps = stringRedisTemplate.opsForHash();// return hashsetOps.get(redisKey, mapKey);}publicMap<byte[],byte[]>getHashByte(String redisKey)throwsException{RedisConnection connection =null;try{
connection = redisTemplate.getConnectionFactory().getConnection();return connection.hGetAll(redisKey.getBytes());}catch(Exception e){thrownewException(e);}finally{if(Objects.nonNull(connection)&&!connection.isClosed()){
connection.close();}}}publicvoidaddHashSet(String redisKey,String mapKey,String mapValue)throwsException{
stringRedisTemplate.opsForHash().put(redisKey, mapKey, mapValue);}publicSet<String>getSet(String key)throwsException{SetOperations<String,String> setOperations = stringRedisTemplate.opsForSet();return setOperations.members(key);}publicvoidaddSetValuesPipelined(finalString[] keys,finalString value)throwsException{
stringRedisTemplate.executePipelined(newRedisCallback<Object>(){@OverridepublicObjectdoInRedis(RedisConnection connection){StringRedisConnection stringRedisConn =(StringRedisConnection) connection;for(int i =0; i < keys.length; i++){
stringRedisConn.sAdd(keys[i], value);}//必须返回nullreturnnull;}});}publicvoiddelSetValuesPipelined(finalString[] keys,finalString value)throwsException{
stringRedisTemplate.executePipelined(newRedisCallback<Object>(){@OverridepublicObjectdoInRedis(RedisConnection connection){StringRedisConnection stringRedisConn =(StringRedisConnection) connection;for(int i =0; i < keys.length; i++){
stringRedisConn.sRem(keys[i], value);}//必须返回nullreturnnull;}});}publicvoiddelHashByKey(String redisKey,String mapKey)throwsException{HashOperations<String,String,String> hashMapOps = stringRedisTemplate.opsForHash();
hashMapOps.delete(redisKey, mapKey);}publicBooleanhasKey(String key)throwsException{return stringRedisTemplate.hasKey(key);}/**
* 设置用户其他类的缓存
*
* @param key
* @param field hash结构的field
* @param data 需要存的数据
* @param timeOut 超时时间
* @param unit 时间单位
*/publicvoidsetHashOther(String key,String field,String data,long timeOut,TimeUnit unit){
stringRedisTemplate.opsForHash().put(key, field, data);
stringRedisTemplate.expire(key, timeOut, unit);}/**
* 返回用户的其他缓存
*
* @param key
* @param field hash结构的field
* @return String
* @throws Exception
*/publicStringgetHashOther(String key,String field)throwsException{returnthis.getHashByKey(key, field);}/**
* 2019-2-20 changyandong 新增incr方法,设置过期时间
*
* @param key
* @param delta
* @param timeout
* @param unit
* @return
*/publicLongincrement(finalString key,finalint delta,finallong timeout,finalTimeUnit unit){if(timeout <=0|| unit ==null){return stringRedisTemplate.opsForValue().increment(key, delta);}List<Object> result = stringRedisTemplate
.executePipelined(newSessionCallback<Object>(){@Overridepublic<K,V>Objectexecute(RedisOperations<K,V> operations)throwsDataAccessException{ValueOperations<K,V> ops = operations.opsForValue();
ops.increment((K) key, delta);
operations.expire((K) key, timeout, unit);returnnull;}});return(Long) result.get(0);}/**
* 管道增加hash结构
*/publicvoidaddHashValuesPipelined(Map<String,Map<String,String>> keys){
stringRedisTemplate.executePipelined((RedisCallback<Object>) connection ->{StringRedisConnection stringRedisConn =(StringRedisConnection) connection;
keys.forEach(stringRedisConn::hMSet);//必须返回nullreturnnull;});}/**
* 管道增加hash结构 删除老hash结构
*/publicvoidaddHashValuesPipelinedRemoveOldHash(Map<String,Map<String,String>> keys){
stringRedisTemplate.executePipelined((RedisCallback<Object>) connection ->{StringRedisConnection stringRedisConn =(StringRedisConnection) connection;
stringRedisConn.del(keys.keySet().toArray(newString[0]));
keys.forEach(stringRedisConn::hMSet);//必须返回nullreturnnull;});}/**
* 分布式锁模板方法
*
* @param businessKey 业务key
* @param callbackFunction 回调方法
* @param s 回调方法具体入参
* @param <S> 回调方法入参类型
* @param <T> 回调方法返回值类型
* @return 回调方法返回值
*/public<S,T>TredisLockCallback(String businessKey,Function<S,T> callbackFunction,S s){try{redisLock(businessKey);return callbackFunction.apply(s);}finally{redisUnLock(businessKey);}}public<T>TredisLockSupplier(String businessKey,Supplier<T> supplier){returnredisLockSupplier(businessKey, supplier, lockMaxSeconds,LOCK_WAIT_MAX_TIME,TimeUnit.SECONDS);}public<T>TredisLockSupplier(String businessKey,Supplier<T> supplier,long lockMaxTime,long tryTimeout,TimeUnit timeUnit){try{redisLock(businessKey, lockMaxTime, tryTimeout, timeUnit);return supplier.get();}finally{redisUnLock(businessKey);}}/**
* 获取锁(不等待,直接返回 是否获取到锁资源)
*
* @param businessKey 业务key
* @return 是否获取到锁资源
*/publicbooleanredisLockSuspend(String businessKey){returnredisLockSuspend(businessKey, lockMaxSeconds,TimeUnit.SECONDS);}/**
* 获取锁(不等待,直接返回 是否获取到锁资源)
* @param businessKey 业务key
* @param lockMaxTime 锁占用时长
* @param timeUnit 时间单位
* @return 是否获取锁资源
*/publicbooleanredisLockSuspend(String businessKey,long lockMaxTime,TimeUnit timeUnit){String lockKey =generateLockKey(businessKey);long finalLockMaxTime = timeUnit.toMillis(lockMaxTime);//可重入锁判断if(isReentrantLock(lockKey)){returnBoolean.TRUE;}RedisCallback<Boolean> callback =(connection)-> connection.set(
lockKey.getBytes(StandardCharsets.UTF_8),
businessKey.getBytes(StandardCharsets.UTF_8),Expiration.milliseconds(finalLockMaxTime),RedisStringCommands.SetOption.SET_IF_ABSENT);return stringRedisTemplate.execute(callback);}/**
* @param keyPrefix redis锁 key前缀
* @param key key
* @param tryTimeout 超时时间
* @param timeUnit 时间单位
* @return 是否获取到锁资源
*/@DeprecatedpublicbooleanredisLock(String keyPrefix,String key,long lockMaxTime,long tryTimeout,TimeUnit timeUnit){String businessKey =getLockKey(keyPrefix, key);returnredisLock(businessKey, lockMaxTime, tryTimeout, timeUnit);}publicbooleanredisLock(String businessKey,long lockMaxTime,long tryTimeout,TimeUnit timeUnit){
tryTimeout =System.currentTimeMillis()+ timeUnit.toMillis(tryTimeout);
lockMaxTime = timeUnit.toMillis(lockMaxTime);returnredisLock(businessKey, lockMaxTime, tryTimeout);}/**
* 获取redis分布式锁 (默认超时时间)
*
* @param keyPrefix redis锁 key前缀
* @param key key
* @return 是否获取到锁资源
*/@DeprecatedpublicbooleanredisLock(String keyPrefix,String key){String businessKey =getLockKey(keyPrefix, key);returnredisLock(businessKey);}publicbooleanredisLock(String businessKey){long endTime =System.currentTimeMillis()+LOCK_WAIT_MAX_TIME;long lockMaxTime =TimeUnit.SECONDS.toMillis(this.lockMaxSeconds);returnredisLock(businessKey, lockMaxTime, endTime);}/**
* 获取redis分布式锁 (默认超时时间)
* @param businessKey 业务key
* @param lockMaxTime 锁占用时长
* @param endTime 结束时间
* @return 是否获取到锁资源
*/privatebooleanredisLock(String businessKey,long lockMaxTime,long endTime){String lockKey =generateLockKey(businessKey);
logger.debug("redisLock businessKey:{}, lockKey:{}, lockMaxTime:{}, endTime:{}", businessKey, lockKey, lockMaxTime, endTime);//可重入锁判断if(isReentrantLock(lockKey)){
logger.debug("redisLock lockKey:{}, threadName:{}, isReentrantLock true", lockKey,Thread.currentThread().getName());returnBoolean.TRUE;}RedisCallback<Boolean> callback =(connection)-> connection.set(
lockKey.getBytes(StandardCharsets.UTF_8),
businessKey.getBytes(StandardCharsets.UTF_8),Expiration.milliseconds(lockMaxTime),RedisStringCommands.SetOption.SET_IF_ABSENT);//在timeout时间内仍未获取到锁,则获取失败while(System.currentTimeMillis()< endTime){if(stringRedisTemplate.execute(callback)){
redisLockReentrant.set(lockKey);
logger.debug("redisLock getKey lockKey:{}, ", lockKey);returntrue;}try{Thread.sleep(100);}catch(InterruptedException e){
logger.error("获取redis分布式锁出错", e);Thread.currentThread().interrupt();}}
logger.debug("redisLock meiyoukey lockKey:{}, ", lockKey);returnfalse;}/**
* 释放分布式锁
*
* @param keyPrefix redis锁 key前缀
* @param key key
*/@DeprecatedpublicBooleanredisUnLock(String keyPrefix,String key){String lockKey =getLockKey(keyPrefix, key);returnredisUnLock(lockKey);}publicBooleanredisUnLock(String businessKey){String lockKey =generateLockKey(businessKey);RedisCallback<Boolean> callback =(connection)-> connection.eval(RELEASE_LOCK_SCRIPT.getBytes(),ReturnType.BOOLEAN,1,
lockKey.getBytes(StandardCharsets.UTF_8),
businessKey.getBytes(StandardCharsets.UTF_8));//清空 ThreadLocal
redisLockReentrant.remove();Boolean execute = stringRedisTemplate.execute(callback);
logger.debug("redisUnLock execute lockKey:{}, ", lockKey);return execute;}privateStringgetLockKey(String keyPrefix,String key){return keyPrefix +"-"+ key;}/**
* 是否为重入锁
*/privatebooleanisReentrantLock(String lockKey){String originValue = redisLockReentrant.get();String redisValue = stringRedisTemplate.opsForValue().get(lockKey);returnStringUtils.isNotBlank(originValue)&& originValue.equals(redisValue);}/**
* 生成规则要求的 key
* xxx:redisLock:${appName}:${classSimpleName}:${methodName}:${businessKey}
* @param businessKey 业务key
* @return key
*/privateStringgenerateLockKey(String businessKey){StackTraceElement[] stackTrace =Thread.currentThread().getStackTrace();String classSimpleName =StringUtils.EMPTY;String methodName =StringUtils.EMPTY;for(StackTraceElement traceElement : stackTrace){String itemClassName = traceElement.getClassName();//如果是当前类或者stack类 continue;if(itemClassName.equals(this.getClass().getName())|| itemClassName.equals(StackTraceElement.class.getName())){continue;}char[] cs=itemClassName.substring(itemClassName.lastIndexOf(".")+1).toCharArray();
cs[0]+=32;//一直找,找到被spring管理的类。Object target;try{
target = applicationContext.getBean(String.valueOf(cs));}catch(NoSuchBeanDefinitionException e){continue;}//如果是代理类,找到实际类if(AopUtils.isAopProxy(target)&& target instanceofAdvised){Advised advised =(Advised) target;try{
target = advised.getTargetSource().getTarget();}catch(Exception e){continue;}}if(Objects.nonNull(target)){
classSimpleName = target.getClass().getSimpleName();
methodName = traceElement.getMethodName();break;}}returnREDIS_LOCK_KEY_PREFIX.concat(REDIS_NAMESPACE_PREFIX).concat(appName.toLowerCase()).concat(REDIS_NAMESPACE_PREFIX).concat(classSimpleName).concat(REDIS_NAMESPACE_PREFIX).concat(methodName).concat(REDIS_NAMESPACE_PREFIX).concat(businessKey);}}
2.4 将自动配置导入
在工程目录中创建
common-redis-lettuce/src/main/resources/META-INF/spring
创建文件名为
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件
文件内容:
com.iceicepip.project.common.redis.CustomRedisConfig
解释
在工程目录中创建
common-redis-lettuce/src/main/resources/META-INF/spring
目录,并在该目录下创建一个名为
org.springframework.boot.autoconfigure.AutoConfiguration.imports
的文件。该文件的作用是指示 Spring Boot 在自动配置期间需要导入哪些额外的配置类。
在
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中,可以添加需要导入的其他配置类的全限定类名。例如,如果我们需要在自动配置期间导入一个名为
CustomRedisConfig
的配置类,可以在该文件中添加以下内容:
com.iceicepip.project.common.redis.CustomRedisConfig
这样,在应用程序启动时,Spring Boot 会自动加载
CustomRedisConfig
类,并将其与自动配置合并,以提供完整的应用程序配置。
3. 使用方式
其中
xxx
为在Spring Boot 配置文件中配置的多数据源的标识.如’user’、“iot”
@Autowired@Qualifier("xxxRedis")privateCustomRedisService xxxRedisService;@Autowired@Qualifier("userRedis")privateCustomRedisService userRedisService;
或者直接使用RedisTemplate 。
@Autowired@Qualifier("userRedisTemplate")privateRedisTemplate userRedisTemplate;@Autowired@Qualifier("xxxStringRedisTemplate")privateStringRedisTemplate xxxStringRedisTemplate;@Autowired@Qualifier("xxxRedisTemplate")privateRedisTemplate xxxRedisTemplate;
4. 源码地址
https://github.com/wangshuai67/Redis-Tutorial-2023
5. Redis从入门到精通系列文章
- 《SpringBoot Redis 使用Lettuce和Jedis配置哨兵模式》
- 《Redis【应用篇】之RedisTemplate基本操作》
- 《Redis 从入门到精通【实践篇】之SpringBoot配置Redis多数据源》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis HyperLogLog 数据结构》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis地理位置数据结构GeoHash》
- 《Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解》
- 《Redis 从入门到精通【进阶篇】之redis主从复制详解》
- 《Redis 从入门到精通【进阶篇】之Redis事务详解》
- 《Redis从入门到精通【进阶篇】之对象机制详解》
- 《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
- 《Redis从入门到精通【进阶篇】之持久化 AOF详解》
- 《Redis从入门到精通【进阶篇】之持久化RDB详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
- 《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》大家好,我是冰点,今天的Redis【实践篇】之SpringBoot Redis 多数据源集成支持哨兵模式和Cluster集群模式,全部内容就是这些。如果你有疑问或见解可以在评论区留言。
版权归原作者 冰点. 所有, 如有侵权,请联系我们删除。