雲中聊天:第 1 部分

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

上週,RabbitMQ 作為服務在 Cloud Foundry 上可用已宣佈。現在,任何在 Cloud Foundry 上執行的應用程式都可以透過 RabbitMQ 代理傳送和接收訊息,該代理可以僅用一個命令(例如 'vmc create-service rabbitmq')作為服務進行配置。訊息傳遞服務的例項可以在應用程式之間共享,並且由於 RabbitMQ 是一個基於協議的代理,這些應用程式甚至可以使用不同的語言編寫。因此,對於那些對在雲中執行模組化、多語言、事件驅動的應用程式感興趣的人來說,這是一個令人興奮的訊息。我將釋出一系列部落格,重點介紹這些型別的應用程式。在這篇文章中,我將保持簡單,重點介紹 Spring 開發人員的初步體驗。

首先,我鼓勵您檢視此教程,這是即使您之前沒有 Cloud Foundry 經驗的最佳入門方式。在那裡,您將看到一個簡單的 Spring 應用程式如何使用 Maven 構建,並使用 VMC 命令列工具部署到 Cloud Foundry。該應用程式隨後引入了 RabbitMQ,其 MVC 控制器得到增強以釋出和檢索訊息。它展示瞭如何透過 Spring AMQP 庫配置和使用 RabbitMQ 服務。

此外,在 Cloud Foundry 最初宣佈的當天(與這些部落格文章同一天),我發表了另一篇部落格,介紹了“cloud”名稱空間支援的基礎知識。閱讀該部落格也可能有助於為您即將看到的內容做好準備。具體來說,我們擴充套件了“cloud”名稱空間,以包含對 RabbitMQ ConnectionFactory 的支援,這一點將在我們下面的配置概述中介紹。

現在,我想介紹另一個示例應用程式,它演示了一個簡單的聊天伺服器。RabbitMQ 為多功能聊天應用程式提供了極好的骨幹,因為它支援不同型別的交換器,例如“direct”/點對點、“topic”主題釋出/訂閱以及用於簡單廣播的“fanout”。此外,RabbitMQ 支援多種語言繫結。加上在雲中啟用訊息傳遞基本上就像撥動開關一樣簡單,現在許多不同的應用程式都可以輕鬆共享該服務。如上所述,我將逐步增強該示例,並稍後釋出更多部落格文章來介紹這些交換器型別和一些多語言聊天,但目前我的目標是提供一個易於訪問的起點,僅透過 fanout 交換器進行全域性廣播。該應用程式的當前狀態並不比教程中介紹的複雜多少。我將逐步講解一些配置和程式碼,但如果您想跟隨並深入瞭解更多細節,我建議從 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 程式碼

如果您克隆倉庫並切換到 '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()

publish(..) 方法用於處理對相對 URL "/publish" 的 HTTP POST 請求,並且它期望請求中有兩個引數:username 和 text。這些引數由您在上一節中看到的 HTML 表單提供。該表單由 chat.jsp 渲染。publish 方法除了將 username + text 值連線成一個字串,然後由 AmqpTemplate 將其轉換為 AMQP 訊息併發送之外,不執行任何其他操作,之後它返回一個簡單的 HTTP 200 (OK) 狀態。模板例項已自動裝配到 Controller 中。我們很快將檢視 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 例項,負責識別在同一應用程式上下文中定義的 Exchanges、Queues 和 Bindings。請注意,queue 元素將其 id 設定為 "chatQueue",但該 id 沒有 "name" 屬性。這將觸發建立一個具有唯一、生成的名稱的 Queue,該 Queue 專屬於此特定應用程式。換句話說,“id”屬性的值不對映到 Queue 的名稱;它是 Spring bean 的 id,而不是 RabbitMQ Queue 的 id。儘管它具有生成的名稱,但它需要在此應用程式上下文中可識別以供引用。例如,您可以看到它在在此定義為 Fanout Exchange 的 "chatExchange" 的繫結中被引用。該交換器也將向 broker 宣告,因為存在 "rabbit:admin" 元素。

“rabbit:template”相當簡單。它需要引用 ConnectionFactory(別擔心,我們稍後會講到),如果您希望其“send”方法釋出到除了無名的預設 Exchange 之外的 Exchange,您可以在此處提供。我們正在釋出到剛剛討論過的 "chatExchange"。

“rabbit:listener-container”與 Spring 的 JMS 支援中同名元素幾乎完全相同。這個正在監聽 "chatQueue",請記住這只是對 bean id 的引用,該特定 Queue 的真實名稱由 broker 生成。每當訊息到達該 Queue 時,我們之前看到的 "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 支援(可在 Dashboard 的“Extensions”選項卡中找到),然後建立一個新的伺服器例項(請參閱 入門指南瞭解所有詳細資訊),然後您可以簡單地將應用程式拖到其中。一旦應用程式新增到伺服器例項中,您就可以透過 UI 配置和繫結服務。以下截圖顯示了 'rabbit-chat' 示例應用程式。

伸縮應用程式

還記得關於 "chatQueue" id 的討論以及 Queue 的名稱是如何生成的嗎,因為它使用了 id 而不是 name 屬性?嗯,我們在這裡使用 id 而不是 name 的原因是我們希望應用程式的每個例項都有自己的專屬且實際上是匿名的 Queue。Fanout Exchange 有一個單一的命名例項。應用程式的每個例項都會將其自己的 Queue 繫結到該 Exchange。這種 Exchanges 和 Queues 的解耦非常適合可伸縮的雲應用程式(特別是那些名稱是生成的 Queue 在其擁有例項關閉時會自動刪除)。

要伸縮應用程式,您可以在命令列使用 VMC

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

或者,您可以使用 STS 支援。這是從上面同一截圖擷取的“例項”配置的聚焦檢視

下一步是什麼?

本部落格旨在成為系列文章的第一篇。在接下來的文章中,我們將探討以下內容

  • 增強訊息傳遞功能,超越當前的全域性廣播,以演示點對點訊息傳遞(與單個命名使用者聊天)以及使用動態分配的 Exchanges 來表示“聊天室”的釋出/訂閱。
  • 新增一個 Node.js 應用程式,與 Java 應用程式並行執行並參與同一聊天,因為兩個應用程式都繫結到同一個 RabbitMQ 服務例項。
  • 包含 Spring 3.1 profile 支援,以展示如何修改此同一配置檔案,使其在 Cloud Foundry 或替代部署(例如本地 Tomcat 例項)上同樣良好執行。

訂閱 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一次簡單訂閱。

瞭解更多

即將到來的活動

檢視 Spring 社群所有即將到來的活動。

檢視全部