做ppt哪个网站好,wordpress兼容ie,建设诚信网站,如何建小企业网站我们通常使用 缓存 过期时间的策略来帮助我们加速接口的访问速度#xff0c;减少了后端负载#xff0c;同时保证功能的更新。
1、缓存穿透
缓存系统#xff0c;按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候#xff0c;就会对后端造…我们通常使用 缓存 过期时间的策略来帮助我们加速接口的访问速度减少了后端负载同时保证功能的更新。
1、缓存穿透
缓存系统按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候就会对后端造成很大的压力。
查询一个必然不存在的数据。比如文章表查询一个不存在的id每次都会访问DB如果有人恶意破坏很可能直接对DB造成影响。
由于缓存不命中每次都要查询持久层。从而失去缓存的意义。
1.1 使用互斥锁排队
业界比价普遍的一种做法即根据key获取value值为空时锁上从数据库中load数据后再释放锁。若其它线程获取锁失败则等待一段时间后重试。这里要注意分布式环境中要使用分布式锁单机的话用普通的锁synchronized、Lock就够了。
public String getWithLock(String key, Jedis jedis, String lockKey,String uniqueId, long expireTime) {// 通过key获取valueString value redisService.get(key);if (StringUtil.isEmpty(value)) {// 分布式锁详细可以参考https://blog.csdn.net/fanrenxiang/article/details/79803037// 封装的tryDistributedLock包括setnx和expire两个功能在低版本的redis中不支持try {boolean locked redisService.tryDistributedLock(jedis,lockKey, uniqueId, expireTime);if (locked) {value userService.getById(key);redisService.set(key, value);redisService.del(lockKey);return value;} else {// 其它线程进来了没获取到锁便等待50ms后重试Thread.sleep(50);getWithLock(key, jedis, lockKey, uniqueId, expireTime);}} catch (Exception e) {log.error(getWithLock exception e);return value;} finally {redisService.releaseDistributedLock(jedis, lockKey, uniqueId);}}return value;}这样做思路比较清晰也从一定程度上减轻数据库压力但是锁机制使得逻辑的复杂度增加吞吐量也降低了有点治标不治本。
1.2 布隆过滤器
bloomfilter就类似于一个hash set用于快速判某个元素是否存在于集合中其典型的应用场景就是快速判断一个key是否存在于某容器不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小下面先来简单的实现下看看效果我这里用guava实现的布隆过滤器
dependencies dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version23.0/version /dependency
/dependencies public class BloomFilterTest {private static final int capacity 1000000;private static final int key 999998;private static BloomFilterInteger bloomFilter BloomFilter.create(Funnels.integerFunnel(), capacity);static {for (int i 0; i capacity; i) {bloomFilter.put(i);}}public static void main(String[] args) {/* 返回计算机最精确的时间单位微妙 */long start System.nanoTime();if (bloomFilter.mightContain(key)) {System.out.println(成功过滤到 key);}long end System.nanoTime();System.out.println(布隆过滤器消耗时间: (end - start));int sum 0;for (int i capacity 20000; i capacity 30000; i) {if (bloomFilter.mightContain(i)) {sum sum 1;}}System.out.println(错判率为: sum);}}成功过滤到999998
布隆过滤器消耗时间:215518
错判率为:318
复可以看到100w个数据中只消耗了约0.2毫秒就匹配到了key速度足够快。然后模拟了1w个不存在于布隆过滤器中的key匹配错误率为318/10000也就是说出错率大概为3%跟踪下BloomFilter的源码发现默认的容错率就是0.03
2、缓存雪崩问题
缓存在同一时间内大量键过期失效接着来的一大波请求瞬间都落在了数据库中导致连接异常。
解决方案
在缓存失效后通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存其他线程等待。可以通过缓存reload机制预先去更新缓存再即将发生大并发访问前手动触发加载缓存不同的key设置不同的过期时间让缓存失效的时间点尽量均匀做二级缓存或者双缓存策略。A1为原始缓存A2为拷贝缓存A1失效时可以访问A2A1缓存失效时间设置为短期A2设置为长期。
public String getByKey(String keyA, String keyB) {String value redisService.get(keyA);if (StringUtil.isEmpty(value)) {value redisService.get(keyB);String newValue getFromDbById();redisService.set(keyA, newValue, 31, TimeUnit.DAYS);redisService.set(keyB, newValue);}return value;}3、热点key
(1) 这个key是一个热点key例如一个重要的新闻一个热门的八卦新闻等等所以这种key访问量可能非常大。
(2) 缓存的构建是需要一定时间的。可能是一个复杂计算例如复杂的sql、多次IO、多个依赖(各种接口)等等
于是就会出现一个致命问题在缓存失效的瞬间有大量线程来构建缓存见下图造成后端负载加大甚至可能会让系统崩溃 。
解决方法使用互斥锁(mutex key):这种解决方案思路比较简单就是只让一个线程构建缓存其他线程等待构建缓存的线程执行完重新从缓存获取数据就可以了 提前使用互斥锁(mutex key)在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。 “永远不过期”
这里的“永远不过期”包含两层意思
(1) 从redis上看确实没有设置过期时间这就保证了不会出现热点key过期问题也就是“物理”不过期。(2) 从功能上看如果不过期那不就成静态的了吗所以我们把过期时间存在key对应的value里如果发现要过期了通过一个后台的异步线程进行缓存的构建也就是“逻辑”过期资源保护可以做资源的隔离保护主线程池如果把这个应用到缓存的构建也未尝不可。
4、缓存和数据库间数据一致性问题
分布式环境下单机就不用说了非常容易出现缓存和数据库间的数据一致性问题针对这一点的话只能说如果你的项目对缓存的要求是强一致性的那么请不要使用缓存。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率而无法保证两者间的强一致性。合适的策略包括 合适的缓存更新策略更新数据库后要及时更新缓存、缓存失败时增加重试机制例如MQ模式的消息队列。
5、拓展
http://www.mobabel.net/总结redis热点key发现及常见解决方案/