Spring Boot 整合 Redis 做缓存机制
1. Redis 简介
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。
1.1. Redis的特点
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
1.2. Redis的优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
1.3. Redis的不同之处
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
2. SpringBoot 整合 Redis
2.1. Pom文件添加Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2. 配置 application.yml
spring:
redis:
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis数据库索引
database: 1
# Redis服务器连接密码(默认为空)
password: redis#1234
jedis:
pool:
# 资源池中最大连接数
# 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整
max-active: 50
# 资源池运行最大空闲的连接数
# 默认8,-1表示无限制;可根据服务并发redis情况及服务端的支持上限调整,一般建议和max-active保持一致,避免资源伸缩带来的开销
max-idle: 50
# 资源池中的最小空闲连接数
min-idle: 0
# 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)
# 默认 -1 表示永不超时,设置5秒
max-wait: 5000
# 连接超时时间(毫秒)
timeout: 1000
2.3. 在启动类上添加 @EnableCaching 注解
@SpringBootApplication
@EnableCaching
public class Application {
public static void main( String[] args ) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
}
}
3. 使用方法
3.1. Redis缓存配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
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.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Redis缓存配置类
* @author tinghesi
*
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
//缓存管理器
@Bean
public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//设置缓存过期时间
cacheManager.setDefaultExpiration(10000);
return cacheManager;
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
StringRedisTemplate template = new StringRedisTemplate(factory);
setSerializer(template);//设置序列化工具
template.afterPropertiesSet();
return template;
}
private void setSerializer(StringRedisTemplate template){
@SuppressWarnings({
"rawtypes", "unchecked" })
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
}
}
3.2. 注解详情介绍
注解 | 作用 |
---|---|
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
-
@Cacheable 表示将返回结果缓存到redis,key值为dict::{ {第一个参数}}
“#p0”表示取第一个参数,如果参数为对象,则可以通过#p0.id获取对象的id。
就是将查询结果缓存到redis中,(key="#p0")指定传入的第一个参数作为redis的key。 -
@CacheEvict 表示删除该缓存数据,就是指定key,删除缓存数据,allEntries=true,方法调用后将立即清除缓存。
-
@CachePut 表示修改该缓存数据,就是指定key,将更新的结果同步到redis中。、
关于springboot缓存名的说明
使用SpringBoot缓存必须配置名字可以使用@CacheConfig(cacheNames = {“itemService”})在类上配置该类公用的名字,也可以使用@Cacheable(value=”item”)在方法上配置只适用于该方法的名字。如果类和方法上都有配置,以方法上的为准。
springBoot会自动拼装缓存名,规则是:配置的名字+两个冒号+方法的实参;
关于@CacheConfig和@Cacheable注解的说明
@Cacheable(value=”item”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 item 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。
在上面代码示例中@Cacheable注解设置了两个参数一个是value,一个是key。key的值"#p0"在执行过程中会被getItemById方法的实参所替换,例如id的值3 那么缓存的名字就会是"item::3";如果不设置key,系统会自动也会是这个效果。
3.3. 缓存数据
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import springboot.domain.User;
@Mapper
@CacheConfig(cacheNames = "users")
public interface UserMapper {
@Insert("insert into user(name,age) values(#{name},#{age})")
int addUser(@Param("name")String name,@Param("age")String age);
@Select("select * from user where id =#{id}")
@Cacheable(key ="#p0")
User findById(@Param("id") String id);
@CachePut(key = "#p0")
@Update("update user set name=#{name} where id=#{id}")
void updataById(@Param("id")String id,@Param("name")String name);
//如果指定为 true,则方法调用后将立即清空所有缓存
@CacheEvict(key ="#p0",allEntries=true)
@Delete("delete from user where id=#{id}")
void deleteById(@Param("id")String id);
}
或在需要缓存的方法上添加 @Cacheable 注解
@Service
@CacheConfig(cacheNames = {
"itemService"})
public class ItemServiceImpl implements ItemService {
@Override
@Cacheable(value = {
"item"},key ="#p0")
public String getItemById(Integer id) {
String name = "name";
return name;
}
}
4. 封装成服务
将下方代码拷贝到项目中,然后只需要将 RedisService 注入即可调用下方代码中的方法。
在这里衷心的感谢 《一只WEE》。
4.1. RedisService 服务层接口代码
import com.technologies.bear.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author tinghesi
*/
@Service
public class RedisServiceImpl implements RedisService {
/**
* slf4j 日志
*/
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 自定义 key 三种
* String key:String value 普通key:value
* String key:Set<String> set key:set集合
* String key:List<String> list key:list集合
*/
private static final String KEY_PREFIX_KEY = "info:bear:key";
private static final String KEY_PREFIX_SET = "info:bear:set";
private static final String KEY_PREFIX_LIST = "info:bear:list";
private final RedisTemplate<String, String> redisTemplate;
/**
* 注入
* @param redisTemplate 模板
*/
@Autowired
public RedisServiceImpl(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 添加 key:string 缓存
* @param k key
* @param v value
* @param time time
* @return
*/
@Override
public boolean cacheValue(String k, String v, long time) {
try {
String key = KEY_PREFIX_KEY + k;
ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.set(key, v);
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Throwable e) {
log.error("缓存存入失败key:[{}] value:[{}]", k, v);
}
return false;
}
/**
* 添加 key:string 缓存
* @param key key
* @param value value
* @return
*/
@Override
public boolean cacheValue(String key, String value) {
return cacheValue(key, value, -1);
}
/**
* 根据 key:string 判断缓存是否存在
* @param key key
* @return boolean
*/
@Override
public boolean containsValueKey(String key) {
return containsKey(KEY_PREFIX_KEY + key);
}
/**
* 判断缓存 key:set集合 是否存在
* @param key key
* @return
*/
@Override
public boolean containsSetKey(String key) {
return containsKey(KEY_PREFIX_SET + key);
}
/**
* 判断缓存 key:list集合 是否存在
* @param key key
* @return boolean
*/
@Override
public boolean containsListKey(String key) {
return containsKey(KEY_PREFIX_LIST + key);
}
/**
* 查询缓存 key 是否存在
* @param key key
* @return true/false
*/
@Override
public boolean containsKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Throwable e) {
log.error("判断缓存存在失败key:[" + key + "],错误信息 Codeor[{}]", e);
}
return false;
}
/**
* 根据 key 获取缓存value
* @param key key
* @return value
*/
@Override
public String getValue(String key) {
try {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
return ops.get(KEY_PREFIX_KEY + key);
} catch (Throwable e) {
log.error("根据 key 获取缓存失败,当前key:[{}],失败原因 Codeor:[{}]", key, e);
}
return null;
}
/**
* 缓存set操作
* @param k key
* @param v value
* @param time time
* @return boolean
*/
@Override
public boolean cacheSet(String k, String v, long time) {
try {
String key = KEY_PREFIX_SET + k;
SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
opsForSet.add(key, v);
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Throwable e) {
log.error("缓存 set 失败 当前 key:[{}] 失败原因 [{}]", k, e);
}
return false;
}
/**
* 添加 set 缓存
* @param key key
* @param value value
* @return true/false
*/
@Override
public boolean cacheSet(String key, String value) {
return cacheSet(key, value, -1);
}
/**
* 添加 缓存 set
* @param k key
* @param v value
* @param time 时间
* @return
*/
@Override
public boolean cacheSet(String k, Set<String> v, long time) {
try {
String key = KEY_PREFIX_SET + k;
SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
opsForSet.add(key, v.toArray(new String[v.size()]));
if (time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
return true;
} catch (Throwable e) {
log.error("缓存 set 失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return false;
}
/**
* 缓存 set
* @param k key
* @param v value
* @return
*/
@Override
public boolean cacheSet(String k, Set<String> v) {
return cacheSet(k,v,-1);
}
/**
* 获取缓存set数据
* @param k key
* @return set集合
*/
@Override
public Set<String> getSet(String k) {
try {
String key = KEY_PREFIX_SET + k;
SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
return opsForSet.members(key);
}catch (Throwable e){
log.error("获取缓存set失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return null;
}
/**
* list 缓存
* @param k key
* @param v value
* @param time 时间
* @return true/false
*/
@Override
public boolean cacheList(String k, String v, long time) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
//此处为right push 方法/ 也可以 left push ..
opsForList.rightPush(key,v);
if (time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
return true;
}catch (Throwable e){
log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return false;
}
/**
* 缓存 list
* @param k key
* @param v value
* @return true/false
*/
@Override
public boolean cacheList(String k, String v) {
return cacheList(k,v,-1);
}
/**
* 缓存 list 集合
* @param k key
* @param v value
* @param time 时间
* @return
*/
@Override
public boolean cacheList(String k, List<String> v, long time) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
opsForList.rightPushAll(key,v);
if (time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
return true;
}catch (Throwable e){
log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return false;
}
/**
* 缓存 list
* @param k key
* @param v value
* @return true/false
*/
@Override
public boolean cacheList(String k, List<String> v) {
return cacheList(k,v,-1);
}
/**
* 根据 key 获取 list 缓存
* @param k key
* @param start 开始
* @param end 结束
* @return 获取缓存区间内 所有value
*/
@Override
public List<String> getList(String k, long start, long end) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
return opsForList.range(key,start,end);
}catch (Throwable e){
log.error("获取list缓存失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return null;
}
/**
* 根据 key 获取总条数 用于分页
* @param key key
* @return 条数
*/
@Override
public long getListSize(String key) {
try {
ListOperations<String, String> opsForList = redisTemplate.opsForList();
return opsForList.size(KEY_PREFIX_LIST + key);
}catch (Throwable e){
log.error("获取list长度失败key[" + KEY_PREFIX_LIST + key + "], Codeor[" + e + "]");
}
return 0;
}
/**
* 获取总条数 用于分页
* @param listOps =redisTemplate.opsForList();
* @param k key
* @return size
*/
@Override
public long getListSize(ListOperations<String, String> listOps, String k) {
try {
return listOps.size(k);
}catch (Throwable e){
log.error("获取list长度失败key[" + KEY_PREFIX_LIST + k + "], Codeor[" + e + "]");
}
return 0;
}
/**
* 根据 key 移除 list 缓存
* @param k key
* @return
*/
@Override
public boolean removeOneOfList(String k) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
opsForList.rightPop(key);
return true;
}catch (Throwable e){
log.error("移除list缓存失败 key[" + KEY_PREFIX_LIST + k + "], Codeor[" + e + "]");
}
return false;
}
/**
* 根据 key 移除 value 缓存
*
* @param key key
* @return true/false
*/
@Override
public boolean removeValue(String key) {
return remove(KEY_PREFIX_KEY + key);
}
/**
* 根据 key 移除 set 缓存
*
* @param key key
* @return true/false
*/
@Override
public boolean removeSet(String key) {
return remove(KEY_PREFIX_SET + key);
}
/**
* 根据 key 移除 list 缓存
*
* @param key key
* @return true/false
*/
@Override
public boolean removeList(String key) {
return remove(KEY_PREFIX_LIST + key);
}
/**
* 移除缓存
*
* @param key key
* @return boolean
*/
private boolean remove(String key) {
try {
redisTemplate.delete(key);
return true;
} catch (Throwable e) {
log.error("移除缓存失败 key:[{}] 失败原因 [{}]", key, e);
}
return false;
}
}
4.2. RedisServiceImpl 服务层实现类代码
import com.technologies.bear.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author tinghesi
*/
@Service
public class RedisServiceImpl implements RedisService {
/**
* slf4j 日志
*/
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 自定义 key 三种
* String key:String value 普通key:value
* String key:Set<String> set key:set集合
* String key:List<String> list key:list集合
*/
private static final String KEY_PREFIX_KEY = "info:bear:key";
private static final String KEY_PREFIX_SET = "info:bear:set";
private static final String KEY_PREFIX_LIST = "info:bear:list";
private final RedisTemplate<String, String> redisTemplate;
/**
* 注入
* @param redisTemplate 模板
*/
@Autowired
public RedisServiceImpl(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 添加 key:string 缓存
* @param k key
* @param v value
* @param time time
* @return
*/
@Override
public boolean cacheValue(String k, String v, long time) {
try {
String key = KEY_PREFIX_KEY + k;
ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.set(key, v);
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Throwable e) {
log.error("缓存存入失败key:[{}] value:[{}]", k, v);
}
return false;
}
/**
* 添加 key:string 缓存
* @param key key
* @param value value
* @return
*/
@Override
public boolean cacheValue(String key, String value) {
return cacheValue(key, value, -1);
}
/**
* 根据 key:string 判断缓存是否存在
* @param key key
* @return boolean
*/
@Override
public boolean containsValueKey(String key) {
return containsKey(KEY_PREFIX_KEY + key);
}
/**
* 判断缓存 key:set集合 是否存在
* @param key key
* @return
*/
@Override
public boolean containsSetKey(String key) {
return containsKey(KEY_PREFIX_SET + key);
}
/**
* 判断缓存 key:list集合 是否存在
* @param key key
* @return boolean
*/
@Override
public boolean containsListKey(String key) {
return containsKey(KEY_PREFIX_LIST + key);
}
/**
* 查询缓存 key 是否存在
* @param key key
* @return true/false
*/
@Override
public boolean containsKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Throwable e) {
log.error("判断缓存存在失败key:[" + key + "],错误信息 Codeor[{}]", e);
}
return false;
}
/**
* 根据 key 获取缓存value
* @param key key
* @return value
*/
@Override
public String getValue(String key) {
try {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
return ops.get(KEY_PREFIX_KEY + key);
} catch (Throwable e) {
log.error("根据 key 获取缓存失败,当前key:[{}],失败原因 Codeor:[{}]", key, e);
}
return null;
}
/**
* 缓存set操作
* @param k key
* @param v value
* @param time time
* @return boolean
*/
@Override
public boolean cacheSet(String k, String v, long time) {
try {
String key = KEY_PREFIX_SET + k;
SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
opsForSet.add(key, v);
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Throwable e) {
log.error("缓存 set 失败 当前 key:[{}] 失败原因 [{}]", k, e);
}
return false;
}
/**
* 添加 set 缓存
* @param key key
* @param value value
* @return true/false
*/
@Override
public boolean cacheSet(String key, String value) {
return cacheSet(key, value, -1);
}
/**
* 添加 缓存 set
* @param k key
* @param v value
* @param time 时间
* @return
*/
@Override
public boolean cacheSet(String k, Set<String> v, long time) {
try {
String key = KEY_PREFIX_SET + k;
SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
opsForSet.add(key, v.toArray(new String[v.size()]));
if (time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
return true;
} catch (Throwable e) {
log.error("缓存 set 失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return false;
}
/**
* 缓存 set
* @param k key
* @param v value
* @return
*/
@Override
public boolean cacheSet(String k, Set<String> v) {
return cacheSet(k,v,-1);
}
/**
* 获取缓存set数据
* @param k key
* @return set集合
*/
@Override
public Set<String> getSet(String k) {
try {
String key = KEY_PREFIX_SET + k;
SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
return opsForSet.members(key);
}catch (Throwable e){
log.error("获取缓存set失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return null;
}
/**
* list 缓存
* @param k key
* @param v value
* @param time 时间
* @return true/false
*/
@Override
public boolean cacheList(String k, String v, long time) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
//此处为right push 方法/ 也可以 left push ..
opsForList.rightPush(key,v);
if (time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
return true;
}catch (Throwable e){
log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return false;
}
/**
* 缓存 list
* @param k key
* @param v value
* @return true/false
*/
@Override
public boolean cacheList(String k, String v) {
return cacheList(k,v,-1);
}
/**
* 缓存 list 集合
* @param k key
* @param v value
* @param time 时间
* @return
*/
@Override
public boolean cacheList(String k, List<String> v, long time) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
opsForList.rightPushAll(key,v);
if (time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
return true;
}catch (Throwable e){
log.error("缓存list失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return false;
}
/**
* 缓存 list
* @param k key
* @param v value
* @return true/false
*/
@Override
public boolean cacheList(String k, List<String> v) {
return cacheList(k,v,-1);
}
/**
* 根据 key 获取 list 缓存
* @param k key
* @param start 开始
* @param end 结束
* @return 获取缓存区间内 所有value
*/
@Override
public List<String> getList(String k, long start, long end) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
return opsForList.range(key,start,end);
}catch (Throwable e){
log.error("获取list缓存失败 当前 key:[{}],失败原因 [{}]", k, e);
}
return null;
}
/**
* 根据 key 获取总条数 用于分页
* @param key key
* @return 条数
*/
@Override
public long getListSize(String key) {
try {
ListOperations<String, String> opsForList = redisTemplate.opsForList();
return opsForList.size(KEY_PREFIX_LIST + key);
}catch (Throwable e){
log.error("获取list长度失败key[" + KEY_PREFIX_LIST + key + "], Codeor[" + e + "]");
}
return 0;
}
/**
* 获取总条数 用于分页
* @param listOps =redisTemplate.opsForList();
* @param k key
* @return size
*/
@Override
public long getListSize(ListOperations<String, String> listOps, String k) {
try {
return listOps.size(k);
}catch (Throwable e){
log.error("获取list长度失败key[" + KEY_PREFIX_LIST + k + "], Codeor[" + e + "]");
}
return 0;
}
/**
* 根据 key 移除 list 缓存
* @param k key
* @return
*/
@Override
public boolean removeOneOfList(String k) {
try {
String key = KEY_PREFIX_LIST + k;
ListOperations<String, String> opsForList = redisTemplate.opsForList();
opsForList.rightPop(key);
return true;
}catch (Throwable e){
log.error("移除list缓存失败 key[" + KEY_PREFIX_LIST + k + "], Codeor[" + e + "]");
}
return false;
}
/**
* 根据 key 移除 value 缓存
* @param key key
* @return true/false
*/
@Override
public boolean removeValue(String key) {
return remove(KEY_PREFIX_KEY + key);
}
/**
* 根据 key 移除 set 缓存
* @param key key
* @return true/false
*/
@Override
public boolean removeSet(String key) {
return remove(KEY_PREFIX_SET + key);
}
/**
* 根据 key 移除 list 缓存
* @param key key
* @return true/false
*/
@Override
public boolean removeList(String key) {
return remove(KEY_PREFIX_LIST + key);
}
/**
* 移除缓存
* @param key key
* @return boolean
*/
private boolean remove(String key) {
try {
redisTemplate.delete(key);
return true;
} catch (Throwable e) {
log.error("移除缓存失败 key:[{}] 失败原因 [{}]", key, e);
}
return false;
}
}
引用自:《 spring boot整合redis,设置缓存过期时间 》
5. Redis(缓存)常见的问题
5.1. 缓存穿透
缓存穿透指查询一个一定不存在的数据,由于缓存不命中就会从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据都要到数据库查询,造成缓存穿透。这种情况其实就是因为数据库不存在的数据无法写入缓存,解决这个问题的方法,就是把这种数据库查不到值的情况也考虑进去。
解决方案:
- 缓存空值,将数据库查询不到的则作为空值存入,那么下次可以从缓存中获取key值是空值可以判断出数据库不存在这个值。
- 使用布隆过滤器,布隆过滤器能判断一个 key 一定不存在(不保证一定存在,因为布隆过滤器结构原因,不能删除,但是旧值可能被新值替换,而将旧值删除后它可能依旧判断其可能存在),在缓存的基础上,构建布隆过滤器数据结构,在布隆过滤器中存储对应的 key,如果存在,则说明 key 对应的值为空。
5.2. 缓存击穿
缓存击穿针对某些高频访问的key,当这个key失效的瞬间,大量的请求击穿了缓存,直接请求数据库。
解决方案:
- 设置二级缓存
- 设置高频key缓存永不过期
- 使用互斥锁,在执行过程中,如果缓存过期,那么先获取分布式锁,在执行从数据库中加载数据,如果找到数据就加入缓存,没有就继续该有的动作,在这个过程中能保证只有一个线程操作数据库,避免了对数据库的大量请求。
5.3. 缓存雪崩
缓存雪崩指缓存服务器重启、或者大量缓存集中在某一个时间段失效,这样失效的时候,会给后端系统带来很大压力,造成数据库的故障。
解决方案:
- 缓存高可用设计,Redis sentinel和Redis Cluster等
- 请求限流与服务熔断降级机制,限制服务请求次数,当服务不可用时快速熔断降级。
- 设置缓存过期时间一定的随机分布,避免集中在同一时间缓存失效,可以在设计时将时间定为一个固定值+随机值。
- 定时更新缓存策略,对于实时性要求不高的数据,定时进行更新。