ActiveMQ与REST API实践

摘要

1. 如何搭建Active REST服务的环境

2. REST API以及相关配置参数介绍

3. ActiveMQ中optimizeAck机制介绍

 

    ActiveMQ实现了基于RESTFUL的API,允许开发者可以直接通过HTTP POST/GET方式发布消息和消费消息,这是一种基于文本(/文件)传输的协议.因为在某些特定的环境中,通过HTTP方式操作消息更加便捷,比如基于HTML5的移动终端上,通过javascript或者窗体事件来获取消息.

 

    通常,在开放互联网环境中,非核心消息/类日志系统中,我们会使用ActiveMQ REST来push(保存)/delete(消费)消息。我们可以将REST服务伪装成一个日志存储与分发系统,我们不需要额外的框架支撑,就能实现基于大数据消息的存储与分发。同时也可以在web brower中使用REST,通过轮询或者keepAlive来实现消息的实时推送。

 

    REST协议的最大特点就是更加面向互联网应用(而非企业级应用),其交互便捷, 数据可读性强.当然其局限性也很明显: 在安全上/消息ACK机制上/对事务的支持上/还有消息的异步操作上都有所欠缺(相对于openwire).无论消费者还是生产者,都需要通过URL来描述操作,这就局限了在REST协议下client端调优的情况,同时它还受限于HTTP协议(:keep-alive)以及servlet容器对异步的支持能力。

 

    每个ActiveMQ broker都集成了servlet环境(默认为jetty环境),在启动activeMQ broker的时候,默认挂载启动.org.apache.activemq.web.MessageServlet是官方已经实现的REST代理类,用来代理REST请求.实现原理也非常简单,servlet接收到请求后,内部将使用普通的JMS API + openwire协议与broker通讯.在REST协议中,AcitveMQ约定了URL格式以及参数列表,以及发布消息必须基于HTTP POST,消费消息可以基于GET/DELETE方式.

Java代码   收藏代码
  1. >>>http://localhost:8161/api/message/test-queue?type=queue&clientId=test1&json=true    
  2. Hello,Restful!   

 

1. 环境准备

   AcitveMQ broker在启动的时候,会挂载启动多个web系统,其中一个web系统就是"api",你可以参看{activemq}/webapps/api中查看相应的配置.这个api系统,就是为REST协议而服务的.{activemq}/conf/activemq.xml文件,是activemq所有配置文件的入口,文件中通过配置引入jetty环境即可使用api系统.

Java代码   收藏代码
  1. <import resource="jetty.xml"/>  

 

Java代码   收藏代码
  1. <transportConnector name="rest" uri="tcp://0.0.0.0:51616?maximumConnections=1000&wireformat.maxFrameSize=1048576&keepAlive =true&soTimeout =30000"/>  

    此外,因为REST底层仍然基于openwire(tcp通道)支持,且REST的特性需要贴近HTTP的“短期”/“小数据”等特性,所以我们在transportConnector中,将uri中增加soTimeout=3000,以及控制最大数据尺寸为1M。

 

    从jetty.xml中,我们可以看到所有web系统的配置信息:

Java代码   收藏代码
  1. <bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint">  
  2.     <property name="name" value="BASIC" />  
  3.     <property name="roles" value="admin" />  
  4.     <!-- 是否使用安全认证 -->  
  5.     <property name="authenticate" value="true" />  
  6. </bean>  
  7. <bean id="securityConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">  
  8.     <property name="constraint" ref="securityConstraint" />  
  9.     <property name="pathSpec" value="/*" />  
  10. </bean>  

 

    5.8之后,jetty环境中所有的web系统默认都是需要授权的,这就意味着,使用REST API时需要提供"name" + "password"校验信息,否则将REST请求将会被拒绝.当然,开发者可以通过注释如下参数式REST不再需要认证.

Java代码   收藏代码
  1. <!-- 是否使用授权认证,"true"/"false" -->  
  2. <!--  
  3. <property name="authenticate" value="true" />  
  4. -->  

 

    此外,让ActiveMQ broker来提供REST服务似乎不是一种良好的架构设计,这将很大程度上限制了开发者对REST服务的扩展性,在很多情况下,我们需要把REST服务迁移到专门的web应用中,那么我们就不需要让jetty环境挂载启动api系统.

Java代码   收藏代码
  1. <!--  
  2. <bean class="org.eclipse.jetty.webapp.WebAppContext">  
  3.     <property name="contextPath" value="/api" />  
  4.     <property name="resourceBase" value="${activemq.home}/webapps/api" />  
  5.     <property name="logUrlOnStart" value="true" />  
  6. </bean>  
  7. -->  

    接下来,我们将自己搭建ActiveMQ REST服务环境。

 

2.配置文件

    如果你基于activeMQ jetty环境,你可能需要查看一下{activemq}/webapps/api/WEB-INF/web.xml文件,如果你的REST server是自己搭建的,你可能需要把此web.xml文件中的核心信息copy到自己的项目中.无论如何,你都需要在servlet环境中,配置并启动"org.apache.activemq.web.MessageServlet". 其中"org.jolokia.http.AgentServlet"仅作为可选,对于自己搭建REST服务,jolokia就无须配置了。

 

    构建一个servlet系统,我们必须首先确定servlet版本,因为这将直接关系到REST的异步特性,目前比较稳定的版本组合为:servlet 2.4(2.5) + tomcat 6.x,servlet 3.0.1 + tomcat7.x,servlet 3.1 + tomcat8.x。在servlet2.x与servlet3.x中,web.xml配置有少许的差别,主要是因为它们对“异步”操作的控制方式不同,所谓异步,就是当REST请求中消费消息时,如果此时Queue/Topic中没有消息,那么此次请求是否挂起直到有消息可供消费。

 

    Tomcat配置(Tomcat 6.x+)

 

Java代码   收藏代码
  1. <Connector port="8080" protocol="HTTP/1.1"   
  2.            connectionTimeout="20000"   
  3.            redirectPort="8443"   
  4.            URIEncoding="utf-8"   
  5.            useBodyEncodingForURI="false"  
  6.            maxThreads="120" maxKeepAliveRequests="60"/>  
    protocol协议为“HTTP/1.1",如果你指定了"HTTP/1.0",那么可能无法良好的使用"keep-alive",需要一些额外的控制,在此就不再赘言。其中connectionTimeout可以根据实际情况调整,用来控制connector释放链接前阻塞的时间,默认情况下keepAliveTimeout和connectionTimeout值一样,用来描述链接保持活性的时间。

    pom.xml(servlet-2.4)

Java代码   收藏代码
  1. <dependency>  
  2.     <groupId>javax.servlet</groupId>  
  3.     <artifactId>servlet-api</artifactId>  
  4.     <version>2.4</version>  
  5.     <scope>provided</scope>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>org.mortbay.jetty</groupId>  
  9.     <artifactId>jetty-util</artifactId>  
  10.     <version>6.1.26</version>  
  11. </dependency>  
  12. <dependency>  
  13.     <groupId>org.apache.activemq</groupId>  
  14.     <artifactId>activemq-web</artifactId>  
  15.     <version>5.8.0</version>  
  16. </dependency>  
  17.     <dependency>  
  18.     <groupId>org.apache.activemq</groupId>  
  19.     <artifactId>activemq-all</artifactId>  
  20.     <version>5.8.0</version>  
  21. </dependency>  

    因为servlet2.x不支持"异步"操作,因此需要使用FauxContinuation(伪延续)来提供"阻塞" + "事件通知"特性,因此引入了"jetty-util"依赖包。

 

    web.xml(servlet-2.4)

Java代码   收藏代码
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  5. <display-name>test-web</display-name>  
  6.   
  7.     <!-- activeMQ global param -->  
  8.     <!-- WebClient -->  
  9.     <!-- Required-->  
  10.     <context-param>  
  11.         <param-name>org.apache.activemq.connectionFactory</param-name>  
  12.         <param-value>activeMQConnectionFactory</param-value>  
  13.     </context-param>  
  14.     <!-- Required-->  
  15.     <context-param>  
  16.         <param-name>org.apache.activemq.brokerURL</param-name>  
  17.         <!-- underlying openwire transport-->  
  18.         <param-value>tcp://127.0.0.1:51616</param-value>  
  19.     </context-param>  
  20.     <!-- optional, global prefetchSize,default "1" -->  
  21.     <context-param>  
  22.         <param-name>org.apache.activemq.connectionFactory.prefetch</param-name>  
  23.         <param-value>1</param-value>  
  24.     </context-param>  
  25.     <!-- optional,optimizeAcknowledge -->  
  26.     <context-param>  
  27.         <param-name>org.apache.activemq.connectionFactory.optimizeAck</param-name>  
  28.         <param-value>true</param-value>  
  29.     </context-param>  
  30.     <!-- optional,HTTP header -->  
  31.     <context-param>  
  32.         <param-name>org.apache.activemq.selectorName</param-name>  
  33.         <!-- parameter of selector,default is "selector" -->  
  34.         <param-value>selector</param-value>  
  35.     </context-param>  
  36.     <servlet>  
  37.         <servlet-name>OrdersQueueServlet</servlet-name>  
  38.         <servlet-class>org.apache.activemq.web.MessageServlet</servlet-class>  
  39.         <load-on-startup>1</load-on-startup>  
  40.         <!-- Required,but can be specified in the url -->  
  41.         <init-param>  
  42.             <param-name>destination</param-name>  
  43.             <param-value>orderQueue?consumer.prefetchSize=10</param-value>  
  44.         </init-param>  
  45.         <!-- Required -->  
  46.         <init-param>  
  47.             <param-name>topic</param-name>  
  48.             <param-value>false</param-value>  
  49.         </init-param>  
  50.         <!-- optional,important -->  
  51.         <!--  
  52.         <init-param>  
  53.             <param-name>destinationOptions</param-name>  
  54.             <param-value></param-value>  
  55.         </init-param>  
  56.         -->  
  57.         <!-- optional,default is "20000" -->  
  58.         <init-param>  
  59.             <param-name>maximumReadTimeout</param-name>  
  60.             <param-value>10000</param-value>  
  61.         </init-param>  
  62.         <!-- optional,defaut is "1000" -->  
  63.         <init-param>  
  64.             <param-name>replyTimeout</param-name>  
  65.             <param-value>10000</param-value>  
  66.         </init-param>  
  67.         <!-- optional,important,default is "text/xml" -->  
  68.         <init-param>  
  69.             <param-name>defaultContentType</param-name>  
  70.             <param-value>text/plain; charset=utf-8</param-value>  
  71.         </init-param>  
  72.     </servlet>  
  73.   
  74.     <servlet-mapping>  
  75.         <servlet-name>OrdersQueueServlet</servlet-name>  
  76.         <url-pattern>/rest/order/*</url-pattern>  
  77.     </servlet-mapping>  
  78.     <filter>  
  79.         <filter-name>continuationFilter</filter-name>  
  80.         <filter-class>org.eclipse.jetty.continuation.ContinuationFilter</filter-class>  
  81.         <init-param>  
  82.             <param-name>faux</param-name>  
  83.             <param-value>true</param-value>  
  84.         </init-param>  
  85.     </filter>  
  86.     <filter-mapping>  
  87.         <filter-name>continuationFilter</filter-name>  
  88.         <url-pattern>/rest/*</url-pattern>  
  89.     </filter-mapping>  
  90.   
  91.   
  92. </web-app>  

    在web.xml中,我们需要引入continuationFitler,这个Fitler主要用来构建"continuation"实例,此filter将与MessageServlet的内部实现互相协调,通过"轮询"(Filter中) +"阻塞" + "事件通知"(continuation中),来实现伪异步。具体相关的jetty continuation原理设计请参见其他文档。

    配置文件中标记为"Required"部分是必须要按照规则配置的,"optional"部分为可选的。

 

 

    pom.xml(servlet 3.x)

Java代码   收藏代码
  1. <dependency>  
  2.     <groupId>javax.servlet</groupId>  
  3.     <artifactId>javax.servlet-api</artifactId>  
  4.     <version>3.0.1</version>  
  5.     <scope>provided</scope>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>org.apache.activemq</groupId>  
  9.     <artifactId>activemq-web</artifactId>  
  10.     <version>5.8.0</version>  
  11. </dependency>  
  12.     <dependency>  
  13.     <groupId>org.apache.activemq</groupId>  
  14.     <artifactId>activemq-all</artifactId>  
  15.     <version>5.8.0</version>  
  16. </dependency>  

    servlet 3.x已经支持了异步,因此我们无需jetty-util的依赖包。

 

    web.xml(servlet 3.x)

Java代码   收藏代码
  1. ?xml version="1.0" encoding="utf-8"?>  
  2. <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"  
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">  
  5. <display-name>test-web</display-name>  
  6.     <!-- 同 servlet 2.4 -->  
  7.     <servlet>  
  8.         <servlet-name>OrdersQueueServlet</servlet-name>  
  9.         <servlet-class>org.apache.activemq.web.MessageServlet</servlet-class>  
  10.         <load-on-startup>1</load-on-startup>  
  11.         <async-supported>true</async-supported>  
  12.         <!-- 同 servlet 2.4 -->  
  13.   
  14.     </servlet>  
  15.   
  16.     <servlet-mapping>  
  17.         <servlet-name>OrdersQueueServlet</servlet-name>  
  18.         <url-pattern>/queue/order/*</url-pattern>  
  19.     </servlet-mapping>  
  20.   
  21. </web-app>  

    此文件和servlet 2.4几乎一样,只是在<servlet>中增加了<async-supported> 表示使用"异步",此外在servlet 3.x中,也无需配置continuationFitler。

 

    此处需要特别声明一下,因为tomcat容器的Thread模型的限制,在servlet2.4中“异步”操作仅仅是一种“模拟的”(Faux),其内部机制仍然是基于当前线程阻塞(wait)而实现,因此servlet2.4中使用NIO + 长连接的方式,tomcat仍然无法支撑过多的request。在servlet3.x之后,tomcat 7.x之后,才能配合实现相对完善的NIO/AIO + 长连接方案,tomcat可以在支撑较大请求(包括连接数)的情况下,仍然能够保证性能(内存消耗,吞吐能力)上相对平稳

 

 

3. MessageServlet原理与使用注意事项

 

    [###]在上述web.xml中,其中<context-param>包含"brokerUrl"/"optimizeAck"/"prefetch"/"selectorName"主要四个参数。

    "brokerUrl"部分需要明确配置brokerUrl参数,这个值和我们常用的openwire协议中的 brokerUrl一样,也允许在brokerUrl中使用查询字符串等用于调优,不过这个brokerUrl需要是一个openwire协议中合法的transport通道(AcitveMQ broker中可以有任意多个openwire transportConnector),比如"tcp://""failover://"等。MessageServlet将根据"brokerUrl"的值构建ConnectionFactory。

 

    "optimizeAck"表明consumer是否使 用"optimizeAck"选项来优化消息ACK策略,它用来描述"消息确认"的时机,如果此值为true,将采取"延迟ACK",否则将会"立即ACK"。如果开启,client端将会使用"延迟 + 批量ACK"策略来确认消息,它可以有效的提高消息消费的效率,但有可能会接收到重复的消息,具体原理请参考其他文档。在MessageServlet中,消息的确认方式为“AUTO_ACKNOWLEDGE”,可以有效的在ActiveMQ "optimizeAcknowledge"机制中运行(简析:AUTO_ACK + optimizeAck + prefetchSize > 1 最终构成“批量延迟ACK”优化机制,这是一种高效的消息消费调优策略)。[optimizeACK机制]

 

    "prefetch"用来描 述消费者"预获取"消息的条数(批量消息),默认值为"1",此参数值即为prefetchSize。如果broker端有足够多的消息,那么Consumer客户端将会在每次"optimizeAck"确认之后,client端可以一次性获取(或者由broker推送)多条消 息并缓存,总缓存的消息量(未消费 + 已消费但未ACK)控制在prefetchSize之内,这是一个优化消费者的参数,通常和"optimizeAck"协同;如果我们的应用中,REST 客户端1) Queue/Topic通道中消息的数量少 2) REST调用频率都比较低 3) 同一个Queue中Consumer个数较少时,我们需要将optimizeAck=false,或者将 prefetchSize设置为1,场景例如:(获取"推荐商品"消息,"即时通讯"消息);这两个参数非常重要,同时REST API中没有提供ACK的接口(url),所以希望开发者慎重考虑这两个参数。"prefetch"参数值将会对所有Connection有效,如果开发者在destinationOptions中没有指定额外的"prefetchSize"选项,那么将会使用此"prefetch"参数作为默认值。

 

    [###]selectorName为可选参数,默认值为"selector",selector在 ActiveMQ中,称为"消息选择器";我们可以在REST Url中通过传递"selector=type%3D1"(备注:urlEncode之前为"type=1"),此后将会根据此selector值来构建 Consumer实例,那么此Consumer将会接收到符合selector条件的消息,具体请参考其他文档;不过此处需要注意,对于每个 clientId,只会创建一个Consumer实例,一旦创建,selector值将不会被修改,即使此后使用REST URL传递不同的selector值也不会生效;这个实现和ActiveMQ在openwire协议下一样,如果需要更新selector,需要重建consumer,在REST API中,重建consumer的API就是"unsubscribe"。

    上面的内容,就是ActiveMQ 消息者优化策略的核心原理,如果您还没有明白,我只能"跪"下了!!!

 

    在<servlet>配置中,还有几个相对简单的配置信息<init-param>,这些配置信息通常和url查询字符串有很大关系,它们决定了REST API的URL风格。

    destination表示"消息目的地",无论是发送消息还是接受消息,必须指定destination才能与broker交互。此外,我们可以在destination中添加"destinationOptions",比如在示例中添加了"customer.prefetchSize=1"表示通过此destination创建的所有的consumer都使用prefetchSize=1。你可以参考更多的文档,来使用"destinationOptions"调优等。不过此后仍然可以在REST URL中使用"&destination=orderQueue"来指定destination,不过我们强烈建议不要使用这种方式,通常情况下,我们会为每个destination单独配置成一个servlet。

 

    destinationOptions也可以通过<init-param>指定,不过此值只会在REST URL中指定了"&destination=orderQueue"查询字符串时才会有效。如果你在web.xml中配置了"destination"参数,那就需要把相应options追加其后即可,而无需再次配置"destinationOptions"参数。

 

    maxinumReadTimeout表示REST请求阻塞的最长时间,REST URL可以通过查询字符串"&readTimeout=1000"来指定当前请求阻塞时间,不过不能指定比"maxunumReadTimeout"更大的值.设置此值的时候必须要注意,它最好不要大于tomcat中connector链接的keepAlive时间。

 

    replyTimeout,在请求应答(request-reply)模式中,等待"reply"的最大时间。通过REST 发送消息时,可以指定消息是否为"sync"(同步)模式,如果URL中"sync=true",此时消息发送之后即等待消费者"reply",如果等待replyTimeout之后,仍然没有得到应答,那么此消息将意味着发送失败(REST客户端将会得到一个501错误),在sync=true下,内部使用了camel组件来实现了这种机制。如果没有指定sync,那么消息将会立即发送给broker,也不会涉及到replyTimeout的问题。

 

    [###] REST API列表

    消费消息

        URL:  /queue/order?clientId=client-001&readTimeout=1000&type=json   

        HTTP METHOD:  GET,DELETE

    发送消息

        URL:  /queue/order?clientId=client-00&JMSDeliveryMode=persistent1 或者
                  /queue/order?clientId=client-001&sync=true&replyTimeout=1000   

        HTTP METHOD: POST
                 Header
                 body:消息内容

    取消消费(关闭consumer)

        URL: /queue/order?clientId=client-001&action=unsubscribe   

        HTTP METHOD: POST

 

    提醒,所有的REST URL中都必须携带clientId参数,无论如何,调用者都需要采取一定的手段,不能让clientId总是变化。这和MessageServlet的内部实现有关,它为了提升性能以及避免对broker造成太多的资源浪费,将会根据clientId来缓存(存入Map)一个"webClient"实例,每个webClient实例中都会持有"JMS Session"/"Consumer"/"producer";因此过多的webClient实例会带来一些风险。当REST调用异常时,比如tomcat server返回501内部错误,我们应该"unsubscribe"相应的clientId,这会触发MessageServlet从缓存中移除相应的webClient实例(包括关闭consumer和producer等)。

    "发送消息"URL中,可以指定"JMSDeliveryMode=persistent"表示消息的传输模式(持久化与否),不过当sync=true时,"JMSDeliveryMode"参数将不会有效。

 

4. 使用REST API

    我们可以使用浏览器,或者任何http客户端,都可以访问这写REST API,在MessageServlet中,支持"POST"/"DELETE"/"GET"三种合法的请求方法(request method),其中"DELETE"/"GET"用于消费消息,"POST"用于发布消息和关闭Consumer链接。如下,我们通过apache httpclient来模拟REST API调用过程以及简单的异常处理。HttpClientUtil.java样例:

 

Java代码   收藏代码
  1. public class HttpClientUtil {  
  2.   
  3.     private static final CloseableHttpClient httpClient;  
  4.     public static final String CHARSET = "UTF-8";  
  5.   
  6.     static {  
  7.         RequestConfig config = RequestConfig.custom()  
  8.                 .setConnectTimeout(60000)  
  9.                 .setSocketTimeout(-1)  
  10.                 .build();  
  11.         httpClient = HttpClientBuilder.create()  
  12.                 .setDefaultRequestConfig(config)  
  13.                 .build();  
  14.         /** 
  15.         keepAliveClient = HttpClientBuilder.create() 
  16.                 .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE) 
  17.                 .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE) 
  18.                 .build(); 
  19.          **/  
  20.     }  
  21.   
  22.     public static String restGet(String url,Map<String,String> params) {  
  23.         if (StringUtils.isBlank(url)) {  
  24.             return null;  
  25.         }  
  26.         try {  
  27.             HttpGet httpget = new HttpGet(url);  
  28.             if (params != null && !params.isEmpty()) {  
  29.                 List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());  
  30.                 for (Map.Entry<String, String> entry : params.entrySet()) {  
  31.                     String value = entry.getValue();  
  32.                     if (value != null) {  
  33.                         pairs.add(new BasicNameValuePair(entry.getKey(), value));  
  34.                     }  
  35.                 }  
  36.                 url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs,CHARSET));  
  37.             }  
  38.             CloseableHttpResponse response = httpClient.execute(httpget); //block here  
  39.             int statusCode = response.getStatusLine().getStatusCode();  
  40.             // >= 400  
  41.             if(statusCode >= HttpStatus.SC_BAD_REQUEST || statusCode < HttpStatus.SC_OK) {  
  42.                 httpget.abort();  
  43.                 response.close();  
  44.                 throw new RuntimeException("HttpClient,error status code :" + statusCode);  
  45.             }  
  46.             HttpEntity entity = response.getEntity();  
  47.             String result = null;  
  48.             if (entity != null) {  
  49.                 result = EntityUtils.toString(entity, "utf-8");  
  50.             }  
  51.             //will close inputStream.  
  52.             EntityUtils.consume(entity);  
  53.             response.close();  
  54.             return result;  
  55.         } catch (Exception e) {  
  56.             e.printStackTrace();  
  57.         }  
  58.         return null;  
  59.     }  
  60.   
  61.     public static String restPost(String url, Map<String,String> params) {  
  62.         if (StringUtils.isBlank(url)) {  
  63.             return null;  
  64.         }  
  65.         try {  
  66.             HttpPost httpPost = new HttpPost(url);  
  67.             if (params != null && !params.isEmpty()) {  
  68.                 List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());  
  69.                 for (Map.Entry<String, String> entry : params.entrySet()) {  
  70.                     String value = entry.getValue();  
  71.                     if (value != null) {  
  72.                         pairs.add(new BasicNameValuePair(entry.getKey(), value));  
  73.                     }  
  74.                 }  
  75.                 httpPost.setEntity(new UrlEncodedFormEntity(pairs, CHARSET));//Utf-8  
  76.             }  
  77.   
  78.             CloseableHttpResponse response = httpClient.execute(httpPost);  
  79.             int statusCode = response.getStatusLine().getStatusCode();  
  80.             if (statusCode != 200) {  
  81.                 httpPost.abort();  
  82.                 response.close();  
  83.                 throw new RuntimeException("HttpClient,error status code :" + statusCode);  
  84.             }  
  85.             HttpEntity entity = response.getEntity();  
  86.             String result = null;  
  87.             if (entity != null) {  
  88.                 result = EntityUtils.toString(entity, CHARSET);  
  89.             }  
  90.             EntityUtils.consume(entity);  
  91.             response.close();  
  92.             return result;  
  93.         } catch (Exception e) {  
  94.             e.printStackTrace();  
  95.         }  
  96.         return null;  
  97.     }  
  98. }  

  

   REST测试客户端代码,RestClient.java

 

Java代码   收藏代码
  1. public class RestClient {  
  2.     private static String baseUrl = "http://localhost:8080/rest/order";  
  3.     //any client must have one clientId  
  4.     // but the best is that they should be different.  
  5.     private static String clientId = "restful";  
  6.   
  7.     /** 
  8.      * @param args 
  9.      */  
  10.     public static void main(String[] args) throws Exception{  
  11.         String selfId = clientId + "_" + RandomStringUtils.random(6,true,true);  
  12.         for(int i=0; i< 5; i++){  
  13.             postMessage(selfId,"Hello,rest activeMQ.." + i);  
  14.         }  
  15.         System.out.println("post message end..");  
  16.         consume(selfId);  
  17.         Thread.sleep(3000);  
  18.         consume(selfId);  
  19.         System.out.println("end!!");  
  20.     }  
  21.   
  22.     public static void consume(String selfId){  
  23.   
  24.         while(true){  
  25.             try{  
  26.                 Map<String,String> params = new HashMap<String,String>();  
  27.                 params.put("clientId",selfId);  
  28.                 params.put("readTimeout","10000");//max blocking time as MS.  
  29.                 String response = HttpClientUtil.restGet(baseUrl,params);  
  30.                 System.out.println(response);  
  31.                 if(StringUtils.isBlank(response)){  
  32.                     Thread.sleep(3000);  
  33.                 }  
  34.             }catch (InterruptedException e){  
  35.                 break;  
  36.             } catch (Exception e){  
  37.                 unSubscribe(selfId);  
  38.             }  
  39.         }  
  40.     }  
  41.   
  42.     protected  static void unSubscribe(String selfId){  
  43.         try{  
  44.             Map<String,String> params = new HashMap<String,String>();  
  45.             params.put("clientId",selfId);  
  46.             params.put("action""unsubscribe");  
  47.             HttpClientUtil.restPost(baseUrl, params);  
  48.         } catch (Exception e){  
  49.             e.printStackTrace();  
  50.         }  
  51.     }  
  52.   
  53.     public static void postMessage(String selfId,String body){  
  54.         try{  
  55.             Map<String,String> params = new HashMap<String,String>();  
  56.             params.put("clientId",selfId);  
  57.             //sync or async  
  58.             // if "sync",after sending out a message,it should be blocking util the message is consumed.  
  59.             //params.put("sync","true");  
  60.             params.put("body",body);  
  61.             HttpClientUtil.restPost(baseUrl,params);  
  62.         } catch (Exception e){  
  63.             e.printStackTrace();  
  64.         }  
  65.     }  
  66.   
  67. }  

    如果按照,上述的配置,测试代码应该可以顺利通过!!

    Active web项目中,还提供了便于Ajax调用调用的REST方案(Servlet + javascript),如果读者能够清楚上述讲解的内容,我相信你自己已经可以搭建和配置响应的环境了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ActiveMQ和RabbitMQ都是流行的消息队列软件,它们的主要区别在于: 1. 语言支持:ActiveMQ是用Java编写的,而RabbitMQ是用Erlang编写的,因此RabbitMQ在处理高并发和高吞吐量时更加出色。 2. 协议支持:ActiveMQ支持多种协议,包括OpenWire、Stomp、AMQP和MQTT等,而RabbitMQ主要支持AMQP协议。 3. 可靠性:RabbitMQ在消息传递方面更加可靠,因为它使用了一些高级特性,如事务和确认机制。 4. 性能:RabbitMQ的性能比ActiveMQ更好,因为它使用了一些高级技术,如预取和流控制等。 总之,如果你需要处理高并发和高吞吐量的消息传递,那么RabbitMQ可能是更好的选择。如果你需要支持多种协议和更灵活的配置选项,那么ActiveMQ可能更适合你。 ### 回答2: ActiveMQ和RabbitMQ都是开源的消息中间件,它们都有自己独特的优势和适用场景。 ActiveMQ是基于Java的消息队列系统,它支持Java Messaging Service(JMS)标准,并且可以通过许多编程语言访问。ActiveMQ支持多种协议,如AMQP、MQTT、OpenWire和STOMP,且其性能较高,适合处理大量消息。ActiveMQ还提供了许多高级功能,如分布式事务、消息持久性、监视和管理等。此外,ActiveMQ还具有高可用性、冗余性和容错性等特点。 而RabbitMQ则是基于Erlang语言开发的,它支持AMQP(Advanced Message Queueing Protocol)和许多高级消息队列特性,如异步通信、消息确认、消息持久性和发布/订阅模式等。RabbitMQ还支持多种客户端库,包括Java、.NET、Python、Ruby和PHP等,适用于各种不同的应用场景。RabbitMQ还提供了许多插件和扩展,如消息转发器、集群和管理插件等,可以适应大量数据流量的需求。 在实际应用中,选择使用哪种消息队列系统取决于应用的具体需求和场景。如果需要高性能、高可用性、高容错性和高度分布式,则可以选择ActiveMQ。如果需要可扩展性、弹性、多语言支持和丰富插件,则可以选择RabbitMQ。当然,在选择之前,需要对这两个开源项目的详细了解和比较,以便能够选择最适合特定应用场景的消息队列系统。 ### 回答3: ActiveMQ和RabbitMQ是两种主要的消息队列中间件。它们在功能和性能方面均有很大的不同,这里将会深入探讨它们之间的差异。 首先是它们的架构和工作原理。ActiveMQ的架构是基于Java消息服务规范的,可以在Java、C++、Python等多种语言中运行。RabbitMQ是基于AMQP协议的,它是一种面向消息的中间件协议,用于消息传递、路由和连接的标准。ActiveMQ使用JMS作为应用程序接口,而RabbitMQ在AMQP基础上提供了自己的AMQP实现代码。所以,ActiveMQ更适合面向Java开发人员,而RabbitMQ适合更广泛的应用场景。 其次是它们的性能和可扩展性。ActiveMQ具有较好的水平扩展性,可以构建集群,并且可以将消息持久化到磁盘中以进行高可靠性。但是它的性能往往不如RabbitMQ。RabbitMQ具有出色的消息处理性能,并能够处理大量的并发请求。它使用预取机制来提高性能,这意味着RabbitMQ将尽可能提供更多消息,使客户机可以更快地处理它们。 再次是它们的功能和应用场景。ActiveMQ提供了许多高级功能,如消息过滤、消息路由、消息事务等,非常适合大规模企业级应用程序。RabbitMQ适用于需要高吞吐量的场景,如物联网、游戏、金融等领域。RabbitMQ还提供了一些高级功能,如发布/订阅、主题路由等。 总的来说,ActiveMQ和RabbitMQ都有各自的优点和缺点,开发者应该根据自己的特定需求选择适合的中间件。如果需要建立高度可靠的企业应用程序,应选择ActiveMQ。如果需要高可扩展性和高吞吐量的大规模应用程序,则应使用RabbitMQ。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值