[优化]上下游交互策略
书接上文https://blog.csdn.net/weixin_43303530/article/details/127227147?spm1001.2014.3001.5502为满足产品提的在24小时内能重试尽量去重试不计较重试的次数的要求在第三方电子卡系统无法提升接口并发数量的情况下优化用户领取红包策略 新人福利 阿里云服务器打折啦https://www.aliyun.com/minisite/goods?userCoden207c1eu1、业务与技术背景1、为了促进公司商城项目的推广与引流项目组引入了第三方的电子卡系统该电子卡系统可以通过生成相应的红包类型运营人员可以通过该系统将红包发放给需要引流的客户来促进消费与推广。2、由于目前该电子卡发红包行为只能在小程序端触发无法从后台进行派发和控制希望礼品卡可以实现运营端应用业务可以像发优惠券那样派发红包实现多样化的用户运营。3、由于目前该电子卡每个红包只能有一百个用户领取而业务希望开发可以在后台系统中增加用户列表excel文件的解析对列表中的用户去领取红包这就不可避免的需要发送大量的领取红包请求到该电子卡系统。4、为了防止由于不稳定的因素(如网络波动、流量峰值过高等)在excel文件解析后直接调用该电子卡系统导致用户领取红包的请求失败导致数据丢失将请求的消息放入mq队列。5、由于该电子卡的系统性能很差请求高频率的失败所以需要使用mq的消息重试但由于上文中采用Spring RetryTemplate做RabbitMQ消息重试机制发现可能会造成队列堵塞且同样避免不了第三方接口性能差的问题大批量的请求可能导致第三方接口堵塞。2、解决思路1、降低接口请求的频率防止大量请求执行至第三方导致接口堵塞2、短时间内大批量失败不再请求第三方接口3、对于短时间内已经执行失败的请求不做处理采取补偿4、当接口请求成功后尝试补偿。3、解决方案1、降低请求第三方接口频率mq消费者每次消费后进行一定时间的sleep此处的requestIntervalMs变量是配置在nacos内的GIFT_RETRY_RECEIVE_RED_PACKET_THREAD_POOL是线程池降低最大消费者数量2、采用feign调用增加hystrix熔断(熔断配置已省略)3、失败的请求保存记录mongo3.1、mongo表结构/** * 批量领红包重试 * author liurui * date 2022/8/16 */Document(collectionReceiveRedPacketRetryTask)publicclassReceiveRedPacketRetryTask{IdprivateStringid;/** * 调用卡系统入参 */privateBatchReceiveRedPacketInputbatchReceiveRedPacketInput;/** * 创建任务id */privateStringprepareRedPacketTaskId;/** * 上传发放红包任务id */privateStringuploadUserTaskId;/** * 状态0待重试1重试成功2重试失败(可以增加一个进行中的状态但此处已经做了分布式锁一个时间只存在一个重试任务所以没有增加) */privateIntegerstatus;/** * 重试次数 */privateIntegerretryCount;/** * 创建人ID */privateLongcreateUserid;/** * 创建时间 */privateLongcreateTime;/** * 最后修改人ID */privateLongupdateUserid;/** * 最后修改时间 */privateLongupdateTime;publicStringgetId(){returnid;}publicvoidsetId(Stringid){this.idid;}publicBatchReceiveRedPacketInputgetBatchReceiveRedPacketInput(){returnbatchReceiveRedPacketInput;}publicvoidsetBatchReceiveRedPacketInput(BatchReceiveRedPacketInputbatchReceiveRedPacketInput){this.batchReceiveRedPacketInputbatchReceiveRedPacketInput;}publicStringgetPrepareRedPacketTaskId(){returnprepareRedPacketTaskId;}publicvoidsetPrepareRedPacketTaskId(StringprepareRedPacketTaskId){this.prepareRedPacketTaskIdprepareRedPacketTaskId;}publicStringgetUploadUserTaskId(){returnuploadUserTaskId;}publicvoidsetUploadUserTaskId(StringuploadUserTaskId){this.uploadUserTaskIduploadUserTaskId;}publicIntegergetStatus(){returnstatus;}publicvoidsetStatus(Integerstatus){this.statusstatus;}publicIntegergetRetryCount(){returnretryCount;}publicvoidsetRetryCount(IntegerretryCount){this.retryCountretryCount;}publicLonggetCreateUserid(){returncreateUserid;}publicvoidsetCreateUserid(LongcreateUserid){this.createUseridcreateUserid;}publicLonggetCreateTime(){returncreateTime;}publicvoidsetCreateTime(LongcreateTime){this.createTimecreateTime;}publicLonggetUpdateUserid(){returnupdateUserid;}publicvoidsetUpdateUserid(LongupdateUserid){this.updateUseridupdateUserid;}publicLonggetUpdateTime(){returnupdateTime;}publicvoidsetUpdateTime(LongupdateTime){this.updateTimeupdateTime;}}4、补偿机制原意是考虑当有请求成功发送时就进行补偿但是会存在一个问题就是如果最后一大批数据都请求失败了就不会有请求去唤醒补偿了或者说要等待下一次发红包请求才有可能进行补偿对此采取了请求成功时进行补偿和job补偿相结合的机制又考虑到并发场景的问题虽然第三方的电子卡系统做了幂等但是为了减免压力和并行补偿的问题进行补偿的时候做了一次redis分布式锁没有获取到锁说明有补偿任务在进行那么就继续之前的补偿任务不做处理没有就进行补偿4.1、请求成功采取异步线程进行补偿(采用线程池优化cpu资源利用)4.2、xxljob每隔半个小时执行一次补偿任务/** * 礼品卡-批量领红包重试 * author liurui * date 2022/8/1 */ComponentpublicclassRetryReceiveRedPacketTaskJobextendsIJobHandler{privatefinalLoggerloggerLogUtils.getLogger(this.getClass());ResourceprivateUserReceiveDetailWriteServiceuserReceiveDetailWriteService;OverrideXxlJob(retryReceiveRedPacketTaskJob)publicReturnTStringexecute(Stringparam)throwsException{ReturnTStringresultnewReturnT();longbeginTimeSystem.currentTimeMillis();try{logger.info(retryReceiveRedPacketTaskJob开始执行);XxlJobLogger.log(retryReceiveRedPacketTaskJob开始beginTime);result.setCode(ReturnT.SUCCESS_CODE);userReceiveDetailWriteService.executeRetryTask();result.setMsg(执行成功);}catch(Exceptione){logger.error(retryReceiveRedPacketTaskJob执行失败,e);result.setCode(ReturnT.FAIL_CODE);result.setMsg(retryReceiveRedPacketTaskJob执行失败e);}finally{logger.info(retryReceiveRedPacketTaskJob结束执行);XxlJobLogger.log(retryReceiveRedPacketTaskJob结束(System.currentTimeMillis()-beginTime));}returnresult;}}xxljob相关信息4.3、补偿逻辑此处采用redisson做redis分布式锁publicvoidexecuteRetryTask(){RLockrLockredisson.getLock(GiftCardCenterConstant.GIFTCARD_RETRYRECEIVE_REDPACKET_LOCK);try{booleanlockSuccessrLock.tryLock(0,TimeUnit.SECONDS);if(lockSuccess){intpageNum1;// 一次查询20条intpageSize20;ListReceiveRedPacketRetryTasklist;// 分页获取数据do{listreceiveRedPacketRetryTaskReadService.getReceiveRedPacketRetryTaskByPageParams(pageNum,pageSize);// 重新请求list.forEach(this::retryBatchReceiveRedPacket);pageNum;}while(CollectionUtils.isNotEmpty(list));}}catch(Exceptione){logger.error(批量领红包重试发生异常,e);}finally{if(rLock.isLocked()){try{rLock.unlock();}catch(Exceptione){}}}}mongo待补偿记录查询publicListReceiveRedPacketRetryTaskgetReceiveRedPacketRetryTaskByPageParams(intpageNum,intpageSize){QueryquerynewQuery(Criteria.where(status).ne(1));query.with(PageRequest.of(pageNum-1,pageSize));query.with(Sort.by(Sort.Direction.ASC,status,retryCount));query.with(Sort.by(Sort.Direction.DESC,createTime));returnmongoTemplate.find(query,ReceiveRedPacketRetryTask.class);}