21. SqSentinel使用陷阱

SqSentinel基于Spring AOP使用注解@SqSentinelResource的方式对请求进行限流和降级处理,   使用方式可参见 SqSentinel接入指南, 在使用过程中需慎入如下陷阱。

陷阱1: Spring AOP仅对Spring管理的bean进行增强操作。NewOrderArrivalHallRunnable是内部类,非ApplicationContext管理的类,因此即使使用@SqSentinelResource注解,限流降级功能也不会生效。eclipse 警告fallbackExecute(降级方法)未使用,也从侧面说明这一问题。

@Service
public class DriverPushService {
	
	private static final Logger LOG = LoggerFactory.getLogger(DriverPushService.class);
    
    @Autowired
    private IDriverInfoService driverInfoService;
	
	@Autowired
	private ISystemPushService systemPushService;
	
	private ThreadPoolExecutor executor = new StandardThreadExecutor(4,10,1000 * 30,TimeUnit.MILLISECONDS,1000);

	public void pushOrderArrival(int type, String orderNo, String driverIds) {
		if(driverIds == null || driverIds.trim().length() == 0) {
			return;
		}
		List<Integer> list = null;
		if(type == 2) {
			Map<String,List<Integer>> pdMap = JSON.parseObject(driverIds,new TypeReference<Map<String,List<Integer>>>(){});
			list = pdMap.entrySet().stream().flatMap(entry -> entry.getValue().stream()).collect(Collectors.toList());
			list.removeIf(driverId->driverId == -1);
		} else {
			list = Splitter.on(",").trimResults().splitToList(driverIds).stream().map(str -> Integer.parseInt(str)).collect(Collectors.toList());
		}
		executor.submit(new NewOrderArrivalHallRunnable(type, orderNo, list));
	}
	
	private class NewOrderArrivalHallRunnable extends MDCRunnable{
		private int type;
		private String orderNo;
		private List<Integer> list;
		public NewOrderArrivalHallRunnable(int type, String orderNo, List<Integer> list){
			this.type = type;
			this.orderNo = orderNo;
			this.list = list;
		}
		@Override
		@SqSentinelResource(configName = "sq-sentinel",    
    		value = "newOrderArrivalPush",
    		blockHandler = "fallbackExecute",
    		fallback = "fallbackExecute")
		public void execute() {
			list.stream().forEach(driverId -> {
				DriverDetailInfo info = driverInfoService.getDriverDetailInfo(driverId);
				Map<String,String> bizMap = new HashMap<String,String>();
				bizMap.put("type", type+"");
				bizMap.put("orderNo", orderNo);
				ObjectMapper mapper = new ObjectMapper();
		        String body = null;
		        try {
		            body = mapper.writeValueAsString(bizMap);
		        } catch (JsonProcessingException e) {
		            throw new RuntimeException(e);
		        }
		        Map<String,Object> paraMap = new HashMap<String,Object>();
		        paraMap.put("nick", null);
		        paraMap.put("msgId", UUID.randomUUID().toString().replace("-", ""));
		        paraMap.put("title", "选单大厅-新单到达");
		        paraMap.put("body", body);
				if(info == null || info.getChatUserId() == null) {
					LOG.warn("获取司机信息失败.driverId:[{}]", driverId);
				} else {
					systemPushService.sendSocket(info.getChatUserId(), paraMap);
				}
			});
			LOG.info("给司机推送新单(选单大厅)到达通知。orderNo:[{}] total:[{}]", orderNo, list.size());
		}
		
		@SuppressWarnings("unused")
		public void fallbackExecute() {
			LOG.info("选单大厅新单到达通知降级处理,停止push.");
			return;
		}
	}
}

 

陷阱2:下例的riskDriverValidateOnce使用@SqSentinelResource进行了降级增强。在riskDriverValidate和内部类RiskFilterDriverThread中调用了riskDriverValidateOnce方法,实际上aop增强(限流降级)都不会生效。 RiskFilterDriverThread#execute调用了外部类的riskDriverValidateOnce,本质上是调用RiskFilterDriverApiServiceImpl.this.riskDriverValidateOnce,并没有调用代理类的riskDriverValidateOnce方法。riskDriverValidate方法调用riskDriverValidateOnce限流降级也不会生效,解释详见陷阱3.

@Service("riskFilterDriverApiService")
public class RiskFilterDriverApiServiceImpl  implements IRiskFilterDriverApiService {

    private Logger LOGGER = LoggerFactory.getLogger(getClass());

    private static final String RISK_CHECK_URL = "/risk/driver/beforedistributevalidate";

    private static final Integer MAX_DRIVER = 99;

    private static final Integer MAX_WAIT_TIME = 3;


    @Override
    public List<RiskFilterResponseBO> riskDriverValidate(RiskFilterParamDTO riskDTO) {
        List<RiskFilterResponseBO> resList = null;
        List<Integer> driverIds = riskDTO.getDriverIdList();
        if(driverIds.size()>MAX_DRIVER){
            //如果司机数量过多分批查询
            List<RiskFilterResponseBO> resListAll = new ArrayList<>(driverIds.size());
            List<List<Integer>> driverIdList = Lists.partition(driverIds, MAX_DRIVER);
            List<Future<List<RiskFilterResponseBO>>> resFutureList = new ArrayList<>(driverIdList.size());
            for (List<Integer> driverList0 : driverIdList){
                RiskFilterParamDTO paramDTO = new RiskFilterParamDTO(
                        driverList0,
                        riskDTO.getPassengerPhone(),
                        riskDTO.getChannelsNum(),
                        riskDTO.getBookingDriverIds()
                );
                Future<List<RiskFilterResponseBO>> res = ThreadUtils.getStandardExecutorInstance().submit(new RiskFilterDriverThread(paramDTO,
                                                                                                        TraceItemDTO
                        .createByCurrentMDC()));
                resFutureList.add(res);
            }

                for (Future<List<RiskFilterResponseBO>> futrue : resFutureList) {
                    try {
                        List<RiskFilterResponseBO> res = futrue.get(MAX_WAIT_TIME,TimeUnit.SECONDS);
                        if(CollectionUtils.isNotEmpty(res)){
                            resListAll.addAll(res);
                        }
                    } catch (Exception e) {
                        LOGGER.error("调用风控接口出错",e);
                    }
                }
            if(resListAll.size()>0){
                resList = resListAll;
            }
        }else {
            resList = riskDriverValidateOnce(new RiskFilterParamDTO(
                    driverIds,
                    riskDTO.getPassengerPhone(),
                    riskDTO.getChannelsNum(),
                    riskDTO.getBookingDriverIds()
            ));
        }
        return resList;
    }


    @SqSentinelResource(configName = "sq-sentinel",
            value = "/risk/driver/beforedistributevalidate",
            blockHandler = "riskDriverValidateOnceFallback",blockHandlerClass = {RiskFilterDriverApiServiceFallBack.class},
            fallback = "riskDriverValidateOnceFallback",fallbackClass = {RiskFilterDriverApiServiceFallBack.class})
    private List<RiskFilterResponseBO> riskDriverValidateOnce(RiskFilterParamDTO riskDTO) {
        String host = UrlDicts.get(DictKeys.RISK_URL);
        String riskUrl = host + RISK_CHECK_URL;
        HashMap<String, Object> params = Maps.newHashMap();
        params.put("driverIds", riskDTO.getDriverIds());
        params.put("passengerPhone", riskDTO.getPassengerPhone());
        params.put("channelsNum", riskDTO.getChannelsNum());
        if(StringUtils.isNotBlank(riskDTO.getBookingDriverIds())) {
            params.put("bookingDriverIds", riskDTO.getBookingDriverIds());
        }
        long start = System.currentTimeMillis();
        try {
            ThreadLocalContext.setLocal(new ApiMonitor(CommonConstant.DISPATCHER, RemoteSystem.RISK, start,RISK_CHECK_URL));
            String result = HttpUtil.getIntance().post(riskUrl, params);
            BaseResponseBO<JSON> baseResponseBO = JSON.parseObject(result, BaseResponseBO.class);
            if (baseResponseBO == null || baseResponseBO.getCode() != ResponseResult.SUCCESS_CODE || baseResponseBO.getData() == null) {
                LOGGER.warn("调用风控验证司机接口失败 url:[{}], 参数:[{}], resp=[{}], 耗时:[{}]ms", RISK_CHECK_URL,
                            params,result, System.currentTimeMillis() - start);
                return null;
            }
            LOGGER.info("调用风控验证司机接口成功 url:[{}], 耗时:[{}]ms", RISK_CHECK_URL, System.currentTimeMillis() - start);
            LOGGER.info("调用风控验证司机接口成功  参数:[{}], resp=[{}] ",params,result);
            JSON data = baseResponseBO.getData();
            return JSON.parseArray(data.toJSONString(),RiskFilterResponseBO.class);
        } catch (Exception e) {
            LOGGER.error("请求获取风控抢单查询接口异常!!!, msg={}", e.getMessage(), e);
        }
        return null;
    }

    class RiskFilterDriverThread extends AbstractCallableWorker<List<RiskFilterResponseBO>> {
        private RiskFilterParamDTO riskDTO;
        private TraceItemDTO traceItemDTO;
        public RiskFilterDriverThread(RiskFilterParamDTO riskDTO,TraceItemDTO traceItemDTO){
            this.riskDTO = riskDTO;
            this.traceItemDTO = traceItemDTO;
        }

        @Override
        protected TraceItemDTO getTraceItem() {
            return traceItemDTO;
        }

        @Override
        protected List<RiskFilterResponseBO> execute() {
            return riskDriverValidateOnce(riskDTO);
        }
    }
}

陷阱3:在本例RiskFilterDriverProcessor中Autowired了riskFilterDriverApiService对象,但这里的riskFilterDriverApiService并非RiskFilterDriverApiServiceImpl 的实例对象,而是经由spring aop(@SqSentinelResource使用的切面)生成的代理对象,以采用jdk动态代理实现aop为例, riskFilterDriverApiService实际上是IRiskFilterDriverApiService的子类,内部也提供了riskDriverValidate和riskDriverValidateOnce的实现,不同的是riskDriverValidateOnce通过SqSentinelResourceAspect#invokeResourceWithSentinel进行了增强,而riskDriverValidate只是简单的重定向调用了真实RiskFilterDriverApiServiceImpl实例对象的riskDriverValidate方法。真实的RiskFilterDriverApiServiceImpl实例对象的riskDriverValidate方法又调用自身的未被加强的riskDriverValidateOnce的方法。在这一过程中,代理对象的增强版的riskDriverValidateOnce方法并未被调用。

@Service
public class RiskFilterDriverProcessor extends AbstractDriverProcessor {
    private static final String ALL_PARTNER = "all_partner";


    @Autowired
    private IRiskFilterDriverApiService riskFilterDriverApiService;

    @Override
    public void doProcessor(Accessory accessory, DriverResult result, DriverProcessorChain chain) {
            //--------------------省略----------------------
            List<RiskFilterResponseBO> resList = riskFilterDriverApiService.riskDriverValidate(new RiskFilterParamDTO(
                    driverIds,
                    carBizOrderDTO.getRiderPhone(),
                    carBizOrderDTO.getChannelsNum(),
                    //carBizOrderDTO.getBookingDriverIds()
                    StringUtils.join(carBizOrderDTO.getBookingDriverIdList(), ",")
            ));
           //---------------------省略----------------------
    }
}

 

结论: 因为 spring AOP的局限性,即使配置了@SqSentinelResource,也可能并未真正生效。正确的使用姿势是@SqSentinelResource修饰的方法必须是受spring applicationContext管理的bean的顶级方法,这里的顶级是指仅仅被外部的bean引用调用。如何确定你配置的sentinel生效了?Sentinel 除了提供了核心库,还提供了可视化的控制台,通过可视化控制台可以看到实时的qps,拒绝的qps及响应时长等信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值