十年前,我们是一个内部应用,用户很少,数据也很少。tomcat一天处理不了很多请求,无聊的时候只能陪我聊天。没办法,因为整个系统只有我们两个人:是的,我就是大名鼎鼎的MySQL,而我和Tomcat在不同的机器上。一般来说,每次通信都是一次网络请求。这样的情况持续了三年,就在我们两个快没话说的时候,人类终于派来了一个新家伙:缓存。从外面看,这个缓存只是一个Map,它存储了(key,value)之类的东西。从里面看,真的是一个Map,一个叫张胖子的人写的线程安全的Map,可以设置过期时间。我和Tomcat都有点看不起他,觉得他实在是太简陋了,甚至很难成为一个独立的组件。更让Tomcat不爽的是,这个单纯的家伙居然和自己共享了JVM进程。渐渐地,事情发生了变化,张大发改变了程序逻辑:以前,用户的请求是发给Tomcat的,如果需要访问数据库中的数据,Tomcat会直接把SQL语句丢给我执行。现在,先去那个Map,不行,去缓存里查,看有没有相关的数据,有就直接返回,根本不用我处理;缓存也被填满,下次就不用访问数据库了。tomcat整天和缓存打交道,聊天如火如荼。观察了几天,我终于明白,这小子抛弃了我的好朋友。tomcat得意的对我说:“这个缓存和我在同一个进程,访问起来很快,数据马上就可以返回,怎么会像你的mysql,执行慢半天?!”说完又做了一个总结:In-processcalls是可以的。其实我比谁都清楚缓存的本质。我内部有一个缓存,就是为了避免频繁访问硬盘。每个人都使用程序的局部性原则。有什么好神秘的?!我耐心冬眠,伺机而动,准备一举干掉这只懵懂的地图。从进程内到进程外几个月后,张胖子升级了系统架构。为了应对高并发访问,他使用了一个nginx来做负载均衡,分发用户请求。进程内缓存很多,我们的系统变成了这样:一看就明白我的机会来了:这些缓存之间很容易出现不一致。例如:用户的请求在JVM1中处理,MySQL更新,JVM1中的相关缓存也被更新或删除,但是JVM2和JVM3中缓存的数据还是旧的。不出所料,数据不一致的问题很严重,用户投诉频发,缓存小子快死了!但是缓存还是想死。他说:“可以这样做。如果一个JVM中的缓存发生变化,其他JVM会得到通知。”但是通知总是会有延迟。如果JVM1还没有来得及通知JVM2和JVM3,而用户的请求已经在这两台机器上处理过了,数据不一致的情况依然存在。特别是各个JVM需要来回交互,缓存更新你要通知我,我再通知你,特别麻烦。Tomcat想出了一个坏主意:“不要让缓存相互更新,让缓存定期从MySQL更新!”但是由于是定时更新的,缓存中的数据和我的在一定时间内还是会出现不一致的情况。这几乎是一个无法解决的问题,除非数据变化非常少。最后,张胖子如我所愿删除了进程内缓存!本来打算赶上Tomcat的(好多Tomcat!),没想到第二天他来了个新人:Redis,或者缓存!与介于两者之间的粗糙Map相比,Redis要强大得多。这个cache自己独占一台机器,让几个tomcat共享访问。换句话说,缓存从进程内移动到进程外!我对Redis说:“你小子也需要网络才能访问,跟我一样,有存在的必要吗?”Redis说:“当然,虽然都是网络访问,但是我这边的数据都在内存中,面试还是比你快。”我承认他是对的。数据不一致那天晚上,访问量突然变得非常大,是平时的一百倍,不,是一千倍。Redis显示,这是张胖子在做压力测试。压测下来,一地鸡毛。清点了一下,发现redis和我的数据不一致。Redis傻眼了,这是怎么回事?数据不一致,人家一定要以我的MySQL数据库数据为准。Tomcat提示Redis:“我猜是高并发导致的,我们看看如何更新数据。”Redis说:“很简单,先更新MySQL,再更新我的数据。”Tomcat说:“这是一个两步操作。如果两个线程都在这样做,就会出问题!”比如MySQL的值为100,现在线程1想把它改成200,线程2想改成300。”Redis说:“它好像这里有很大的漏洞,怎么办?””看着两人一脸的无奈,我不禁道:“不容易啊。当需要更新数据时,不更新缓存,只删除缓存中的相关数据即可。”雷迪斯道:“你这是在报政府的私仇。你删除了我的数据。下次用户访问时,它不会在那里。我还得问你,对吧?”我说:“你得问我,但是可以解决你的问题,两个线程同时写,不会出现数据库和数据库不一致的情况。缓存。”而且,这其实也不是我们能管的。等着看张胖子是怎么做的吧。”第二天,张胖子按照我说的逻辑修改了程序,他称之为:CacheAsidePattern。虽然一直想干掉缓存,但是这几天的体验后来深刻地教会了我,缓存还是必不可少的……缓存穿透、击穿、雪崩……)
