RPC 原理:Dubbo为了偷懒而存在的中间商
Dubbo 的核心使命只有一个让程序员在调用远程方法时产生一种“我就在本机内存里调个函数”的错觉。为了实现这个巨大的谎言Dubbo 在底层搞了三场惊天动地的“魔术”。咱们这就钻进 JVM 和网卡的缝隙里看看它到底是怎么忽悠你的。动态代理、序列化和Netty一次“精心包装的跨国快递”。你消费者想从远方的仓库提供者拿东西但你不需要亲自去而是通过一个复杂的物流系统。下面咱们将结合你提到的动态代理、序列化和Netty三大核心技术为你深度拆解 Dubbo 的工作全流程。1. 动态代理看不见的“替身”“只会传话”的假对象你以为你拿到的是真佛真实的 Service 实现类其实你拿到的只是一个开光的牌位代理对象。—— 让你感觉不到是在远程调用当你写代码demoService.sayHello(world)时你以为你在调用本地的对象但实际上这个对象是 Dubbo 给你变出来的“幻影”。Java 是一门死板的语言你不能直接对着空气喊“喂那边的UserService给我查个用户” Java 要求你必须有一个对象才能点它的 method。原理Dubbo 在启动时利用Javassist(直接生成字节码比 JDK 原生反射快)或JDK 动态代理技术为远程接口生成了一本地代理对象Proxy当场捏造了一个实现了接口的“替身”。作用这个代理对象拥有和远程服务一模一样的接口。当你调用代理对象的sayHello方法时它不会执行真正的业务逻辑而是拦截这次调用。它会把你调用的方法名sayHello、参数类型String、参数值world以及版本号等信息打包成一个标准的请求对象RpcInvocation。底层视角这就像你给秘书代理下指令秘书记录下来准备发传真而不是自己去干活。当你调用userService.getUser(1001)时实际上发生了什么拦截这个“替身”根本没有业务逻辑它唯一的任务就是拦截你的调用。打包它迅速把你调用的方法名getUser、参数类型Long、参数值1001甚至连你是谁Request ID都记下来塞进一个叫RpcInvocation的盒子里。潜台词它在你耳边说“老板这活儿我干不了我得把这盒子发给远在千里之外的真正干活的人。”代码视角的真相你以为是user userService.getUser(1001); // 简单优雅实际上是// 伪代码这就是代理内部干的事 RpcInvocation invocation new RpcInvocation(getUser, new Object[]{1001}); Result result invoker.invoke(invocation); // 扔给网络层去跑2. 序列化数据的“压缩与装箱”把对象“挫骨扬灰”那个RpcInvocation盒子还在内存里它是 Java 对象有引用地址有堆内存结构。网线可不认识这些网线只认0和1。—— 把 Java 对象变成二进制流所以必须转换成二进制字节数组。这就是序列化的过程要把内存里的复杂对象图“拍扁”成二进制流默认协议Dubbo 默认使用Hessian2序列化协议。Hessian2 就像一个无情的粉碎机它遍历你的对象把字段名、字段值、类型信息全部转换成字节数组byte[]。Dubbo 协议头光有序列化数据还不够Dubbo 还要在这个字节流前面加个“快递单”。这就是 Dubbo 协议的 Header魔数0xdabb、请求 ID、序列化方式标识等。为什么是 Hessian2体积小比 Java 原生序列化小很多节省带宽。速度快编解码效率高。跨语言虽然 Dubbo 主要是 Java但 Hessian2 支持多语言交互。其他选择Dubbo 也支持 JSON、Protostuff、Kryo、Fastjson2 等你可以根据性能需求切换。流程代理生成的RpcInvocation对象进入序列化器。被转换成二进制的byte[]。加上 Dubbo 协议的魔数、标志位、请求 ID 等头信息封装成完整的数据包。底层真相你的对象在过安检。Hessian2 把它脱光了检查一遍然后压成一个压缩包贴上标签“这是第 10086 号请求去执行getUser”。3. 网络传输 (Netty)高速公路上的“异步飙车”数据包好了怎么发过去如果是传统的 BIOBlocking I/O那就是每来一个请求开一个线程线程等着结果回来。如果并发一高你的服务器线程池瞬间爆炸CPU 全花在上下文切换上啥也别想干—— 高效的数据搬运工。一旦数据打包完成就需要通过网络发送。Dubbo 底层默认使用Netty框架进行 NIO非阻塞 I/O通信。连接模型单一长连接HTTP/1.1 以前每次调用都要 TCP 握手、挥手。就像每次寄信都要重新修一条邮路蠢透了。Dubbo 默认采用单一长连接策略。Consumer 和 Provider 之间建立一条 TCP 连接后就死都不放手会一直保持心跳检测所有的请求都在这条管道里排队发送Pipeline后续的所有请求都通过这条连接发送。心跳检测为了防止这条管子太久没用被防火墙掐断Dubbo 会定期发个“心跳包”Ping/Pong告诉对方“我还活着别杀我。”优势避免了频繁建立和断开 TCP 连接三次握手带来的巨大开销非常适合内部微服务间高频、小数据的调用场景。I/O 模型NIO 异步非阻塞Dubbo 利用 Netty 的 Reactor 模型Boss 线程组负责连接Worker 线程组负责读写。包工头与搬运工BossGroup包工头只负责接客。客户端连进来Boss 说“好嘞你去找 Worker 玩吧。” Boss 不干活只管建立连接TCP 三次握手。WorkerGroup搬运工负责真正的读写。它们通过Selector多路复用器轮询成千上万个连接。关键点一个线程可以管理几万个连接。只有当连接真的有数据要读/写时线程才会介入。其他时间线程在睡觉或处理别的连接绝不空转。异步发送Consumer 发送请求后不会阻塞当前线程傻等结果而是立即返回一个Future对象然后继续处理其他任务。你的线程拿着 Future 继续干活或者挂起等待get()。Netty 线程在后台监听响应。一旦 Provider 的结果回来了Netty 根据 Request ID 找到对应的 Future把结果填进去唤醒你的线程。回调机制当 Provider 处理完返回结果时Netty 会通过回调通知 ConsumerConsumer 再唤醒等待的线程或直接处理结果。底层视角你在餐厅点菜。服务员Netty记下菜单请求给你个号牌Future然后立马去接待下一桌。厨房Provider做好了喊一声服务员再根据你的号牌把菜端给你。你不用站在厨房门口傻等。4. 服务端处理镜像般的逆向过程Provider 端的 Netty Server 接收到二进制数据包后开始逆向操作反序列化Netty 读取字节流利用 Hessian2 将二进制还原为RpcInvocation对象。定位服务根据请求中的接口名、版本、方法名找到对应的真实实现类Invoker。反射调用利用 Java 反射机制调用真实的业务方法sayHello(world)得到结果。响应将结果再次序列化通过 Netty 原路发回给 Consumer。全景流转一次 RPC 的“受难记”让我们把所有环节串起来看看一次调用在底层经历了什么消费者发起调用你调用了demoService.sayHello(dubbo)。代理层拦截Javassist 生成的代理类截获请求封装成RpcInvocation对象。集群容错Dubbo 看了看配置发现你有 3 个提供者。根据负载均衡策略比如随机挑了一个 IP192.168.1.20:20880。序列化Hessian2 把RpcInvocation变成二进制流加上 Dubbo 协议头魔数0xdabb。Netty 发送Netty 的 Channel 获取到这个字节流通过 TCP 长连接异步写入内核缓冲区推送到网卡。网络传输数据包经过交换机、路由器到达 Provider 机器。Provider 接收Provider 的 Netty Server 监听到数据包读取字节流。反序列化Hessian2 把二进制流还原成RpcInvocation。反射调用Dubbo 根据接口名和方法名找到本地真实的DemoServiceImpl利用反射或者生成的优化代码执行sayHello(dubbo)。原路返回结果被封装、序列化、通过网络发回 Consumer。唤醒Consumer 收到响应反序列化更新 Future 状态你的主线程从future.get()醒来拿到结果。全景流程图一次 RPC 的生命周期为了更直观地理解整合成一张全链路图服务提供者是如何启动并暴露服务的服务提供者Provider的启动与暴露本质上是一场在 Spring 容器生命周期内精心编排的“三幕剧”。它的核心目标是将你编写的 Java 接口变成一个可以通过网络被远程调用的服务。整个过程可以概括为Spring 容器启动 - Dubbo 组件扫描与初始化 - 服务参数确定 - 启动网络服务器 - 向注册中心注册。第一幕春雷惊蛰万物萌动 (Spring 容器启动)一切的起点都源于 Spring 容器的初始化。Dubbo 巧妙地利用了 Spring 的生命周期回调机制将自己的启动流程无缝嵌入其中。触发机关当你在 Spring Boot 应用主类上使用EnableDubbo或DubboComponentScan注解时就相当于按下了启动按钮。监听事件Dubbo 会注册一个核心的监听器——DubboDeployApplicationListener。这个监听器就像一个忠实的哨兵时刻等待着 Spring 容器发出的ContextRefreshedEvent事件。大幕拉开一旦 Spring 容器完成所有 Bean 的加载和刷新就会广播ContextRefreshedEvent事件。DubboDeployApplicationListener监听到该事件后便会触发 Dubbo 自身的部署启动器 (DefaultModuleDeployer.start())正式拉开了服务暴露的序幕。第二幕排兵布阵整装待发 (服务配置与封装)在这一阶段Dubbo 的主要任务是“清点人马”即扫描并封装所有需要暴露的服务。扫描服务实现类Dubbo 会根据配置的扫描路径如dubbo.scan.base-packages找到所有被DubboService注解标记的服务实现类。封装服务配置对于每一个找到的服务Dubbo 会创建一个ServiceConfig对象。这个对象是服务的“身份证”和“档案袋”它通过一套优先级规则配置中心 DubboService注解 application.yml配置文件收集并合并所有配置信息最终形成一个包含接口、实现类、版本、分组、超时时间等完整信息的配置对象。生成 Invoker紧接着Dubbo 会通过一个代理工厂 (ProxyFactory)将你的服务实现类和ServiceConfig中的元数据包装成一个Invoker对象。Invoker是 Dubbo 内部对可执行单元的抽象你可以把它理解为一个已经准备好、只待网络请求触发的“本地方法调用器”。第三幕开疆拓土扬名立万 (服务暴露与注册)这是最激动人心的一步服务将从内存中的对象转变为网络上可访问的实体。这个过程由Protocol协议层主导分为本地暴露和远程注册两个关键环节。环节一本地暴露 —— 启动网络服务器职责Protocol接口会调用其具体实现如DubboProtocol的export()方法。行动这个方法的核心任务是启动一个网络服务器来监听指定的端口默认是 20880。由于 Dubbo 默认使用 Netty 作为通信框架所以这里实际上是在启动一个 Netty Server。结果此时你的服务已经在本地 20880 端口上“安营扎寨”准备接收来自网络的二进制数据流了。同时Dubbo 会将之前生成的Invoker和一个代表服务的 URL 关联起来保存在一个本地的注册表 (ProviderConsumerRegTable) 中。这样当网络请求到达时服务器就能根据 URL 找到对应的Invoker来执行业务逻辑。环节二远程注册 —— 向世界宣告存在服务在本地启动后还需要让潜在的调用者Consumer知道它在哪里。这就是注册中心发挥作用的时候。Dubbo 3.0 在此引入了革命性的变化。Dubbo 2.x 的接口级注册方式服务提供者将自己的完整 URL包含 IP、端口、协议、方法等信息直接注册到注册中心如 Zookeeper的特定路径下例如/dubbo/com.example.DemoService/providers。痛点当一个应用提供几十个甚至上百个接口时注册中心会存储海量的节点数据。任何一次服务上下线都会导致大量数据的推送给注册中心带来巨大压力。Dubbo 3.0 的应用级注册 (核心变革)方式服务提供者不再关心自己有多少个接口而是以“应用”为单位进行注册。它会向注册中心注册一个包含应用名、IP、端口等实例信息的ServiceInstance对象存储路径类似于/services/your-application-name。优势无论应用内部有多少个服务接口它在注册中心只对应一个实例节点。这极大地减少了注册中心的数据量和变更频率提升了系统的可扩展性并能更好地与 Kubernetes、Spring Cloud 等生态互通。如何解决“消费者如何发现接口”的问题为了兼容应用级注册Dubbo 3.0 引入了两个配套机制接口-应用映射将接口名 - 应用名的映射关系注册到 Zookeeper 的/dubbo/mapping路径下。消费者通过这个映射就能知道想调用某个接口应该去找哪个应用。元数据中心服务提供者会将自己的详细接口定义方法、参数、返回值等作为元数据存储在独立的元数据中心可以是本地、Zookeeper 或 Nacos 等。消费者获取到应用实例后可以去元数据中心拉取详细的接口配置。双注册模式为了平滑迁移Dubbo 3.0 默认开启了“双注册”模式即同时将服务以接口级和应用级的形式注册到注册中心确保了与旧版本 Dubbo 消费者的兼容性。总结Dubbo 之所以快且强不是因为它发明了新的网络协议而是因为它把这些复杂的网络通信Netty、对象转换Serialization、服务发现Registry封装得滴水不漏让你产生了一种“分布式系统其实很简单”的错觉透明化用动态代理骗过了开发者让远程调用像本地调用一样自然。高效化用Netty NIO 长连接解决了高并发下的网络连接瓶颈。紧凑化用Hessian2 序列化保证了数据传输的体积最小、速度最快。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2550191.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!