Broker-Cluster实现负载均衡(分流、提高吞吐率)

一般中间件都提供了横向扩展和纵向扩展,横向扩展就是我们经常说的负载均衡,纵向扩展提供了Master-Slaver;

消息中间件解决了应用之间的消息传递、解耦、异步的问题。

Broker-Cluster部署方式中,各个broker通过网络互相连接,并共享queue,提供了2种部署方式:static Broker-Cluster和Dynamic Broker-Cluster

1).static Broker-Cluster


将ActiveMq拷贝2份,分别命名:apache-activemq-5.10.0_M1,apache-activemq-5.10.0_M2,

下面就是配置activemq.xml:


M1做如下配置:(黑色是必须的,红色是可选的)

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


<!--networkConnectors节点的配置必须要在persistenceAdapter节点之前 --> 


duplex:网络是否为双向的,默认为false。如果为true,则remote Broker不仅是“订阅者”,还可以作为消息的“生产者”,即remote Broker也会向Local Broker转发消息。
我们通常是,每个broker均与其他broker建立单向的networkConnector,比如有三个broker A/B/C,那么在A上配置2个单向的:A->B,A->C,那么在B上配置:B->A,B->C,以此论推。
<networkConnectors>
    <networkConnector uri="static:(tcp://192.168.1.9:61617)"  duplex="true"/>
</networkConnectors>
<persistenceAdapter>
     <jdbcPersistenceAdapter dataSource="#mysql-ds" useDatabaseLock="false" />
</persistenceAdapter>



<transportConnectors>
        <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
<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://192.168.1.4:3306/AS?relaxAutoCommit=true"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="poolPreparedStatements" value="true"/>
</bean>

<broker>标签之外配置数据源mysql-ds

M2做如下配置(黑色是必须的,红色是可选的)


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


<!--networkConnectors节点的配置必须要在persistenceAdapter节点之前 -->


<networkConnectors>
    <networkConnector uri="static:(tcp://192.168.1.9:61616)" duplex="true"/>
</networkConnectors>
<persistenceAdapter>
    <jdbcPersistenceAdapter dataSource="#mysql-ds" useDatabaseLock="false"/>
</persistenceAdapter>



<transportConnectors>
        <transportConnector name="openwire" uri="tcp://0.0.0.0:61617?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
<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://192.168.1.4:3306/BS?relaxAutoCommit=true"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="poolPreparedStatements" value="true"/>
</bean>

<broker>标签之外配置数据源mysql-ds

通过以上配置使M1和M2这两个 broker通过网络互相连接,并共享queue,启动M1和M2,可以看到如下启动日志:network connection has been established

Network connection between vm://AS#44 and tcp:///192.168.1.9:61617@45131 (BS) has been established

测试

Send发送类:

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;

public class Sender {

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session;
        Destination destination;
        MessageProducer producer;
        connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "tcp://192.168.1.9:61616");
        //connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "tcp://192.168.1.9:61618");
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
            destination = session.createQueue("FirstQueue");
            producer = session.createProducer(destination);
            producer.setDeliveryMode(DeliveryMode.PERSISTENT);
            sendMessage(session, producer);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != connection)
                    connection.close();
            } catch (Throwable ignore) {}
        }
    }

    private static void sendMessage(Session session, MessageProducer producer) throws Exception {
        for (int i = 1; i <= 5; i++) {
            TextMessage message = session.createTextMessage("===发送的消息" + i);
            System.out.println("发送消息:" + "ActiveMq 发送的消息" + i);
            producer.send(message);
        }
    }
}



Receiver接收类:

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;

public class Receiver {

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session;
        Destination destination;
        MessageConsumer consumer;
        connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "tcp://192.168.1.9:61617");
        //connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "tcp://192.168.1.9:61619");
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
            destination = session.createQueue("FirstQueue");
            consumer = session.createConsumer(destination);
            while (true) {
                TextMessage message = (TextMessage) consumer.receive(1000);
                if (null != message) {
                    System.out.println("收到消息" + message.getText());
                } else {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != connection)
                    connection.close();
            } catch (Throwable ignore) {}
        }
    }
}

经测试Receiver可以接受到数据,表示M1和M2已经共享了queue

2).Dynamic Broker-Cluster

Dynamic Discovery集群方式在配置ActiveMQ实例时,不需要知道所有其它实例的URI地址


组播(multicast)基于UDP协议,它是指在一个可连通的网络中,某一个数据报发送源向一组数据报接收目标进行操作的过程。在这个过程中,数据报发送者只需要向这个组播地址(一个D类IP)发送一个数据报,那么加入这个组播地址的所有接收者都可以收到这个数据报。组播实现了网络中单点到多点的高效数据传送,能够节约大量网络带宽,降低网络负载。


对activemq.xml做如下配置:

M1做如下配置:(黑色是必须的,红色是可选的)

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="AD" dataDirectory="${activemq.data}">
<networkConnectors>
    <networkConnector uri="multicast://239.0.0.5" duplex="false"/>
</networkConnectors>

<!-- networkConnectors节点的配置必须要在persistenceAdapter节点之前 -->


<persistenceAdapter>
            <jdbcPersistenceAdapter dataSource="#mysql-ds" useDatabaseLock="false"/>
        </persistenceAdapter>




<transportConnectors>
        <transportConnector discoveryUri="multicast://239.0.0.5" name="openwire" uri="tcp://0.0.0.0:61618?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>


配置JDBC方式的持久化适配器

<broker>标签之外配置数据源mysql-ds

<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://192.168.1.4:3306/AD?relaxAutoCommit=true"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="poolPreparedStatements" value="true"/>
</bean>




M2做如下配置:(黑色是必须的,红色是可选的)

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


<!--networkConnectors节点的配置必须要在persistenceAdapter节点之前 -->


<networkConnectors>
    <networkConnector uri="multicast://239.0.0.5" duplex="false"/>
</networkConnectors>
<persistenceAdapter>
             <jdbcPersistenceAdapter dataSource="#mysql-ds" useDatabaseLock="false"/>
        </persistenceAdapter>




<transportConnectors>
        <transportConnector discoveryUri="multicast://239.0.0.5" name="openwire" uri="tcp://0.0.0.0:61619?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>


配置JDBC方式的持久化适配器

<broker>标签之外配置数据源mysql-ds

<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://192.168.1.4:3306/BD?relaxAutoCommit=true"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="poolPreparedStatements" value="true"/>
</bean>




启动M1和M2,可以看到如下启动日志: network connection has been established

Network connection between vm://BD#0 and tcp:///192.168.1.9:61618@50410 (AD) has been established.

测试同static broker-cluster,可以得到相同的结果。
官网配置说明:http://activemq.apache.org/networks-of-brokers.html

Master-Slaver保证了数据的可靠性,Broker-Cluster提供了负载均衡,所以一般正式环境中都会采用:Master-Slaver+Broker-Cluster的模式


注意问题:

1.JMS之ActiveMQ启动时报锁定数据库的问题解决(JDBC方式时候):activeMQ的broker在启动时会锁定数据库。我们每个人在调试时,自己的运行环境中就会运行一个broker,所以会出现争用锁的现象(如果只有一个人运行则不会出现这样的问题),报错如下:Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED. SQLState: HY000 Vendor code: 1665 | org.apache.activemq.store.jdbc.adapter.DefaultJDBCAdapter

处理办法:在activemq.xml中,修改jdbcPersistenceAdapter选项,添加一个:useDatabaseLock="false"

2. 
mysql JDBC驱动包mysql-connector-java-5.1.39.jar, commons-dbcp-1.4.jar,  commons-pool-1.6.jar   
放到${ACTIVEMQ_HOME}/lib/下 
3.错误如下:impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging无法写入二进制日志自binlog_format 语句。
处理方法:修改my.cnf,binlog_format=mixed
4.端口61616,61617,8161等加入防火墙
5.chmod -R  777  /yxue/activeMQ  ,否则-bash: ./activemq: Permission denied