Rocketmq概念大起底

  1. 基本概念
    RocketMQ 核心的四大组件:Name Server、Broker、Producer、Consumer ,每个组件都可以部署成集群模式进行水平扩展,下面是一张架构图:
    在这里插入图片描述
  2. 生产者Producer
    生产者(Producer)负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。 RocketMQ 提供了三种方式发送消息:同步、异步和单向。
  1. 同步发送
    同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如重要通知邮件、营销短信。
  2. 异步发送 异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。
  3. 单向发送 单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集。
  1. 生产者组
    生产者组(Producer Group)是一类 Producer 的集合,这类 Producer 通常发送一类消息并且发送逻辑一致,所以将这些 Producer 分组在一起。从部署结构上看生产者通过 Producer Group 的名字来标记自己是一个集群。
  2. 消费者Consumer
    消费者(Consumer)负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。站在用户应用的角度消费者有两种类型:拉取型消费者、推送型消费者。

1.拉取型消费者
拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程,所以 Pull 称为主动消费型。
2.推送型消费者
推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消息。

  1. 消费者组(Consumer Group)
    消费者组(Consumer Group)是一类 Consumer 的集合名称,这类 Consumer 通常消费同一类消息并且消费逻辑一致
  2. 消息服务器Broker
    消息服务器(Broker)是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。它还存储与消息相关的元数据,包括用户组、消费进度偏移量、队列信息等。从部署结构图中可以看出 Broker 有 Master 和 Slave 两种类型,Master 既可以写又可以读,Slave 不可以写只可以读。从物理结构上看 Broker 的集群部署方式有四种:单 Master 、多 Master 、多 Master 多 Slave(同步双写)、多 Master多 Slave(异步复制)
  1. 单 Master
    这种方式一旦 Broker 重启或宕机会导致整个服务不可用,这种方式风险较大,所以显然不建议线上环境使用。
  2. 多 Master
    所有消息服务器都是 Master ,没有 Slave 。这种方式优点是配置简单,单个 Master 宕机或重启维护对应用无影响。缺点是单台机器宕机期间,该机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受影响。
  3. 多 Master 多 Slave(异步复制)
    每个 Master 配置一个 Slave,所以有多对 Master-Slave,消息采用异步复制方式,主备之间有毫秒级消息延迟。这种方式优点是消息丢失的非常少,且消息实时性不会受影响,Master 宕机后消费者可以继续从 Slave 消费,中间的过程对用户应用程序透明,不需要人工干预,性能同多 Master 方式几乎一样。缺点是 Master 宕机时在磁盘损坏情况下会丢失极少量消息。
  4. 多 Master 多 Slave(同步双写)
    每个 Master 配置一个 Slave,所以有多对 Master-Slave ,消息采用同步双写方式,主备都写成功才返回成功。这种方式优点是数据与服务都没有单点问题,Master 宕机时消息无延迟,服务与数据的可用性非常高。缺点是性能相对异步复制方式略低,发送消息的延迟会略高。
  1. 名称服务器NameServer
    名称服务器(NameServer)用来保存 Broker 相关元信息并给 Producer 和 Consumer 查找 Broker 信息。NameServer 被设计成几乎无状态的,可以横向扩展,节点之间相互之间无通信,通过部署多台机器来标记自己是一个伪集群。每个 Broker 在启动的时候会到 NameServer 注册,Producer 在发送消息前会根据 Topic 到 NameServer 获取到 Broker 的路由信息,Consumer 也会定时获取 Topic 的路由信息。
  2. 消息
    消息(Message)就是要传输的信息。一条消息必须有一个主题(Topic),主题可以看做是你的信件要邮寄的地址。一条消息也可以拥有一个可选的标签(Tag)和额处的键值,它们可以用于设置一个业务 key 并在 Broker 上查找此消息以便在开发期间查找问题。
    Producer发送消息的时候,会预先选择(默认轮询)好该Topic下面的某一条队列地发送;Consumer消费的时候也会负载均衡地分配若干个队列,只拉取对应队列的消息
  1. 主题
    主题(Topic)可以看做消息的归类,它是消息的第一级类型。比如一个电商系统可以分为:交易消息、物流消息等,一条消息必须有一个 Topic 。Topic 与生产者和消费者的关系非常松散,一个 Topic 可以有0个、1个、多个生产者向其发送消息,一个生产者也可以同时向不同的 Topic 发送消息。一个 Topic 也可以被 0个、1个、多个消费者订阅。
  2. 标签
    标签(Tag)可以看作子主题,它是消息的第二级类型,用于为用户提供额外的灵活性。使用标签,同一业务模块不同目的的消息就可以用相同 Topic 而不同的 Tag 来标识。比如交易消息又可以分为:交易创建消息、交易完成消息等,一条消息可以没有 Tag 。标签有助于保持您的代码干净和连贯,并且还可以为 RocketMQ 提供的查询系统提供帮助。
  1. 消息队列
    消息队列(Message Queue),主题被划分为一个或多个子主题,即消息队列。一个 Topic 下可以设置多个消息队列,发送消息时执行该消息的 Topic ,RocketMQ 会轮询该 Topic 下的所有队列将消息发出去。下图是Broker 内部消息情况:
    在这里插入图片描述
  2. 消息消费模式
    消息消费模式有两种:集群消费(Clustering)和广播消费(Broadcasting)。默认情况下就是集群消费,该模式下一个消费者集群共同消费一个主题的多个队列,一个队列只会被一个消费者消费,如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。而广播消费消息会发给消费者组中的每一个消费者进行消费。
    集群消费:
    在这里插入图片描述
    广播消费:
    在这里插入图片描述
  3. 消息顺序
    消息顺序(Message Order)有两种:顺序消费(Orderly)和并行消费(Concurrently)。顺序消费表示消息消费的顺序同生产者为每个消息队列发送的顺序一致,所以如果正在处理全局顺序是强制性的场景,需要确保使用的主题只有一个消息队列。并行消费不再保证消息顺序,消费的最大并行数量受每个消费者客户端指定的线程池限制。
  4. 基于消息的分布式事务
    首先RocketMQ是一种最终一致性的分布式事务解决方案。
    在这里插入图片描述

1.生产者发送半消息到mq server
2.成功之后回调生产者方法
3.执行本地事务
4.根据执行结果反馈mq server状态,COMMIT_MESSAGE/ROLLBACK_MESSAGE/UNKNOW
5.如果没收到生产者的返回信息或者返回状态为UNKNOW,会进行回查
6.生产者查询处理本地事务
7.根据步骤6的结果反馈到mq server
8.根据结果来执行消息是发送还是丢弃

主要的实现代码如下:

//创建事务生产者
TransactionMQProducer producer = new
        TransactionMQProducer("transProducerGroup");
//设置事务监听器
producer.setTransactionListener(new TransactionExecuterTest());
//发送事务消息
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
      

监听类

/**
 * 事物执行器-测试类 具体来说,就是把消息的发送分成了2个阶段:Prepare阶段和确认阶段。 具体来说,上面的2个步骤,被分解成3个步骤:
 * (1)发送Prepared消息 (2) update DB (3) 根据update DB结果成功或失败,Confirm或者取消Prepared消息。
 * 当前2步执行成功了,最后1步失败,
 * RocketMQ会定期(默认是1分钟)扫描所有的Prepared消息,
 * 询问发送方,到底是要确认这条消息发出去?还是取消此条消息?
 * 
 */
public class TransactionExecutorTest implements TransactionListener {

	private Logger log = LoggerFactory.getLogger(this.getClass());

	private ConcurrentHashMap<String, Integer> countHashMap = new ConcurrentHashMap<>();

	// 设置最大回查次数
	private final static int MAX_COUNT = 5;

	/**
	 * 未决事务 该方法主要是告知RocketMQ消息是否需要提交还是回滚, 如果本地事务表存在记录,则认为提交,
	 * 如果不存在,可以设置回查次数,如果指定次数内还是未查到,则回滚,
	 * 否则返回未知,rocketmq回按一定的频率回查事务, 当然回查次数也有限制,默认为5次,可配置。
	 */
	@Override
	public LocalTransactionState checkLocalTransaction(MessageExt msg) {

		log.info("未决事物执行器接收值:{}", msg);
		//Integer status = 0;
		// 模拟业务-从数据库查查询,如果该表中存在记录,则提交,
		String bizUniNo = new String(msg.getBody());
		// 查询表,是否存在bizUniNo,如果存在,则返回COMMIT_MESSAGE,
		// 不存在,则记录查询次数,未超过次数,返回UNKNOW,超过次数,返回ROLLBACK_MESSAGE
		System.out.println(bizUniNo);
		return rollBackOrUnown(bizUniNo);
	}

	/**
	 * 本地事务  该方法,主要是设置本地事务状态,该方法与业务方代码在一个事务中,例如OrderServer.create中,只要本地事务提交成功,
	 * 该方法也会提交成功。    在这里,主要是往数据库表添加一条记录,在事务回查时,如果存在记录,就认为是该消息需要提交。
	 */
	@Override
	public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
		log.info("本地事物执行器接收值:{},{}", msg, arg);
		String bizUniNo = new String(msg.getBody());
		System.out.println(bizUniNo);
		// 模拟业务-插入数据库操作。。。
		// 模拟未决事务
		return LocalTransactionState.UNKNOW;
	}

	/**
	 * 查询是否回滚
	 * 
	 * @param bizUniNo
	 * @return
	 */
	private int query(String bizUniNo) {
		return 1; // select count(1) from t_message_transaction a where
					// a.biz_uni_no=#{bizUniNo}
	}

	/**
	 * 模拟提交、回滚、未决事务
	 * 
	 * @param bizUniNo
	 * @return
	 */
	public LocalTransactionState rollBackOrUnown(String bizUniNo) {
		if ("0,1,2,3,4".contains(bizUniNo)) {
			// 模拟提交事务
			return LocalTransactionState.COMMIT_MESSAGE;
		} else if ("5,6,7,8".contains(bizUniNo)) {
			// 模拟回滚事务
			return LocalTransactionState.ROLLBACK_MESSAGE;
		}
		Integer num = countHashMap.get(bizUniNo);
		// >9的内容模拟超过最大回查次数MAX_COUNT次,回滚
		if (num != null && ++num > MAX_COUNT) {
			countHashMap.remove(bizUniNo);
			return LocalTransactionState.ROLLBACK_MESSAGE;
		}

		if (num == null) {
			num = new Integer(1);
		}
		countHashMap.put(bizUniNo, num);
		return LocalTransactionState.UNKNOW;

	}

}
  1. 常见问题
  1. 消息的顺序问题
    如果要实现严格的消息一致性,则需要保证生产者 – 消息队列- 消费者是一对一对一的关系,非特殊情况不建议这么做,牺牲性能。

  2. 消息的重复问题
    只要存在数据交换,就无法避免消息重复这个问题,既然没法避免,就解决当客户端收到重复消息时该怎么办。

  • 消费端处理消息的业务逻辑保持幂等性
  • 保证每条消息都有唯一编号且有日志记录表,可以是整个消息,也可以是已经处理成功的消息的id,如果在日志表中就不再处理此消息,或者其它一些解决方案。

不管是哪种方式都应该在业务端去解决,最好不要让mq去做这个事,不要让小概率事件,影响整个性能,这或许是RocketMQ不实现去重的一个重要考虑。
RocketMQ不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重。
3.消息堆积问题
堆积的产生,消费的速度跟不上生产的速度,只出现在push模式中。
推送时会判断从所有Message Queue获取但未处理完成的消息个数(默认1000条),消息内存总大小(默认100M)、offset跨度(默认2000)。

// 设定阈值,提前处理。
  public ConsumeConcurrentlyStatus consumeMessage(//
            List<MessageExt> msgs, //
            ConsumeConcurrentlyContext context) {
        long offset = msgs.get(0).getQueueOffset();
        String maxOffset =  msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET);
        long diff = Long.parseLong(maxOffset) - offset;
        if (diff > 1000) {
            // TODO 消息堆积情况的特殊处理
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
        // TODO 正常消费过程
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }

4.发送失败

  • 如果是同步发送,默认重试次数2次producer.setRetryTimesWhenSendFailed(2); // 设置重试次数
  • 如果是异步发送,默认重试次数2次,通过retryTimesWhenSendAsyncFailed 来设置
  1. 消费失败
  • 异常或者处理超时【没有返回ConsumeConcurrentlyStatus状态】,mq重新发到broker,默认10秒后重新发到消费者组。
  • 默认重试16次,18个延时级别,默认为0。从源码来看当失败完直接跳到3级。实际生产中也没必要重试16次,因为重试了3次左右还不成功,一般就代码逻辑或者客观因素导致,继续重试也会是失败,可按自己的逻辑进行相应处理,1重试次数到达后发到死信队列,通过监控平台人工干预。
  • private String messageDelayLevel = “1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”;
  1. windows下启动broker时,注意指定autoCreateTopicEnable=true
  • 先启动name server,然后启动broker,start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值