记录一次还算优雅的代码设计

时间:2023-02-09 12:26:03

作者:京东零售 常文标

商卡聚合服务是一个小巧的rpc应用,功能是统一查询商品的促销、自营包邮、价格信息、区域库存、区域可配送等等利益点或其他信息。本文重点分享商卡聚合服务的代码设计,包括合理的Sirector线程调度(cpu使用率低),和可维护性的设计。 简版代码示例如下: git@github.com: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());