研究一款入门级别的 RPC 框架 -- XXL-RPC

📒 研究一款入门级别的 RPC 框架 -- XXL-RPC

XXL-RPC 框架是 XXL-JOB 作者出品, 官网地址 分布式服务框架XXL-RPC

XXL-RPC 是一款轻量、简单,但五脏俱全的 RPC 框架,本地环境搭建十分简单, 对初学 RPC 的人来说,非常的友好,建议阅读学习,对于理解 RPC 大有裨益。 本文也是围绕 XXL-RPC 框架行文的。

官网文章已经写得很好,而本文是基于自己的学习和理解而成文的;内容会涉及源码,篇幅偏长。为了更好理解 XXL-PRC,需要有 Spring 初始化过程和 Netty 知识背景。

📖一、RPC 基础

1.1 入门理解

PRC 是为调用远程的服务像调用本地一样服务简单。

举例:在 node1 节点上调用 node2 的 serviceB,就像 node1 上调用 node1 上的 serviceB 一样。

要实现这一目标,由上图可见,通信组件必不可少。如果不考虑集群,可以将 node2 的各种信息配置写入 node1,但是考虑集群的扩容和缩容,就必须要有一个组件能及时地更新各节点的信息,暂且称这个组件为服务注册与发现组件。

对 RPC 有了最基本的了解后,接着我们再进一步看 RPC 的理论和设计。

1.2 基础原理和设计

如果 node1 要调用 node2 服务就需要设计一个简单的通信;可以粗略概括如下:

为了更好的理解上图,补充一下术语:

术语 理解
stub Stub 是在客户端端点的代理程序,用于代表客户端应用程序与远程服务进行通信。它隐藏了底层的网络通信细节,当客户端应用程序调用 stub 中的函数时,stub 将负责将请求打包并通过网络发送给远程服务端。
skeleton Skeleton 是在服务端端点的代理程序,用于接收来自客户端的请求,并将其解包后传递给实际的服务实现。Skeleton 隐藏了底层的网络通信细节,并负责将请求分派到相应的服务实现函数上。当服务实现函数执行完毕后,skeleton将负责将结果打包并发送给客户端

stub 和 skeleton 是 RPC 中用于代理客户端和服务端之间通信的组件。

RPC 并没有具体规范,但是大致原理是一样的, 一次简单 RPC 调用过程如下:

图来源网络

有了这些基础概念以后,接下来我们正式进入 XXL-RPC 这个框架的学习。

📜二、XXL-RPC 的设计和理论

2.1 XXL-RPC 的设计

XXL-RPC 中关于 RPC 的一次调用过程,原理剖析如下,作者的设计实现,本文的代码分析也是围绕下面这张图详细展开理解的。

其中 TCP 的模型, XXL-RPC 采用 NIO 进行底层通讯。

XXL-RPC 底层通信使用了 Netty。 如果要想学习了解 RPC 框架, Netty 是必须要掌握的。

客户端可以跟每一个服务提供者( ip + port )建立一个 socket 链接。 如果服务提供者是集群,那么每个客户端可以跟这些服务提供者一一建立链接,形成连接池。客户端请求的时候,根据负载均衡算法从中取一个进行使用即可。

kotlin 复制代码
// 地址由 ip 和 port 组成
this.registryAddress = IpUtil.getIpPort(this.ip, this.port);

2.2 XXL-RPC 工程

XXL-RPC 工程代码如下,有两个示例,接下来就从示例进行入手 (provider 和 consumer/invoker) 到这里了,不妨拉一下源码看看。

工程是以 spring 为基础的,从 @XxlRpcService@XxlRpcReference 两个注解入手比较合适。而两个注解都是通过 spring 加载过程的后置处理器进行处理,如果把这两个注解捋顺了,整个过程就基本清晰了。

bash 复制代码
/xxl-rpc-sample-springboot-client :服务消费方 consumer 调用示例
/xxl-rpc-sample-springboot-server :服务提供方 provider 示例

📓三、源码理解

@XxlRpcService@XxlRpcReference 框架是依赖 spring 容器。在启动过程中,后置处理器会进行各自的初始化处理,源码理解就围绕这两个注解进行展开。

3.1 @XxlRpcService

XxlRpcService 处理的入口是 XxlRpcSpringProviderFactory, 也是服务提供者的核心类; XxlRpcSpringProviderFactory 实现 ApplicationContextAware 和 InitializingBean 两个接口, 这个类在创建的时候,会扫描所有 XxlRpcService 的注解类。在 afterPropertiesSet 中启动 Netty 服务端。

接下来详细查看一下源码:

  1. 扫描所有 XxlRpcService 类,Spring 后置处理,比较简单。
Java 复制代码
// 将带有 XxlRpcService 注解的所有类都解析放入到 Map 中
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(XxlRpcService.class);
    if (serviceBeanMap!=null && serviceBeanMap.size()>0) {
        for (Object serviceBean : serviceBeanMap.values()) {
            // valid
            if (serviceBean.getClass().getInterfaces().length ==0) {
                throw new XxlRpcException("XXL-RPC, service(XxlRpcService) must inherit interface.");
            }
            // add service
            XxlRpcService xxlRpcService = serviceBean.getClass().getAnnotation(XxlRpcService.class);

            String iface = serviceBean.getClass().getInterfaces()[0].getName();
            String version = xxlRpcService.version();
            // iface + version 作为 key,将其放入到 map 中
            super.addService(iface, version, serviceBean);
        }
    }
}
  1. 回调 afterPropertiesSet ,触发 Netty 服务端的创建。
scala 复制代码
public class XxlRpcSpringProviderFactory extends XxlRpcProviderFactory implements ApplicationContextAware, InitializingBean,DisposableBean {
    // 回调调用 start 创建服务    
    @Override
    public void afterPropertiesSet() throws Exception {
        super.start();
    }
}
  1. 创建 Netty 服务端

Netty 服务端启动代码; 业务核心处理类是 NettyServerHandler 类;这个类会处理具体请求,不复杂,模板式的代码。

JAVA 复制代码
public class NettyServer extends Server {

    private Thread thread;

    @Override
    public void start(final XxlRpcProviderFactory xxlRpcProviderFactory) throws Exception {

        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 设置线程数大小。默认 60、300 
                final ThreadPoolExecutor serverHandlerPool = ThreadPoolUtil.makeServerThreadPool(
                        NettyServer.class.getSimpleName(),
                        xxlRpcProviderFactory.getCorePoolSize(),
                        xxlRpcProviderFactory.getMaxPoolSize());
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workerGroup = new NioEventLoopGroup();

                try {
                    // start server
                    ServerBootstrap bootstrap = new ServerBootstrap();
                    bootstrap.group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel channel) throws Exception {
                                    channel.pipeline()
                                            // 心跳检活
                                            .addLast(new IdleStateHandler(0,0, Beat.BEAT_INTERVAL*3, TimeUnit.SECONDS))     // beat 3N, close if idle                     
                                            // 编解码
                                            .addLast(new NettyDecoder(XxlRpcRequest.class, xxlRpcProviderFactory.getSerializerInstance()))
                                            .addLast(new NettyEncoder(XxlRpcResponse.class, xxlRpcProviderFactory.getSerializerInstance()))
                                            // 处理请求的具体类
                                            .addLast(new NettyServerHandler(xxlRpcProviderFactory, serverHandlerPool));
                                }
                            })
                            .childOption(ChannelOption.TCP_NODELAY, true)
                            .childOption(ChannelOption.SO_KEEPALIVE, true);

                    // bind
                    ChannelFuture future = bootstrap.bind(xxlRpcProviderFactory.getPort()).sync();

                    // 激活服务注册
                    onStarted();

                    // wait util stop
                    future.channel().closeFuture().sync();
                .......
    }

上面的 Netty 服务端启动代码是非常标准的模板式代码。具体调用过程如下:

  1. NettyServerHandler 服务请求处理的 handler

在这个 handler 中,将任务提交到线程池;再通过反射处理请求,再将结果写回 Netty 通道。

Java 复制代码
@Override
public void channelRead0(final ChannelHandlerContext ctx, final XxlRpcRequest xxlRpcRequest) throws Exception {
     ......
    // do invoke
    try {
        serverHandlerPool.execute(new Runnable() {
            @Override
            public void run() {
                // invoke + response
                // 真正做调用的地方
                XxlRpcResponse xxlRpcResponse = xxlRpcProviderFactory.invokeService(xxlRpcRequest);
                // 写回数据
                ctx.writeAndFlush(xxlRpcResponse);
            }
        });
    } catch (Exception e) {
        .......
        ctx.writeAndFlush(xxlRpcResponse);
    }
}
  1. 反射调用接口 xxlRpcProviderFactory.invokeService。 从 Map 中寻找 service,然后通过反射进行调用。
Java 复制代码
// 通过反射进行调用
Class<?> serviceClass = serviceBean.getClass();
String methodName = xxlRpcRequest.getMethodName();
Class<?>[] parameterTypes = xxlRpcRequest.getParameterTypes();
Object[] parameters = xxlRpcRequest.getParameters();
Method method = serviceClass.getMethod(methodName, parameterTypes);
method.setAccessible(true);
Object result = method.invoke(serviceBean, parameters);

服务端的代码还是比较简单和清晰。大致有以下几个步骤:

  1. XxlRpcSpringProviderFactory 实现了 ApplicationContextAware 接口,所以 spring 启动会回调; 在回调的时候,将所有 XxlRpcService 注解的类都标记成为服务提供的类,用一个 Map 容器承载。
  2. XxlRpcSpringProviderFactory 实现了 InitializingBean 接口,在所有类都初始化完成后,回调 afterPropertiesSet() 方法,这个时候会通过这个方法启动 Netty 服务端。
  3. Netty 服务端是非常标准的模板代码,其中 NettyServerHandler 为核心的业务 handler,用来处理服务请求的核心。
  4. NettyServerHandler 中使用线程池来,这样能够处理大量请求;进行具体业务方法的调用
  5. xxlRpcProviderFactory.invokeService 完成具体方法调用。

上面的代码就是服务端(provider) 启动和调用的过程,比较简单清晰的。

服务注册启动是在 Netty 启动的时候,回调 start 事件启动的

接下来讲解客户端(consumer/invoker) 的一个启动和调用过程

3.2 @XxlRpcReference

带有 XxlRpcReference 注解接口,客户端都没有显示的实现类, 因此需要依赖 XxlRpcReference 注解,代理生成一个具体的实现类,这个实现类在被调用具体方法的时候,能够映射成远程方法请求,并将结果再返回

整个调用可以分为以下两个核心点:

  • 代理对象生成
  • 远程方法调用

梳理整个过程,大致如下:

接下来重点分析生成可远程调用对象的代码。

  1. XxlRpcSpringInvokerFactory 为入口类。在这个类,需要触发生成 @XxlRpcReference 的具体代理对象。

下图是启动的时候去填充 @XxlRpcReference 描述的字段代码

比如 DemoService 被 @XxlRpcReference 描述, 就需要生成具体的代理对象

JAVA 复制代码
@Controller
public class IndexController {
   
   @XxlRpcReference
   private DemoService demoService;
   ......
}

代码如下:给所有 @XxlRpcReference 描述的属性字段生成具体的代理对象

JAVA 复制代码
public class XxlRpcSpringInvokerFactory implements InitializingBean, DisposableBean, BeanFactoryAware, InstantiationAwareBeanPostProcessor {
    .......
    // spring 后置处理接口,填充属性字段
    @Override
    public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException {
        // collection
        final Set<String> serviceKeyList = new HashSet<>();

        // parse XxlRpcReferenceBean 
        // 处理属性字段
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                if (field.isAnnotationPresent(XxlRpcReference.class)) {
                    // valid
                    Class iface = field.getType();
                    if (!iface.isInterface()) {
                        throw new XxlRpcException("XXL-RPC, reference(XxlRpcReference) must be interface.");
                    }
                  
                    XxlRpcReference rpcReference = field.getAnnotation(XxlRpcReference.class);

                    // init reference bean
                    XxlRpcReferenceBean referenceBean = new XxlRpcReferenceBean();
                   ......
                   referenceBean.setInvokerFactory(xxlRpcInvokerFactory);

                    // get proxyObj 生成代理对象
                    Object serviceProxy = null;
                    try {
                        // 核心,生成远程调用的代理对象 
                        serviceProxy = referenceBean.getObject();
                    } catch (Exception e) {
                        throw new XxlRpcException(e);
                    }

                    // set bean 反射设置属性
                    field.setAccessible(true);
                    field.set(bean, serviceProxy);
                    ......
                }
            }
        });
}
  1. serviceProxy = referenceBean.getObject(); 包装成远程调用的代理对象

整个客户端的核心代码,有必要认真阅读一下

阅读前需要先理解 callType 有四种方式,而 referenceBean.getObject() 实现了下面 4 种方式的调用:

callType 描述
SYNC 同步等返回结果
FUTURE future 获取结果
CALLBACK 返回结果进行方法回调
ONEWAY 不关注返回结果

由于 NIO 是异步通讯模型,调用线程并不会阻塞获取调用结果,因此,XXL-RPC 实现了在异步通讯模型上的同步调用,即 "sync-over-async" 。即如何恰当地返回异步结果,下图是作者设计的思路,即通过 wait 和 notify 来实现异步结果的获取。

有了上面的理解,我们再阅读核心的生成代理对象的代码就比较容易了。

下面生成代理的代码也是围绕如何进行远程调用(省略参数封装、泛化调用), 逻辑并不复杂

Java 复制代码
public Object getObject() throws Exception {
    ......
   // newProxyInstance 生成代理对象
   return Proxy.newProxyInstance(Thread.currentThread()
         .getContextClassLoader(), new Class[] { iface },
         new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    .......
                    // send  核心,发送远程调用,对应四种方式
               if (CallType.SYNC == callType) {
                  // future-response set
                  XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
                  try {
                     // do invoke 
                     clientInstance.asyncSend(finalAddress, xxlRpcRequest);
                     // future get
                     XxlRpcResponse xxlRpcResponse = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
                     if (xxlRpcResponse.getErrorMsg() != null) {
                        throw new XxlRpcException(xxlRpcResponse.getErrorMsg());
                     }
                     return xxlRpcResponse.getResult();
                  } catch (Exception e) {
                     .....
                  } finally{
                     // future-response remove
                     futureResponse.removeInvokerFuture();
                  }
               } else if (CallType.FUTURE == callType) {
                  // future-response set
                  XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
                           try {
                     // invoke future set
                     XxlRpcInvokeFuture invokeFuture = new XxlRpcInvokeFuture(futureResponse);
                     XxlRpcInvokeFuture.setFuture(invokeFuture);

                               // do invoke
                     clientInstance.asyncSend(finalAddress, xxlRpcRequest);

                               return null;
                           } catch (Exception e) {
                     logger.info(">>>>>>>>>>> XXL-RPC, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);

                     // future-response remove
                     futureResponse.removeInvokerFuture();
                    ......
               } else if (CallType.CALLBACK == callType) {
                  // get callback
                  XxlRpcInvokeCallback finalInvokeCallback = invokeCallback;
                  XxlRpcInvokeCallback threadInvokeCallback = XxlRpcInvokeCallback.getCallback();
                  if (threadInvokeCallback != null) {
                     finalInvokeCallback = threadInvokeCallback;
                  }
                  if (finalInvokeCallback == null) {
                     throw new XxlRpcException("XXL-RPC XxlRpcInvokeCallback(CallType="+ CallType.CALLBACK.name() +") cannot be null.");
                  }
                  // future-response set
                  XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, finalInvokeCallback);
                  try {
                     clientInstance.asyncSend(finalAddress, xxlRpcRequest);
                  } catch (Exception e) {
                    .......
                  }
                  return null;
               } else if (CallType.ONEWAY == callType) {
                  clientInstance.asyncSend(finalAddress, xxlRpcRequest);
                 return null;
                ....
               }
            }
         });
}

关于如何实现 "sync-over-async" 细节参考 3.3 sync 和 future 模式。

最终的远程通信还是使用 Netty。

  1. clientInstance.asyncSend(finalAddress, xxlRpcRequest);
jAVA 复制代码
@Override
public void send(XxlRpcRequest xxlRpcRequest) throws Exception {
    this.channel.writeAndFlush(xxlRpcRequest).sync();
}

做一个简单的总结:

  1. XxlRpcSpringInvokerFactory 在启动的时候,实现了 postProcessAfterInstantiation 接口,在对象填充属性前调用,这个时候对拥有 XxlRpcReference 属性字段的对象做该字段的填充,即生成具体的代理对象
  2. XxlRpcReferenceBean 代理核心类,最核心的作用就是包装,远程调用。
  3. 远程调用提供了四种类型。核心是实现 "sync-over-async"

基本上我们已经摸清楚 provider/服务端 和 consumer/Invoker/客户端的实现和调用过程。

下面再追加 sync 和 future 模式的理解。

3.3 sync 和 future 模式

通过 wait() 和 notifyAll() 来处理异步结果,作者的解释,可以结合图进行理解

  • 1、consumer发起请求:consumer 会根据远程服务的 stub 实例化远程服务的代理服务,在发起请求时,代理服务会封装本次请求相关底层数据,如服务iface、methods、params等等,然后将数据经过 serialization 之后发送给provider;
  • 2、provider 接收请求:provider 接收到请求数据,首先会deserialization获取原始请求数据,然后根据 stub 匹配目标服务并调用;
  • 3、provider 响应请求:provider 在调用目标服务后,封装服务返回数据并进行serialization,然后把数据传输给 consumer;
  • 4、consumer 接收响应:consumer 接收到相应数据后,首先会deserialization 获取原始数据,然后根据 stub 生成调用返回结果,返回给请求调用处。结束。

get() 等待获取结果对象

csharp 复制代码
@Override
public XxlRpcResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
   if (!done) {
     // lock.wait() 等待集合
      synchronized (lock) {
         try {
            if (timeout < 0) {
               lock.wait();
            } else {
               long timeoutMillis = (TimeUnit.MILLISECONDS==unit)?timeout:TimeUnit.MILLISECONDS.convert(timeout , unit);
               lock.wait(timeoutMillis);
            }
         } catch (InterruptedException e) {
            throw e;
         }
      }
   }
   ......
   return response;
}

调用成功后,在 notifyAll()

JAVA 复制代码
// ---------------------- for invoke back ----------------------

public void setResponse(XxlRpcResponse response) {
   this.response = response;
   // 接触等待
   synchronized (lock) {
      done = true;
      lock.notifyAll();
   }
}

设计还是比较精彩。

下面是 future 方式获取结果对象的方式。

Java 复制代码
// invoke future set
XxlRpcInvokeFuture invokeFuture = new XxlRpcInvokeFuture(futureResponse);
XxlRpcInvokeFuture.setFuture(invokeFuture);
// do invoke
clientInstance.asyncSend(finalAddress, xxlRpcRequest);

重写了 future 。 其中 get 也是在复用上面能力。

Java 复制代码
public class XxlRpcInvokeFuture implements Future {

    .......
    @Override
    public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        try {
            // 也是使用 await 和 notifyAll
            XxlRpcResponse xxlRpcResponse = futureResponse.get(timeout, unit);
            if (xxlRpcResponse.getErrorMsg() != null) {
                throw new XxlRpcException(xxlRpcResponse.getErrorMsg());
            }
            return xxlRpcResponse.getResult();
        } finally {
            stop();
        }
    }
    .......
}

通过上面两个注解过程的讲解,RPC 的调用过程就已经讲完了。

接下来再稍微分析一下服务注册与发现(上面的内容是核心重点)

📚四、服务中心系统设计(粗讲)

内部通过广播机制,集群节点实时同步服务注册信息,确保一致。客户端借助 long pollong 实时感知服务注册信息,简洁、高效;

4.1 服务注册

服务端的调用是在 Netty 启动后注册的回调事件进行服务注册的。

JAVA 复制代码
serverInstance.setStartedCallback(new BaseCallback() {     // serviceRegistry started
   @Override
   public void run() throws Exception {
      // start registry 注册信息
      if (serviceRegistry != null) {
         registerInstance = serviceRegistry.newInstance();
         registerInstance.start(serviceRegistryParam);
         if (serviceData.size() > 0) {
            // XxlRpcService 所有的接口。进行注册
            registerInstance.registry(serviceData.keySet(), registryAddress);
         }
      }
   }
});
serverInstance.setStopedCallback(new BaseCallback() {     // serviceRegistry stoped
   @Override
   public void run() {
      // stop registry 
      if (registerInstance != null) {
         if (serviceData.size() > 0) {        
            registerInstance.remove(serviceData.keySet(), registryAddress);
         }
         registerInstance.stop();
         registerInstance = null;
      }
   }
});

然后在内部启动一个独立的线程,每隔 10 秒进行,上报注册信息

JAVA 复制代码
// registry thread
registryThread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (!registryThreadStop) {
            try {
                if (registryData.size() > 0) {
                    // 使用 http 上报元数据信息
                    boolean ret = registryBaseClient.registry(new ArrayList<XxlRpcAdminRegistryDataItem>(registryData));
                    logger.debug(">>>>>>>>>>> xxl-rpc, refresh registry data {}, registryData = {}", ret?"success":"fail",registryData);
                }
            } catch (Exception e) {
                 ......
            try {
                // 休息 10 s
                TimeUnit.SECONDS.sleep(10);
            } catch (Exception e) {
             ......
            }
        }
    }
});

服务提供者在启动 Netty 后,上报数据到服务注册中心。

使用的是 http 往注册中心进行上报(注册过程)。

4.2 服务发现

客户端调用,就是拉取注册中心的数据,选择一个合适的地址进行调用(负载均衡算法)

Java 复制代码
if (finalAddress==null || finalAddress.trim().length()==0) {
   if (invokerFactory!=null && invokerFactory.getRegister()!=null) {
      // 拉取数据
      String serviceKey = XxlRpcProviderFactory.makeServiceKey(className, varsion_);
      TreeSet<String> addressSet = invokerFactory.getRegister().discovery(serviceKey);
      // 负载均衡调用
      if (addressSet==null || addressSet.size()==0) {
         // pass
      } else if (addressSet.size()==1) {
         finalAddress = addressSet.first();
      } else {
         finalAddress = loadBalance.xxlRpcInvokerRouter.route(serviceKey, addressSet);
      }

   }
}

另外在程序中有一个独立的线程在不断地进行数据的定时刷新 com.xxl.rpc.core.registry.impl.xxlrpcadmin.XxlRpcAdminRegistryClient#XxlRpcAdminRegistryClient。 核心代码 refreshDiscoveryData

typescript 复制代码
discoveryThread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (!registryThreadStop) {
           ......
            } else {
                try {
                     .......
                    // 定时更新数据。(10s) 
                    refreshDiscoveryData(discoveryData.keySet());
                } catch (Exception e) {
                 .......
        }
    }
});

在 xxl-rpc-admin 模块中 com.xxl.rpc.admin.service.impl.XxlRpcRegistryServiceImpl#afterPropertiesSet 有几个线程在不断刷新数据和广播消息,实现服务治理,限于篇幅就不再深入。

负载均衡、泛化调用、服务监控就不一一展开了,感兴趣的可以阅读代码进行研究。

到这里,对 RPC 的整体实现已经有了一个清晰的认识了。再来看看作者这张架构图就不难理解了。

✒️五、总结

XXL-RPC 是比较小而美的 RPC 框架,很轻量、简单; 对于 RPC 初学者非常的友好。 通过学习这个框架再去了解 Dubbo 就会比较轻松。

XXL-RPC 代码结构比较清晰, 底层 Netty 也是模板式的代码,XXL-RPC 作者对线程的理解也非常到位,值得推荐。

因为轻量,所以很多细节并没有考虑周全,比如线程池的优雅关闭等,这些问题,我们学习 Dubbo 的时候再细说,本文到此结束。

详细地址 XXL-RPC。

相关推荐
Cikiss7 分钟前
微服务实战——SpringCache 整合 Redis
java·redis·后端·微服务
Cikiss9 分钟前
微服务实战——平台属性
java·数据库·后端·微服务
OEC小胖胖22 分钟前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617621 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端
计算机学姐1 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
Yvemil72 小时前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
2401_854391082 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
虽千万人 吾往矣2 小时前
golang gorm
开发语言·数据库·后端·tcp/ip·golang
这孩子叫逆3 小时前
Spring Boot项目的创建与使用
java·spring boot·后端
coderWangbuer4 小时前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql