Redis实战——商户查询(一)

这篇具有很好参考价值的文章主要介绍了Redis实战——商户查询(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

商户查询

  • 缓存(Cache):就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,缓存数据在内存中,内存的读写性能完全高于磁盘,使用缓存可以大大降低用户访问并发量带来的服务器读写压力。当数据量较大时,如果没有缓存来作为“避震器(防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪)”,系统很难支撑。

数据库直接查询

  • 在没有缓存时,查询商户信息,我们直接操作从数据库中去进行查询,但是从数据库中查询肯定是个耗时操作。如下通过id在数据库中查询商铺。

    • controller层
    @RestController
    @RequestMapping("/shop")
    public class ShopController {
    
        @Resource
        public IShopService shopService;
    
        /**
         * 根据id查询商铺信息
         * @param id 商铺id
         * @return 商铺详情数据
         */
        @GetMapping("/{id}")
        public Result queryShopById(@PathVariable("id") Long id) {
            Shop shop = shopService.getShopById(id);
            if (ObjectUtil.isNull(shop)){
                return Result.fail("商铺不存在");
            }
            return Result.ok(shop);
        }
    }    
    
    • service层
    public interface IShopService extends IService<Shop> {
    
        Shop getShopById(Long id);
    }
    
    @Service
    public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    
    
        @Override
        public Shop getShopById(Long id) {
            return this.getById(id);
        }
    }
    

缓存查询

  • 缓存模型和思路

    • 客户端查询数据
      • 先在缓存中查询
        • 缓存中存在,从缓存中返回
        • 缓存中不存在。查询数据库,写入缓存并返回

Redis实战——商户查询(一),redis,java

  • 根据Id查询商铺信息

Redis实战——商户查询(一),redis,java

  • service层

    @Service
    public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    
        @Resource
        StringRedisTemplate stringRedisTemplate;
    
        @Override
        public Shop getShopById(Long id) {
            //组装redis中的key
            String cacheShopKey = CACHE_SHOP_KEY + id;
            //根据ID在redis中查询商铺信息
            String shopString = stringRedisTemplate.opsForValue().get(cacheShopKey);
            //redis中查询到商铺信息
            if (StrUtil.isNotBlank(shopString)){
                Shop shop = BeanUtil.toBean(shopString, Shop.class);
                return shop;
            }
            //根据商铺id查询商铺信息
            Shop shop = this.getById(id);
            //数据库中没查询到该商铺信息
            if (ObjectUtil.isNull(shop)){
                return null;
            }
            //数据库中查询到了该商铺信息
            stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop));
            //返回给商铺信息
            return shop;
        }
    }
    
  • 增加相关常量

    /**
     * redis中缓存商铺信息
    */
    public static final String CACHE_SHOP_KEY = "cache:shop:";
    

缓存更新

  • 缓存更新是reids为了节约内存而设计的,主要是因为内存数据宝贵,当向redis插入太多数据,可能会导致缓存中的数据过多,所有redis会对部门数据进行更新(也许叫淘汰更合适)。

    • 内存淘汰:redis自动更新,当redis内存叨叨我们设定的max-memery时,会自动出发淘汰机制,淘汰掉一些不重要的数据化(二阳自己设置策略方式)
    • 超时剔除:为redis存储的数据设置过期时间(TTL),redis会将超时的数据进行删除
    • 主动更新:活动调用方法删除缓存,通常用于解决缓存和数据库不一致问题
策略 内存淘汰 超时剔除 主动更新
说明 redis利用redis的内存淘汰机制自动维护,当内存不足时,自动淘汰部分数据,下次查询时更新缓存 为redis数据添加TTL时间,到期后redis自动删除,下次查询时更新缓存 开发人员编写业务逻辑,在修改数据库的同时,更新缓存。
一致性 一般
维护成本
  • 使用场景

    • 低一致性需求:使用内存淘汰机制。例如商铺类型查询缓存

      • 查询商铺类型信息

        Redis实战——商户查询(一),redis,java

        public interface IShopTypeService extends IService<ShopType> {
        
            List<ShopType> queryTypeList();
        }
        
        @Service
        public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {
        
            @Autowired
            private StringRedisTemplate stringRedisTemplate;
            @Override
            public List<ShopType> queryTypeList() {
                //从redis中获取缓存数据
                Long size = stringRedisTemplate.opsForList().size(CACHE_SHOP_TYPE_KEY);
                //从redis中能够获取商铺类型数据
                if (size > 0){
                    List<String> shopTypeListStr = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_KEY, 0, size);
                    //将字符串类型转换为ShopType对象
                    List<ShopType> shopTypeList = shopTypeListStr.stream().map(shopTypeStr -> JSONUtil.toBean(shopTypeStr, ShopType.class)).collect(Collectors.toList());
                    return shopTypeList;
                }
                // 从redis中没有查询到商铺类型信息,那么去数据库中查询
                List<ShopType> shopTypeList = this.list(new LambdaQueryWrapper<ShopType>().orderByAsc(ShopType::getSort));
                // 数据库中有商铺类型信息
                if (ObjectUtil.isNotNull(shopTypeList) && shopTypeList.size() > 0){
                    //缓存到redis中
                    List<String> shopTypeJsonList = shopTypeList.stream().map(shopType -> JSONUtil.toJsonStr(shopType)).collect(Collectors.toList());
                    stringRedisTemplate.opsForList().rightPushAll(CACHE_SHOP_TYPE_KEY, shopTypeJsonList);
                    return shopTypeList;
                }
                return null;
            }
        }
        
        
        @RestController
        @RequestMapping("/shopType")
        public class ShopTypeController {
            @Resource
            private IShopTypeService typeService;
        
            @GetMapping("list")
            public Result queryTypeList() {
                List<ShopType> typeList = typeService.queryTypeList();
                return ObjectUtil.isNull(typeList) ? Result.fail("没有查询到商铺类型"): Result.ok(typeList);
            }
        }
        
    • 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询缓存

数据库缓存不一致解决方案

由于Redis缓存数据来源于数据库,当数据库中的数据发生变化时,如果当数据库中数据发生变化,Redis缓存却没有同步,此时就会出现数据一致性问题,可能会导致用户使用缓存中的过时数据,就会产生类型多线程数据安全问题。

  • 解决方案:
    • Cache Aside Pattern 人工编码方式:由缓存调用者在更新数据库的同时更新缓存,也称为双写方案
    • Read/Write Through Pattern:缓存和数据库整合为一个服务,数据库和缓存的问题交由系统本身处理
    • Write Behind Caching Pattern:调用者只操作缓存,其他线程去异步处理数据库,实现最终一致性

经综合考虑,一般采用方案一,采用方案一时,需要考虑的问题

  • 删除缓存还是更新缓存

    • 更新缓存:每次更新数据都更新缓存,无效写操作较多
    • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存

    应该采用删除缓存,如果采用更新缓存,那么每次操作数据库之后,都要进行缓存更新,如果在反复操作数据库的过程中,没有人进行过查询操作,那么可以认为这些更新缓存的操作,只要最后一次是有效的,其他的都是无用功,没什么意义,所有我们可以把缓存进行删除,等待再次查询时,在进行缓存更新

  • 需要保证缓存与数据库的操作的同时成功和失败

    • 单体系统:将缓存与数据库操作放在一个事务
    • 分布式系统:利用TCC等分布式事务方案
  • 先操作缓存还是先操作数据库

    • 先删除缓存,再操作数据库

      Redis实战——商户查询(一),redis,java

    • 先操作数据库,再删除缓存

      Redis实战——商户查询(一),redis,java

    应该先操作数据库,在删除缓存,因为我们先删除缓存,在操作数据库,假设两个线程并发访问时,线程1先进入,它先删除了缓存,还没操作数据库呢,线程2进来进行查询,它查询缓存数据并不存在,于是它从数据库中获取数据,并写入缓存,当线程2写入缓存后,线程1才完成数据库的更新操作,那么这个时候,数据库的数据是新数据,缓存的数据还是旧数据,会造成数据不一致问题。

总结:

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

    • ① 低一致性需求:使用Redis自带的内存淘汰机制;

    • ② 高一致性需求:主动更新,并以超时剔除作为兜底方案

      • 读操作
        • 缓存命中则直接返回
        • 缓存未命中则查询数据库,并写入缓存,设定超时时间
      • 写操作
        • 先写数据库,然后在删除缓存
        • 需要确保数据库与缓存操作的原子性
实现商铺的缓存与数据库双写一致
  • 分析:

    • 根据上面总结的读操作,需要修改根据ID查询商铺信息,

      • 缓存命中则直接返回
      • 缓存未命中,则进行数据库查询,并将数据库查询结果写入缓存,并设置超时时间
      /**
       * redis中缓存商铺信息
       */
      public static final String CACHE_SHOP_KEY = "cache:shop:";
      /**
       * redis中缓存商铺信息的有效时间
       */
      public static final Long CACHE_SHOP_TTL = 30L;
      
      @Override
      public Shop getShopById(Long id) {
          //组装redis中的key
          String cacheShopKey = CACHE_SHOP_KEY + id;
          //根据ID在redis中查询商铺信息
          String shopString = stringRedisTemplate.opsForValue().get(cacheShopKey);
          //redis中查询到商铺信息
          if (StrUtil.isNotBlank(shopString)){
              Shop shop = BeanUtil.toBean(shopString, Shop.class);
              return shop;
          }
          //根据商铺id查询商铺信息
          Shop shop = this.getById(id);
          //数据库中没查询到该商铺信息
          if (ObjectUtil.isNull(shop)){
              return null;
          }
          //数据库中查询到了该商铺信息,写入缓存,并设置有效时间为30分钟
          stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
          //返回给商铺信息
          return shop;
      }
      
    • 根据上面总结的写操作,需要编写根据ID更新店铺信息

      • 根据ID更新店铺信息时,先修改数据库,再删除缓存,并确保操作数据库和操作缓存的原子性
      /**
       * 更新商铺信息
       * @param shop 商铺数据
       * @return 无
       */
      @PutMapping
      public Result updateShop(@RequestBody Shop shop) {
          // 写入数据库
          if (ObjectUtil.isNull(shop.getId())){
              return Result.fail("店铺Id不能为空");
          }
          shopService.updateShopById(shop);
          return Result.ok();
      }
      
      void updateShopById(Shop shop);
      
      /**
       * 根据id更新商铺信息
       * @param shop
       */
      @Transactional //通过事务,来保证数据库更新和缓存删除的一致性
      @Override
      public void updateShopById(Shop shop) {
          this.updateById(shop);
          stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());
      }
      

本文由mdnice多平台发布文章来源地址https://www.toymoban.com/news/detail-527799.html

到了这里,关于Redis实战——商户查询(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java实战:Redis实现查找附近的人

    本文将介绍如何使用Redis实现查找附近的人的功能。我们将探讨如何使用Redis的地理空间(Geospatial)索引功能,并展示一个简单的Java代码示例,该示例使用Jedis库和Redis的GEOADD命令来添加位置信息,以及使用GEORADIUS命令来查找附近的人。通过本文,可以了解到如何在Java应用程

    2024年02月21日
    浏览(40)
  • 如何使用Redis实现附近商家查询

    🔥🔥宏夏Coding网站,致力于为编程学习者、互联网求职者提供最需要的内容!网站内容包括求职秘籍,葵花宝典(学习笔记),资源推荐等内容。在线阅读:https://hongxiac.com🔥🔥 在日常生活中,我们经常能看见查询附近商家的功能。 常见的场景有,比如你在点外卖的时候

    2024年02月12日
    浏览(65)
  • Redis实战解读-初识Redis&Redis基本数据类型

    1.什么是Redis ​ Redis是一个速度非常快的非关系型数据库(non-relational database),它可以存储键(key)与五种不同类型的值的映射(mapping),可以将存储在内存的键值对数据持久化到磁盘,可以使用复制特性来扩展读性能,也可以采用客户端分片来扩展写性能。--《Redis实战》 2.Redis与

    2024年02月08日
    浏览(41)
  • redis实战-redis实现异步秒杀优化

    当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一人一单 5、扣减库存 6、创建订单  在这六步操作中,又有很多操作是要去操作数据库的,而且

    2024年02月07日
    浏览(43)
  • 项目记录:利用Redis实现缓存以提升查询效率

    当我们查询所有数据时,如果缓存中没有,则去数据库查询,如果有,直接查缓存的数据就行。注意定期更新缓存数据。 BoundHashOperations是绑定键值的方法,意味着之后的操作都是对此键进行操作。 ObjectMapper类提供了一系列json序列化和反序列化的操作。 缓存更新操作是通过

    2024年02月03日
    浏览(47)
  • Redis学习(三)分布式缓存、多级缓存、Redis实战经验、Redis底层原理

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(42)
  • linux 系统查询redis中指定key值(命令)

    登上服务器之后直接输入 redis-cli 进入到127.0.0.1:6379下 在127.0.0.1:6379下,输入如下指令 auth 你的redis密码 查询所有key 指令: keys * 查询指定key值 指令: get 你的key名

    2024年02月08日
    浏览(42)
  • PHP使用Redis实战实录5:Redis实现消息队列

    PHP使用Redis实战实录系列 PHP使用Redis实战实录1:宝塔环境搭建、6379端口配置、Redis服务启动失败解决方案 PHP使用Redis实战实录2:Redis扩展方法和PHP连接Redis的多种方案 PHP使用Redis实战实录3:数据类型比较、大小限制和性能扩展 PHP使用Redis实战实录4:单例模式和面向过程操作

    2024年02月11日
    浏览(47)
  • Redis:原理速成+项目实战——Redis企业级项目实战终结篇(HyperLogLog实现UV统计)

    👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习 🌌上期文章:Redis:原理速成+项目实战——Redis实战14(BitMap实现用户签到功能) 📚订阅专栏:Redis:原理速成+项目实战 希望文章对你们有所帮助 这篇是实战部分的终结篇,其实Redis的核心操作,主要是

    2024年01月17日
    浏览(50)
  • 【Redis】Redis 哈希 Hash 键值对集合操作 ( 哈希 Hash 键值对集合简介 | 查询操作 | 增加操作 | 修改操作 )

    Redis 中的 Hash 数据 是一个 键值对集合 , 类似于 Java 中的 Map 集合 ; Hash 数据底层数据结构是 : 压缩列表 ZipList : Hash 中的 键值对 长度较短时 使用 压缩列表 ; 哈希表 HashTable : Hash 中的 键值对 长度较长时 使用 哈希表 ; Redis 中存储对象的方式 : 存储序列化之后的数据 : 将 对象

    2024年02月15日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包