TOPIC 和 Queue的技术特点对比
| Topic | Queue |
概要 | Publish Subscribe messaging 发布订阅消息 | Point-to-Point 点对点 |
有无状态 | topic数据默认不落地,是无状态的 | Queue数据默认会在mq服务器上以文件形式保存 比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面。 也可以配置成DB存储 |
完整性保障 | 并不保证publisher发布的每条数据,Subscriber都能接受到。 | Queue保证每条数据都能被receiver接收。 |
消息是否丢失 | 一般来说publisher发布消息到某一个topic时, 只有正在监听该topic地址的sub能够接收到消息; 如果没有sub在监听,该topic就丢失了。 | Sender发送消息到目标Queue,receiver可以异步接收这个Queue上的消息。 Queue上的消息如果暂时没有receiver来取,也不会丢失。 |
消息发布就收策略 | 一对多的消息发布接收策略, 监听同一个topic地址的多个sub都能收到publisher发送的消息。 Sub接收完通知mq服务器 | 一对一的消息发布接收策略,一个sender发送的消息,只能有一个receiver接收。 receiver接收完后,通知mq服务器已接收,mq服务器对queue里的消息采取删除或其他操作。 |
ActiveMQ使用线程池实现消息的生产与消费
JMS消息生产者
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.pool.PooledConnectionFactory;
import javax.jms.*;
import java.beans.ExceptionListener;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* JMS消息生产者
*/
public class JMSProducer implements ExceptionListener {
//设置连接的最大连接数
public final static int DEFAULT_MAX_CONNECTIONS = 5;
private int maxConnections = DEFAULT_MAX_CONNECTIONS;
//设置每个连接中的最大活动会话数.
public final static int DEFAULT_MAXIMUM_ACTIVE_SESSION_PER_CONNECTION = 300;
private int maximumActiveSessionPerConnection = DEFAULT_MAXIMUM_ACTIVE_SESSION_PER_CONNECTION;
//线程池数量.
public final static int DEFAULT_THREAD_POOL_SIZE = 50;
private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;
//强制使用同步返回数据的格式.
public final static boolean DEFAULT_USE_ASYNC_SEND_FOR_JMS = true;
private boolean useAsyncSendForJms = DEFAULT_USE_ASYNC_SEND_FOR_JMS;
//是否支持持久化消息
public final static boolean DEFAULT_IS_PERSISTENT = true;
private boolean isPersistent = DEFAULT_IS_PERSISTENT;
//链接地址
private String brokerUrl;
private String userName;
private String passWord;
private ExecutorService threadPool;
private PooledConnectionFactory connectionFactory;
public JMSProducer(String brokerUrl, String userName, String passWord) {
this(brokerUrl, userName, passWord, DEFAULT_MAX_CONNECTIONS, DEFAULT_MAXIMUM_ACTIVE_SESSION_PER_CONNECTION, DEFAULT_THREAD_POOL_SIZE, DEFAULT_USE_ASYNC_SEND_FOR_JMS, DEFAULT_IS_PERSISTENT);
}
public JMSProducer(String brokerUrl, String userName, String password, int maxConnections, int maximumActiveSessionPerConnection, int threadPoolSize, boolean useAsyncSendForJMS, boolean isPersistent) {
this.useAsyncSendForJms = useAsyncSendForJMS;
this.isPersistent = isPersistent;
this.brokerUrl = brokerUrl;
this.userName = userName;
this.passWord = password;
this.maxConnections = maxConnections;
this.maximumActiveSessionPerConnection = maximumActiveSessionPerConnection;
this.threadPoolSize = threadPoolSize;
init();
}
private void init() {
//设置Java线程池
this.threadPool = Executors.newFixedThreadPool(this.threadPoolSize);
//ActiveMQ的连接工厂
ActiveMQConnectionFactory actualConnectionFactory = new ActiveMQConnectionFactory(this.userName, this.passWord, this.brokerUrl);
actualConnectionFactory.setUseAsyncSend(this.useAsyncSendForJms);
//Active中的连接池工厂
this.connectionFactory = new PooledConnectionFactory();
this.connectionFactory.setCreateConnectionOnStartup(true);
this.connectionFactory.setMaxConnections(this.maxConnections);
this.connectionFactory.setMaximumActiveSessionPerConnection(this.maximumActiveSessionPerConnection);
}
/**
* 执行发送消息的具体方法.
*
* @param queue
* @param map
*/
public void send(final String queue, final Map<String, Object> map) {
this.threadPool.execute(new Runnable() {
@Override
public void run() {
try {
sendMsg(queue, map);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* 真正的执行消息发送
*
* @param queue
* @param map
* @throws Exception
*/
private void sendMsg(String queue, Map<String, Object> map) throws Exception {
Connection connection = null;
Session session = null;
try {
//从连接池中获取一个连接
connection = this.connectionFactory.createConnection();
//false:表示为非事务型消息,后面的参数表示消息的确认类型.
session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
//PTP消息方式
Destination destination = session.createQueue(queue);
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(this.isPersistent ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT);
Message message = getMessage(session, map);
producer.send(message);
} catch (Exception e) {
e.printStackTrace();
}finally {
closeSession(session);
closeConnection(connection);
}
}
private Message getMessage(Session session, Map<String, Object> map) throws Exception {
MapMessage message = session.createMapMessage();
if (null != map && !map.isEmpty()) {
Set<String> keys = map.keySet();
for (String key : keys) {
message.setObject(key, map.get(key));
}
}
return message;
}
private void closeSession(Session session) {
try {
if (null != session) {
session.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void closeConnection(Connection connection) {
try {
if (null != connection) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void exceptionThrown(Exception e) {
e.printStackTrace();
}
}
JMS消息消费者
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQPrefetchPolicy;
import javax.jms.*;
/**
* JMS消息消费者
*/
public class JMSConsumer implements ExceptionListener {
//队列预取策略.
public final static int DEFAULT_QUEUE_PREFETCH = 10;
private int queuePrefetch = DEFAULT_QUEUE_PREFETCH;
private String brokerUrl;
private String userName;
private String passWord;
private MessageListener messageListener;
private Connection connection;
private Session session;
private String queue;
public void start() throws Exception {
//ActiveMQ的连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(this.userName, this.passWord, this.brokerUrl);
connection = connectionFactory.createConnection();
//ActiveMQ预取策略
ActiveMQPrefetchPolicy prefetchPolicy = new ActiveMQPrefetchPolicy();
prefetchPolicy.setQueuePrefetch(queuePrefetch);
((ActiveMQConnection) connection).setPrefetchPolicy(prefetchPolicy);
connection.setExceptionListener(this);
connection.start();
//回话采用非事务级别,消息到达机制使用自动通知机制.
session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(this.queue);
MessageConsumer consumer = session.createConsumer(destination);
consumer.setMessageListener(this.messageListener);
}
/**
* 关闭链接.
*/
public void shutdown() {
try {
if (null != session) {
session.close();
session = null;
}
if (null != connection) {
connection.close();
connection = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public int getQueuePrefetch() {
return queuePrefetch;
}
public void setQueuePrefetch(int queuePrefetch) {
this.queuePrefetch = queuePrefetch;
}
public String getBrokerUrl() {
return brokerUrl;
}
public void setBrokerUrl(String brokerUrl) {
this.brokerUrl = brokerUrl;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public MessageListener getMessageListener() {
return messageListener;
}
public void setMessageListener(MessageListener messageListener) {
this.messageListener = messageListener;
}
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public String getQueue() {
return queue;
}
public void setQueue(String queue) {
this.queue = queue;
}
@Override
public void onException(JMSException e) {
}
}
import javax.jms.Message;
/**
* 提供消息操作的回调接口
*/
public interface MessageHandler {
public void handle(Message message);
}
消息消费者监听服务
import javax.jms.Message;
import javax.jms.MessageListener;
import java.util.concurrent.ExecutorService;
/**
* Created by CYX on 2017/3/17 0017.
*/
public class MultiThreadMessageListener implements MessageListener {
//默认线程池数量
public final static int DEFAULT_HANDLE_THREAD_POOL = 10;
//最大的处理线程数
private int maxHnadleThreads;
//提供消息回调调用接口
private MessageHandler messageHandler;
private ExecutorService handleThreadPool;
public MultiThreadMessageListener(MessageHandler messageHandler) {
this.messageHandler = messageHandler;
}
public MultiThreadMessageListener(int maxHnadleThreads, MessageHandler messageHandler) {
this.maxHnadleThreads = maxHnadleThreads;
this.messageHandler = messageHandler;
//支持阻塞的固定大小的线程池(自行手动创建)
this.handleThreadPool = new FixedAndBlockedThreadPoolExecutor(this.maxHnadleThreads);
}
/**
* 监听程序中自动调用的方法.
*
* @param message
*/
@Override
public void onMessage(Message message) {
//使用支持阻塞的固定大小的线程池来执行操作.
this.handleThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
MultiThreadMessageListener.this.messageHandler.handle(message);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
支持阻塞的固定大小线程池
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 支持阻塞的固定大小的线程池.
*/
public class FixedAndBlockedThreadPoolExecutor extends ThreadPoolExecutor {
//一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义.但功能更强大.
private ReentrantLock lock = new ReentrantLock();
private Condition condition = this.lock.newCondition();
public FixedAndBlockedThreadPoolExecutor(int size) {
super(size, size, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
/**
* 当线程池中没有空闲线程时,会挂起此方法的调用线程.直到线程池中有空闲线程.
*
* @param command
*/
@Override
public void execute(Runnable command) {
//进行同步锁定
this.lock.lock();
super.execute(command);
try {
//如果线程池的数量已经达到最大线程池的数量,则进行挂起操作.
if (getPoolSize() == getMaximumPoolSize()) {
this.condition.wait();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
this.lock.unlock();
}
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
try {
this.lock.lock();
this.condition.signal();
} catch (Exception e) {
e.printStackTrace();
}
}
}
生产消息测试
import com.activemq.activemq6.JMSProducer;
import java.util.HashMap;
import java.util.Map;
/**
* 生产者
*/
public class JMSProducerTest {
public static void main(String[] args) {
locationTest();
}
/**
* JMSProducer可以设置成全局的静态变量,只需要实例化一次即可使用,禁止循环重复实例化JMSProducer(因为其内部存在一个线程池.)
* <p>
* (有待考证)
* 支持openwire协议的默认链接为'tcp://localhost:61616',支持stomp协议的默认链接为'tcp://localhost:61613'.
* <p>
* 'nio://localhost:61613','tcp://localhost:61613' 可以在activemq.xml配置文件中进行设置.
*/
private static void locationTest() {
JMSProducer producer = new JMSProducer("tcp://localhost:61616", "admin", "admin");
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("name", "123456");
map.put("password", "password");
producer.send("test", map);
}
}
消息接收测试
import com.activemq.activemq6.JMSConsumer;
import com.activemq.activemq6.MessageHandler;
import com.activemq.activemq6.MultiThreadMessageListener;
import javax.jms.MapMessage;
import javax.jms.Message;
/**
* 消费者
*/
public class JMSConsumerTest {
/**
* JMSConsumer可以设置成全局的静态变量.只需实例化一次即可使用,禁止循环重复实例化JMSConsumer(因为其内部存在一个线程池.)
*
* @param args
*/
public static void main(String[] args) throws Exception{
JMSConsumer consumer = new JMSConsumer();
consumer.setBrokerUrl("tcp://localhost:61616");
consumer.setQueue("test");
consumer.setUserName("admin");
consumer.setPassWord("admin");
consumer.setQueuePrefetch(500);
consumer.setMessageListener(new MultiThreadMessageListener(50, new MessageHandler() {
@Override
public void handle(Message message) {
try {
System.out.println("name is : " + ((MapMessage) message).getString("id"));
System.out.println("name is : " + ((MapMessage) message).getString("name"));
System.out.println("name is : " + ((MapMessage) message).getString("password"));
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}));
consumer.start();
}
}