好处
缺点
使用场景
三种策略
这三个策略中,一致性最好的就是主动更新。能够根据代码实时的更新数据,但是维护成本也是最高的;算法剔除和超时剔除一致性都做的不够好,但是维护成本却非常低。
根据缓存的使用场景,我们会采用不同的更新策略。
实际开发中我给大家以下两个建议。
我们知道,用户第一次访问客户端,客户端访问 Redis 肯定是没有的,这个时候只能从数据库 DB 那里获取信息,代码如下
select * from t_teacher where id= {id}
在 Redis 设置用户信息缓存,代码如下
set teacher:{id} select * from t_teacher where id= {id}
这个时候我们来看看缓存粒度问题。
因为我们要更新全部属性。到底我们是采用 select * 还是仅仅只是更新你需要更新的那些字段呢?如下两段代码
set key1 = ? from select * from t_teacher
set key1 = ? from select key1 from t_teacher
缓存粒度控制可以从以下三个角度来观察,通过这三点来决定如何选择。
大量请求不命中(即访问缓存拿数据时发现缓存里没有数据,然后再访问数据库拿数据发现还是没有)
解决方案1:缓存空对象
当缓存中不存在,访问数据库的时候,又找不到数据,需要设置给 cache 的值为 null,这样下次再次访问该 id 的时候,就会直接访问缓存中的 null 了。
解决方案2:布隆过滤器拦截
布隆过滤器,实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
无底洞问题就是即使加机器,性能却没有提升,反而降低了。到底这是怎么回事呢?先看下面的图。
当客户端增加一个缓存的时候,只需要 mget 一次,但是如果增加到三台缓存,这个时候则需要 mget 三次了,每增加一台,客户端都需要做一次新的 mget,给服务器造成性能上的压力。
同时,mget 需要等待最慢的一台机器操作完成才能算是完成了 mget 操作。这还是并行的设计,如果是串行的设计就更加慢了。
通过上面这个实例可以总结出:更多的机器不等于更高的性能
但是并不是没办法,一般在优化 IO 的时候可以采用以下几个方法。
我们知道,使用缓存,如果获取不到,才会去数据库里获取。但是如果是热点 key,访问量非常的大,数据库在重建缓存的时候,会出现很多线程同时重建的情况。
如上图,就是因为高并发导致的大量热点的 key 在重建还没完成的时候,不断被重建缓存的过程,由于大量线程都去做重建缓存工作,导致服务器拖慢的情况。只有最后一个是重建完成,命中缓存。
为了解决以上的问题,我们着重研究了三个目标和两个解决方案。
三个目标为:
两个解决方案为
我们根据三个目标,解释一下两个解决方案。
互斥锁(mutex key)
由下图所示,第一次获取缓存的时候,加一个锁,然后查询数据库,接着是重建缓存。这个时候,另外一个请求又过来获取缓存,发现有个锁,这个时候就去等待,之后都是一次等待的过程,直到重建完成以后,锁解除后再次获取缓存命中。
示例代码:
public String getKey(String key){
String value = redis.get(key);
if(value == null){
String mutexKey = "mutex:key:"+key; //设置互斥锁的key
if(redis.set(mutexKey,"1","ex 180","nx")){ //给这个key上一把锁,ex表示只有一个线程能执行,过期时间为180秒
value = db.get(key);
redis.set(key,value);
redis.delete(mutexKety);
}else{
// 其他的线程休息100毫秒后重试
Thread.sleep(100);
getKey(key);
}
}
return value;
}
永远不过期
首先在缓存层面,并没有设置过期时间(过期时间使用 expire 命令)。但是功能层面,我们为每个 value 添加逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
缓存的收益:加快读写、降低后端存储负载。
缓存的成本:缓存和存储数据不一致性,代码维护成本、运维成本。
更新策略:推荐结合剔除、超时、主动更新三种方案共同完成。
穿透的优化:使用缓存空对象和布隆过滤器来解决,注意各自使用场景和局限性。
无底洞问题:分布式缓存中,堆积机器不一定会使得性能更好,有四种批量操作方式:串行命令、串行 IO、并行 IO、hash_tag。
雪崩问题:缓存层高可用、客户端降级、提前演练是解决雪崩问题的重要方法。
热点key问题:互斥锁和永不过期这两种方式能够在一定程度上解决热点key问题,但各自有自己的缺点需要我们了解。