消息队列,常用于应用间通信。
本篇文章基于RocketMQ官方文档
相关概念和组件
概念
Topic:消息分类,依靠topic来定义消息类型
Tag:消息二级分类,可选,同个topic用不同的tag区分消息类别
Message : 泛指MQ所传送的消息体
Producer:消息生产者
Consumer:消息消费者
组件
Name Server:有点类似于zookeeper,负责服务的注册与发现,维护Broker与Topic的映射关系
Broker:负责消息的存储与生产者消费者消息接收与分发,与Name Server建立长连接,保持心跳上传负责的topic信息
Producer:消息生产者,从Name Server获取Broker对应Topic映射关系,然后与Broker建立连接发送消息
Consumer:消息消费者,从Name Server获取Broker对应Topic映射关系,然后与Broker建立连接接收消息
Name Server如果出现宕机,当前Producer和Consumer已经获取的topic路由信息会保持,既有功能不受影响,但若是新增topic和Broker,将会无法传达消息
特点
先进先出
队列的特点,先进先出,不受人为的干预
2.发布-订阅
3.持久化
4.分布式
MQ的定义就是多应用并发通信的高性能中间件。
应用场景
应用解耦
应用间通信不使用MQ会发生并发问题,同时可能会消耗应用资源,影响性能。
通知
应用b一直在监听MQ中来自应用a的消息,逐条消费。
应用分发
MQ是一对多的,可以在多应用间分发(广播)消息。
限制流量
具有队列的特性,这就说明MQ不存在并发问题,同时有且仅有一条数据被消费。
分布式事务
RocketMQ
项目中使用了RocketMQ,这个mq中间件只提供了按topic进行发布-订阅的通信方式,并未提供点对点的消息队列。
涉及的一些概念:
(1)NameServer:在MQ集群中做的是做命名服务,更新和路由发现 broker服务; (2)Broker-Master:broker 消息主机服务器; (3)Broker-Slave:broker 消息从机服务器; (4)Producer:消息生产者; (5)Consumer:消息消费者;
开始使用RocketMQ
quick-start:发送与接收消息
RocketMQ有三种消息发送方式,示例代码均来自于官网。
同步消息,是很可靠的传输方式:
public static void main(String[] args) throws Exception { //Instantiate with a producer group name. DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses. producer.setNamesrvAddr("localhost:9876"); //Launch the instance. producer.start(); for (int i = 0; i < 100; i++) { //Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); //Call send message to deliver message to one of brokers. SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } //Shut down once the producer instance is not longer in use. producer.shutdown(); }
异步消息,同样可靠,消息生产者追求效率可以采用此种方式:
public static void main(String[] args) throws Exception { //Instantiate with a producer group name. DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses. producer.setNamesrvAddr("localhost:9876"); //Launch the instance. producer.start(); producer.setRetryTimesWhenSendAsyncFailed(0); for (int i = 0; i < 100; i++) { final int index = i; //Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId()); } @Override public void onException(Throwable e) { System.out.printf("%-10d Exception %s %n", index, e); e.printStackTrace(); } }); } //Shut down once the producer instance is not longer in use. producer.shutdown(); }
单向消息,不回传结果,传输可靠性不高,但是效率巨高。比如日志收集:
public static void main(String[] args) throws Exception{ //Instantiate with a producer group name. DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); // Specify name server addresses. producer.setNamesrvAddr("localhost:9876"); //Launch the instance. producer.start(); for (int i = 0; i < 100; i++) { //Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); //Call send message to deliver message to one of brokers. producer.sendOneway(msg); } //Shut down once the producer instance is not longer in use. producer.shutdown(); }
接收消息:
public static void main(String[] args) throws InterruptedException, MQClientException { // Instantiate with specified consumer group name. DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name"); // Specify name server addresses. consumer.setNamesrvAddr("localhost:9876"); // Subscribe one more more topics to consume. consumer.subscribe("TopicTest", "*"); // Register callback to execute on arrival of messages fetched from brokers. consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); //Launch the consumer instance. consumer.start(); System.out.printf("Consumer Started.%n"); }
消息的顺序性保证:Selector
首先需要了解的是,对于部署了多个队列(多个broker)的topic消息,在选择消息队列时采用的是随机算法(或hash、或随机、或依赖时间戳)在消费消息时会轮询Queue列表消费,这样能最大限度的做到负载均衡,但并不能保证消息的顺序性,或者说在同一个topic多节点部署broker时,当消息量很大时,大概率消息的消费顺序是与消息生产不一致的。但有时很多时候消息要保证传输的顺序性,即FIFO,例如支付流程中先后发出的支付请求和支付成功请求。producer可以通过调用
send(Message msg, MessageQueueSelector selector, Object arg)
在发送消息时指定selector,从而按指定的规则选择发送消息的通道(队列)。
MessageQueueSelector是函数式接口:
public interface MessageQueueSelector { MessageQueue select(final List<MessageQueue> mqs, final Message msg, final Object arg); }
其中mqs就是消息队列的列表,msg是消息体,arg是自定义的参数。
通过继承该接口定义新的队列选择器,发送消息:
public static void main(String[] args) throws Exception { //Instantiate with a producer group name. MQProducer producer = new DefaultMQProducer("example_group_name"); //Launch the instance. producer.start(); String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 100; i++) { int orderId = i % 10; //Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg, new MessageQueueSelector() { @Override public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) { Integer id = (Integer) arg; int index = id % mqs.size(); return mqs.get(index); } }, orderId); System.out.printf("%s%n", sendResult); } //server shutdown producer.shutdown(); }
同样,消费端也需要做相应的处理
//TODO
参考链接:MQ(消息队列)常见的应用场景解析