1 屈服于现实的磁盘
MQ都使用磁盘来存储消息。这样服务器下电也不会丢数据。绝大多数用于生产系统的服务器,都会使用多块磁盘组成磁盘阵列,这样即使其中的一块异常,也可把数据从其他磁盘中恢复。
另外磁盘也便宜,就可用较低成本,存储海量消息。所以,不仅仅是MQ,几乎所有存储系统的数据,都需保存到磁盘。
但磁盘读写很慢。SSD可读写几千次/s,若程序在处理业务请求时直接读写磁盘,假设处理每次请求需要读写3~5次,即使每次请求数据量不大,程序最多也就能处理1000次/s左右请求。
而内存随机读写速度是磁盘10万倍!内存作为缓存来加速程序访问速度,是所有高性能系统都会采用的方案。
缓存思想简单,就是把低速存储的数据,复制一份放到高速存储,加速数据访问。使用也简单
在做业务系统时,在一些执行较慢方法上加个@Cacheable
2 缓存最佳实践
采用@Cacheable注解缓存的命中率如何?
怎样才能提高缓存命中率?
缓存是否总能返回最新的数据?
如果缓存返回了过期的数据该怎么办?
只读缓存 VS 读写缓存
唯一区别:更新数据时,是否经过缓存。
Kafka使用的PageCache,是个典型的读写缓存。os会利用系统空闲物理内存给文件读写做缓存,这缓存叫做PageCache。应用程序在写文件时,os会先把数据写入PageCache,成功写进后,对于用户代码,写入就结束了。
然后,os再异步更新数据到磁盘。应用程序在读文件时,os是先尝试从PageCache查数据,找到就直接返回,找不到会触发一个缺页中断,然后os把数据从文件读取到PageCache,再返回给应用程序。
数据写到PageCache后,并不是同时写到磁盘,期间有个延迟。
os可保证即使程序异常退出,os也会把这部分数据同步到磁盘。但若服务器都突然掉下电,这部分数据就丢了。
读写缓存的设计,本身就不可靠,牺牲数据一致性换取性能。当然,程序可以调用sync等系统调用,强制操作系统立即把缓存数据同步到磁盘文件中去,但是该同步过程很慢,也失去了缓存的意义。
写缓存实现非常复杂。应用程序不停更新PageCache数据,os需记录哪些数据变化,同时还要在另外一个线程,把缓存中变化的数据更新到磁盘。
在提供并发读写同时异步更新数据,这过程要保证数据一致性,且有非常好性能,可为强人锁男。
所以不推荐使用读写缓存。
那为什Kafka可使用PageCache提升性能?
这由MQ特点决定。
MQ读写比例大致1:1,因大部分MQ都是一收一发。这种读写比例,只读缓存既无法给写加速,读加速也有限,并不能提升多少性能。
Kafka并不是只靠磁盘保证数据可靠性,它更依赖在不同节点上的多副本保证数据可靠性,这样即使某服务器掉电丢失一部分文件内容,也可从其他节点找到正确数据,不会丢消息。
而且PageCache读写缓存是os实现,Kafka只要按照正确姿势使用即可,不涉及实现复杂度问题。所以,Kafka其实在设计上,充分利用PageCache读写缓存的优势,且规避了PageCache一些劣势,达到很好效果。
和Kafka一样,大部分其他MQ,也会采用读写缓存加速消息写入,只是实现方式不同。
不同于MQ,大部分业务类程序,读写比都是严重不均衡,一般读频率远高于写数,一般都几倍到几十倍。使用只读缓存来加速系统才是明智选择。
设计只读缓存又该考虑哪些问题呢?
维护缓存数据时效性
对只读缓存,缓存中数据源只有一个途径:磁盘。当数据需更新时,磁盘数据和缓存副本都需更新。在分布式系统中,除非是使用事务(性能差)或者一些分布式一致性算法(复杂)保证数据一致性。否则,由于节点宕机、网络传输故障等,是无法保证缓存中数据和磁盘中的数据完全一致的。
若出现数据不一致,数据一定是以磁盘上那份拷贝为准的。
需解决问题:尽量让缓存数据与磁盘数据保持同步。
何时更新缓存数据
在更新磁盘数据同时,更新下缓存数据不就行?想法没任何问题,缓存中数据会一直保持最新。但在并发环境,实现起来不太容易