Paul Paul Public Builder & 全栈工程师
search
arrow_back 返回标签拓扑

服务发现

共 4 篇。

2026-08-30 · 约 1 分钟

Dubbo3 应用级服务发现的取舍

Dubbo3 把接口级发现改成应用级(服务自省),本质是用元数据中心换注册中心的压力:注册数据量从 O(接口数) 降到 O(应用数)。 MetadataService.java java content_copy 复制代码 // 应用级发现的关键:Provider 暴露 MetadataService, // Consumer 先查元数据再建立订阅 ServiceInstancesChangedListener listener = serviceDiscovery.createListener(serviceNames); 疑问还没想清楚:元数据版本冲突时 Consumer 的回退策略,在规模上去之后会不会成为新瓶颈。

阅读全文 arrow_forward
2026-08-25 · 约 1 分钟

Nacos 服务发现的数据模型速记

Nacos Naming 的核心数据模型是三层:Service → Cluster → Instance。临时实例走 Distro 协议(AP,节点间分片负责 + 异步同步),持久实例走 Raft(CP)。 判断一个实例是临时还是持久,看 ephemeral 字段;1.x 时代默认临时,2.x grpc 长连接下沉后这个语义没变,只是传输层换了。 待验证:Distro 协议在网络分区恢复后的数据校验轮次,是否能解释我们线上那次「实例列表抖动」。

阅读全文 arrow_forward
newest_tech 2022-08-18 · 约 5 分钟

Nacos-Naming模块源码讲解

前言 上篇文章列举了Nacos大致的功能列表,但没有深入讲解 Nacos 的实现细节。我们知道其实还有很多同类的产品比如Zookeeper、Eureka、Etcd、Consul等等,这些产品的功能在大体上都和 Nacos 很相似,最主要的区别就在于它的实现。今天我们来深入了解 Nacos 的 Naming 模块的实现。 版本说明 Nacos:2.1.1 jdk:1.8 代码分支:develope 服务注册 根据服务注册url可以定位到服务注册的web层入口为com.alibaba.nacos.naming.controllers.InstanceController#register,示意代码如下所示。 java content_copy 复制代码 public class InstanceController{ /** * Register new instance. * * @param request http request * @return 'ok' if success * @throws Exception any error during register */ @CanDistro @PostMapping @Secured(action = ActionTypes.WRITE) public String register(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); NamingUtils.checkServiceNameFormat(serviceName); final Instance instance = HttpRequestInstanceBuilder.newBuilder() .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build(); getInstanceOperator().registerInstance(namespaceId, serviceName, instance); NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "", false, namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), instance.getIp(), instance.getPort())); return "ok"; } } 可以看到整个过程只有两个步骤。一是注册Instance,一是通过NotyfyCenter发布事件。我们拆开看,先看注册Instance的过程。

阅读全文 arrow_forward
newest_tech 2022-08-06 · 约 1 分钟

Dubbo3.0-服务自省

前言 Dubbo3.0中有个特别的机制“服务自省”,对于这个机制很多人都很陌生,我用自己的理解来讲讲这个机制。 版本说明 Dubbo版本:3.0.9 定义 所谓的服务自省就是一个元数据同步机制。在Dubbo3.0中,原来面向RPC定义的注册中心数据被拆分为服务元数据和服务地址信息,Dubbo的流量管理能力依赖服务的元数据,因此需要一个机制能很好的去把Provider端的元数据同步到Consumer端。 机制讲解 服务自省是Consumer端获取Provider端服务元数据的一个机制,那么肯定Provider端和Consumer端都有对应的处理。 Provider暴露MetadataService Provider需要提供服务的元数据,所以在Provider端启动时会先暴露所有的业务服务,当所有业务服务都准备好之后(启动并且已经向注册中心注册),暴露MetadataService供Consumer端来查询元数据(此时业务服务也元数据服务都能正常调用,注册中心也有了元数据和应用地址数据)。 Consumer监听MetadataService Consumer端需要在调用时决定调用哪个后端服务(也就是说要进行流量管理),因此在Consumer端启动时先通过应用名查询注册中心得到后端Provider的地址,并完成订阅。拿到Provider地址后,直接发起元数据查询,通过MetadataService得到服务端元数据,并且会有一个监听器,监听服务端元数据的变化(即reversion机制),一旦服务端元数据发生变化,Consumer就会重新发起MetadataService调用获取元数据。 源码解析 Provider暴露MetadataService Dubbo暴露服务的起点是org.apache.dubbo.config.ServiceConfig#export,当然实际的暴露动作是委托给org.apache.dubbo.rpc.Protocol#export。但这是单个服务的暴露,MetadataService肯定是要在所有业务服务暴露完之后再暴露,因此我们需要从DubboBootstrap中去寻找答案(因为现在Dubbo的启动都是由这个类完成)。 DubboBootstrap这个类的结构我们这次不讲,我们需要了解的是Dubbo3.0有两个Deployer类负责启动。ApplicationDeployer负责应用的初始化和启动,ModuleDeployer负责服务的暴露和引用。因此我们可以到ModuleDeployer中找答案。 org.apache.dubbo.config.deploy.DefaultModuleDeployer#start 从这个模板方法里面我们可以看到在暴露和引用服务之后我们会调用org.apache.dubbo.config.deploy.DefaultModuleDeployer#onModuleStarted后续的调用链路如下。 org.apache.dubbo.config.deploy.DefaultApplicationDeployer#notifyModuleChanged -> org.apache.dubbo.config.deploy.DefaultApplicationDeployer#prepareApplicationInstance -> org.apache.dubbo.config.deploy.DefaultApplicationDeployer#exportMetadataService -> org.apache.dubbo.config.metadata.ExporterDeployListener#onModuleStarted -> org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter#export 最终暴露元数据服务的动作就在org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter#export中。 Consumer监听MetadataService 这里和Provider的分析过程刚好相反,因为Consumer需要让每个ReferenceConfig都要监听服务端的元数据变化,否则流量管理能力会失效。所以,我们直接从org.apache.dubbo.config.ReferenceConfig#get看就好。 首先初始化过程会调用init方法,我们直接跳过一些其他的配置代码,我们知道代理类的创建其实也是委托给org.apache.dubbo.rpc.Protocol#refer,具体来说调用链路如下。 org.apache.dubbo.registry.integration.RegistryProtocol#refer -> org.apache.dubbo.registry.integration.RegistryProtocol#doRefer -> org.apache.dubbo.registry.integration.RegistryProtocol#interceptInvoker -> org.apache.dubbo.registry.client.migration.MigrationRuleListener#onRefer -> org.apache.dubbo.registry.client.migration.MigrationRuleHandler#doMigrate -> org.apache.dubbo.registry.client.migration.MigrationRuleHandler#refreshInvoker -> org.apache.dubbo.registry.client.migration.ServiceDiscoveryMigrationInvoker#migrateToApplicationFirstInvoker -> org.apache.dubbo.registry.client.migration.MigrationInvoker#refreshServiceDiscoveryInvoker -> org.apache.dubbo.registry.integration.RegistryProtocol#getServiceDiscoveryInvoker -> org.apache.dubbo.registry.integration.RegistryProtocol#doCreateInvoker -> org.apache.dubbo.registry.integration.DynamicDirectory#subscribe ->org.apache.dubbo.registry.client.ServiceDiscoveryRegistry#subscribe -> org.apache.dubbo.registry.client.ServiceDiscoveryRegistry#doSubscribe -> org.apache.dubbo.registry.client.ServiceDiscoveryRegistry#subscribeURLs 最终在org.apache.dubbo.registry.client.ServiceDiscoveryRegistry#subscribeURLs中我们能看到整个Consumer端的服务自省过程。 关键代码如下: java content_copy 复制代码 public class ServiceDiscoveryRegistry { protected void subscribeURLs(URL url, NotifyListener listener, Set<String> serviceNames) { serviceNames = toTreeSet(serviceNames); String serviceNamesKey = toStringKeys(serviceNames); //... Lock appSubscriptionLock = getAppSubscription(serviceNamesKey); try { appSubscriptionLock.lock(); ServiceInstancesChangedListener serviceInstancesChangedListener = serviceListeners.get(serviceNamesKey); if (serviceInstancesChangedListener == null) { serviceInstancesChangedListener = serviceDiscovery.createListener(serviceNames); serviceInstancesChangedListener.setUrl(url); for (String serviceName : serviceNames) { List<ServiceInstance> serviceInstances = serviceDiscovery.getInstances(serviceName); if (CollectionUtils.isNotEmpty(serviceInstances)) { serviceInstancesChangedListener.onEvent(new ServiceInstancesChangedEvent(serviceName, serviceInstances)); } } serviceListeners.put(serviceNamesKey, serviceInstancesChangedListener); } if (!serviceInstancesChangedListener.isDestroyed()) { serviceInstancesChangedListener.setUrl(url); listener.addServiceListener(serviceInstancesChangedListener); serviceInstancesChangedListener.addListenerAndNotify(protocolServiceKey, listener); serviceDiscovery.addServiceInstancesChangedListener(serviceInstancesChangedListener); } else { logger.info(String.format("Listener of %s has been destroyed by another thread.", serviceNamesKey)); serviceListeners.remove(serviceNamesKey); } } finally { appSubscriptionLock.unlock(); } } } 从上面看到org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener被创建,并且在监听ServiceInstancesChangedEvent,通过org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener#onEvent -> org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener#doOnEvent 这里就是Consumer端发生服务自省的地方。从代码中可以看到对MetadataService的调用是通过org.apache.dubbo.registry.client.ServiceDiscovery#getRemoteMetadata(java.lang.String, java.util.List<org.apache.dubbo.registry.client.ServiceInstance>)

阅读全文 arrow_forward