首页 > 其他分享 >商户查询缓存

商户查询缓存

时间:2023-06-21 16:32:01浏览次数:30  
标签:shop 缓存 return 商户 stringRedisTemplate 查询 key id


什么是缓存

缓存就是数据交换的缓冲区,是存数据的临时地方,一般读写性能较高

商户查询缓存_json

添加Redis缓存

商户查询缓存_json_02

ShopServiceImpl

@Override
    public Result queryById(Long id) {
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        Gson gson = new Gson();
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = gson.fromJson(shopJson, Shop.class);
            return Result.ok(shop);
        }
        Shop shop = this.getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,gson.toJson(shop));
        return Result.ok(shop);
    }
  • 作业

商户查询缓存_redis_03

ShopTypeServiceImpl

@Override
    public Result queryTypeListByRedis() {
        List<String> list = stringRedisTemplate.opsForList().range(CACHE_SHOP_LIST_KEY, 0, stringRedisTemplate.opsForList().size(CACHE_SHOP_LIST_KEY));
        Gson gson = new Gson();
        if (list != null && !list.isEmpty()){
            List<ShopType> shopTypeList = list.stream().map((json) ->{
                return gson.fromJson(json,ShopType.class);
            }).collect(Collectors.toList());
            return Result.ok(shopTypeList);
        }
        List<ShopType> shopTypeList = this.query().orderByDesc("sort").list();
        List<String> shopJsonList = shopTypeList.stream().map(gson::toJson).collect(Collectors.toList());
        stringRedisTemplate.opsForList().leftPushAll(CACHE_SHOP_LIST_KEY,shopJsonList);
        return Result.ok(shopTypeList);
    }

缓存更新策略

商户查询缓存_json_04

商户查询缓存_redis_05

缓存更新策略的最佳实践方案:

1.低一致性需求:使用Redis自带的内存淘汰机制

2.高一致性需求:主动更新,并以超时剔除兜底

  • 读操作:
  • 缓存命中则直接返回
  • 缓存未命中查数据库,并写入缓存,设定超时时间
  • 写操作
  • 先写数据库,然后再删除缓存
  • 确保数据库与缓存操作的原子性

给查询店铺的缓存添加超时剔除和主动更新的策略

  • 超时剔除

ShopServiceImpl

@Override
    public Result queryById(Long id) {
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        Gson gson = new Gson();
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = gson.fromJson(shopJson, Shop.class);
            return Result.ok(shop);
        }
        Shop shop = this.getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
		// 给缓存的数据添加过期时间就完成了超时剔除操作
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,gson.toJson(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }
  • 主动更新
@Override
    // 同步操作需要加上事务注解,要么全部成功,要么全部失败
    @Transactional
    public Result update(Shop shop) {
        // 先判断id的合法性
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空!");
        }
		// 先更新数据库
        updateById(shop);
    	// 再删除redis的缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+shop.getId());
        return Result.ok();
    }

缓存穿透

  • 是什么?

指客户端请求的数据即不在redis,也不在数据库,这样缓存永远不会生效。这些请求都会被打到数据库。

  • 怎么解决?
  • 缓存空对象
  • 优点:简单粗暴
  • 缺点:额外的内存消耗,可能造成短期不一致
  • 布隆过滤

商户查询缓存_缓存_06

  • 优点:内存占用少,没有多余的key
  • 缺点:存在误判,实现复杂

解决商铺查询缓存穿透问题:

public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在,根据id查询数据库
        R r = dbFallback.apply(id);
        // 5.不存在,返回错误
        if (r == null) {
            // 将空值写入redis
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
        return r;
    }

缓存雪崩

在同一时刻缓存大量失效或者redis宕机,导致大量请求到达数据库,带来压力

  • 解决方案
  • 给不同的key设置不同的ttl
  • 利用redis集群提高服务的高可用性
  • 就缓存业务实现降级限流策略
  • 给业务添加多级缓存

缓存击穿

也叫热点key问题,被一个高并发并且缓存重建业务较为复杂的key失效了,无数的请求会在瞬间给数据库带来巨大的冲击。

  • 解决方案:
  • 互斥锁
  • 逻辑过期

商户查询缓存_redis_07

商户查询缓存_json_08

基于互斥锁解决缓存击穿问题:

使用redis的setnx操作

// 通过逻辑过期解决缓存击穿
    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isBlank(json)) {
            // 3.存在,直接返回
            return null;
        }
        // 4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            // 5.1.未过期,直接返回店铺信息
            return r;
        }
        // 5.2.已过期,需要缓存重建
        // 6.缓存重建
        // 6.1.获取互斥锁
        String lockKey = LOCK_SHOP_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.返回过期的商铺信息
        return r;
    }

// 根据互斥锁解决缓存击穿
    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(shopJson, type);
        }
        // 判断命中的是否是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        R r = null;
        try {
            boolean isLock = tryLock(lockKey);
            // 4.2.判断是否获取成功
            if (!isLock) {
                // 4.3.获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
            }
            // 4.4.获取锁成功,根据id查询数据库
            r = dbFallback.apply(id);
            // 5.不存在,返回错误
            if (r == null) {
                // 将空值写入redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        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);
    }

标签:shop,缓存,return,商户,stringRedisTemplate,查询,key,id
From: https://blog.51cto.com/u_16123065/6529676

相关文章

  • 金蝶云星空查询包含未过账的科目余额表
    selecta.FACCOUNTID,FBEGINBALANCE,FBEGINBALANCEFOR,FDC,b.FNUMBER,FYTDDEBIT,FYTDCREDIT,isnull(x.FCREDIT,0)FCREDIT,isnull(x.FDEBIT,0)FDEBIT, casewhenFDC='-1'then-FBEGINBALANCE-isnull(x.FDEBIT,0)+isnull(x.FCREDIT,0)elseFBEGINBALANCE+isnu......
  • MySQL查询缓存的优缺点
    1.MySQL查询缓存的优缺点目录1.MySQL查询缓存的优缺点1.1.前言1.2.工作原理1.3.查询缓存对什么样的查询语句,无法缓存其记录集,大致有以下几类:1.4.查询缓存的优缺点:1.5.查询缓存的配置1.6.维护1.6.1.查询缓存区的碎片整理1.6.2.清空查询缓存的数据1.7.性能监控1.8.适合......
  • PG-DBA培训05:PostgreSQL数据查询与SQL语句增删改 原创
    一、风哥PG-DBA培训05:PostgreSQL数据查询与SQL语句增删改本课程由风哥发布的基于PostgreSQL数据库的系列课程,本课程属于PostgreSQL数据库SQL开发与应用实战阶段之PostgreSQL数据查询与SQL语句增删改,学完本课程可以掌握PostgreSQLSQL增删改,插入数据(insert),修改数据(update),删除......
  • PG-DBA培训05:PostgreSQL数据查询与SQL语句增删改
    一、风哥PG-DBA培训05:PostgreSQL数据查询与SQL语句增删改本课程由风哥发布的基于PostgreSQL数据库的系列课程,本课程属于PostgreSQL数据库SQL开发与应用实战阶段之PostgreSQL数据查询与SQL语句增删改,学完本课程可以掌握PostgreSQLSQL增删改,插入数据(insert),修改数据(update),删除数......
  • MySQL 关于缓存的 “杂七杂八”
    开头还是介绍一下群,如果感兴趣polardb,mongodb,mysql,postgresql,redis等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。你是否可以想象如果MYSQL没有了innodb_buffer_pool是什么样子的情况,本期需要说说MYSQL的缓存,已经如何使用他更加有效用或者说性......
  • MYSQL 8 中间字段有NULL 值,还是无法走索引,所以我高估了MYSQL 的查询智商
    开头还是介绍一下群,如果感兴趣polardb,mongodb,mysql,postgresql ,redis等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。基于半瓶子咣当的状态,PG,MYSQL,POLARDB,MONGODB,REDIS还是都能舞刀弄枪几下的,但是这个弄这弄着,这记忆力就会不好,因为我一......
  • 使用graylog rest api查询日志
    由于项目需要,调研使用graylog收集项目操作日志,并使用api查询日志python代码if__name__=='__main__':importrequestssearch_content={"query_string":{"type":"elasticsearch","query_string&q......
  • JPA在事务结束时自动更新查询数据
    目录现象产生的原因解决方法现象最近解决了一个困惑几天的bug,数据库里的某一些记录莫名其妙的被刷新了,排查过代码跟应用日志,可以确定不是代码执行的更新。直到今天看到了一条日志,在事务提交时报错“Column'user_name'cannotbenull”,在出错的事务中,针对这一个表只会执行query......
  • SpringBoot整合Cache缓存深入理解
    我们在上一篇的基础上继续学习。SpringBoot整合cache缓存入门一、@Caching注解@Caching注解用于在方法或者类上,同时指定多个Cache相关的注解。属性名描述cacheable用于指定@Cacheable注解put用于指定@CachePut注解evict用于指定@CacheEvict注解示例代码如下:importcom.example.mys......
  • 树状数组详解!(C++_单点/区间查询_单点/区间修改)
    先把这张著名的树状数组结构图摆在最前面,接下来我们就以这张图讲起!       首先图中的A数组就是所谓的原数组,也就是普通的数组形态,C则是我们今天要说的树状数组(可以看出一个树的形状,但其实和树没多大关系)从图中可以明显看到以下几个式子:有点像前缀和不是?但这样还看不出什......