同一次業務操作過程中,往往會出現某種操作被重復執行,邏輯上來講如果只執行一次是最理想的。這裡所指的操作特指一些IO操作,比如從數據庫中獲取登錄人的信息,也就是說如果一次請求中包含5個小邏輯,這5個小邏輯包含3次獲取用戶信息的操作,理想的情況是3次只有一次是從數據庫中加載,其余的兩次從緩存中獲取。
限於非web環境,這裡是dubbo實現的微服務。如果是web環境的話解決問題比較簡單,因為我們可以充分利用Spring Framwork中提到的三個bean生命周期的特殊來解決:
將老的價格數據遷移成新的價格數據,這裡大概是如下的步驟:
上面步驟的價格,規則,關系數據分別屬於三個業務對象,自身都具備CRUD的服務接口,這些CRUD都需要記錄操作人信息,記錄的標准就是接口傳入的操作人所持有的token,我們需要將這個token轉換成userId,userName之類的信息與價格,規則等信息一並存儲。
時序圖如下:
由於遷移價格會涉及到多個對象的操作,而操作這些具體業務對象的接口並不支持傳具體的userId,userName只支持token,所以不可避免的會在保存價格等信息時各自去根據token查詢操作人信息。實測一個價格完成一次數據遷移涉及到獲取用戶信息的次數多達20+次,效率是比較低,如何去解決呢?
由於我目前實現的微服務是無狀態的,也不是web環境,所以上面提到的那些bean的作用域功能就使用不上。
實現類似request作用域的功能,一次請求僅執行一次,其余的請求從緩存中獲取結果以提高IO操作效率。
可采用TreadLocal來當緩存,存儲頻繁讀取的數據。增加了CacheContext,獲取用戶首先從CacheContext中取,如果為空則從數據庫加載然後回寫到Treadlocal中,下一次再請求用戶信息時就可以命中緩存不需要再次從數據庫中加載,顯然效率得到了質的提升。
時序圖如下:
實現步驟如下:
@Service public class ProductContext { private static Logger logger = Logger.getLogger(CiaServiceImpl.class); private ThreadLocal<CiaUserInfo> ciaUserInfoThreadLocal=new ThreadLocal<>(); private void clearCiaUserInfo(){ this.ciaUserInfoThreadLocal.remove(); this.logger.info("清除getTokenInfo緩存成功"); } public void setCiaUserInfoToCache(CiaUserInfo ciaUserInfo){ this.clearCiaUserInfo(); this.ciaUserInfoThreadLocal.set(ciaUserInfo); this.logger.info("將getTokenInfo存儲到緩存中"); } public CiaUserInfo getCiaUserInfoFromCache(String token){ CiaUserInfo ciaUserInfo =this.ciaUserInfoThreadLocal.get(); if(null!=ciaUserInfo){ this.logger.info("從緩存中獲取到用戶信息getTokenInfo"); } return ciaUserInfo; } public void clearAll(){ this.clearCiaUserInfo(); this.logger.info("清除ProductContext的緩存成功"); } }
public CiaUserInfo getTokenInfo(String token) throws Exception { CiaUserInfo result = this.productContext.getCiaUserInfoFromCache(token); if(null!=result){ return result; } else { result=new CiaUserInfo(); } //...get user from db this.productContext.setCiaUserInfoToCache(result); return result; }
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface LocalCacheContext { /** * 是否啟動 * @return */ boolean enable() default true; }
@Aspect public class LocalCacheContextInterceptor { private Logger logger = LoggerFactory.getLogger(getClass().getName()); @Autowired private ProductContext productContext; @Pointcut("execution(* product.service.service.impl.*.*(..))") public void pointCut() { } @After("pointCut()") public void after(JoinPoint joinPoint) throws ProductServiceException { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method targetMethod = methodSignature.getMethod(); LocalCacheContext localCacheContext= targetMethod.getAnnotation(LocalCacheContext.class); if(null!=localCacheContext){ this.productContext.clearAll(); } } }
@LocalCacheContext public void migrationPrice(Long priceId) throws ProductServiceException { this.migrationPriceService.migrationPrice(priceId); }