随着互联网的越来越普及,用户越来越多,系统性能瓶颈成了越来越热门的话题。要解决性能问题的技术手段有很多,比如:缓存、CDN加速、页面静态化、集群、分布式、异步等。
缓存通常被作为首先技术方案,简单而且提升效果明显,它能够将速度提升100倍。那么问题来了,缓存为啥会怎么快呢?
因为传统的数据库操作是基于磁盘的,而缓存是基于内存的,内存操作和磁盘操作的速度根本不是一个数量级的。目前市面上主流的缓存有:redis 和 memcache,这两个都是基于内存的缓存技术,二者的区别我在这里暂时不讲。使用缓存的伪代码一般如下:
String order = redisClient.get(key);
if(order != null) {
return order;
}
order = db.get(key);
redisClient.put(key,order);
redisClient.expire(key,3000);
return order;
根据key获取数据,先从缓存中查一下有没有,如果有则直接返回。如果没有,再从数据库中查到数据,然后将数据放入缓存中,并且给当前key设置一个失效时间,下次再用同样的key来请求数据时,就能够直接从缓存中查询到并返回,减少请求数据库的频次,提升性能,因为数据库连接是稀有资源。
那么问题又来了,为啥要设置失效时间,不设置不行吗?
著名的2/8原则告诉我们,经常访问的数据集中在20%,而另外的80%属于不常用数据。我们都知道内存相当于磁盘来说价格是比较昂贵的,不信你买个500G的硬盘 和 一个 500G的内存试试。既然这么贵,我们应该节约使用,所以才会有设置失效时间这种策略,一旦检测到某个key超过了失效时间,就会将该key从缓存中删除,可以节约内存。
还有个问题:如果在某个key失效的时候,有大量的请求一起过来会怎么样?
这就是我今天要给大家讲的:击穿。
大量的请求访问同一个key,刚好那个key失效了,那么同一时间所有的请求,都会穿过缓存,直接请求数据库,此时的数据库有可能因为无法扛着这么大的并发,直接挂了。
再问一下:如果大量的请求访问多个key,刚好key同时失效了会怎么样?
这就是我今天要给大家讲的:雪崩。
雪崩比上面的击穿更严重,击穿只是一个key失效了,大量请求直接访问数据库都有可能把数据库搞挂,更何况大量的key同时失效的场景,数据库面临的压力更大,更有可能挂掉。
接下来的问题:如果大量的用户请求缓存中不存在的key又会怎么样?
这就是我今天要给大家讲的:穿透。
有大量的请求访问时,只有少部分的key在缓存中存在,而有大量的key不存在,这样请求也会直接访问到数据库,也会导致数据库扛不住压力而挂掉。这种情况往往是黑客伪造请求,发起的恶意攻击。
那么,这些问题有没有解决办法呢?
首先,击穿的解决办法-加锁。
伪代码如下:
String order = redisClient.get(key);
if(order != null) {
return order;
}
lock() {
String order = redisClient.get(key);
if(order != null) {
return order;
}
order = db.get(key);
redisClient.put(key,order);
redisClient.expire(key,3000);
}
return order;
如果根据key从缓存中查询不到数据,需要从数据库中查询数据的时候,加一把锁,保证同一时间只有一个线程可以查询数据库,然后把查询出来的结果放回到缓存中。这样其他的线程再用相同的key查询时,就可以直接从缓存中查到数据。这样就能够极大的减少数据库的访问频次。
其次,雪崩的解决办法- 加锁 + key设置不同的失效时间。
加锁的伪代码跟上面是一样的我就不写了。
雪崩还有一个必要条件就是在同一时间,有大量的key同时失效。我们只要保证不会出现同一时间有大量的key同时失效就可以了,每个key设置不同的失效时间就能解决问题。
最后,穿透的解决办法- 业务规则过滤 + 布隆过滤器
业务规则过滤 可以校验 key的长度或者比如前缀SD开头的等,过滤一批非法数据。
接下来看看布隆过滤器:
布隆过滤器中会初始化数据库中key的标识。如果有大量请求访问不存在的key时,先通过布隆过滤器检查一下key在数据库中是否存在,如果存在才允许访问数据库。如果不存在,则直接返回,这样就可以过滤掉大量的非法请求。