首页 > 其他分享 >RocketMQ - 生产者原理

RocketMQ - 生产者原理

时间:2023-02-16 09:33:47浏览次数:44  
标签:重试 生产者 Broker 发送 消息 原理 tpInfo RocketMQ

https://rocketmq.apache.org/

Apache RocketMQ是一款开源的、分布式的消息投递与流数据平台。出生自阿里巴巴,在阿里巴巴内部经历了3个版本后,作为Apache
顶级开源项目之一直到现在。在GitHub上有10000+star、5000+fork、170+contributors(在GitHub上提交代码并被采纳的开发者)

RocketMQ的前世

和大部分组件产生的原因类似,阿里巴巴内部为了适应淘宝 B2C 的更快、更复杂的业务,2001年启动了“五彩石项目”,阿里巴巴的第一代消息队列服务Notify就是在这个背景下产生的。
2010 年,阿里巴巴内部的 Apache ActiveMQ 仍然作为核心技术被广泛用于各个业务线,而顺序消息、海量消息堆积、完全自主控制消息队列服务,也是阿里巴巴同时期急需的。在这种背景下,2011年,MetaQ 诞生

RocketMQ 云化

2011年,LinkedIn将Kafka开源。2012年,阿里巴巴参考Kafka的设计,基于对MetaQ的理解和实际使用,研发了一套通用消息队列引擎,也就是 RocketMQ。自此才有了第一代真正的RocketMQ,2016年阿里云上线云RocketMQ消息队列服务。

Apache RocketMQ——金融级消息队列,一个拥有亚毫秒级延迟、万亿级消息容量保证、高消息容错设计的中间件,在阿里巴巴、VIPKID、微众银行、民生银行、蚂蚁金服、滴滴等国内知名互联网公司的实践中,有着完美的表现。
随着RocketMQ 5.0的发布,借助OpenMessaging提供跨平台、多语言的能力,将会打通 Prometheus、ELK 等上游组件,通过消息、Streaming 等形式将数据扭转到 Flink、Elasticsearch、Hbase、Spark等下游组件。届时整个生态体系将会更加完美、便捷。

RocketMQ支持3种消息:普通消息(并发消息)、顺序消息、事务消息
RocketMQ支持3种发送方式:同步发送、异步发送、单向发送。

生产者概述

发送消息的一方被称为生产者,它在整个RocketMQ的生产和消费体系中扮演的角色如图所示。
image

生产者组: 一个逻辑概念,在使用生产者实例的时候需要指定一个组名。一个生产者组可以生产多个Topic的消息。
生产者实例: 一个生产者组部署了多个进程,每个进程都可以称为一个生产者实例。
Topic: 主题名字,一个Topic由若干Queue组成。

RocketMQ 客户端中的生产者有两个独立实现类 :org.apache.rocketmq.client.producer.DefaultMQProducer 和org.apache.rocketmq.client.producer.TransactionMQProducer 。 前者用于生产普通消息、顺序消息、单向消息、批量消息、延迟消息,后
者主要用于生产事务消息

消息结构和消息类型

消息类的核心字段

public class Message implements Serializable {
	private static final long serialVersionUID = 8445773977080406428L;
	// 主题名字,可以通过RocketMQ Console创建
	private String topic;
	// 目前没用
	private int flag;
	// 消息扩展信息,Tag、keys、延迟级别都保存在这里
	private Map<String, String> properties;
	// 消息体,字节数组。需要注意生产者使用什么编码,消费者也必须使用相同编码解码,否则会产生乱码
	private bytel[] body;
	// 设置消息的 key,多个 key 可 以 用MessageConst.KEY_SEPARATOR(空格)分隔或者直接用另一个重载方法。如果 Broker 中 messageIndexEnable=true 则会根据 key创建消息的Hash索引,帮助用户进行快速查询。
	public void setKeys(String keys) {}	
	public void setKeys(Collection<String> keys){}
	// 消息过滤的标记,用户可以订阅某个Topic的某些 Tag,这样Broker只会把订阅了topic-tag的消息发送给消费者。
	public void setTags(String tags) {}
	// 设置延迟级别,延迟多久消费者可以消费
	public void setDelayTimeLevel(int level) { )
	public void setTopic(String topic) { }
	// 如果还有其他扩展信息,可以存放在这里。内部是一个Map,重复调用会覆盖旧值。
	public void putUserProperty(final String name, final String value) {...}
}

普通消息

普通消息也称为并发消息,和传统的队列相比,并发消息没有顺序,但是生产消费都是并行进行的,单机性能可达十万级别的TPS

分区有序消息

与Kafka中的分区类似,把一个Topic消息分为多个分区“保存”和消费,在一个分区内的消息就是传统的队列,遵循FIFO(先进先出)原则。

全局有序消息

如果把一个 Topic 的分区数设置为 1,那么该Topic 中的消息就是单分区,所有消息都遵循FIFO(先进先出)的原则。

延迟消息

消息发送后,消费者要在一定时间后,或者指定某个时间点才可以消费。在没有延迟消息时,基本的做法是基于定时计划任务调度,定时发送消息。在 RocketMQ中只需要在发送消息时设置延迟级别即可实现

事务消息

主要涉及分布式事务,即需要保证在多个操作同时成功或者同时失败时,消费者才能消费消息。RocketMQ通过发送Half消息、处理本地事务、提交(Commit)消息或者回滚(Rollback)消息优雅地实现分布式事务。

生产者高可用

客户端保证

第一种保证机制:重试机制

RocketMQ 支持同步、异步发送,不管哪种方式都可以在配置失败后重试,如果单个 Broker 发生故障,重试会选择其他 Broker 保证消息正常发送。
配置项 retryTimesWhenSendFailed表示同步重试次数,默认为 2次,加上正常发送 1次,总共3次机会。

同步发送的重试: 代码可以参考org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl.sendDefaultImpl(),每次发送失败后,除非发送被打断否则都会执行重试代码。

异步发送重试: 代码可以参考org.apache.rocketmq.client.impl.MQClientAPIImpl.sendMessageAsync()
重试是在通信层异步发送完成的,当operationComplete()方法返回的response值为null时,会重新执行重试代码。返回值 response为 null 通常是因为客户端收到 TCP请求解包失败,或者没有找到匹配的request
生产者配置项 retryTimesWhenSendAsyncFailed 表示异步重试的次数,默认为 2 次,加上正常发送的1次,总共有3次发送机会。

第二种保证机制:客户端容错

RocketMQ Client会维护一个“Broker-发送延迟”关系,根据这个关系选择一个发送延迟级别较低的 Broker 来发送消息,这样能最大限度地利用 Broker 的能力,剔除已经宕机、不可用或者发送延迟级别较高的 Broker,尽量保证消息的正常发送。

这种机制主要体现在发送消息时如何选择 Queue,源代码在 MQFaultStrategy.selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName)方法中

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
    if (this.sendLatencyFaultEnable) {
        try {
            //第一步:获取一个在延迟上可以接受,并且和上次发送相同的Broker。首先获取一个自增序号 index,通过取模获取Queue的位置下标 Pos。如果 Pos对应的 Broker的延迟时间是可以接受的,并且是第一次发送,或者和上次发送的Broker相同,则将Queue返回。
            int index = tpInfo.getSendWhichQueue().incrementAndGet();
            for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                if (pos < 0)
                    pos = 0;
                MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                if (latencyFaultTolerance.isAvailable(mq.getBrokerName()))
                    return mq;
            }
            //第二步:如果第一步没有选中一个Broker,则选择一个延迟较低的Broker。
            final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
            int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
            if (writeQueueNums > 0) {
                final MessageQueue mq = tpInfo.selectOneMessageQueue();
                if (notBestBroker != null) {
                    mq.setBrokerName(notBestBroker);
                    mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums);
                }
                return mq;
            } else {
                latencyFaultTolerance.remove(notBestBroker);
            }
        } catch (Exception e) {
            log.error("Error occurred when selecting message queue", e);
        }

        return tpInfo.selectOneMessageQueue();
    }
    //第三步:如果第一、二步都没有选中一个Broker,则随机选择一个Broker
    return tpInfo.selectOneMessageQueue(lastBrokerName);
}

tpInfo.selectOneMessageQueue(lastBrokerName) 该方法的功能就是随机选择一个Broker

public MessageQueue selectOneMessageQueue(final String lastBrokerName)
{
	//第一步 如果没有上次使用的Broker作为参考,那么随机选择一个Broker。
	if (lastBrokerName == null) {
		return selectOneMessageQueue ();
	} else {//第二步 如果存在上次使用的Broker,就选择非上次使用的 Broker,目的是均匀地分散Broker的压力
		int index = this.sendwhichQueue.getAndIncrement();
		for (int i = 0;i < this.messageQueueList.size(); i++){
			int pos = Math.abs(index++)  this.messageQueueList.size();
			if (pos <0)
				pos =0;
			MessageQueue mg = this.messageQueueList.get(pos);
			if (!mq.getBrokerName().equals(lastBrokerName)) {
				return mq;
			}
		}
		//第三步 如果第一、二步都没有选中一个Broker,则采用兜底方案——随机选择一个Broker
		return selectOneMessageQueue();
	}
}

Broker 端保证

数据同步方式保证:在后面 Broker章节中会讲到 Broker主从复制分为两种:同步复制和异步复制。同步复制是指消息发送到MasterBroker后,同步到Slave Broker才算发送成功;异步复制是指消息发送到Master Broker,即为发送成功。在生产环境中,建议至少部署2个Master和2个Slave,下面分为几种情况详细描述。

  • 1个Slave掉电。Broker同步复制时,生产第一次发送失败,重试到另一组Broker后成功;Broker异步复制时,生产正常不受影响。
  • 2个Slave掉电。Broker同步复制时,生产失败;Broker异步复制时,生产正常不受影响。
  • 1个Master掉电。Broker 同步复制时,生产第一次失败,重试到另一组 Broker后成功;Broker异步复制时的做法与同步复制相同。
  • 2个Master掉电。全部生产失败。
  • 同一组Master和Slave掉电。Broker同步复制时,生产第一次发送失败,重试到另一组Broker后成功;Broker异步复制时,生产正常不受影响。
  • 2组机器都掉电:全部生产失败。

综上所述,想要做到绝对的高可靠,将 Broker 配置的主从同步进行复制即可,只要生产者收到消息保存成功的反馈,消息就肯定不会丢失。一般适用于金融领域的特殊场景。绝大部分场景都可以配置Broker主从异步复制,这样效率极高

标签:重试,生产者,Broker,发送,消息,原理,tpInfo,RocketMQ
From: https://www.cnblogs.com/vipsoft/p/17118769.html

相关文章

  • mysql事务隔离级别及实现原理,深度理解
    1、事务是什么?事务是数据库一个不可分的工作单元,可以将多个操作步骤表示为一个步骤。2、事务的四大特性Atomicity原子性,Consistency一致性,Isolation隔离性,Durability持......
  • 《分布式技术原理与算法解析》学习笔记Day13
    分布式计算模式:MapReduce什么是分治法?分治法是将一个复杂、难以直接解决的大问题,分割成一些规模小、可以比较简单或者直接求解的子问题,这些子问题之间相互独立且与原问题......
  • lvs+keepalive 工作原理及搭建
    目录lvs+keepalive工作原理及搭建nginx应用场景keepalive介绍keepalived工作原理VRRP工作原理简述keepalive软件结构开始搭建环境准备工作:安装Nginx什么是高可用?解决的问......
  • 【RocketMQ】DLedger选主源码分析
    RocketMQ4.5版本之前,可以采用主从架构进行集群部署,但是如果master节点挂掉,不能自动在集群中选举出新的Master节点,需要人工介入,在4.5版本之后提供了DLedger模式,使用Raft算......
  • 《分布式技术原理与算法解析》学习笔记Day12
    调度框架:共享状态调度什么是共享状态调度?共享状态调度是为了解决单体调度和两层调度遇到的问题而创建出来的新的调度框架。它通过将单体调度器分解为多个调度器,每个调度......
  • springboot starter 原理解析及实践
    什么是springbootstarterstarter是springBoot的一个重要部分。通过starter,我们能够快速的引入一个功能,而无需额外的配置。同时starter一般还会给我提供预留的自定配置选......
  • tcp通信原理
    (40条消息)【转】C#Socket编程详解(一)TCP与UDP简介_sinolover的博客-CSDN博客......
  • 双麦定向拾音束回音消除及远场拾音降噪模块 A-68(原理篇)
        最近手上有个双麦定向拾音束降噪和消回音模块,型号是A-68,一般的降噪处理设备不同的是这个模块可以实现定向拾音。只要找好两个麦克风的夹角就能够很好的屏蔽掉......
  • DNS服务原理
    DNS服务一、是什么DNS(DomainNameSystem域名系统)Ip:所有网络设备在互联网上的地址标记作用:解析域名背一个dns服务器:推荐202.106.0.208.8.8.8     3. h......
  • SpringBoot注册Servlet、Filter和Listener的方式和原理
    1实战在SpringBoot项目中,如果使用内嵌Web服务器,可以很方便地注册Servlet、Filter和Listener等组件。总的来说,包括以下方式:创建实现ServletContextInitializer接口的b......