赞
踩
本节将介绍高并发下缓存相关问题和解决思想: 缓存读写策略、缓存高可用、缓存穿透
对不同的业务场景,缓存的读写策略是不同的,以标准的 缓存 + 数据库 场景为例。我见过的不少项目是这样做的:
写操作:先更新 DB,再更新缓存
读操作:先查询缓存,缓存没命中就查 DB,并把结果写缓存
存在问题: 容易出现缓存和 DB 不一致。比如 A 线程先更新 DB, 紧接着 B 线程更新 DB。但是由于线程的切换是 “非公平” 的,迟迟没有轮到 A 线程执行,或者后续还有别的操作,最终 A 线程更新缓存的操作落后于 B 线程,导致数据不一致。
问题本质: 写数据库和写缓存,是两个独立的操作,没有并发控制(也可理解为没有事务控制) 想保证数据一致性,一定程度上写性能就会下降,下面将介绍高并发场景下的几种常用策略
这里介绍一种 旁路缓存(Cache Aside)策略
基本做法是先更 DB 再删缓存:
updateDB()
deleteCache()
旁路缓存基本满足大多数场景,但也可能会存在数据不一致问题。比如 DB 做了读写分离,可能主从同步还没有完成,读请求没有命中缓存再去 DB 查询时仍然是旧数据。
不过多数场景下,缓存在一定时间内和 DB 不一致是允许的。所以可以把缓存的过期时间设置短一些,比如 5~10 秒。或者是写操作并发不高时,也可以写缓存时加上 分布式锁。 还有通过消息队列或者订阅 binlog 异步更新缓存。
对于高并发系统而言,缓存命中率至关重要,特别是有的场景需要命中率维持在 99% 以上,分布式缓存的高可用常用方案主要有 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 就认为这个元素在集合中,否则则认为不在集合中。
存在缺陷:
解决方案:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。