./activemq start > /myactiveMQ/run_time.log
注意如果启动不了amq,有一种原因是:linux机器名带有下滑线_,腾讯云服务器也会带下划线,会在日志中报错。
通过ConnectionFactory获取connection,connection生成Session,然后session可以生成Message,MessageProducer和MessageConsumer。
一个生产者生产12条消息,两个消费者并列在线,会轮流
send(Destination destination, int deliveryMode, int priority, long timeToLive)
@Expiration: 默认不过期, 如果timeToLive = 0, 永不过期
@Priority:0-9 十个级别,0-4是普通消息,5-9是加急消息
JMS不要求严格按照十个等级发消息,但必须保证加急消息要先于普通消息到达,默认是4级
TextMessage:
MapMessage:key为String类型,值为Java的基本类型
BytesMessage:二进制数组消息,包含一个byte[]
StreamMessage:Java数据流消息,用标准流操作来顺序地填充和读取
ObjectMessage:对象消息,包含一个可序列化的Java对象
如果需要除消息头字段以外的值,可以使用消息属性。识别/去重/重点标注等操作非常有用的方法。
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
非持久化消息重启broker后,消息会丢失!持久化消息重启broker后,消息仍然存在。
默认就是开启持久化。
持久化Topic:先启动订阅再启动生产
下述代码把connection.start放到后面保证其创建客户端ID和创建订阅者后,先向ActiveMQ注册订阅者,无论消费者是否在线都会接受到订阅消息,不在线的话,重新连接后会获取所有没收到过的消息。之后再接受订阅。(类似于先关注微信公众号,再接受推送)
connection = ActiveMQConnection.makeConnection("tcp://192.168.157.133:31616");
connection.setClientID("z3");
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark");
// 开启connection,表明先订阅,再接收消息
connection.start();
Message receive = topicSubscriber.receive(2000);
while (null != receive) {
extMessage textMessage = (TextMessage) receive;
System.out.println(textMessage.getText());
receive = topicSubscriber.receive(5000);
}
事务偏生产者,设置为false,执行send方法进入消息队列。设置为true,执行send,还是0条消息进入消息队列,在session关闭前,提交commit。
生产者侧事务:
try {
// ok
session.commit();
} catch (Exception e) {
e.printStackTrace();
// error
session.rollback();
} finally {
if (null != session)
session.close();
}
消费者侧如果设置为true,但是没有commit,拿到消息仍然算没有消费,会一直重复消费。提交commit可以避免消息重复消费。
Session.DUPS_OK_ACKNOWLEDGE 允许多次签收
Session.AUTO_ACKNOWLEDGE 自动签收
CLIENT_ACKNOWLEDGE 客户端自动签收
SESSION_TRANSACTED 事务签收
如果session开启事务(transacted:true)并且开启客户端手动签收。再代码加上 commit后,会自己签收掉,不会重复消费。 如果开启事务,不开启commit,手动签收,仍然会重复消费。
结论:
如果事务回滚,则消息会被再次传送。
非持久订阅,只有当消费者在线(和mq保持联系的时候)才能获取订阅消息。持久化订阅,消费者先向mq注册客户端ID,然后上线可以获取未收取的消息。
activemq制定配置文件启动:
./activemq start xbean:file:/usr/local/amq/amq_01/conf/activemq.xml
com.fasterxml.jackson.core
jackson-databind
2.11.1
public static void main(String[] args) throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setUseJmx(true);
brokerService.addConnector("tcp://localhost:61619");
brokerService.start();
}
写个测试发现嵌入broker正常工作。
org.springframework
spring-jms
5.2.8.RELEASE
org.apache.activemq
activemq-pool
5.13.2
org.springframework.boot
spring-boot-starter-parent artifactId>
2.3.2.RELEASE
org.springframework.boot
spring-boot-starter-activemq
1.5.0.RELEASE
application.yml:
server:
port: 7777
spring:
activemq:
broker-url: tcp://192.168.157.133:31616
user: admin
password: admin
jms:
pub-sub-domain: false # false = Queue true = Topic
myqueue: boot-amq-queue
ConfigBean.java
package com.xxx.mq.springboot;
@Component
@EnableJms
public class ConfigBean {
@Value("${myqueue}")
private String myQueue;
@Bean
public Queue queue() {
return new ActiveMQQueue(myQueue);
}
}
Producer:
package com.xxx.mq.springboot;
@Component
public class QueueProducer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
public void produceMsg() {
jmsMessagingTemplate.convertAndSend(queue, "****"+ UUID.randomUUID().toString().substring(0, 6));
}
}
主启动类:
package com.xxx.mq.springboot;
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
Springboot单元测试类:
import javax.annotation.Resource;
@SpringBootTest(classes = MainApp.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration // 微服务 以web形式
public class TestAMQ {
@Resource
private QueueProducer queueProducer;
@Test
public void testSend() throws Exception {
queueProducer.produceMsg();
}
}
可以直接使用springboot的 @JmsListener 来监听
URI描述信息的头部都是采用协议名称:
描述amqp协议的监听端口,采用URI描述格式 “amqp://…”
唯独用openwire协议使用: “tcp://…”,amq默认协议openwire,消息是通过 wire protocol来序列化成字节流。默认amq把 wire protocol 叫做OpenWire,促使消息数据快速交互。
TCP协议优点:
TCP协议传输可靠,稳定性强
字节流方式传递,效率高
应用广泛,支持任何平台
基于TCP协议之上,使用NIO会有更好的性能.普通协议使用BIO模型,效率比较低。
修改activemq配置文件:
启动后发现activemq控制台 network 会出现 nio
把 JAVA 代码中的 URL 链接改一下,就可以使用 NIO。
让某个端口支持 NIO 网络IO模型,又支持多个协议:
5.13.0后, amq能自动检测协议
使用auto关键字
使用 “+” 为端口设置多种特性
可以使用tcp和nio开头的url,自动适配协议。使用mqtt等可能会报错,因为不同协议的java代码可能不同,mqtt 需要 mqttClient 。
TCP + BIO 默认
NIO + TCP
NIO + TCP/Mqtt/
SSL 安全链接
STOMP Streaming Text Orientated Message Protocol, 流文本定向消息协议。
VM VM本身不是协议,当客户端和代理在同一个JVM中,通信可以不通过网络通道,而是直接通信,使用VM
AUTO
AMQP Advanced Message Queuing Protocol,高级消息队列协议
WS WebsSocket
安装 MQ 和 MySQL
把mysql数据库驱动包放到amq lib文件夹下
wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar
jdbcPersistenceAdapter 配置:
createTablesOnStartup : 是否在启动的时候创建数据表。首次true,重启后改成false。
创建数据库 bean:
dbcp是默认带的jar包,使用c3p0等别的需要额外倒入jar包
mysql创建对应数据库
然后 activemq 会自动创建以下三张表:
ACTIVEMQ_MSGS, ACTIVEMQ_ACKS, ACTIVEMQ_LOCK
ACTIVEMQ_MSGS:记录消息(签收删除)
字段名 | 意义 |
---|---|
ID | 自增数据库主键 |
CONTAINER | 消息的Destination |
MSGID_PROD | 消息发送者的主键 |
MSG_SEQ | 消息发送到顺序, MSGID_PROD + MSG_SEQ 可组成JMS的MessageID |
EXPIRATION | 消息的过期时间,存储的是 1970-01-01到现在的毫秒数 |
MSG | 消息本体的Java序列化对象的二进制数据 |
PRIORITY | 优先级 |
ACTIVEMQ_ACKS: 记录订阅者(删除删除)
字段名 | 意义 |
---|---|
CONTAINER | 消息的Destination |
SUB_DEST | 如果使用Static集群,这个字段会有集群其他系统的信息 |
CLIENT_ID | 每个订阅者都有一个唯一的客户端ID用以区分 |
SUB_NAME | 订阅者名字 |
SELECTOR | 消息选择器 |
LAST_ACKED_ID | 记录消费过的消息的ID |
ACTIVEMQ_LOCK:
在集群环境中才有用,只有一个Broker可以获取消息,称为Mater Broker
下划线坑爹:操作系统名字有下划线,无法启动 activemq
journal文件先记录amq消息,批量定期写入DB,提升性能。
数据不一定会立刻出现在DB中,会有延迟。如果生产1000条消息,消费了900条,这样只需要写入100条到数据库就行。
和KahaDB类似,也是基于文件的本地数据库储存形式,但是比KahaDB更快。它不使用自定义的BTree索引,而是用基于LevelDB的索引。
默认配置:
基于日志文件,基于文件的持久化数据库,从5.4之后默认持久化插件。类似于 redis aof
kahadb 路径下有以下文件:
db-1.log db.data db.redo lock db.free
KahaDB 消息存储使用一个事物日志和仅仅用一个索引文件来存储它所有的地址,数据被追加到data logs中,当log文件中的数据不再需要的时候,log文件会被丢弃。
db-1.log 文件大小到限制时,会自动创建db-2.log文件。文件会被归档或者删除,当数据文件不再被引用后
db.data 包含了持久化的BTree索引,BTree作为索引指向db-1.log中的数据
db.free 当前db.data文件里哪些页面是空闲的,文件具体内容是所有空闲页ID
db.redo 用来进行消息恢复,如果KahaDB在强制退出后启动,用于恢复BTree索引
lock文件锁,表示获取当前读写权限的broker
AMQ Message Store。基于文件的储存,现在已经弃用
基于ZooKeeper和levelDB搭建ActiveMQ集群,提供主从集群
拷贝三份 activemq 文件夹
修改 三个 activemq 的默认 61616 和 8161 端口,一个在 activemq.xml 一个在 jetty.xml 文件中
host 名字映射
修改三个broker name,使得他们名字都一样
三个节点持久化配置一样
replicas 指的是 复制的机器数
bind 地址需要不同机器 不通地址
修改 三个配置文件绑定的 61616端口,三个不同 (如果在一个机器上)
启动 三台 zk 启动 三台 mq
查看集群状态
对于一个慢点消费者,可能会阻塞producer,所以使用异步。异步投递可以显著的提高发送性能。AMQ默认使用异步,除非指定使用同步方式或者在未使用事务的前提下发送持久化的消息。
当使用非事务并且发送的是持久化消息(不能保证一致性),每一次发送消息都会同步发送且阻塞producer直到broker返回一个确认,表示消息已经被安全的持久化到磁盘。保障了消息安全,但是阻塞了客户端。
缺点:
消耗较多Client端内存,和导致broker性能消耗增加
不能保证消息发送成功,在useAsyncSend=true的情况下客户端要容忍消息丢失的可能。
开启异步(默认开启),三种方式:
ConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616?jms.useAsyncSend=true");
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory();
cf.setUseAsyncSend(true);
connection = ActiveMQConnection.makeConnection(AMQ_URL);
connection.setUseAsyncSend(true);
异步如何保证消息发送成功?
如果MQ突然宕机,生产者内存中尚未被发送至MQ的消息都会丢失。
所以正确的异步发需要回调确认:
下述的 new AsyncCallback() 就是回调函数
ActiveMQMessageProducer activeMQMessageProducer = (ActiveMQMessageProducer) session.createProducer(destination);
activeMQMessageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
TextMessage message = session.createTextMessage("myTextMessage");
message.setJMSMessageID(UUID.randomUUID().toString().substring(0, 6));
String msgID = message.getJMSMessageID();
activeMQMessageProducer.send(message, new AsyncCallback() {
@Override
public void onSuccess() {
System.out.println(msgID + "has been sent");
}
@Override
public void onException(JMSException e) {
System.out.println(msgID + "has failed");
}
});
需要在xml配置文件中,broker标签打开 schedulerSupport
参数:
AMQ_SCHEDULED_DELAY long 延时投递的时间
AMQ_SCHEDULED_PERIOD long 重复投递的间隔
AMQ_SCHEDULED_REPEAT int 重复投递次数
AMQ_SCHEDULED_CRON String Cron表达式
Java 代码里面封装的辅助消息类型: ScheduledMessage
TextMessage message = session.createTextMessage("DelayMyTextMessage " + UUID.randomUUID().toString().substring(0, 6));
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 10000);
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
activeMQMessageProducer.send(message);
哪些情况会引起消息重发:
Client用了transations且在session中调用了rollback()
Client用了transations且在调用commit()之前关闭或没有commit
Client在CLIENT_ACKNOWLEDGE的传递模式下,在session中调用recover()
有毒消息Poison ACK:
一个消息被重发超过默认次数(6),消费端会给MQ发送一个poison ack,broker会把它放入DLQ (死信队列)
初始化重发时间间隔: 1s
initialRedeliveryDelay = 1000L
maximumRedeliveries = 6
可以通过 RedeliveryPolicy 来设置重发次数:
ActiveMQConnectionFactory acf = new ActiveMQConnectionFactory(AMQ_URL);
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);
acf.setRedeliveryPolicy(redeliveryPolicy);
connection = acf.createConnection();
connection.start();
一般生产环境中设计两个队列:业务队列,死信队列
默认把所有的DeadLetter保存在一个共享的队列中,这是AMQ默认策略。 ActiveMQ.DLQ,也可以用以下配置来设置额
默认,无论topic还是queue,broker都会使用Queue来存放DeadLetter,也可以指定使用Topic:(还可以设置队列前缀)
自动删除过期消息:
processExpired = true, 默认表示放入死信队列
processNonExpired = false,默认不会把非持久的死消息发到死信队列中
如果消息是做数据库的插入操作,给这个消息做一个唯一主键,不会出现重复消费。
如果还不行,可以使用第三方服务,例如redis,给消息分配一个全局id,如果消费过,就把记录放到redis中,其他消费者消费前只要查询redis即可。