ActiveMQ高可用+负载均衡配置

本文参照了https://blog.csdn.net/haoyuyang/article/details/53931710这篇文章中的搭建方法,并将centos环境换成了windows环境的单机部署,并且对部署过程中的一些细节进行了测试与展示,还对看原作中自己没懂的地方进行了一些尝试。这里先感谢原作者,请大家认真阅读原作,原理部分我就不再赘述。如需转载,请注明出处https://blog.csdn.net/yixueweima/article/details/83118530

首先介绍一下我搭建结构:

1、1个zookeeper,用来注册所有的ActiveMQ Broker用。

2、6个ActiveMQ,3个为一组,分为两组:组1和组2。

高可用实现方式:一组中的3个ActiveMQ可以共享消息,一个挂掉,其他成员的可以继续提供服务。

负载均衡实现方式:一个生产者向组1发送消息,两个接收者分别接收组1和组2,这时候两个接收者都可以接收到消息。证明消息被分发到了两个组中。

这里可以将解压的ActiveMQ复制6份。

每1份中需要修改的文件只有2个:activemq.xml和jetty.xml,一共需要改12个文件。

activemq.xml文件内容

<!--
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    this work for additional information regarding copyright ownership.
    The ASF licenses this file to You under the Apache License, Version 2.0
    (the "License"); you may not use this file except in compliance with
    the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->
<!-- START SNIPPET: example -->
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">

    <!-- Allows us to use system properties as variables in this configuration file -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <value>file:${activemq.conf}/credentials.properties</value>
        </property>
    </bean>

   <!-- Allows accessing the server log -->
    <bean id="logQuery" class="io.fabric8.insight.log.log4j.Log4jLogQuery"
          lazy-init="false" scope="singleton"
          init-method="start" destroy-method="stop">
    </bean>

    <!--
        The <broker> element is used to configure the ActiveMQ broker.
    -->
    <broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">

        <destinationPolicy>
            <policyMap>
              <policyEntries>
                <policyEntry topic=">" >
                    <!-- The constantPendingMessageLimitStrategy is used to prevent
                         slow topic consumers to block producers and affect other consumers
                         by limiting the number of messages that are retained
                         For more information, see:

                         http://activemq.apache.org/slow-consumer-handling.html

                    -->
                  <pendingMessageLimitStrategy>
                    <constantPendingMessageLimitStrategy limit="1000"/>
                  </pendingMessageLimitStrategy>
                </policyEntry>
              </policyEntries>
            </policyMap>
        </destinationPolicy>


        <!--
            The managementContext is used to configure how ActiveMQ is exposed in
            JMX. By default, ActiveMQ uses the MBean server that is started by
            the JVM. For more information, see:

            http://activemq.apache.org/jmx.html
        -->
        <managementContext>
            <managementContext createConnector="false"/>
        </managementContext>

        <!--
            Configure message persistence for the broker. The default persistence
            mechanism is the KahaDB store (identified by the kahaDB tag).
            For more information, see:

            http://activemq.apache.org/persistence.html
        -->
        <!--
		<persistenceAdapter>
            <kahaDB directory="${activemq.data}/kahadb"/>
        </persistenceAdapter>
		-->
		<persistenceAdapter>
            <replicatedLevelDB  directory="${activemq.data}/leveldb"
				replicas="3"
				bind="tcp://0.0.0.0:6262"
				zkAddress="127.0.0.1:2181"
				hostname="localhost"
				zkPath="/activemq/leveldb-stores"
			/>
        </persistenceAdapter>

          <!--
            The systemUsage controls the maximum amount of space the broker will
            use before disabling caching and/or slowing down producers. For more information, see:
            http://activemq.apache.org/producer-flow-control.html
          -->
          <systemUsage>
            <systemUsage>
                <memoryUsage>
                    <memoryUsage percentOfJvmHeap="70" />
                </memoryUsage>
                <storeUsage>
                    <storeUsage limit="100 gb"/>
                </storeUsage>
                <tempUsage>
                    <tempUsage limit="50 gb"/>
                </tempUsage>
            </systemUsage>
        </systemUsage>

        <!--
            The transport connectors expose ActiveMQ over a given protocol to
            clients and other brokers. For more information, see:

            http://activemq.apache.org/configuring-transports.html
        -->
        <transportConnectors>
            <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
            <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
        </transportConnectors>
		<networkConnectors>
			 <networkConnector uri="static:(tcp://127.0.0.1:61626,tcp://127.0.0.1:61627,tcp://127.0.0.1:61628)" duplex="false"/>
		</networkConnectors>
        <!-- destroy the spring context on shutdown to stop jetty -->
        <shutdownHooks>
            <bean xmlns="http://www.springframework.org/schema/beans" class="org.apache.activemq.hooks.SpringContextHook" />
        </shutdownHooks>

    </broker>

    <!--
        Enable web consoles, REST and Ajax APIs and demos
        The web consoles requires by default login, you can disable this in the jetty.xml file

        Take a look at ${ACTIVEMQ_HOME}/conf/jetty.xml for more details
    -->
    <import resource="jetty.xml"/>

</beans>
<!-- END SNIPPET: example -->

jetty.xml文件内容


    <!--
        Licensed to the Apache Software Foundation (ASF) under one or more contributor
        license agreements. See the NOTICE file distributed with this work for additional
        information regarding copyright ownership. The ASF licenses this file to You under
        the Apache License, Version 2.0 (the "License"); you may not use this file except in
        compliance with the License. You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or
        agreed to in writing, software distributed under the License is distributed on an
        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
        implied. See the License for the specific language governing permissions and
        limitations under the License.
    -->
    <!--
        An embedded servlet engine for serving up the Admin consoles, REST and Ajax APIs and
        some demos Include this file in your configuration to enable ActiveMQ web components
        e.g. <import resource="jetty.xml"/>
    -->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="securityLoginService" class="org.eclipse.jetty.security.HashLoginService">
        <property name="name" value="ActiveMQRealm" />
        <property name="config" value="${activemq.conf}/jetty-realm.properties" />
    </bean>

    <bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint">
        <property name="name" value="BASIC" />
        <property name="roles" value="user,admin" />
        <!-- set authenticate=false to disable login -->
        <property name="authenticate" value="true" />
    </bean>
    <bean id="adminSecurityConstraint" class="org.eclipse.jetty.util.security.Constraint">
        <property name="name" value="BASIC" />
        <property name="roles" value="admin" />
         <!-- set authenticate=false to disable login -->
        <property name="authenticate" value="true" />
    </bean>
    <bean id="securityConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">
        <property name="constraint" ref="securityConstraint" />
        <property name="pathSpec" value="/api/*,/admin/*,*.jsp" />
    </bean>
    <bean id="adminSecurityConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">
        <property name="constraint" ref="adminSecurityConstraint" />
        <property name="pathSpec" value="*.action" />
    </bean>
    
    <bean id="rewriteHandler" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
        <property name="rules">
            <list>
                <bean id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
                  <property name="pattern" value="*"/>
                  <property name="name" value="X-FRAME-OPTIONS"/>
                  <property name="value" value="SAMEORIGIN"/>
                </bean>
            </list>
        </property>
    </bean>
    
	<bean id="secHandlerCollection" class="org.eclipse.jetty.server.handler.HandlerCollection">
		<property name="handlers">
			<list>
   	            <ref bean="rewriteHandler"/>
				<bean class="org.eclipse.jetty.webapp.WebAppContext">
					<property name="contextPath" value="/admin" />
					<property name="resourceBase" value="${activemq.home}/webapps/admin" />
					<property name="logUrlOnStart" value="true" />
				</bean>
				<bean class="org.eclipse.jetty.webapp.WebAppContext">
					<property name="contextPath" value="/api" />
					<property name="resourceBase" value="${activemq.home}/webapps/api" />
					<property name="logUrlOnStart" value="true" />
				</bean>
				<bean class="org.eclipse.jetty.server.handler.ResourceHandler">
					<property name="directoriesListed" value="false" />
					<property name="welcomeFiles">
						<list>
							<value>index.html</value>
						</list>
					</property>
					<property name="resourceBase" value="${activemq.home}/webapps/" />
				</bean>
				<bean id="defaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler">
					<property name="serveIcon" value="false" />
				</bean>
			</list>
		</property>
	</bean>    
    <bean id="securityHandler" class="org.eclipse.jetty.security.ConstraintSecurityHandler">
        <property name="loginService" ref="securityLoginService" />
        <property name="authenticator">
            <bean class="org.eclipse.jetty.security.authentication.BasicAuthenticator" />
        </property>
        <property name="constraintMappings">
            <list>
                <ref bean="adminSecurityConstraintMapping" />
                <ref bean="securityConstraintMapping" />
            </list>
        </property>
        <property name="handler" ref="secHandlerCollection" />
    </bean>

    <bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
    </bean>

    <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>

    <bean id="Server" depends-on="jettyPort" class="org.eclipse.jetty.server.Server"
        destroy-method="stop">

        <property name="handler">
            <bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
                <property name="handlers">
                    <list>
                        <ref bean="contexts" />
                        <ref bean="securityHandler" />
                    </list>
                </property>
            </bean>
        </property>

    </bean>

    <bean id="invokeConnectors" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    	<property name="targetObject" ref="Server" />
    	<property name="targetMethod" value="setConnectors" />
    	<property name="arguments">
    	<list>
           	<bean id="Connector" class="org.eclipse.jetty.server.ServerConnector">
           		<constructor-arg ref="Server" />
                    <!-- see the jettyPort bean -->
                   <property name="host" value="#{systemProperties['jetty.host']}" />
                   <property name="port" value="#{systemProperties['jetty.port']}" />
               </bean>
                <!--
                    Enable this connector if you wish to use https with web console
                -->
                <!-- bean id="SecureConnector" class="org.eclipse.jetty.server.ServerConnector">
					<constructor-arg ref="Server" />
					<constructor-arg>
						<bean id="handlers" class="org.eclipse.jetty.util.ssl.SslContextFactory">
						
							<property name="keyStorePath" value="${activemq.conf}/broker.ks" />
							<property name="keyStorePassword" value="password" />
						</bean>
					</constructor-arg>
					<property name="port" value="8162" />
				</bean -->
            </list>
    	</property>
    </bean>

	<bean id="configureJetty" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
		<property name="staticMethod" value="org.apache.activemq.web.config.JspConfigurer.configureJetty" />
		<property name="arguments">
			<list>
				<ref bean="Server" />
				<ref bean="secHandlerCollection" />
			</list>
		</property>
	</bean>
    
    <bean id="invokeStart" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" 
    	depends-on="configureJetty, invokeConnectors">
    	<property name="targetObject" ref="Server" />
    	<property name="targetMethod" value="start" />  	
    </bean>
    
    
</beans>

activemq.xml与默认文件比修改了如下几处 :jetty.xml只需要修改port,我直接按照顺序把6个文件分别改成 8161、8162、8163、8164、8165、8166,这个就是管理页面那个端口。

按照顺序,改好其余文件。我把其余的文件打包发了出来。

https://download.csdn.net/download/yixueweima/10728194

接下来就可以测试了!

首先测试,高可用:

1、先启动zookeeper。

2、按顺序启动第一组的三个mq。

管理界面也是打不开的。

再启动第二个mq,显示启动的是一台从服务,日志里还显示下了东西,考虑为同步消息

这时其实内部也完成了选举master的工作,再看管理界面可以打开了

这里继续启动第三个mq,就可以了。这里注意一下,这三个mq虽然都启动了,但是只有被选为master的可以访问,其他的只有在master挂掉后,才有可能被选为master,被访问。如下图,虽然8162,被启动了,但是不能访问。

用代码发一些消息:

	package com.linan.test;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ClustorProducer {
    private ConnectionFactory factory;
    private Connection connection;
    private Session session;
    private Destination destination;
    private MessageProducer producer;
 
    public ClustorProducer() throws JMSException {
        this.factory = new ActiveMQConnectionFactory(ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)?randomize=false");
        this.connection = factory.createConnection();
        connection.start();
        this.session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
        this.destination = session.createQueue("first");
        producer = session.createProducer(destination);
    }
 
    public void send() throws JMSException, InterruptedException {
        for(int i=1; i<=50000; i++) {
            Message message = session.createTextMessage("内容第一组:" + i);
            producer.send(destination, message);
            System.out.println(message);
            Thread.sleep(200);
        }
    }
 
    public static void main(String[] args) throws JMSException, InterruptedException {
        ClustorProducer clustorProducer = new ClustorProducer();
        clustorProducer.send();
    }
}

这里看到了:将主mq关掉,看看能不能实现高可用

关掉8161后。访问看到8161不能访问了,8162可以,并且还是那9条消息。证明高可用可行。

进而关掉8162,看看8163什么情况:没有足够的数量选举master,这时候2、3都不能访问了。就证明高可用这种方式的实现必须有两个以上的节点才行。

 

进一步验证负载均衡:先把6个mq都启动

刚才生产者代码中是向tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618 组1发送消息,做了负载均衡之后我们还用上边这个生产者,这时候就会有疑问,也是我在读原文的时候的疑问,这时候发送者发送后会把消息分发到两个中吗?我就测试了一下,发现并没有,消息都发到组1中了。那么负载均衡是不是就没配置成功,哪里配置错了?这时候我就查阅了一些文章。在https://blog.csdn.net/xiajun07061225/article/details/47068451?utm_source=blogxgwz6这篇文中得到了启示。

首先,配置负载均衡成功后会在日志文件中检索到Network connection between关键字段,我能检索到,就证明我成功了,但是为什么就只有组1接受到消息。

文中的图例和文字又给了我提示,他们会被投递给有订阅需求的broker,所以是不是我没有消费者?

那我就做一个消费者,消费组2的消息,看看能不能消费到给组1发送改的消息。事实证明,我只向组1发过消息,但是通过访问组2进行消费,消费到了组1接收的消息。看来原因就是没有消费者,消息主动向消费者流动!

为了证明负载均衡,那么就同时开启两个消费者,分别消费组1和组2,一个发送者只向组1发送消息,看看两个消费者是不是都能得到消息。

首先记录一下初始状态:

组1和组2都没有消息。

先开启生产者,生产一些消息。因为刚才证明了只向组1发的,没有消费者的情况下,只有组1能接到消息,所以按照图片中显示,所有消息71条消息都到了8161上,组2还是0。

接着开启两个消费者,分别消费组1和组2,看看他们能不能分担这些消息,下面两个消费者的链接地址不同

package com.linan.test;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ClustorConsumer {
    private ConnectionFactory factory;
    private Connection connection;
    private Session session;
    private Destination destination;
    private MessageConsumer consumer;
 
    public ClustorConsumer() throws JMSException {
        this.factory = new ActiveMQConnectionFactory(ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                "failover:(tcp://127.0.0.1:61626,tcp://127.0.0.1:61627,tcp://127.0.0.1:61628)?randomize=false");
        this.connection = factory.createConnection();
        connection.start();
        this.session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
        this.destination = session.createQueue("first");
        this.consumer = session.createConsumer(destination);
    }
 
    public void consume() throws JMSException, InterruptedException {
        while (true) {
            Message message = consumer.receive();
            if(message == null)
                break;
            System.out.println(message);
            Thread.sleep(1000);
        }
    }
 
    public static void main(String[] args) throws JMSException, InterruptedException {
        ClustorConsumer clustorConsumer = new ClustorConsumer();
        clustorConsumer.consume();
    }
}
package com.linan.test;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;

public class ClustorConsumer1 {
    private ConnectionFactory factory;
    private Connection connection;
    private Session session;
    private Destination destination;
    private MessageConsumer consumer;
 
    public ClustorConsumer1() throws JMSException {
        this.factory = new ActiveMQConnectionFactory(ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)?randomize=false");
        this.connection = factory.createConnection();
        connection.start();
        this.session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
        this.destination = session.createQueue("first");
        this.consumer = session.createConsumer(destination);
    }
 
    public void consume() throws JMSException, InterruptedException {
        while (true) {
            Message message = consumer.receive();
            if(message == null)
                break;
            System.out.println(message);
            Thread.sleep(1000);
        }
    }
 
    public static void main(String[] args) throws JMSException, InterruptedException {
        ClustorConsumer clustorConsumer = new ClustorConsumer();
        clustorConsumer.consume();
    }
}

效果明显,两个消费者,分别消费了发给组1的消息。

至此,负载均衡也验证结束。

内容有些长,如有错误,请多多指教。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值