善于总结,才能更快进步
通常,我们队高并发的数据都会进行缓存,而且为了防止缓存过大,通常我们都会把缓存设置一个超时时间,并且会有cache miss机制。本文,我记录一下错误的缓存机制引起的BUG。
起因
好好的一个国庆,自己完全没歇停,让我给毁了。线上一次cache miss导致缓存数据错误,便一直在查因。然后重写代码、测试、上线。emmm......
直接看代码
当然是伪代码了
cache = new cache();
data = cache.getData();
if(isempty(data)) {
data = getDataFromResource()
if(!isempty(data)) {
cache.setData(data)
}
}
看上去没错哈,一般我们处理缓存的确是用这个步骤:
读取缓存
若cache miss(超时、网络原因),从数据源读取缓存
重新设置缓存
正常来说,这样的确是没问题的。
但是,请接着往下看。
资源类大致是这样的
//上文getDataFromResource() 就是本类中读取数据
class resource{
private static connection = new Connection();
public static getConnection() {
return connection;
}
public getData() {
try{
//todo:do anythings
data = connection.get();
return data;
}catch(e){
return null;
}
}
}
而我的缓存类是基于资源类的
class cache extends resource {
}
就是说,我缓存类依赖的连接资源,也是我原始资源的来源。
事故原因
当其中某次请求发生错误的时候(比如连接不可用,网络卡顿丢包等等),资源类中的基类方法请求失败,因此返回了NULL。 可能会感觉很奇怪啊,明明我有空校验。但是,业务是复杂的,缓存的数据是从多方资源获取而来,因此,上文getDataFromResource()方法并不为空,而是有部分数据存在。
因此导致了缓存只将部分数据写入失败!!!!!!
解决方式
不要信任数据源一定是正确的,要考虑数据源可能存在不正确的方式(目前处理方式)
if(isempty(data)) {
data = getDataFromResource()
if(!isempty(data)) {
//todo:增加数据校验
if(isValid(data)) {
cache.setData(data)
}else{
//todo:发送邮件通知,告诉开发数据可能不稳定
mail.send();
//todo:抛出异常,控制器处理,本次请求失败
throw Exception();
}
}
}
或者提前计算好缓存,本次cache miss直接抛出异常,不需要计算考虑复杂的逻辑