这里咱们用三种场景来分别讨论: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的命名规范。