消息队列:
作用:
消息队列中间件是分布式系统中的重要组件,主要解决异步消息,应用解耦,流量削峰等问题,从而实现高性能,高可用,可伸缩和最终一致性的架构
今天就来分享一个ActiveMq(apache开源项目)的demo
ActiveMq的下载地址:http://activemq.apache.org/ 解压即能用,运行批处理文件即运行(activemq.bat),
运行ActiveMq后可以访问到web界面:http://localhost:8161/
这里的登陆账号,是activemq默认的,可以在配置文件里找到(jetty-realm.properties)
# Defines users that can access the web (console, demo, etc.)
# username: password [,rolename ...]
admin: admin, admin
user: user, user
入门Demo:
记得要先打开ActiveMq服务,同时先用消息消费者订阅broker,然后再运行消息生产者
package com.chenfu;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.Test;
import javax.jms.*;
/**
* @Author: romantic_ke@163.com
* @Description:
* @Date: 2019/1/9 22:26
*/
public class ActiveMqTest {
// 消息发送端
@Test
public void msgProdecer() throws JMSException {
// 获得工厂
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
// 拿到连接
Connection connection = factory.createConnection();
// 打开连接
connection.start();
// 是否开启事务,设置应答方式
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 设置中间件
Topic topic = session.createTopic("chenfuTop");
// 创建生产者
MessageProducer producer = session.createProducer(topic);
// 创建消息
TextMessage textMessage = session.createTextMessage();
// 设置消息
textMessage.setText("ping");
// 发送消息
producer.send(textMessage);
// 关闭连接
producer.close();
session.close();
connection.close();
}
// 消息接收端
@Test
public void msgConsumer() throws JMSException {
// 获得工厂
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
factory.setBrokerURL("tcp://localhost:61616");
// 获得连接
Connection connection = factory.createConnection();
// 打开连接
connection.start();
// 获得连接
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建主题
Topic topic = session.createTopic("chenfuTop");
// 创建消费者
MessageConsumer consumer = session.createConsumer(topic);
// 指定消息监听器
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
System.out.println("message:" + textMessage);
try {
System.out.println("连接到客户端:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
while (true) {
}
}
}
持久化
默认的持久化(到文件)
activemq默认的持久化操作是讲消息保存在文件里,在activemq.xml配置文件里可以看到
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
采用默认持久化方式的案例:
package com.chenfu;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.Test;
import javax.jms.*;
/**
* @Author: romantic_ke@163.com
* @Description:
* @Date: 2019/1/10 22:51
*/
public class PersistenceMqTest {
@Test
public void createProducer() throws JMSException {
// 创建获得connection的工厂
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
// 设置工厂的连接参数
factory.setBrokerURL("tcp://localhost:61616");
// 获得activeMq的连接
Connection connection = factory.createConnection();
// 打开连接
connection.start();
// 创建会话
// 第一个参数是是否是开启事务,第二个参数是设置消息的应答机制
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
// 创建主题
Topic chenfuTopic = session.createTopic("chenfuTopic");
// 创建生产者
MessageProducer producer = session.createProducer(chenfuTopic);
// 创建消息
TextMessage textMessage = session.createTextMessage();
// 设置消息内容
textMessage.setText("兄弟,借个火");
// 生产者发送消息
// 第一个参数是消息,第二个参数是是否启用持久化,第三个是设置优先级,第四个参数是持久化时间
producer.send(textMessage,DeliveryMode.PERSISTENT,1,1000 * 60 * 60 * 24);
// 关闭连接
producer.close();
session.close();
connection.close();
}
@Test
public void createConsumer() throws JMSException {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
factory.setBrokerURL("tcp://localhost:61616");
Connection connection = factory.createConnection();
String clientId = "chenfu-01";
connection.setClientID(clientId);
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic chenfuTopic = session.createTopic("chenfuTopic");
TopicSubscriber subscriber = session.createDurableSubscriber(chenfuTopic, clientId);
subscriber.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
while (true){
}
}
}
以上两种的区别:
采用了持久化方案后,消息生产件在发送消息的时候,需要调用能设置持久化方案的发送方法
// 第一个参数是消息,第二个参数是是否启用持久化,第三个是设置优先级,第四个参数是持久化时间
producer.send(textMessage,DeliveryMode.PERSISTENT,1,1000 * 60 * 60 * 24);
消息的消费者是支持事务的消费者类型
同时这种消费者被创建的时候需要一个客户端ID
String clientId = "chenfu-01";
connection.setClientID(clientId);
...
TopicSubscriber subscriber = session.createDurableSubscriber(chenfuTopic, clientId);
持久化到数据库
1.将数据库的驱动jar放在ActiveMq的lib目录下(我这里用的mysql数据库),因为我在这里用的druid连接池,所以需要将druid的jar也放入【PS:注意mysql驱动jar包的版本,我第一次用了一个8.0的版本一直连接失败,反复建立连接】
2.修改ActiveMq的配置文件(activemq.xml),将默认的持久化规则修改为持久化到数据库
<persistenceAdapter>
<jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#druid-ds"/>
</persistenceAdapter>
<bean id="druid-ds" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql:///activemq" />
<!-- <property name="url" value="jdbc:mysql://localhost:3306/activemq?relaxAutoCommit=true" /> -->
<property name="username" value="root" />
<property name="password" value="root" />
<property name="poolPreparedStatements" value="false" />
</bean>
此时开启ActiveMq服务后,可以看到生成了三张表
此时的消息表是空的
启动消息订阅者后,acks表多了一条记录
关闭消息订阅者,运行消息发布者,msg表生成一条记录
再次运行消息订阅者,控制台输出了消息
持久化到MySQL数据库的Demo:
package com.chenfu;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.junit.Test;
import javax.jms.*;
/**
* @Author: romantic_ke@163.com
* @Description:
* @Date: 2019/1/11 22:49
*/
public class PersistenceMqToMysqlTest {
@Test
public void msgProducer() throws JMSException {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
factory.setBrokerURL("tcp://localhost:61616");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("chenfuTopic");
MessageProducer producer = session.createProducer(topic);
TextMessage msg = session.createTextMessage();
msg.setText("啊哈!very nice!");
producer.send(msg,DeliveryMode.PERSISTENT,1,1000 * 60 *60 *24);
producer.close();
session.close();
connection.close();
}
@Test
public void msgConsumer() throws JMSException {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
factory.setBrokerURL("tcp://localhost:61616");
Connection connection = factory.createConnection();
String chenfuClient = "chenfu-02";
connection.setClientID(chenfuClient);
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("chenfuTopic");
TopicSubscriber subscriber = session.createDurableSubscriber(topic, chenfuClient);
sout(subscriber);
while (true){
}
}
static void sout(TopicSubscriber subscriber) throws JMSException {
subscriber.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage)message;
try {
System.out.println(textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
zookeeper+activemq集群实现高可用
使用ZooKeeper实现的Master-Slave实现方式,是对ActiveMQ进行高可用的一种有效的解决方案。高可用的原理:使用ZooKeeper(集群)注册所有的ActiveMQ Broker。只有其中的一个Broker可以对外提供服务(也就是Master节点),其他的Broker处于待机状态,被视为Slave。如果Master因故障而不能提供服务,则利用ZooKeeper的内部选举机制从Slave中选举出一个Broker充当Master节点,继续对外提供服务。通过zookeeper+activeMQ实现的集群,可以有效的排除单点故障引起的服务中断
1、准备三台虚拟机,IP分别为:
192.168.106.129
192.168.106.130
192.168.106.131
2.需要预装好jdk环境
3.准备好zookeeper和activema的安装包
安装zookeeper
第一步:将下载的zookeeper安装包分别上传到三台虚拟机,分别进行下面操作
第二步:解压安装包
tar –zxvf zookeeper-3.4.10.tar.gz –C /usr/local
第三步:将/usr/local目录下的zookeeper-3.4.10改名
mv zookeeper-3.4.10 zookeeper
第四步:在zookeeper目录下创建两个目录data和logs,分别存放数据和日志
cd /usr/local/zookeeper/
mkdir data
mkdir logs
第五步:将zookeeper/conf目录下的zoo_sample.cfg文件名改为zoo.cfg
cd /usr/local/zookeeper/conf/
mv zoo_sample.cfg zoo.cfg
安装activeMQ
第一步:将下载的activemq安装包分别上传到三台虚拟机,分别进行下面操作
第二步:解压安装包
tar –zxvf apache-activemq-5.14.0-bin.tar.gz –C /usr/local
第三步:将/usr/local目录下的apache-activemq-5.14.0改名
mv apache-activemq-5.14.0 activemq
集群配置
第一步:在/usr/local/zookeeper/data下创建myid文件,文件内容为1。同理,其他虚拟机中也创建myid文件,内容分别为2和3
第二步:修改/usr/local/zookeeper/conf/zoo.cfg文件,加入以下内容
dataDir=/usr/local/zookeeper/data/
dataLogDir=/usr/local/zookeeper/logs
server.1=192.168.106.129:2888:3888
server.2=192.168.106.130:2888:3888
server.3=192.168.106.131:2888:3888
第三步:分别启动三台zookeeper(需要关闭防火墙)
关闭防火墙(centos7关闭防火墙的指令如下)
systemctl stop firewalld.service
查看防火墙
firewall-cmd --state
启动zookeeper
/usr/local/zookeeper/bin/zkServer.sh start
第四步:修改/usr/local/activemq/conf目录下的activemq.xml
vim /usr/local/activemq/conf/activemq.xml
修改brokerName=”activemq-cluster”
将文件中持久化适配器改为
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="192.168.106.129:2181,192.168.106.130:2181,192.168.106.131:2181"
hostname="192.168.106.129"
zkPath="/activemq/leveldb-stores"/>
</persistenceAdapter>
注意:其他虚拟机中修改时hostname需要改为对应的ip
第五步:启动mq服务
./activemq start
从外部访问
可以看到,此时的130节点是不提供服务的,131也是
此时,停掉master节点(129)
130节点开始提供服务
最后抛一个疑问:
博主发现,当有两台节点都停掉(activemq)之后,还剩一台机器存活的时候,此时的最后一台机器没有独自去提供服务,当再开启一台节点后,才会重新去提供服务,即至少要有两台节点存活(activemq)才能正常提供服务,不知道是什么原因导致的,欢迎路过的高手指点解惑