云端畅聊:第一部分

工程 | Mark Fisher | 2011年8月16日 | ...

上周,RabbitMQ 作为 Cloud Foundry 上的服务正式上线的消息被宣布。现在,任何运行在 Cloud Foundry 上的应用程序都可以通过 RabbitMQ 代理发送和接收消息,该代理可以通过单个命令(例如 'vmc create-service rabbitmq')进行配置。消息服务的实例可以在应用程序之间共享,而且由于 RabbitMQ 是一个基于协议的代理,这些应用程序甚至可以用不同的语言编写。因此,对于那些对在云中运行的模块化、多语言、事件驱动的应用程序感兴趣的人来说,这是一个令人兴奋的消息。我将发布一系列关注这类应用程序的博客文章。在本文中,我将保持简单,重点关注 Spring 开发人员的初始体验。

首先,我建议您查看本教程,这是即使您以前没有 Cloud Foundry 经验的最佳入门方法。在那里,您将看到如何使用 Maven 构建一个简单的 Spring 应用程序,并使用 VMC 命令行工具将其部署到 Cloud Foundry。然后,该应用程序引入了 RabbitMQ,并增强了其 MVC 控制器以发布和检索消息。它展示了如何通过 Spring AMQP 库配置和使用 RabbitMQ 服务。

此外,在 Cloud Foundry 正式发布的当天(与这些博客 文章同一天),我发布了另一篇博客,其中涵盖了“cloud”命名空间支持的基础知识。阅读这篇文章也可能有助于您了解接下来将要看到的内容。具体来说,我们扩展了“cloud”命名空间以包含对 RabbitMQ ConnectionFactory 的支持,这将在下面的配置概述中介绍。

现在,我想介绍另一个演示简单聊天服务器的示例应用程序。RabbitMQ 为多功能聊天应用程序提供了强大的支持,因为它支持不同类型的交换机,例如“direct”/点对点、“topic” based 发布/订阅和用于简单广播的“fanout”。此外,RabbitMQ 还支持多种语言绑定。再加上在云中启用消息传递基本上只需轻轻一按开关,现在许多不同的应用程序可以轻松共享该服务。如上所述,我将逐步增强示例,并在以后发布更多博客文章,以涵盖这些交换类型和一些多语言聊天,但目前我的目标是通过扇出交换机进行全局广播,提供一个易于访问的起点。应用程序的当前状态并不比教程中介绍的复杂多少。我将逐步介绍一些配置和代码,但如果您想跟随并深入了解更多细节,我建议您从 github 上的 SpringSource cloudfoundry-samples 存储库克隆示例。

“rabbit-chat”示例应用程序

以下是运行中的应用程序的样子

该表单将使用 jQuery 提交 HTTP POST 请求


$('#chatForm').submit(
	function() {
		$.post(
			$('#chatForm').attr("action"),
			$('#chatForm').serialize(),
			function(response) {
				if (response) {
					confirm(response.id);
				}
			});
		$('#text').val("");
		return false;
	});

并且,聊天记录将通过轮询定期更新——同样使用 jQuery 的 AJAX 支持


$.ajax({
	url : "chatlog",
	success : function(message) {
		if (message && message.length) {
			var messagesDiv = $('#messages');
			messagesDiv.html(message);
			messagesDiv.animate({ scrollTop: messagesDiv.attr("scrollHeight") - messagesDiv.height() }, 150);
		}
		timer = poll();
	},
	error : function() {
		timer = poll();
	},
	cache : false
});

Java 代码

如果您克隆存储库并 cd 到 'rabbit-chat' 目录,您将看到以下结构

├── pom.xml
├── src
│   └── main
│       ├── java
│       │   └── org
│       │       └── cloudfoundry
│       │           └── samples
│       │               └── rabbitmq
│       │                   └── chat
│       │                       └── ChatController.java
│       ├── resources
│       │   └── static
│       │       └── js
│       │           └── jquery.min.js
│       └── webapp
│           └── WEB-INF
│               ├── spring
│               │   └── servlet-context.xml
│               ├── views
│               │   └── chat.jsp
│               └── web.xml

pom.xml 文件声明了依赖项。特别需要注意以下几点

  • spring-webmvc (3.0.5.RELEASE)
  • spring-rabbit (1.0.0.RC3)
  • cloudfoundry-runtime (0.7.1)

web.xml 文件声明了一个 Spring MVC DispatcherServlet 和一个捕获所有请求的 servlet-mapping ("/")。

如您所见,只有一个名为“ChatController”的控制器。它是通过注解配置的。它使用了 @Controller 和 @RequestMapping 注解以及 @Autowired。由于 ChatController 确实是应用程序的核心(也是它唯一的 Java 代码),让我们快速浏览一下整个实现


@Controller
public class ChatController {

	@Autowired
	private volatile AmqpTemplate amqpTemplate;

	private final Queue<String> messages = new LinkedBlockingQueue<String>();

	@RequestMapping(value = "/")
	public String home() {
		return "WEB-INF/views/chat.jsp";
	}

	@RequestMapping(value = "/publish", method = RequestMethod.POST)
	@ResponseStatus(value = HttpStatus.OK)
	public void publish(@RequestParam String username, @RequestParam String text) {
		this.amqpTemplate.convertAndSend(username + ": " + text);
	}

	@RequestMapping(value = "/chatlog")
	@ResponseBody
	public String chatlog() {
		return StringUtils.arrayToDelimitedString(this.messages.toArray(), "<br/>");
	}

	/**
	 * This method is invoked when a RabbitMQ Message is received.
	 */
	public void handleMessage(String message) {
		if (messages.size() > 100) {
			messages.remove();
		}
		messages.add(message);
	}
}

有 3 个控制器方法(用 @RequestMapping 注解的方法),每个方法都只有一行代码。其中最简单的是 home(),它返回要渲染的 JSP 的位置。我通常会使用 Spring MVC ViewResolver,但由于它只是一个使用 AJAX 的单页应用程序,因此这是唯一直接渲染的视图。如您所见,每当您请求应用程序根目录时,都会调用 home()

对相对 URL“/publish”的 HTTP POST 请求将调用 publish(..) 方法,并且它需要请求中的两个参数:username 和 text。这些参数由您在上一节中看到的 HTML 表单提供。它由 chat.jsp 渲染。publish 方法只不过是将 username + text 值连接成一个字符串,将其转换为 AMQP Message 并由 AmqpTemplate 发送,之后它会以简单的 HTTP 200 (OK) 状态进行响应。模板实例已自动装配到控制器中。我们很快将介绍 AmqpTemplate 配置以及支持在 Cloud Foundry 上使用 RabbitMQ 服务的底层 ConnectionFactory

chatlog() 方法只返回最多 100 条最新的聊天消息。它是上一节中显示的 AJAX 请求轮询的方法。handleMessage(..) 方法负责将这些聊天消息排队,因此它连接到底层消息侦听器。这几乎涵盖了应用程序的功能。

Spring 配置

现在,我们可以逐步介绍此应用程序的配置。这完全可以使用 Java 和注解完成,但希望您同意这是一个相当简洁的配置文件


<context:component-scan base-package="org.cloudfoundry.samples.rabbitmq.chat"/>

<mvc:annotation-driven/>

<mvc:resources location="file:./src/main/resources/static/,classpath:/static/" mapping="static/**"/>

<rabbit:queue id="chatQueue"/>

<rabbit:fanout-exchange name="chatExchange">
	<rabbit:bindings>
		<rabbit:binding queue="chatQueue"/>
	</rabbit:bindings>
</rabbit:fanout-exchange>

<rabbit:template connection-factory="rabbitConnectionFactory" exchange="chatExchange"/>

<rabbit:admin connection-factory="rabbitConnectionFactory"/>

<rabbit:listener-container>
	<rabbit:listener queues="chatQueue" ref="chatController" method="handleMessage"/>
</rabbit:listener-container>

<cloud:rabbit-connection-factory id="rabbitConnectionFactory"/>

“component-scan”元素使带有 @Controller 注解的类能够注册为 Spring 管理的对象,并且它还激活了对 @Autowired 的支持。带有“mvc”前缀的两个元素只是设置 MVC @RequestMapping 支持并启用静态资源的加载(在本例中用于“resources/static/js”目录中提供的 jQuery 支持)。

其余元素与 RabbitMQ 配置相关。“rabbit:admin”生成一个 RabbitAdmin 实例,负责识别在同一应用程序上下文中定义的交换机、队列和绑定。请注意,queue 元素的 **id** 为“chatQueue”,但该 id 没有“name”属性。这将触发创建一个具有唯一生成名称的队列,该名称为此特定应用程序专用。换句话说,“id”属性的值并不映射到队列的名称;它是 Spring bean 的 id,而不是 RabbitMQ 队列的 id。即使它具有生成的名称,也需要能够在此应用程序上下文中引用它。例如,您可以看到它在“chatExchange”的绑定中被引用,这里将其定义为扇出交换机。由于存在“rabbit:admin”元素,该交换机也将针对代理声明。

“rabbit:template”非常简单。它需要引用 ConnectionFactory(别担心,我们稍后会介绍它),如果您希望它的“send”方法发布到除无名默认交换机之外的交换机,您可以在这里提供它。我们正在发布到我们刚刚讨论的“chatExchange”。

“rabbit:listener-container”实际上与 Spring JMS 支持中的同名元素相同。这个正在侦听“chatQueue”,请记住这只是对 bean id 的引用,该特定队列的真实名称是由代理生成的。每当队列中有消息到达时,就会调用我们之前看到的“handleMessage”方法。在这种情况下,如果方法参数是 String,侦听器容器的适配器将自动处理 Message 正文的转换。由于该方法参数不需要接受实际的 Message 实例,并且方法名称可以是任何我们想要的名称,因此我们将此称为“消息驱动的 POJO”。换句话说,它不直接依赖于消息传递 API。侦听器容器调用它是一种控制反转的形式。

最后,是 Connection Factory 配置。在本例中,我们使用“cloud”命名空间及其“rabbit-connection-factory”元素。只要您的应用程序绑定到 Cloud Foundry 中的单个“rabbitmq”服务,就不需要其他信息来创建 ConnectionFactory 实例。该命名空间支持的底层代码将从环境本身确定凭据。该命名空间支持由“cloudfoundry-runtime”库提供,您可以在此应用程序的 pom.xml 文件中看到它的声明。

运行应用程序

您可以使用 vmc 命令行工具SpringSource Tool Suite 运行该应用程序。使用 *vmc*,您将拥有如下内容

$ vmc push
Would you like to deploy from the current directory? [Yn]: y
Application Name: rabbit-chat-sample
Application Deployed URL: 'rabbit-chat-sample.cloudfoundry.com'? 
Detected a Java SpringSource Spring Application, is this correct? [Yn]: y
Memory Reservation [Default:512M](64M, 128M, 256M, 512M or 1G)   
Creating Application: OK
Would you like to bind any services to 'rabbit-chat-sample'? [yN]: y
Would you like to use an existing provisioned service [yN]? n
The following system services are available:
1. mongodb
2. mysql
3. postgresql
4. rabbitmq
5. redis
Please select one you wish to provision: 4
Specify the name of the service [rabbitmq-5e262]:          
Creating Service: OK
Binding Service: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (3K): OK   
Push Status: OK
Staging Application: OK                                                         
Starting Application: OK

如果使用 STS,您只需启用 Cloud Foundry 支持(可从仪表板的“扩展”选项卡获得),然后创建一个新的服务器实例(有关所有详细信息,请参阅入门指南),您可以简单地将应用程序拖到该实例。将应用程序添加到服务器实例后,您可以通过 UI 配置和绑定服务。以下屏幕截图显示了“rabbit-chat”示例应用程序。

扩展应用程序

还记得关于“chatQueue”id 的讨论以及如何生成队列名称,因为它使用的是 id 而不是 name 属性吗?好吧,我们在这里使用 id 而不是 name 的原因是我们希望应用程序的每个实例都有自己的专用队列,并且实际上是匿名的。只有一个名为 Fanout Exchange 的实例。应用程序的每个实例都将其自己的队列绑定到该交换机。这种交换机和队列的解耦非常适合可扩展的云应用程序(尤其是在关闭拥有实例时,名称生成的队列将自动删除)。

要扩展应用程序,您可以在命令行使用 VMC

$ vmc instances rabbit-chat-sample +1
Scaling Application instances up to 2: OK

或者,您可以使用 STS 支持。以下是上面发布的同一屏幕截图中“实例”配置的重点视图。

下一步是什么?

本博客旨在作为系列文章的第一篇。在接下来的文章中,我们将探讨以下内容:

  • 增强消息传递功能,使其超越当前的全局广播,以演示点对点消息传递(用于与单个指定用户聊天)以及使用动态分配的交换器来表示“聊天室”的发布/订阅。
  • 添加一个与 Java 应用程序一起运行并参与同一聊天的 Node.js 应用程序,因为这两个应用程序都绑定到同一个 RabbitMQ 服务实例。
  • 包含 Spring 3.1 配置文件支持,以展示如何修改此配置文件使其同样适用于 Cloud Foundry 或其他部署,例如本地 Tomcat 实例。

获取 Spring 简报

保持关注 Spring 简报

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅即可提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部