• mq不丢与延时


    参考:

    微信公众号:架构师之路

    MQ不丢消息,究竟是怎么实现的?

    通过消息队列(MsgQueue,MQ)发送任务和消息,万一MQ重启了怎么办?能否保证MQ不丢消息?
    今天就聊聊MQ的消息必达性架构与流程。
    不丢消息,MQ架构设计的核心方向是什么?MQ要想消息必达,架构上有两个核心设计点:(1)消息落地;(2)消息超时、重传、确认;
    为了实现上述两个核心点,MQ架构如何?

    上图是一个MQ的核心架构图,可以分为三大块:(1)发送方 -> 左侧粉色部分;(2)MQ核心集群 -> 中间蓝色部分;(3)接收方 -> 右侧屎黄色部分;
    粉色发送方又由两部分构成:(1)业务调用方;(2)MQ-client-sender;其中后者向前者提供了两个核心API

    (1)SendMsg(bytes[] msg);

    (2)SendCallback();

    蓝色MQ核心集群又分为四个部分:

    (1)MQ-server

    (2)zk;

    (3)db;

    (4)管理后台web;


    黄色接收方也由两部分构成:(1)业务接收方;(2)MQ-client-receiver;其中后者向前者提供了两个核心API

    (1)RecvCallback(bytes[] msg);

    (2)SendAck();

    MQ是一个系统间解耦的利器,它能够很好的解除发布订阅者之间的耦合,它将上下游的消息投递解耦成两个部分,如架构图中的1箭头和2箭头:

    箭头1:发送方将消息投递给MQ,上半场;箭头2:MQ将消息投递给接收方,下半场;
    MQ消息可靠投递核心流程如何?MQ既然将消息投递拆成了上下半场,为了保证消息的可靠投递,上下半场都必须保证消息必达。

    MQ消息投递上半场,MQ-client-sender到MQ-server流程见上图1-3:(1)MQ-client将消息发送给MQ-server;画外音:此时业务方调用API:SendMsg。(2)MQ-server将消息落地,落地后即为发送成功;(3)MQ-server将应答发送给MQ-client;画外音:此时回调业务API:SendCallback。

    MQ消息投递下半场,MQ-server到MQ-client-receiver流程见上图4-6:(4)MQ-server将消息发送给MQ-client;画外音:此时回调业务API:RecvCallback。(5)MQ-client回复应答给MQ-server;画外音:此时业务方主动调用API:SendAck。(6)MQ-server收到ack,将之前已经落地的消息删除,完成消息的可靠投递;
    如果消息丢了怎么办?MQ消息投递的上下半场,都可以出现消息丢失,为了保证消息可达性,MQ需要进行超时和重传。
    上半场如何实施超时与重传?

    MQ上半场的1或者2或者3如果丢失或者超时,MQ-client-sender内的timer会重发消息,直到期望收到3,如果重传N次后还未收到,则SendCallback回调发送失败,需要注意的是,这个过程中MQ-server可能会收到同一条消息的多次重发。
    下半场如何实施超时与重传?

    MQ下半场的4或者5或者6如果丢失或者超时,MQ-server内的timer会重发消息,直到收到5并且成功执行6,这个过程可能会重发很多次消息。画外音:一般采用指数退避的策略,先隔x秒重发,2x秒重发,4x秒重发,以此类推。需要注意的是,这个过程中MQ-client-receiver也可能会收到同一条消息的多次重发。
    总结
    MQ是系统之间的解耦利器,MQ为了保证消息必达,架构设计方向为:(1)消息收到先落地;(2)消息超时、重传、确认保证消息必达;

    如何快速实现“延时消息”?

    快狗打车订单完成后,如果用户一直不评价,48小时后会将自动评价为5星。

    怎么实现这类“48小时后自动评价为5星”需求呢?
    画外音:这类“一段时间之后,完成一个任务”的需求很常见。

    cron是不是最容易想到的方案?
    启动一个cron定时任务,每小时跑一次,将完成时间超过48小时,且仍未评价的订单取出,置为5星,并把评价状态置为已评价。

    假设订单表的结构为:
    order(oid, finish_time, stars, status, …)

    更具体的,定时任务每隔一个小时会这么做一次:
    select oid from order where finish_time > 48 and status=0;
    update order set stars=5 and status=1 where oid in[…];

    如果数据量很大,需要分页查询,分页update,这将会是一个for循环。

    cron方案有什么不足?
    (1)轮询效率比较低;
    (2)每次扫库,已经被执行过记录,仍然会被扫描(只是不会出现在结果集中),有重复计算的嫌疑;
    (3)时效性不够好,如果每小时轮询一次,最差的情况下,时间误差会达到1小时;
    (4)如果通过增加cron轮询频率来减少时间误差,则轮询低效和重复计算的问题会进一步凸显;

    对于这类需要延时执行的任务,如何保证效率的同时,又保证实时性呢?
    答案是:高效延时消息。

    高效延时消息,包含两个重要的数据结构:
    (1)环形队列,例如可以创建一个包含3600个slot的环形队列(本质是个数组);
    (2)任务集合,环上每一个slot是一个Set<Task>;

    同时,启动一个timer:
    (1)此timer每隔1s,在环形队列中移动一格;
    (2)用一个Current Index来标识正在检测的slot;

    Task结构中有两个很重要的属性:
    (1)Cycle-Num:当Current Index第几圈扫描到这个Slot时,执行任务;
    (2)Task-Function:需要执行的任务函数;


    如上图,假设当前Current Index指向第一格,当有延时消息到达之后,例如希望3610秒之后,触发一个延时消息任务,只需:
    (1)计算这个Task应该放在哪一个slot,现在指向1,3610秒之后,应该是第11格,所以这个Task应该放在第11个slot的Set<Task>中;
    (2)计算这个Task的Cycle-Num,由于环形队列是3600格(每秒移动一格,正好1小时),这个任务是3610秒后执行,所以应该绕3610/3600=1圈之后再执行,于是Cycle-Num=1;

    Current Index不停的移动,每秒移动一格,当移动到一个新slot,遍历这个slot中对应的Set<Task>,每个Task看Cycle-Num是不是0:
    (1)如果不是0,说明还需要多移动几圈,将Cycle-Num减1;
    (2)如果是0,说明马上要执行这个Task了,取出Task-Funciton执行,丢给工作线程执行,并把这个Task从Set<Task>中删除;
    画外音:注意,不要用timer来执行任务,否则timer会越来越不准。

    使用了“延时消息”方案之后,“订单48小时后关闭评价”的需求,只需将在订单关闭时,触发一个48小时之后的延时消息即可:
    (1)无需再轮询全部订单,效率高;
    (2)一个订单,任务只执行一次;
    (3)时效性好,精确到秒;
    画外音:控制timer移动频率可以控制精度。

  • 相关阅读:
    开发板S3C2440挂起NFS步骤
    wind10系统 Atheros AR9271 Wireless Network Adapter USBwifi无线网卡的驱动安装解决无法搜索wifi信号,连接wifi信号无法上网的问题
    编写一个多线程函数实现对数组排序,要求: 1.至少用两个线程 2.数组的元素值可以事先定义好,或者可以从键盘输入(增加一个线程)。 3.用一个线程对数组排序,用另一个线程输出排序结果。 4.保证先排好序,再输出。
    led.c驱动框架2nd
    led.c驱动框架
    文件I/O的操作实例
    Python os.removedirs() 和shutil.rmtree() 用于删除文件夹
    Python os.remove() 删除文件
    Python os.chdir() 方法
    Python os.access() 方法
  • 原文地址:https://www.cnblogs.com/xuwc/p/13412092.html
Copyright © 2020-2023  润新知