缓存(redis)安装使用+分布式锁+springcache

目录

一、缓存分类

1.本地缓存

2.分布式缓存

二、Redis安装

1.之前的docker安装有Docker+Mysql+Redis+Elaticsearch+Kibana+nginx-CSDN博客

2.windows安装解压式

三、SpringBoot使用Redis 

1.整合Redis

2.封装redis

3.测试使用即可

​编辑

四、缓存穿透

五、缓存雪崩 

六、缓存击穿

1.碰到的问题 

2.解决 

3.本地锁的局限性

七、 分布式锁Redisson

1.Redisson整合依赖

 2.redissson配置类

3.可重入锁 

4.读写锁

5. 闭锁

6. 信号量(Semaphore)--限流

 7.缓存数据一致性问题

八、SpringCache

(AOP防止缓存与业务聚合)

1.依赖引入

2.配置 (此处使用redis)

3.使用

3.1常用的注解:

①@Cacheable:用于查询

②@CachePut:用于更新

 ③@CacheEvict:清空缓存

④@Caching:组合注解

 ⑤@CacheConfig:公共配置

4.默认处理结果

5.自定义key和TTL 

6.JSON格式存储

7.不足与解决 


一、缓存分类

1.本地缓存

其实就是把缓存数据存储在内存中(Map <String,Object>).在单体架构中肯定没有问题。

2.分布式缓存

在分布式环境下,我们原来的本地缓存就不是太使用了,原因是:

  • 缓存数据冗余

  • 缓存效率不高

分布式缓存的结构图  

二、Redis安装

1.之前的docker安装有Docker+Mysql+Redis+Elaticsearch+Kibana+nginx-CSDN博客

2.windows安装解压式

Redis安装地址

2.1解压

2.2配置密码端口等(可选)

1. 打开redis.windows.conf文件  找到requirepass 放开并修改值

requirepass root

2.找到port 6379 并修改值即可

2.3启动

redis-server --service-install redis.windows.conf

三、SpringBoot使用Redis 

1.整合Redis

要整合Redis那么我们在SpringBoot项目中首页来添加对应的依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

导入到common模块中 其他模块报错了  我们就单独导入到我们需要使用的模块中即可

然后我们需要添加对应的配置信息  

redis:
    host:your Ip
    port:your Port

有密码的话:

spring下的:
redis:
  host: 127.0.0.1
  port: 6379
  password: root

测试操作Redis的数据  

 @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Test
    public void testStringRedisTemplate(){
        // 获取操作String类型的Options对象
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        // 插入数据
        ops.set("name","111"+ UUID.randomUUID());
        // 获取存储的信息
        System.out.println("刚刚保存的值:"+ops.get("name"));
    }

2.封装redis

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
 
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
 
/**
 * 工具类
 * @author xjx
 * @email 15340655443@163.com
 * @date 2024-01-03 17:49:14
 */
 
@Component
public class RedisCaching extends CachingConfigurerSupport {
 
 
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

 
         /*-------------------key相关操作---------------------*/
 
        /**
         * 删除key
         *
         * @param key
         */
        public void delete(String key) {
            stringRedisTemplate.delete(key);
        }
 
        /**
         * 批量删除key
         *
         * @param keys
         */
        public void delete(Collection<String> keys) {
            stringRedisTemplate.delete(keys);
        }
 
        /**
         * 序列化key
         *
         * @param key
         * @return
         */
        public byte[] dump(String key) {
            return stringRedisTemplate.dump(key);
        }
 
        /**
         * 是否存在key
         *
         * @param key
         * @return
         */
        public Boolean exists(String key) {
            return stringRedisTemplate.hasKey(key);
        }
 
        /**
         * 设置过期时间
         *
         * @param key
         * @param timeout
         * @param unit
         * @return
         */
        public Boolean expire(String key, long timeout, TimeUnit unit) {
            return stringRedisTemplate.expire(key, timeout, unit);
        }
 
        /**
         * 设置过期时间
         *
         * @param key
         * @param date
         * @return
         */
        public Boolean expireAt(String key, Date date) {
            return stringRedisTemplate.expireAt(key, date);
        }
 
        /**
         * 查找匹配的key
         *
         * @param pattern
         * @return
         */
        public Set<String> keys(String pattern) {
            return stringRedisTemplate.keys(pattern);
        }
 
        /**
         * 将当前数据库的 key 移动到给定的数据库 db 当中
         *
         * @param key
         * @param dbIndex
         * @return
         */
        public Boolean move(String key, int dbIndex) {
            return stringRedisTemplate.move(key, dbIndex);
        }
 
        /**
         * 移除 key 的过期时间,key 将持久保持
         *
         * @param key
         * @return
         */
        public Boolean persist(String key) {
            return stringRedisTemplate.persist(key);
        }
 
        /**
         * 返回 key 的剩余的过期时间
         *
         * @param key
         * @param unit
         * @return
         */
        public Long getExpire(String key, TimeUnit unit) {
            return stringRedisTemplate.getExpire(key, unit);
        }
 
        /**
         * 返回 key 的剩余的过期时间
         *
         * @param key
         * @return
         */
        public Long getExpire(String key) {
            return stringRedisTemplate.getExpire(key);
        }
 
        /**
         * 从当前数据库中随机返回一个 key
         *
         * @return
         */
        public String randomKey() {
            return stringRedisTemplate.randomKey();
        }
 
        /**
         * 修改 key 的名称
         *
         * @param oldKey
         * @param newKey
         */
        public void rename(String oldKey, String newKey) {
            stringRedisTemplate.rename(oldKey, newKey);
        }
 
        /**
         * 仅当 newkey 不存在时,将 oldKey 改名为 newkey
         *
         * @param oldKey
         * @param newKey
         * @return
         */
        public Boolean renameIfAbsent(String oldKey, String newKey) {
            return stringRedisTemplate.renameIfAbsent(oldKey, newKey);
        }
 
        /**
         * 返回 key 所储存的值的类型
         *
         * @param key
         * @return
         */
        public DataType type(String key) {
            return stringRedisTemplate.type(key);
        }
 
        /** -------------------string相关操作--------------------- */
 
        /**
         * 设置指定 key 的值
         * @param key
         * @param value
         */
        public void set(String key, String value) {
            stringRedisTemplate.opsForValue().set(key, value);
        }
 
        /**
         * 获取指定 key 的值
         * @param key
         * @return
         */
        public String get(String key) {
            return stringRedisTemplate.opsForValue().get(key);
        }
 
        /**
         * 返回 key 中字符串值的子字符
         * @param key
         * @param start
         * @param end
         * @return
         */
        public String getRange(String key, long start, long end) {
            return stringRedisTemplate.opsForValue().get(key, start, end);
        }
 
        /**
         * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
         *
         * @param key
         * @param value
         * @return
         */
        public String getAndSet(String key, String value) {
            return stringRedisTemplate.opsForValue().getAndSet(key, value);
        }
 
        /**
         * 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
         *
         * @param key
         * @param offset
         * @return
         */
        public Boolean getBit(String key, long offset) {
            return stringRedisTemplate.opsForValue().getBit(key, offset);
        }
 
        /**
         * 批量获取
         *
         * @param keys
         * @return
         */
        public List<String> multiGet(Collection<String> keys) {
            return stringRedisTemplate.opsForValue().multiGet(keys);
        }
 
        /**
         * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
         *
         * @param key
         * @param
         * @param value
         *            值,true为1, false为0
         * @return
         */
        public boolean setBit(String key, long offset, boolean value) {
            return stringRedisTemplate.opsForValue().setBit(key, offset, value);
        }
 
        /**
         * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
         *
         * @param key
         * @param value
         * @param timeout
         *            过期时间
         * @param unit
         *            时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
         *            秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
         */
        public void setEx(String key, String value, long timeout, TimeUnit unit) {
            stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
        }
 
        /**
         * 只有在 key 不存在时设置 key 的值
         *
         * @param key
         * @param value
         * @return 之前已经存在返回false,不存在返回true
         */
        public boolean setIfAbsent(String key, String value) {
            return stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        }
 
        /**
         * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
         *
         * @param key
         * @param value
         * @param offset
         *            从指定位置开始覆写
         */
        public void setRange(String key, String value, long offset) {
            stringRedisTemplate.opsForValue().set(key, value, offset);
        }
 
        /**
         * 获取字符串的长度
         *
         * @param key
         * @return
         */
        public Long size(String key) {
            return stringRedisTemplate.opsForValue().size(key);
        }
 
        /**
         * 批量添加
         *
         * @param maps
         */
        public void multiSet(Map<String, String> maps) {
            stringRedisTemplate.opsForValue().multiSet(maps);
        }
 
        /**
         * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
         *
         * @param maps
         * @return 之前已经存在返回false,不存在返回true
         */
        public boolean multiSetIfAbsent(Map<String, String> maps) {
            return stringRedisTemplate.opsForValue().multiSetIfAbsent(maps);
        }
 
        /**
         * 增加(自增长), 负数则为自减
         *
         * @param key
         * @param
         * @return
         */
        public Long incrBy(String key, long increment) {
            return stringRedisTemplate.opsForValue().increment(key, increment);
        }
 
        /**
         *
         * @param key
         * @param
         * @return
         */
        public Double incrByFloat(String key, double increment) {
            return stringRedisTemplate.opsForValue().increment(key, increment);
        }
 
        /**
         * 追加到末尾
         *
         * @param key
         * @param value
         * @return
         */
        public Integer append(String key, String value) {
            return stringRedisTemplate.opsForValue().append(key, value);
        }
 
        /** -------------------hash相关操作------------------------- */
 
        /**
         * 获取存储在哈希表中指定字段的值
         *
         * @param key
         * @param field
         * @return
         */
        public Object hGet(String key, String field) {
            return stringRedisTemplate.opsForHash().get(key, field);
        }
 
        /**
         * 获取所有给定字段的值
         *
         * @param key
         * @return
         */
        public Map<Object, Object> hGetAll(String key) {
            return stringRedisTemplate.opsForHash().entries(key);
        }
 
        /**
         * 获取所有给定字段的值
         *
         * @param key
         * @param fields
         * @return
         */
        public List<Object> hMultiGet(String key, Collection<Object> fields) {
            return stringRedisTemplate.opsForHash().multiGet(key, fields);
        }
 
        public void hPut(String key, String hashKey, String value) {
            stringRedisTemplate.opsForHash().put(key, hashKey, value);
        }
 
        public void hPutAll(String key, Map<String, String> maps) {
            stringRedisTemplate.opsForHash().putAll(key, maps);
        }
 
        /**
         * 仅当hashKey不存在时才设置
         *
         * @param key
         * @param hashKey
         * @param value
         * @return
         */
        public Boolean hPutIfAbsent(String key, String hashKey, String value) {
            return stringRedisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
        }
 
        /**
         * 删除一个或多个哈希表字段
         *
         * @param key
         * @param fields
         * @return
         */
        public Long hDelete(String key, Object... fields) {
            return stringRedisTemplate.opsForHash().delete(key, fields);
        }
 
        /**
         * 查看哈希表 key 中,指定的字段是否存在
         *
         * @param key
         * @param field
         * @return
         */
        public boolean hExists(String key, String field) {
            return stringRedisTemplate.opsForHash().hasKey(key, field);
        }
 
        /**
         * 为哈希表 key 中的指定字段的整数值加上增量 increment
         *
         * @param key
         * @param field
         * @param increment
         * @return
         */
        public Long hIncrBy(String key, Object field, long increment) {
            return stringRedisTemplate.opsForHash().increment(key, field, increment);
        }
 
        /**
         * 为哈希表 key 中的指定字段的整数值加上增量 increment
         *
         * @param key
         * @param field
         * @param delta
         * @return
         */
        public Double hIncrByFloat(String key, Object field, double delta) {
            return stringRedisTemplate.opsForHash().increment(key, field, delta);
        }
 
        /**
         * 获取所有哈希表中的字段
         *
         * @param key
         * @return
         */
        public Set<Object> hKeys(String key) {
            return stringRedisTemplate.opsForHash().keys(key);
        }
 
        /**
         * 获取哈希表中字段的数量
         *
         * @param key
         * @return
         */
        public Long hSize(String key) {
            return stringRedisTemplate.opsForHash().size(key);
        }
 
        /**
         * 获取哈希表中所有值
         *
         * @param key
         * @return
         */
        public List<Object> hValues(String key) {
            return stringRedisTemplate.opsForHash().values(key);
        }
 
        /**
         * 迭代哈希表中的键值对
         *
         * @param key
         * @param options
         * @return
         */
        public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
            return stringRedisTemplate.opsForHash().scan(key, options);
        }
 
        /** ------------------------list相关操作---------------------------- */
 
        /**
         * 通过索引获取列表中的元素
         *
         * @param key
         * @param index
         * @return
         */
        public String lIndex(String key, long index) {
            return stringRedisTemplate.opsForList().index(key, index);
        }
 
        /**
         * 获取列表指定范围内的元素
         *
         * @param key
         * @param start
         *            开始位置, 0是开始位置
         * @param end
         *            结束位置, -1返回所有
         * @return
         */
        public List<String> lRange(String key, long start, long end) {
            return stringRedisTemplate.opsForList().range(key, start, end);
        }
 
        /**
         * 存储在list头部
         *
         * @param key
         * @param value
         * @return
         */
        public Long lLeftPush(String key, String value) {
            return stringRedisTemplate.opsForList().leftPush(key, value);
        }
 
        /**
         *
         * @param key
         * @param value
         * @return
         */
        public Long lLeftPushAll(String key, String... value) {
            return stringRedisTemplate.opsForList().leftPushAll(key, value);
        }
 
        /**
         *
         * @param key
         * @param value
         * @return
         */
        public Long lLeftPushAll(String key, Collection<String> value) {
            return stringRedisTemplate.opsForList().leftPushAll(key, value);
        }
 
        /**
         * 当list存在的时候才加入
         *
         * @param key
         * @param value
         * @return
         */
        public Long lLeftPushIfPresent(String key, String value) {
            return stringRedisTemplate.opsForList().leftPushIfPresent(key, value);
        }
 
        /**
         * 如果pivot存在,再pivot前面添加
         *
         * @param key
         * @param pivot
         * @param value
         * @return
         */
        public Long lLeftPush(String key, String pivot, String value) {
            return stringRedisTemplate.opsForList().leftPush(key, pivot, value);
        }
 
        /**
         *
         * @param key
         * @param value
         * @return
         */
        public Long lRightPush(String key, String value) {
            return stringRedisTemplate.opsForList().rightPush(key, value);
        }
 
        /**
         *
         * @param key
         * @param value
         * @return
         */
        public Long lRightPushAll(String key, String... value) {
            return stringRedisTemplate.opsForList().rightPushAll(key, value);
        }
 
        /**
         *
         * @param key
         * @param value
         * @return
         */
        public Long lRightPushAll(String key, Collection<String> value) {
            return stringRedisTemplate.opsForList().rightPushAll(key, value);
        }
 
        /**
         * 为已存在的列表添加值
         *
         * @param key
         * @param value
         * @return
         */
        public Long lRightPushIfPresent(String key, String value) {
            return stringRedisTemplate.opsForList().rightPushIfPresent(key, value);
        }
 
        /**
         * 在pivot元素的右边添加值
         *
         * @param key
         * @param pivot
         * @param value
         * @return
         */
        public Long lRightPush(String key, String pivot, String value) {
            return stringRedisTemplate.opsForList().rightPush(key, pivot, value);
        }
 
        /**
         * 通过索引设置列表元素的值
         *
         * @param key
         * @param index
         *            位置
         * @param value
         */
        public void lSet(String key, long index, String value) {
            stringRedisTemplate.opsForList().set(key, index, value);
        }
 
        /**
         * 移出并获取列表的第一个元素
         *
         * @param key
         * @return 删除的元素
         */
        public String lLeftPop(String key) {
            return stringRedisTemplate.opsForList().leftPop(key);
        }
 
        /**
         * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
         *
         * @param key
         * @param timeout
         *            等待时间
         * @param unit
         *            时间单位
         * @return
         */
        public String lBLeftPop(String key, long timeout, TimeUnit unit) {
            return stringRedisTemplate.opsForList().leftPop(key, timeout, unit);
        }
 
        /**
         * 移除并获取列表最后一个元素
         *
         * @param key
         * @return 删除的元素
         */
        public String lRightPop(String key) {
            return stringRedisTemplate.opsForList().rightPop(key);
        }
 
        /**
         * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
         *
         * @param key
         * @param timeout
         *            等待时间
         * @param unit
         *            时间单位
         * @return
         */
        public String lBRightPop(String key, long timeout, TimeUnit unit) {
            return stringRedisTemplate.opsForList().rightPop(key, timeout, unit);
        }
 
        /**
         * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
         *
         * @param sourceKey
         * @param destinationKey
         * @return
         */
        public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
            return stringRedisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                    destinationKey);
        }
 
        /**
         * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
         *
         * @param sourceKey
         * @param destinationKey
         * @param timeout
         * @param unit
         * @return
         */
        public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
                                            long timeout, TimeUnit unit) {
            return stringRedisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                    destinationKey, timeout, unit);
        }
 
        /**
         * 删除集合中值等于value得元素
         *
         * @param key
         * @param index
         *            index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
         *            index<0, 从尾部开始删除第一个值等于value的元素;
         * @param value
         * @return
         */
        public Long lRemove(String key, long index, String value) {
            return stringRedisTemplate.opsForList().remove(key, index, value);
        }
 
        /**
         * 裁剪list
         *
         * @param key
         * @param start
         * @param end
         */
        public void lTrim(String key, long start, long end) {
            stringRedisTemplate.opsForList().trim(key, start, end);
        }
 
        /**
         * 获取列表长度
         *
         * @param key
         * @return
         */
        public Long lLen(String key) {
            return stringRedisTemplate.opsForList().size(key);
        }
 
 
        /** --------------------set相关操作-------------------------- */
 
        /**
         * set添加元素
         *
         * @param key
         * @param values
         * @return
         */
        public Long sAdd(String key, String... values) {
            return stringRedisTemplate.opsForSet().add(key, values);
        }
 
        /**
         * set移除元素
         *
         * @param key
         * @param values
         * @return
         */
        public Long sRemove(String key, Object... values) {
            return stringRedisTemplate.opsForSet().remove(key, values);
        }
 
        /**
         * 移除并返回集合的一个随机元素
         *
         * @param key
         * @return
         */
        public String sPop(String key) {
            return stringRedisTemplate.opsForSet().pop(key);
        }
 
        /**
         * 将元素value从一个集合移到另一个集合
         *
         * @param key
         * @param value
         * @param destKey
         * @return
         */
        public Boolean sMove(String key, String value, String destKey) {
            return stringRedisTemplate.opsForSet().move(key, value, destKey);
        }
 
        /**
         * 获取集合的大小
         *
         * @param key
         * @return
         */
        public Long sSize(String key) {
            return stringRedisTemplate.opsForSet().size(key);
        }
 
        /**
         * 判断集合是否包含value
         *
         * @param key
         * @param value
         * @return
         */
        public Boolean sIsMember(String key, Object value) {
            return stringRedisTemplate.opsForSet().isMember(key, value);
        }
 
        /**
         * 获取两个集合的交集
         *
         * @param key
         * @param otherKey
         * @return
         */
        public Set<String> sIntersect(String key, String otherKey) {
            return stringRedisTemplate.opsForSet().intersect(key, otherKey);
        }
 
        /**
         * 获取key集合与多个集合的交集
         *
         * @param key
         * @param otherKeys
         * @return
         */
        public Set<String> sIntersect(String key, Collection<String> otherKeys) {
            return stringRedisTemplate.opsForSet().intersect(key, otherKeys);
        }
 
        /**
         * key集合与otherKey集合的交集存储到destKey集合中
         *
         * @param key
         * @param otherKey
         * @param destKey
         * @return
         */
        public Long sIntersectAndStore(String key, String otherKey, String destKey) {
            return stringRedisTemplate.opsForSet().intersectAndStore(key, otherKey,
                    destKey);
        }
 
        /**
         * key集合与多个集合的交集存储到destKey集合中
         *
         * @param key
         * @param otherKeys
         * @param destKey
         * @return
         */
        public Long sIntersectAndStore(String key, Collection<String> otherKeys,
                                       String destKey) {
            return stringRedisTemplate.opsForSet().intersectAndStore(key, otherKeys,
                    destKey);
        }
 
        /**
         * 获取两个集合的并集
         *
         * @param key
         * @param otherKeys
         * @return
         */
        public Set<String> sUnion(String key, String otherKeys) {
            return stringRedisTemplate.opsForSet().union(key, otherKeys);
        }
 
        /**
         * 获取key集合与多个集合的并集
         *
         * @param key
         * @param otherKeys
         * @return
         */
        public Set<String> sUnion(String key, Collection<String> otherKeys) {
            return stringRedisTemplate.opsForSet().union(key, otherKeys);
        }
 
        /**
         * key集合与otherKey集合的并集存储到destKey中
         *
         * @param key
         * @param otherKey
         * @param destKey
         * @return
         */
        public Long sUnionAndStore(String key, String otherKey, String destKey) {
            return stringRedisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
        }
 
        /**
         * key集合与多个集合的并集存储到destKey中
         *
         * @param key
         * @param otherKeys
         * @param destKey
         * @return
         */
        public Long sUnionAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
            return stringRedisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
        }
 
        /**
         * 获取两个集合的差集
         *
         * @param key
         * @param otherKey
         * @return
         */
        public Set<String> sDifference(String key, String otherKey) {
            return stringRedisTemplate.opsForSet().difference(key, otherKey);
        }
 
        /**
         * 获取key集合与多个集合的差集
         *
         * @param key
         * @param otherKeys
         * @return
         */
        public Set<String> sDifference(String key, Collection<String> otherKeys) {
            return stringRedisTemplate.opsForSet().difference(key, otherKeys);
        }
 
        /**
         * key集合与otherKey集合的差集存储到destKey中
         *
         * @param key
         * @param otherKey
         * @param destKey
         * @return
         */
        public Long sDifference(String key, String otherKey, String destKey) {
            return stringRedisTemplate.opsForSet().differenceAndStore(key, otherKey,
                    destKey);
        }
 
        /**
         * key集合与多个集合的差集存储到destKey中
         *
         * @param key
         * @param otherKeys
         * @param destKey
         * @return
         */
        public Long sDifference(String key, Collection<String> otherKeys,
                                String destKey) {
            return stringRedisTemplate.opsForSet().differenceAndStore(key, otherKeys,
                    destKey);
        }
 
        /**
         * 获取集合所有元素
         *
         * @param key
         * @param
         * @param
         * @return
         */
        public Set<String> setMembers(String key) {
            return stringRedisTemplate.opsForSet().members(key);
        }
 
        /**
         * 随机获取集合中的一个元素
         *
         * @param key
         * @return
         */
        public String sRandomMember(String key) {
            return stringRedisTemplate.opsForSet().randomMember(key);
        }
 
        /**
         * 随机获取集合中count个元素
         *
         * @param key
         * @param count
         * @return
         */
        public List<String> sRandomMembers(String key, long count) {
            return stringRedisTemplate.opsForSet().randomMembers(key, count);
        }
 
        /**
         * 随机获取集合中count个元素并且去除重复的
         *
         * @param key
         * @param count
         * @return
         */
        public Set<String> sDistinctRandomMembers(String key, long count) {
            return stringRedisTemplate.opsForSet().distinctRandomMembers(key, count);
        }
 
        /**
         *
         * @param key
         * @param options
         * @return
         */
        public Cursor<String> sScan(String key, ScanOptions options) {
            return stringRedisTemplate.opsForSet().scan(key, options);
        }
 
        /**------------------zSet相关操作--------------------------------*/
 
        /**
         * 添加元素,有序集合是按照元素的score值由小到大排列
         *
         * @param key
         * @param value
         * @param score
         * @return
         */
        public Boolean zAdd(String key, String value, double score) {
            return stringRedisTemplate.opsForZSet().add(key, value, score);
        }
 
        /**
         *
         * @param key
         * @param values
         * @return
         */
        public Long zAdd(String key, Set<TypedTuple<String>> values) {
            return stringRedisTemplate.opsForZSet().add(key, values);
        }
 
        /**
         *
         * @param key
         * @param values
         * @return
         */
        public Long zRemove(String key, Object... values) {
            return stringRedisTemplate.opsForZSet().remove(key, values);
        }
 
        public Long zRemove(String key, Collection<String> values) {
            if(values!=null&&!values.isEmpty()){
                Object[] objs = values.toArray(new Object[values.size()]);
                return stringRedisTemplate.opsForZSet().remove(key, objs);
            }
            return 0L;
        }
 
        /**
         * 增加元素的score值,并返回增加后的值
         *
         * @param key
         * @param value
         * @param delta
         * @return
         */
        public Double zIncrementScore(String key, String value, double delta) {
            return stringRedisTemplate.opsForZSet().incrementScore(key, value, delta);
        }
 
        /**
         * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
         *
         * @param key
         * @param value
         * @return 0表示第一位
         */
        public Long zRank(String key, Object value) {
            return stringRedisTemplate.opsForZSet().rank(key, value);
        }
 
        /**
         * 返回元素在集合的排名,按元素的score值由大到小排列
         *
         * @param key
         * @param value
         * @return
         */
        public Long zReverseRank(String key, Object value) {
            return stringRedisTemplate.opsForZSet().reverseRank(key, value);
        }
 
        /**
         * 获取集合的元素, 从小到大排序
         *
         * @param key
         * @param start
         *            开始位置
         * @param end
         *            结束位置, -1查询所有
         * @return
         */
        public Set<String> zRange(String key, long start, long end) {
            return stringRedisTemplate.opsForZSet().range(key, start, end);
        }
 
        /**
         * 获取zset集合的所有元素, 从小到大排序
         *
         */
        public Set<String> zRangeAll(String key) {
            return zRange(key,0,-1);
        }
 
        /**
         * 获取集合元素, 并且把score值也获取
         *
         * @param key
         * @param start
         * @param end
         * @return
         */
        public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
                                                        long end) {
            return stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);
        }
 
        /**
         * 根据Score值查询集合元素
         *
         * @param key
         * @param min
         *            最小值
         * @param max
         *            最大值
         * @return
         */
        public Set<String> zRangeByScore(String key, double min, double max) {
            return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);
        }
 
 
        /**
         * 根据Score值查询集合元素, 从小到大排序
         *
         * @param key
         * @param min
         *            最小值
         * @param max
         *            最大值
         * @return
         */
        public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
                                                               double min, double max) {
            return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
        }
 
        /**
         *
         * @param key
         * @param min
         * @param max
         * @param start
         * @param end
         * @return
         */
        public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
                                                               double min, double max, long start, long end) {
            return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
                    start, end);
        }
 
        /**
         * 获取集合的元素, 从大到小排序
         *
         * @param key
         * @param start
         * @param end
         * @return
         */
        public Set<String> zReverseRange(String key, long start, long end) {
            return stringRedisTemplate.opsForZSet().reverseRange(key, start, end);
 
        }
 
        public Set<String> zReverseRangeByScore(String key, long min, long max) {
            return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
 
        }
 
        /**
         * 获取集合的元素, 从大到小排序, 并返回score值
         *
         * @param key
         * @param start
         * @param end
         * @return
         */
        public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
                                                               long start, long end) {
            return stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, start,
                    end);
        }
 
        /**
         * 根据Score值查询集合元素, 从大到小排序
         *
         * @param key
         * @param min
         * @param max
         * @return
         */
        public Set<String> zReverseRangeByScore(String key, double min,
                                                double max) {
            return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
        }
 
        /**
         * 根据Score值查询集合元素, 从大到小排序
         *
         * @param key
         * @param min
         * @param max
         * @return
         */
        public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
                String key, double min, double max) {
            return stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
                    min, max);
        }
 
        /**
         *
         * @param key
         * @param min
         * @param max
         * @param start
         * @param end
         * @return
         */
        public Set<String> zReverseRangeByScore(String key, double min,
                                                double max, long start, long end) {
            return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
                    start, end);
        }
 
        /**
         * 根据score值获取集合元素数量
         *
         * @param key
         * @param min
         * @param max
         * @return
         */
        public Long zCount(String key, double min, double max) {
            return stringRedisTemplate.opsForZSet().count(key, min, max);
        }
 
        /**
         * 获取集合大小
         *
         * @param key
         * @return
         */
        public Long zSize(String key) {
            return stringRedisTemplate.opsForZSet().size(key);
        }
 
        /**
         * 获取集合大小
         *
         * @param key
         * @return
         */
        public Long zZCard(String key) {
            return stringRedisTemplate.opsForZSet().zCard(key);
        }
 
        /**
         * 获取集合中value元素的score值
         *
         * @param key
         * @param value
         * @return
         */
        public Double zScore(String key, Object value) {
            return stringRedisTemplate.opsForZSet().score(key, value);
        }
 
        /**
         * 移除指定索引位置的成员
         *
         * @param key
         * @param start
         * @param end
         * @return
         */
        public Long zRemoveRange(String key, long start, long end) {
            return stringRedisTemplate.opsForZSet().removeRange(key, start, end);
        }
 
        /**
         * 根据指定的score值的范围来移除成员
         *
         * @param key
         * @param min
         * @param max
         * @return
         */
        public Long zRemoveRangeByScore(String key, double min, double max) {
            return stringRedisTemplate.opsForZSet().removeRangeByScore(key, min, max);
        }
 
        /**
         * 获取key和otherKey的并集并存储在destKey中
         *
         * @param key
         * @param otherKey
         * @param destKey
         * @return
         */
        public Long zUnionAndStore(String key, String otherKey, String destKey) {
            return stringRedisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
        }
 
        /**
         *
         * @param key
         * @param otherKeys
         * @param destKey
         * @return
         */
        public Long zUnionAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
            return stringRedisTemplate.opsForZSet()
                    .unionAndStore(key, otherKeys, destKey);
        }
 
        /**
         * 交集
         *
         * @param key
         * @param otherKey
         * @param destKey
         * @return
         */
        public Long zIntersectAndStore(String key, String otherKey,
                                       String destKey) {
            return stringRedisTemplate.opsForZSet().intersectAndStore(key, otherKey,
                    destKey);
        }
 
        /**
         * 交集
         *
         * @param key
         * @param otherKeys
         * @param destKey
         * @return
         */
        public Long zIntersectAndStore(String key, Collection<String> otherKeys,
                                       String destKey) {
            return stringRedisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
                    destKey);
        }
 
        /**
         *
         * @param key
         * @param options
         * @return
         */
        public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
            return stringRedisTemplate.opsForZSet().scan(key, options);
        }
 
        /**
         * 扫描主键,建议使用
         * @param patten
         * @return
         */
        public Set<String> scan(String patten){
            Set<String> keys = stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
                Set<String> result = new HashSet<>();
                try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                        .match(patten).count(10000).build())) {
                    while (cursor.hasNext()) {
                        result.add(new String(cursor.next()));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return result;
            });
            return  keys;
        }
 
        /**
         * 管道技术,提高性能
         * @param type
         * @param values
         * @return
         */
        public List<Object> lRightPushPipeline(String type,Collection<String> values){
            List<Object> results = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
                    //集合转换数组
                    String[] strings = values.toArray(new String[values.size()]);
                    //直接批量发送
                    stringRedisConn.rPush(type, strings);
                    return null;
                }
            });
            return results;
        }
 
 
        public  List<Object>  refreshWithPipeline(String futureKey,String topicKey,Collection<String> tasks){
 
            List<Object> result = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
                @Nullable
                @Override
                public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    StringRedisConnection stringRedisConnection = (StringRedisConnection)redisConnection;
                    String[] allTask = tasks.toArray(new String[tasks.size()]);
                    stringRedisConnection.lPush(topicKey,allTask);
                    stringRedisConnection.zRem(futureKey,allTask);
                    return null;
                }
            });
            return result;
 
        }
    }

3.测试使用即可

案例

还需要的json依赖

<!--        fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

    /**
     * 查询Catalog2Vo集合
     * 改造查询逻辑-添加缓存机制
     * @return
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalog2VoListJSON() {
        //先去redis缓存中进行查找
        String catalogJSON = redisCaching.get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON)){
            //如果缓存未命中--到数据库中进行查询
            Map<String, List<Catalog2Vo>> jsonForDB = this.getCatalog2VoListJSONForDB();
            //写入redis中
            String jsonString = JSON.toJSONString(jsonForDB);
            redisCaching.set("catalogJSON",jsonString);
            return jsonForDB;
        }
        //缓存命中
        Map<String, List<Catalog2Vo>> jsonForRedis = JSON.parseObject(
                catalogJSON,
                new TypeReference<Map<String, List<Catalog2Vo>>>() {});
        return jsonForRedis;
    }

四、缓存穿透

指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.

利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃,解决方案也比较简单,直接把null结果缓存,并加入短暂的过期时间  值为1过期时间5秒

五、缓存雪崩 

缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就避免集体失效的事件

 

六、缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

解决方案:加锁(这里使用本地锁),大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db。  

1.碰到的问题 

 原因是释放锁和查询结果缓存的时序问题

2.解决 

3.本地锁的局限性

本地锁在分布式环境下,是没有办法锁住其他节点的操作的,这种情况肯定是有问题的

针对本地锁的问题,我们需要通过分布式锁来解决,那么是不是意味着本身锁在分布式场景下就不需要了呢?

 

显然不是这样的,因为如果分布式环境下的每个节点不控制请求的数量,那么分布式锁的压力会非常大,这时我们需要本地锁来控制每个节点的同步,来降低分布式锁的压力,所以实际开发中我们都是本地锁和分布式锁结合使用的。  

七、 分布式锁Redisson

文档查阅Table of Content · redisson/redisson Wiki · GitHub

1.Redisson整合依赖

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.1</version>
        </dependency>

 2.redissson配置类

这里我使用的是第二种文件配置具体可以到官网查询

官网文档Table of Content · redisson/redisson Wiki · GitHub

package com.yueluo.mall.product.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.io.IOException;

/**
 * redis相关配置类
 * @author xjx
 * @email 15340655443@163.com
 * @date 2024/1/25 12:33
 */
@Configuration
public class MyRedisConfig {

    /**
     * redisssonClient整合配置
     * 编程式配置
     * @return
     */
//    @Bean
//    public RedissonClient getRedissonClient(){
//        Config config = new Config();
//        // 配置连接的信息
//        config.useSingleServer()
//                .setAddress("redis://127.0.0.1:6379")
//                .setPassword("root");
//        RedissonClient redissonClient = Redisson.create(config);
//        return  redissonClient;
//    }

    /**
     * redisssonClient整合配置
     * 文件yaml配置
     * @return
     */
    @Bean
    public RedissonClient getRedissonClientByFile(){
        Config config = null;
        try {
            config = Config.fromYAML(new File("src/main/resources/redissonclient.yml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return Redisson.create(config);
    }
}

文件:redissonclient.yml

singleServerConfig:
  address: "redis://127.0.0.1:6379"
  password: "root"

3.可重入锁 

指定了过期时间后那么自动续期就不会生效了,这时我们就需要注意设置的过期时间一定要满足我们的业务场景
实际开发中我们最好指定过期时间->性能角度考虑

/**
     * 1.锁会自动续期,如果业务时间超长,运行期间Redisson会自动给锁重新添加30s,不用担心业务时间,锁自动过去而造成的数据安全问题
     * 2.加锁的业务只要执行完成, 那么就不会给当前的锁续期,即使我们不去主动的释放锁,锁在默认30s之后也会自动的删除
     *
     * 如果我们括定了锁的过期时问,那么在源码中会直接帮我们创建一个过期时间是指定值的锁,时问到期后就会直接把该锁给删除
     * 如果我们没有指定过期时间,那么在执行的时候首先会创建一把锁且过期时间是30s然后会创建异步任务,每个10S执行一次任务来续期
     * @return
     */
    @ResponseBody
    @GetMapping("/hello")
    public String hello(){
        RLock myLock = redissonClient.getLock("myLock");
        // 加锁
        //my Lock.lock();
        //获取锁,并且给定的过期时间是10s问题?业务如果时间超过了10s,会不会自动续期?
        //通过效果演示我们可以发现,指定了过期时间后那么自动续期就不会生效了,这时我们就需要注意设置的过期时间一定要满足我们的业务场景
        //实际开发中我们最好指定过期时间->性能角度考虑
        myLock.lock();
        try {
            System.out.println("加锁成功...业务处理....." + Thread.currentThread().getName());
            Thread.sleep(30000);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("释放锁成功..." +  Thread.currentThread().getName());
            // 释放锁
            myLock.unlock();
        }
        return "hello";
    }

4.读写锁

根据业务操作我们可以分为读写操作,读操作其实不会影响数据,那么如果还对读操作做串行处理,效率会很低,这时我们可以通过读写锁来解决这个问题

在读写锁中,只有读读的行为是共享锁,相互之间不影响,只要有写的行为存在,那么就是一个互斥锁(排他锁)

共享:A加锁  B也可以加锁

互斥:A加锁  B就只能阻塞 

@GetMapping("/writer")
    @ResponseBody
    public String writerValue(){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        // 加写锁
        RLock rLock = readWriteLock.writeLock();
        String s = null;
        rLock.lock(); // 加写锁
        try {
            s = UUID.randomUUID().toString();
            stringRedisTemplate.opsForValue().set("msg",s);
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return s;
    }

    @GetMapping("/reader")
    @ResponseBody
    public String readValue(){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        // 加读锁
        RLock rLock = readWriteLock.readLock();
        rLock.lock();
        String s = null;
        try {
            s = stringRedisTemplate.opsForValue().get("msg");
        }finally {
            rLock.unlock();
        }

        return s;
    }

5. 闭锁

.基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象 RCountDownLatch采用了与 java.util.concurrent.CountDownLatch相似的接口和用法。

控制什么时候出门(执行方法)  类似于开关门

    /**
     * 闭锁的实现
     * lockDoor进行封锁  door.trySetCount(5);  总数为5
     * 当goHome执行一次  door.countDown();  进行递减操作  直至递减为0 那么则会放开lockDoor  开门
     * @return
     */
@GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor(){
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.trySetCount(5);
        try {
            door.await(); // 等待数量降低到0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "关门熄灯...";
    }

    @GetMapping("/goHome/{id}")
    @ResponseBody
    public String goHome(@PathVariable Long id){
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.countDown(); // 递减的操作
        return id + "下班走人";
    }

6. 信号量(Semaphore)--限流

基于Redis的Redisson的分布式信号量(Semaphore)Java对象

RSemaphore采用了与 java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口

控制什么时候进(满了没满) 类似于停车场

 /**
     * 信号量
     * 相当于停车场  要事先指定一个key(这里指定的是park在redis中表示可以停放的车辆总数)
     * 停一辆车park()  在redis中的值就会减少一次    release()释放一辆车  redis的值就会增加一辆
     * @return
     */
@GetMapping("/park")
    @ResponseBody
    public String park(){
        RSemaphore park = redissonClient.getSemaphore("park");
        boolean b = true;
        try {
            // park.acquire(); // 获取信号 阻塞到获取成功
            b = park.tryAcquire();// 返回获取成功还是失败
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "停车是否成功:" + b;
    }

    @GetMapping("/release")
    @ResponseBody
    public String release(){
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release();
        return "释放了一个车位";
    }

 7.缓存数据一致性问题

针对于上的两种解决方案我们怎么选择?

  1. 缓存的所有数据我们都加上过期时间,数据过期之后主动触发更新操作

  2. 使用读写锁来处理,读读的操作是不相互影响的

无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?

  1. 如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可

  2. 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。

  3. 缓存数据+过期时间也足够解决大部分业务对于缓存的要求。

  4. 通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略)

总结:

  • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。

  • 我们不应该过度设计,增加系统的复杂性

  • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

八、SpringCache

(AOP防止缓存与业务聚合)

1.依赖引入

<!--        spring-cache导入-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2.配置 (此处使用redis)

配置文件中添加:

spring:
  cache:
    type: redis # 缓存的类型为redis
    redis:
      time-to-live: 6000000 # 指定过期时间 s,000
#      key-prefix: yueluo  # 防止覆盖掉业务方法上配置的注解的key
      use-key-prefix: true # 是否使用前缀
      cache-null-values: true # 防止缓存穿透 允许空值

启动类上添加注解: 

@EnableCaching

3.使用

3.1常用的注解:

①@Cacheable:用于查询

@Cacheable:能够根据方法的请求参数对其结果进行缓存,多用于查询

@Cacheable注解的作用:

Spring在调用该方法之前,首先在缓存中查找方法的返回值,默认的key是根据参数值生成,如果存在,直接返回缓存中的值,否则执行该方法,并将返回值保存到缓存中

@Cacheable运行流程:

  1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;

           (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。

  2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数值;

           key是按照某种策略生成的;默认是使用keyGenerator生成的,               

     Spring默认加载的是SimpleCacheManage,SimpleKeyGenerator生成key的默认策略是:

                       如果没有参数;key=new SimpleKey()

                       如果有一个参数:key=参数的值

                       如果有多个参数:key=new SimpleKey(params)

  3.没有查到缓存就调用目标方法;

  4.将目标方法返回的结果,放进缓存中

@Cacheable属性说明:

  1.acheNames/value:该属性值必须提供,指定缓存组件的名字,将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;

      如:cacheNames = "product"或者cacheNames = {"product1","product2"}

  2.key:缓存数据使用的key,不指定key则默认是使用方法参数的值该属性值支持SpEL表达式

       3.cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器

       4.condition:指定符合条件的情况下才缓存

  5.unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断

                  unless = "#result == null"

                   unless = "#a0==2":如果第一个参数的值是2,结果不缓存;

  6.sync:是否使用异步模式

@Cacheable(cacheNames = "product")// 默认key为参数,多个参数SimpleKey [arg1,arg2]
@Cacheable(cacheNames = "product",key = "#root.methodName+'['+#id+']'")
@Cacheable(cacheNames = "product",keyGenerator = "myKeyGenerator")
//带条件的缓存满足condition=true缓存,满足unless=true则不缓存 
@Cacheable(cacheNames = "product",key = "#root.methodName+'['+#id+']'",condition="#a0>10",unless = "#a0==11") 

②@CachePut:用于更新

@CachePut: 执行方法,并缓存结果

@CachePut注解的作用简单的说一句话:既调用方法,又缓存数据。

@cachePut和@Cacheable两个注解都可以用于填充缓存,但使用上略有点差异,@Cacheable注解的执行流程是先在按key在缓存中查找,存在则返回,不存在则执行目标方法,并缓存目标方法的结果。而@CachePut并不会检查缓存,总是先执行目标方法,并将目标方法的结果保存到缓存中。实际中比如执行到更新操作时,则希望将最新的数据更新到缓存,如果该方法返回异常,将不再执行保存缓存的逻辑。

@CachePut属性说明

@CachePut注解属性与@CachePut类似,并没有增加其他属性

@CachePut(value="product",key = "#result.productId",condition = "#result!=null")

 ③@CacheEvict:清空缓存

@CacheEvict:清空缓存

 该注解的作用根据指定的key或者是allEntries属性值移除缓存中特性的键值对。

@CacheEvict属性说明

与@Cacheable相比@CacheEvict注解提供了另外两个属性:

  1. allEntries:表示是否清空所有缓存内容,默认false,如果该值为true则清空指定cacheNames缓存块下所有内容,如果指定了allEntries为true,那么再zhidingkey值将没有意义

  2. beforeInvocation:是否在执行方法前请空缓存,默认值为false,如果该值为true则在调用目标方法前执行清空缓存,为false的情况下,如果目标方法抛出异常,则不再执行清空缓存逻辑

@CacheEvict(value="product",key="#id")
//清楚所有缓存
@CacheEvict(value="product",allEntries = true)
//清楚所有缓存
@CacheEvict(value="product",allEntries = true,beforeInvocation = true)

④@Caching:组合注解

@Caching:能够同时应用多个缓存注解功能

该注解是一个分组注解,作用是可以同时应用多个其他注解,该注解提供了3个属性cacheable,put,evict分别用于组合@Cacheable、@CachePut、@CacheEvict三个注解

 注意:

当@Cacheing同时含有CachePut注解和Cacheable注解时,仍然会先执行目标方法。(并不是按@Cacheable的执行过程,先检查缓存,存在则返回)

 @Caching(
         cacheable = {@Cacheable(value="product",key="#productName")},
         put = {
                 @CachePut(value="product",key="#result.productId"),
                 @CachePut(value="product",key="#result.productName")
        }
 )

 ⑤@CacheConfig:公共配置

@CacheConfig: 用于抽取缓存的公共配置(类级别) 

注意:

在类上使用该注解,指定cacheNames属性值,则类中方法上的注解将默认继承了该属性值,如果方法上注解使用和了@CacheConfig向同的属性,则以方法上的为准。

@CacheConfig(cacheNames = "product")
 1 @Service
 2 @CacheConfig(cacheNames = "product")
 3 public class ProductService {
 4     @Autowired
 5     private ProductMapper productMapper;
 6 
 7     @Cacheable(cacheNames = "product1",key = "#root.methodName+'['+#id+']'")
 8     public Product getProductById(Long id){
 9        Product product =productMapper.getProductById(id);
10        System.out.println(product);
11        return product;
12     }
13 }

4.默认处理结果

1.存储在redis中的缓存数据Key是默认生成的  缓存名称:simpleKey[]

2.默认在缓存中的数据存储时间为-1 (永久)

3.缓存中存储的数据值默认是jdk的序列化机制

5.自定义key和TTL 

key:

        可以通过字符串注意单引号“ ’ keyName‘ ”

        通过SPEL表达式 使用   #root.method.name

TTL(存活时间)通过配置的方式进行指定   spring.cache.redis.time-to-live

spring:
  cache:
    type: redis # 缓存的类型为redis
    redis:
      time-to-live: 6000 # 指定过期时间 秒,000

6.JSON格式存储


import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * spring-cache配置类
 * @author xjx
 * @email 15340655443@163.com
 * @date 2024/1/27 14:05
 */
@Configuration
public class MyCacheConfig {

    /**
     * 自定义序列化存储  JSON格式存储
     * @return
     */
    @Bean
    public RedisCacheConfiguration getRedisConfiguration(CacheProperties cacheProperties){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //指定自定义序列化方式
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        //使得配置属性文件中的存活时间、前缀等生效 CacheProperties
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive()!=null){
            config=config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix()!=null){
            config=config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()){
            config=config.disableCachingNullValues();
        }
        return config;
    }
}

7.不足与解决 

SpringCache的不足:

1).读模式

  • 缓存穿透:查询一个null的数据。可以解决 cache-null-values=true

  • 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:分布式锁 sync=true 本地锁

  • 缓存雪崩:大量的key同一个时间点失效。解决方案:添加过期时间 time-to-live=60000 指定过期时间

2).写模式

  • 读写锁

  • 引入canal,监控binlog日志文件来同步更新数据

  • 读多写多,直接去数据库中读取数据即可

总结:

  • 常规数据(读多写少):而且对及时性和数据的一致性要求不高的情况,我们完全可以使用SpringCache

  • 特殊情况:特殊情况特殊处理。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值