Spring boot下的应用访问Redis数据库,采用属性配置文件读取数据,哨兵方式访问数据库。

pom文件
在pom文件中引入依赖包:
<dependency>
<groupId>org.springframework.boot
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
在application.properties配置文件中添加:
# ------------------------------------------------ # Redis配置 # 是否启用Redis服务 redis.redisService.enabled=false # redis 连接 # 客户端超时时间单位是毫秒 默认是2000,未使用 redis.timeout=2000 # 最大空闲数 redis.maxIdle=300 # 连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal #redis.maxActive=600 # 控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性 redis.maxTotal=1000 # 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 redis.maxWaitMillis=1000 # 连接的最小空闲时间 默认1800000毫秒(30分钟) redis.minEvictableIdleTimeMillis=300000 # 每次释放连接的最大数目,默认3 redis.numTestsPerEvictionRun=1024 # 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 redis.timeBetweenEvictionRunsMillis=30000 # 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 redis.testOnBorrow=true # 在空闲时检查有效性, 默认false redis.testWhileIdle=true # 哨兵 redis.sentinel.master=mymaster redis.sentinel.nodes=127.0.0.1:9379 redis.database=0 redis.password=
这里要说明一下,redis.redisService.enabled 是标识是否需要启用访问redis功能,特别是在使用自身电脑在移动办公开发时,有时并不需要连接Redis服务(例如我)。具体实现的原理是通过 @ConditionalOnExpression(“${redis.redisService.enabled:true}”) 实现对bean的注入控制。
redis配置注入
使用注解 @Configuration 声明java文件作为配置文件,注入相关的java类,完整文件见 redis\RedisCacheConfig.java。
为方便理解,以思考顺序描述相关内容。
注入redisService类
spring-data-redis针对jedis提供了一个高度封装的 RedisTemplate 类,针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口。
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
由于业务逻辑使用时,需要@Autowired注入redisService类,所以此处进行注入类的声明。
/**
* 注入封装RedisTemplate
*/
@Bean(name = "redisService")
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisService redisService(RedisTemplate<String, Object> redisTemplate) {
RedisService redisService = new RedisService();
redisService.setRedisTemplate(redisTemplate);
return redisService;
}
在进行redisService初始化时,需要参数对象RedisTemplate,有了下文的注入RedisTemplate类。
注入RedisTemplate类
此处使用JedisPoolConfig进行RedisTemplate的配置,同时设置序列化方式。
/**
* RedisTemplate
*
* @return redisTemplate
*/
@Bean
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置数据存入 redis 的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 事务开关
redisTemplate.setEnableTransactionSupport(false);
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
此处的初始化需要redisConnectionFactory类。所以有redisConnectionFactory类的注入。
注入redisConnectionFactory类
通过连接工厂,以哨兵的方式连接Redis数据库服务。
/**
* jedis连接工厂
* @param jedisPoolConfig
* @return
*/
@Bean
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
//哨兵
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
String[] host = redisNodes.split(",");
for(String redisHost : host){
String[] item = redisHost.split(":");
String ip = item[0];
String port = item[1];
redisSentinelConfiguration.addSentinel(new RedisNode(ip, Integer.parseInt(port)));
}
redisSentinelConfiguration.setDatabase(database);
redisSentinelConfiguration.setMaster(master);
//获得默认的连接池构造器
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb =
(JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
//指定jedisPoolConifig来修改默认的连接池构造器
jpcb.poolConfig(jedisPoolConfig);
//通过构造器来构造jedis客户端配置
JedisClientConfiguration jedisClientConfiguration = jpcb.build();
//jedis连接工厂
return new JedisConnectionFactory(redisSentinelConfiguration, jedisClientConfiguration);
}
注入jedisPoolConfig类
edisPoolConfig类配置所需的参数由@Value从配置文件中读取。
/**
* 连接池配置信息
* @return
*/
@Bean
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空闲数
jedisPoolConfig.setMaxIdle(maxIdle);
// 连接池的最大数据库连接数
jedisPoolConfig.setMaxTotal(maxTotal);
// 最大建立连接等待时间
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
// 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
jedisPoolConfig.setTestOnBorrow(testOnBorrow);
// 在空闲时检查有效性, 默认false
jedisPoolConfig.setTestWhileIdle(testWhileIdle);
return jedisPoolConfig;
}
redis操作服务
@Component
@Slf4j
public class RedisService {
private RedisTemplate<String, Object> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
//=============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key,long time){
try {
if(time> 0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
log.error("指定缓存失效时间异常,信息为:{}", e);
return false;
}
}
redis服务使用
数据存储
注入redis服务
@Autowired private RedisService redisService;
存储数据
/** * 验证码存放在Redis中的itemkey */ public static final String ITEMKEY = "verifyCode"; /** * 验证码存放在Redis中的有效期,以秒为单位 */ public static final Integer EXPIRETIME = 180;
//验证码存放Redis,有效期3分钟 redisService.hset(Constant.ITEMKEY, mobile, verifyCode, Constant.EXPIRETIME);
//是否验证验证码 String realSmsCode = (String) redisService.hget(Constant.ITEM, mobile);
缓存mybatis数据
分布式项目中最常见的缓存机制就是通过redis缓存mybatis的查询数据,一般称为二级缓存,可以使用@CacheConfig,@Cacheable,@CachePut,@CacheEvict。
public interface UserMapper {
@Cacheable(cacheNames = "User:Id")
public User findById(@Param("id") Integer id);
}
CacheManagerConfig
缓存对象集合中,缓存是以key-value形式保存的。当不指定缓存的key时,SpringBoot会使用SimpleKeyGenerator生成key。它是使用方法的参数组合生成的一个key。key采用 参数列表。
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
此时有一个问题:如果2个方法,参数是一样的,但执行逻辑不同,那么将会导致执行第二个方法时命中第一个方法的缓存。
解决办法是在@Cacheable注解参数中指定key,或者自己实现一个KeyGenerator,在注解中指定KeyGenerator。但是如果这样的情况很多,每一个都要指定key、KeyGenerator很麻烦。
Spring同样提供了方案:继承CachingConfigurerSupport并重写keyGenerator()。key采用 包名+方法名+参数列表。
完整代码文件见CacheManagerConfig.java。
@Bean
@Override
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj:objects){
sb.append(obj.toString());
}
log.info("keyGenerator=" + sb.toString());
return sb.toString();
}
};
}
redisCacheManager
同样使用redisConnectionFactory配置缓存的连接。
@Bean(name = "redisCacheManager")
@ConditionalOnExpression("${redis.redisService.enabled:true}")
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheManager redisCacheManager = RedisCacheManager.create(redisConnectionFactory);
log.info("redis初始化-----------------------------------");
return redisCacheManager;
}