1. ActiveMQ概述与HelloWorld

1. JMS简介

  1. JMS全称:Java Message Service ,中文:Java 消息服务,是 Java 的一套 API 标准
  2. 最初的目的是为了使应用程序能够访问现有的 MOM系
  3. MOM:Message Oriented Middleware,即消息中间件,它可以利用高效可靠的消息传递机 制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成
  4. 常见 MOM 系统包括 Apache 的 ActiveMQ、 阿里巴巴的 RocketMQ、IBM 的 MQSeries、Microsoft 的 MSMQ、BEA 的 RabbitMQ 等。并非全部的 MOM 系统都遵循 JMS 规范,也就是并非全提供了JMS实现,提供了JMS 实现的 MOM,又被称为 JMSProvider
  5. JMS与MOM的关系类似JDBC和数据库之间的关系

2 消息中间件的应用场景

  1. 异步通信:消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它
  2. 过载保护:不将请求直接发送给服务端,而是由服务端自己来取,这样防止大量请求同时到达服务端,使整个系统崩溃
  3. 解耦:A和B直接相连时,一旦B死掉,那么A的功能也都不好用了,采用消息中间件解耦,可以保证B死掉,A也能正常发送给队列,当B复活后,又可以继续完成之前队列中的任务
  4. 消息通讯:客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果
  5. 顺序保证:在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
  6. 数据流处理:分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择
  7. 扩展性:消息中间件可以很容易横向扩容

3 常用消息队列比较

特性MQ ActiveMQ RabbitMQ RocketMQ Kafka
生产者消费者模式 支持 支持 支持 支持
发布订阅模式 支持 支持 支持 支持
请求回应模式 支持 支持 不支持 不支持
Api完备性
多语言支持 支持 支持 java 支持
单机吞吐量 万级 万级 万级 十万级
消息延迟 微秒级 毫秒级 毫秒级
可用性 高(主从) 高(主从) 非常高(分布式) 非常高(分布式)
消息丢失 理论上不会丢失 理论上不会丢失
文档的完备性
提供快速入门
社区活跃度
商业支持 商业云 商业云

4 JMS中的角色

  1. Broker:消息服务器,相当于server,提供消息核心服务
  2. Provider:消息生产者,是由会话创建的一个对象,用于把消息发送到一个目的地
  3. Consumer:消息消费者,是由会话创建的一个对象,它用于接收发送到目的地的消息
    1. 消费消息的两种方式:
      1. 同步消费:调用消费者的receive方法,从目的地中显式提取消息。receive方法可以一直阻塞到消息到达
      2. 异步消费:客户端可以为消费者注册一个消息监听器,以定义在消息到达时所采取的动作
  4. p2p:基于点对点的消息模型
    1. 消息生产者生产消息发送到 queue 中,然后消息消费者从 queue 中取出并且消费消息
    2. 消息被消费以后,queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消息
    3. Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费、其它 的则不能消费此消息了
    4. 当消费者不存在时,消息会一直保存,直到有消费消费
      在这里插入图片描述
  5. pub/sub:基于订阅/发布的消息模型
    1. 消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消
    2. 和点对点方式不同,发布到 topic 的消息会被所有订阅者消费
    3. 当生产者发布消息,不管是否有消费者。都不会保存消息 一定要先有消息的消费者,后有消息的生产者
      在这里插入图片描述
  6. PTP 和 PUB/SUB 简单对比
1 Topic Queue
Publish Subscribe messaging 发布 订阅消息 Point-to-Point 点对点
有无状态 topic 数据默认不落地,是无状态的,也就是发消息时,如果接收的人不在线,那么该消息他就收不到了。 Queue 数据默认会在 mq 服 务器上以文件形式保存,比如 Active MQ 一 般 保 存 在 $AMQ_HOME\data\kahadb 下 面。也可以配置成 DB 存储。
完整性保障 并不保证 publisher 发布的每条数 据,Subscriber 都能接受到。 Queue 保证每条数据都能 被 receiver 接收。消息不超时。
消息是否会丢失 一般来说 publisher 发布消息到某 一个 topic 时,只有正在监听该 topic 地址的 sub 能够接收到消息;如果没 有 sub 在监听,该 topic 就丢失了。 Sender 发 送 消 息 到 目 标 Queue, receiver 可以异步接收这 个 Queue 上的消息。Queue 上的 消息如果暂时没有 receiver 来 取,也不会丢失。前提是消息不 超时。
消息发布接 收策略 一对多的消息发布接收策略,监 听同一个topic地址的多个sub都能收 到 publisher 发送的消息。Sub 接收完 通知 mq 服务器 一对一的消息发布接收策 略,一个 sender 发送的消息,只 能有一个 receiver 接收。 receiver 接收完后,通知 mq 服务器已接 收,mq 服务器对 queue 里的消 息采取删除或其他操作。
  1. Queue:队列存储,常用与点对点消息模型 ,默认只能由唯一的一个消费者处理。一旦处理消息删除
  2. Topic:主题存储,用于订阅/发布消息模型主题中的消息,会发送给所有的消费者同时处理。只有在消息可以重复处 理的业务场景中可使用。Queue/Topic都是 Destination 的子接口
  3. ConnectionFactory :连接工厂,jms中用它创建连接。连接工厂是客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQConnectionFactory
  4. Connection:JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接
  5. Destination:消息的目的地。在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)
  6. Session:JMS Session是生产和消费消息的一个单线程上下文。会话用于创建消息生产者(producer)、消息消费者(consumer)和消息(message)等。会话提供了一个事务性的上下文,在这个上下文中,一组发送和接收被组合到了一个原子操作中

5 JMS的消息格式与类型

5.1 格式

  1. 消息头
  2. 消息属性
  3. 消息体

5.2 类型

5.2.1 TextMessage:文本消息
5.2.2 MapMessage:k-v
  1. 发送端
MapMessage mapMessage = session.createMapMessage();
mapMessage.setString("name","lucy");
mapMessage.setBoolean("yihun",false);
mapMessage.setInt("age", 17);
producer.send(mapMessage);
  1. 接收端
Message message = consumer.receive();
MapMessage mes = (MapMessage) message;
System.out.println(mes);
System.out.println(mes.getString("name"));
5.2.3 BytesMessage:字节流,一般用于传输小文件、图片
  1. 发送端
BytesMessage bytesMessage = session.createBytesMessage();
bytesMessage.writeBytes("str".getBytes());
bytesMessage.writeUTF("哈哈");
  1. 接收端
//方法一
if(message instanceof BytesMessage) {
	BytesMessage bm = (BytesMessage)message;
	 byte[] b = new byte[1024];
           int len = -1;
           while ((len = bm.readBytes(b)) != -1) {
               System.out.println(new String(b, 0, len));
           }
}
//方法二:使用ActiveMQ给提供的便捷方法,但要注意读取和写入的顺序,写入是什么顺序,读取时就是什么顺序
bm.readBoolean();
bm.readUTF();
5.2.4 StreamMessage:java原始的数据流
5.2.5 ObjectMessage:序列化的java对象
  1. 发送端
//必须先将要序列化的对象,添加到信任列表,否则反序列化时,会抛出如下异常
//Exception in thread "main" javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.mashibing.mq.Girl! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
connectionFactory.setTrustedPackages(
				new ArrayList<String>(Arrays.asList(new String[] { Girl.class.getPackage().getName() })));
Girl girl = new Girl("qiqi",25,398.0);
Message message = session.createObjectMessage(girl);
  1. 接收端
if(message instanceof ActiveMQObjectMessage) {
	Girl girl = (Girl)((ActiveMQObjectMessage)message).getObject();
	System.out.println(girl);
	System.out.println(girl.getName());
}

6 消息的特性

6.1 持久性
  1. 持久化消息后,即使ActiveMQ宕机,消息也不会消失,消息被消费者消费掉后,数据库中内容才会消失
  2. MQ接受消息,还需要向数据库中写,会影响效率,所以不建议使用大数据库,而是推荐使用kahadb这种小型数据库,速度非常快
  3. 生产环境几乎不可能用mysql或oracle进行消息持久化存储,此处用oracle是为了方便观察消息在数据库中的存储形式
  4. JMS中的持久化
//MessageProducer
//DeliveryMode.PERSISTENT:持久化消息
//DeliveryMode.NON_PERSISTENT:不持久化消息
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
6.1.1 KahaDB存储
  1. KahaDB是默认的持久化策略,所有消息顺序添加到一个日志文件中,同时另外有一个索引文件记录指向这些日志的存储地址,还有一个事务日志用于消息回复操作。是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化
  2. 在data/kahadb这个目录下,会生成四个文件,来完成消息持久化
    1. db.data:消息的索引文件,本质上是B-Tree(B树),使用B-Tree作为索引指向db-*.log里面存储的消息
    2. db.redo:用来进行消息恢复
    3. db-.log:存储消息内容。新的数据以APPEND的方式追加到日志文件末尾。属于顺序写入,因此消息存储是比较 快的。默认是32M,达到阀值会自动递增
    4. lock:锁,写入当前获得kahadb读写权限的broker ,用于在集群环境下的竞争处理
  3. 配置
<persistenceAdapter> 
	<!--directory:保存数据的目录;journalMaxFileLength:保存消息的文件大小,是每个数据文件大小,超过后滚动 --> 
	<kahaDBdirectory="${activemq.data}/kahadb"journalMaxFileLength="16mb"/> </persistenceAdapter>

  1. 特性
    1. 日志形式存储消息
    2. 消息索引以 B-Tree 结构存储,可以快速更新
    3. 完全支持 JMS 事务
    4. 支持多种恢复机制
6.1.2 JDBC存储
  1. activemq.xml
<!--设置数据源,名为oracle-ds,使用org.apache.commons.dbcp.BasicDataSource来管理连接池-->
<bean id="oracle-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
	<property name="driverClassName" value="oracle.jdbc.OracleDriver"/> 
	<property name="url" value="jdbc:oracle:thin:@192.168.15.110:1521:fcrhost"/> 
	<property name="username" value="c50hst"/>
	<property name="password" value="c50hst"/>
	<property name="maxActive" value="200"/>
	<property name="poolPreparedStatements" value="true"/> 
</bean>
...
<persistenceAdapter>
	<!--屏蔽之前使用的kahadb进行持久化,并如果数据库中没有表,自动建立表-->
    <!--<kahaDB directory="${activemq.data}/kahadb"/>-->
	<jdbcPersistenceAdapter dataSource="#oracle-ds" createTablesOnStartup="true" /> 
</persistenceAdapter>
  1. 数据库连接池和数据库jdbc连接,需要依赖如下jar包
    1. commons-dbcp-1.4.jar
    2. commons-pool-1.6.jar
    3. ojdbc6.jar
  2. 使用JDBC持久化方式,数据库默认会创建3个表
  3. activemq_msgs:用于存储消息,Queue和Topic都存储在这个表中
id:自增的数据库主键 
container:消息的destination 
msgid_prod:消息发送者客户端的主键 
msg_seq:是发送消息的顺序,msgid_prod+msg_seq可以组成jms的messageid 
expiration:消息的过期时间,存储的是从1970-01-01到现在的毫秒数 
msg:消息本体的java序列化对象的二进制数据 
priority:优先级,从0-9,数值越大优先级越高 
xid:用于存储订阅关系。如果是持久化topic,订阅者和服务器的订阅关系在这个表保存。
  1. activemq_acks:用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存
container:消息的destination 
sub_dest:如果是使用static集群,这个字段会有集群其他系统的信息 
client_id:每个订阅者都必须有一个唯一的客户端id用以区分 
sub_name:订阅者名称 
selector:选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性and和or操作 
last_acked_id:记录消费过的消息的id。
  1. activemq_lock:在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker,其他的只能作为备份等待Master Broker不可用,才可能成为下一个Master Broker。这个表用于记录哪个Broker是当前的Master Broker
6.1.3 JDBC Message store with ActiveMQ Journal
  1. 消息中间件收到消息后,向内存中存储消息同时,往文件里写,然后就返回ack
  2. 此时有consumer来消费,就会从文件中删除
  3. 如果指定时间内未被消费,会通过jdbc写入数据库,而且是批量地写和删(不是单条),减少写入数据库中的数据量
6.2 本地事务
  1. 在一个JMS客户端,可以使用本地事务来组合消息的发送和接收
  2. 可以在创建session时,指定开启事务
  3. 当事务设置为true,应答模式默认只能是Session.SESSION_TRANSACTED,即使设置为其他的,也不会生效,当设置为其他值时,手动调用message.acknowledge(),会和producer.send类似,只要没最终通过session.commit提交事务,就无法通知ActiveMQ该消息被确认
  4. 开启事务可以避免频繁发送消息造成的网络连接问题,也可以在出现问题时,及时回滚
//Connection
//true表示开启事务,如果事务开启,只要不调用session.commit方法, 即使调用了producer.send或message.acknowledge(),消息也不会被发送到消息队列、消息也无法被确认
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
6.3 可靠性
  1. 消息的成功消费通常包含三个阶段:客户接收消息、客户处理消息和消息被确认
  2. 消息队列收到确认包后,才会将该消息从消息队列中移除(如果持久化消息,会从数据库中移除)
  3. 在一个session中consumer接收到的消息,在另一个consumer中无法重复接收该消息,如果第一个客户端始终没确认消息,且最后该客户端对应session断开,那么消息会重新投递给第二个客户端
  4. 在事务性会话中,当一个事务被提交的时候,确认自动发生
  5. 在非事务性会话中,消息何时被确认取决于创建会话时的签收模式(acknowledgement mode),acknowledgement其实就是ack,也就是确认包
//1. Session.AUTO_ACKNOWLEDGE
	//1. 消费者调用consumer.receive方法成功后
	//2. 或消费者中,MessageListener.onMessage方法成功返回后,会话自动确认客户收到的消息
//2. Session.CLIENT_ACKNOWLEDGE
	//1. 消费者调用message.acknowledge()方法成功后,消息被确认
	//2. 确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认
//3. Session.DUPS_ACKNOWLEDGE:不需要确认消息
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
6.3 优先级
  1. 可以设置消息优先级,这样就可以不按发送消息的顺序去消费
  2. 优先级分10个级别,从0(最低)到9(最高)。如果不指定优先级,默认级别是4
  3. 开启优先级:activemq.xml
<!--为queue1开启消息优先级,xml中如果不设置不会生效-->
<policyEntry queue="queue1" prioritizedMessages="true" />
//方案一
producer.setPriority(9);
//方案二
producer.send(textMessage,DeliveryMode.PERSISTENT,9,1000 * 100);

7 HelloWorld

  1. 下载ActiveMQ
http://activemq.apache.org/
  1. 启动ActiveMQ
//实际上调用的就是bin\win64\wrapper.exe,只不过直接调wrapper.exe的话,如果有异常,会直接闪退,而不会打印错误信息
bin/win64/activemq.bat
  1. 进入管理界面
//ActiveMQ是在jetty(web容器)上运行的,会在本机开一个8161端口,提供网页控制台
http://localhost:8161/
  1. 修改访问端口
conf/jetty.xml
  1. 创建maven项目
    1. 会自动帮你下载、并引入需要的activemq-all-5.15.12.jar以及源码,如果创建正常项目,需要将E:\Program Files (x86)\apache-activemq-5.15.12\下的该jar包引入
    2. File–new–Project–Maven Project–选中create a simple project–填写Goup Id(com.mashibing.mq)与Artifact ID(activemq02)
    3. 修改pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mashibing.mq</groupId>
	<artifactId>activemq02</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
	<dependencies>
		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>activemq-all</artifactId>
			<version>5.15.12</version>
		</dependency>
	</dependencies>
</project>
  1. Sender
package com.mashibing.mq;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

public class Sender {
	public static void main(String[] args) throws Exception{
		// 1.获取连接工厂
		ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
				"admin",
				"admin",
				"tcp://localhost:61616"
				);
		// 2.获取一个向ActiveMQ的连接
		Connection connection = connectionFactory.createConnection();
		// 3.获取session
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 4. 找目的地,获取destination,消费端,也会从这个目的地取消息
		Queue queue = session.createQueue("user");
		// 51.消息创建者
		MessageProducer producer = session.createProducer(queue);
	//	producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
		// consumer -> 消费者
		// producer -> 创建者
		// 5.2. 创建消息
		for (int i = 0; i < 1000; i++) {
			TextMessage textMessage = session.createTextMessage("hi: " + i);
			// 5.3 向目的地写入消息
			if(i % 4 == 0) {
				// 设置消息的优先级
				// 对producer 整体设置
			//	producer.setPriority(9);
			//	producer.send(textMessage,DeliveryMode.PERSISTENT,9,1000 * 100);
				textMessage.setJMSPriority(9);
			}		
				producer.send(textMessage);
		Thread.sleep(3000);
		}	
		// 6.关闭连接
		connection.close();	
		System.out.println("System exit....");
	}
}

  1. Receiver
package com.mashibing.activemq01;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

public class Receiver {
	public static void main(String[] args) throws Exception {
		// 1. 建立工厂对象,
		ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
				ActiveMQConnectionFactory.DEFAULT_USER,
				ActiveMQConnectionFactory.DEFAULT_PASSWORD,
				"tcp://localhost:61616"
				);
		//2 从工厂里拿一个连接
		Connection connection = activeMQConnectionFactory.createConnection();
		connection.start();
		//3 从连接中获取Session(会话)
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		// 从会话中获取目的地(Destination)消费者会从这个目的地取消息
		Queue queue = session.createQueue("f");
		//从会话中创建消息提供者
		MessageConsumer consumer = session.createConsumer(queue);
		//从会话中创建文本消息(也可以创建其它类型的消息体)
		while (true) {
			TextMessage receive = (TextMessage)consumer.receive();
			System.out.println("TextMessage:" + receive.getText());
		}
	}
}

8 Active MQ的安全机制

  1. web控制台安全:重启后生效
//conf/jetty.xml中id="securityLoginService">配置了其登录相关用户的配置文件
conf/jetty-realm.properties
//用户名:密码,角色
admin: admin, admin
user: user, user
  1. 消息安全机制:配置建立连接工厂时的用户和密码
conf/activemq.xml
<!--添加在</shutdownHooks>后面,与它平级即可-->
<plugins>
      <simpleAuthenticationPlugin>
          <users>
              <authenticationUser username="admin" password="admin" groups="admins,publishers,consumers"/>
              <authenticationUser username="publisher" password="publisher"  groups="publishers,consumers"/>
              <authenticationUser username="consumer" password="consumer" groups="consumers"/>
              <authenticationUser username="guest" password="guest"  groups="guests"/>
          </users>
      </simpleAuthenticationPlugin>
 </plugins>

9 消息超时/过期

  1. 消息未被消费时,会存放于内存中,长期堆积存在内存装不下的风险,为防止这种情况产生,通常可以设置消息的超时时间
//单位毫秒
producer.setTimeToLive(1000);
  1. ActiveMQ会隔一段时间、或在消费端尝试访问某队列中消息时,检查该队列中消息是否超时,如果超时,不会让消费者消费此消息,而是让死信队列消费该消息,同时自动将该消息放入死信队列
9.1 死信队列
  1. 死信队列保存一些因为业务逻辑处理失败,而导致消息的失败或者说是消息发送过期的消息,有了死信队列能够保证在发送消息和接收消息过程中因为某些异常导致消息丢失的队列
  2. 由于非持久化消息,系统可能认为这些消息并不重要,丢不丢失无所谓,默认情况下,不会进入死信队列
  3. 对于不进死信队列的消息,超时候,控制台上会发现该消息被莫名其妙消费
  4. 死信队列和正常队列功能相同,本质上就是一个默认名为ActiveMQ.DLQ的队列,客户端同样可以从该队列中获取消息
  5. 修改死信队列名称与非持久化消息进入死信队列
<policyEntry queue="user" prioritizedMessages="true" >
  <deadLetterStrategy> 
    <!--queuePrefix:指定死信队列前缀,也就是将死信队列名设为"DLxxQ.user",useQueueForQueueMessages开启死信队列,默认开启,processNonPersistent:非持久化消息也进入死信队列-->	
    <individualDeadLetterStrategy   queuePrefix="DLxxQ." useQueueForQueueMessages="true" processNonPersistent="true" /> 
  </deadLetterStrategy> 
</policyEntry>
  1. 不让消息进入死信队列
<individualDeadLetterStrategy   processExpired="false"  /> 

10 独占消费者

  1. 创建Queue或Topic时,可以设置,其创建出的消费者,必须独占这个队列中的所有消息
  2. 也就是说,当这个消费者开始消费这个队列中的消息,只要这个消费者没挂掉,剩下所有消息,必须都由这个消费者来消费, 其他消费者无法消费到该消息
//xxoo为队列名,该语句是创建consumer时用的,而不是producer
Queue queue = session.createQueue("xxoo?consumer.exclusive=true");
//同时设置优先级,Broker会根据consumer的优先级来发送消息到较高的优先级的Consumer上,此处优先级和上面消息的优先级概念不同,它是消费者的优先级
Queue queue = session.createQueue("xxoo?consumer.exclusive=true&consumer.priority=10");
  1. 使用selector
    1. 可以为消息分组,同时设定consumer只消费某组下的消息
    2. 可以达到定向分发、消费消息,也就是负载均衡的感觉
    3. 如果我们已知一个服务处理消息的速度,我们就可以动态的去调整给每个服务器发多少消息
//在producer端,设置消息的属性,其实就是在消息头中,设定了properties={week=xx}
textMessage.setLongProperty("week", i%7);
//在consumer端,可以为consumer设置selector,设置只接收头中week属性值为1的消息,selector本质上就是一个字符串形式的表达式
MessageConsumer consumer = session.createConsumer(queue,"week=1");
//selector里面也可以写and,类似sql的语法
//MessageConsumer consumer = session.createConsumer(queue,"age > 17 and price<200");

11 消息发送原理

  1. 同步发送:调用send 方法发送消息时,该方法一直阻塞,直到ActiveMQ确认消息已经存储在持久性数据存储中,并发回确认消息
  2. 异步发送时,调用的是ActiveMQSession中send方法中的this.connection.asyncSendPacket(msg),而同步发送,调用的是其下面的this.connection.syncSendPacket(msg,sendTimeout)
开启事务 关闭事务
持久化 异步 同步
非持久化 异步 异步
  1. 注意send只是阻塞到mq将数据都存放到数据库中,而不是阻塞到consumer处理完消息
  2. 可以用以下几种方式设置为异步发送
//方法一
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin",
				"tcp://localhost:61616?jms.useAsyncSend=true");
//方法二
((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);
//方法三
((ActiveMQConnection)connection).setUseAsyncSend(true)
  1. 异步发送消息是有消息丢失的风险的
  2. 设置异步发送时的windowSize
    1. 用来约束在异步发送时,producer端允许积压的(尚未ACK)的消息的尺寸,
    2. 只对异步发送有意义,因为同步发送根本不会积压ACK
    3. 每次发送消息之后,都将会导致memoryUsage尺寸增加(+message.size),当broker返回producerAck时,memoryUsage尺寸减少producerAck.size,此size表示先前发送消息的大小
    4. 发送消息时,会检测memoryUsage中是否还有足够空间,如果足够,正常发送,如果不足,将会阻塞
    5. 可以通过如下2种方式设置
//方法一:会对所有producer生效
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin",
				"tcp://localhost:61616?jms.producerWindowSize=1048576");
//方法二:会对使用该目的地的所有producer生效,会覆盖方法一的设置
Queue queue = session.createQueue("user?producer.windowSize=1048576");

12 延迟消息投递

  1. 消息发送给消息中间件,然后消息中间件决定这个信息多久后才生效
  2. 需要在配置文件中开启延迟和调度
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">
  1. 编程时设置消息的属性为延迟投递
//其实就是给消息头,加了一对属性,key就是ScheduledMessage.AMQ_SCHEDULED_DELAY对应的字符串,value就是10*1000
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 10*1000);
  1. 带间隔的重复发送
long delay = 10 * 1000;
long period = 2 * 1000;
int repeat = 9;
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
//间隔时间
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
//额外重复几次,第一次不算,repeat必须设置为int,不能设为long,会失效
message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
createProducer.send(message);

13 Cron表达式指定时间发送消息

  1. Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式
    1. Seconds Minutes Hours DayofMonth Month DayofWeek Year
    2. Seconds Minutes Hours DayofMonth Month DayofWeek
  2. 每一个域可出现的字符如下
    1. Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
    2. Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
    3. Hours:可出现", - * /"四个字符,有效范围为0-23的整数
    4. DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
    5. Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
    6. DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
    7. Year:可出现", - * /"四个字符,有效范围为1970-2099年
  3. 特殊字符的含义
    1. :表示匹配该域的任意值,假如在Minutes域使用 , 即表示每分钟都会触发事件
    2. ?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样
    3. -:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
    4. /:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次
    5. ,:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次
    6. L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发
    7. W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
    8. LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五
    9. #:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三
  4. 举例
//每天中午12点触发 
"0 0 12 * * ?" 
//每天上午10:15触发 
"0 15 10 ? * *" 
//每天上午10:15触发 
"0 15 10 * * ?" 
//每天上午10:15触发 
"0 15 10 * * ? *" 
//2005年的每天上午10:15触发 
"0 15 10 * * ? 2005" 
//在每天下午2点到下午2:59期间的每1分钟触发 
"0 * 14 * * ?" 
//在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14 * * ?" 
//在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 
//在每天下午2点到下午2:05期间的每1分钟触发 
"0 0-5 14 * * ?" 
//每年三月的星期三的下午2:10和2:44触发 
"0 10,44 14 ? 3 WED" 
//周一至周五的上午10:15触发 
"0 15 10 ? * MON-FRI" 
//每月15日上午10:15触发 
"0 15 10 15 * ?" 
//每月最后一日的上午10:15触发 
"0 15 10 L * ?" 
//每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L" 
//2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 
//每月的第三个星期五上午10:15触发
"0 15 10 ? * 6#3" 

14 activemq.xml中的memoryUsage

<!--启动broker时,相当于启动一个java程序,该参数设定最多使用分配给broker的内存空间的百分之多少,来存储消息数据-->
<memoryUsage>
	<memoryUsage percentOfJvmHeap="70" />
</memoryUsage>
<!--表示activemq那些持久化消息,可最多使用磁盘空间的大小,如果持久化到mysql中,该参数不生效,持久化到kahadb中生效-->
<!--如果启动时,硬盘只有20g,那么最大空间如果设置为100g,那么此时就算再腾出空间,也只能用到20g-->
<storeUsage>
	<storeUsage limit="100 gb" />
</storeUsage>
<!--不持久化的消息,会先写入内存,如果超过了memoryUsage值,就会写入到一块临时空间,该参数用于限制这块临时空间大小-->
<tempUsage>
	<tempUsage limit="50 gb" />
</tempUsage>

15 监听器

  1. 使用for(true)接收消息时
    1. 如果一条消息特别大,会接收很久,receive方法会一直阻塞
    2. for循环中的业务处理逻辑,如果需要处理很久,其他消息也同样无法进来
    3. 无法高并发处理
  2. 可以使用监听器来处理消息接收,从而并发处理消息,当收到消息后会调用onMessage方法对消息进行业务处理,就不用手动调用consumer.receive来接收消息了
consumer.setMessageListener(new MessageListener() {
	public void onMessage(Message message) {
		try {
			System.out.println("message2:" + ((TextMessage)message).getText());
		} catch (JMSException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章