Spring緩存穿透問題修復

Spring緩存穿透問題修復

來自專欄網易雲社區9 人贊了文章

本文來自網易雲社區。

本劇情純屬真實,猶如雷同實乃緣分。

發生

事情的發生在某天早上,天氣怎樣反正是忘了,只記得當時監控平台大量的資料庫錯誤報警。 作為後端開發,當看到日誌中大量的db連接獲取失敗,心情是複雜的。

看了下配置和實際連接數,竟然。。。沒滿。恩,可能是突發流量。然而沒多久,一大波報警又襲來,感覺事情沒那麼簡單。

常規措施無果,連續數次如此,看日誌發現時間有點奇怪,都是間隔5分鐘, 難道。。。緩存失效了? 看業務代碼和緩存配置,很有可能。

業務代碼表示

@Cacheable(value = "item_volume", key = "item_ + #gid", unless = "#result == null") public Item queryiItem(long gid) { Optional<Item> optional = itemService.getItem(null, gid); return optional.orNull(); }

註: cache的實現用的是spring->

初步分析

沒錯,換配置item_volume過期時間5分鐘,也就是說,當緩存過期後,此時如果有大量請求,那麼這些請求都會因為緩存失效而請求資料庫。 看起來情形是這樣的:

如果假設成立,那就是spring在處理緩存的時候,如果沒有命中,直接穿透執行實際操作(db查詢),也就是說,中間是不加鎖的。

這樣就解釋通了,但這是bug嗎,還是spring認為是個feature, 這是個問題。

發展

Talk is cheap, show me the code. --linux之爹

關鍵是,code在哪。又得上套路了:套路: 既然是AOP,找找Advice。 最直接能想到,就是在spring中找所有Advice介面的繼承樹,然而數量太多,逐個尋找驗證實在是耗時。

熟悉spring事務的同學應該能想到@Transactional的Advice是TransactionInterceptor,那麼cache是否對應對一個CacheInterceptor呢。一看,還真有,那就好辦了,而且看起就是要找的。

修改代碼

順著CacheInterceptor的invoke方法,定位到CacheAspectSupport.execute,看代碼實現,確實沒加鎖,那就加個鎖唄:

private Lock lock = new ReentrantLock(); //execute中部分代碼 lock.lock(); try { result = findCachedItem(contexts.get(CacheableOperation.class)); if (result == null) { result = new SimpleValueWrapper(invokeOperation(invoker)); } collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests); for (CachePutRequest cachePutRequest : cachePutRequests) { cachePutRequest.apply(result.get()); } processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get()); } finally { lock.unlock(); }

其中,lock相關為新加部分。

高潮

代碼是改好了,怎麼生效呢。還得回頭看看CacheInterceptor是如何注入的,也不難找到:

那就寫個類 MyCacheAspectSupport.java.txt 來代替CacheInterceptor,然後注入。這裡又會用到一些 套路:bean覆蓋套路:beanname規則 等。

方法1:由於ProxyCachingConfiguration沒有指定Advice的name,那就用默認的:

<bean id="errorHandler" class="org.springframework.cache.interceptor.SimpleCacheErrorHandler"/> <bean name="org.springframework.cache.interceptor.CacheInterceptor#0" class="org.springframework.cache.interceptor.MyCacheAspectSupport" p:errorHandler-ref="errorHandler" p:cacheManager-ref="cacheManager"/>

驗證下,可以工作,然而總覺得哪裡不對,恩,如果有多個bean。。。

方法2: 注意到ProxyCachingConfiguration中Advisor的name了嗎,那就定義Advisor:

<bean id="errorHandler" class="org.springframework.cache.interceptor.SimpleCacheErrorHandler"/> <bean name="myCacheAdvice" class="org.springframework.cache.interceptor.MyCacheAspectSupport" p:errorHandler-ref="errorHandler" p:cacheManager-ref="cacheManager"/> <bean id="annotationCacheOperationSource" class="org.springframework.cache.annotation.AnnotationCacheOperationSource"/> <bean name="org.springframework.cache.config.internalCacheAdvisor" class="org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor" p:adviceBeanName="myCacheAdvice" p:cacheOperationSource-ref="annotationCacheOperationSource" />

驗證下,可以工作。其實還可以有3:

方法3:實現BeanPostProcessor介面

@Autowired private MyCacheAspectSupport mycacheAdvice; @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (CacheInterceptor.class.isAssignableFrom(bean.getClass())) { return mycacheAdvice; } return bean; }

驗證下,可以工作。

結尾

好了,問題解決,測了下性能也沒太大下降(<1%,場景不同,僅供參考),終於又可以愉快的使用Cacheable等註解了。

spring和相關衍生擁有相當大的代碼量,好在有很多套路都是通用的,利用這些套路能讓我們解決問題事半功倍。

註: 文中spring版本為4.2.6.RELEASE

本文來自網易雲社區,經作者王大喜授權發布。

原文:Spring緩存穿透問題修復

了解 網易雲 :

網易雲官網:https://www.163yun.com

網易雲社區:sq.163yun.com/blog

新用戶大禮包:https://www.163yun.com/gift

更多網易研發、產品、運營經驗分享請訪問網易雲社區。


推薦閱讀:

New faces for Spring Festival Gala
Struts2.1.6與Spring2.5.6框架整合
spring(構造函數注入)
SpringAOP的增強順序
Spring配置DBCP連接池

TAG:Spring | 緩存 | 網易雲 |