SpringBoot整合Redis工具类

下载启动redis

可以使用windows版的redis,方便开发

Releases · tporadowski/redis · GitHub

顺便安装了一个Redis图形化界面工具,Another Redis Desktop,除了不能在里面输入命令,没什么大问题

在pom中引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--   工具类     -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.3</version>
</dependency>
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
</dependency>

修改配置文件

spring:
  redis:
    port: 6379 # 端口
    host: localhost # ip地址

封装redis工具类

通过函数式编程的方式,在不修改原来业务代码的情况下使用redis缓存数据,耦合性低

@Component
public class RedisCacheClient {
    private final StringRedisTemplate stringRedisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public RedisCacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 普通存储
     * @param key key
     * @param value value
     * @param time 数据过期时间, 如30L
     * @param unit 时间单位, 如TimeUnit.MINUTES
     */
    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 设置逻辑过期时间
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /**
     * 对于不存在的查询, 缓存空对象, 解决缓存穿透
     * @param keyPrefix key的固定前缀
     * @param id 查询mysql的参数, key后缀 区分不同key
     * @param type dbFallback方法的返回值类型.class
     * @param dbFallback 查询mysql数据库的方法
     * @param time key过期时间
     * @param unit 时间类型
     * @param <R> dbFallback方法的返回值类型
     * @param <ID> dbFallback方法的参数
     * @return R
     */
    public <R, ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            // 直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断是否为 "" 空值
        if ("".equals(json)) {
           return null;
        }
        R r = dbFallback.apply(id);
        if (r == null) {
            // 将空值写入redis
            this.set(key, "", CACHE_NULL_TTL, unit);
            return null;
        }
        // 写入redis
        this.set(key, r, time, unit);
        return r;
    }

    /**
     * 逻辑过期 解决缓存击穿
     */
    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(json)) {
            // 存在 直接返回
            return null;
        }
        // 命中, 把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 1.未过期, 直接返回信息
            return r;
        }
        // 2. 已过期, 需要缓存重建
        // 缓存重建 获取互斥锁
        String lockKey = LOCK_BLOG_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2.判断是否获取锁成功
        if (isLock) {
            // 6.3 成功 开启独立线程 实现缓存重建
             CACHE_REBUILD_EXECUTOR.submit(() -> {
                 try {
                     // 查询数据库
                     R newR = dbFallback.apply(id);
                     this.setWithLogicalExpire(key, newR, time, unit);
                 } catch (Exception e) {
                     throw new RuntimeException(e);
                 }finally {
                     // 释放锁
                     unlock(lockKey);
                 }
             });
        }
        // 6.4.返回过期的blog信息
        return r;
    }


    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }

}
@Data
public class RedisData {
    /** 过期时间 */
    private LocalDateTime expireTime;
    /** 数据  */
    private Object data;
}
public class RedisConstants {
    public static final Long CACHE_NULL_TTL = 2L;
    public static final Long CACHE_USER_TTL = 30L;
    public static final String CACHE_USER_KEY = "user:";
	public static final String LOCK_BLOG_KEY = "lock:blog:";
}

如何使用 ❓

在Service层中已经写好了getById方法,如:

public User getById(Integer userId) {
    // 根据userId查询mysql
    return new User();
}

用一个新的方法,使用封装好的缓存击穿方案,即可

public User queryById(Integer userId) {
    User user = redisCacheClient.queryWithPassThrough(CACHE_USER_KEY,
            userId, User.class, this::getById, CACHE_USER_TTL, TimeUnit.MINUTES);
    if (user == null) {
        return null;
    }
    return user;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值