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");