微服务系统面经之三: 以秒杀系统为例-多级缓存及其更新机制

22 多级缓存(百度一面)

22.1 你用了什么缓存

答:本地内存+redis

22.2 内存标记是如何被使用被更新的

22.3 什么时候修改这个第一层缓存(内存标记),把它改为false

答:当redis预减库存为0的时候,这个就需要改为false

22.4 这个内存标记可以被称为第二级缓存吗?(我答错了)

22.4.1 内存标记和redis预减库存的操作,哪一个属于一级缓存哪一个属于二级

在秒杀系统设计中,"内存标记"和"Redis预减库存"都是优化手段,旨在减少对数据库的访问以提高系统的性能。但在缓存的层级分类中,这两者的位置可以如下解释:

  1. 内存标记:它直接在应用程序的内存中进行,速度非常快。因为这种操作不需要与任何外部系统通信,所以它通常被视为一级缓存

  2. Redis预减库存:Redis是一个外部的内存存储系统,虽然它非常快,但与直接在应用程序的内存中进行操作相比,访问Redis会涉及到网络通信的延迟。因此,从缓存的层级分类角度来看,Redis通常被视为二级缓存

所以,简而言之,内存标记是一级缓存,而Redis预减库存是二级缓存。

22.4.2 一级、二级缓存的区别标志是什么呢

一级缓存和二级缓存的区别通常基于以下特性或标志来定义:

  1. 存储位置

    • 一级缓存:通常存储在事务或会话的上下文中,这意味着它是局部的,仅与单个会话或线程相关。例如,在某些ORM框架中,一级缓存是与单个会话关联的。
    • 二级缓存:通常是全局的,它存储在进程或应用之外的位置,并可供多个会话或线程共享。例如,分布式缓存系统如Redis或Memcached。
  2. 生命周期

    • 一级缓存:生命周期通常限于单个会话或事务。会话结束时,缓存中的数据也可能被清除。
    • 二级缓存:生命周期超出单个会话,通常基于时间或容量失效策略来管理。
  3. 数据共享性

    • 一级缓存:因为它是局部的,所以不会被多个会话或线程共享。
    • 二级缓存:可以被多个会话、线程或甚至是多个应用实例共享。
  4. 访问速度

    • 一级缓存:访问速度非常快,因为它通常在应用程序的内存中。
    • 二级缓存:虽然也是非常快的,但由于可能涉及到进程间或网络通信,所以相对较慢。
  5. 失效策略

    • 一级缓存:通常在会话结束或事务完成后自动失效。
    • 二级缓存:通常基于具体的失效策略,如LRU、TTL等。
  6. 复杂性

    • 一级缓存:通常更简单,自动管理。
    • 二级缓存:可能需要更多的配置和管理,尤其是当涉及到分布式缓存系统时。

根据上述特点,开发者可以根据实际需要选择使用一级缓存、二级缓存或两者结合,以优化应用程序的性能。

22.4.3 我觉得可以根据距离cpu的逻辑远近来判断,比如本地内存标记不需要网络通信,所以是一级,redis需要网络通信,所以是二级 另外一种角度看,数据是首先同步到redis,然后再同步到内存标记的

确实,根据数据存取的速度和距离CPU的逻辑远近来判断缓存层级是非常直观的方式。在计算机架构中,这种分类方式常被用来描述CPU的缓存层级(L1、L2、L3等),其中距离CPU核心越近、速度越快的缓存被视为更低的层级。

对于应用层面的缓存:

  • 一级缓存:通常指直接存储在应用进程中的缓存,如Java对象在JVM堆上的存储。这种缓存的访问速度极快,不涉及任何跨进程或网络通信。

  • 二级缓存:涉及到跨进程通信或网络通信的缓存,如Redis、Memcached等分布式缓存系统。虽然它们仍然是内存存储,但由于涉及通信,访问速度相对较慢。

你的观点完全成立。如果数据首先同步到Redis然后再同步到内存标记,那么可以将Redis视为一种“前置缓存”或“缓冲层”,而应用内存中的标记则可以视为更接近实际数据处理和业务逻辑的一级缓存。

这种层级化的缓存策略使得应用能够更加灵活地处理高并发情况,同时确保数据的一致性和系统的响应速度。

22.5 内存标记和redis缓存的区别是什么呢?

答:使用内存标记就不需要和远程主机进行网络通信

23 一致性与缓存和mysql的更新机制(百度一面)

23.1 如果有一部分流量进来了之后,就卡在这个边界点,你怎么去判断?如果秒杀第10件商品的时候,有两个请求同时打到后端,有没有可能这两个请求都进到你的消息队列中去?

答:有可能的,因为redission可能会发生数据丢失,redis集群本身只能保证最终一致性

23.2 如果有一主多从的话,是不是可能存在多个流量进来呢?

答:对,但是这种概率很低,因为一个redis集群连续发生多次主从切换的可能性很小

23.3 前端这个时候,你会提示它在等待吗?

答:对,会显示排队中,客户端会不断轮询后端暴露出来的一个查询订单的接口,查看是否存在,这个接口会增加这个订单到redis缓存中

23.3.1 为什么这个时候数据库和redis的同步机制是先更新mysql,后更新缓存?难道不存在不一致问题吗?

答:这个时候,客户端只涉及到查询订单操作,而不涉及到修改,而且只有这一个服务端线程会修改redis缓存,所以可以采用先更新mysql,再删除redis

23.4 如果用户下单后没多久又取消订单了怎么办?如果没有支付怎么办(误区:(1)把这两个弄成了一个事情,库存回退没有说,两级缓存更新没有说)

答:

当用户下单后没多久又取消订单,或者下单后未支付,系统需要有一套相应的处理机制。这些机制通常涉及订单的生命周期管理、库存管理、支付超时等。以下是一些常见的处理方式:

  1. 订单取消

    • 库存回滚:当用户取消订单,前端会向指定的接口立即发送请求,系统应立即将对应的商品库存数量回滚。
    • 订单状态管理:订单应有多个状态,如“已创建”、“已支付”、“已取消”等。当用户取消订单时,系统应将订单状态更改为“已取消”。
    • 库存回退和缓存更新:会将这个库存回退到mysql的商品表中,然后会删除redis中相应的订单,同时删除redis的关于该库存信息的缓存,然后再将业务服务器中的该商品是否售罄的缓存更新为false
  2. 未支付的订单

    • 支付超时机制:为订单设定一个支付超时时间(例如15或30分钟)。如果用户在这个时间内没有完成支付,系统自动将订单状态更改为“已取消”并回滚库存。
    • 超时提醒:当订单即将到达超时时间时,可以通过短信、邮箱或应用内通知提醒用户支付。
    • 定期检查:后端可以使用定时任务或其他机制定期检查未支付的订单,并根据其创建时间处理超时订单。
    • 库存回退和缓存更新:(基本逻辑和订单取消的相应逻辑相同)

总之,对于用户的取消订单或未支付行为,系统应该有一套完整的策略和流程,确保商家的利益和其他用户的正常购买体验不受影响。

23.5 你的一级缓存如何更新呢?这是不是涉及到了多个本地内存机器的内存标记的更新呢?

答:

一级缓存通常指的是业务服务器本地的内存缓存,例如Java中的ConcurrentHashMap或其他本地缓存工具。更新一级缓存的方法和策略如下:

  • 数据变动触发:当有关键数据(如库存)变动时,我们需要同时更新一级缓存中的相关数据。
  • 多机器同步问题:在分布式环境中,可能有多个业务服务器实例,因此当一级缓存的数据在一个实例中被修改时,我们需要确保其他实例的缓存数据也被相应地更新。这通常通过消息中间件如Kafka、RabbitMQ等来实现,当一个实例更新了缓存,它会发送一个消息到消息中间件,其他实例监听到这个消息后会更新自己的一级缓存。
  • 定期刷新:一些数据可能不是经常变动,但为了确保数据的时效性和准确性,可以设置一个定期刷新的策略,例如每隔一段时间从数据库或二级缓存(如Redis)中重新加载数据。

23.6 你的缓存更新会不会造成并发的冲突呢?比如客户端线程在读,服务端线程在写

答:

缓存并发的问题确实是一个需要关注的点。根据不同的缓存类型和策略,处理方式也会有所不同。

  • Redis缓存:Redis是单线程模型,它确保了每次只有一个命令在执行,因此不会有并发冲突。但在分布式应用中,多个客户端同时发送命令到Redis可能会造成读旧数据的情况。为了避免这种情况,可以使用Redis的事务功能或乐观锁来确保数据的一致性。

  • 本地内存缓存:在多线程环境中,本地缓存可能会出现并发的问题。例如,当一个线程正在更新缓存数据时,另一个线程可能正在读取这些数据。为了解决这个问题,可以使用并发工具,如Java中的ConcurrentHashMap或ReadWriteLock。ConcurrentHashMap允许多个读线程并发进行,但写操作会被锁定,确保每次只有一个线程可以写入。ReadWriteLock也提供了类似的功能,但提供了更细粒度的控制。

综上所述,确保缓存的并发安全是关键,需要根据实际情况选择合适的工具和策略来实现。

你可能感兴趣的:(微服务,缓存,spring,秒杀)