5~60V 恒流驱动HI7002替代惠海 H5116 聚能芯半导体智芯电子一级代理
2026/6/26 4:12:09
@Component @Slf4j public class CacheClient { private final StringRedisTemplate stringRedisTemplate; public CacheClient(RedisTemplate redisTemplate, StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public void set(String key, Object value, long time, TimeUnit timeUnit) { stringRedisTemplate.opsForValue().set(key, value.toString(), time, timeUnit); } public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit timeUnit) { //先从reids看看有没有 String key = keyPrefix + id; String json = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(json)) { //有,直接返回 return BeanUtil.toBean(json, type); } //判断是不是空值 //要知道知道空值与空字符串不是一个,这里说的如果是后面再reddis中存的空字符串就直接报错,不可数据库上压力的可能 if (json != null) { return null; } R r = dbFallback.apply(id); if (r == null) { //还要将空值写入redis,为了防止缓存穿透 stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); //正常返回 return null; } this.set(key, r, time, timeUnit); return r; } private static final ExecutorService CACHE_THREAD_POOL = Executors.newFixedThreadPool(10); 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)); } public <R,ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,Function<ID,R> dbFallback,Long time, TimeUnit timeUnit) { //先从reids看看有没有 String key = keyPrefix + id; String shopJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isBlank(shopJson)) { //有,直接返回 return null; } //由于下面设置的是属性的过期时间,所以要麻烦的取一下 //在Java中不方便操作JSON格式语句,先转换为通过toBean方法转换为RedisData对象,后面的字节码文件就是告诉toBean方法,按照RedisData类的属性模板来转换 RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class); //随后又通过get方法得到一个object的对象,但是object没有get,set方法,所以还要继续转成JSONObject类型的 JSONObject data = (JSONObject) redisData.getData(); //随后又将JSONObject类型的对象,转换为Shop类型的对象,方便传回 R r = JSONUtil.toBean(data, type); //时间的类型是LocalDateTime,直接与当前时间比较,判断是否过期即可 LocalDateTime expirTime = redisData.getExpireTime(); //如果过期时间在当前时间之后,说明没有过期 if (expirTime.isAfter(LocalDateTime.now())) { //没有过期,直接返回 return r; } //只有过期了,才需要重建缓存 String lockKey = LOCK_SHOP_KEY + id; boolean islock = tryLock(lockKey); if (islock) { CACHE_THREAD_POOL.submit(() -> { //saveShop2Redis是当前类的方法,所以要使用this调用 try { R r1 = dbFallback.apply(id); this.set(key, r1, time, timeUnit); } catch (Exception e) { throw new RuntimeException(e); } finally { unLock(lockKey); } }); } return r; } private boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); //防止空指针,如果是空指针的话会直接返回 false而不是抛异常 return BooleanUtil.isTrue(flag); } private void unLock(String key) { stringRedisTemplate.delete(key); } }原来要写 stringRedisTemplate.opsForValue().set("user:1", userInfo.toString(), 30, TimeUnit.MINUTES);但是现在只要写cacheClient.set("user:1", userInfo, 30, TimeUnit.MINUTES);public void setWithLogicalExpire(String key, Object value, long time, TimeUnit unit) { //创建RedisData,这个类只有两个属性:Object data真正要缓存的业务对象,LocalDateTime expireTime:自定义的逻辑过期时间 RedisData redisData = new RedisData(); //将你的数据放进包装对象 redisData.setData(value); //不直接设置TTL过期时间,而是直接设置一个逻辑过期时间点的属性 redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); //随后将整个RedisData转成JSON,存入redis stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); }//<R,ID>这个是声明了一个自定义的类型,R就是后面R的类型 public <R,ID> R queryWithLogicalExpire( String keyPrefix, ID id, // ID用作入参类型 Class<R> type, // R代表要转换的实体类 Function<ID,R> dbFallback, // 函数入参ID,返回R Long time, TimeUnit timeUnit )//第一层 JSON 转RedisData对象,分出两块内容:业务数据、逻辑过期时间 RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class); //`redisData.getData()`返回 Object,但是object无法解析内部字段,所以接着转JSONObject JSONObject data = (JSONObject) redisData.getData(); //随后就是用方法toBean就是反序列化成需要的对象 R r = JSONUtil.toBean(data, type); //同时取出预先存入的逻辑过期时间`expirTime`,用于判断缓存是否失效 LocalDateTime expirTime = redisData.getExpireTime();String lockKey = LOCK_SHOP_KEY + id; boolean islock = tryLock(lockKey);//只有当前线程拿到锁才有下一步 if (islock) { //submit把括号里的任务交给新的子线程去跑,主线程不会等待这段代码执行完毕,会直接跳出if,执行最后的return,返回旧数据,下面的子线程只进行大括号里面的 CACHE_THREAD_POOL.submit(() -> { try{ //apply就是传入参数t后执行传入的逻辑 R r1 = dbFallback.apply(id); //this就是当前对象实例,将最新的数据存进去 this.set(key, r1, time, timeUnit); } catch (Exception e) { throw new RuntimeException(e); //不论try里面的代码是不是对的,一定会执行内部代码 } finally { unLock(lockKey); } }); } //最后return r,但检测到过期的这一次还是旧数据public Shop queryWithMutex(Long id) { //1. 查询缓存 String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id); if (StrUtil.isNotBlank(shopJson)) { //缓存命中,直接返回 return BeanUtil.toBean(shopJson, Shop.class); } //2. 缓存是空字符串(之前存的无效id标记,防穿透) if (shopJson != null) { return null; } //走到这里:shopJson == null,缓存彻底过期/无缓存,需要重建 String lockKey = LOCK_SHOP_KEY + id; Shop shop = null; try { //3. 尝试获取分布式锁 boolean flag = tryLock(lockKey); if (!flag) { //没抢到锁:休眠50ms,递归重新执行本方法,再次查缓存 Thread.sleep(50); return queryWithMutex(id); } //4. 抢到锁,查询数据库 shop = getById(id); if (shop == null) { //数据库无数据,存入空字符串防穿透 stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES); return null; } //5. 数据库有数据,同步写入Redis缓存 stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { //6. 无论正常/异常,强制释放锁,防止死锁 unLock(lockKey); } return shop; }互斥锁 缓存没了才上锁,抢到锁才查库写缓存;没抢到就等着重试;能拦截不存在 id(防穿透),用户会卡顿 逻辑过期 缓存一直都在,过期直接返回旧数据,后台悄悄更新;不用等用户无延迟,没法拦截无效 id