当前位置: 首页 > 科技观察

四类缓存陷阱总结

时间:2023-03-19 18:44:08 科技观察

背景分布式、缓存、异步、多线程被誉为互联网发展的四大法宝。今天总结一下项目开发中经常遇到的四种缓存在实际项目中遇到的问题。JVM堆内缓存JVM堆内缓存在项目中仍然被广泛使用,因为它可以避免Memcached和Redis等集中式缓存中的网络通信失败。堆内缓存需要注意GC问题。如果我们的设计是周期性的从远程拉取数据来更新本地缓存。一定要注意两点:一是不要拉满全量,二是不要整体更换一个大物件。让我们先谈谈全拉覆盖。Fullpull会产生较大的网络开销,造成网络流量尖峰。有人说还好,我们带宽够用,访问内网也不怕。但稳定需要修炼的一件事是削峰填谷。让系统在稳定的环境中运行。不然从大缓存中拉新数据的时候突然爆数据?根据墨菲定律,任何有可能发生的事情都会发生。谨慎编程。再说说大对象整体替换的问题,会引起GC问题。伪代码如下:ListoldList=initList();publicvoidrefresh(){ListnewList=dataFromNetworkService.getAll();旧列表=新列表();for(POJOpojo:newList){oldList.add(pojo);}}如果是从网上拉取的数据和缓存中存放的数据,对象类型没有变化。产生的转换开销略小。因为例如对象POJO存在于列表中。尽管这个列表非常大,但它包含对对象的引用。实际的POJO没有改变。上面的伪代码虽然创建了一个新的List对象,但是遍历添加新对象比直接oldList=newList要笨。但是遍历过程其实并没有改变POJO对象。所以这里影响GC的只是oldList对象(不包括从网络上拉回数据的过程)。但是如果代码这样写:ListoldList=initList();publicvoidrefresh(){ListnewList=dataFromNetworkService.getAll();旧列表=新列表();for(POJO2pojo:newList){oldList.add(Beanutils.copy(newPOJO2(),pojo));}}遍历过程会把原来的所有POJO1重新创建一遍。一般这些对象都是先进入新生代堆内存,再经过几次YoungGC后进入老年代。会造成频繁的GC。对于我做过的项目来说,一般认为一天一到两次FullGC是比较合理的值。这样,比如提前知道某个时间点会有大促,就可以提前触发GC,避免高峰期FullGC的爆发。YoungGC至少每5分钟触发一次,甚至更长时间被认为是正常的。这样就可以通过控制避免尖峰等场景。JVM堆外缓存的内存回收原理利用了Java的虚引用。这种设计可以避免JVM的GC问题,但是如果处理不当,可能会造成更严重的后果:整机内存满,机器可能会挂掉。其实在一般企业生产环境挂掉是没问题的,因为容灾通常会有冗余的机器。但是更常见的情况是机器忙于swap内存交换,机器是活着的但是响应很慢。属于半死不活。网上没遇到过这个问题,我同事以前在超大厂的时候遇到过。有同学说我要严格计算内存,做好监控。需要依靠人为因素进行应急处理。人是所有稳定性中最不可靠的。因为当人清醒并且手头的东西很少时,通常不会出现问题。这是让事情变得更糟的存在。比如大促期间,流量会增加,线程数也会增加。每个线程都会申请线程栈资源,系统会处理IO。这时候系统会申请更多的buffers/cachedmemory。在Linux的buffers/cachedLinux系统上运行top命令或free命令,可以看到buffers和cached相关的数据。需要注意的是,我们平时看到的监控数据的freememory百分比并不是下面所示的free/total,而是(free+buffers+cached)/total。缓冲区通常用作Linux系统中块存储的IO缓存。所谓块存储,可以简单理解为直接将数据写入裸盘。Cached一般用于文件系统的IO缓存。比如pagecache的内存分页功能。看不懂也没关系,因为其实两者经常一起使用。例如,在与磁盘交换数据和执行网络通信时使用它。Buffers和Cached其实是操作系统的系统进程使用的,但是如果用户进程需要,可以很快释放。所以它通常被计入剩余可用内存。但是这个也要注意。例如,在一个IO密集型系统中,如果buffers/cached被大量占用,IO速度就会降低,从而降低系统吞吐量。请求到达应用程序甚至可能需要几秒钟,从而导致请求超时。集中式缓存Redis缓存其实也有一个本地代理,可以在本地机器上缓存一些活跃的数据,本地机器在取不到数据的时候不需要跨网络通信。但是因为Redis本质上是一个key-value结构。如果需要根据通配符取全量数据,如果网络出现故障,可能会影响数据的完整性。但是Redis缓存最让人担心的是不规范的使用方式。例如,存储一个大值。具体由此给网络和存储带来的问题就不详述了。想象一个堵塞的马桶。总结贝尔实验室的面向对象编程专家TomCargill说:前90%的开发工作会用掉你前90%的开发时间,剩下的10%的开发工作会用完其他时间90%的开发时间。时间。我了解到,剩下的10%由于超出了原有的知识储备,占用了90%的时间,需要补习,甚至用锤子找钉子。所以或者你也可以这样做:每周继续投入5%的学习时间,10%的思考时间,用100%的时间去完成100%的开发。