這裏咱們用三種場景來分別討論:java
非dubbo的消費端調用dubbo的REST服務(non-dubbo --> dubbo)git
dubbo消費端調用dubbo的REST服務 (dubbo --> dubbo)github
dubbo的消費端調用非dubbo的REST服務 (dubbo --> non-dubbo)spring
這種場景的客戶端與dubbo自己無關,直接選用相應語言和框架中合適的方式便可。json
若是是仍是java的客戶端(但沒用dubbo),能夠考慮直接使用標準的JAX-RS Client API或者特定REST實現的Client API來調用REST服務。下面是用JAX-RS Client API來訪問上述的UserService的registerUser():api
User user = new User(); user.setName("Larry");Client client = ClientBuilder.newClient();WebTarget target = client.target("http://localhost:8080/services/users/register.json");Response response = target.request().post(Entity.entity(user, MediaType.APPLICATION_JSON_TYPE));try { if (response.getStatus() != 200) { throw new RuntimeException("Failed with HTTP error code : " + response.getStatus()); } System.out.println("The generated id is " + response.readEntity(RegistrationResult.class).getId()); } finally { response.close(); client.close(); // 在真正開發中不要每次關閉client,好比HTTP長鏈接是由client持有的}
上面代碼片斷中的User和RegistrationResult類都是消費端本身編寫的,JAX-RS Client API會自動對它們作序列化/反序列化。負載均衡
固然,在java中也能夠直接用本身熟悉的好比HttpClient,FastJson,XStream等等各類不一樣技術來實現REST客戶端,在此再也不詳述。框架
這種場景下,和使用其餘dubbo的遠程調用方式同樣,直接在服務提供端和服務消費端共享Java服務接口,並添加spring xml配置(固然也能夠用spring/dubbo的annotation配置),便可透明的調用遠程REST服務:ide
<dubbo:reference id="userService" interface="xxx.UserService"/>
如前所述,這種場景下必須把JAX-RS的annotation添加到服務接口上,這樣在dubbo在消費端才能共享相應的REST配置信息,並據之作遠程調用:post
@Path("users")public interface UserService { @GET @Path("{id : \\d+}") @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) User getUser(@PathParam("id") Long id); }
若是服務接口的annotation中配置了多種數據格式,這裏因爲兩端都是dubbo系統,REST的大量細節被屏蔽了,因此不存在用前述URL後綴之類選擇數據格式的可能。目前在這種狀況下,排名最靠前的數據格式將直接被使用。
所以,咱們建議你在定義annotation的時候最好把最合適的數據格式放到前面,好比以上咱們是把json放在xml前面,由於json的傳輸性能優於xml。
這種場景下,能夠直接用場景1中描述的Java的方式來調用REST服務。但其實也能夠採用場景2中描述的方式,即更透明的調用REST服務,即便這個服務並非dubbo提供的。
若是用場景2的方式,因爲這裏REST服務並不是dubbo提供,通常也就沒有前述的共享的Java服務接口,因此在此咱們須要根據外部REST服務的狀況,本身來編寫Java接口以及相應參數類,並添加JAX-RS、JAXB、Jackson等的annotation,dubbo的REST底層實現會據此去自動生成請求消息,自動解析響應消息等等,從而透明的作遠程調用。或者這種方式也能夠理解爲,咱們嘗試用JAX-RS的方式去仿造實現一遍外部的REST服務提供端,而後把寫成服務接口放到客戶端來直接使用,dubbo的REST底層實現就能像調用dubbo的REST服務同樣調用其餘REST服務。
例如,咱們要調用以下的外部服務
http://api.foo.com/services/users/1001 http://api.foo.com/services/users/1002
獲取不一樣ID的用戶資料,返回格式是JSON
{ "id": 1001, "name": "Larry"}
咱們可根據這些信息,編寫服務接口和參數類便可:
@Path("users")public interface UserService { @GET @Path("{id : \\d+}") @Produces({MediaType.APPLICATION_JSON}) User getUser(@PathParam("id") Long id); }
public class User implements Serializable { private Long id; private String name; // …}
對於spring中的配置,由於這裏的REST服務不是dubbo提供的,因此沒法使用dubbo的註冊中心,直接配置外部REST服務的url地址便可(如多個地址用逗號分隔):
<dubbo:reference id="userService" interface="xxx.UserService" url="rest://api.foo.com/services/"/>
注意:這裏協議必須用rest://而不是http://之類。若是外部的REST服務有context path,則在url中也必須添加上(除非你在每一個服務接口的@Path annotation中都帶上context path),例如上面的/services/。同時這裏的services後面必須帶上/,這樣才能使dubbo正常工做。
另外,這裏依然能夠配置客戶端可啓動的最大鏈接數和超時時間:
<dubbo:reference id="userService" interface="xxx.UserService" url="rest://api.foo.com/services/" timeout="2000" connections="10"/>
Dubbo中的REST開發是徹底兼容標準JAX-RS的,但其支持的功能目前是完整JAX-RS的一個子集,部分由於它要受限於dubbo和spring的特定體系。
在dubbo中使用的JAX-RS的侷限包括但不限於:
服務實現只能是singleton的,不能支持per-request scope和per-lookup scope
不支持用@Context annotation對服務的實例字段注入 ServletConfig、ServletContext、HttpServletRequest、HttpServletResponse等等,但能夠支持對服務方法參數的注入。但對某些特定REST server實現,(祥見前面的敘述),也不支持對服務方法參數的注入。
能夠的,並且是自動集成的,也就是你在dubbo中開發的全部REST服務都會自動註冊到服務冊中心和監控中心,能夠經過它們作管理。
可是,只有當REST的消費端也是基於dubbo的時候,註冊中心中的許多服務治理操做才能徹底起做用。而若是消費端是非dubbo的,天然不受註冊中心管理,因此其中不少操做是不會對消費端起做用的。
若是dubbo REST的消費端也是dubbo的,則Dubbo REST和其餘dubbo遠程調用協議基本徹底同樣,由dubbo框架透明的在消費端作load balance、failover等等。
若是dubbo REST的消費端是非dubbo的,甚至是非java的,則最好配置服務提供端的軟負載均衡機制,目前可考慮用LVS、HAProxy、 Nginx等等對HTTP請求作負載均衡。
http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params
http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects
我認爲dubbo當前體系中顯然也有很多不足之處,這裏列出幾個與REST有關的、並影響用戶使用的問題(不包括內部實現的問題),供參考評論,爲下一步重構做準備。
在前文,前面咱們已經提到過RpcContext用法的侵入性,因爲它是用單例的方式來訪問上下文信息,這徹底不符合spring應用的通常風格,不利於應用擴展和單元測試。將來咱們可能用依賴注入方式注入一個接口,再用它去訪問ThreadLocal中的上下文信息。
dubbo支持多種遠程調用方式,但全部調用方式都是用<dubbo:protocol/>
來配置的,例如:
<dubbo:protocol name="dubbo" port="9090" server="netty" client="netty" codec="dubbo" serialization="hessian2" charset="UTF-8" threadpool="fixed" threads="100" queues="0" iothreads="9" buffer="8192" accepts="1000" payload="8388608"/>
其實,上面不少屬性實際上dubbo RPC遠程調用方式特有的,不少dubbo中的其它遠程調用方式根本就不支持例如server, client, codec, iothreads, accepts, payload等等(固然,有的是條件所限不支持,有的是根本沒有必要支持)。這給用戶的使用徒增不少困惑,用戶也並不知道有些屬性(好比作性能調優)添加了其實是不起做用的。
另外一方面,各類遠程調用方式每每有大量本身獨特的配置須要,特別是咱們逐步爲每種遠程調用方式都添加更豐富、更高級的功能,這就不可避免的擴展<protocol/>
中的屬性(例如目前咱們在REST中已經添加了keepalive和extension兩個屬性),到最後會致使<protocol/>
臃腫不堪,用戶的使用也更加困惑。
固然,dubbo中有一種擴展<protocol/>
的方式是用<dubbo:parameter/>
,但這種方式顯然頗有侷限性,並且用法複雜,缺少schema校驗。
因此,最好的方式是爲每種遠程調用方式設置本身的protocol元素,好比<protocol-dubbo/>
,<protocol-rest/>
等等,每種元素用XML schema規定本身的屬性(固然屬性在各類遠程調用方式之間能通用是最好的)。
如此一來,例如前面提到過的extension配置也能夠用更自由的方式,從而更清楚更可擴展(如下只是舉例,固然也許有更好的方式):
<dubbo:protocol-rest port="8080"> <dubbo:extension>someInterceptor</dubbo:extension> <dubbo:extension>someFilter</dubbo:extension> <dubbo:extension>someDynamicFeature</dubbo:extension> <dubbo:extension>someEntityProvider</dubbo:extension> </dubbo:protocol-rest>
dubbo的XML配置中大量命名都不符合spring規範,好比:
<dubbo:protocol name="dubbo" port="9090" server="netty" client="netty" codec="dubbo" serialization="hessian2" charset="UTF-8" threadpool="fixed" threads="100" queues="0" iothreads="9" buffer="8192" accepts="1000" payload="8388608"/>
上面threadpool應該改成thread-pool,iothreads應該改成io-threads,單詞之間應該用"-"分隔。這雖然看起來是個小問題,但也涉及到了可讀性,特別是可擴展性,由於有時候咱們不可避免要用更多單詞來描述XML元素和屬性。
其實dubbo自己也是建議遵照spring到XML的命名規範。