雲端聊天:第一部分

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

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

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

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

現在,我將介紹另一個演示簡單聊天伺服器的示例應用程式。RabbitMQ 為多功能聊天應用程式提供了一個極好的骨幹,因為它支援不同型別的交換機,例如“direct”/點對點、“topic”基於釋出/訂閱和“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 程式碼

如果您克隆倉庫並進入“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 對映 (“/”)。

如您所見,只有一個名為“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 值連線成一個字串,然後將其轉換為 AMQP 訊息,並透過 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”元素使帶 @Contoller 註解的類註冊為 Spring 管理的物件,並激活 @Autowired 支援。兩個帶“mvc”字首的元素只是設定 MVC @RequestMapping 支援並啟用靜態資源的載入(在這種情況下用於“resources/static/js”目錄中提供的 jQuery 支援)。

其餘元素與 RabbitMQ 配置相關。“rabbit:admin”生成一個 RabbitAdmin 例項,該例項負責識別在同一應用程式上下文中定義的交換機、佇列和繫結。請注意,佇列元素將其 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 例項,並且方法名稱可以是任何我們想要的名稱,我們稱之為“訊息驅動的 POJO”。換句話說,它對訊息 API 沒有直接依賴。它被監聽器容器呼叫是一種控制反轉的形式。

最後,是連線工廠配置。在這種情況下,我們使用“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 支援(可從儀表板的“Extensions”選項卡獲得),然後建立一個新的伺服器例項(有關所有詳細資訊,請參閱 入門指南),然後您可以簡單地將應用程式拖到該例項上。將應用程式新增到伺服器例項後,您可以透過 UI 配置和繫結服務。以下螢幕截圖顯示了“rabbit-chat”示例應用程式。

擴充套件應用程式

還記得關於“chatQueue”id 的討論以及由於使用了 id 而不是 name 屬性而生成的佇列名稱嗎?嗯,我們在這裡使用 id 而不是 name 的原因是,我們希望應用程式的每個例項都有自己獨有的、實際上是匿名的佇列。扇出交換機只有一個命名例項。應用程式的每個例項都會將其自己的佇列繫結到該交換機。這種交換機和佇列的解耦對於可擴充套件的雲應用程式非常適用(特別是那些名稱是生成的佇列將在其擁有例項關閉時自動刪除)。

要擴充套件應用程式,您可以在命令列使用 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 社群所有即將舉行的活動。

檢視所有