当前位置:首页 > 科技  > 软件

你知道缓存的这个问题到底把多少程序员坑惨了吗?

来源: 责编: 时间:2024-07-05 09:05:12 1012观看
导读引言你是不是也经历过这种情况?某个阳光明媚的上午,你正享受着枸杞泡水和敲代码的美妙时刻,突然接到客服部门的电话。电话那头的声音急切又焦虑:“客户抱怨说,怎么点击购买总是失败?”作为技术人员,你瞬间脑袋里浮现出无数可

引言

你是不是也经历过这种情况?某个阳光明媚的上午,你正享受着枸杞泡水和敲代码的美妙时刻,突然接到客服部门的电话。kMa28资讯网——每日最新资讯28at.com

电话那头的声音急切又焦虑:“客户抱怨说,怎么点击购买总是失败?”kMa28资讯网——每日最新资讯28at.com

作为技术人员,你瞬间脑袋里浮现出无数可能的原因,其中最让人头疼的,莫过于缓存与数据库的不一致问题。kMa28资讯网——每日最新资讯28at.com

这个问题不仅影响用户体验,还可能导致业务数据的混乱。kMa28资讯网——每日最新资讯28at.com

今儿就来聊聊缓存与数据库不一致的问题,帮大家理清楚问题所在,并给出推荐方案。kMa28资讯网——每日最新资讯28at.com

问题剖析

在现代系统中,缓存可以极大地提升性能,减少数据库的压力。kMa28资讯网——每日最新资讯28at.com

然而,一旦缓存和数据库的数据不一致,就会引发各种诡异的问题。kMa28资讯网——每日最新资讯28at.com

系统维护的重要性在此凸显,极端手段比如清空缓存,可能会在短时间内解决问题,但从长远来看,这些操作往往带来更大的隐患。kMa28资讯网——每日最新资讯28at.com

解决方案概览

我们来看看几种常见的解决缓存与数据库不一致的方案,每种方案都有各自的优缺点:kMa28资讯网——每日最新资讯28at.com

  1. 先更新缓存,再更新数据库
  2. 先更新数据库,再更新缓存
  3. 先删除缓存,后更新数据库
  4. 先更新数据库,后删除缓存

深入探讨

先更新缓存,再更新数据库

这种方案看似简单,实际上很少被推荐。kMa28资讯网——每日最新资讯28at.com

原因在于如果在更新数据库之前发生了错误,缓存中的数据将和数据库中的数据不一致,最终导致更大的问题。kMa28资讯网——每日最新资讯28at.com

public void updateCacheThenDatabase(String key, String value) {    cache.put(key, value);    try {        database.update(key, value);    } catch (Exception e) {        // 数据库更新失败,缓存数据可能错误        System.err.println("数据库更新失败: " + e.getMessage());    }}

先更新数据库,再更新缓存

这种方法解决了更新缓存失败的问题,但可能引发另外一个问题:kMa28资讯网——每日最新资讯28at.com

在高并发场景下,数据库已经更新,但缓存还没有更新时,其他请求可能会读到旧的缓存数据。kMa28资讯网——每日最新资讯28at.com

public void updateDatabaseThenCache(String key, String value) {    database.update(key, value);    cache.put(key, value);}

先删除缓存,后更新数据库

这种方案在高并发下容易产生问题:kMa28资讯网——每日最新资讯28at.com

在缓存删除和数据库更新之间的时间窗口内,其他请求可能会读取到旧的数据,导致短时间内的数据不一致。kMa28资讯网——每日最新资讯28at.com

public void deleteCacheThenUpdateDatabase(String key, String value) {    cache.remove(key);    try {        database.update(key, value);    } catch (Exception e) {        // 数据库更新失败,需要重新设置缓存        cache.put(key, getOldValueFromDatabase(key));        System.err.println("数据库更新失败: " + e.getMessage());    }}

先更新数据库,后删除缓存

这是较为推荐的一种方法,但在高并发场景下也有一定的局限性:kMa28资讯网——每日最新资讯28at.com

如果数据库更新成功但缓存删除失败,可能导致短时间内的数据不一致。kMa28资讯网——每日最新资讯28at.com

public void updateDatabaseThenDeleteCache(String key, String value) {    database.update(key, value);    cache.remove(key);}

强一致性与最终一致性

在讨论一致性的时候,我们常常会提到强一致性和最终一致性。kMa28资讯网——每日最新资讯28at.com

强一致性保证每次读取的数据都是最新的,但在分布式系统中实现成本较高。kMa28资讯网——每日最新资讯28at.com

最终一致性则允许数据在一定时间内不一致,适用于大多数实际业务场景。kMa28资讯网——每日最新资讯28at.com

根据业务需求权衡这两者,是缓存策略设计中的重要一步。kMa28资讯网——每日最新资讯28at.com

后面我会给出一个弱一致性的推荐方案,供大家参考。kMa28资讯网——每日最新资讯28at.com

SpringCache

SpringCache是一个非常实用的缓存管理框架,能帮助我们简化缓存操作。kMa28资讯网——每日最新资讯28at.com

以下是一个简单的SpringCache配置示例:kMa28资讯网——每日最新资讯28at.com

@Configuration@EnableCachingpublic class CacheConfig {    @Bean    public CacheManager cacheManager() {        return new ConcurrentMapCacheManager("entities");    }}

SpringCache 的优点:kMa28资讯网——每日最新资讯28at.com

  • 简化缓存管理:SpringCache 提供了简洁的 API,能够轻松集成到现有项目中。
  • 注解驱动:使用注解如 @Cacheable、@CachePut 和 @CacheEvict,使得缓存操作更直观。
  • 灵活配置:支持多种缓存实现,能够根据需求选择合适的缓存方案。

SpringCache 的缺点:kMa28资讯网——每日最新资讯28at.com

  • 限制性:默认的缓存管理器在一些复杂场景下可能不够灵活。
  • 性能瓶颈:在高并发场景下,内存缓存可能会成为性能瓶颈,需要合理设计和优化。

常用的注解包括@Cacheable、@CachePut和@CacheEvict:kMa28资讯网——每日最新资讯28at.com

@Cacheable("entities")public Entity findById(Long id) {    // 如果缓存中存在,则直接返回缓存中的数据    // 否则调用方法查询数据库,并将结果存入缓存    return repository.findById(id).orElse(null);}@CachePut(value = "entities", key = "#entity.id")public Entity updateEntity(Entity entity) {    // 更新数据库,同时更新缓存中的数据    return repository.save(entity);}@CacheEvict(value = "entities", allEntries = true)public void clearCache() {    // 清空缓存}

缓存预热策略

缓存预热的重要性不言而喻,上线后瞬时大流量可能导致缓存击穿。kMa28资讯网——每日最新资讯28at.com

以下是几种常见的缓存预热方案:kMa28资讯网——每日最新资讯28at.com

  1. 启动时加载:系统启动时加载常用数据到缓存。
@Componentpublic class CachePreloader {    @Autowired    private CacheManager cacheManager;    @PostConstruct    public void preload() {        Cache cache = cacheManager.getCache("entities");        if (cache != null) {            // 假设我们有一个服务可以获取需要预热的数据            List<Entity> entities = fetchEntitiesForPreloading();            for (Entity entity : entities) {                cache.put(entity.getId(), entity);            }        }    }    private List<Entity> fetchEntitiesForPreloading() {        // 获取需要预热的数据        return repository.findAll();    }}
  1. 定时加载:定时任务定期加载数据。
@Componentpublic class ScheduledCachePreloader {    @Autowired    private CacheManager cacheManager;    @Scheduled(fixedRate = 60000) // 每分钟执行一次    public void preload() {        Cache cache = cacheManager.getCache("entities");        if (cache != null) {            // 获取需要预热的数据并加载到缓存            List<Entity> entities = fetchEntitiesForPreloading();            for (Entity entity : entities) {                cache.put(entity.getId(), entity);            }        }    }    private List<Entity> fetchEntitiesForPreloading() {        // 获取需要预热的数据        return repository.findAll();    }}
  1. 手动加载:手动执行预热脚本。
@Componentpublic class ManualCachePreloader {    @Autowired    private CacheManager cacheManager;    public void preload() {        Cache cache = cacheManager.getCache("entities");        if (cache != null) {            // 获取需要预热的数据并加载到缓存            List<Entity> entities = fetchEntitiesForPreloading();            for (Entity entity : entities) {                cache.put(entity.getId(), entity);            }        }    }    private List<Entity> fetchEntitiesForPreloading() {        // 获取需要预热的数据        return repository.findAll();    }}

推荐方案

综合考虑各种方案的优缺点,我给大家一种工作中真正常用的方案,也是我待过的互联网公司中实践过的方案。kMa28资讯网——每日最新资讯28at.com

基本策略:删除Redis中缓存 -> 更新数据库 -> 最新数据set到RediskMa28资讯网——每日最新资讯28at.com

延迟双删:删除Redis中缓存 -> 更新数据库 -> 休眠500ms -> 再次删除Redis中缓存 -> 最新数据set到RediskMa28资讯网——每日最新资讯28at.com

延迟三删:删除Redis中缓存 -> 更新数据库 -> 休眠500ms -> 再次删除Redis中缓存 -> 休眠500ms -> 再次删除Redis中缓存 -> 最新数据set到RediskMa28资讯网——每日最新资讯28at.com

这是一种非常简单且成本很低的操作,但能解决绝大多数的缓存与数据库不一致问题。kMa28资讯网——每日最新资讯28at.com

原理很好理解,就是更新数据库之后设置合理的休眠时间,然后再次删除掉其他线程请求进来导致的旧缓存,最终达到缓存和数据库都是最新数据的目的。kMa28资讯网——每日最新资讯28at.com

其中休眠时间要根据自身业务的平均耗时来决定,而延迟双删其实就够了,延迟三删只是为了开阔大家的思路,因为真有些公司删除三次来保证一些极端情况的不一致,但我觉得没必要,太极端就不是弱一致性了。kMa28资讯网——每日最新资讯28at.com

如果是比较复杂的项目,甚至能再进一步的优化,也就是借用定时任务和MQ来替代休眠线程,实现异步删除缓存,达到弱一致性的结果。kMa28资讯网——每日最新资讯28at.com

总结与思考

缓存与数据库一致性是一个复杂的问题,需要根据具体业务场景选择合适的策略。kMa28资讯网——每日最新资讯28at.com

大家需要记住一点,高可用性是系统的最优先选择,所以弱一致性就必然成为数据不一致性的最优解,这是一种良性闭环。kMa28资讯网——每日最新资讯28at.com

因为系统不能用,那是直接亏损,但数据出现低量的不一致,完全可以接受。kMa28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-98863-0.html你知道缓存的这个问题到底把多少程序员坑惨了吗?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 使用Spring Boot和流量控制算法解决视频会议系统网络波动问题

下一篇: SpringBoot自带Controller接口监控,赶紧用起来

标签:
  • 热门焦点
Top