消息中间件的必要性:
1.场景一:我们可以想象一个淘宝的双十一,无数用户同时下订单,先不说有没有分布式&集群,但可以肯定的是,淘宝的代码肯定不是同小型项目一样(纯想象,没在淘宝工作过),直接Controller->Service->Dao,会阻塞死的。所以,急需将这些模块分开,Controller只用来接受简单请求,做数据转发,其它啥都不干,然后把收到的消息直接扔给消息中间件,Service收到消息后,做业务处理,至于Dao嘛……这样的话,至少用户能够下单成功,而不是点击下单后,页面直接卡住。
2.场景二:有大波定时业务需要处理,放在Redis&DataBase然后用定时任务扫描?怎么想都感觉很low啊,失去了及时性,而且对性能影响也很大。暂且不论Spring全家桶系列的定时任务,也有很多坑,阻塞模式吧,效率不行,中途异常处理不当的话坑定比较坑BOSS,非阻塞模式吧,一致性很难保证,如果再用分布式锁,效率会急剧下降。我们更需要一种机制,可以指定特定时间去执行,而不是傻乎乎的扫描,刚好,ActiveMQ支持,下面的入门Demo可以看到代码实现。
入门代码:
第一步:
去apache官网下载软件包,根据自己的系统选择Win或者Linux,我是Win系统(Linux老出现引导失败,啊)。直接解压,然后进入到:
.\apache-activemq-5.15.0-bin\apache-activemq-5.15.0\bin\win64
直接执行:
activemq.bat
当然,也可以自定编辑这个文件,指定一些配置文件路径之类的,双击直接启动,正常启动的画面应该是如下:
wrapper | --> Wrapper Started as Console
wrapper | Launching a JVM...
jvm 1 | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.org
jvm 1 | Copyright 1999-2006 Tanuki Software, Inc. All Rights Reserved.
jvm 1 |
jvm 1 | Java Runtime: Oracle Corporation 1.8.0_121 C:\Program Files\Java\jdk1.8.0_121\jre
jvm 1 | Heap sizes: current=251392k free=236973k max=932352k
jvm 1 | JVM args: -Dactivemq.home=../.. -Dactivemq.base=../.. -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=../../conf/broker.ks -Djavax.net.ssl.trustStore=../../conf/broker.ts -Dcom.sun.management.jmxremote -Dorg.apache.activemq.UseDedicatedTaskRunner=true -Djava.util.logging.config.file=logging.properties -Dactivemq.conf=../../conf -Dactivemq.data=../../data -Djava.security.auth.login.config=../../conf/login.config -Xmx1024m -Djava.library.path=../../bin/win64 -Dwrapper.key=aesu3DOUh14DC35B -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.pid=21996 -Dwrapper.version=3.2.3 -Dwrapper.native_library=wrapper -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1
jvm 1 | Extensions classpath:
jvm 1 | [..\..\lib,..\..\lib\camel,..\..\lib\optional,..\..\lib\web,..\..\lib\extra]
jvm 1 | ACTIVEMQ_HOME: ..\..
jvm 1 | ACTIVEMQ_BASE: ..\..
jvm 1 | ACTIVEMQ_CONF: ..\..\conf
jvm 1 | ACTIVEMQ_DATA: ..\..\data
jvm 1 | Loading message broker from: xbean:activemq.xml
jvm 1 | INFO | Refreshing org.apache.activemq.xbean.XBeanBrokerFactory$1@521a9779: startup date [Sun Jul 30 15:23:13 CST 2017]; root of context hierarchy
jvm 1 | INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[D:\Soft\mq\apache-activemq-5.15.0-bin\apache-activemq-5.15.0\bin\win64\..\..\data\kahadb]
jvm 1 | INFO | KahaDB is version 6
jvm 1 | INFO | Recovering from the journal @1:144358
jvm 1 | INFO | Recovery replayed 135 operations from the journal in 0.053 seconds.
jvm 1 | INFO | PListStore:[D:\Soft\mq\apache-activemq-5.15.0-bin\apache-activemq-5.15.0\bin\win64\..\..\data\localhost\tmp_storage] started
jvm 1 | INFO | JobSchedulerStore: ..\..\data\localhost\scheduler started.
jvm 1 | INFO | Apache ActiveMQ 5.15.0 (localhost, ID:DESKTOP-J9I9CUI-8400-1501399395220-0:1) is starting
jvm 1 | INFO | Listening for connections at: tcp://DESKTOP-J9I9CUI:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1 | INFO | Connector openwire started
jvm 1 | INFO | Listening for connections at: amqp://DESKTOP-J9I9CUI:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1 | INFO | Connector amqp started
jvm 1 | INFO | Listening for connections at: stomp://DESKTOP-J9I9CUI:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1 | INFO | Connector stomp started
jvm 1 | INFO | Listening for connections at: mqtt://DESKTOP-J9I9CUI:1883?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1 | INFO | Connector mqtt started
jvm 1 | WARN | ServletContext@o.e.j.s.ServletContextHandler@6cc5f5db{/,null,STARTING} has uncovered http methods for path: /
jvm 1 | INFO | Listening for connections at ws://DESKTOP-J9I9CUI:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1 | INFO | Connector ws started
jvm 1 | INFO | Apache ActiveMQ 5.15.0 (localhost, ID:DESKTOP-J9I9CUI-8400-1501399395220-0:1) started
jvm 1 | INFO | For help or more information please see: http://activemq.apache.org
jvm 1 | WARN | Store limit is 102400 mb (current store usage is 0 mb). The data directory: D:\Soft\mq\apache-activemq-5.15.0-bin\apache-activemq-5.15.0\bin\win64\..\..\data\kahadb only has 13765 mb of usable space. - resetting to maximum available disk space: 13765 mb
jvm 1 | WARN | Temporary Store limit is 51200 mb (current store usage is 0 mb). The data directory: D:\Soft\mq\apache-activemq-5.15.0-bin\apache-activemq-5.15.0\bin\win64\..\..\data\localhost only has 13765 mb of usable space. - resetting to maximum available disk space: 13765 mb
jvm 1 | INFO | No Spring WebApplicationInitializer types detected on classpath
jvm 1 | INFO | ActiveMQ WebConsole available at http://0.0.0.0:8161/
jvm 1 | INFO | ActiveMQ Jolokia REST API available at http://0.0.0.0:8161/api/jolokia/
jvm 1 | INFO | Initializing Spring FrameworkServlet 'dispatcher'
jvm 1 | INFO | No Spring WebApplicationInitializer types detected on classpath
jvm 1 | INFO | jolokia-agent: Using policy access restrictor classpath:/jolokia-access.xml
jvm 1 | WARN | Transport Connection to: tcp://127.0.0.1:8436 failed: java.net.SocketException: Connection reset
第二步:
构建一个简单的maven工程:pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ripple</groupId>
<artifactId>activemq</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.0</version>
</dependency>
</dependencies>
</project>
第三步:
写一个生产者,用来干嘛?生产消息(好苍白的解释),代码比较简单,所以也不过多解释了(无Spring环境集成)
package com.ripple.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ScheduledMessage;
import javax.jms.*;
import java.util.Date;
/**
* @author RippleChan
* @date 2017-07-30
* @time 13:33
* 生产者
*/
public class Producer {
public static void main(String[] args) throws JMSException {
/**
* 参照activemq.xml配置
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
*/
String tcpURI = "tcp://127.0.0.1:61616";
//通过activemq.xml配置用户名和密码,如果不配置,默认全部为null即可
/**
<plugins>
<simpleAuthenticationPlugin anonymousAccessAllowed="false">
<users>
<authenticationUser username="username" password="password" groups="users,admins"/>
</users>
</simpleAuthenticationPlugin>
</plugins>
*/
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("username", "password", tcpURI);
Connection connection = connectionFactory.createConnection();
connection.start();
//Boolean.FALSE不开启事务
//Session.AUTO_ACKNOWLEDGE自动签收机制
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
//队列名称
Queue queue = session.createQueue("q1");
MessageProducer producer = session.createProducer(queue);
//DeliveryMode.NON_PERSISTENT不缓存消息(重启MQ服务后保存的消息全部丢失)
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//TextMessage类消息
TextMessage textMessage = session.createTextMessage();
long time = 20 * 1000;
/**
* 延迟推送需要配置activemq.xml文件
* <broker …… schedulerSupport="true">
* ScheduledMessage.AMQ_SCHEDULED_DELAY延迟推送20秒
*/
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
textMessage.setText("发送时间"+new Date());
producer.send(textMessage);
System.out.println("发送消息");
if (connection != null) {
//关闭连接
connection.close();
}
}
}
第四步:
消费者,消费者,用来消费消息,真实环境,这玩意就是用来真实处理业务的。
package com.ripple.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ScheduledMessage;
import javax.jms.*;
import java.util.Date;
/**
* @author RippleChan
* @date 2017-07-30
* @time 13:33
* 生产者
*/
public class Producer {
public static void main(String[] args) throws JMSException {
/**
* 参照activemq.xml配置
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
*/
String tcpURI = "tcp://127.0.0.1:61616";
//通过activemq.xml配置用户名和密码,如果不配置,默认全部为null即可
/**
<plugins>
<simpleAuthenticationPlugin anonymousAccessAllowed="false">
<users>
<authenticationUser username="username" password="password" groups="users,admins"/>
</users>
</simpleAuthenticationPlugin>
</plugins>
*/
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("username", "password", tcpURI);
Connection connection = connectionFactory.createConnection();
connection.start();
//Boolean.FALSE不开启事务
//Session.AUTO_ACKNOWLEDGE自动签收机制
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
//队列名称
Queue queue = session.createQueue("q1");
MessageProducer producer = session.createProducer(queue);
//DeliveryMode.NON_PERSISTENT不缓存消息(重启MQ服务后保存的消息全部丢失)
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//TextMessage类消息
TextMessage textMessage = session.createTextMessage();
long time = 20 * 1000;
/**
* 延迟推送需要配置activemq.xml文件
* <broker …… schedulerSupport="true">
* ScheduledMessage.AMQ_SCHEDULED_DELAY延迟推送20秒
*/
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
textMessage.setText("发送时间"+new Date());
producer.send(textMessage);
System.out.println("发送消息");
if (connection != null) {
//关闭连接
connection.close();
}
}
}
小总结:流程一定要熟悉,P:生产者,S:ActiveMQ服务器,C:消费者,那么流程应该是这样的
P生产一条消息,发送给了S,S接受到消息后,发送给C,C签收了这条消,S把已经签收的消息删除