微服务远程接口调用失败本地消息补偿方案
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
这里写目录标题
背景
用户在平台购买了相应的并发资源业务量在平台服务生成订单、账户扣款同时下发最新的并发资源业务量到对应微服务。平台服务购买成功下发对应微服务失败。
解决方案
一、通过手动抛异常的形式回滚本地事务。
如果下发微服务接口异常或者返回结果告知失败则手动抛出异常本地方法加@transcational回滚本地事务保持分布式事务一致性。
该方案如果下发失败不会生成对应的订单、修改账户。
二、用本地消息表记录下发失败的消息并且定时重新发送记录重新发送失败的次数成功则删除该消息。相当于柔性事务保证分布式事务最终一致性。
该方案如果下发失败会生成对应订单、修改账户。
着重说下方案二
本地消息补偿方案
1.建一个本地消息失败表。
CREATE TABLE `bill_concurrency_fail_record` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(32) NOT NULL DEFAULT '' COMMENT '租户ID',
`resource_type` int(11) NOT NULL DEFAULT '0' COMMENT '资源类型',
`fail_num` int(11) NOT NULL COMMENT '失败次数',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT '1970-01-01 00:00:00' COMMENT '删除时间',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除1-已删除0-未删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='并发类型下发业务方失败记录表'
2.发送失败记录失败消息
调用失败先重复3次如果仍失败则记录本地消息失败表第一次新增之后更新失败次数failNum + 1。成功则删除对应失败消息记录。
concurrencyDto.setChangeLimit((long) allResourceCount);
Boolean executeFlag = false;
int executeNum = 0;
do {
try {
executeNum++;
executeFlag = handler.doSyncLimit(concurrencyDto);
} catch (Exception e) {
log.info("并发类型事件 第{}次同步并发量到业务方异常! concurrencyDto : {}", executeNum, concurrencyDto);
e.printStackTrace();
}
} while (!executeFlag && executeNum < 3);
if (executeFlag) {
log.info("并发类型事件 同步并发量到业务方 成功 concurrencyDto : {}", concurrencyDto);
//同步成功删除失败消息表对应记录
failRecordMapper.deleteOne(concurrencyDto.getTenantId(), concurrencyDto.getResourceType().code());
} else {
log.info("并发类型事件 同步并发量到业务方 失败 concurrencyDto : {}", concurrencyDto);
//同步业务方失败加入到失败消息表定时重试
noteConcurrencyFailRecord(concurrencyDto);
}
/**
* 添加失败记录 存在就更新失败次数加1
* @param concurrencyDto
*/
private void noteConcurrencyFailRecord(AbsMealConcurrencyDto concurrencyDto) {
String tenantId = concurrencyDto.getTenantId();
int resourceType = concurrencyDto.getResourceType().code();
BillConcurrencyFailRecord existRecord = failRecordMapper.selectOne(tenantId, resourceType);
if (existRecord == null) {
BillConcurrencyFailRecord failRecord = new BillConcurrencyFailRecord();
failRecord.setTenantId(tenantId);
failRecord.setResourceType(resourceType);
failRecord.setFailNum(1);
failRecord.setCreateTime(new Date());
failRecord.setUpdateTime(new Date());
failRecord.setIsDeleted(false);
failRecordMapper.insert(failRecord);
} else {
failRecordMapper.updateFailNumById(existRecord.getId(), existRecord.getFailNum() + 1);
}
}
3. 定时任务重试
xxl-job新建定时任务重试调用发送业务方接口
/**
* 并发同步业务方失败重试 查找失败次数大于100的
*/
@XxlJob(value = "mealConcurrencySyncFailRetryTaskHandler")
public void concurrencySyncFailRetry() {
List<BillConcurrencyFailRecord> FailRecords = billConcurrencyFailRecordMapper.selectList(concurrencyMaxRetryTimes);
if (CollectionUtil.isEmpty(FailRecords)) {
return;
}
FailRecords.forEach(r -> mealConcurrencyContext.concurrencyNotice(buildEventDto(r)));
}
补充
本方案不能完全保证分布式事务一致性比方说下发业务方成功但是因为网络抖动导致上报结果失败异常。本方案中因为下发的并发量是全量下发微服务接口多次调用是幂等的所以可以多次调用。