缓存中间件与数据库的相爱相杀

前言

最近马D梅因为年前太任性辞了职,碰巧遇到这次疫情,搞得心慌慌。最近四处投简历找工作,毕竟还是要吃饭的。

这不就接到一个公司的面试通知,即将开始一轮尴尬的面试。。。

面试官:为了减轻数据库的压力,通常采用的解决方案有哪些?

马D梅:缓存是最常用一种。包括浏览器缓存、Nginx反向代理缓存、JVM缓存、缓存中间件还有数据库缓存这几个方面。

面试官:就缓存中间件和数据库的问题,对于查询和更新的操作,你是怎么处理的?

马D梅:对于更新请求,先更新数据库再删除缓存;当查询请求过来查询不到缓存时,就会从数据库查询数据并放到缓存中。

面试官:那如果查询请求和更新请求发生并发时,如图1所示,所示步骤是有可能发生的,那如何避免情况?

马D梅:(慌了,我倒还没考虑过,不是编译通过就行了吗。。。)那就先删除缓再更新数据库。

面试官:(这小子乱打一通?)如图2所示,也还是存在问题。


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

在这里插入图片描述
如图1所示,当更新请求与查询请求发生并发情况时,会导致缓存与数据库的数据不一致。


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

在这里插入图片描述
如图2所示,当更新请求与查询请求发生并发情况时,会导致缓存与数据库的数据不一致。


解决方案

大前提:使用缓存就要容忍数据不一致。能保证最终一致性即可。

设置有效期

好处:缓存超过有效期被淘汰之后,程序会从数据库获取最新的数据重新放入缓存,保持一致性。

坏处:因为缓存失效,从而引发缓存击穿缓存雪崩等问题。

加锁

不管是单机锁还是分布式锁,这种做法可以保证不同线程之间的操作不会出现不确定的结果,但是实际上在项目中并不会采用这种做法。本来使用缓存就是为了提高效率,现在因为缓存而加锁,只会得不偿失。

所有涉及缓存和数据库的操作都加上锁,极大地降低了处理并发的能力。

缓存中心

上面这些问题归结起来,就是修改缓存的入口太多。

那么为了解决这些问题,我们可以把缓存统一由一个模块来进行管理。
(示意图)
建立缓存中心之后,业务系统就不需要对缓存进行管理,由缓存中心统一对缓存进行维护。

代码示例:

/**
 * 建立缓存中心前
 */
public String findHotData() {
	// 查询缓存
    String result = redisTemplate.opsForValue().get(HOT_DATA_KEY);
    if (StringUtils.isNotBlank(result)) {
        return result;
    }
    // 缓存未命中则查询数据库
    result = dao.findHotData();
    if (StringUtils.isNoneBlank(result)) {
    	// 将查询的数据放入缓存
        redisTemplate.opsForValue().set(HOT_DATA_KEY, result);
    }
    return result;
}

/**
 * 建立缓存中心后
 */
public String findHotData() {
    return redisTemplate.opsForValue().get(HOT_DATA_KEY);
}

数据预热

(示意图)

对于一些热点的数据,我们可以通过缓存中心进行以下操作:

  • 应用系统或缓存中间件启动(重启)时,把数据加载到缓存中
  • 通过后台任务定时刷新缓存

监听日志,更新缓存

(示意图)
(工具:canal)

发布了107 篇原创文章 · 获赞 88 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/Code_shadow/article/details/104831013
今日推荐