神奇的閃電緩存,帶系統飛
本文轉載自微信公眾號「小姐姐味道」,作者小姐姐味道 。轉載本文請聯系小姐姐味道公眾號。
緩存,在高并發的應用中,用的那是相當多。為什么?就因為I/O實在是慢!為了解決不同組件之間的速度差,大家都寄希望于加入一個中間層,期待產生一些魔幻的事。
就拿Redis來說,火的就一塌糊涂,但中間會產生很多數據同步和數據一致性問題。有的牛x公司嫌煩,同時有錢,干脆干掉緩存后面的DB,直接把所有的數據放在了緩存上。哦不,這時候緩存已經不叫做緩存,應該叫做快存,因為它最終是要通過rdb落地的。
看到這里,先不要懷疑事實的正確性。有些公司的業務,確實不需要什么關系型數據庫,一個redis就能玩得轉。
閃電緩存場景
那閃電緩存又是何方神圣?實在不好意思, 這個名詞,是xjjdog自創的。
它用在下面的場景之中。
- 一份數據,通過耗時的請求獲取之后,會在極短的時間內,再次被用到。
- 業務對數據的一致性要求不是特別強烈,但也不是無底線忍受。
- 內存的空間有限,不適合把大量數據放在內存中。
- 數據的使用跨方法、跨代碼塊、甚至跨線程,只在時間概念上有關聯
這個時候,我們就可以將數據緩存一小段時間,盡量在下次的使用的時候,從這個時間極短的緩存中獲取。
srping-data-jpa背后的Hibernate一級緩存,在同一session下的數據被自動緩存,可以變相的看作是閃電緩存的一種實現。不過人家叫一級緩存,顯得更高大上一些,應用也更局限一些。
Java有多種緩存數據的方法,也有不同的生命周期。你可以想一下Session中的數據該如何存取,也可以想一下Java框架中各種各樣的Context,都是為了共享數據。
實現方式
閃電緩存,在Java中其實是有多種方式的,也有各種各樣的優缺點。
ThreadLocal
第一種方式,就是ThreadLocal。拿最常用的Spring來說,它事務管理的傳播機制,就是使用ThreadLocal實現的。
我可以在數據第一次被獲取的時候,使用set方法給它設置一個值。然后在最后一個操作用的使用,把它remove掉,變相的實現請求級別的閃電緩存。(為什么要remove?因為在線程池中可能會有復用的問題)
但由于ThreadLocal是線程私有的,所以它不能夠跨線程。上面Spring的事務傳播機制是不能夠跨線程的,我們的閃電緩存也是不能夠跨線程的。
這就決定了ThreadLocal的應用場景有限。但它還有其他兩個硬傷:
ThreadLocal的生命周期不好管理,何時生成,何時銷毀,都是問題
ThreadLocal使用自定義的ThreadLocalMap。它雖然叫Map,但卻沒有實現Map的接口,它使用開放尋址(遇到沖突,依次查找,直到空閑位置)的方法,這種方式是非常低效的
綜上三點,ThreadLocal這個方案其實并不太好。
普通Cache加過期時間
我們可以變換一下思路,使用普通的Cache,然后給它一個超短的緩存時間,那么就可以變相的實現閃電緩存的功能。
實現也是非常簡單的。比如,下面幾行代碼,就是一個對對象緩存了3秒的例子。
- LoadingCache<String, String> lc = CacheBuilder
- .newBuilder()
- .expireAfterWrite(3,TimeUnit.SECONDS)
- .build(new CacheLoader<String, String>() {
- @Override
- public String load(String key) throws Exception {
- return slowMethod(key);
- }});
在這3秒之間,系統中所有用到這個數據的請求,都可以達到復用的效果。這對于并發量非常高的應用來說,減少的請求量可能是數量級的。
我曾經就做過一個對用戶基本信息的優化,把對用戶服務的請求量從8w/s,降低到1000/s,一度讓負責服務的同學以為上游業務當機了。
End
技術通常都是工具,只有真正用到業務場景中,才有它的價值。閃電緩存這個概念本身沒有什么神奇的,它的最優實現方式,竟然是普通的Cache加極短的過期時間。
技術本身是件非常簡單的事情,但想到它應用的場景,卻是比較難的。事實上,我已經把這個概念做到了我的ppt上,展現的時候大家比較迷惑,以為是什么高大上的東西,我自熱也不好意思戳破這層窗戶紙,就讓它繼續神秘下去吧。
如今xjjdog告訴你了,要保密哦。
作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高并發世界,給你不一樣的味道。我的個人微信xjjdog0,歡迎添加好友,進一步交流。