缓存穿透处理

一、什么是缓存的穿透问题

如图,一个正常的请求一般都会经过cache层再到storage层,如果cache层没有而在storage层查到,则将数据新增到cache层后返回,下次再有同样的请求则直接从cache层返回数据,无需再请求storage层;而如果在storage层也获取不到数据,则没有数据新增到cache里,下次再有同样的请求会继续到达storage层,这就是缓存穿透的定义。

缓存的一个作用就是保护storage,也就是MySQL之类的数据库,而缓存穿透的话就失去了这一层保护。

 

二、解决方案

1、程序路由阶段就限定ID的范围(譬如使用正则)

2、程序硬编码判断ID值的合法性

3、数据库查询判断等

三、方案详情

假设有一张新闻表的ID范围是100到2000,这时如果查询2004或者3000等ID是没有的,但不代表未来没有。因此这里有其中一个做法(设置空键)如下:
1、譬如key=news2004,如果数据库查询返回空
2、那么我们依然往redis存储一个我们约定好的数据结构并设置过期时间,譬如设置为200秒(到底多少秒要根据实际情况来调整)
3、下次如果继续查到这个key,那么我们增加这个key的过期时间(譬如5秒)

以下为“伪代码”演示:

String newsID = getParameter("id");
News getNews = getFromRedis("news" + newsID);

if(getNews == null){

	getNews = getFromDB("news" + newsID);
	
	if(getNews == null){
		getNews = defaultCache(); // 设置一个业务不可能出现的 默认固定缓存,譬如字符串 “-1”
	}

	setToRedis(getNews,200); // 塞入redis,初始过期时间为200秒

}else if(getNews.equal(defaultCache())){ // 如果取出的缓存是 默认缓存,则增加5秒的过期时间

	expireCache("news" + newsID,50);

}

response.write(getNews);

接下来我们对以上方案进行一点优化,增加一个防护手段(封杀单IP)
如果有恶意用户进行反复请求,那么我们的redis里很快就会塞满各种无用的新闻key。数量过多,对数据库也会有压力(毕竟第一次还是要访问数据库的)
优化策略是:对于一个IP,我们单独开辟一个key/value,key=前缀+ip
1、如果DB没有命中,则 +2分
2、如果命中“默认值”, +1分
3、一旦有以上操作,对应key的数据重新expire指定的时间(譬如3600秒)
4、当该IP的分数值达到我们设定的阈值(譬如6分),则让该IP无法正常访问我们的新闻页面(或者给一个静态页面,类似于404默认页)


譬如有一个IP 192.168.10.20,我们将其塞入了缓存,但是一些稍微专业的“恶意用户”会更换IP,用192.168.10.9、192.168.10.8、192.168.10.101等继续恶搞。

对于这种情况我们需要设定封禁IP列表(封杀IP段),譬如192.168.10开头的IP一律不能访问。redis可用列表(List)解决这个问题,一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

三、方案优化 - 布隆过滤器

布隆过滤器是一种比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

适用于精确性要求不严格的场景,如上面的黑白名单、是否签到等。业务流程如下:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值