本篇简述springboot集成redis,基于redis哨兵模式。
sproingboot 集成组件三步曲(参见springboot+JPA介绍:依赖、配置、封装)(假定springboot脚手架已搭建完成并成功运行,可参考历史分享springboot+mybatis)
1. maven添加redis依赖2. redis 配置org.springframework.boot spring-boot-starter-data-redisorg.springframework.data spring-data-redis
2.1 yml
#spring
spring:
#redis
redis:
host: 192.168.2.9
port: 6379
password: CacheDB123
jedis:
pool:
max-active: 8
max-wait: 6000
max-idle: 5
min-idle: 1
# 哨兵配置
sentinel:
master: mymaster
nodes: 192.168.2.9:6379,192.168.2.10:6379
2.2 RedisTemplate config
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer
3. redis 功能封装
3.1 RedisService
import java.util.concurrent.TimeUnit;
public interface RedisService {
long TIME_ONE_SECOND = 1; // 1秒
long TIME_ONE_MINUTE = 60 * TIME_ONE_SECOND; // 1分
long TIME_ONE_HOUR = 60 * TIME_ONE_MINUTE; // 1小时
long TIME_ONE_DAY = 24 * TIME_ONE_HOUR; // 1天
long TIME_ONE_MonTH = 30 * TIME_ONE_DAY; // 1个月
long getExpire(String... keys);
boolean setByKey(V value, long exp, TimeUnit unit, String... keys);
boolean setByKey(V value, long exp, String... keys);
boolean setByKey(V value, String... keys);
long increment(String key, long value);
long increment(String key, long value, long exp);
boolean rpush(String key, V value, long exp);
boolean lpush(String key, V value, long exp);
V lrange(String key);
boolean sadd(String key, V value, long exp);
V smembers(String key);
boolean zadd(String key, V value, long exp);
V zrange(String key);
V getByKey(String... keys);
boolean delByKey(String... keys);
void convertAndSend(String channel, Object message);
}
3.2 RedisServiceImpl
import com.alibaba.fastjson.JSON;
import com.example.demo.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class RedisServiceImpl implements RedisService {
@Autowired
private RedisTemplate redisTemplate;
private String getMemKey(String... keys){
if(keys == null || keys.length == 0){
return null;
}
if(keys.length == 1){
return keys[0];
}
StringBuffer memKey = new StringBuffer();
Arrays.stream(keys).forEach(key -> {
memKey.append(":");
memKey.append(key);
});
return memKey.toString().replaceFirst(":", "");
}
private boolean expireByKey(String key, long exp, TimeUnit unit){
if (exp == 0) {
exp = RedisService.TIME_ONE_MONTH;
}
if (unit == null) {
unit = TimeUnit.SECONDS;
}
return redisTemplate.expire(key, exp, unit);
}
@Override
public long getExpire(String... keys) {
String memKey = this.getMemKey(keys);
Long expire = redisTemplate.getExpire(memKey);
log.info("获取缓存数据剩余有效期:key-{}, value-{}", memKey, expire);
return expire == null ? 0 : expire;
}
@Override
public boolean setByKey(V value, long exp, TimeUnit unit, String... keys) {
String memKey = this.getMemKey(keys);
redisTemplate.opsForValue().set(memKey, value);
return this.expireByKey(memKey, exp, unit);
}
@Override
public boolean setByKey(V value, long exp, String... keys) {
return setByKey(value, exp, TimeUnit.SECONDS, keys);
}
@Override
public boolean setByKey(V value, String... keys) {
return setByKey(value, 0, keys);
}
@Override
public long increment(String key, long value) {
// 永不过期
return redisTemplate.opsForValue().increment(key, value);
}
@Override
public long increment(String key, long value, long exp) {
long afterIncrementValue = redisTemplate.opsForValue().increment(key, value);
this.expireByKey(key, exp, TimeUnit.SECONDS);
return afterIncrementValue;
}
@Override
public boolean rpush(String key, V value, long exp) {
redisTemplate.opsForList().rightPush(key, value);
return this.expireByKey(key, exp, TimeUnit.SECONDS);
}
@Override
public boolean lpush(String key, V value, long exp) {
redisTemplate.opsForList().leftPush(key, value);
return this.expireByKey(key, exp, TimeUnit.SECONDS);
}
@Override
public V lrange(String key) {
log.debug("获取缓存数据:key-{}", key);
return (V) redisTemplate.opsForList().range(key, 0, -1);
}
@Override
public boolean sadd(String key, V value, long exp) {
redisTemplate.opsForSet().add(key, value);
return this.expireByKey(key, exp, TimeUnit.SECONDS);
}
@Override
public V smembers(String key) {
log.debug("获取缓存数据:key-{}", key);
return (V) redisTemplate.opsForSet().members(key);
}
@Override
public boolean zadd(String key, V value, long exp) {
redisTemplate.opsForZSet().add(key, value, Math.random());
return this.expireByKey(key, exp, TimeUnit.SECONDS);
}
@Override
public V zrange(String key) {
log.debug("获取缓存数据:key-{}", key);
return (V) redisTemplate.opsForZSet().range(key, 0, -1);
}
@Override
@SuppressWarnings("unchecked")
public V getByKey(String... keys) {
String memKey = this.getMemKey(keys);
V value = (V) redisTemplate.opsForValue().get(memKey);
log.info("获取缓存数据:key-{}, value-{}", memKey, JSON.toJSonString(value));
return value;
}
@Override
public boolean delByKey(String... keys) {
return redisTemplate.opsForValue().getOperations().delete(this.getMemKey(keys));
}
@Override
public void convertAndSend(String channel, Object message) {
redisTemplate.convertAndSend(channel, message);
}
}
写在最后:
简单提下缓存三点问题:
- 缓存穿透:Redis及DB中都未查到数据,击穿Redis穿透DB,频繁高频请求会影响DB。可以缓存一定时长有效期的空值来拦截恶意攻击请求。
- 缓存击穿:某一热点数据过期,短时间内请求击穿Redis,直达DB,DB峰值飙升。可以设置热点数据永不过期。
- 缓存雪崩:大量热点数据在短时间内几乎同时过期了,DB瞬时压力陡升爆掉。同上可以设置热点数据永不过期或有效期随机错开。
缓存中间件Redis的生产应用模式,一般有主从模式,sentinel哨兵模式,cluster集群模式。后面有空了会在服务运维专栏聊聊各种模式的搭建及测试应用。



