目前流行的微服务,Rest风格的Http模式远程调用大行其道。 Rest格式的调用,可以做到对Provider方的代码0依赖,做到完全的解耦,规避Provider方接口升级带来的各种问题。
在日常的业务中,会涉及到各种协议的多系统间交互,比如Hessian、老系统常用的Webservice 等Http的远程调用,Dubbo 都提供了封装与扩展。
相比SpringCloud , Dubbo 一个接口发布多协议,一个系统调用多个外部协议接口时,会非常的便利,几乎可以做到代码的0 增加。
学习与理解这些不同协议的封装,能加深对Dubbo 设计的理解,指导自己的编写新的扩展实现。
一、 Dubbo的 http 调用协议配置
Dubbo 支持http调用的协议比较多,在官网 协议参考手册 里面介绍的就有:hessian、http、webservice、rest 这4种, 在Dubbo 的2.7.3版本的代码里面,有jsonrpc、xml这2中应该也是透过http协议进行调用的。
通过继承图,我们可以看到,这几个协议都是直接继承AbstractProxyProtocol 的。
以webservice 协议为例:在dubbo配置里面加上:
<dubbo:protocol name="webservice" server="servlet" port="8080" contextpath="webservice"/>
然后在web.xml里面配置dubbo的DispatchServlet拦截,就可以发布webservice协议的服务。
web.xml 配置:
<servlet>
<servlet-name>dubboDispatch</servlet-name>
<servlet-class>org.apache.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dubboDispatch</servlet-name>
<url-pattern>/webservice/*</url-pattern>
</servlet-mapping>
代码以外部Servlet模式发布服务,因为现在实际代码运行的时候,更多的是在外部容器里面,servlet模式,应用更广,代码阅读更清晰。 内嵌jetty或者tomcat模式,只是多了自己启动服务器,手动配置servlet而已(个人猜测,没看代码)。
发布的时候,port 为 应用的端口号,contextpath 为servlet 的映射路径。
二、 Webservice 协议服务 export
Dubbo 的http 相关协议,没有默认采用netty传输,dubbo编码 那一堆理不断剪还乱的channel、channelhandler 包装链。 借助成熟的应用服务器,cxf相关的开源组件, 封装与发布都非常简单。
成熟的应用服务器,承担了原生netty传输里面的 线程控制,channel重用等逻辑。
cxf开源组件, 承担了数据的编码、解码,底层数据的封装等逻辑。
有了之前Dubbo 默认服务的基础,我们直接从 WebServiceProtocol 协议的 export 方法开始。使用父类 AbstractProxyProtocol的 export方法。 所以dubbo的几个http 协议都是相同的处理模式。
1 public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException { 2 final String uri = serviceKey(invoker.getUrl()); 3 Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri); 4 if (exporter != null) { 5 // When modifying the configuration through override, you need to re-expose the newly modified service. 6 if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) { 7 return exporter; 8 } 9 } 10 //注释 1 11 final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl()); 12 13 //注释 2 14 exporter = new AbstractExporter<T>(invoker) { 15 @Override 16 public void unexport() { 17 super.unexport(); 18 exporterMap.remove(uri); 19 if (runnable != null) { 20 try { 21 runnable.run(); 22 } catch (Throwable t) { 23 logger.warn(t.getMessage(), t); 24 } 25 } 26 } 27 }; 28 exporterMap.put(uri, exporter); //将uri 与exporter放入map, 29 return exporter; 30 }
整个方法很简单,也比较清晰,没有复杂的逻辑。 export 放入的入参 invoker ,是一个经过多从包装的invoker 的filter链,最底层是实现类的ref调用。
注释1. proxyFactory.getProxy(invoker,true) ,作用是将invoker 链,代理成接口的实现类。然后调用 各个子类的doExport 方法,完成接口服务的绑定与配置。 返回一个runnable,runnable的逻辑是注销接口的操作。
注释2. 将invoker 与runnable 对象,封装为exporter
进入WebServiceProtocol 的doExport方法:
protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException { String addr = getAddr(url); HttpServer httpServer = serverMap.get(addr); if (httpServer == null) { //注释1 。 将webServiceHandler 绑定url ,只会绑定一次 httpServer = httpBinder.bind(url, new WebServiceHandler()); serverMap.put(addr, httpServer); } //注释 2 调用CXF的 组件,构建发布Service EndPoint final ServerFactoryBean serverFactoryBean = new ServerFactoryBean(); serverFactoryBean.setAddress(url.getAbsolutePath()); serverFactoryBean.setServiceClass(type); serverFactoryBean.setServiceBean(impl); serverFactoryBean.setBus(bus); serverFactoryBean.setDestinationFactory(transportFactory); //注释3. transportFactory后续会重用到 serverFactoryBean.create(); return new Runnable() { //注释4. 返回runnable,逻辑主要是销毁这个Server @Override public void run() { if(serverFactoryBean.getServer()!= null) { serverFactoryBean.getServer().destroy(); } if(serverFactoryBean.getBus()!=null) { serverFactoryBean.getBus().shutdown(true); } } }; }
注释1. 将WebServiceHandler 与url绑定,实际操作是 将webservicehandler 在DispatchServlet里面注册。
配置的是servlet,所以httpbinder 就是 ServletHttpBinder ,bind方法 new 一个 ServletHttpServer 。 在里面就一个关键代码:
DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler); ,将handler 放入DispatchServlet的map里面
同一个server ,getAddr() 方法获取到的addr 都是一样的,所以 注释1.只会被运行一次。
注释2. 就看开始调用CXF的组件,构建接口的Endpoint 。 用的的变量 bus、transportFactory都是 new出来的,都是cxf的代码,这个不深入,目前会用即可。
transportFactory 应该是一个比较重要的对象,保存了接口的实现调用。 在webservicehandler里面,invoke的时候,会用到。
注释4. 构建返回的runnable, 运行的时候,会销户这个serverFactoryBean 。
接下来看看WebServiceHandler 类,方法也比较简单:
private class WebServiceHandler implements HttpHandler { private volatile ServletController servletController; @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (servletController == null) { HttpServlet httpServlet = DispatcherServlet.getInstance(); if (httpServlet == null) { response.sendError(500, "No such DispatcherServlet instance."); return; } synchronized (this) { if (servletController == null) { //注释1. servletController 双重检测不存在,第一次调用的时候就 构建一个, servletController = new ServletController(transportFactory.getRegistry(), httpServlet.getServletConfig(), httpServlet); } } } RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); servletController.invoke(request, response);//注释2. 调用请求,转交给cxf处理并返回 } }
主要就2个点:
注释1. 双重检测 servletController 是否存在,不存在就构建一个。 在构建的时候用到了httpservlet 。
这是延迟构建,所以不用像rest协议需要在启动的时候配置BootstrapListener,并且还需要在spring的listener之前配置。
注释2. 调用cxf的方法,执行请求,并在response里面写返回。
我们再看看 DispatcherServlet 的service 方法:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpHandler handler = handlers.get(request.getLocalPort()); if (handler == null) {// service not found. response.sendError(HttpServletResponse.SC_NOT_FOUND, "Service not found."); } else { handler.handle(request, response); } }
mapping到请求后,从handler的map里面,获取handler,直接调用handler方法。 接上面代码就是:WebServiceHandler.handle()方法,清晰明了。
发布: 1. 在transportFactory 里面注册服务 。 2. handler 存放transportFactory , 3. key=端口,value = handler 放入DispatchServlet的map里面。
被调用: 1. DispatchServlet 接收请求 2. 根据port查找handler 3. 调用handler 并将结果写入response (使用transportFactory 保存的信息,处理请求)
二、 Webservice 协议服务 refer
refer 方法, 方法是在AbstractProtocol 里面的,调用AbstractProxyProtocol.protocolBindingRefer() ,再调用WebServiceProcotol.doRefer()
方法简单,这个我们倒着看, 先看 WebServiceProcotol.doRefer()
protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
ClientProxyFactoryBean proxyFactoryBean = new ClientProxyFactoryBean();
proxyFactoryBean.setAddress(url.setProtocol("http").toIdentityString());
proxyFactoryBean.setServiceClass(serviceType);
proxyFactoryBean.setBus(bus);
T ref = (T) proxyFactoryBean.create();
Client proxy = ClientProxy.getClient(ref);
HTTPConduit conduit = (HTTPConduit) proxy.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setConnectionTimeout(url.getParameter(Constants.CONNECT_TIMEOUT_KEY, Constants.DEFAULT_CONNECT_TIMEOUT));
policy.setReceiveTimeout(url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT));
conduit.setClient(policy);
return ref;
}
WebServiceProcotol.doRefer() 这个方法比较简单, 入参接口class 与provider 的url ,调用CXF的代码,返回一个接口实现的代理。
AbstractProxyProtocol.protocolBindingRefer()方法:
protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException { //doRefer的结果,接口实现代理, 由Dubbo的proxyFactory封装成Invoker 返回 final Invoker<T> target = proxyFactory.getInvoker(doRefer(type, url), type, url); Invoker<T> invoker = new AbstractInvoker<T>(type, url) { //对Invodker 再次封装,捕获CXF调用时的各种异常 @Override protected Result doInvoke(Invocation invocation) throws Throwable { try { Result result = target.invoke(invocation); //cxf远程调用,异常被捕获封装 // FIXME result is an AsyncRpcResult instance. Throwable e = result.getException(); if (e != null) { for (Class<?> rpcException : rpcExceptions) { if (rpcException.isAssignableFrom(e.getClass())) { throw getRpcException(type, url, invocation, e); } } } return result; } catch (RpcException e) { if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) { e.setCode(getErrorCode(e.getCause())); } throw e; } catch (Throwable e) { throw getRpcException(type, url, invocation, e); } } }; invokers.add(invoker); return invoker; }
这个方法逻辑比较清晰:
1. 先调用doRefer方法,将接口、url 与cxf组合起来,返回接口的 cxf 代理实现, 再由proxyFactory 封装为Invoker。
2. Invoker 再次被封装一次,捕获调用时的各种异常。
3. 封装的Invoker 返回,在AbstractProtocol 的refer里面,再次被封装成AsyncToSyncInvoker ,后被返回。
AbstractProtocol.refer() 最终返回的Invoker ,会被Directory 放入Invoker list里面,供路由选择。
至此,refer 方法结束, 总得来说,有3个主要步骤:
1. 将接口class 与provider ,调用cxf的代码,生成proxy
2. 将proxy生成Invoker,供上层代码使用。
3. invoker 的封装
三、其他
说下之前没注意到的2个抽象Class : AbstractInvoker 与 AbstractProxyInvoker
AbstractProxyInvoker 是将 接口实现 组装成Invoker,invoke 里面有 CompletableFuture ,这个invoke 是全链路异步实现的关键代码,将最终的调用异步化。
AbstractInvoker 一般是对Invoker 的再次封装,调用前做一些基础数据的设置,异常转换
四、总结
以Dubbo的WebServiceProtocol 为例,介绍了dubbo的 关于http 的协议实现。 借助成熟的服务容器,协议开源组件,扩展协议比起 默认的netty传输,dubbo协议要简单很多。基本只需要做好 开源组件的设置就好。 其他的协议,可以对照源码,都是一个模式,具体细节会有所不同。
对于REST 协议,需要在启动最开始 配置 org.apache.dubbo.remoting.http.servlet.BootstrapListener ,是因为在 Rest 的服务发布时,需要用到ServletContext, 如果将这个 配置在Spring 的listener之后,spring 启动的时候,发布服务初始化就会失败。
一般dubbo都默认用dubbo协议, 在与外围系统交互的时候,dubbo的多协议适配,会比较方便。
外围交互,会有一端是非dubbo框架,所以无法使用到dubbo的路由、负载均衡、动态配置等服务治理特性。