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

Nacos

共 4 篇。

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-22 · 约 7 分钟

Nacos-Config模块源码讲解

前言 我们知道Nacos其实是由两个重要的模块组成,一是 Naming 模块,另一个就是今天要讲的 Config 模块。 版本说明 Nacos:2.1.1 jdk:1.8 代码分支:develope 配置中心基本原理 配置中心有三个角色分别是是配置发布者、配置服务端、配置客户端。整个流程由配置发布者发起,先发布一条配置到服务端,然后客户端监听这条配置,当配置有变动时由服务端推送到客用户端。 配置发布 我们从web层着手,可以很快的发现入口为com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,这个方法看上去很长,但其实大体逻辑很简单,示意代码如下所示。 java content_copy 复制代码 public class ConfigController { /** * Adds or updates non-aggregated data. * <p> * request and response will be used in aspect, see * {@link com.alibaba.nacos.config.server.aspect.CapacityManagementAspect} and * {@link com.alibaba.nacos.config.server.aspect.RequestLogAspect}. * </p> * @throws NacosException NacosException. */ @PostMapping @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam(value = "content") String content, @RequestParam(value = "tag", required = false) String tag, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "config_tags", required = false) String configTags, @RequestParam(value = "desc", required = false) String desc, @RequestParam(value = "use", required = false) String use, @RequestParam(value = "effect", required = false) String effect, @RequestParam(value = "type", required = false) String type, @RequestParam(value = "schema", required = false) String schema) throws NacosException { final String srcIp = RequestUtil.getRemoteIp(request); final String requestIpApp = RequestUtil.getAppName(request); if (StringUtils.isBlank(srcUser)) { srcUser = RequestUtil.getSrcUserName(request); } //check type if (!ConfigType.isValidType(type)) { type = ConfigType.getDefaultType().getType(); } // encrypted Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content); content = pair.getSecond(); // check tenant ParamUtils.checkTenant(tenant); ParamUtils.checkParam(dataId, group, "datumId", content); ParamUtils.checkParam(tag); Map<String, Object> configAdvanceInfo = new HashMap<>(10); MapUtil.putIfValNoNull(configAdvanceInfo, "config_tags", configTags); MapUtil.putIfValNoNull(configAdvanceInfo, "desc", desc); MapUtil.putIfValNoNull(configAdvanceInfo, "use", use); MapUtil.putIfValNoNull(configAdvanceInfo, "effect", effect); MapUtil.putIfValNoNull(configAdvanceInfo, "type", type); MapUtil.putIfValNoNull(configAdvanceInfo, "schema", schema); ParamUtils.checkParam(configAdvanceInfo); if (AggrWhitelist.isAggrDataId(dataId)) { LOGGER.warn("[aggr-conflict] {} attempt to publish single data, {}, {}", RequestUtil.getRemoteIp(request), dataId, group); throw new NacosException(NacosException.NO_RIGHT, "dataId:" + dataId + " is aggr"); } final Timestamp time = TimeUtils.getCurrentTime(); String betaIps = request.getHeader("betaIps"); ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); configInfo.setType(type); String encryptedDataKey = pair.getFirst(); configInfo.setEncryptedDataKey(encryptedDataKey); if (StringUtils.isBlank(betaIps)) { if (StringUtils.isBlank(tag)) { persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime())); } else { persistService.insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime())); } } else { // beta publish configInfo.setEncryptedDataKey(encryptedDataKey); persistService.insertOrUpdateBeta(configInfo, betaIps, srcIp, srcUser, time, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(true, dataId, group, tenant, time.getTime())); } ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, time.getTime(), InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT_PUB, content); return true; } } 整个方法就是先组装ConfigInfo,然后委托com.alibaba.nacos.config.server.service.repository.PersistService#insertOrUpdateTag进行处理,这里如果使用的是mysql那么实现类就是com.alibaba.nacos.config.server.service.repository.extrnal.ExternalStoragePersistServiceImpl,这个是一个插入数据库的动作就不细讲了,只是要注意的是这个方法包含新增和更新,当插入失败时会变成更新操作。而操作完数据库之后会发送一个ConfigDataChangeEvent事件,这个事件的处理类是com.alibaba.nacos.config.server.service.notify.AsyncNotifyService,示意代码如下所示。

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

Nacos总览

前言 说到微服务,注册中心是避不开的话题。在微服务中注册中心的主要功能就是提供服务发现、服务注册这两个功能。市面上的注册中心种类非常多,今天我就讲讲阿里的开源产品 Nacos。 快速构建 源码下载 地址 本地编译 参考官网的Download source code from Github 。 启动 参考官网Start Server 。本地启动的时候一定要加-m standalone Naming+Config=Nacos 从Nacos官网上我们其实也了解到,Nacos分为两个部分。一个是命名服务,另一个是分布式配置,接下来会分开介绍 分布式系统中的命名服务 命名服务的定义看这里 。简单来说在分布式系统中的命名服务的作用主要是给系统中的每个RPC服务分配一个资源符号,命名服务通过这个资源符号能得到这个RPC服务的详细信息以及所有提供这些服务的机器信息。 为什么需要命名服务? 在进行服务调用时有两个信息至关重要,一是我要调用的最新的服务信息(RPC信息、地址信息)。如果不知道服务信息我们是无法调用的,而如果我们的服务信息不是最新的,那又会因为服务信息的不一致导致调用异常(试想下,如果你还在用旧的地址信息,而后端服务这个时候换了机器部署,那就会出现调用异常的情况。同理,后端服务信息发生变更,比如:方法签名发生变更。使用旧的信息仍然会出现调用失败的情况)。一是保证调到正常提供服务的机器上(可能有版本发布或是机器异常的情况,需要剔除掉这些信息)。正式基于这两点,我们需要一个服务能够管理服务的注册信息。 命名服务的主要功能点 信息推送 前面我们讲到了,必须让应用能时刻保持最新的服务信息,而实现这一功能的手段就是消息推送。消息推送的形式有很多种,比如:TCP全双工、UDP广播、回调,这些方式都可以实现。 心跳检测 当然,为了提升服务的可用性,异常服务的剔除也是必不可少的。而实现它的方式就是心跳检测,基于一个固定的时间去发送“ping命令”检查服务的情况。 Nacos中的命名服务 数据结构 上图是Dubbo启动后会注册两个服务,以及它的订阅者。可以看到Nacos的数据结构和Zookeeper的数据结构有很大的不一样。Nacos是基于Key-Value的结构,而Zookeeper是基于一种树形结构。 数据类型 和 Zookeeper 一样 Nacos 也分为两种节点,临时节点和永久节点,临时节点会和创建这个节点的应用建立心跳检测机制用于保证这个机器可达(服务健康检查也是基于这个机制)。而永久节点一经创建就不会被 Nacos 删除,除非你通过删除操作来删除。 分布式系统中的配置服务 配置服务也叫配置中心,用于管理整个系统的配置。提供简单的读写接口、实时更新到系统中各个机器上、自动载入到系统中。 为什么需要配置服务 在开发过程中我们会经常用到配置,比如:应用启动参数配置、业务参数配置等等。对于系统启动配置,我们希望有多个配置,能够针对不同的环境自动匹配(类似Spring的@Profile,可以让配置绑定特定环境)。针对应用业务配置,我们希望配置能被很好的管理,他的生命周期能和应用独立开(在以前配置都是散落在各个机器上,每个机器上都有一份,而且是散落在各个地方,我们没有一个很好的办法去同时修改这些配置文件)。当然,我们现在的业务系统很多业务逻辑,经常会遇到需要禁用某个功能或是有多个功能,这些功能的切换或者功能的禁用都需要基于配置,而传统的配置很难做到统一变更管理,统一的推送。 Nacos中的配置服务 Nacos 基于上面三点做了一个很好的实现,但是在使用时也需要注意配置并不支持高并发的变更(当然也没有高并发的实际场景,所有配置服务都要注意不能高并发更新)。 写在最后 本篇文章只从功能特点上来看 Nacos,主要为了引入分布式系统中的命名服务和配置服务这两个概念,后面会专门从源码实现层面讲。附录里面最后3个链接是 Nacos 和其他同类产品的对比,因为作者们的视角不同,因此需要大家看的时候舍弃部分重复的内容。 附录 Nacos Spring Boot 服务注册发现框架比较 服务注册与发现框架对比 微服务架构 - 服务发现、配置中心对比 - Nacos

阅读全文 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