让您做一个电商平台,您如何设置一个在买家下订单后的”第60秒“发短信通知卖家发货,您需要考虑的是 像淘宝一样的大并发量的订单。

个人记录:2018年,工作的第6到7个年头。
重点研究自己不太擅长的技术:分布式、高并发、大数据量、数据库优化、高性能、负载均衡等。


问题源头:http://ifeve.com/question/%e5%85%b3%e4%ba%8e%e6%b7%98%e7%82%b9%e7%82%b9%e9%9d%a2%e8%af%95%e4%b8%ad%e7%a2%b0%e5%88%b0%e7%9a%84%e6%9e%b6%e6%9e%84%e8%ae%be%e8%ae%a1%e9%97%ae%e9%a2%98%ef%bc%9f/


关于淘点点面试中碰到的架构设计问题?
此题是面试淘点点的问题,由于没经验所以败在这个题目下面,因此请权威人士指点 指点,谢谢。。
问题描述:让您做一个电商平台,您如何设置一个在买家下订单后的”第60秒“发短信通知卖家发货,您需要考虑的是 像淘宝一样的大并发量的订单。




第1次思考
表结构1
Order{
   order_id 订单id
   info  订单相关字段
   sms_notify 短信通知
}


//买家下单
void makeOrder(Order order){
  //默认下单,没有发送短信通知
  order.setSmsNotify(0);
  order.setCreateTime(当前时间);
  boolean succeed=saveOrder(order);
  if(!succeed){
    //下单失败
return;
  }
  //订单保存成功,发送mq消息,告知“下单mq消费者”
  try{
    sendMq(order);
  }catch(Exception e){
    //log error 
//记录错误日志,方便排查
  }
}


//mq消息补发


//线程安全,优先级队列
PriorityBlockingQueue queue;




//mq消费者,多个客户端
void receiveNewOrder(order){
   //发送短信的时间 
   time = order.getCreateTime()+60秒;
   
   //有序队列 PriorityBlockingQueue 
   //Comparator比较器
   //notifyTime=createTime+60,时间小的放在前面
   queue.add(orderNotifyInfo);
}


//发送短信的工作线程
void sendSMSThread(orderNotifyInfo){
   //先查询是否发过了,根据sms_notify字段
   boolean hasSend = query(orderId);
   if(hasSend){
      logger.error(...);
      return;
   }
   
   //获取优先级最高的order
   //比较notify时间,如果时间到了
   try{
     //sms send log,发送标记为0


     boolean sendSucceed=sendSms();
if(sendSucceed){
   //更新订单通知true
   updateOrderNotifyInfo();
}
   }catch(Exception e){
     logger.error();
   }
 
}


第1次思考补充
表结构2
Order{
   order_id 订单id
   info 订单相关字段
}
OrderInfo{
  order_id 订单id
  sms_notify 短信通知
}


如果Order的扩充字段是单独建表的,需要使用“事务”。
Order和OrderInfo应该设计为在1个数据源,如果使用了分库分表,orderId相同,正好可以落在1个库上。
不存在分布式事务。


万一是“分布式事务”,可以使用 局部事务+MQ的方式。
比如
@Transactional
void makeOrder(){
  saveOrder();
  saveOrderLog();
}


SendSmgThread{
  
  //定时扫描Order表,没有发送SMS的,发送MQ消息给"新增Order消费者"
  task(){
  
  }
}




第1次思考补充2
如果SMS队列所在机器,突然挂了,当前机器上的所有MQ处理问题:
   短信发送成功了,MQ回传或者直接修改smsNotify失败呢?
   MQ消费成功,还是失败?
   MQ消费成功,如果sendSMSThread再次发送,关键还是smsNotify?
   
   订单的产生和MQ消息,需要事务+MQ+消息重试,消息重试的基础是,先使用局部事务,saveOrder+saveOrderNotify。
   订单的消费和MQ消息,也需要如此。
   而之前的发短信过程有问题。
   需要改进下。
   
   
   为啥为想到用“队列”、“优先级队列”,感觉自己是受到了网友方案的影响。
   
改进方案:
   不需要队列。
   确保数据一致性。
   
   //买家下单


void makeOrder(Order order){
   //如果sms_notify不再order表,需要使用事务
   @Transactional
   saveOrder();
   saveOrderNotify();
}


//定时任务
//根据create_time,先处理时间较早的
//根据sms_notify字段,生成SMS列表
void makeSms(){
  //如果sms_notify为0,生成1条sms记录
  saveSms();



sms表结构
order_sms{
  order_id
  sms_to
  sms_content
  sms_send_succeed
  create_time
}
 
//再设计1个定时任务
//根据create_time,先处理时间较早的
//根据sms_send_succeed发送SMS
void sendSms(){
    //如果create_time+60
booleean sendSMSSucceed=false;

try{
 sendSMSSucceed=sendSMS();
}catch(Exception e){
  //发送失败,问题不大,下次会重试
}
if(sendSMSSucceed){
  //更新sms_notify为true
  updateSmsNotify(true);
}
}


//疑难杂症
如果updateSmsNotify报错,比如数据库短暂异常,那么标记失败。
下次还会再次处理一次,这个时候再次发送短信。
发短信这个第三方服务和咱们自己的接口,不能做到“分布式事务”。


为了防止重新发送短信,第三方服务,可以提供1个接口
SMSService{


   boolean hasSendSms();


}
如果已经发送了,就不再发送了。
同时更新sms为已发送。


或者,参考商户和第三方支付的回调机制。
短信发送成功后,会调用咱们的URL或接口,处理成功。


再或者
try{
   updateSmsNotify(true);
 }catch(Exception e){
   logger.error(sms error format);
 }
 //定时处理错误日志
 再次尝试更新smsNotify。
 
 
 不可能完成的任务?
 第60秒?一切正常,第60秒没问题。但是,数据量大了,比如SMS短信接口有瓶颈。或者系统出问题,重试,就会第61秒发送。
 不漏发?调用SMS有可能发送失败。
 不重发?SMS发送成功,但是smsNotify状态没有更新成功,再次发送。
 最可行的办法,只能是记录更新smsNotify的日志。解析日志,再次更新。
 
 定时任务性能问题?
 根据Order生成SMS列表,不存在这个问题。60秒时间,只要生成1条就可以了。
 根据SMS列表,发送需要判断时间。如果时间还没有到,怎么处理呢?
 
 定时任务每5秒执行1次,发送短信,可能就延迟了1到4秒。
 比如:11:10:50秒。
 45秒执行,45、46、47、48、49、50。
 处理45秒这一批,如果程序缓慢,可能46秒才发送。
 
 另外,就算短信接口调用成功了,也不是实时发送的。
 
 这个地方的“业务”,只能优化。
 精确到1秒,技术问题不能100%完成,性能也是个考验。
 
 定时任务每秒执行1次也可以,但是要考虑到“发送时间过了的”情况,也应该发送。
 
 订单量
“一图读懂京东超市双11:1分钟订单量达4万 - B2C - 亿邦动力网” 
1秒钟按照1000,5台机器,1台机器处理200条。
这个量不算太大。


 Spring自带定时任务:处理好多台机器之间的。每个机器只处理自己的,比如 机器号1尾数1和2,机器2,位数3和4。其中1台机器挂了呢?
 分布式调度框架:还没怎么用过。elastic-job。
 
 之前自己的习惯,都是使用“悲观锁”,只让1台机器执行,不用考虑分配的问题,就是量大的时候,会比较慢。
 
 网友观点:
 一
 1. 准备一个FIFO队列,因为订单肯定有先后顺序的,所以最先进队列的一定先发;
2. 一个专门的消费者线程处理出队,判断时间是否到达”60秒”,没到就空循环或sleep,到达时间后丢给发短信线程池处理;
3. 如果要求故障恢复的话,保存订单同时保存该订单处理机器标识,监控程序发现某机器宕机后通知集群另一台机器从存储中获取宕机机器所有“未发送通知”的订单。
 
 二、订单系统和发短信任务系统分离,订单系统创建订单后异步添加短信发送任务到任务系统,任务系统每秒钟扫描。
 
 三、可以通过分布式调度框架来实现,将这种通用的调度任务交给专门的系统来处理,接收下单的时间和订单的基本信息做为触发这个任务的参数即可
触发动作可以用过类似kafka的消息中间件进行传递。


四、我的思路大致如下:
1,首先要把下订单和发送信息做成异步.比如下订单后就把对应的消息缓存起来或者通过消息发送给消息队列.
2,另外发送短信的逻辑就负责从消息队列中轮训或者定时调度获取消息发送给用户.
但是在设计上有一些可以优化的地方可以提高性能. 比如:如果这些订单时间分布不均匀,怎样才能最高效? 怎么尽可能的一次处理更多的消息?
机遇这些考虑,可以把FIFO消息队列或者缓存中的消息带上一个时间戳的属性.然后按照时间间隔来把消息放入不同的分组.比如0-10s的消息放入一个组,10-20s的消息放入一个组.这个组的时间间隔可以自定义从1秒-N秒,定义的范围越大,短消息发送时间就会有一些误差. 但是个人觉得每次消息是65秒发送,还是55秒发送,差隔几秒没有什么影响.
消息按照时间间隔分组之后,处理消息队列的调度程序就可以从这个消息队列中依次读取,因为最先的消息肯定是最前面的.出理完第一个消息组之后,再停止N秒(这个N秒就是前面设定的时间间隔)进行下一次调度.
 
五、1、引入消息队列;
2、引入失败回调;


六、1.创建订单和发送短信通知必须进行业务解耦;
2.消息队列可以存放通知短信消息;
3.避免不同时段订单量不均,是否可以设计动态可配置消费者数量进行消费;
4.另外,可根据时段,地区等细分队列策略,意图均摊高峰消息;




 当事人自己的总结:http://my.oschina.net/u/926166/blog/522227

你可能感兴趣的:(Java技术专家)