订单履约状态通知乱序问题分析
- 第一步,调度出库,仓库操作员根据订单进行拣货出库;
- 第二步,物流配送,快递员将商品打包邮寄配送;
- 第三步,完成签收,消费者最终完成签收;
都会产生一个订单履约状态的消息,投递到RocketMQ,订单服务专门的线程负责消费处理,更新订单状态;
分析一下订单履约状态通知乱序的问题,可能导致订单状态数据错乱;
通过将同一个订单的履约状态消息投递到同一个ConsumerQueue;
另外,思考一下,履约状态消息消费过程中,怎么保证幂等消费(分布式锁),同时,如果消费出现异常,又要怎么保证履约状态消息的顺序性(SUSPEND_CURRENT_QUEUE_A_MOMENT,消息挂起);
SendResult sendResult = defaultMQProducer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message message, Object arg) {
//根据订单id选择发送queue
String orderId = (String) arg;
long index = hash(orderId) % mqs.size();
return mqs.get((int) index);
}
}, orderId);
需要关注一下这里设计模式的一些应用实战;
思考一下,如何保证 1. 履约服务,创建履约单;2. 通知仓储服务,调度出库发货;3. 通知物流服务,物流配送;强一致性问题;
引入 Seata Saga长事务机制,缺点,没有全局锁概念,没有写隔离机制;
适用场景,业务链路比较长,耗时较长的特定业务场景;
{
"Name": "order_fulfill",
"Comment": "订单履约流程",
"StartState": "FulfillService",
"Version": "1.0.0",
"States": {
"FulfillService": {
"Type": "ServiceTask",
"ServiceName": "fulfillSagaService",
"ServiceMethod": "createFulfillOrder",
"CompensateState": "CreateFulfillOrderCompensate",
"Next": "ChoiceWmsState",
"Input": [
"$.[receiveFulfillRequest]"
],
"Output": {
"CreateFulfillOrderResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
},
"Catch": [
{
"Exceptions": [
"java.lang.Throwable"
],
"Next": "CompensationTrigger"
}
]
},
"ChoiceWmsState":{
"Type": "Choice",
"Choices":[
{
"Expression":"[CreateFulfillOrderResult] == true",
"Next":"WmsService"
}
],
"Default":"Fail"
},
"WmsService": {
"Type": "ServiceTask",
"ServiceName": "wmsSageService",
"ServiceMethod": "pickGoods",
"CompensateState": "WmsPickGoodsCompensate",
"Next": "ChoiceTmsState",
"Input": [
"$.[receiveFulfillRequest]"
],
"Output": {
"WmsPickGoodsResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
},
"Catch": [
{
"Exceptions": [
"java.lang.Throwable"
],
"Next": "CompensationTrigger"
}
]
},
"ChoiceTmsState":{
"Type": "Choice",
"Choices":[
{
"Expression":"[WmsPickGoodsResult] == true",
"Next":"TmsService"
}
],
"Default":"Fail"
},
"TmsService": {
"Type": "ServiceTask",
"ServiceName": "tmsSagaService",
"ServiceMethod": "sendOut",
"CompensateState": "TmsSendOutCompensate",
"Input": [
"$.[receiveFulfillRequest]"
],
"Output": {
"TmsSendOutResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
},
"Catch": [
{
"Exceptions": [
"java.lang.Throwable"
],
"Next": "CompensationTrigger"
}
],
"Next": "Succeed"
},
"CreateFulfillOrderCompensate": {
"Type": "ServiceTask",
"ServiceName": "fulfillSagaService",
"ServiceMethod": "createFulfillOrderCompensate",
"Input": [
"$.[receiveFulfillRequest]"
]
},
"WmsPickGoodsCompensate": {
"Type": "ServiceTask",
"ServiceName": "wmsSageService",
"ServiceMethod": "pickGoodsCompensate",
"Input": [
"$.[receiveFulfillRequest]"
]
},
"TmsSendOutCompensate": {
"Type": "ServiceTask",
"ServiceName": "tmsSagaService",
"ServiceMethod": "sendOutCompensate",
"Input": [
"$.[receiveFulfillRequest]"
]
},
"CompensationTrigger": {
"Type": "CompensationTrigger",
"Next": "Fail"
},
"Succeed": {
"Type":"Succeed"
},
"Fail": {
"Type":"Fail",
"ErrorCode": "500",
"Message": "订单履约异常!!"
}
}
}
Seata Saga 长事务运行原理
思考一下,空补偿,空回滚的问题,具体方案参考之前 Seata TCC 模式处理锁定库存的代码实现;