activemq的高可用(zookeeper+leveldb)主从集群

一、架构和技术介绍

1、简介

ActiveMQ Apache出品,最流行的,能力强劲的开源消息总线。完全支持JMS1.1J2EE 1.4规范的 JMS Provider实现

2、activemq的特性

1). 多种语言和协议编写客户端。语言: Java, C, C++, C#, Ruby, Perl, Python, PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP

2).完全支持JMS1.1J2EE 1.4规范 (持久化,XA消息,事务)

3).Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性

4).通过常见J2EE服务器( Geronimo,JBoss 4, GlassFish,WebLogic)的测试,其中通过JCA 1.5 resourceadaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE1.4商业服务器上

5).支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA

6).支持通过JDBCjournal提供高速的消息持久化

7).从设计上保证了高性能的集群,客户端-服务器,点对点

8).支持Ajax

9).支持与Axis的整合

10).可以很容易得调用内嵌JMS provider,进行测试

二、集群介绍

ActiveMQ 5.9  开始 ActiveMQ  的集群实现方式取消了传统的 Master-Slave  方式 增加了基于 ZooKeeper + LevelDB  Master-Slave 实现方式 其他两种方式 目录共享 数据库共享 依然存在。  

三种集群方式的对比 

(1)基于共享文件系统KahaDB默认): 

<persistenceAdapter> <kahaDB directory="${activemq.data}/kahadb"/> </persistenceAdapter> 

(2)基于 JDBC

 <bean id="MySQL-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>

      <property name="url" value="jdbc:mysql://localhost:3306/amq?relaxAutoCommit=true"/>

      <property name="username" value="root"/>

       <property name="password" value="root"/>

     <property name="maxActive" value="20"/>

       <property name="poolPreparedStatements" value="true"/>

</bean>

 <persistenceAdapter> 

        <jdbcPersistenceAdapter dataDirectory="${activemq.data}" dataSource="#mysql-ds" createTablesOnStartup="false"/>

 </persistenceAdapter>

(3)基于可复制的 LevelDB(本文采用这种集群方式): 

LevelDB  Google开发的一套用于持久化数据的高性能类库。LevelDB并不是一种服务,用户需要自 行实现Server。是单进程的服务能够处理十亿级别规模Key-Value 型数据占用内存小。 

<persistenceAdapter> 

    <replicatedLevelDB 

           directory="${activemq.data}/leveldb"     replicas="3"   

           bind="tcp://0.0.0.0:62621"   zkAddress="localhost:2181,localhost:2182,localhost:2183" 

           hostname="localhost"    zkPath="/activemq/leveldb-stores"/> 

</persistenceAdapter>

本文主要讲解基于 ZooKeeper 和LevelDB 搭建ActiveMQ 集群。集群仅提供主备方式的高可用集 群功能避免单点故障没有负载均衡功能。 

官方文档http://activemq.apache.org/replicated-leveldb-store.html 

集群原理图

高可用的原理:使用ZooKeeper(集群)注册所有的ActiveMQ Broker。只有其中的一个Broker 可以提供 服务,被视为Master,其他的Broker 处于待机状态,被视为Slave。如果Master 因故障而不能提供服务,ZooKeeper会从 Slave中选举出一个 Broker充当 Master Slave 连接 Master并同步他们的存储状态,Slave不接受客户端连接。所有的存储操作都将被复制到 连接至 Master 的Slaves。如果 Master 宕了,得到了最新更新的 Slave 会成为 Master。故障节点在恢复后 会重新加入到集群中并连接 Master 进入Slave 模式。 所有需要同步的disk 的消息操作都将等待存储状态被复制到其他法定节点的操作完成才能完成。所如果你配置了replicas=3那么法定大小是(3/2)+1=2Master 将会存储并更新然后等待 (2-1)=1 Slave存储和更新完成才汇报 success至于为什么是 2-1熟悉 Zookeeper 的应该知道有一个 node要作为观擦者存在。当一个新的Master 被选中你需要至少保障一个法定node 在线以能够找到拥有最新 状态的node。这个node 可以成为新的Master。因此推荐运行至少3 个replica nodes以防止一个node失败了服务中断。原理与 ZooKeeper 集群的高可用实现方式类似

1ActiveMQ集群部署规划

 环境CentOS 6.5 x64 、JDK8

版本ActiveMQ 5.13.3

ZooKeeper 集群环境:192.168.1.81:2181,192.168.1.82:2182,192.168.1.83:2183

2、防火墙打开对应的端口

edu-zk-01:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8161 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 51511 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 62621 -j ACCEPT

edu-zk-02:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8162 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 51512 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 62622 -j ACCEPT

edu-zk-03:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8163 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 51513 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 62623 -j ACCEPT

3、分别在三台主机中创建/home/yxq/activemq目录

# mkdir /home/yxq/activemq

上传 apache-activemq-5.13.3-bin.tar.gz 到/home/yxq/activemq 目录\

4、解压并按节点命名

# cd /home/yxq/activemq 

# tar -xvf apache-activemq-5.13.3-bin.tar.gz

# mv apache-activemq-5.13.3     node-0X    #(X代表节点号 1、2、3,下同)

 5、修改管理控制台端口(默认为 8161)可在 conf/jetty.xml 中修改,如下:

 node-01 管控台端口

 <bean id="jettyPort" class="org.apache.activemq.web.WebConsolePort" init-method="start">
             <!-- the default port number for the web console -->
        <property name="host" value="0.0.0.0"/>
        <property name="port" value="8161"/>
  </bean>

 node-02管控台端口

 <bean id="jettyPort" class="org.apache.activemq.web.WebConsolePort" init-method="start">
             <!-- the default port number for the web console -->
        <property name="host" value="0.0.0.0"/>
        <property name="port" value="8162"/>
  </bean>

 node-03管控台端口

<bean id="jettyPort" class="org.apache.activemq.web.WebConsolePort" init-method="start">
             <!-- the default port number for the web console -->
        <property name="host" value="0.0.0.0"/>
        <property name="port" value="8163"/>

  </bean>

6、集群配置 

在 3 个ActiveMQ 节点中配置conf/activemq.xml 中的持久化适配器。修改其中bind、zkAddress、hostname和 zkPath。注意:每个 ActiveMQ 的 BrokerName 必须相同,否则不能加入集群。

所有节点中activemq.xml配置

   <broker xmlns="http://activemq.apache.org/schema/core" brokerName="DubboEdu" dataDirectory="${activemq.data}">



node-01 中的持久化配置:


node-02 中的持久化配置:


node-03 中的持久化配置:




修改各节点的消息端口(注意,避免端口冲突):

node-01 中的消息端口配置:


node-02 中的消息端口配置:



 node-03 中的消息端口配置:


7、按顺序启动 3 ActiveMQ节点(前提是:   zookeeper集群已经启动)

# /home/yxq/activemq/node-01/bin/activemq start

# /home/yxq/activemq/node-02/bin/activemq start

# /home/yxq/activemq/node-03/bin/activemq start

监听日志:

 tail -f /home/yxq/activemq/node-01/data/activemq.log 

 tail -f /home/yxq/activemq/node-02/data/activemq.log 

 tail -f /home/yxq/activemq/node-03/data/activemq.log 

8、集群的节点状态分析

zookeeper信息查看工具,下载地址:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip

解压,打开:ZooInspector\build\zookeeper-dev-ZooInspector.jar

 集群启动后对 ZooKeeper 数据的抓图


可以看到ActiveMQ 的有3 个节点分别是000000000000000000000100000000002, 我这里是 000000000050000000000600000000007

以下第一张图展现了 000000000005的值可以看到elected 的值是不为空说明这个节点是Master

 

其他两个节点是 Slave






 

 9.集群可用性测试

ActiveMQ的客户端只能访问Master的Broker,其他处于Slave的Broker不能访问,所以客户端连接的Broker应该使用failover协议(失败转移)

failover:(tcp://192.168.1.81:51511,tcp://192.168.1.82:51512,tcp://192.168.1.83:51513)?randomize=false

 


           当一个ActiveMQ节点挂掉,或者一个Zookeeper节点挂掉,ActiveMQ服务依然正常运转,如果仅剩一个ActiveMQ节点,因为不能选举Master,ActiveMQ不能正常运行:同样的,如果Zookeeper仅剩一个节点活动,不管ActiveMQ各节点存活,ActiveMQ也不能正常提供服务。(ActiveMQ集群的高可用,依赖于Zookeeper集群的高可用)

10.设置开机启动

#vi /etc/rc.local

su - yxq -c '/home/yxq/activemq/node-01/bin/activemq start'

su - yxq -c '/home/yxq/activemq/node-02/bin/activemq start'

su - yxq -c '/home/yxq/activemq/node-03/bin/activemq start'

或者

生产软链接到 /etc/init.d/ 下

ln -s  /home/yxq/activemq/node-01/bin/activemq   /etc/init.d/

检查 链接的权限是否有执行权限

chmod +x /etc/init.d/activemq

加入管理服务 

chkconfig --add  activemq

设置管理 ON状态  

chkconfig activemq  on

查看

chkconfig --list activemq
service activemq  start/restart/stop  测试
碰到的问题:
使用service activemq start 提示:
INFO: Loading ‘/etc/default/activemq’
ERROR: Configuration variable JAVA_HOME or JAVACMD is not defined
correctly.
(JAVA_HOME=’ ’ , JAVACMD=’java’)
根据提示的意思是: 无法正确找到 JAVA_HOME 和 JAVACMD

配置服务启动级别和PATH,保证执行启动时,所需执行级别和jdk依赖没有问题
vi  activemq   
# chkconfig 2345 63 67
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/yxue/jdk/jdk1.8.0_74/bin:/yxue/jdk/jdk1.8.0_74/jre/bin:/root/bin
export PATH

11.demo例子

maven引入:

		<!--ActiveMQ依赖 -->
		<dependency>
			 <groupId>org.apache.activemq</groupId>
			 <artifactId>activemq-all</artifactId>
			 <version>5.13.3</version>
		</dependency>
		<!-- Spring整合ActiveMQ所需依赖 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jms</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-messaging</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:amq="http://activemq.apache.org/schema/core"
	   xmlns:jms="http://www.springframework.org/schema/jms"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jms
        http://www.springframework.org/schema/jms/spring-jms-4.0.xsd
        http://activemq.apache.org/schema/core
        http://activemq.apache.org/schema/core/activemq-core-5.8.0.xsd">

	<!-- ActiveMQ 连接工厂 -->
	<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
	<amq:connectionFactory id="amqConnectionFactory" userName="admin" password="admin" brokerURL="failover:(tcp://192.168.248.129:61616,tcp://192.168.248.128:61616,tcp://192.168.248.127:61616)?randomize=false"   />

	<!-- Spring Caching连接工厂 -->
	<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
	<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
		<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
		<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
		<!-- 同上,同理 -->
		<!-- <constructor-arg ref="amqConnectionFactory" /> -->
		<!-- Session缓存数量 -->
		<property name="sessionCacheSize" value="100" />
	</bean>

	<!-- Spring JmsTemplate 的消息生产者 start-->
	<!-- 定义JmsTemplate的Queue类型 -->
	<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
		<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
		<constructor-arg ref="connectionFactory" />
		<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
		<property name="pubSubDomain" value="false" />
	</bean>

	<!-- 定义JmsTemplate的Topic类型 -->
	<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
		<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
		<constructor-arg ref="connectionFactory" />
		<!-- pub/sub模型(发布/订阅) -->
		<property name="pubSubDomain" value="true" />
		<!-- 发送模式  DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久-->
		<property name="deliveryMode" value="2" />
	</bean>
	<!--Spring JmsTemplate 的消息生产者 end-->

	<!-- 消息消费者 start-->
	<!-- 定义Queue监听器 -->
	<jms:listener-container destination-type="queue" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
		<jms:listener destination="test.queue" ref="queueReceiver1"/>
		<jms:listener destination="test.queue" ref="queueReceiver2"/>
	</jms:listener-container>

	<!-- 定义Topic监听器 -->
	<jms:listener-container destination-type="topic" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
		<jms:listener destination="test.topic" ref="topicReceiver1"/>
		<jms:listener destination="test.topic" ref="topicReceiver2"/>
	</jms:listener-container>
	<!-- 消息消费者 end -->
</beans>

QueueSender:
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;

/**
 * ActiveMQ的消息队列模式
 * 队列消息的生产者,发哦是哪个消息到队列
 */
@Component
public class QueueSender {
    //通过Qualifier来注入对应的Bean
    @Autowired
    @Qualifier("jmsQueueTemplate")
    private JmsTemplate jmsTemplate;
    /**
     * 发送一条消息到指定的队列(目标)
     * @param queueName 队列名称
     * @param message 消息内容
     */
    public void send(String queueName,final String message){
        jmsTemplate.send(queueName, new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                return session.createTextMessage(message);
            }
        });
    }
}
TopicSender:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;

/**
 * ActiveMQ的topic/sub模式
 * Topic生产者发送消息到Topic
 */
@Component
public class TopicSender {
    @Autowired
    @Qualifier("jmsTopicTemplate")
    private JmsTemplate jmsTemplate;
    /**
     * 发送一条消息到指定的队列(目标)
     * @param topicName 队列名称
     * @param message 消息内容
     */
    public void send(String topicName,final String message){
        jmsTemplate.send(topicName, new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                return session.createTextMessage(message);
            }
        });
    }
}

QueueReceiver1:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;

/**
 * 消息队列监听器
 *
 */
@Component
public class QueueReceiver1 implements MessageListener {

    public void onMessage(Message message) {
        try {
            System.out.println("QueueReceiver1接收到消息:"+((TextMessage)message).getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

QueueReceiver2:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;

/**
 * 消息队列监听器
 *
 */
@Component
public class QueueReceiver2 implements MessageListener {

    public void onMessage(Message message) {
        try {
            System.out.println("QueueReceiver2接收到消息:"+((TextMessage)message).getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

TopicReceiver1:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;

@Component
public class TopicReceiver1 implements MessageListener{

    public void onMessage(Message message) {
        try {
            System.out.println("TopicReceiver1接收到消息:"+((TextMessage)message).getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

TopicReceiver2:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;

@Component
public class TopicReceiver2 implements MessageListener{

    public void onMessage(Message message) {
        try {
            System.out.println("TopicReceiver2接收到消息:"+((TextMessage)message).getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

ActivemqController调用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("activemq")
public class ActivemqController {
    @Autowired
    private QueueSender queueSender;
    @Autowired
    private TopicSender topicSender;

    /**
     * 发送消息到队列
     * Queue队列:仅有一个订阅者会收到消息,消息一旦被处理就不会存在队列中
     * @return String
     */
    @ResponseBody
    @RequestMapping("queueSender")
    public String queueSender(){
        String opt = "suc";
        try {
            queueSender.send("test.queue", "=======queue消息");
        } catch (Exception e) {
            opt = e.getCause().toString();
        }
        return opt;
    }

    @ResponseBody
    @RequestMapping("topicSender")
    public String topoicSender(){
        String opt = "suc";
        try {
            topicSender.send("test.topic", "==========topoic消息");
        } catch (Exception e) {
            opt = e.getCause().toString();
        }
        return opt;
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值