JMS的基本概念和规范
消息传递域 (JMS domains)
也叫消息模式, 消息模型, 有2种:
- 点对点(p2p)
概念
- 消息队列(Queue)
- 提供者(Sender)
- 消费者(Receiver)
- 每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
特点
- 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
- 提供者和消费者之间在时间上没有依赖性,也就是说当提供者发送了消息之后,不管消费者有没有正在运行,它不会影响到消息被发送到队列
- 每条消息仅会传送给一个消费者。可能会有多个消费者在一个队列中侦听,但是每个队列中的消息只能被队列中的一个消费者所消费。
- 消息存在先后顺序。一个队列会按照消息服务器将消息放入队列中的顺序,把它们传送给消费者。当已被消费时,就会从队列头部将它们删除(除非使用了消息优先级)。
- 消费者在成功接收消息之后需向队列应答成功
2. 发布订阅(pub/sub)
概念
- 主题(Topic)
- 发布者(Publisher)
- 订阅者(Subscriber)
特点
- 每个消息可以有多个消费者
- 发布者和订阅者之间有时间上的依赖性。针对某个主题的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态。
- 为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有运行,它也能接收到发布者的消息。
- 每条消息都会传送给称为订阅者的多个消息消费者。
- 发布者通常不会知道、也意识不到哪一个订阅者正在接收主题消息。
- 消息被推送给消费者,这意味着消息会传送给消费者,而无须请求。
消息组成(数据格式)
1.消息头
包含消息的识别信息和路由信息
2.消息体
-
TextMessage --一个字符串对象
-
MapMessage --一套名称-值对
-
BytesMessage --一个字节的数据流
-
StreamMessage – Java原始值的数据流
-
ObjectMessage --一个序列化的 Java对象
配置详解:
从上可知:p2p模型和发布订阅(pub/sub)有个很大的区别就是时间上的依赖性的区别。p2p默认不依赖时间,而发布订阅需要创建一个可持久化的订阅。发布订阅模型默认自带了kahaDB 存储方式 (官方推荐。基于日志文件,5.4 之后的默认持久化)。无需配置。但是为了方便查看,这边改用jdbc来持久化订阅。下面提一下配置(linux上安装)。
第一步:引入3个jar包:
mysql(数据库驱动) 、HikariCP(连接池) 、slf4j(日志接口) , 放到 activemq安装目录下的lib文件夹下;
第二步:安装目录activemq/conf/activemq.xml 配置 dataSource
<!-- Hikari Datasource -->
<bean id="activemq-mysql" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://192.168.5.4:3306/activemq?serverTimezone=GMT%2B8"/>
<property name="username" value="root" />
<property name="password" value="1234" />
</bean>
配置持久化
<persistenceAdapter>
<!--默认配置 <kahaDB directory="${activemq.data}/kahadb"/> -->
<jdbcPersistenceAdapter dataSource="#activemq-mysql" createTablesOnStartup="true"/></persistenceAdapter>
第三步:先建好数据库(库名同上jdbc配置),再重启activeMQ;
第四步:成功,自动建表如下:
ACTIVEMQ_ ACKS : 存储持久订阅的信息
ACTIVEMQ_ LOCK : 锁表(集群使用)
ACTIVEMQ_ MSGS : 消息表
三个表详解:
activemq_acks:
用于存储订阅关系,如果是持久化的Topic,订阅者和服务器订阅关系在这个表保:主要的数据库字段如下
container:消息的Destination
sub_dest:如果是使用的Static集群,这个字段会有集群和其他系统的信息
client_id:订阅者都必须有一个唯一的客户端id用于区分
sub_name:订阅者名称
selector:选择器,可以选择只消费满足条件的消息,条件可以用自定义属性实现,可以支持多属性and和or操作
last_acked_id:记录消费过的消息id
activemq_lock
在集群环境中才有用,只有一个broker可以获得消息,称为Master Broker,其他的只能作为备份等待
activemq_msg:
用于存储消息,Quene和Topic都存储在这个表:
id数据库的自增主键
container:消息的Destination
msgid_prod:消息的发送者客户端的主键
msg_seq:是发消息的顺序,msgid_prod+msg_seq可以组成JMS的MessageID
expiration:消息的过期时间,存储的是1970-01-01到现在的毫秒数
msg:消息本体的java序列对象的二进制数据
prioritry:优先级从0-9,数据越大优先级越高
最后附上测试代码:
jar包
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.11</version>
</dependency>
连接配置
public class MyConfig {
//定义ActiveMQ的连接地址
public static final String ACTIVEMQ_URL = "tcp://192.168.5.4:61616";
//topic 队列名称
public static final String TOPIC_NAME = "topic_01";
}
发布者
public class PersistentSender {
public static void main(String[] args) throws JMSException {
//创建连接工厂
ConnectionFactory activeMQConnectionFactory =
new ActiveMQConnectionFactory(MyConfig.ACTIVEMQ_URL);
//创建连接
Connection connection = activeMQConnectionFactory.createConnection();
//打开连接
connection.start();
//创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建队列目标
Destination destination = session.createTopic(MyConfig.TOPIC_NAME);
//创建一个生产者
MessageProducer producer = session.createProducer(destination);
//发送消息时用使用持久模式(默认的,可写可不写)
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
//创建消息
TextMessage message = session.createTextMessage("我是永恒的帅哥擦擦擦");
//发送消息
producer.send(message);
//在本地打印消息
System.out.println("我现在发的消息是:" + message.getText());
//关闭连接
connection.close();
}
}
接收者
public class PersistentRecv01 {
public static void main(String[] args) throws JMSException {
String myId = "PersistentRecv01";
//创建连接工厂
ConnectionFactory activeMQConnectionFactory =
new ActiveMQConnectionFactory(MyConfig.ACTIVEMQ_URL);
//创建连接
Connection connection = activeMQConnectionFactory.createConnection();
//持久订阅
connection.setClientID(myId);
//打开连接
connection.start();
//创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建队列目标
Topic topic = session.createTopic(MyConfig.TOPIC_NAME);
//创建消费者
MessageConsumer consumer = session.createDurableSubscriber(topic, myId);
//接收消息(这里也可以用while循环)
//textMessage = (TextMessage)consumer.receive(); 是一个阻塞队列
//这里用监听器持续接收消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage receive = (TextMessage)message;
try {
System.out.println("PersistentRecv 111 : " + receive.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}