1. 何时需要考虑缓存一致性
缓存一致性主要在以下情况需要特别考虑:
-
读多写少场景:当数据读取操作远多于写入时,为了提高系统性能,通常会将数据缓存起来。但一旦数据源(如数据库)中的数据发生变化,就需要确保缓存中的数据也能得到相应的更新,以保持数据的一致性。
例子:
电商平台的商品详情页: 商品信息如价格、库存量通常读取频次远高于修改频次。当库存数量因订单生成而减少时,需要确保这一变更能快速反映到缓存中,否则用户看到的可能是过时的库存信息,导致超卖问题。此时,可以通过监听数据库的更新事件,触发缓存的更新机制来维持一致。
-
缓存击穿:大量并发请求访问刚好过期的缓存数据,如果没有合适的处理机制,可能会导致所有请求穿透到数据库,增加数据库压力。此时,通过预先加载或使用锁机制来避免击穿问题,保证缓存与数据库的一致性。
例子:
新闻网站的热点文章: 假设某篇文章突然成为热点,其缓存过期后,短时间内有大量请求涌入,若无适当处理,会导致所有请求直接查询数据库。为避免这种情况,可以采用提前刷新策略,即在缓存即将到期前,后台任务自动重新加载数据到缓存;或者使用“逻辑过期”策略,即使缓存过期,也先返回旧数据,并异步更新缓存
-
缓存雪崩:大量缓存同时失效,可能导致所有请求直接打到数据库上,造成数据库负载过高。设置合理的缓存过期策略(如添加随机过期时间)可以减轻雪崩效应。
例子:
社交应用的动态列表: 如果所有用户的动态数据缓存都设置了相同的过期时间,当这个时间点到达,所有请求都会直接打到数据库。为减轻这种雪崩效应,可以为不同的缓存项设置随机的过期时间范围,分散缓存失效时间点,从而避免集中失效造成的压力。
2. 何时需要用分布式锁,何时本地锁就足够了
-
分布式锁:在分布式系统中,当多个服务实例可能同时尝试修改共享资源(如数据库记录或缓存数据)时,需要使用分布式锁。例如,在高并发场景下,为了防止缓存击穿,确保只有一个线程能执行数据库查询并更新缓存,这时候分布式锁就显得尤为重要。
@Cacheable
注解中的sync=true
就是一个简单的基于Spring Cache的同步控制,它利用了AOP代理实现类似锁的效果,以防止并发下的数据不一致,但其粒度相对粗且依赖于框架实现。
分布式锁具体例子:
- 秒杀系统: 在电商的秒杀活动中,成千上万的用户可能同时尝试购买同一件商品,这时需要确保库存的正确扣减。通过Redis分布式锁,可以确保同一时刻只有一个请求能够执行扣减库存的操作,其他请求则等待锁释放或失败,从而维护库存的准确性和交易的公平性。
-
本地锁:如果应用程序运行在单个JVM中,或者操作的数据仅对当前进程可见,那么使用本地锁(如Java的
synchronized
关键字或ReentrantLock
)就足够了。本地锁可以提供更细粒度的控制,减少锁竞争带来的性能开销。但在微服务架构中,由于服务间的独立性,本地锁无法跨越服务边界,因此对于跨服务的资源访问控制,分布式锁是必要的选择。
本地锁具体例子:
- 单体应用的数据处理: 假设有一个单体应用负责处理报表生成任务,其中包含一个方法用于统计每日销售数据。因为这个方法可能被多个线程同时调用,使用Java的
synchronized
关键字或ReentrantLock
可以确保在任何给定时间只有一个线程能够执行该统计逻辑,防止数据错乱。
总结来说,是否采用分布式锁取决于系统的部署架构(是否为分布式系统)、并发访问的程度以及对数据一致性的严格要求。在分布式环境下,特别是在处理全局共享资源时,分布式锁是确保数据一致性和防止并发问题的有效手段。而在单机环境或服务内部操作时,本地锁即可满足需求,实现简单且效率更高。