1. MMU Notifier的核心背景解析在计算机体系结构中内存管理单元MMU作为CPU与物理内存之间的关键桥梁负责虚拟地址到物理地址的转换工作。而MMU Notifier机制的出现本质上是为了解决动态内存映射场景下的协同问题。当内核或驱动需要修改页表映射关系时如KVM虚拟机内存热插拔、GPU显存回收等场景必须确保所有相关组件都能及时感知到映射变化。传统的内存管理存在一个典型痛点假设某个设备驱动通过DMA直接访问用户空间内存而此时用户进程突然调用mprotect()修改了内存权限如果没有通知机制设备可能继续访问已变为只读的内存区域导致数据不一致甚至系统崩溃。MMU Notifier通过回调机制在页表更新前后触发预先注册的钩子函数为这类问题提供了标准化解决方案。2. 问题域与典型应用场景2.1 硬件加速器内存同步问题现代异构计算系统中GPU、FPGA等加速器常通过DMA直接访问主机内存。当CPU侧修改页表属性或进行内存迁移时必须确保加速器能及时更新其内部映射表。以NVIDIA GPU驱动为例其实现的mmu_notifier_ops会监听内存区域(unmap_start - invalidate_range_end)事件在回调中同步更新GPU的页表项PTE。2.2 虚拟化场景下的内存热插拔在KVM虚拟化环境中当宿主机需要回收分配给虚拟机的内存时如通过virtio-balloon驱动qemu进程会调用madvise(MADV_DONTNEED)。此时MMU Notifier会触发kvm_mmu_notifier_invalidate_range()回调使KVM模块立即刷新对应的影子页表避免虚拟机继续访问已被回收的物理页面。2.3 用户态驱动与RDMA技术RDMA如InfiniBand需要长期保持内存区域的物理地址稳定。当用户调用mlock()锁定内存后内核会通过mmu_notifier_change_pte()通知RDMA驱动更新内存注册信息。实测数据显示这种机制能将RDMA内存注册延迟从毫秒级降低到微秒级。3. 技术实现深度# 1. 概述本文分享服务引用的最后一部分创建 Invoker 对象。在 《精尽 Dubbo 源码分析 —— 服务引用一之本地引用Injvm》 和 《精尽 Dubbo 源码分析 —— 服务引用二之远程引用Dubbo》 中我们已经分享本地引用和远程引用的 Invoker 创建的过程那么本文是分享远程引用的剩余部分注册中心引用服务引用启动时检查泛化引用2. createInvokerForRemote本文涉及的远程引用服务的代码在#createInvokerForRemote(map, url)方法中如下/** * 创建远程服务的 Invoker 对象 * * param map 集合 * param url URL * param T 泛型 * return Invoker 对象 */ SuppressWarnings(unchecked) private T InvokerT createInvokerForRemote(MapString, String map, URL url) { // 省略【服务引用启动时检查】和【泛化引用】相关代码 // 注册中心引用 if (url.getParameter(registry, false)) { // 创建 RegistryProtocol 对象并调用 refer 方法生成 Invoker 对象 return registryProtocol.refer(interfaceClass, url); } // 省略【远程引用-Dubbo】相关代码 }3. 注册中心引用注册中心引用的服务实际上不是真实的服务提供者而是注册中心的引用。通过注册中心可以动态发现服务提供者。因此注册中心引用的服务调用方式相对复杂整体如下图在Protocol#refer(ClassT type, URL url)方法中调用RegistryProtocol#refer(...)方法引用注册中心。在RegistryProtocol#refer(...)方法中首先调用#getRegistry(...)方法创建注册中心对象。而后调用#doRefer(...)方法创建注册中心引用的 Invoker 对象。在#doRefer(...)方法中创建注册中心目录RegistryDirectory 对象并设置注册中心。向注册中心注册自己服务消费者。向注册中心订阅服务提供者 路由 配置维度的数据。创建 Invoker 代理对象。3.1 RegistryProtocol3.1.1 refer本文涉及的RegistryProtocol#refer(ClassT type, URL url)方法代码如下/** * 注册中心协议对象 */ private RegistryProtocol registryProtocol; Override SuppressWarnings(unchecked) // 泛型 public T InvokerT refer(ClassT type, URL url) throws RpcException { // 获得注册中心 URL url url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); // 获得注册中心 Registry registry registryFactory.getRegistry(url); // 普通 Dubbo 服务引用 if (RegistryService.class.equals(type)) { return proxyFactory.getInvoker((T) registry, type, url); } // groupa,b or group* // 通过逗号分割将服务引用数组聚合为一个返回 Invoker MapString, String qs StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); String group qs.get(Constants.GROUP_KEY); if (group ! null group.length() 0) { if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length 1 || *.equals(group)) { return doRefer(getMergeableCluster(), registry, type, url); } } // 执行服务引用 return doRefer(cluster, registry, type, url); }第 5 至 6 行修改protocol为registry的协议即dubbo改成registry。注意此处的url的protocol是dubbo通过registry参数设置注册中心的地址。第 8 行调用RegistryFactory#getRegistry(url)方法获得注册中心 Registry 对象。第 10 至 12 行若引用服务是com.alibaba.dubbo.registry.RegistryService类型调用ProxyFactory#getInvoker(...)方法返回 Invoker 对象。一般情况下我们不会引用RegistryService服务。第 15 至 22 行通过group的配置决定是否调用#doRefer(Cluster, Registry, ClassT, URL)聚合引用服务提供者。详细解析见 《精尽 Dubbo 源码分析 —— 集群容错六之 Configurator 实现》 的 「4. MergeableCluster」 部分。第 24 行调用#doRefer(cluster, registry, type, url)方法执行服务引用。注意此处传入的cluster对象是clusteravailable的 Cluster 实现类。3.1.2 doRefer#doRefer(cluster, registry, type, url)方法执行服务引用。代码如下private T InvokerT doRefer(Cluster cluster, Registry registry, ClassT type, URL url) { // 创建 RegistryDirectory 对象并设置注册中心 RegistryDirectoryT directory new RegistryDirectoryT(type, url); directory.setRegistry(registry); directory.setProtocol(protocol); // 所有 注册的 和 订阅的都绑定到这个路径上 URL subscribeUrl new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters()); // 向注册中心注册自己服务消费者 if (!Constants.ANY_VALUE.equals(url.getServiceInterface()) url.getParameter(Constants.REGISTER_KEY, true)) { registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY, Constants.CHECK_KEY, String.valueOf(false))); } // 向注册中心订阅服务提供者 路由 配置维度的数据 directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY , Constants.CONFIGURATORS_CATEGORY , Constants.ROUTERS_CATEGORY)); // 通过集群合并多个服务提供者为一个 Invoker InvokerT invoker cluster.join(directory); // 向本地注册表注册消费者 ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory); return invoker; }第 3 至 5 行创建 RegistryDirectory 对象并设置注册中心到它的属性。关于 RegistryDirectory 在 《精尽 Dubbo 源码分析 —— 集群容错三之 Directory 实现》 有详细解析。第 7 至 8 行创建服务消费者的 URL 对象。第 10 至 13 行向注册中心注册自己服务消费者。第 15 至 18 行向注册中心订阅服务提供者 路由 配置维度的数据。通过这样的方式注册中心会推送这三个维度的数据给 RegistryDirectory 从而实时的更新Directory 的动态数据。第 20 行创建 Invoker 对象。此处使用clusteravailable的 Cluster 实现类合并多个服务提供者为一个 Invoker 对象。关于 AvailableCluster 在 《精尽 Dubbo 源码分析 —— 集群容错四之 LoadBalance 实现》 有详细解析。第 22 行向本地注册表注册消费者。在 《精尽 Dubbo 源码分析 —— 注册中心一之抽象 API》 的 「5. ProviderConsumerRegTable」 有详细解析。第 23 行返回 Invoker 对象。3.2 流程整体流程如下4. 服务引用启动时检查在引用服务时有一个配置项check用于配置是否检查该服务是否可用。若开启当服务不可用时会直接抛出异常从而阻止 Dubbo 应用初始化完成。代码如下/** * 创建远程服务的 Invoker 对象 * * param map 集合 * param url URL * param T 泛型 * return Invoker 对象 */ SuppressWarnings(unchecked) private T InvokerT createInvokerForRemote(MapString, String map, URL url) { // 服务引用启动时检查 Boolean c check; if (c null map ! null) { c Boolean.parseBoolean(map.get(Constants.CHECK_KEY)); } if (c null) { c true; // default true } if (c !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol())) { // 非消费者协议 if (map ! null map.containsKey(Constants.LAZY_CONNECT_KEY)) { c Boolean.parseBoolean(map.get(Constants.LAZY_CONNECT_KEY)); } else { c true; } } if (c) { // 可用性检查 if (logger.isInfoEnabled()) { logger.info(Check init status of service interfaceName); } // 循环引用验证服务是否已经暴露 if (!url.getParameter(Constants.REGISTRY_KEY, false)) { // 非注册中心直连服务提供者验证服务是否可用 DubboProtocol.getDubboProtocol().refer(interfaceClass, url); } else { // 注册中心通过注册中心判断服务是否可用 Registry registry registryFactory.getRegistry(url); if (RegistryService.class ! interfaceClass) { ListURL us registry.lookup(url); if (us null || us.isEmpty()) { throw new IllegalStateException(No such any registry to reference interfaceName on the consumer NetUtils.getLocalHost() use dubbo version Version.getVersion() , please config dubbo:registry address\...\ / to your spring config.); } for (URL u : us) { String protocol u.getProtocol(); if (!Constants.REGISTRY_PROTOCOL.equals(protocol)) { DubboProtocol.getDubboProtocol().refer(interfaceClass, u); } } } } } // 省略【注册中心引用】和【泛化引用】相关代码 }第 11 至 19 行获得check配置项。默认情况下开启检查。第 20 至 28 行当开启检查时进一步检查。第 32 至 34 行非注册中心直连服务提供者验证服务是否可用。第 35 至 49 行注册中心通过注册中心判断服务是否可用。第 39 至 44 行调用Registry#lookup(url)方法获得对应的服务提供者 URL 数组。第 45 至 48 行循环服务提供者 URL 数组调用DubboProtocol#refer(interfaceClass, url)方法验证服务是否可用。5. 泛化引用泛化引用是指在引用服务时不需要服务消费者依赖服务提供者的接口。例如// 引用远程服务 ReferenceConfigGenericService reference new ReferenceConfigGenericService(); // 该实例很重量里面封装了所有与注册中心及服务提供方连接请缓存 reference.setApplication(new ApplicationConfig(generic-demo-consumer)); reference.setRegistry(new RegistryConfig(zookeeper://127.0.0.1:2181)); reference.setInterface(com.alibaba.dubbo.demo.DemoService); // 弱类型接口名 reference.setGeneric(true); // 声明为泛化接口 // 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用 GenericService genericService reference.get(); // 基本类型以及Date,List,Map等不需要转换直接调用 Object result genericService.$invoke(sayHello, new String[] {java.lang.String}, new Object[] {world});通过GenericService代替DemoService从而避免引入依赖。通过GenericService#$invoke(method, parameterTypes, args)方法实现对远程DemoService的调用。泛化引用的代码在#createInvokerForRemote(map, url)方法中如下/** * 创建远程服务的 Invoker 对象 * * param map 集合 * param url URL * param T 泛型 * return Invoker 对象 */ SuppressWarnings(unchecked) private T InvokerT createInvokerForRemote(MapString, String map, URL url) { // 省略【服务引用启动时检查】和【注册中心引用】相关代码 // 泛化引用 if (url.getParameter(Constants.GENERIC_KEY, false)) { // 设置 generic 属性 url url.setProtocol(Constants.GENERIC_PROTOCOL); // 移除 generic 属性 url url.removeParameter(Constants.GENERIC_KEY); // 创建 Invoker 对象 return protocol.refer(interfaceClass, url); } // 省略【远程引用-Dubbo】相关代码 }第 14 行设置protocol为generic即dubbo改成generic。第 16 行移除generic属性避免后续处理时又设置成generic协议。第 18 行调用Protocol#refer(interfaceClass, url)方法引用服务返回 Invoker 对象。此处 Protocol 对象实际是ProtocolFilterWrapper对象通过#buildInvokerChain(invoker, key, group)方法包装成带有过滤器链的 Invoker 对象。关于ProtocolFilterWrapper在 《精尽 Dubbo 源码分析 —— 服务暴露一之本地暴露Injvm》「4. ProtocolFilterWrapper」 有详细解析。5.1 GenericProtocol5.1.1 refercom.alibaba.dubbo.rpc.protocol.GenericProtocol实现 Protocol 接口泛化协议实现类。代码如下public class GenericProtocol extends AbstractProtocol { Override public T InvokerT refer(ClassT type, URL url) throws RpcException { // 创建 GenericInvoker 对象 return new GenericInvokerT(type, url, getClients(url)); } }创建 GenericInvoker 对象。5.1.2 GenericInvokercom.alibaba.dubbo.rpc.protocol.generic.GenericInvoker实现 Invoker 接口泛化 Invoker 实现类。代码如下public class GenericInvokerT implements InvokerT { /** * 服务类型 */ private final ClassT type; /** * URL 对象 */ private final URL url; /** * 通信客户端数组 */ private final ExchangeClient[] clients; /** * 使用的 {link #clients} 的位置 */ private final AtomicPositiveInteger index new AtomicPositiveInteger(); public GenericInvoker(ClassT type, URL url, ExchangeClient[] clients) { this.type type; this.url url; this.clients clients; } Override public ClassT getInterface() { return type; } Override public Result invoke(Invocation invocation) throws RpcException { // 设置 generic 属性 if (invocation.getMethodName().equals(Constants.$INVOKE) invocation.getArguments() ! null invocation.getArguments().length 3) { Object[] args (Object[]) invocation.getArguments()[2]; if (args ! null) { for (Object arg : args) { if (arg instanceof Map) { ((Map) arg).put(Constants.GENERIC_KEY, Boolean.TRUE.toString()); } } } } // 远程调用 return clients[index.getAndIncrement() % clients.length].request(invocation); } Override public URL getUrl() { return url; } Override public boolean isAvailable() { for (ExchangeClient client : clients) { if (client.isConnected()) { return true; // 只要有一个客户端连接就返回 true } } return false; } Override public void destroy() { for (ExchangeClient client : clients) { try { client.close(); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } }type属性服务类型。通过构造方法传入值为com.alibaba.dubbo.rpc.service.GenericService。clients属性通信客户端数组。通过构造方法传入调用DubboProtocol#getClients(url)方法基于 Dubbo Protocol 实现。#invoke(invocation)方法远程调用。代码如下第 27 至 37 行当方法名为$invoke方法参数为 3 时设置generic属性为true。该属性用于服务提供者的泛化实现在 《精尽 Dubbo 源码分析 —— 服务调用三之泛化实现》 详细解析。第 39 行顺序轮询从clients数组中获得一个 ExchangeClient 对象。第 39 行调用ExchangeClient#request(invocation)方法远程调用。666. 彩蛋服务引用终于终于终于写完啦开心 下一篇服务调用走起
企业数字化 ERP 产品动态
相关推荐
RAG 入门:AI Agent 知识获取管道全解析,附代码与避坑指南 做 AI Agent 开发的老朋友应该都有体会:真正让 Agent 从“玩具”变成“工具”的,往往不是模型本身多聪明,而是它能不能在关键时刻拿到对的那条知识。这里说的知识获取管道,说白了就是 RAG(Retrieval-Augmented Generat… · 2026/11/4 21:37:34
PostgreSQL表膨胀优化:字段顺序与存储机制解析 1. PostgreSQL表膨胀的三大元凶:从对齐规则到存储机制深度解析作为一名长期与PostgreSQL打交道的DBA,我见过太多因为表结构设计不当导致的存储空间浪费案例。上周排查一个生产环境数据库时,发现某核心表实际占用空间是预估值的2.3倍——这直接… · 2026/11/4 21:37:34
构建智能供应链透明度系统:Hermes Agent产品溯源与认证全指南 构建智能供应链透明度系统:Hermes Agent产品溯源与认证全指南
在全球化贸易日益复杂的今天,供应链透明度已成为企业赢得信任和市场竞争力的关键。Hermes Agent作为一款强大的开源智能代理框架,为构建端到端的产品溯源与认证系统提供了灵活而… · 2026/11/4 22:24:51
构建智能环境控制系统:Hermes Agent智能家居场景自动化 构建智能环境控制系统:Hermes Agent智能家居场景自动化
什么是Hermes Agent智能家居自动化
Hermes Agent是一款功能强大的开源智能环境控制工具,它能够帮助用户轻松构建个性化的智能家居场景自动化系统。通过Hermes Agent,您可以实现灯光、开… · 2026/11/4 22:24:51
Hermes Agent与边缘AI推理优化:模型量化与剪枝全攻略 Hermes Agent与边缘AI推理优化:模型量化与剪枝全攻略
Hermes Agent作为一款强大的AI代理框架,在边缘设备上实现高效推理是其核心优势之一。本文将深入探讨如何通过模型量化与剪枝技术,在保持性能的同时显著降低资源消耗,让AI模型… · 2026/11/4 22:23:49
探索Hermes Agent的计算机视觉目标跟踪算法:SORT与DeepSORT 探索Hermes Agent的计算机视觉目标跟踪算法:SORT与DeepSORT
Hermes Agent是一个功能强大的AI代理框架,其中集成了多种先进的计算机视觉技术,目标跟踪算法便是其中的重要组成部分。本文将深入探讨Hermes Agent中的SORT(Simple Onli… · 2026/11/4 22:23:49
探索Hermes Agent的强化学习策略优化:超参数搜索方法 探索Hermes Agent的强化学习策略优化:超参数搜索方法
Hermes Agent是一款功能强大的AI代理框架,其强化学习模块提供了全面的策略优化能力。本文将深入探讨如何通过超参数搜索方法提升Hermes Agent的强化学习性能,帮助开发者快速掌握高效的参… · 2026/11/4 22:23:49
Hermes Agent与元宇宙虚拟经济集成:加密货币与NFT管理的终极指南 Hermes Agent与元宇宙虚拟经济集成:加密货币与NFT管理的终极指南
Hermes Agent是一款功能强大的AI代理工具,能够与元宇宙虚拟经济系统无缝集成,为用户提供全面的加密货币与NFT管理解决方案。通过其灵活的插件架构和智能自动化功能࿰… · 2026/11/4 22:23:49
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38