客服MM被投诉说下单耗时很长,老板下令必须控制在1秒以内

2020-07-23 19:23:48 浏览数 (1)

近期,公司的订单量一直在持续增加,客服经常被投诉到说我们的下单时间过长,有时要好几秒,然后客服 MM 就反映到我们技术部门,老板得知后就说,这怎么行呢?不能让我们可爱的用户等 1 秒以上才能创建好订单,你们得去改代码。

然后我就去看了看我们现有订单系统是怎么写的,一顿搜索加源码查看,终于理清楚了具体的流程了,现在将主要的业务流程图画出来,方便分析:

如上图所示,每当订单成功支付后就会进行一系列的连锁操作,主要包括:

  • 订单状态更新
  • 库存扣减
  • 积分处理
  • 发放优惠券
  • 短信发送
  • 发货处理

其中,涉及到很多耦合系统:

  • 自身数据库
  • 库存系统
  • 积分系统
  • 促销系统
  • 短信系统
  • 三方物流系统等

从上面这些一连贯系统来看,我想你心里已经清楚了,这一次核心链路执行时间为什么会要好几秒的时间了,肯定在想,你们这么搞,是不是耦合度太强啊,你时间不长,谁时间长,心疼客服 MM。

的确,在我们创业初期,的确这么干的,资源有限嘛,你懂的,怎么快怎么来,当然我们是肯定不是瞎来哈,还是保证产品质量的。好了,现在我们已经理清了订单系统目前锁面临的问题,那么,现在我们就来开始怎么优化这块的业务,将耗时控制在 1 秒以内。

目标

在用户完成支付之后,从支付界面跳转回订单中,确保速度要足够的块,要让用户迅速的感知到订单已完成。

改造思想

如上分析,我们知道,其实与订单系统关联的那极大系统中,并不是全是需要耦合的,不难看出,我们值需要保证核心流程即可。那是哪些流程要确保连载一起呢,经过分析,支付完成订单只需要更改订单状态、扣减库存操作组成核心流程就可以了。

至于像积分的变化、发放优惠券、短信发送以及通知发货等操作,我们就可以把它放到 MQ 中去,利用 MQ 的异步化去实现。MQ 我们这里选型的是 RocketMQ 中间件,为什么选用 RocketMQ,可以看前面的文章(消息中间件能干什么?RabbitMQ、Kafka、RocketMQ正确选型姿势)有详细的分析。下面,我们先展示我们的 RocketMQ 线上部署架构:

好了,上面我们分析出来了我们订单系统该如何去改造了,并且也已经搭好了生产 RocketMQ 集群环境了,那我们就针对这两点去动手改造吧:

方案落地

通过上面的各种前期工作以及中间件环境的准备,主要涉及到如下两点:

  • 订单系统保留核心流程,状态更新以及扣减库存
  • 使用 RocketMQ 进行系统间异步调用

现在,改造后的架构图是这个样子了:

如上图所示,首先,现在的订单系统只需要同步更新订单状态和扣减库存这两个最关键的步骤就行了,因为,订单一旦支付成功,只要确保订单状态变为 “已支付” ,库存能扣减,就可以保证我们订单核心数据不会错乱。

其次,订单系统就会向 RocketMQ 中发送个一个订单已支付的消息,接着关联系统就会各自业务处理,如:

  • 积分系统从 RocketMQ 里获取消息,然后依据消息去累加积分;
  • 营销系统会从 RocketMQ 里获取消息然后会去发送优惠券;
  • 短息推送系统会从 RocketMQ 里获取消息去向用户推送短信;
  • 仓储系统会从 RockertMQ 里获取消息进而生产物流订单和发货单,去通知仓库管理员打包商品,准备交接给物流公司发货

代码落地

现在,我们已经完成了所有的方案设计,接下来,我们就可以实施我们的现有改造的技术方案了。主要设计两块:

  • 订单系统自身的改造
  • 耦合系统的改造
订单系统自身改造

我们订单系统只需要保留订单状态的更新以及库存的扣减操作,它需要将积分系统、促销系统、短信推送系统以及仓储系统等这些耦合性的系统从自身剥离出去,进而改造成,将发送一条订单支付的消息到 RocketMQ 中去。

我们来看下之前的订单系统处理代码:

代码语言:javascript复制
/**
 * <p>
 * 收到订单支付成功的通知
 * </p>
 *
 */
public void payOrderSuccess(Order order) {

//更新本地订单数据里的订单状态
updateOrderStatus(order);
//调用库存服务接口,扣减库存
stockService.updateProductStock(order);
//调用积分服务接口,增加积分
creditService.updateCredit(order);
//调用促销服务接口,增加优惠券
marketingService.addVoucher(order);
//调用推送服务接口,发送短信
pushService.sendMessage(order);
//调用仓储服务接口,通知发货
warehouseService.deliveryGoods(order);
}

好了,首先我们需要对上面的代码进行改造,按照我们上面的技术方案来看,这里我们需要去除掉一些代码逻辑,其次再增加一个发送消息到 RocketMQ 的逻辑进来。

现在这里如果用到 RocketMQ 发消息的话,首先我们需要将其依赖加入到我们的项目中来:

代码语言:javascript复制
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.3.0</version>
</dependency>

接下来,我们需要封装一个 RocketMQ 的消息生产类,不是很复杂,一看就懂:

代码语言:javascript复制
public class RocketMQProducer {

//这个是RocketMQ的生产类,用这个类就可以发送消息到RocketMQ中
private static DefaultMQProducer producer;

static {
//构建一个Producer实例对象
producer = new DefaultMQProducer("order_producer_group");
//为Producer设置NameServer地址,让它可以拉去路由信息
//这样就可以知道每个Topic的数据分散在哪些Broker机器上,然后才可以把消息发送到Broker上去
producer.setNameSrvAddr("localhost:9876");
//启动Producer
producer.start();
}

public static void send(String topic, String message) throws Exception {
//构建一条消息对象
Message messge = new Message(
topic, // 指定发送消息到哪个Topic上去
"",   // 消息的Tag
message.getBytes(RemotingHelper.DEFAULT_CHARSET) // 具体消息内容
);

//使用Producer 发送消息
SendResult sentResult = producer.send(message);
log.info("sendResult= {}", sentResult);
}
}

增加了上面的代码之后,我们就具备了将支付消息发送到 RocketMQ 里的一个 Topic 中去了。如此,我们的支付成功 ”TopicOrderPaySuccess“ 这个 Topic 的消息就会分散到我们的两个 Master Broker中去。

当我们发送一条订单支付成功消息到 RocketMQ 里的时候,他会依据自己的负载均衡算法以及容错算法将消息发送到其中一个Broker中去。如果想了解更细节的算法以及如何分配,后面再分享出来哈。

其他耦合系统改造

通过上面对于订单系统自身的改造,主要保留了状态更新以及库存扣减,然后将支付成功消息推送到 RocketMQ 里去,也完成了相应的代码编写,那接下来,我们就需要在关联系统中来获取订单系统发送来的消息,然后依据自己系统的业务来进行相应的处理。下面来看下相应示例代码:

代码语言:javascript复制
public class RocketMQConsumer {
public static void start() {
new Thread() {
public void run() {
try{
//构建RocketMQ 消费者实例对象
//credit_group 为消费者分组
DefaultMQPushConsumer consumer = DefaultMQPushConsumer("credit_group");
consumer.setNamesrvAddr("localhost:9876");
//订阅“TopicOrderPaySuccess”的消息
consumer.subscribe("TopicOrderPaySuccess");
//注册消息监听器处理拉取到的消息
consumer.registerMessageListener(new MessageListenerConcurrently(){
public ConsumerConcurrentlyStatus consumeMessage(
                            List<MessageExt> msgs,
                            ConsumerConcurrentlyContext context){
//处理业务逻辑
return ConsumerConcurrentlyStatus.CONSUNER_SUCCESS;
}
});

//启动消费者实例
consumer.start();
log.info("Consumer Started...");

while(true){
Thread.sleep(1000);
}
} catch(Exception e){
e.printStackTrance();
}
}
}.start();
}
}

如上代码所示,我们的积分系统、营销系统、推送系统以及仓储系统,均从 RocketMQ 里消费 “TopicOrderPaySuccess” 中的消息,然后依据自己的业务逻辑进行相关业务处理。

改造前,订单状态更新耗时 30 ms,调用库存服务扣减库存耗时 80 ms左右,积分增加耗时 50 ms,发放优惠券 耗时 60ms,发送短信需要耗时 100 ms,这块由于调用第三方推送,可能会网络抖动会超过 1 s,通知发货耗时 500 ms,也会掉三方物流这块,所以一次下单核心流程可能会消耗几秒钟。

现在改造完之后,只要支付成功,订单系统现在只需要更新订单状态 30 ms 扣减库存 80 ms 发送订单消息到RocketMq z中10 ms ,总共大概 120 ms 就可以了,完美的控制在 1 s以内,可以实现 10 倍的性能提升效果。

0 人点赞