Nacos-技术专题-配置中心实现

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"什么是 Nacos"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":"是阿里发起的开源项目,地址:"},{"type":"link","attrs":{"href":"https://link.zhihu.com/?target=https%3A//github.com/alibaba/nacos","title":null},"content":[{"type":"text","text":"https://github.com/alibaba/nacos"}]},{"type":"text","text":"。"},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":"主要提供两种服务,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"一是配置中心,支持配置注册、变更下发、层级管理等,意义是不停机就可以动态刷新服务内部的配置项;"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":"二是作为命名服务,提供服务的注册和发现功能,通常用于在 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":"RPC"}],"marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":" 框的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":"Client"}],"marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":" 和 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":"Server"}],"marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#9254DE","name":"purple"}},{"type":"strong"}],"text":" 中间充当媒介,还附带有健康监测、负载均衡等功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 本文聚焦于 "},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 的第一块功能,即配置中心的实现。先叙述一个配置中心通常需要哪些组成部分,再结合 "},{"type":"codeinline","content":[{"type":"text","text":"Nacos 1.1.4"}]},{"type":"text","text":" 的源码,探究一下这些设计是如何反映在源码上的。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"配置中心的架构"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 配置中心本身并不复杂,前提是你先将 "},{"type":"codeinline","content":[{"type":"text","text":"CAP"}]},{"type":"text","text":" 的取舍问题晾在一边的话。"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"配置中心最基础的功能就是存储一个键值对,用户发布一个配置("},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"configKey"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"),然后客户端获取这个配置项("},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"configValue"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":");进阶的功能就是当某个配置项发生变更时,将变更告知客户端刷新旧值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"下方的架构图,简要描述了一个配置中心的大致架构,用户可以通过管理平台发布配置,通过 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"HTTP"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":" 调用将配置注册到服务端,服务端将之保存在 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"MySQL"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"等持久化存储引擎中;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":" 用户通过客户端 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"SDK"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"访问服务端的配置,同时建立 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"HTTP"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"的长轮询监听配置项变更,同时为了减轻服务端压力和保证容灾特性,配置项拉取到客户端之后会保存一份快照在本地文件中,SDK 优先读取文件里的内容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 例如配置"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"分层设计,权限校验,客户端长轮询的间隔设置"},{"type":"text","text":",服务端每次查询都需要访问 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"MySQL"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"么,配置变更是主动推送还是等定时轮询触发等,"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"还有就是运维高可用方面的工作(私以为这个是配置中心的精华),例如节点跨地域部署,网络分区时配置如何保证可写可推送变更等。"},{"type":"text","marks":[{"type":"strong"}],"text":"真正实现一个高质量的配置中心,还是需要长时间打磨的。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7c/7c2bac31e8bfcde6e89f547a316cb1be.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Nacos 使用示例"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下文涉及的源码均基于 Nacos 1.1.4 版本"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"官方代码示例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 先看一下官方文档中对于 "},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"API"}]},{"type":"text","text":" 使用的示例代码,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 第一步是传递配置,新建 "},{"type":"codeinline","content":[{"type":"text","text":"ConfigService"}]},{"type":"text","text":" 实例,第二步可以通过相应的接口获取配置和注册配置监听器。使用方式非常简单易懂,不再赘述。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"try {\n // 传递配置\n String serverAddr = \"{serverAddr}\";\n String dataId = \"{dataId}\";\n String group = \"{group}\";\n Properties properties = new Properties();\n properties.put(\"serverAddr\", serverAddr);\n // 新建 configService\n ConfigService configService = NacosFactory.createConfigService(properties);\n String content = configService.getConfig(dataId, group, 5000);\n System.out.println(content);\n // 注册监听器\n configService.addListener(dataId, group, new Listener() {\n @Override\n public void receiveConfigInfo(String configInfo) {\n System.out.println(\"recieve1:\" + configInfo);\n }\n @Override\n public Executor getExecutor() {\n return null;\n }\n \t});\n\t} catch (NacosException e) {\n // TODO -generated catch block\n e.printStackTrace();\n\t}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Properties 解读"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"serverAddr"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 传递的是配置中心服务端的地址列表,被内部名为 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ServerListManager"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 的类解析成地址列表进行管理,进行 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"HTTP"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 调用时会从中选择存活的机器拼接成 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"URL"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 完成调用,一旦在调用时该地址抛异常,则客户端会有一些处理措施,例如转换下次选择的节点等。"},{"type":"text","text":"值得注意的是,通常在实践中不会采取这种硬编码的方式,可以将其配置在 "},{"type":"codeinline","content":[{"type":"text","text":"Zookeeper"}]},{"type":"text","text":" 或者注册发现中心上,在启动时动态拉取。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置项的层级设计"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 官方给出了这样的设计图:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a4/a4ebc2f6eede79b7efb7ae090bf590ee.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"dataId"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 可以理解为用户自定义的配置健,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"group"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 可以理解为配置分组名称,这个属于配置层级设计的概念。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"简单来说,配置中心会通过层次设计,来支持不同的分区,以此区分不同的环境、不同的分组、甚至不同的开发者,满足在开发过程中灰度发布、测试等需求。"},{"type":"text","text":"因此怎样设计都可以,只要有含义就好,例如下图也不是不可以。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/28/28d3bcbac84f1945497b1f1a44bbf061.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Nacos 客户端解析"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"获取配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 获取配置的主要方法是 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"NacosConfigService"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"类的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"getConfigInner"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"方法,通常情况下该方法直接从本地文件中取得配置的值,如果本地文件不存在或者内容为空,则再通过 "},{"type":"codeinline","content":[{"type":"text","text":"HTTP GET"}]},{"type":"text","text":" 方法从远端拉取配置,并保存到本地快照中。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2a/2a3c7cd6d6638a1b89a234f916d41985.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 当通过 "},{"type":"codeinline","content":[{"type":"text","text":"HTTP"}]},{"type":"text","text":" 获取远端配置时,"},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 提供了两种熔断策略,一是超时时间,二是最大重试次数,默认重试三次。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"注册监听器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 配置中心客户端对某个配置项注册监听器是很常见的需求,达到在配置项变更时候执行回调的功能。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"iconfig.addListener(dataId, group, ml);\niconfig.getConfigAndSignListener(dataId, group, 1000, ml);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 可以通过以上方式注册监听器,它们内部的实现均是调用 "},{"type":"codeinline","content":[{"type":"text","text":"ClientWorker"}]},{"type":"text","text":" 类的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"addCacheDataIfAbsent"}]},{"type":"text","text":"。其中 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"CacheData"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"是一个维护配置项和其下注册的所有监听器的实例,私以为这个名字取得并不好,不容易理解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 所有的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"CacheData"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"都保存在 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ClientWorker"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"类中的原子 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"cacheMap"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"中,其内部的核心成员有:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89f9d49d9ae7492ae199817cc1289f78.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 其中,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"content"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 是配置内容,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"MD5"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 值是用来检测配置是否发生变更的关键,内部还维护着一个若干监听器组成的数组,一旦发生变更则依次回调这些监听器。"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置长轮询"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ClientWorker"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 通过其下的两个线程池完成配置长轮询的工作,一个是单线程的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"executor"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":",每隔 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"10ms"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 按照每 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"3000"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 个配置项为一批次捞取待轮询的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"cacheData"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 实例,将其包装成为一个 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"LongPollingTask"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 提交进入第二个线程池 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"executorService"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" 处理。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3f/3f6e89ce92795090eede18c58051db2b.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"该长轮询任务内部主要分为四步:"}]},{"type":"blockquote","content":[{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"检查本地配置,忽略本地快照不存在的配置项,检查是否存在需要回调监听器的配置项"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"如果本地没有配置项的,从服务端拿,返回配置内容发生变更的键值列表"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"每个键值再到服务端获取最新配置,更新本地快照,补全之前缺失的配置"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"检查 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":"MD5"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":" 标签是否一致,不一致需要回调监听器"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 如果该轮询任务抛出异常,等待一段时间再开始下一次调用,减轻服务端压力。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":"另外,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":"Nacos"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":" 在 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":"HTTP"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":" 工具类中也有限流器的代码,通过多种手段降低轮询或者大流量情况下的风险。下文还会讲到,如果在服务端没有发现变更的键值,那么服务端会夯住这个 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":"HTTP"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":" 请求一段时间(客户端侧默认传递的超时是 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":"30s"}],"marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"bgcolor","attrs":{"color":"#FDED8A","name":"yellow"}}],"text":"),以此进一步减轻客户端的轮询频率和服务端的压力。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Nacos 服务端解析"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置 Dump"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 服务端启动时就会依赖 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"DumpService"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"init"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"方法,从数据库中 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"load"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"配置存储在本地磁盘上,并将一些重要的元信息例如 "},{"type":"codeinline","content":[{"type":"text","text":"MD5"}]},{"type":"text","text":" 值缓存在内存中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库 "},{"type":"codeinline","content":[{"type":"text","text":"dump"}]},{"type":"text","text":" 全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是 "},{"type":"codeinline","content":[{"type":"text","text":"6h"}]},{"type":"text","text":" 以内的话)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 全量 "},{"type":"codeinline","content":[{"type":"text","text":"dump"}]},{"type":"text","text":" 当然先清空磁盘缓存,然后根据主键 "},{"type":"codeinline","content":[{"type":"text","text":"ID"}]},{"type":"text","text":" 每次捞取一千条配置刷进磁盘和内存。增量 "},{"type":"codeinline","content":[{"type":"text","text":"dump"}]},{"type":"text","text":" 就是捞取最近六小时的新增配置(包括更新的和删除的),先按照这批数据刷新一遍内存和文件,再根据内存里所有的数据全量去比对一遍数据库,如果有改变的再同步一次,相比于全量 "},{"type":"codeinline","content":[{"type":"text","text":"dump"}]},{"type":"text","text":" 的话会减少一定的数据库 "},{"type":"codeinline","content":[{"type":"text","text":"IO"}]},{"type":"text","text":" 和磁盘 "},{"type":"codeinline","content":[{"type":"text","text":"IO"}]},{"type":"text","text":" 次数。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置注册"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 服务端是一个 "},{"type":"codeinline","content":[{"type":"text","text":"SpringBoot"}]},{"type":"text","text":" 实现的服务,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 注册配置主要代码位于 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ConfigController"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"和 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ConfigServletInner"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"中。服务端一般是多节点部署的集群,因此请求一开始只会打到一台机器,这台机器将配置插入 "},{"type":"codeinline","content":[{"type":"text","text":"MySQL"}]},{"type":"text","text":" 中进行持久化,这部分代码很简单不再赘述。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 因为服务端并不是针对每次配置查询都去访问 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"MySQL"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"的,而是会依赖 "},{"type":"codeinline","content":[{"type":"text","text":"dump"}]},{"type":"text","text":" 功能在本地文件中将配置缓存起来。因此当单台机器保存完毕配置之后,需要通知其他机器刷新内存和本地磁盘中的文件内容,因此它会发布一个名为 "},{"type":"codeinline","content":[{"type":"text","text":"ConfigDataChangeEvent"}]},{"type":"text","text":" 的事件,这个事件会通过 "},{"type":"codeinline","content":[{"type":"text","text":"HTTP"}]},{"type":"text","text":" 调用通知所有集群节点(包括自身),触发本地文件和内存的刷新。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ed/edb8f649074416f895aa0ee88d53e69b.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"处理长轮询"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 上文提到,客户端会有一个长轮询任务,拉取服务端的配置变更,那么服务端是如何处理这个长轮询任务的呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 源码逻辑位于 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"LongPollingService"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" "},{"type":"text","text":"类,其中有一个 "},{"type":"codeinline","content":[{"type":"text","text":"Runnable"}]},{"type":"text","text":" 任务名为 "},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling"}]},{"type":"text","text":",服务端会将受到的轮询请求包装成一个 "},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling"}]},{"type":"text","text":" 任务,该任务持有一个 "},{"type":"codeinline","content":[{"type":"text","text":"AsyncContext"}]},{"type":"text","text":" 响应对象("},{"type":"codeinline","content":[{"type":"text","text":"Servlet 3.0"}]},{"type":"text","text":" 的新机制),通过定时线程池延后 "},{"type":"codeinline","content":[{"type":"text","text":"29.5s"}]},{"type":"text","text":" 执行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为什么比客户端 "},{"type":"codeinline","content":[{"type":"text","text":"30s"}]},{"type":"text","text":" 的超时时间提前 "},{"type":"codeinline","content":[{"type":"text","text":"500ms"}]},{"type":"text","text":" 返回是为了最大程度上保证客户端不会因为网络延时造成超时"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6f/6f1812fb0ef619d07060b7171e4cbc26.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 这里需要注意的是,在 "},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling"}]},{"type":"text","text":" 任务被提交进入线程池待执行的同时,服务端也通过一个队列 "},{"type":"codeinline","content":[{"type":"text","text":"allSubs"}]},{"type":"text","text":" 保存了所有正在被夯住的轮询请求,这是因为在配置项被夯住的期间内,如果用户通过管理平台操作了配置项变更、或者服务端该节点收到了来自其他节点的 "},{"type":"codeinline","content":[{"type":"text","text":"dump"}]},{"type":"text","text":" 刷新通知,那么都应立即取消夯住的任务,及时通知客户端数据发生了变更。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了达到这个目的,"},{"type":"codeinline","content":[{"type":"text","text":"LongPollingService"}]},{"type":"text","text":" 类继承自 "},{"type":"codeinline","content":[{"type":"text","text":"Event"}]},{"type":"text","text":" 接口,实际上本身是个事件触发器,需要实现 "},{"type":"codeinline","content":[{"type":"text","text":"onEvent"}]},{"type":"text","text":" 方法,其事件类型是 "},{"type":"codeinline","content":[{"type":"text","text":"LocalDataChangeEvent"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当服务端在请求被夯住的期间接收到某项配置变更时,就会发布一个 "},{"type":"codeinline","content":[{"type":"text","text":"LocalDataChangeEvent"}]},{"type":"text","text":" 类型的事件通知(注意同上文中的 "},{"type":"codeinline","content":[{"type":"text","text":"ConfigDataChangeEvent"}]},{"type":"text","text":" 区别),之后会将这个变更包装成一个 "},{"type":"codeinline","content":[{"type":"text","text":"DataChangeTask"}]},{"type":"text","text":" 异步执行,内容就是从 "},{"type":"codeinline","content":[{"type":"text","text":"allSubs"}]},{"type":"text","text":" 中找出夯住的 "},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling"}]},{"type":"text","text":" 请求,写入变更强制其立即返回。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此完整的流程如下,如果非接收请求的节点,那么忽略第一步持久化配置后开始:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/84/842ad28bb22c885dce3c4ebdf2242042.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"全文总结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文聚焦于 "},{"type":"codeinline","content":[{"type":"text","text":"Nacos"}]},{"type":"text","text":" 作为配置中心的源码实现,包含了客户端和服务端两部分,内容基本覆盖了配置中心功能的关键点,既作为学习总结,也希望对阅读的朋友有所帮助。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章