Dubbo源码学习笔记 之 调用&发布 Rest&WebService协议服务

  目前流行的微服务,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 的。  

                                         Dubbo的http协议继承图

以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的路由、负载均衡、动态配置等服务治理特性。 

 

posted on 2019-07-12 18:02  qingcaolin  阅读(1330)  评论(0编辑  收藏  举报

导航