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 保证方法被调用,又希望结果被缓存。
  1. @Cacheable 表示将返回结果缓存到redis,key值为dict::{ {第一个参数}}
    “#p0”表示取第一个参数,如果参数为对象,则可以通过#p0.id获取对象的id。
    就是将查询结果缓存到redis中,(key="#p0")指定传入的第一个参数作为redis的key。

  2. @CacheEvict 表示删除该缓存数据,就是指定key,删除缓存数据,allEntries=true,方法调用后将立即清除缓存。

  3. @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等
  • 请求限流与服务熔断降级机制,限制服务请求次数,当服务不可用时快速熔断降级。
  • 设置缓存过期时间一定的随机分布,避免集中在同一时间缓存失效,可以在设计时将时间定为一个固定值+随机值。
  • 定时更新缓存策略,对于实时性要求不高的数据,定时进行更新。

猜你喜欢

转载自blog.csdn.net/baidu_41847368/article/details/114835707