基于AOP的优惠券发送异常哨兵监控

勿忘初心2018-09-29 10:52


本文来自网易云社区

作者:王贝


 最近总是发现支付发红包优惠券发完的情况,但是发现的比较迟缓,于是乎,想加一个哨兵监控,统计了一下,组内不少需求都有发送优惠券的行为,也是经常遇到发送异常的情况,所以,想针对优惠券发送封装一个公共的方法进行调用,下面是封装的公共方法:

public CouponResponse<BatchDispatchResult> sendCoupon(List<String> reedcodeList,String accountId,AntiInfoVO antiInfoVO){
//发券dubbo接口
CouponResponse<BatchDispatchResult> couponResponseVo = couponComposeFacade.batchDispatchCouponByRedeemCodeList(reedcodeList, accountId, antiInfoVO, 1);
//哨兵监控
if(couponResponseVo != null && couponResponseVo.isNotSuccess()){
StatsTool.onIntegerKey1Value1Stats("couponSendFail", 1);
}
return couponResponseVo;
}

方法很简单,就是封装一下,加个监控,但是发现代码中调用发券dubbo接口的地方很多,要改不少地方,于是想起来面向切面编程AOP,这里先简单介绍一下AOP(引用自http://www.cnblogs.com/hongwz/p/5764917.html)。

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和接口调用监控也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
于是乎,说干就干,很快代码出炉
@Aspect
public class CouponWarnAspect {

/**
* 单发
*/
@Pointcut(value = "execution(com.netease.kaola.compose.coupon.vo.CouponResponse<java.util.List<com.netease.kaola.compose.coupon.vo.CouponVO>>" +
" com.netease.kaola.compose.coupon.provider.CouponComposeFacade.*(..))")
public void couponWarnPoint(){}

/**
* 批量发送
*/
@Pointcut(value = "execution(com.netease.kaola.compose.coupon.vo.CouponResponse<com.netease.kaola.compose.coupon.vo.BatchDispatchResult>" +
" com.netease.kaola.compose.coupon.provider.CouponComposeFacade.*(..))")
public void couponBatchWarnPoint(){}

@Around(value = "couponWarnPoint()")
public CouponResponse<List<CouponVO>> couponWarn(ProceedingJoinPoint pjp) throws Throwable {
try {
CouponResponse<List<CouponVO>> couponResponse = (CouponResponse) pjp.proceed();
if(couponResponse != null && couponResponse.isNotSuccess()){
StatsTool.onIntegerKey1Value1Stats("couponSendFail",1);
}
return couponResponse;
} catch (Throwable throwable) {
StatsTool.onIntegerKey1Value1Stats("couponSendError",1);
throw throwable;
}
}

@Around(value = "couponBatchWarnPoint()")
public CouponResponse<BatchDispatchResult> couponBatchWarn(ProceedingJoinPoint pjp) throws Throwable {
try {
CouponResponse<BatchDispatchResult> couponResponse = (CouponResponse) pjp.proceed();
if(couponResponse != null && couponResponse.isNotSuccess()){
StatsTool.onIntegerKey1Value1Stats("couponBatchSendFail",1);
}
return couponResponse;
} catch (Throwable throwable) {
StatsTool.onIntegerKey1Value1Stats("couponBatchSendError",1);
throw throwable;
}
}
}
多么干净利落,这样子就不用通过动原来逻辑来添加哨兵监控了。首先定义pointcut切入点,切入点就是指定一个Adivce将被引发的一系列连接点的集合,这里根据正则匹配定义了两个切入点,第一个切入点匹配的是 com.netease.kaola.compose.coupon.provider.CouponComposeFacade里返回类型是 com.netease.kaola.compose.coupon.vo.CouponResponse<java.util.List<com.netease.kaola.compose.coupon.vo.CouponVO>>的方法,从目前系统dubbo调用看,匹配的是单个发券的方法,第二个切入点匹配的是 com.netease.kaola.compose.coupon.provider.CouponComposeFacade里返回类型是com.netease.kaola.compose.coupon.vo.CouponResponse<com.netease.kaola.compose.coupon.vo.BatchDispatchResult>的方法,从目前系统dubbo调用看,匹配的是批量发券的方法。
切入点定义好之后,开始在Advice中进行引用,当然,也可以直接在Advice中通过正则匹配指定切入点。Advice( 通知),包括前置(before)、后置(AfterReturning)、异常(AfterThrowing)、环绕(around)等这几类常用的。我们这里用到了环绕,环绕就是对调用的目标方法进行包裹,可以在调用前后做一些我们需要的操作,比如上述,就是在调用目标方法后通过判断返回码和异常来添加哨兵监控。
最后通过spring配置和aspect注解创建代理类:
	<aop:aspectj-autoproxy proxy-target-/>

<bean id="couponWarnAspect" ></bean>

测试一下,优惠券发送失败的情况完美的呈现在哨兵上了:

通过这次,大概总结了一下,编程不能按部就班,多思考一下,就会发现有很多捷径可走,从而高效率的把系统设计的更具健壮性和可维护性,技术上也得到突破和提升,何乐而不为。

网易云免费体验馆,0成本体验20+款云产品! 

更多网易研发、产品、运营经验分享请访问网易云社区