资讯专栏INFORMATION COLUMN

dubbo源码解析(二十二)远程调用——Protocol

孙淑建 / 3479人阅读

摘要:七该类也实现了,也是装饰了接口,但是它是在服务引用和暴露过程中加上了监听器的功能。如果是注册中心,则暴露该创建一个暴露者监听器包装类对象该方法是在服务暴露上做了监听器功能的增强,也就是加上了监听器。

远程调用——Protocol

</>复制代码

  1. 目标:介绍远程调用中协议的设计和实现,介绍dubbo-rpc-api中的各种protocol包的源码,是重点内容。
前言

在远程调用中协议是非常重要的一层,看下面这张图:

该层是在信息交换层之上,分为了并且夹杂在服务暴露和服务引用中间,为了有一个约定的方式进行调用。

dubbo支持不同协议的扩展,比如http、thrift等等,具体的可以参照官方文档。本文讲解的源码大部分是对于公共方法的实现,而具体的服务暴露和服务引用会在各个协议实现中讲到。

下面是该包下面的类图:

源码分析 (一)AbstractProtocol

该类是协议的抽象类,实现了Protocol接口,其中实现了一些公共的方法,抽象方法在它的子类AbstractProxyProtocol中定义。

1.属性

</>复制代码

  1. /**
  2. * 服务暴露者集合
  3. */
  4. protected final Map> exporterMap = new ConcurrentHashMap>();
  5. /**
  6. * 服务引用者集合
  7. */
  8. //TODO SOFEREFENCE
  9. protected final Set> invokers = new ConcurrentHashSet>();
2.serviceKey

</>复制代码

  1. protected static String serviceKey(URL url) {
  2. // 获得绑定的端口号
  3. int port = url.getParameter(Constants.BIND_PORT_KEY, url.getPort());
  4. return serviceKey(port, url.getPath(), url.getParameter(Constants.VERSION_KEY),
  5. url.getParameter(Constants.GROUP_KEY));
  6. }
  7. protected static String serviceKey(int port, String serviceName, String serviceVersion, String serviceGroup) {
  8. return ProtocolUtils.serviceKey(port, serviceName, serviceVersion, serviceGroup);
  9. }

该方法是为了得到服务key group+"/"+serviceName+":"+serviceVersion+":"+port

3.destroy

</>复制代码

  1. @Override
  2. public void destroy() {
  3. // 遍历服务引用实体
  4. for (Invoker invoker : invokers) {
  5. if (invoker != null) {
  6. // 从集合中移除
  7. invokers.remove(invoker);
  8. try {
  9. if (logger.isInfoEnabled()) {
  10. logger.info("Destroy reference: " + invoker.getUrl());
  11. }
  12. // 销毁
  13. invoker.destroy();
  14. } catch (Throwable t) {
  15. logger.warn(t.getMessage(), t);
  16. }
  17. }
  18. }
  19. // 遍历服务暴露者
  20. for (String key : new ArrayList(exporterMap.keySet())) {
  21. // 从集合中移除
  22. Exporter exporter = exporterMap.remove(key);
  23. if (exporter != null) {
  24. try {
  25. if (logger.isInfoEnabled()) {
  26. logger.info("Unexport service: " + exporter.getInvoker().getUrl());
  27. }
  28. // 取消暴露
  29. exporter.unexport();
  30. } catch (Throwable t) {
  31. logger.warn(t.getMessage(), t);
  32. }
  33. }
  34. }
  35. }

该方法是对invoker和exporter的销毁。

(二)AbstractProxyProtocol

该类继承了AbstractProtocol类,其中利用了代理工厂对AbstractProtocol中的两个集合进行了填充,并且对异常做了处理。

1.属性

</>复制代码

  1. /**
  2. * rpc的异常类集合
  3. */
  4. private final List> rpcExceptions = new CopyOnWriteArrayList>();
  5. /**
  6. * 代理工厂
  7. */
  8. private ProxyFactory proxyFactory;
2.export

</>复制代码

  1. @Override
  2. @SuppressWarnings("unchecked")
  3. public Exporter export(final Invoker invoker) throws RpcException {
  4. // 获得uri
  5. final String uri = serviceKey(invoker.getUrl());
  6. // 获得服务暴露者
  7. Exporter exporter = (Exporter) exporterMap.get(uri);
  8. if (exporter != null) {
  9. return exporter;
  10. }
  11. // 新建一个线程
  12. final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
  13. exporter = new AbstractExporter(invoker) {
  14. /**
  15. * 取消暴露
  16. */
  17. @Override
  18. public void unexport() {
  19. super.unexport();
  20. // 移除该key对应的服务暴露者
  21. exporterMap.remove(uri);
  22. if (runnable != null) {
  23. try {
  24. // 启动线程
  25. runnable.run();
  26. } catch (Throwable t) {
  27. logger.warn(t.getMessage(), t);
  28. }
  29. }
  30. }
  31. };
  32. // 加入集合
  33. exporterMap.put(uri, exporter);
  34. return exporter;
  35. }

其中分为两个步骤,创建一个exporter,放入到集合汇中。在创建exporter时对unexport方法进行了重写。

3.refer

</>复制代码

  1. @Override
  2. public Invoker refer(final Class type, final URL url) throws RpcException {
  3. // 通过代理获得实体域
  4. final Invoker target = proxyFactory.getInvoker(doRefer(type, url), type, url);
  5. Invoker invoker = new AbstractInvoker(type, url) {
  6. @Override
  7. protected Result doInvoke(Invocation invocation) throws Throwable {
  8. try {
  9. // 获得调用结果
  10. Result result = target.invoke(invocation);
  11. Throwable e = result.getException();
  12. // 如果抛出异常,则抛出相应异常
  13. if (e != null) {
  14. for (Class rpcException : rpcExceptions) {
  15. if (rpcException.isAssignableFrom(e.getClass())) {
  16. throw getRpcException(type, url, invocation, e);
  17. }
  18. }
  19. }
  20. return result;
  21. } catch (RpcException e) {
  22. // 抛出未知异常
  23. if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
  24. e.setCode(getErrorCode(e.getCause()));
  25. }
  26. throw e;
  27. } catch (Throwable e) {
  28. throw getRpcException(type, url, invocation, e);
  29. }
  30. }
  31. };
  32. // 加入集合
  33. invokers.add(invoker);
  34. return invoker;
  35. }

该方法是服务引用,先从代理工厂中获得Invoker对象target,然后创建了真实的invoker在重写方法中调用代理的方法,最后加入到集合。

</>复制代码

  1. protected abstract Runnable doExport(T impl, Class type, URL url) throws RpcException;
  2. protected abstract T doRefer(Class type, URL url) throws RpcException;

可以看到其中抽象了服务引用和暴露的方法,让各类协议各自实现。

(三)AbstractInvoker

该类是invoker的抽象方法,因为协议被夹在服务引用和服务暴露中间,无论什么协议都有一些通用的Invoker和exporter的方法实现,而该类就是实现了Invoker的公共方法,而把doInvoke抽象出来,让子类只关注这个方法。

1.属性

</>复制代码

  1. /**
  2. * 服务类型
  3. */
  4. private final Class type;
  5. /**
  6. * url对象
  7. */
  8. private final URL url;
  9. /**
  10. * 附加值
  11. */
  12. private final Map attachment;
  13. /**
  14. * 是否可用
  15. */
  16. private volatile boolean available = true;
  17. /**
  18. * 是否销毁
  19. */
  20. private AtomicBoolean destroyed = new AtomicBoolean(false);
2.convertAttachment

</>复制代码

  1. private static Map convertAttachment(URL url, String[] keys) {
  2. if (keys == null || keys.length == 0) {
  3. return null;
  4. }
  5. Map attachment = new HashMap();
  6. // 遍历key,把值放入附加值集合中
  7. for (String key : keys) {
  8. String value = url.getParameter(key);
  9. if (value != null && value.length() > 0) {
  10. attachment.put(key, value);
  11. }
  12. }
  13. return attachment;
  14. }

该方法是转化为附加值,把url中的值转化为服务调用invoker的附加值。

3.invoke

</>复制代码

  1. @Override
  2. public Result invoke(Invocation inv) throws RpcException {
  3. // if invoker is destroyed due to address refresh from registry, let"s allow the current invoke to proceed
  4. // 如果服务引用销毁,则打印告警日志,但是通过
  5. if (destroyed.get()) {
  6. logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
  7. + ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
  8. }
  9. RpcInvocation invocation = (RpcInvocation) inv;
  10. // 会话域中加入该调用链
  11. invocation.setInvoker(this);
  12. // 把附加值放入会话域
  13. if (attachment != null && attachment.size() > 0) {
  14. invocation.addAttachmentsIfAbsent(attachment);
  15. }
  16. // 把上下文的附加值放入会话域
  17. Map contextAttachments = RpcContext.getContext().getAttachments();
  18. if (contextAttachments != null && contextAttachments.size() != 0) {
  19. /**
  20. * invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
  21. * because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
  22. * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
  23. * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
  24. */
  25. invocation.addAttachments(contextAttachments);
  26. }
  27. // 如果开启的是异步调用,则把该设置也放入附加值
  28. if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) {
  29. invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
  30. }
  31. // 加入编号
  32. RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
  33. try {
  34. // 执行调用链
  35. return doInvoke(invocation);
  36. } catch (InvocationTargetException e) { // biz exception
  37. Throwable te = e.getTargetException();
  38. if (te == null) {
  39. return new RpcResult(e);
  40. } else {
  41. if (te instanceof RpcException) {
  42. ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
  43. }
  44. return new RpcResult(te);
  45. }
  46. } catch (RpcException e) {
  47. if (e.isBiz()) {
  48. return new RpcResult(e);
  49. } else {
  50. throw e;
  51. }
  52. } catch (Throwable e) {
  53. return new RpcResult(e);
  54. }
  55. }

该方法做了一些公共的操作,比如服务引用销毁的检测,加入附加值,加入调用链实体域到会话域中等。然后执行了doInvoke抽象方法。各协议自己去实现。

(四)AbstractExporter

该类和AbstractInvoker类似,也是在服务暴露中实现了一些公共方法。

1.属性

</>复制代码

  1. /**
  2. * 实体域
  3. */
  4. private final Invoker invoker;
  5. /**
  6. * 是否取消暴露服务
  7. */
  8. private volatile boolean unexported = false;
2.unexport

</>复制代码

  1. @Override
  2. public void unexport() {
  3. // 如果已经消取消暴露,则之间返回
  4. if (unexported) {
  5. return;
  6. }
  7. // 设置为true
  8. unexported = true;
  9. // 销毁该实体域
  10. getInvoker().destroy();
  11. }
(五)InvokerWrapper

该类是Invoker的包装类,其中用到类装饰模式,不过并没有实现实际的功能增强。

</>复制代码

  1. public class InvokerWrapper implements Invoker {
  2. /**
  3. * invoker对象
  4. */
  5. private final Invoker invoker;
  6. private final URL url;
  7. public InvokerWrapper(Invoker invoker, URL url) {
  8. this.invoker = invoker;
  9. this.url = url;
  10. }
  11. @Override
  12. public Class getInterface() {
  13. return invoker.getInterface();
  14. }
  15. @Override
  16. public URL getUrl() {
  17. return url;
  18. }
  19. @Override
  20. public boolean isAvailable() {
  21. return invoker.isAvailable();
  22. }
  23. @Override
  24. public Result invoke(Invocation invocation) throws RpcException {
  25. return invoker.invoke(invocation);
  26. }
  27. @Override
  28. public void destroy() {
  29. invoker.destroy();
  30. }
  31. }
(六)ProtocolFilterWrapper

该类实现了Protocol接口,其中也用到了装饰模式,是对Protocol的装饰,是在服务引用和暴露的方法上加上了过滤器功能。

1.buildInvokerChain

</>复制代码

  1. private static Invoker buildInvokerChain(final Invoker invoker, String key, String group) {
  2. Invoker last = invoker;
  3. // 获得过滤器的所有扩展实现类实例集合
  4. List filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
  5. if (!filters.isEmpty()) {
  6. // 从最后一个过滤器开始循环,创建一个带有过滤器链的invoker对象
  7. for (int i = filters.size() - 1; i >= 0; i--) {
  8. final Filter filter = filters.get(i);
  9. // 记录last的invoker
  10. final Invoker next = last;
  11. // 新建last
  12. last = new Invoker() {
  13. @Override
  14. public Class getInterface() {
  15. return invoker.getInterface();
  16. }
  17. @Override
  18. public URL getUrl() {
  19. return invoker.getUrl();
  20. }
  21. @Override
  22. public boolean isAvailable() {
  23. return invoker.isAvailable();
  24. }
  25. /**
  26. * 关键在这里,调用下一个filter代表的invoker,把每一个过滤器串起来
  27. * @param invocation
  28. * @return
  29. * @throws RpcException
  30. */
  31. @Override
  32. public Result invoke(Invocation invocation) throws RpcException {
  33. return filter.invoke(next, invocation);
  34. }
  35. @Override
  36. public void destroy() {
  37. invoker.destroy();
  38. }
  39. @Override
  40. public String toString() {
  41. return invoker.toString();
  42. }
  43. };
  44. }
  45. }
  46. return last;
  47. }

该方法就是创建带 Filter 链的 Invoker 对象。倒序的把每一个过滤器串连起来,形成一个invoker。

2.export

</>复制代码

  1. @Override
  2. public Exporter export(Invoker invoker) throws RpcException {
  3. // 如果是注册中心,则直接暴露服务
  4. if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
  5. return protocol.export(invoker);
  6. }
  7. // 服务提供侧暴露服务
  8. return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
  9. }

该方法是在服务暴露上做了过滤器链的增强,也就是加上了过滤器。

3.refer

</>复制代码

  1. @Override
  2. public Invoker refer(Class type, URL url) throws RpcException {
  3. // 如果是注册中心,则直接引用
  4. if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
  5. return protocol.refer(type, url);
  6. }
  7. // 消费者侧引用服务
  8. return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
  9. }

该方法是在服务引用上做了过滤器链的增强,也就是加上了过滤器。

(七)ProtocolListenerWrapper

该类也实现了Protocol,也是装饰了Protocol接口,但是它是在服务引用和暴露过程中加上了监听器的功能。

1.export

</>复制代码

  1. @Override
  2. public Exporter export(Invoker invoker) throws RpcException {
  3. // 如果是注册中心,则暴露该invoker
  4. if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
  5. return protocol.export(invoker);
  6. }
  7. // 创建一个暴露者监听器包装类对象
  8. return new ListenerExporterWrapper(protocol.export(invoker),
  9. Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
  10. .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
  11. }

该方法是在服务暴露上做了监听器功能的增强,也就是加上了监听器。

2.refer

</>复制代码

  1. @Override
  2. public Invoker refer(Class type, URL url) throws RpcException {
  3. // 如果是注册中心。则直接引用服务
  4. if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
  5. return protocol.refer(type, url);
  6. }
  7. // 创建引用服务监听器包装类对象
  8. return new ListenerInvokerWrapper(protocol.refer(type, url),
  9. Collections.unmodifiableList(
  10. ExtensionLoader.getExtensionLoader(InvokerListener.class)
  11. .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
  12. }

该方法是在服务引用上做了监听器功能的增强,也就是加上了监听器。

后记

</>复制代码

  1. 该部分相关的源码解析地址:https://github.com/CrazyHZM/i...

该文章讲解了远程调用中关于协议的部分,其实就是讲了一些公共的方法,并且把关键方法抽象出来让子类实现,具体的方法实现都在各个协议中自己实现。接下来我将开始对rpc模块的代理进行讲解。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72957.html

相关文章

  • dubbo源码解析(四十六)消费端发送请求过程

    摘要:可以参考源码解析二十四远程调用协议的八。十六的该类也是用了适配器模式,该类主要的作用就是增加了心跳功能,可以参考源码解析十远程通信层的四。二十的可以参考源码解析十七远程通信的一。 2.7大揭秘——消费端发送请求过程 目标:从源码的角度分析一个服务方法调用经历怎么样的磨难以后到达服务端。 前言 前一篇文章讲到的是引用服务的过程,引用服务无非就是创建出一个代理。供消费者调用服务的相关方法。...

    fish 评论0 收藏0
  • dubbo源码解析(四十七)服务端处理请求过程

    摘要:而存在的意义就是保证请求或响应对象可在线程池中被解码,解码完成后,就会分发到的。 2.7大揭秘——服务端处理请求过程 目标:从源码的角度分析服务端接收到请求后的一系列操作,最终把客户端需要的值返回。 前言 上一篇讲到了消费端发送请求的过程,该篇就要将服务端处理请求的过程。也就是当服务端收到请求数据包后的一系列处理以及如何返回最终结果。我们也知道消费端在发送请求的时候已经做了编码,所以我...

    yzzz 评论0 收藏0
  • dubbo源码解析(四十八)异步化改造

    摘要:大揭秘异步化改造目标从源码的角度分析的新特性中对于异步化的改造原理。看源码解析四十六消费端发送请求过程讲到的十四的,在以前的逻辑会直接在方法中根据配置区分同步异步单向调用。改为关于可以参考源码解析十远程通信层的六。 2.7大揭秘——异步化改造 目标:从源码的角度分析2.7的新特性中对于异步化的改造原理。 前言 dubbo中提供了很多类型的协议,关于协议的系列可以查看下面的文章: du...

    lijinke666 评论0 收藏0
  • dubbo源码解析二十七)远程调用——injvm本地调用

    摘要:远程调用本地调用目标介绍本地调用的设计和实现,介绍的源码。前言是一个远程调用的框架,但是它没有理由不支持本地调用,本文就要讲解关于本地调用的实现。服务暴露者集合取消暴露调用父类的取消暴露方法从集合中移除二该类继承了类,是本地调用的实现。 远程调用——injvm本地调用 目标:介绍injvm本地调用的设计和实现,介绍dubbo-rpc-injvm的源码。 前言 dubbo是一个远程调用的...

    sean 评论0 收藏0
  • dubbo源码解析二十六)远程调用——http协议

    摘要:前言基于表单的远程调用协议,采用的实现,关于协议就不用多说了吧。后记该部分相关的源码解析地址该文章讲解了远程调用中关于协议的部分,内容比较简单,可以参考着官方文档了解一下。 远程调用——http协议 目标:介绍远程调用中跟http协议相关的设计和实现,介绍dubbo-rpc-http的源码。 前言 基于HTTP表单的远程调用协议,采用 Spring 的HttpInvoker实现,关于h...

    xiyang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<