記錄一次還算優雅的程式碼設計

語言: CN / TW / HK

作者:京東零售 常文標

商卡聚合服務是一個小巧的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());