ActiveMQ之快速上手

JMS規範

ActiveMQ遵循JMS規範,是實現JMS接口的消息中間件,其遵循如下規範。
Provider(MessageProvider):生產者
Consumer(MessageConsumer):消費者
PTP:Point to Point,即點對點的消息模型
Pub/Sub:Publish/Subscribe,即發佈/訂閱的消息模型
Queue:隊列目標,只對單個用戶消費
Topic:主題目標,可對多個用戶消費
ConnectionFactory:連接工廠,JMS用它創建連接
Connection:JMS客戶端到JMS Provider的連接
Destination:消息的目的地
Session:會話,一個發送或者接收消息的線程

消息類型:
StreamMessage Java原始值的數據流
MapMessage 一套名稱-值對
TextMessage 一個字符串對象
ObjectMessage 一個序列化的Java對象
BytesMessage 一個爲解釋字節的數據流

HelloWord

1、下載ActiveMQ需要的jar包,官網

package helloworld;

import java.util.concurrent.TimeUnit;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

public class Helloworld {
	public static void main(String[] args) throws JMSException {
		new Thread(new Producer()).start();
		new Thread(new Consumer()).start();
	}
	
	private static class Producer implements Runnable{
		@Override
		public void run() {
			//用戶名,密碼,連接地址
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
					"admin","admin","tcp://192.168.80.128:61616"
					);
			Connection connection = null;
			try {
				connection = connectionFactory.createConnection();
				connection.start();
				//第一個參數是是否支持事務,第二個參數表示簽收模式
				Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
				//隊列名稱
				Destination destination = session.createQueue("helloworld");
				//參數爲destination
				MessageProducer producer = session.createProducer(null);
				//創建文本類型的消息
				TextMessage msg = session.createTextMessage("消息內容;helloworld");
				//發送消息
				producer.send(destination,msg);		
			} catch (JMSException e) {
				e.printStackTrace();
			}finally{
				try {
					if(connection != null)connection.close();
				} catch (JMSException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private static class Consumer implements Runnable{
		 @Override
		public void run() {
				//用戶名,密碼,連接地址
				ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
						"admin","admin","tcp://192.168.80.128:61616"
						);
				Connection connection = null;
				try {
					connection = connectionFactory.createConnection();
					connection.start();
					//第一個參數是是否支持事務,第二個參數表示簽收模式
					Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
					//隊列名稱
					Destination destination = session.createQueue("helloworld");
					//參數爲destination
					MessageConsumer consumer = session.createConsumer(destination);
					TimeUnit.SECONDS.sleep(1);
					TextMessage msg = (TextMessage)consumer.receive();
					System.out.println(msg.getText());
				} catch (JMSException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally{
					try {
						if(connection != null)connection.close();
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}			
		}
	}
}

ActiveMQ安全機制

通過http://192.168.80.128:8161/admin/可登陸ActiveMQ管控臺,用戶密碼默認爲admin,admin。在conf/jetty-realm.properties中配置,如圖:
在這裏插入圖片描述
在Java程序中,我們通過Connection連接ActiveMQ,需要通過用戶密碼進行認證,這裏的用戶密碼也可進行自定義設置。
在broker標籤中。

		<plugins>
            <simpleAuthenticationPlugin>
                <users>
                    <authenticationUser username="abc" password="abc" groups="users,admins"/>
                </users>
            </simpleAuthenticationPlugin>
        </plugins>

創建ConnectionFactory的代碼修改成如下:

			//用戶名,密碼,連接地址
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
					"abc","abc","tcp://192.168.80.128:61616"
					);

ActiveMQ修改持久化數據源

ActiveMQ默認持久化數據源爲kahadb,若希望更換成mysql等其他數據源,我們通過修改activemq.xml配置即可。
這裏以mysql爲例:
1、
<persistenceAdapter>標籤中添加如下內容,並將原有內容註釋:

<jdbcPersistenceAdapter dataSource="#mysql-ds"/>

2、添加一個新的bean到ActiveMQ,配置如下,添加到<bean>標籤中。

	<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/activemq?relaxAutoCommit=true"/>
			<property name="username" value="root"/>
			<property name="password" value="123456"/>
			<property name="maxActive" value="200"/>
			<property name="poolPreparedStatements" value="true"/>
	</bean>

3、dbcp連接池的版本取決於以上的配置,如果想換成其他持久化數據源,修改對應的bean配置即可。接下來在mysql中手動創建名爲activemq的數據庫,之後運行運行activemq後,發現報錯,依據異常類型ClassNotFoundException,確定爲缺少cdbcp和jdbc的jar包(dbcp和mysql的jar版本要對應),添加到activemq的lib目錄,之後運行後還是報錯,發先缺少commons-pool的jar包,下載與mysql對應的版本即可。最終添加後成功啓動。

ActiveMQ的其他API

1、設置是否持久化

	producer.setDeliveryMode(DeliveryMode.PERSISTENT);

2、Producer.send(arg1,arg2,arg3,arg4,arg5)方法可有5個參數
arg1:目標隊列
arg2:消息
arg3:是否持久化,DelivryMode.PERSISTENT
arg4:優先級0-4普通消息,5-9加急消息,默認爲4,當使用優先級時,需要在ActiveMQ.xml中添加配置
arg5:消息過期時間ms爲單位

3、獨佔消費
一個訂單從支付到完成需要進行順序消費,這就意味着,後發送給acitvemq的消息不能被先消費,因此,acitivemq提供了獨佔消費機制,當存在多個消費者時,一個消息隊列只會被其中一個消費者消費。在AcitveMQ4.*版本中設置獨佔消費的方式較爲簡單。

    queue = new ActiveMQQueue("TEST,QUEQUE?consumer.exclusive=true");
    consumer = session.createConsumer(queue);

4、獨佔消費的另外一種方法,手寫一個順序消費中間件
1、中間件負責順序接收來自ActiveMQ中的消息,並保存到隊列中,每個隊列可通過orderid與之對應(使用ConcurrentHashMap結構)
2、中間件通過負載均衡算法推送給某個消費,消費者完成消費後,發送確認給中間件,中間件又通過負載均衡算法進一步推送給第二個消費者,直到隊列中的消息都消費完成。
例如:Broker中有order1對應4條消息需順序消費,現在有消費者ABC,Broker先將order1推給A,待A完成併發送確認消息給Broker,Broker再將第2條消息發送給B,待B完成並回送確認消息給Broker,Broker再講第3條發送給C,依次輪詢,直到order1倍處理完成。特別多,如果Broker中還有order2,在發送完order1的第一條消息給A之後,無需等待A確認,Broker可繼續發送order2的消息給A。這樣的設計增加了併發效率。

5、監聽器
消費者常使用監聽器的方式進行消費。

	private class Consumer implements Runnable{
		@Override
		public void run() {
			//用戶名,密碼,連接地址
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
					"abc","abc","tcp://127.0.0.1:61616"
					);
			Connection connection = null;
			try {
				connection = connectionFactory.createConnection();
				connection.start();
				Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
				Destination destination = session.createQueue("helloworld");
				MessageConsumer consumer = session.createConsumer(destination);
				consumer.setMessageListener(new MyMessageListener());
			} catch (JMSException e) {
				e.printStackTrace();
			} 		
		}
		private class MyMessageListener implements MessageListener{
			@Override
			public void onMessage(Message msg) {
				try {
					if(msg instanceof TextMessage){
						TextMessage message = (TextMessage)msg;
						System.out.println(message.getText());
					}else if(msg instanceof MapMessage){
						
					}
				} catch (JMSException e) {
					e.printStackTrace();
				}
			}
		}
	}

6、過濾消費
ActiveMQ可對消息進行過濾,只接收消費包含特定屬性的消息,針對MapMessage,其操作支持sql的寫法。注意:其只對MapMessage的Property進行過濾,可常用來進行負載均衡。例如:

public class MapMessageMQ {

	public static void main(String[] args) {
		new Thread(new Producer()).start();
		new Thread(new Consumer()).start();
	}

	private static class Producer implements Runnable{
		@Override
		public void run() {
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("abc", "abc", "tcp://127.0.0.1:61616");
			Connection connection = null;
			try {
				connection = connectionFactory.createConnection();
				connection.start();
				Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
				Destination destination = session.createQueue("helloworld");
				MessageProducer producer = session.createProducer(null);
				producer.setDeliveryMode(DeliveryMode.PERSISTENT);

				MapMessage mapMessage1 = session.createMapMessage();
				mapMessage1.setString("name", "張三");
				mapMessage1.setStringProperty("name", "張三");
				mapMessage1.setInt("age", 20);
				mapMessage1.setIntProperty("age", 20);
				MapMessage mapMessage2 = session.createMapMessage();
				mapMessage2.setString("name", "李四");
				mapMessage2.setStringProperty("name", "李四");
				mapMessage2.setInt("age", 30);
				mapMessage2.setIntProperty("age", 30);

				producer.send(destination,mapMessage1);
				producer.send(destination,mapMessage2);
			} catch (JMSException e) {
				e.printStackTrace();
			}finally{
				try {
					if(connection != null){
						connection.close();
						connection = null;
					}
				} catch (JMSException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private static class Consumer implements Runnable{
		private static String SELECTOR1 = "age > 25 and name='李四'";
		@Override
		public void run() {
			ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("abc", "abc", "tcp://127.0.0.1:61616");
			Connection connection = null;
			try {
				connection = connectionFactory.createConnection();
				connection.start();
				Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
				Destination destination = session.createQueue("helloworld");
				MessageConsumer consumer = session.createConsumer(destination,SELECTOR1);
				consumer.setMessageListener(new MyMessageListener());
			} catch (JMSException e) {
				e.printStackTrace();
			}
		}

		private class MyMessageListener implements MessageListener{
			@Override
			public void onMessage(Message msg) {
				try {
					if(msg instanceof MapMessage){	//下面的邏輯可使用線程池異步執行
						MapMessage mapMessage = (MapMessage)msg;
						System.out.println("name:" + mapMessage.getString("name"));
						System.out.println("age:" + mapMessage.getInt("age"));
					}else if(msg instanceof TextMessage){
						TextMessage textMessage = (TextMessage)msg;
						System.out.println(textMessage.getText());
					}
				} catch (JMSException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

發佈訂閱模式
一條消息讓多個消費者同時接收,則可使用發佈訂閱模式,如下:

	Destination destination = session.createTopic("helloworld");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章