0 準備
- 安裝註冊中心:Zookeeper、Dubbox自帶的dubbo-registry-simple;
- 安裝DubboKeeper監控:https://github.com/dubboclub/dubbokeeper;
以上兩點準備,不是本文重點,不做詳細介紹,安裝比較簡單,自行查閱相關資料安裝學習。
1 服務端
1.2 介面定義
-
建立Maven模組:msa-demo-api
-
msa-demo-api:配置pom.xml
<!-- Dubbox依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.4</version> </dependency> <!-- END --> <!-- 如果要使用lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- END --> <!-- 如果要使用REST風格遠端呼叫 --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.0.7.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>3.0.7.Final</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <!-- END --> <!-- 如果要使用json序列化 --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson-provider</artifactId> <version>3.0.7.Final</version> </dependency> <!-- END --> <!-- 如果要使用xml序列化 --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> <version>3.0.7.Final</version> </dependency> <!-- END --> <!-- 如果要使用netty server --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-netty</artifactId> <version>3.0.7.Final</version> </dependency> <!-- END --> <!-- 如果要使用Sun HTTP server --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jdk-http</artifactId> <version>3.0.7.Final</version> </dependency> <!-- END --> <!-- 如果要使用tomcat server --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.0.11</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-logging-juli</artifactId> <version>8.0.11</version> </dependency> <!-- END --> <!-- 如果要使用Kyro序列化 --> <dependency> <groupId>com.esotericsoftware.kryo</groupId> <artifactId>kryo</artifactId> <version>2.24.0</version> </dependency> <dependency> <groupId>de.javakaffee</groupId> <artifactId>kryo-serializers</artifactId> <version>0.26</version> </dependency> <!-- END --> <!-- 如果要使用FST序列化 --> <dependency> <groupId>de.ruedigermoeller</groupId> <artifactId>fst</artifactId> <version>1.55</version> </dependency> <!-- END --> <!-- 如果要使用Jackson序列化 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.3.3</version> </dependency> <!-- END --> 複製程式碼
以上POM配置,從dubbox-2.8.4開始,所有依賴庫的使用方式將和dubbo原來的一樣:即如果要使用REST、Kyro、FST、Jackson等功能,需要使用者自行手工新增相關的依賴。
-
定義介面:UserService.java
/** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ public interface UserService { User getUser(Long id); Long registerUser(User user); } 複製程式碼
-
定義REST介面:AnotherUserRestService.java
package com.alibaba.dubbo.demo.user.facade; import com.alibaba.dubbo.demo.user.User; import com.alibaba.dubbo.rpc.protocol.rest.support.ContentType; import javax.validation.constraints.Min; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; /** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ // 在Dubbo中開發REST服務主要都是通過JAX-RS的annotation來完成配置的, // 在上面的示例中,我們都是將annotation放在服務的實現類中。但其實,我 // 們完全也可以將annotation放到服務的介面上,這兩種方式是完全等價的. // // 在一般應用中,我們建議將annotation放到服務實現類,這樣annotation和 // java實現程式碼位置更接近,更便於開發和維護。另外更重要的是,我們一般傾向 // 於避免對介面的汙染,保持介面的純淨性和廣泛適用性。 // 但是,如後文所述,如果我們要用dubbo直接開發的消費端來訪問此服務,則annotation必須放到介面上。 // 如果介面和實現類都同時新增了annotation,則實現類的annotation配置會生效,介面上的annotation被直接忽略。 @Path("u") @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8}) public interface AnotherUserRestService { @GET @Path("{id : \\d+}") // 在一個REST服務同時對多種資料格式支援的情況下,根據JAX-RS標準, // 一般是通過HTTP中的MIME header(content-type和accept)來指定當前想用的是哪種格式的資料。 // @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8}) // 但是在dubbo中,我們還自動支援目前業界普遍使用的方式,即用一個URL字尾(.json和.xml)來指定 // 想用的資料格式。例如,在新增上述annotation後,直接訪問http://localhost:8888/users/1001.json // 則表示用json格式,直接訪問http://localhost:8888/users/1002.xml則表示用xml格式, // 比用HTTP Header更簡單直觀。Twitter、微博等的REST API都是採用這種方式。 // 如果你既不加HTTP header,也不加字尾,則dubbo的REST會優先啟用在以上annotation定義中排位最靠前的那種資料格式。 // 注意:這裡要支援XML格式資料,在annotation中既可以用MediaType.TEXT_XML,也可以用MediaType.APPLICATION_XML, // 但是TEXT_XML是更常用的,並且如果要利用上述的URL字尾方式來指定資料格式,只能配置為TEXT_XML才能生效。 User getUser(@PathParam("id") @Min(1L) Long id); @POST @Path("register") RegistrationResult registerUser(User user); } 複製程式碼
-
定義實體:User.java
package com.alibaba.dubbo.demo.user; import lombok.Data; import org.codehaus.jackson.annotate.JsonProperty; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; /** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ // 由於JAX-RS的實現一般都用標準的JAXB(Java API for XML Binding)來序列化和反序列化XML格式資料, // 所以我們需要為每一個要用XML傳輸的物件新增一個類級別的JAXB annotation(@XmlRootElement) ,否則序列化將報錯。 @Data @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class User implements Serializable { @NotNull @Min(1L) private Long id; // REST的底層實現會在service的物件和JSON/XML資料格式之間自動做序列化/反序列化。 // 但有些場景下,如果覺得這種自動轉換不滿足要求,可以對其做定製。 // Dubbo中的REST實現是用JAXB做XML序列化,用Jackson做JSON序列化, // 所以在物件上新增JAXB或Jackson的annotation即可以定製對映。 @JsonProperty("username") @XmlElement(name = "username") @NotNull @Size(min = 6, max = 50) private String name; } 複製程式碼
-
定義REST響應結果實體:RegistrationResult.java
package com.alibaba.dubbo.demo.user.facade; import lombok.Data; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; /** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ // 此外,如果service方法中的返回值是Java的 primitive型別(如int,long,float,double等), // 最好為它們新增一層wrapper物件,因為JAXB不能直接序列化primitive型別。這樣不但能夠解決XML序列化的問題, // 而且使得返回的資料都符合XML和JSON的規範。 // 這種wrapper物件其實利用所謂Data Transfer Object(DTO)模式,採用DTO還能對傳輸資料做更多有用的定製。 @Data @XmlRootElement public class RegistrationResult implements Serializable { private Long id; } 複製程式碼
1.3 服務實現
-
建立Maven模組:msa-demo-provider
-
msa-demo-provider:配置pom.xml
<!-- Module依賴 START --> <dependency> <groupId>com.alibaba</groupId> <artifactId>msa-demo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Module依賴 END --> 複製程式碼
-
實現UserService介面:UserServiceImpl.java
package com.alibaba.dubbo.demo.user; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicLong; /** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ @Slf4j public class UserServiceImpl implements UserService { private final AtomicLong idGen = new AtomicLong(); public User getUser(Long id) { User user = new User(); user.setId(id); user.setName("username" + id); return user; } public Long registerUser(User user) { // System.out.println("Username is " + user.getName()); return idGen.incrementAndGet(); } } 複製程式碼
-
實現REST介面AnotherUserRestService:AnotherUserRestServiceImpl.java
package com.alibaba.dubbo.demo.user.facade; import com.alibaba.dubbo.demo.user.User; import com.alibaba.dubbo.demo.user.UserService; import com.alibaba.dubbo.rpc.RpcContext; import lombok.extern.slf4j.Slf4j; /** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ @Slf4j public class AnotherUserRestServiceImpl implements AnotherUserRestService { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } public User getUser(Long id) { System.out.println("Client name is " + RpcContext.getContext().getAttachment("clientName")); System.out.println("Client impl is " + RpcContext.getContext().getAttachment("clientImpl")); return userService.getUser(id); } public RegistrationResult registerUser(User user) { Long id = userService.registerUser(user); RegistrationResult registrationResult = new RegistrationResult(); registrationResult.setId(id); return registrationResult; } } 複製程式碼
-
Dubbox與Spring整合配置:msa-demo-provider.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 當前應用資訊配置 --> <dubbo:application name="msa-demo-provider" owner="tbr" organization="tbr"/> <dubbo:monitor address="x.x.x.x:20884"/> <!-- 多註冊中心配置,豎號分隔表示同時連線多個不同註冊中心,同一註冊中心的多個叢集地址用逗號分隔 --> <dubbo:registry protocol="zookeeper" address="x.x.x.x:2181,x.x.x.x:2181,x.x.x.x:2181"/> <dubbo:protocol name="dubbo" port="20880" serialization="kryo"/> <!-- 1. 選用了嵌入式的jetty來做rest server,同時,如果不配置server屬性,rest協議預設也是選用jetty。 jetty是非常成熟的java servlet容器,並和dubbo已經有較好的整合(目前5種嵌入式server中只有jetty 和後面所述的tomcat、tjws,與dubbo監控系統等完成了無縫的整合),所以,如果你的dubbo系統是單獨啟動的程式, 你可以直接預設採用jetty即可。dubbo中的rest協議預設將採用80埠. <dubbo:protocol name="rest" server="jetty"/> 2. 配置選用了嵌入式的tomcat來做rest server。在嵌入式tomcat上,REST的效能比jetty上要好得多(參見後面的基準測試), 建議在需要高效能的場景下采用tomcat。 <dubbo:protocol name="rest" server="tomcat"/> 3. 配置選用嵌入式的netty來做rest server。 <dubbo:protocol name="rest" server="netty"/> 4. 配置選用嵌入式的tjws或Sun HTTP server來做rest server。這兩個server實現非常輕量級, 非常方便在整合測試中快速啟動使用,當然也可以在負荷不高的生產環境中使用。 注:tjws目前已經 被deprecated掉了,因為它不能很好的和servlet 3.1 API工作。 <dubbo:protocol name="rest" server="tjws"/> <dubbo:protocol name="rest" server="sunhttp"/> 5. 如果你的dubbo系統不是單獨啟動的程式,而是部署到了Java應用伺服器中,則建議你採用以下配置: <dubbo:protocol name="rest" server="servlet"/> 6. 通過將server設定為servlet,dubbo將採用外部應用伺服器的servlet容器來做rest server。同時,還要在dubbo系統的web.xml中新增如下配置: <web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/META-INF/spring/dubbo-demo-provider.xml</param-value> </context-param> <listener> <listener-class>com.alibaba.dubbo.remoting.http.servlet.BootstrapListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> 即必須將dubbo的BootstrapListener和DispatherServlet新增到web.xml,以完成dubbo的REST功能與外部servlet容器的整合。 其實,這種場景下你依然可以堅持用嵌入式server,但外部應用伺服器的servlet容器往往比嵌入式server更加強大 (特別是如果你是部署到更健壯更可伸縮的WebLogic,WebSphere等),另外有時也便於在應用伺服器做統一管理、監控等等。 如果將dubbo REST部署到外部Tomcat上,並配置server="servlet",即啟用外部的tomcat來做為rest server的底層實現, 則最好在tomcat上新增如下配置: <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443" minSpareThreads="20" enableLookups="false" maxThreads="100" maxKeepAliveRequests="-1" keepAliveTimeout="60000"/> 特別是maxKeepAliveRequests="-1",這個配置主要是保證tomcat一直啟用http長連線,以提高REST呼叫效能。 但是請注意,如果REST消費端不是持續的呼叫REST服務,則一直啟用長連線未必是最好的做法。另外,一直啟用長連 接的方式一般不適合針對普通webapp,更適合這種類似rpc的場景。所以為了高效能,在tomcat中,dubbo REST應 用和普通web應用最好不要混合部署,而應該用單獨的例項。 7. 注意:如果你是用spring的ContextLoaderListener來載入spring, 則必須保證BootstrapListener配置在ContextLoaderListener之前,否則dubbo初始化會出錯。 --> <!-- 1. 設定一個所有rest服務都適用的基礎相對路徑,即java web應用中常說的context path。只需要新增如下contextpath屬性即可. <dubbo:protocol name="rest" port="8888" keepalive="true" server="netty" iothreads="5" threads="100" contextpath="services"/> 2. 可以為rest服務配置執行緒池大小: <dubbo:protocol name="rest" threads="500"/> 注意:目前執行緒池的設定只有當server="netty"或者server="jetty"或者server="tomcat"的時候才能生效。另外,如果server="servlet",由於這時候啟用 的是外部應用伺服器做rest server,不受dubbo控制,所以這裡的執行緒池設定也無效。 如果是選用netty server,還可以配置Netty的IO worker執行緒數: <dubbo:protocol name="rest" iothreads="5" threads="100"/> 3. 注意:如果你是選用外部應用伺服器做rest server, 即配置: <dubbo:protocol name="rest" port="8888" contextpath="services" server="servlet"/> 則必須保證這裡設定的port、contextpath,與外部應用伺服器的埠、DispatcherServlet的上下文路徑(即webapp path加上servlet url pattern)保持一致。 4. Dubbo中的rest服務預設都是採用http長連線來訪問,如果想切換為短連線,直接配置: <dubbo:protocol name="rest" keepalive="false"/> 注意:這個配置目前只對server="netty"和server="tomcat"才能生效。 5. 配置伺服器提供端所能同時接收的最大HTTP連線數,防止REST server被過多連線撐爆,以作為一種最基本的自我保護機制: <dubbo:protocol name="rest" accepts="500" server="tomcat/> 注意:這個配置目前只對server="tomcat"才能生效。 6. 如果rest服務的消費端也是dubbo系統,可以像其他dubbo RPC機制一樣,配置消費端呼叫此rest服務的最大超時時間以及每個消費端所能啟動的最大HTTP連線數。 <dubbo:service interface="xxx" ref="xxx" protocol="rest" timeout="2000" connections="10"/> 當然,由於這個配置針對消費端生效的,所以也可以在消費端配置: <dubbo:reference id="xxx" interface="xxx" timeout="2000" connections="10"/> 但是,通常我們建議配置在服務提供端提供此類配置。按照dubbo官方文件的說法:“Provider上儘量多配置Consumer端的屬性,讓Provider實現者一開始就思考Provider服務特點、服務質量的問題。” 注意:如果dubbo的REST服務是釋出給非dubbo的客戶端使用,則這裡<dubbo:service/>上的配置完全無效,因為這種客戶端不受dubbo控制。 7. Dubbo的REST支援用GZIP壓縮請求和響應的資料,以減少網路傳輸時間和頻寬佔用,但這種方式會也增加CPU開銷。 --> <!-- 1. use tomcat server 2. 用rest協議在8888埠暴露服務 3. Dubbo的REST也支援JAX-RS標準的Filter和Interceptor,以方便對REST的請求與響應過程做定製化的攔截處理。 其中,Filter主要用於訪問和設定HTTP請求和響應的引數、URI等等。如:CacheControlFilter.java Interceptor主要用於訪問和修改輸入與輸出位元組流,例如,手動新增GZIP壓縮.如:GZIPWriterInterceptor.java 4. 在標準JAX-RS應用中,我們一般是為Filter和Interceptor新增@Provider annotation,然後JAX-RS runtime會 自動發現並啟用它們。而在dubbo中,我們是通過新增XML配置的方式來註冊Filter和Interceptor. 5. 在此,我們可以將Filter、Interceptor和DynamicFuture這三種型別的物件都新增到extension屬性上,多個之間用逗號分隔。(DynamicFuture是另一個介面,可以方便我們更動態的啟用Filter和Interceptor,感興趣請自行google。) 6. 當然,dubbo自身也支援Filter的概念,但我們這裡討論的Filter和Interceptor更加接近協議實現的底層, 相比dubbo的filter,可以做更底層的定製化。 注:這裡的XML屬性叫extension,而不是叫interceptor或者filter,是因為除了Interceptor和Filter,未來我們 還會新增更多的擴充套件型別。 7. 如果REST的消費端也是dubbo系統(參見下文的討論),則也可以用類似方式為消費端配置Interceptor和Filter。但注 意,JAX-RS中消費端的Filter和提供端的Filter是兩種不同的介面。例如前面例子中服務端是ContainerResponseFilter介面, 而消費端對應的是ClientResponseFilter. 8. Dubbo的REST也支援JAX-RS標準的ExceptionMapper,可以用來定製特定exception發生後應該返回的HTTP響應。 9. Dubbo rest支援輸出所有HTTP請求/響應中的header欄位和body訊息體。LoggingFilter --> <dubbo:protocol name="rest" port="8888" threads="500" contextpath="services" server="tomcat" accepts="500" extension="com.alibaba.dubbo.demo.extension.TraceInterceptor, com.alibaba.dubbo.demo.extension.TraceFilter, com.alibaba.dubbo.demo.extension.ClientTraceFilter, com.alibaba.dubbo.demo.extension.DynamicTraceBinding, com.alibaba.dubbo.demo.extension.CustomExceptionMapper, com.alibaba.dubbo.rpc.protocol.rest.support.LoggingFilter"/> <!-- use the external tomcat or other server with the servlet approach; the port and contextpath must be exactly the same as those in external server <dubbo:protocol name="rest" port="8888" contextpath="services" server="servlet"/> --> <dubbo:protocol name="http" port="8889"/> <dubbo:protocol name="hessian" port="8890"/> <dubbo:protocol name="webservice" port="8892"/> <!-- 宣告需要暴露的服務介面 --> <dubbo:service interface="com.alibaba.dubbo.demo.user.UserService" ref="userService" protocol="dubbo" group="xmlConfig"/> <!-- 1. 為了和其他dubbo遠端呼叫協議保持一致,在rest中作校驗的annotation必須放在服務的介面上, 把annotation放在介面上至少有一個好處是,dubbo的客戶端可以共享這個介面的資訊,dubbo甚 至不需要做遠端呼叫,在本地就可以完成輸入校驗。 然後按照dubbo的標準方式在XML配置中開啟驗證: <dubbo:service interface=xxx.UserService" ref="userService" protocol="rest" validation="true"/> 2. 在dubbo的其他很多遠端呼叫協議中,如果輸入驗證出錯,是直接將RpcException拋向客戶端,而在rest中由於客戶端經常是非dubbo,甚至非java的系統,所以不便直接丟擲Java異常。因此,目前我們將校驗錯誤以XML的格式返回: <violationReport> <constraintViolations> <path>getUserArgument0</path> <message>User ID must be greater than 1</message> <value>0</value> </constraintViolations> </violationReport> 如果你認為預設的校驗錯誤返回格式不符合你的要求,可以如上面章節所述,新增自定義的ExceptionMapper來自由的定製錯誤返回格式。 需要注意的是,這個ExceptionMapper必須用泛型宣告來捕獲dubbo的RpcException,才能成功覆蓋dubbo rest預設的異常處理策略。 為了簡化操作,其實這裡最簡單的方式是直接繼承dubbo rest的RpcExceptionMapper,並覆蓋其中處理校驗異常的方法即可. --> <dubbo:service interface="com.alibaba.dubbo.demo.user.facade.AnotherUserRestService" ref="anotherUserRestService" protocol="rest" timeout="2000" connections="100" validation="true"/> <bean id="userService" class="com.alibaba.dubbo.demo.user.UserServiceImpl"/> <bean id="anotherUserRestService" class="com.alibaba.dubbo.demo.user.facade.AnotherUserRestServiceImpl"> <property name="userService" ref="userService"/> </bean> <!-- 對於jax-rs和spring mvc,其實我對spring mvc的rest支援還沒有太深入的看過,說點初步想法,請大家指正: spring mvc也支援annotation的配置,其實和jax-rs看起來是非常非常類似的。 我個人認為spring mvc相對更適合於面向web應用的restful服務,比如被AJAX呼叫,也可能輸出HTML之類的,應用中還 有頁面跳轉流程之類,spring mvc既可以做好正常的web頁面請求也可以同時處理rest請求。但總的來說這個restful服務 是在展現層或者叫web層之類實現的 而jax-rs相對更適合純粹的服務化應用,也就是傳統Java EE中所說的中間層服務,比如它可以把傳統的EJB釋出成restful 服務。在spring應用中,也就把spring中充當service之類的bean直接釋出成restful服務。總的來說這個restful服務是 在業務、應用層或者facade層。而MVC層次和概念在這種做比如(後臺)服務化的應用中通常是沒有多大價值的。 當然jax-rs的有些實現比如jersey,也試圖提供mvc支援,以更好的適應上面所說的web應用,但應該是不如spring mvc。 在dubbo應用中,我想很多人都比較喜歡直接將一個本地的spring service bean(或者叫manager之類的)完全透明的釋出 成遠端服務,則這裡用JAX-RS是更自然更直接的,不必額外的引入MVC概念。當然,先不討論透明發布遠端服務是不是最佳實踐, 要不要新增facade之類。 當然,我知道在dubbo不支援rest的情況下,很多朋友採用的架構是spring mvc restful呼叫dubbo (spring) service 來發布restful服務的。這種方式我覺得也非常好,只是如果不修改spring mvc並將其與dubbo深度整合,restful服務不能 像dubbo中的其他遠端呼叫協議比如webservices、dubbo rpc、hessian等等那樣,享受諸多高階的服務治理的功能,比如: 註冊到dubbo的服務註冊中心,通過dubbo監控中心監控其呼叫次數、TPS、響應時間之類,通過dubbo的統一的配置方式控制其 比如執行緒池大小、最大連線數等等,通過dubbo統一方式做服務流量控制、許可權控制、頻次控制。另外spring mvc僅僅負責服務 端,而在消費端,通常是用spring restTemplate,如果restTemplate不和dubbo整合,有可能像dubbo服務客戶端那樣自動 或者人工干預做服務降級。如果服務端消費端都是dubbo系統,通過spring的rest互動,如果spring rest不深度整合dubbo, 則不能用dubbo統一的路由分流等功能。 當然,其實我個人認為這些東西不必要非此即彼的。我聽說spring創始人rod johnson總是愛說一句話, the customer is always right,其實與其非要探討哪種方式更好,不如同時支援兩種方式就是了, 所以原來在文件中也寫過計劃支援spring rest annoation,只是不知道具體可行性有多高。 1. JAX-RS中過載的方法能夠對映到同一URL地址嗎? http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params 2. JAX-RS中作POST的方法能夠接收多個引數嗎? http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects 注:以上備註,均來自:https://dangdangdotcom.github.io/dubbox/rest.html --> </beans> 複製程式碼
-
配置dubbo.properties
#dubbo.container=log4j,spring #dubbo.application.name=demo-provider #dubbo.application.owner= #dubbo.registry.address=multicast://224.5.6.7:1234 #dubbo.registry.address=zookeeper://127.0.0.1:2181 #dubbo.registry.address=redis://127.0.0.1:6379 #dubbo.registry.address=dubbo://127.0.0.1:9090 #dubbo.monitor.protocol=registry #dubbo.protocol.name=dubbo #dubbo.protocol.port=20880 #dubbo.service.loadbalance=roundrobin #dubbo.log4j.file=logs/msa-demo-provider.log #dubbo.log4j.level=INFO #dubbo.log4j.subdirectory=20880 dubbo.application.logger=slf4j dubbo.spring.config=classpath*:msa-*.xml 複製程式碼
1.4 服務啟動
定義服務啟動類
package com.alibaba.dubbo.demo.provider;
/**
* @author TaoBangren
* @version 1.0
* @since 2017/5/17 上午9:26
*/
public class DemoProvider {
public static void main(String[] args) {
com.alibaba.dubbo.container.Main.main(args);
}
}
複製程式碼
執行main方法啟動,看到以下日誌輸出時,msa-demo-provider啟動成功:
檢視DubboKeeper監控大盤,msa-demo-provider釋出服務成功,可以看到我們釋出的兩個介面:
2. 客戶端
-
建立Maven模組:msa-demo-client
-
msa-demo-client:配置pom.xml
<!-- Module依賴 START --> <dependency> <groupId>com.alibaba</groupId> <artifactId>msa-demo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Module依賴 END --> 複製程式碼
-
Dubbox與Spring整合配置:msa-demo-client.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 當前應用資訊配置 --> <!--<dubbo:application name="msa-demo-client" owner="shark" organization="shark"/>--> <!-- 多註冊中心配置,豎號分隔表示同時連線多個不同註冊中心,同一註冊中心的多個叢集地址用逗號分隔 --> <!--<dubbo:registry protocol="zookeeper" address="x.x.x.x:2181,x.x.x.x:2181,x.x.x.x:2181"/>--> <!--<dubbo:monitor address="x.x.x.x:20884"/>--> <dubbo:reference id="userService" interface="com.alibaba.dubbo.demo.user.UserService" group="xmlConfig"/> <dubbo:reference id="anotherUserRestService" interface="com.alibaba.dubbo.demo.user.facade.AnotherUserRestService"/> <!-- directly connect to provider to simulate the access to non-dubbo rest services <dubbo:reference id="anotherUserRestService" interface="com.alibaba.dubbo.demo.user.facade.AnotherUserRestService" url="rest://localhost:8888/services/"/> --> </beans> 複製程式碼
3. 消費端
3.1 消費端實現
-
建立Maven模組:msa-demo-consumer
-
msa-demo-consumer:配置pom.xml
<!-- Module依賴 START --> <dependency> <groupId>com.jeasy</groupId> <artifactId>msa-demo-client</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Module依賴 END --> 複製程式碼
-
建立消費端測試類:DemoAction.java
package com.alibaba.dubbo.demo; import com.alibaba.dubbo.rpc.RpcContext; import com.alibaba.dubbo.demo.user.User; import com.alibaba.dubbo.demo.user.UserService; import com.alibaba.dubbo.demo.user.facade.AnotherUserRestService; /** * @author TaoBangren * @version 1.0 * @since 2017/5/17 上午9:26 */ public class DemoAction { private UserService userService; private AnotherUserRestService anotherUserRestService; public void setUserService(final UserService userService) { this.userService = userService; } public void setAnotherUserRestService(final AnotherUserRestService anotherUserRestService) { this.anotherUserRestService = anotherUserRestService; } public void start() throws Exception { User user = new User(); user.setId(1L); user.setName("larrypage"); System.out.println("SUCCESS: registered user with id by rest" + anotherUserRestService.registerUser(user).getId()); System.out.println("SUCCESS: registered user with id " + userService.registerUser(user)); RpcContext.getContext().setAttachment("clientName", "demo"); RpcContext.getContext().setAttachment("clientImpl", "dubbox rest"); System.out.println("SUCCESS: got user by rest" + anotherUserRestService.getUser(1L)); System.out.println("SUCCESS: got user " + userService.getUser(1L)); } } 複製程式碼
-
Dubbox與Spring整合配置:msa-demo-consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 當前應用資訊配置 --> <dubbo:application name="msa-demo-consumer" owner="tbr" organization="tbr"/> <!-- 多註冊中心配置,豎號分隔表示同時連線多個不同註冊中心,同一註冊中心的多個叢集地址用逗號分隔 --> <dubbo:registry protocol="zookeeper" address="x.x.x.x:2181,x.x.x.x:2181,x.x.x.x:2181"/> <dubbo:monitor address="x.x.x.x:20884"/> <bean class="com.alibaba.dubbo.demo.DemoAction" init-method="start"> <property name="userService" ref="userService"/> <property name="anotherUserRestService" ref="anotherUserRestService"/> </bean> </beans> 複製程式碼
-
配置dubbo.properties
#dubbo.container=log4j,spring #dubbo.application.name=demo-consumer #dubbo.application.owner= #dubbo.registry.address=multicast://224.5.6.7:1234 #dubbo.registry.address=zookeeper://127.0.0.1:2181 #dubbo.registry.address=redis://127.0.0.1:6379 #dubbo.registry.address=dubbo://127.0.0.1:9090 #dubbo.monitor.protocol=registry #dubbo.log4j.file=logs/msa-demo-consumer.log #dubbo.log4j.level=INFO dubbo.application.logger=slf4j dubbo.spring.config=classpath*:msa-*.xml 複製程式碼
3.2 消費端測試
定義消費啟動類:
package com.jeasy;
/**
* @author TaoBangren
* @version 1.0
* @since 2017/5/17 上午9:26
*/
public class DemoConsumer {
public static void main(String[] args) {
com.alibaba.dubbo.container.Main.main(args);
}
}
複製程式碼
執行main方法啟動,看到以下日誌輸出時,msa-demo-consumer啟動成功:
同時服務端會輸出服務呼叫日誌資訊,並呼叫成功,如下:
4. 規範使用
模組 | 描述 | 是否必須 |
---|---|---|
msa-xxx-api | 定義介面&實體 | 必須 |
msa-xxx-provider | 依賴api模組,實現服務介面,提供服務 | 必須 |
msa-xxx-client | 依賴api模組,Spring配置檔案&測試用例,提供給第三方呼叫服務使用 | 必須 |
msa-xxx-consumer | 依賴client模組,建議保留該模組,避免client模組直接與應用方緊耦合 | 可選 |
5. 推薦閱讀
5.1 Dubbox相關資源
- 原始碼地址 : github.com/dangdangdot…
- 在Dubbo中開發REST風格的遠端呼叫 : dangdangdotcom.github.io/dubbox/rest…
- 在Dubbo中使用高效的Java序列化 : dangdangdotcom.github.io/dubbox/seri…
- 使用JavaConfig方式配置dubbox : dangdangdotcom.github.io/dubbox/java…
- Dubbo Jackson序列化使用說明 : dangdangdotcom.github.io/dubbox/jack…
- Demo : dangdangdotcom.github.io/dubbox/demo…
- 噹噹網開源Dubbox,擴充套件Dubbo服務框架支援REST風格遠端呼叫 : www.infoq.com/cn/news/201…
- Dubbox Wiki : github.com/dangdangdot…
5.2 Dubbo相關資源
- 原始碼地址 : github.com/alibaba/dub…
- Dubbo Wiki : github.com/alibaba/dub…
- http://dubbo.io/