目录redis
缓存穿透是指查询一个必定不存在的数据,由于缓存中也无该数据的信息,则会直接去数据库层进行查询,从系统层面来看像是穿透了缓存层直接达到db,从而称为缓存穿透,没有了缓存层的保护,这种查询必定不存在的数据对系统来讲多是一种危险,若是有人恶意用这种必定不存在的数据来频繁请求系统,不,准确的说是攻击系统,请求都会到达数据库层致使db瘫痪从而引发系统故障。数据库
缓存失效的时候,先去得到锁,获得锁了,再去请求数据库。没获得锁,则休眠一段时间重试。后端
不管 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存若是过时,异步起一个线程去读数据库,更新缓存。须要作缓存预热(项目启动前,先加载缓存)操做。缓存
提供一个能迅速判断请求是否有效的拦截机制,好比,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。若是不合法,则直接返回。服务器
在第一次查询完不存在的数据后,将该key与对应的空值也放入缓存中,只不过设定为较短的失效时间,例如几分钟,这样则能够应对短期的大量的该key攻击,设置为较短的失效时间是由于该值可能业务无关,存在乎义不大,且该次的查询也未必是攻击者发起,无太久存储的必要,故能够早点失效。并发
缓存雪崩是指在咱们设置缓存时采用了相同的过时时间,致使缓存在某一时刻同时失效,请求所有转发到数据库,数据库瞬时压力太重雪崩。异步
加上一个随机值,避免集体失效。分布式
只让一个线程构建缓存,其余线程等待构建缓存的线程执行完,从新从缓存获取数据才能够,每一个时刻只有一个线程在执行请求,减轻了db的压力,但缺点也很明显,下降了系统的qps。高并发
咱们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。本身作缓存预热操做。性能
双缓存细分如下几个小点:从缓存 A 读数据,有则直接返回;A 没有数据,直接从B读数据,直接返回,而且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。
缓存击穿其实是缓存雪崩的一个特例.
对于一些设置了过时时间的key,若是这些key可能会在某些时间点被超高并发地访问,是一种很是“热点”的数据。这个时候,须要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是不少key。
缓存在某个时间点过时的时候,刚好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过时通常都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
伪代码示例: public String get(key) { String value = redis.get(key); if (value == null) { //表明缓存值过时 //设置3min的超时,防止del操做失败的时候,下次缓存过时一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //表明设置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); } else { //这个时候表明同时候的其余线程已经load db并回设到缓存了,这时候重试获取缓存值便可 sleep(50); get(key); //重试 } } else { return value; } }
在value内部设置1个超时值(timeout1),timeout1比实际的缓存中的timeout(timeout2)小。当从cache读取到timeout1发现它已通过期时候,立刻延长timeout1并从新设置到cache。而后再从数据库加载数据并设置到cache中。
这里的“永远不过时”包含两层意思:
(1) 从redis上看,确实没有设置过时时间,这就保证了,不会出现热点key过时问题,也就是“物理”不过时。
(2) 从功能上看,若是不过时,那不就成静态的了吗?因此咱们把过时时间存在key对应的value里,若是发现要过时了,经过一个后台的异步线程进行缓存的构建,也就是“逻辑”过时
从实战看,这种方法对于性能很是友好,惟一不足的就是构建缓存时候,其他线程(非构建缓存的线程)可能访问的是老数据,可是对于通常的互联网功能来讲这个仍是能够忍受。
伪代码以下: String get(final String key) { V v = redis.get(key); String value = v.getValue(); long timeout = v.getTimeout(); if (v.timeout <= System.currentTimeMillis()) { // 异步更新后台异常执行 threadPool.execute(new Runnable() { public void run() { String keyMutex = "mutex:" + key; if (redis.setnx(keyMutex, "1")) { // 3 min timeout to avoid mutex holder crash redis.expire(keyMutex, 3 * 60); String dbValue = db.get(key); redis.set(key, dbValue); redis.delete(keyMutex); } } }); } return value; }
采用netflix的hystrix,能够作资源的隔离保护主线程池,若是把这个应用到缓存的构建也何尝不可.
对于热点数据进行二级缓存,并对于不一样级别的缓存设定不一样的失效时间,则请求不会直接击穿缓存层到达数据库。
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操做,可是最后执行的顺序和咱们指望的顺序不一样,这样也就致使告终果的不一样!
须要说明一下,我提早百度了一下,发现答案基本都是推荐用 Redis 事务机制。
我并不推荐使用 Redis 的事务机制。由于咱们的生产环境,基本都是 Redis 集群环境,作了数据分片操做。
你一个事务中有涉及到多个 Key 操做的时候,这多个Key不必定都存储在同一个 redis-server 上。所以,Redis 的事务机制,十分鸡肋。
这种状况下,准备一个分布式锁,你们去抢锁,抢到锁就作 set 操做便可,比较简单。
假设有一个 key1,系统 A 须要将 key1 设置为 valueA,系统 B 须要将 key1 设置为 valueB,系统 C 须要将 key1 设置为 valueC。
指望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。这种时候咱们在数据写入数据库的时候,须要保存一个时间戳。
好比
系统A key1 {valueA 3:00} 系统B key1 {valueB 3:05} 系统C key1 {valueC 3:10}
那么,假设这会系统 B 先抢到锁,将 key1 设置为{valueB 3:05}。接下来系统 A 抢到锁,发现本身的 valueA 的时间戳早于缓存中的时间戳,那就不作 set 操做了,以此类推。
其余方法,好比利用队列,将 set方法变成串行访问也能够。总之,灵活变通。
新的缓存系统没有任何缓存数据,在缓存重建数据的过程当中,系统性能和数据库负载都不太好,因此最好是在系统上线以前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。
解决思路:
缓存热备即当一台缓存服务器不可用时能实时切换到备用缓存服务器,不影响缓存使用。集群模式下,每一个主节点都会有一个或多个从节点来当备用,一旦主节点挂点,从节点当即充当主节点使用。