【高并发系统设计理论】03 - 缓存

本节将介绍高并发下缓存相关问题和解决思想: 缓存读写策略、缓存高可用、缓存穿透


1. 缓存读写策略

对不同的业务场景,缓存的读写策略是不同的,以标准的 缓存 + 数据库 场景为例。我见过的不少项目是这样做的:

写操作:先更新 DB,再更新缓存
读操作:先查询缓存,缓存没命中就查 DB,并把结果写缓存

存在问题: 容易出现缓存和 DB 不一致。比如 A 线程先更新 DB, 紧接着 B 线程更新 DB。但是由于线程的切换是 “非公平” 的,迟迟没有轮到 A 线程执行,或者后续还有别的操作,最终 A 线程更新缓存的操作落后于 B 线程,导致数据不一致。

问题本质: 写数据库和写缓存,是两个独立的操作,没有并发控制(也可理解为没有事务控制) 想保证数据一致性,一定程度上写性能就会下降,下面将介绍高并发场景下的几种常用策略


这里介绍一种 旁路缓存(Cache Aside)策略

基本做法是先更 DB 再删缓存:

updateDB()
deleteCache()

旁路缓存基本满足大多数场景,但也可能会存在数据不一致问题。比如 DB 做了读写分离,可能主从同步还没有完成,读请求没有命中缓存再去 DB 查询时仍然是旧数据。

不过多数场景下,缓存在一定时间内和 DB 不一致是允许的。所以可以把缓存的过期时间设置短一些,比如 5~10 秒。或者是写操作并发不高时,也可以写缓存时加上 分布式锁。 还有通过消息队列或者订阅 binlog 异步更新缓存。

2. 缓存高可用

对于高并发系统而言,缓存命中率至关重要,特别是有的场景需要命中率维持在 99% 以上,分布式缓存的高可用常用方案主要有 3 大类: 客户端方案中间代理层方案服务端方案

  • 客户端方案 指在客户端配置好了多个缓存的节点,由客户端的读写策略、loadblance 策略来实现分布式,提高可用性。
  • 中间件代理方案 通过代理层实现高可用,比如阿里云的云 Redis 产品
  • 服务端方案 比如 Redis Sentinel

3. 缓存穿透

缓存穿透 指的是请求一个不存在的数据,导致每次请求都会命中数据库。一般来说会有 2 种解决方案:空值缓存布隆过滤器


空值缓存

Object data = getFromDB(key);
if (data == null) {
    // 为这个key设置空值缓存,假设默认过期时间为 10s
	cache.set(key, null, 10);
}

问题: 如果攻击者大量请求不存在的 key,将导致缓存被大量吃满


布隆过滤器

1970 年布隆提出了一种布隆过滤器的算法,用来判断一个元素是否在一个集合中。这种算法由一个二进制数组和一个 Hash 算法组成。

我们把集合中的每一个值按照提供的 Hash 算法算出对应的 Hash 值,然后将 Hash 值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从 0 改成 1。在判断一个元素是否存在于这个集合中时,你只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为 1 就认为这个元素在集合中,否则则认为不在集合中。

存在缺陷:

  1. 有一定错误几率的,比如会把不是集合中的元素判断为处在集合中;
  2. 不支持删除元素。

解决方案:

  1. 使用多个 hash 函数计算出多个 hash 值
  2. 不在存位,而是存数字,比如删除的时候就把计算出 hash 对应位置上的数减1,但是这样额外的空间消耗需要评估
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值