消息中间件学习笔记(一)——基础学习

目录

一、基本概念

二、JMS、AMQP、MQTT

1、JMS

a)、JMS对象模型要素

b)、JMS消息类型

c)、JMS消息格式

d)、JMS版本

2、AMQP

a)、AMQP的消息路由

b)、AMQP要素

c)、AMQP消息类型

3、MQTT

三、常见的消息中间件


一、基本概念

分布式系统的通信方式主要有:

  • RPC(Remote Procedure Call Protocol),一般是C/S方式,同步的,跨语言跨平台,面向过程
  • CORBA(Common Object Request Broker Architecture),从概念上扩展了RPC,面向对象的,企业级的
  • RMI(Remote Method Invocation),面向对象方式的 Java RPC
  • WebService,基于Web,C/S或B/S,跨系统跨平台跨网络,多为同步调用, 实时性要求较高
  • MOM(Message oriented Middleware),面向消息中间件,主要适用于消息通道、消息总线、消息路由和发布/订阅的场景。目前主流标准有JMS(Java Message Service)、AMQP(Advanced Message Queuing Protocol)和STOMP(Streaming Text Oriented Messaging Protocol)

对于“分布式消息中间件”,需要先理解三个概念——分布式、中间件、消息中间件:

  • 分布式:各个组件分布在网络计算机上,组件之间通过消息协调行动
  • 中间件:可以是上层应用程序与底层服务的桥梁,也可以是应用于应用之间的桥梁,不能直接给最终用户使用,不能直接给客户带来价值的软件称为中间件
  • 消息中间件:在分布式系统中,支持发送和接收消息的组件

消息中间件的应用场景:

  • 业务解耦:应用之间不需要知道对方的存在,只需要发布、接收消息
  • 削峰填谷:若上游系统的吞吐能力高于下游系统,消息中间件可以在峰值时堆积消息,峰值过后下游系统慢慢消费消息
  • 事件驱动:系统与系统之间可以通过消息传递的形式驱动业务

二、JMS、AMQP、MQTT

1、JMS

JMS即Java消息服务(Java Message Service)应用程序接口,是Java平台中关于面向消息中间件(MOM)的API。它是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持(类似于JDBC,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务)。

a)、JMS对象模型要素

  1. 连接工厂(ConnectionFactory),由管理员创建,并绑定到JNDI树中。客户端使用JNDI查找连接工厂,然后利用连接工厂创建一个JMS连接
  2. 连接(Connection),表示JMS客户端和服务器端之间的一个活动的连接,由客户端通过调用连接工厂的方法建立
  3. 会话(Session),表示JMS客户与JMS服务器之间的会话状态。JMS会话建立在JMS连接上,表示客户与服务器之间的一个会话线程(一个连接可以有多个会话)
  4. 目的(Destination),又称为消息队列,是实际的消息源
  5. 生产者和消费者(Message Producer、Message Consumer),由Session创建,用于发送和接收消息
  6. 消息(Message), 包含消息头,一组消息属性,一个消息体
  7. 消息监听者(MessageListener),提供给消费者监听消息使用,在添加了某个监听器之后,一旦消费到达,则会调用其onMessage方法

消息如何从producer端达到consumer端由消息路由来决定。在JMS中,消息路由非常简单,由producer和consumer链接到同一个queue(p2p)或者topic(pub/sub)来实现消息的路由。JMSconsumer同时支持message selector(消息选择器),通过消息选择器,consumer可以只消费那些通过了selector筛选的消息

b)、JMS消息类型

 点对点(Point-to-Point):

  • 一个生产者对应一个消费者,两者没有依赖,生产者只需要把消息放到相应队列
  • 队列中的消息只有被消费或者超时才会被销毁

发布/订阅(Publish/Subscribe):

  • 一个消息可以传给多个订阅者
  • 默认情况下,发布者发布消息时,订阅者必须在线同时消费,即发布主题时,订阅者必须在线监听
  • 为了解除这种耦合,JMS提供可持久化订阅,即针对于某些特定的订阅者,Topic会缓存至其订阅者消费或消息超时

c)、JMS消息格式

  • StreamMessage -- Java原始值的数据流
  • MapMessage--一套名称-值对
  • TextMessage--一个字符串对象
  • ObjectMessage--一个序列化的 Java对象
  • BytesMessage--字节消息

Message由消息头(header)、属性(properties)和消息体(body)组成,消息头中的字段有:

  • JMSMessageID(类型为String),消息的唯一标识ID,由消息提供者设置,并且必须以ID开头。 因为消息ID可能导致JMS提供程序产生一些开销,消息提供者可以建议JMS提供程序JMS应用程序不依赖于这个消息头的值(通过MessageProducer.setDisableMessageID()方法设置),如果JMS提供程序同意该建议,则消息标识必须设置为null。但JMS提供程序可以忽略此调用并始终分配消息ID。
  • JMSDestination(类型为Destination),消息目的地,由消息提供者设置;
  • JMSTimestamp(类型为long),消息提供者发送消息的时间。JMS提供者可以建议JMS生产者不设置JMSTimestamp头(通过MessageProducer.setDisableMessageTimestamp()),如果JMS生产者接受此建议,则它则将JMSTimestamp设置为零;
  • JMSPriority(类型为 int),消息优先级,0最小9最大;
  • JMSReplyTo(类型为Destination),回复消息应发送的地方,由发送消息的JMS程序设置;
  • JMSType(类型为String),消息类型;
  • JMSExpiration(类型为long),消息失效时间。这个值在发送过程中计算,是发送方法的生存时间(time-to-live)值和当前时间值的和。消息的超时值可以使用MessageProducer.setTimeToLive()方法设置该生产者发送的所有消息的生存时间或使用MessageProducer.send()方法来设置单个消息的超时值(单位均为毫秒)。提供者不会发送过期的消息,值 0 表明消息不会过期;
  • JMSDeliveryMode(类型为int),消息发送模式,包含值 DeliveryMode.PERSISTENT 或者 DeliveryMode.NON_PERSISTENT。持久性消息被传输并且只被传输一次,非持久性消息最多被传输一次。要知道“最多一次”包括根本 不传输。非持久性消息在应用程序或者系统出故障时被提供者弄丢。由于持久化消息提供了额外的可靠性保护,所以需要更多的空间和性能消耗。在决定消息的发送模式时,必须仔细考虑,在可靠性和性能之间进行权衡。
  • JMSCorrelationID(类型为String),通常用于链接响应消息和请求消息。在大多数情况下,JMSCorrelationID用于将一条消息标记为对JMSMessageID标示的上一条消息的应答,不过,JMSCorrelationID可以是任何值,不仅仅是JMSMessageID;
  • JMSRedelivered(类型为boolean),消息是否重复发送过,如果该消息之前发送过,那么这个属性的值需要被设置为true, 客户端可以根据这个属性的值来确认这个消息是否重复发送过,以避免重复处理;

开发者在JMS客户端中可设置的message字段仅有JMSType、JMSReplyTo和JMSCorrelationID,设置其他字段均无效。JMSPriority和JMSExpiration由开发者设置生产者来间接设置。

d)、JMS版本

JMS重要的版本有1.1和2.0,JMS2.0随着java7发布,2.0兼容1.1。2.0的重要变化有:简化API、简化配置、支持多个消费者共享topic。

(1)、简化API

提供三个新接口:JMSContext(综合了1.1里的Connection和Session)、JMSProducer(1.1里MessageProducer的替换者,支持消息发送选项、头信息,还可以用方法链配置属性)、JMSConsumer(替换1.1里的MessageConsumer,用法和MessageConsumer类似)。示例如下:

JMS1.1:

public void sendMessage11(ConnectionFactory connectionFactory, Queue queue, String msg) {
   Connection connection = null;
   try {
      connection = connectionFactory.createConnection();
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
      MessageProducer messageProducer = session.createProducer(queue);
      TextMessage textMessage = session.createTextMessage(msg);
      messageProducer.send(textMessage);
   } catch (JMSException ex) {
      // handle exception
   } finally {
      if (connection != null) {
         try {
            connection.close();
         } catch (JMSException ex) {
         }
      }      
   }
}

JMS2.0:

public void sendMessage20(ConnectionFactory connectionFactory, Queue queue, String msg) {
   try (JMSContext context = connectionFactory.createContext();){
      context.createProducer().send(queue, msg);
   } catch (JMSRuntimeException ex) {
      // handle exception
   }
}

主要优化为:

  • 1.1要分别创建Connection和Session对象,而2.0只创建一个JMSContext对象。
  • 1.1要在发送完消息之后释放连接(MessageProducer和Session不用分别close,连接关闭后会被删除),但2.0里的JMSContext实现了Java SE 7里的java.lang.AutoCloseable接口,我们就可以使用Java SE 7里的try-with-resources块,这个块执行完之后会自动调用JMSContext的close方法释放资源。
  • 1.1里创建Session对象时需要指定会话是否使用本地事务,以及消息确认方式。2.0提供了默认配置,也提供了API去设置其他的会话模式。
  • 上面的例子是发送一个文本消息。1.1里需要创建消息对象(TextMessage),将消息体设置为指定的字符串。2.0直接把字符串传给send方法就可以了,JMS提供者会自动完成1.1里需要开发人员做的事情。
  • 1.1的API抛出的都是受检查的异常JMSException。2.0里新的API则会抛出运行时异常JMSRuntimeException,所以我们的代码里可以不再显式地进行捕获。

(2)、直接将JMSContext注入:因为Java7的应用服务器会负责JMSContext的创建和关闭,开发人员只需要把它注入应用就可以了。

// @Inject注解告诉容器创建JMSContext
@Inject 
// @JMSConnectionFactory注解告诉容器应用使用的连接工厂的JNDI名
@JMSConnectionFactory("jms/connectionFactory") 
private JMSContext context;

@Resource(lookup="jms/queue")
private Queue queue;

public void sendMessage(String msg) {
   context.send(queue, msg);
}

(3)、更简单的资源配置

JMS使用连接工厂、目的地时需要进行配置(连接工厂需要知道JMS提供者所在的主机名和监听端口,应用通过连接工厂创建到JMS提供者的连接;队列或主题则要知道作为消息端点的物理队列或物理主题),由于每个JMS提供者创建、配置它们的方式不同,所以JMS推荐单独创建、配置,然后绑定到JNDI树上,JMS应用使用时再通过JNDI查找、直接获取连接工厂或目的地对象。Java EE规范推荐把代码和配置分开,是为了保证应用的可移植性,代码不需要知道具体的物理细节(像主机名、端口、物理名称等)。

但对于大规模集群上部署的应用来说,代码和配置分离反而会增加额外的工作量,尤其是在集群不能统一配置的情形下。而且在这样的生产环境下,应用重部署和迁移的情况相对较少,那能否在应用部署的过程中就一起把应用要使用的资源对象给创建出来、省去管理员单独配置的环节呢?JMS2.0解决了这个问题,开发人员可以在代码里使用新增的JMS资源定义注解,也可以在部署描述符里定义,也可以两者结合。需要注意的是,使用JMS资源定义注解定义的连接工厂和目的地必须放在java:comp、java:module、java:app或者java:global名字空间里,生命周期和应用保持一致,即部署应用时生成,解部署应用时就会删除。

@JMSConnectionFactoryDefinition(
    name="java:global/jms/connectionFactory",
    maxPoolSize = 30,
    minPoolSize= 20,
    properties = {
        "addressList=mq://localhost:7676",
        "reconnectEnabled=true"
    }
) 
@JMSDestinationDefinition(
    name = "java:global/jms/queue",
    interfaceName = "javax.jms.Queue",
    destinationName = "queue"
  )
public class TestServlet extends HttpServlet {
  ...
}

如果需要定义多个连接工厂或目的地,可以使用JMSConnectionFactoryDefinitions和JMSDestinationDefinitions注解包含多个。

@JMSConnectionFactoryDefinitions({
    @JMSConnectionFactoryDefinition(
       name="java:global/jms/connectionFactory1",
       maxPoolSize = 30,
       minPoolSize= 20,       
       properties = {
          "addressList=mq://localhost:7676",
          "reconnectEnabled=true"
       }
    ),
    @JMSConnectionFactoryDefinition(
       name="java:global/jms/connectionFactory2",
       maxPoolSize = 30,
       minPoolSize= 20,
       properties = {
          "addressList=mq://localhost:7677",
          "reconnectEnabled=true"
       }
    ) 
})
@JMSDestinationDefinitions({
    @JMSDestinationDefinition(
       name="java:global/jms/queue1",
       interfaceName = "javax.jms.Queue",
       destinationName = "queue1"
    ),
    @JMSDestinationDefinition(
       name="java:global/jms/queue2",
       interfaceName = "javax.jms.Queue",
       destinationName = "queue2"
    ) 
})
public class TestServlet extends HttpServlet {
  ...
}

如果不想让资源的配置侵入代码,或者开发的时候还不确定JMS提供者的真实信息(比如主机地址和端口),可以选择在应用的部署描述里定义连接工厂和目的地,比如web.xml。

<jms-connection-factory>
   <name>java:global/jms/connectionFactory</name>
   <max-pool-size>30</max-pool-size>
   <min-pool-size>20</min-pool-size>
   <property>
      <name>addressList</name>
      <value>mq://localhost:7676</value>
   </property>
   <property>
      <name>reconnectEnabled</name>
      <value>true</value>
   </property>    
</jms-connection-factory>

<jms-destination>
   <name>java:global/jms/queue</name>
   <interfaceName>javax.jms.Queue</interfaceName>
   <destinationName>queue</destinationName> 
</jms-destination>

(4)、延迟发送:消息生产者可以指定一个延迟发送时间,JMS提供者等过了这么长时间之后再发送消息,通过MessageProducer和JMSProducer的setDeliveryDelay(毫秒)方法设置。

(5)、多个消费者共享一个topic:在1.1中,多个消费者可以同时订阅一个topic,若这个topic有消息a、b、c,则每个消费者都会得到一份a、b、c;2.0可以实现topic共享,即这个消费者消费a,另一个消费b、c。这样有利于多线程。

private void createSharedConsumer(ConnectionFactory connectionFactory, Topic topic) throws JMSException {
   Connection connection = connectionFactory.createConnection();
   Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
   MessageConsumer messageConsumer = session.createSharedConsumer(topic, "ourSubscription"); // 需要指定共享订阅的名称,以便多个消费者能确定彼此共享的订阅
   connection.start();
   Message message = messageConsumer.receive(10000);
   while (message != null) {
      System.out.println("Message received: " + ((TextMessage) message).getText());
      message = messageConsumer.receive(10000);
   }
   connection.close();
}

(6)、JMSXDeliveryCount从可选属性变为必需属性,这个值标识JMS提供者给消费者发送消息的尝试次数。消费者可以根据这个值确认消息是否被重复发送了,进而可以进行特殊的处理,比如超过多少次时把这条消息放入死信队列。

class SampleMessageListener implements MessageListener {
   @Override
   public void onMessage(Message message) {
      try {
         int deliveryCount = message.getIntProperty("JMSXDeliveryCount");
         if (deliveryCount < 10){
             // 故意抛出运行时一场,模拟消息处理失败的情形,使得JMS提供者能重发消息
             throw new RuntimeException("Exception thrown to simulate a bad message");
         } else {
             // 消息已经被发送了10次,放弃重发,进行其他的处理
         }
      } catch (JMSException e) {
         throw new RuntimeException(e);
      }
   }
}

2、AMQP

AMQP(advanced message queuing protocol)是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。AMQP不从API层进行限定,而是直接定义网络交换的数据格式,这是其和JMS的本质差别。AQMP可以用http来进行类比,即不关心实现的语言,只要大家都按照相应的数据格式去发送报文请求,不同语言的client均可以和不同语言的server链接。

a)、AMQP的消息路由

AMQP消息路由和JMS存在一些差别,在AMQP中增加了exchange和binding的角色。producer将消息发送给exchange,binding决定exchange的消息应该发送到那个queue,而consumer直接从queue中消费消息。queue和exchange的绑定(binding)由consumer来决定。

b)、AMQP要素

  • 消息(Message):消息是不具名的,由消息头和消息体组成。消息体是不透明的,而消息头是由一系列的可选属性组成,包括routing-key(路由键)、priority(权重)、delivery-mode(指出该消息可能需要持久化存储)等;
  • 生成者(Pulisher):向交换器发布消息的客户端应用程序;
  • 交换器(Exchange):用来接收生产者发送的消息,路由给服务器中的队列。它具有四种类型:direct(默认:点对点),fanout,topic,headers;
  • 消息队列(Queue):用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或者多个队列;
  • 绑定(Binding):用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,Exchange和Queue的绑定可以是多对多的关系;
  • 网络连接(Connection):例如TCP连接;
  • 信道(Channel):信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息,订阅队列还是接收消息也都是由信道完成的。因为对于操作系统来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接;
  • 消费者(Consumer):一个从消息队列中取得消息的客户端应用程序;
  • 虚拟主机(Virtual Host):表示一批交换器、消息队列和相关的对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。virtual host 是AMQP概念的基础,必须在连接时指定;
  • 实体(Broker):表示消息队列服务器实体。

c)、AMQP消息类型

Direct单播,消息中的路由键(routing-key) 如果和Binding中的binding key一致,交换器就发到对应的队列中。

Fanout广播,每个发到fanout类型交换器的消息都会分到所有绑定队列上去。fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。fanout类型转发消息是最快的。

Topic有选择的广播,topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串分成单词,这些单词用点隔开(a.b)。它同样也会识别两个通配符:符号:# 匹配0或者多个单词,*匹配一个单词。

其他消息类型不常用。

3、MQTT

MQTT(Message Queueing Telemetry Transport)是专门为小设备设计的。计算性能不高的设备不能适应AMQP上的复杂操作,它们需要一种简单而且可互用的方式进行通信。这是MQTT的基本要求,而如今,MQTT是物联网(IOT)生态系统中主要成分之一。

MQTT特点:面向流,内存占用低;为小型无声设备之间通过低带宽发送短消息而设计;不支持长周期存储和转发;不允许分段消息;支持主题发布-订阅;不支持事务;不支持安全连接等。

三、常见的消息中间件

 ActiveMQRabbitMQKafka
跨语言支持(java优先)语言无关支持(java优先)
支持协议AMQP、XMPP、OpenWire、StompAMQP自有协议
单机吞吐量万级万级十万级
topic数量对吞吐量影响  topic从几十个到几百个的时候,吞吐量会大幅度下降
时效性毫秒级微秒级(延迟最低)毫秒级
可靠性有较低概率丢失数据 可以做到0丢失
优点安装部署方便由Erlang编写,继承了Erlang天生的并发性;最初用于金融,稳定性、安全性有保障可动态扩展节点;高吞吐量;无限扩容;消息可指定追溯
缺点有数据丢失概率Erlang语言难度大;不支持动态扩展严格的消息顺序机制,不支持消息优先级设定;不支持标准的消息协议,不利于平台迁移
综合评价适合中小企业适合对稳定性要求较高的企业应用于对实时性可靠性要求较低的场景

参考链接:

https://blog.csdn.net/hpttlook/article/details/23391967
https://blog.csdn.net/belonghuang157405/article/details/83184388
https://www.cnblogs.com/zhangyu1024/p/6138166.html
https://blog.csdn.net/weixin_39352976/article/details/7988135
https://ivywang.iteye.com/blog/1930201

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值