作者:京東零售 常文標
商卡聚合服務是一個小巧的rpc應用,功能是統一查詢商品的促銷、自營包郵、價格信息、區域庫存、區域可配送等等利益點或其他信息。本文重點分享商卡聚合服務的代碼設計,包括合理的Sirector線程調度(cpu使用率低),和可維護性的設計。 簡版代碼示例如下: [email protected]:changwenbiao/demosoa.git
代碼使用sirector-core組件並行調度(使用線程並行執行EventHandler的onEvent方法)請求上游rpc接口獲取各利益點或其他商品信息。因爲請求上游有些通用處理邏輯比如ump監控、調用開關等,所以抽象出一個通用的EventHandler名爲AbstractBenefitHandler。具體調用利益點的實現類只需繼承AbstractBenefitHandler並重寫其抽象方法。
接下來重點講代碼如何節省cpu使用率和易於維護的設計。
1.如何節省cpu
AbstractBenefitHandler提供isSwitchOn方法,用於決定是否使用sirector組件分配線程執行調度EventHandler。相對於分配線程執行全部的EventHandler,判斷是否需要調用才分配線程調用的方式可有效減少線程調度從而減少cpu使用率。isSwitchOn方法中可加入cms控制開關邏輯比如使用ducc開關,也可加入根據用戶參數判斷開關的邏輯,比如查詢區域庫存需要四級地址,若用戶不傳四級地址則關閉調用EventHandler(請求上游rpc)。代碼實現如下:其中ducc開關在父類中的isSwitchOn中實現,sirector.begin方法接受可變參數列表參數,可將List<AbstractBenefitHandler>轉化爲AbstractBenefitHandler[]作爲入參。
@Override
public boolean isSwitchOn() {
boolean superSwitchOn = super.isSwitchOn();
if (!superSwitchOn) {
return false;
} else {
//正常爲四級地址,如果少於四級則關閉調用
String area = seckillBenefitRequest.getSeckillParam().getArea();
return !StringUtils.isBlank(area) && area.contains("_") && area.split("_").length >= 4;
}
}
List<String> handlerNames = Lists.newArrayList("areaStockHandler", "partitionProductsHandler");
List<AbstractBenefitHandler> handlerList = handlerNames.stream()
.map(handlerName -> applicationContext.getBean(handlerName, AbstractBenefitHandler.class).setBenefitRequestAndBizName(request, "demoAppName"))
.filter(AbstractBenefitHandler::isSwitchOn).collect(Collectors.toList());
Sirector<MiaoShaEvent> sirector = new Sirector<MiaoShaEvent>(bigSeckillEventProcessThreadPool);
AbstractBenefitHandler[] eventHandlersArr = new AbstractBenefitHandler[handlerList.size()];
handlerList.toArray(eventHandlersArr);
sirector.begin(eventHandlersArr);
sirector.ready();
sirector.publish(new MiaoShaEvent(), 500); //這裏開始使用線程並行執行EventHandler的onEvent方法
2. 如何容易維護
減少一些模版代碼(如ump監控):所有Handler的實現類的ump監控都寫在父類中的onEvent中,父類的onEvent調用子類實現的onEvent0(處理具體利益點rpc請求處理)方法。
短小代碼的實現:AbstractBenefitHandler提供fillResponseInfo方法以向“ResponseVO”中填數據,具體填利益點數據的代碼則由相應handler實現類處理。因此各個handler填充利益點“ResponseVO”的代碼都是短小的,避免了代碼寫在一起的長代碼。單個handler填充利益點數據和批量統一填充利益點數據代碼分別如下:
@Override
public void fillResponseInfo(List<BftInfo> bftInfoList) {
if (MapUtils.isNotEmpty(areaStockMap)) {
for (BftInfo result : bftInfoList) {
String skuId = result.getBaseInfo().getSkuId();
if (areaStockMap.containsKey(skuId)) {
result.getCommonInfo().setAreaStock(areaStockMap.get(skuId));
}
}
}
}
handlerList.forEach(h -> h.fillResponseInfo(bftInfoList));
減少一些硬編碼:handler實現類配置爲原型模式(scope="prototype")的spring bean,通過applicationContext.getBean方法統一獲取,避免一些創建(new關鍵字)具體實現類的代碼,若新增利益點調用只需編碼AbstractBenefitHandler實現類並配置爲spring bean即可。批量獲取handler代碼如下
List<String> handlerNames = Lists.newArrayList("areaStockHandler", "partitionProductsHandler");
List<AbstractBenefitHandler> handlerList = handlerNames.stream()
.map(handlerName -> applicationContext.getBean(handlerName, AbstractBenefitHandler.class).setBenefitRequestAndBizName(request, "demoAppName"))
.filter(AbstractBenefitHandler::isSwitchOn).collect(Collectors.toList());