研究一款入门级别的 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。

相关推荐
苏三的开发日记4 分钟前
linux搭建hadoop服务
后端
sir76119 分钟前
Redisson分布式锁实现原理
后端
大学生资源网40 分钟前
基于springboot的万亩助农网站的设计与实现源代码(源码+文档)
java·spring boot·后端·mysql·毕业设计·源码
苏三的开发日记1 小时前
linux端进行kafka集群服务的搭建
后端
苏三的开发日记1 小时前
windows系统搭建kafka环境
后端
爬山算法1 小时前
Netty(19)Netty的性能优化手段有哪些?
java·后端
Tony Bai1 小时前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
想用offer打牌2 小时前
虚拟内存与寻址方式解析(面试版)
java·后端·面试·系统架构
無量2 小时前
AQS抽象队列同步器原理与应用
后端
9号达人2 小时前
支付成功订单却没了?MyBatis连接池的坑我踩了
java·后端·面试