背景
前些天公司服务器数据库访问量偏高,运维人员收到告警推送,安排我团队小伙伴排查原因.
我们发现原来系统定期会跑一个回归测试,该测运行的任务较多,每处理一条任务都会到数据库中取相关数据,高速地回归测试也带来了高频率的数据库读取.
解决方案1
我们认为每个任务要取的数据大相径庭,因此我们考虑对这个过程进行修改,加入MemoryCache把数据库中读取到的数据进行缓存.
整个修改非常简单,相信对常年混迹在博客园中的各位大佬来说小菜一碟,因此小弟不再叙述添加缓存的步骤细节.
从缓存的添加,代码提交,Teamcity 编译通过,到测试环境,QA环境的安装无比流畅,一切显得如手到擒来.
嗯,优秀是一种习惯, 没有一点办法.
人生如戏,当我们还沉浸在"我加的Cache不可能又BUG"的自信中时,QA传来噩耗,回归测试大量未通过 ....
故障排查
之前习惯了使用Redis缓存,因此,常识告诉我们 --- 在数据库中数据没有改动的前提下,加了缓存后读取的数据的效果和从数据库中读取的效果是一模一样的.
除非 ,,, 除非 这个常识是错误的....
因此我们加了日志,对写入缓存前后读取出来的数据进行了对比,结果出人意料.
该死 MemoryCache 毁我老脸,丢我精度,拿命来!!!!!
从日志中看到,第一行是从数据库中读取的结果,第二行是从cache中读取的,前两条数据完全一致,到了第三条,第四条,第五条,仔细观察发现,在小数点后面,居然有些小数点后比较微小的变化,不管变化的大小但数据确实发生改变了,所以MemoryCache会影响数据精度??这样会改变数据精度的MemoryCache又有何用??
机智的我,似乎早已看穿了一切,这肯定不是MenoryCache的锅!!!
不一样的MemoryCache
我从https://referencesource.microsoft.com 中扒出了MemoryCache的源码一探究竟.
定位到MemoryCache中的AddOrGetExisting方法,我们看到,其实我们把数据存储到该缓存的过程本质是把该对象存到一个名为_entries的 Hashtable 中,同样,取数据也是通过Key到该Hashtable中取出来,整个过程并没有对该对象进行序列化反序列等,也没有对该对象进行clone操作.这就意味着我们之前存入的,和后面取出的(不管我们从MemoryCache中取数据取多少次),永远只取出同一个对象.
这一点,和我之前使用的RedisCache是有很大区别的.我们在Redis中存入数据,是把对象序列化后存到Redis中,取数据是把Redis中的字节数据反序列成对象,意味着前一次存入的,和后一次取出的,已经不是同一个对象了,因此Redis中的数据是安全的.
猜想
我做出了一个大胆的猜想,之前从MemoryCache中取出来的数据之所以变化了,可能是取出对象后,复杂的处理过程中对该对象进行了什么修改操作,所以后期,再次从数据库中读取数据,读出来的已经已经不是最初存入的数据,而是前一次修改之后的数据.带着这个猜想,我对代码进行了修改.
解决方案2
从MenoryCache中取到数据后对结果进行clone(),这样即使程序对取出来的结果进行了修改也不会影响Cache中的数据了.
又是一次提心掉到的提交,编译,安装后, 回归测试顺利通过.
感觉人生到达了高潮 -_-
把踩得坑分享出来,希望后面的小伙伴引以为鉴,