一、供应商重试逻辑编写
在整个供应商对接模块中我们需要完成以下相关逻辑:
1.网络故障/充值失败重试,需要添加一个重试任务
2.重试次数到达阈值后停止供应商对接
3.余额或押金不足情况下的失败轮转
4.供应商异步回调,订单状态修改
在和供应商做接口对接的过程中,我们会产生一些延迟任务,比如说充值失败或者网络故障后,要添加一个一分钟后要执行的重试的任务。说白了就是要对接延迟任务服务的相关的接口,比如说要去调用任务的添加,任务的消费这些接口。
二、重试逻辑编写
下面我们来进行充值失败重试逻辑的编写:
2.1 添加重试任务方法实现
步骤1:在chongba_recharge_supplier模块中的com.chongba.supplier.inf 包中新增供应商相关任务接口:SupplierTask,目的是为了将对接逻辑和重试逻辑分离
public interface SupplierTask { /** * 添加重试任务 * @param rechargeRequest */ public void addRetryTask(RechargeRequest rechargeRequest); /** * 重试 拉取/消费重试任务 */ public void retryRecharge(); }
代码解释1:添加重试任务方法:addRetryTask(RechargeRequest rechargeRequest)参数为什么传RechargeRequest?
任务对象Task中有一个参数parameter,存储实际的任务数据,后期消费时会提取任务中的parameter参数进行实际的业务操作,对于重试任务,任务消费后要重新去对接供应商,需要再次调用SupplierServiceImpl类中的对接方法:recharge(RechargeRequest rechargeRequest),该方法参数需要RechargeRequest ,所以我们添加重试任务的时候方法也传参:RechargeRequest
步骤2:在com.chongba.supplier.service包下创建供应商任务接口实现类:SupplierTaskImpl,添加重试任务需要调用延迟任务接口,因此需要注入延迟任务feign接口:TaskServiceClient
@Slf4j @Service public class SupplierTaskImpl implements SupplierTask { @Autowired private TaskServiceClient taskServiceClient; @Override public void addRetryTask(RechargeRequest rechargeRequest) { } @Override public void retryRecharge() { } }
步骤3:完成添加重试任务方法逻辑,延迟任务系统添加任务需要传递的是任务对象:Task,而addRetryTask方法接收的是一个RechargeRequest方法,因此需要转换,逻辑实现如下:
@Override public void addRetryTask(RechargeRequest rechargeRequest) { Task task = new Task(); TaskTypeEnum taskTypeEnum = TaskTypeEnum.getTaskType(rechargeRequest.getErrorCode()); task.setTaskType(taskTypeEnum.getTaskType()); task.setPriority(taskTypeEnum.getPriority()); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE,0); //实际业务中可能是1分钟后重试,为了测试方便立即重试 task.setExecuteTime(calendar.getTimeInMillis()); //protostuff 序列化优化 task.setParameters(ProtostuffUtil.serialize(rechargeRequest)); System.out.println("调用延迟任务系统添加"+taskTypeEnum.getDesc()+"任务:"+task); taskServiceClient.push(task); }
代码解释1:任务类型和优先级如何确定?
在chongba_common工程中有一个任务类型和优先级的枚举类:TaskTypeEnum,根据不同的业务状态码绑定不同的任务类型和优先级,这些业务状态码都定义在了:StatusCode类中,在添加重试任务方法的参数RechargeRequest中有一个属性:errorCode代表了业务类型错误码。
@Getter public enum TaskTypeEnum { ORDER_REQ_FAILED(StatusCode.ORDER_REQ_FAILED, 1001, 1,"话费充值失败,重试"), BALANCE_NOT_ENOUGH(StatusCode.BALANCE_NOT_ENOUGH, 1001, 2,"供应商余额不足,轮转其他供应商"), REMOTEERROR(StatusCode.REMOTEERROR, 1001, 3,"远程调用失败,重试"), STATECHECK(StatusCode.STATECHECK, 1001, 4,"检查订单状态"); private int errorCode; //错误码 private int taskType; //对应具体业务 private int priority; //业务不同级别 private String desc; //描述信息 }
代码解释2:重试任务执行时间如何确定?
重试的时间间隔可以根据业务情况自行定制,可以每隔1分钟重试1次,或者自定义其他时间趋势比如可以采用RechargeRequest对象中的重试次数repeat作为下次重试时间,即呈现:1,2,3,4逐次递增的形式
代码解释3:序列化选型问题?
我们需要将添加重试任务的参数:RechargeRequest序列化当作任务对象Task的parameter参数,存储在数据库表中,表字段采用的是:blob字段
消费任务时还需要取出然后反序列化成RechargeRequest对象,调用对接供应商接口方法:recharge并传递该参数
影响序列化选择有两个因素:1):序列化之后码流的大小,如果太大,那么将会影响网络传输的性能。2):序列化和反序列化过程的性能
在chongba_common工程的:com.chongba.utils包下有两个工具类:JdkSerializeUtil,ProtostuffUtil
JdkSerialize:java内置的序列化能将实现了Serilazable接口的对象进行序列化和反序列化,ObjectOutputStream的writeObject()方法可序列化对象生成字节数组
Protostuff:google开源的protostuff采用更为紧凑的二进制数组,表现更加优异,然后使用protostuff的编译工具生成pojo类
在common工程中有一个测试方法用于比较jdk的序列化和Protostuff序列化的性能差异!
2.2 消费重试任务方法实现
步骤4:消费对接重试任务,完成重试下单方法:retryRecharge的逻辑,重试下单其实就是根据重任务的类型和优先级调用延迟任务系统拉取任务,得到任务对象Task,从Task对象中获取其参数parameter反序列化为对接请求对象:RechargeRequest,调用对接供应商接口的recharge(RechargeRequest rechargeRequest)方法
@Autowired private SupplierService supplierService; @Override @Scheduled(fixedRate = 1000) public void retryRecharge() { TaskTypeEnum taskTypeEnum = TaskTypeEnum.ORDER_REQ_FAILED; ResponseMessage responseMessage = taskServiceClient.poll(taskTypeEnum.getTaskType(), taskTypeEnum.getPriority()); if(responseMessage.isFlag()){ if(responseMessage.getData() !=null){ String taskStr = JSON.toJSONString(responseMessage.getData()); Task task = JSON.parseObject(taskStr,Task.class); RechargeRequest rechargeRequest = ProtostuffUtil.deserialize(task.getParameters(), RechargeRequest.class); rechargeRequest.setRepeat(rechargeRequest.getRepeat()+1); System.out.println("消费了"+taskTypeEnum.getDesc()+"任务,"+rechargeRequest); supplierService.recharge(rechargeRequest); } } }
在chongba_recharge_supplier模块的启动类上添加开启定时的注解:@EnableScheduling
代码解释1:为什么要定时?
对接供应商下单重试任务要得到消费,就必须要执行retryRecharge方法,如何发起调用,第一章我们消费任务是循环的去拉取任务,因此我们在这可以做一个定时,每隔1秒钟去拉取重试任务。
代码解释2:如何正确的从延迟任务系统返回的ResponseMessage对象中获取任务对象Task ?
不能用 Task task = (Task)responseMessage.getData(); 这个
如果这么获取会报如下错误:
java.lang.ClassCastException:java.util.LinkedHashMap cannot be cast to
com.chongba.entity.Task
原因是feign帮我们在类型封装的时候把Object类型的封装成了LinkedHashMap,所以我们可以通过转成json字符串然后再反转成对象的方式来规避这个问题。
步骤5:模拟对接重试业务并测试,在SupplierServiceImp类中的对接方法中模拟失败重试,
@Autowired private SupplierTask supplierTask; @Override public void recharge(RechargeRequest rechargeRequest) { Result<RechargeResponse> result = doDispatchSupplier(rechargeRequest); if(result !=null){ //重试逻辑的编写---添加重试任务 rechargeRequest.setErrorCode(StatusCode.ORDER_REQ_FAILED); supplierTask.addRetryTask(rechargeRequest); } }
2.3 添加消费重试任务方法测试
测试:
启动各个系统:chongba_schedule_service,chongba_schedule_job,chongba_recharge_web,chongba_recharge_mock,chongba_recharge_supplier
访问:http://localhost:191,进行话费充值业务,看是否有延迟任务的添加和消费
测试结果预期:能够正常添加失败重试任务,消费任务,但是会出现死循环。原因是:因为我们没有对重试次数加以限制,所以造成了死循环!!!
2.4 限制重试次数逻辑编写
添加失败重试次数限制。如果超过了最大次数,停止供应商对接,修改订单为失败
步骤一:在chongba_recharge_supplier工程中的配置文件application-dev.yml中配置最大重试次数
supplier:
apis: {
"jisuapi": "http://127.0.0.1:8090/jisuapi/mobilerecharge/recharge",
"juheapi": "http://127.0.0.1:8090/juheapi/recharge"
}
maxrepeat: 4 #最大重试次数
在配置类中添加对应配置
@Data @Component @ConfigurationProperties(prefix = "supplier") public class SupplierConfig { private Map<String,String> apis; //加载供应商api地址 private int maxrepeat;//最大重试次数 }
步骤二:在对接供应商服务接口实现类:SupplierServiceImpl中,改造对接供应商下单方法:recharge(RechargeRequest rechargeRequest),添加失败重试次数限制。如果超过了最大次数,停止供应商对接,订单失败
@Override public void recharge(RechargeRequest rechargeRequest) { //限制最大重试次数 如果超过了最大次数,停止供应商对接,订单失败---实际业务中是对接订单服务 if(rechargeRequest.getRepeat() >= supplierConfig.getMaxrepeat()){ updateTrade(rechargeRequest.getOrderNo(), OrderStatusEnum.FAIL.getCode()); return; } Result<RechargeResponse> result = doDispatchSupplier(rechargeRequest); if(result !=null){ //重试逻辑的编写---添加重试任务 rechargeRequest.setErrorCode(StatusCode.ORDER_REQ_FAILED); supplierTask.addRetryTask(rechargeRequest); } }
再次测试:重启chongba_recharge_supplier工程,再次充值:已经限制了添加消费重试任务次数。