Nacos配置中心交互模型是 push 还是 pull ?你应该这么回答

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文案例收录在 https://github.com/chengxy-nds/Springboot-Notebook","attrs":{}}]}],"attrs":{}},{"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":"大家好,我是小富~","attrs":{}}]},{"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":"对于","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"大家应该都不太陌生,出身阿里名声在外,能做动态服务发现、配置管理,非常好用的一个工具。然而这样的技术用的人越多面试被问的概率也就越大,如果只停留在使用层面,那面试可能要吃大亏。","attrs":{}}]},{"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":"比如我们今天要讨论的话题,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"在做配置中心的时候,配置数据的交互模式是服务端推过来还是客户端主动拉的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8c/8c824870e269bb1a96e51a9dfec3d06a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"这里我先抛出答案:客户端主动拉的!","attrs":{}}]},{"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":"接下来咱们扒一扒","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"的源码,来看看它具体是如何实现的?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"配置中心","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聊","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"之前简单回顾下配置中心的由来。","attrs":{}}]},{"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":"简单理解配置中心的作用就是对配置统一管理,修改配置后应用可以动态感知,而无需重启。","attrs":{}}]},{"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":"因为在传统项目中,大多都采用静态配置的方式,也就是把配置信息都写在应用内的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"yml","attrs":{}}],"attrs":{}},{"type":"text","text":"或","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"properties","attrs":{}}],"attrs":{}},{"type":"text","text":"这类文件中,如果要想修改某个配置,通常要重启应用才可以生效。","attrs":{}}]},{"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":"但有些场景下,比如我们想要在应用运行时,通过修改某个配置项,实时的控制某一个功能的开闭,频繁的重启应用肯定是不能接受的。","attrs":{}}]},{"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":"尤其是在微服务架构下,我们的应用服务拆分的粒度很细,少则几十多则上百个服务,每个服务都会有一些自己特有或通用的配置。假如此时要改变通用配置,难道要我挨个改几百个服务配置?很显然这不可能。所以为了解决此类问题配置中心应运而生。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/05/055b7a89bd97e0eae765b2244a4ca374.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"推与拉模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端与配置中心的数据交互方式其实无非就两种,要么推","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"push","attrs":{}}],"attrs":{}},{"type":"text","text":",要么拉","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pull","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"推模型","attrs":{}}]},{"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":"客户端与服务端建立","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TCP","attrs":{}}],"attrs":{}},{"type":"text","text":"长连接,当服务端配置数据有变动,立刻通过建立的长连接将数据推送给客户端。","attrs":{}}]},{"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":"优势:长链接的优点是实时性,一旦数据变动,立即推送变更数据给客户端,而且对于客户端而言,这种方式更为简单,只建立连接接收数据,并不需要关心是否有数据变更这类逻辑的处理。","attrs":{}}]},{"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":"弊端:长连接可能会因为网络问题,导致不可用,也就是俗称的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"假死","attrs":{}}],"attrs":{}},{"type":"text","text":"。连接状态正常,但实际上已无法通信,所以要有的心跳机制","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"KeepAlive","attrs":{}}],"attrs":{}},{"type":"text","text":"来保证连接的可用性,才可以保证配置数据的成功推送。","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"拉模型","attrs":{}}]},{"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":"客户端主动的向服务端发请求拉配置数据,常见的方式就是轮询,比如每3s向服务端请求一次配置数据。","attrs":{}}]},{"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":"轮询的优点是实现比较简单。但弊端也显而易见,轮询无法保证数据的实时性,什么时候请求?间隔多长时间请求一次?都是不得不考虑的问题,而且轮询方式对服务端还会产生不小的压力。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"长轮询","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开篇我们就给出了答案,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"采用的是客户端主动拉","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pull","attrs":{}}],"attrs":{}},{"type":"text","text":"模型,应用长轮询(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Long Polling","attrs":{}}],"attrs":{}},{"type":"text","text":")的方式来获取配置数据。","attrs":{}}]},{"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":"额?以前只听过轮询,长轮询又是什么鬼?它和传统意义上的轮询(暂且叫短轮询吧,方便比较)有什么不同呢?","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"短轮询","attrs":{}}]},{"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":"不管服务端配置数据是否有变化,不停的发起请求获取配置,比如支付场景中前段JS轮询订单支付状态。","attrs":{}}]},{"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":"这样的坏处显而易见,由于配置数据并不会频繁变更,若是一直发请求,势必会对服务端造成很大压力。还会造成推送数据的延迟,比如:每10s请求一次配置,如果在第11s时配置更新了,那么推送将会延迟9s,等待下一次请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/26/2682e2cf5eb03137c3d04727aa04efaa.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"为了解决短轮询的问题,有了长轮询方案。","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"长轮询","attrs":{}}]},{"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":"长轮询可不是什么新技术,它不过是由服务端控制响应客户端请求的返回时间,来减少客户端无效请求的一种优化手段,其实对于客户端来说与短轮询的使用并没有本质上的区别。","attrs":{}}]},{"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":"客户端发起请求后,服务端不会立即返回请求结果,而是将请求挂起等待一段时间,如果此段时间内服务端数据变更,立即响应客户端请求,若是一直无变化则等到指定的超时时间后响应请求,客户端重新发起长链接。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/78/786cbb59a3753a11b5c74ecdd9399b13.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Nacos初识","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了后续演示操作方便我在本地搭了个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意:","attrs":{}},{"type":"text","text":" 运行时遇到个小坑,由于","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"默认是以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cluster","attrs":{}}],"attrs":{}},{"type":"text","text":"集群的方式启动,而本地搭建通常是单机模式","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"standalone","attrs":{}}],"attrs":{}},{"type":"text","text":",这里需手动改一下启动脚本","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"startup.X","attrs":{}}],"attrs":{}},{"type":"text","text":"中的启动模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fb/fbb7da470fe760067d7965ca5ab201e2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"直接执行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/bin/startup.X","attrs":{}}],"attrs":{}},{"type":"text","text":"就可以了,默认用户密码均是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/006d1cd64d76537e89faa1d9b992a683.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"几个概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的几个核心概念:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"group","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"namespace","attrs":{}}],"attrs":{}},{"type":"text","text":",它们的层级关系如下图:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/03/039a73915d949373554d7609c47473ca.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":":是配置中心里最基础的单元,它是一种","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"key-value","attrs":{}}],"attrs":{}},{"type":"text","text":"结构,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"key","attrs":{}}],"attrs":{}},{"type":"text","text":"通常是我们的配置文件名称,比如:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"application.yml","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"mybatis.xml","attrs":{}}],"attrs":{}},{"type":"text","text":",而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"value","attrs":{}}],"attrs":{}},{"type":"text","text":"是整个文件下的内容。","attrs":{}}]},{"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":"目前支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JSON","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"XML","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"YAML","attrs":{}}],"attrs":{}},{"type":"text","text":"等多种配置格式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/55/55393c35b94ff8970f39c2ebdadfa4bd.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"codeinline","content":[{"type":"text","text":"group","attrs":{}}],"attrs":{}},{"type":"text","text":":dataId配置的分组管理,比如同在dev环境下开发,但同环境不同分支需要不同的配置数据,这时就可以用分组隔离,默认分组","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DEFAULT_GROUP","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"namespace","attrs":{}}],"attrs":{}},{"type":"text","text":":项目开发过程中肯定会有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dev","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"test","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pro","attrs":{}}],"attrs":{}},{"type":"text","text":"等多个不同环境,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"namespace","attrs":{}}],"attrs":{}},{"type":"text","text":"则是对不同环境进行隔离,默认所有配置都在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"public","attrs":{}}],"attrs":{}},{"type":"text","text":"里。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"架构设计","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下图简要描述了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的架构流程。","attrs":{}}]},{"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":"客户端、控制台通过发送Http请求将配置数据注册到服务端,服务端持久化数据到Mysql。","attrs":{}}]},{"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":"客户端拉取配置数据,并批量设置对","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"的监听发起长轮询请求,如服务端配置项变更立即响应请求,如无数据变更则将请求挂起一段时间,直到达到超时时间。为减少对服务端压力以及保证配置中心可用性,拉取到配置数据客户端会保存一份快照在本地文件中,优先读取。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fb/fbd2068955c4b154ec6d18bc3e2e7c93.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"这里我省略了比较多的细节,如鉴权、负载均衡、高可用方面的设计(其实这部分才是真正值得学的,后边另出文讲吧),主要弄清客户端与服务端的数据交互模式。","attrs":{}}]},{"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":"下边我们以Nacos 2.0.1版本源码分析,2.0以后的版本改动较多,和网上的很多资料略有些不同地址:https://github.com/alibaba/nacos/releases/tag/2.0.1","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"客户端源码分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的客户端源码在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos-client","attrs":{}}],"attrs":{}},{"type":"text","text":"项目,其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NacosConfigService","attrs":{}}],"attrs":{}},{"type":"text","text":"实现类是所有操作的核心入口。","attrs":{}}]},{"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":"说之前先了解个客户端数据结构","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":",这里大家重点记住它,因为它几乎贯穿了Nacos客户端的所有操作,由于存在多线程场景为保证数据一致性,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"采用了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AtomicReference","attrs":{}}],"attrs":{}},{"type":"text","text":"原子变量实现。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * groupKey -> cacheData.\n */\nprivate final AtomicReference> cacheMap = new AtomicReference>(new HashMap<>());\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"是个Map结构,key为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupKey","attrs":{}}],"attrs":{}},{"type":"text","text":",是由dataId, group, tenant(租户)拼接的字符串;value为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"对象,每个dataId都会持有一个CacheData对象。","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"获取配置","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"获取配置数据的逻辑比较简单,先取本地快照文件中的配置,如果本地文件不存在或者内容为空,则再通过HTTP请求从远端拉取对应dataId配置数据,并保存到本地快照中,请求默认重试3次,超时时间3s。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6a/6a023ee7326031840b8ed2e130a75752.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"获取配置有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfig()","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfigAndSignListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"这两个接口,但","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfig()","attrs":{}}],"attrs":{}},{"type":"text","text":"只是发送普通的HTTP请求,而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getConfigAndSignListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"则多了发起长轮询和对dataId数据变更注册监听的操作","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addTenantListenersWithContent()","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic String getConfig(String dataId, String group, long timeoutMs) throws NacosException {\n return getConfigInner(namespace, dataId, group, timeoutMs);\n}\n\n@Override\npublic String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)\n throws NacosException {\n String content = getConfig(dataId, group, timeoutMs);\n worker.addTenantListenersWithContent(dataId, group, content, Arrays.asList(listener));\n return content;\n}\n","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"注册监听","attrs":{}}]},{"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":"客户端注册监听,先从","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"中拿到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"对应的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"对象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void addTenantListenersWithContent(String dataId, String group, String content,\n List extends Listener> listeners) throws NacosException {\n group = blank2defaultGroup(group);\n String tenant = agent.getTenant();\n // 1、获取dataId对应的CacheData,如没有则向服务端发起长轮询请求获取配置\n CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);\n synchronized (cache) {\n // 2、注册对dataId的数据变更监听\n cache.setContent(content);\n for (Listener listener : listeners) {\n cache.addListener(listener);\n }\n cache.setSyncWithServer(false);\n agent.notifyListenConfig();\n }\n}\n","attrs":{}}]},{"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":"如没有则向服务端发起长轮询请求获取配置,默认的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Timeout","attrs":{}}],"attrs":{}},{"type":"text","text":"时间为30s,并把返回的配置数据回填至","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"对象的content字段,同时用content生成MD5值;再通过","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"注册监听器。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8a/8a979dfccaec6e8135473a69dde09a9c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"也是个出场频率非常高的一个类,我们看到除了dataId、group、tenant、content这些相关的基础属性,还有几个比较重要的属性如:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"listeners","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"md5","attrs":{}}],"attrs":{}},{"type":"text","text":"(content真实配置数据计算出来的md5值),以及注册监听、数据比对、服务端数据变更通知操作都在这里。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bf/bf5c38f35c76276aadf9abd268366b3f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"其中","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"listeners","attrs":{}}],"attrs":{}},{"type":"text","text":"是对dataId所注册的所有监听器集合,其中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ManagerListenerWrap","attrs":{}}],"attrs":{}},{"type":"text","text":"对象除了持有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Listener","attrs":{}}],"attrs":{}},{"type":"text","text":"监听类,还有一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lastCallMd5","attrs":{}}],"attrs":{}},{"type":"text","text":"字段,这个属性很关键,它是判断服务端数据是否更变的重要条件。","attrs":{}}]},{"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":"在添加监听的同时会将","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"对象当前最新的md5值赋值给","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ManagerListenerWrap","attrs":{}}],"attrs":{}},{"type":"text","text":"对象的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lastCallMd5","attrs":{}}],"attrs":{}},{"type":"text","text":"属性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void addListener(Listener listener) {\n ManagerListenerWrap wrap =\n (listener instanceof AbstractConfigChangeListener) ? new ManagerListenerWrap(listener, md5, content)\n : new ManagerListenerWrap(listener, md5);\n}\n","attrs":{}}]},{"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":"看到这对dataId监听设置就完事了?我们发现所有操作都围着","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"结构中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"对象,那么大胆猜测下一定会有专门的任务来处理这个数据结构。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc89b88a8bb5044641f73bc647b3d145.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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","marks":[{"type":"strong","attrs":{}}],"text":"变更通知","attrs":{}}]},{"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":"客户端又是如何感知服务端数据已变更呢?","attrs":{}}]},{"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":"我们还是从头看,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NacosConfigService","attrs":{}}],"attrs":{}},{"type":"text","text":"类的构造器中初始化了一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientWorker","attrs":{}}],"attrs":{}},{"type":"text","text":",而在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientWorker","attrs":{}}],"attrs":{}},{"type":"text","text":"类的构造器中又启动了一个线程池来轮询","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/39/3912cec9242a2d1b688c1093bc6529a8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"而在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"executeConfigListen()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法中有这么一段逻辑,检查","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cacheMap","attrs":{}}],"attrs":{}},{"type":"text","text":"中dataId的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CacheData","attrs":{}}],"attrs":{}},{"type":"text","text":"对象内,MD5字段与注册的监听","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"listener","attrs":{}}],"attrs":{}},{"type":"text","text":"内的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"lastCallMd5值","attrs":{}}],"attrs":{}},{"type":"text","text":",不相同表示配置数据变更则触发","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"safeNotifyListener","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,发送数据变更通知。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"void checkListenerMd5() {\n for (ManagerListenerWrap wrap : listeners) {\n if (!md5.equals(wrap.lastCallMd5)) {\n safeNotifyListener(dataId, group, content, type, md5, encryptedDataKey, wrap);\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"safeNotifyListener()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法单独起线程,向所有对","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"注册过监听的客户端推送变更后的数据内容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/547b20857ac988a38a53582451ea99a3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"客户端接收通知,直接实现","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"receiveConfigInfo()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法接收回调数据,处理自身业务就可以了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"configService.addListener(dataId, group, new Listener() {\n @Override\n public void receiveConfigInfo(String configInfo) {\n System.out.println(\"receive:\" + configInfo);\n }\n\n @Override\n public Executor getExecutor() {\n return null;\n }\n});\n","attrs":{}}]},{"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":"为了理解更直观我用测试demo演示下,获取服务端配置并设置监听,每当服务端配置数据变化,客户端监听都会收到通知,一起看下效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String[] args) throws NacosException, InterruptedException {\n String serverAddr = \"localhost\";\n String dataId = \"test\";\n String group = \"DEFAULT_GROUP\";\n Properties properties = new Properties();\n properties.put(\"serverAddr\", serverAddr);\n ConfigService configService = NacosFactory.createConfigService(properties);\n String content = configService.getConfig(dataId, group, 5000);\n System.out.println(content);\n configService.addListener(dataId, group, new Listener() {\n @Override\n public void receiveConfigInfo(String configInfo) {\n System.out.println(\"数据变更 receive:\" + configInfo);\n }\n @Override\n public Executor getExecutor() {\n return null;\n }\n });\n\n boolean isPublishOk = configService.publishConfig(dataId, group, \"我是新配置内容~\");\n System.out.println(isPublishOk);\n\n Thread.sleep(3000);\n content = configService.getConfig(dataId, group, 5000);\n System.out.println(content);\n}\n","attrs":{}}]},{"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":"结果和预想的一样,当向服务端","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"publishConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"数据变化后,客户端可以立即感知,愣是用主动拉","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pull","attrs":{}}],"attrs":{}},{"type":"text","text":"模式做出了服务端实时推送的效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"数据变更 receive:我是新配置内容~\ntrue\n我是新配置内容~\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"服务端源码分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的服务端源码主要在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos-config","attrs":{}}],"attrs":{}},{"type":"text","text":"项目的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ConfigController","attrs":{}}],"attrs":{}},{"type":"text","text":"类,服务端的逻辑要比客户端稍复杂一些,这里我们重点看下。","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"处理长轮询","attrs":{}}]},{"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":"服务端对外提供的监听接口地址","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/v1/cs/configs/listener","attrs":{}}],"attrs":{}},{"type":"text","text":",这个方法内容不多,顺着","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"doPollingConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"往下看。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/30/30f4ed492c6964f96694c318c34e0257.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"服务端根据请求","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"header","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Long-Pulling-Timeout","attrs":{}}],"attrs":{}},{"type":"text","text":"属性来区分请求是长轮询还是短轮询,这里咱们只关注长轮询部分,接着看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LongPollingService","attrs":{}}],"attrs":{}},{"type":"text","text":"(记住这个service很关键)类中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addLongPollingClient()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法是如何处理客户端的长轮询请求的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4b/4b0495887953464ee50aa9800289c0ca.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"正常客户端默认设置的请求超时时间是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"30s","attrs":{}}],"attrs":{}},{"type":"text","text":",但这里我们发现服务端“偷偷”的给减掉了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"500ms","attrs":{}}],"attrs":{}},{"type":"text","text":",现在超时时间只剩下了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"29.5s","attrs":{}}],"attrs":{}},{"type":"text","text":",那为什么要这样做呢?","attrs":{}}]},{"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":"用官方的解释之所以要提前500ms响应请求,为了最大程度上保证客户端不会因为网络延时造成超时,考虑到请求可能在负载均衡时会耗费一些时间,毕竟","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"最初就是按照阿里自身业务体量设计的嘛!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/00267f3c087c82c7924f85b1ffb78b1a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"此时对客户端提交上来的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupkey","attrs":{}}],"attrs":{}},{"type":"text","text":"的MD5与服务端当前的MD5比对,如","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"md5","attrs":{}}],"attrs":{}},{"type":"text","text":"值不同,则说明服务端的配置项发生过变更,直接将该","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupkey","attrs":{}}],"attrs":{}},{"type":"text","text":"放入","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"changedGroupKeys","attrs":{}}],"attrs":{}},{"type":"text","text":"集合并返回给客户端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"MD5Util.compareMd5(req, rsp, clientMd5Map)\n","attrs":{}}]},{"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":"如未发生变更,则将客户端请求挂起,这个过程先创建一个名为","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling","attrs":{}}],"attrs":{}},{"type":"text","text":"的调度任务","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Runnable","attrs":{}}],"attrs":{}},{"type":"text","text":",并提交给","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"scheduler","attrs":{}}],"attrs":{}},{"type":"text","text":"定时线程池延后","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"29.5s","attrs":{}}],"attrs":{}},{"type":"text","text":"执行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ConfigExecutor.executeLongPolling(\n new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag));\n","attrs":{}}]},{"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":"这里每个长轮询任务携带了一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"asyncContext","attrs":{}}],"attrs":{}},{"type":"text","text":"对象,使得每个请求可以延迟响应,等延时到达或者配置有变更之后,调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"asyncContext.complete()","attrs":{}}],"attrs":{}},{"type":"text","text":"响应完成。","attrs":{}}]},{"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":"asyncContext 为 Servlet 3.0新增的特性,异步处理,使Servlet线程不再需要一直阻塞,等待业务处理完毕才输出响应;可以先释放容器分配给请求的线程与相关资源,减轻系统负担,其响应将被延后,在处理完业务或者运算后再对客户端进行响应。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0a/0a94a301e96c110bf0f6093d8fee5fe9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"codeinline","content":[{"type":"text","text":"ClientLongPolling","attrs":{}}],"attrs":{}},{"type":"text","text":"任务被提交进入延迟线程池执行的同时,服务端会通过一个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"队列保存所有正在被挂起的客户端长轮询请求任务,这个是客户端注册监听的过程。","attrs":{}}]},{"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":"如延时期间客户端据数一直未变化,延时时间到达后将本次长轮询任务从","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"队列剔除,并响应请求","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"response","attrs":{}}],"attrs":{}},{"type":"text","text":",这是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"取消监听","attrs":{}}],"attrs":{}},{"type":"text","text":"。收到响应后客户端再次发起长轮询,循环往复。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/995d70e714ff0cfbfa707f2617e7dff9.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"到这我们知道服务端是如何挂起客户端长轮询请求的,一旦请求在挂起期间,用户通过管理平台操作了配置项,或者服务端收到了来自其他客户端节点修改配置的请求。","attrs":{}}]},{"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":"怎么能让对应已挂起的任务立即取消,并且及时通知客户端数据发生了变更呢?","attrs":{}}]},{"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","marks":[{"type":"strong","attrs":{}}],"text":"数据变更","attrs":{}}]},{"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":"管理平台或者客户端更改配置项接位置","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ConfigController","attrs":{}}],"attrs":{}},{"type":"text","text":"中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"publishConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab33d3772da6bbdd54d99b5616ffde62.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"值得注意得是,在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"publishConfig","attrs":{}}],"attrs":{}},{"type":"text","text":"接口中有这么一段逻辑,某个","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dataId","attrs":{}}],"attrs":{}},{"type":"text","text":"配置数据被修改时会触发一个数据变更事件","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Event","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime()));\n","attrs":{}}]},{"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":"仔细看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"LongPollingService","attrs":{}}],"attrs":{}},{"type":"text","text":"会发现在它的构造方法中,正好订阅了数据变更事件,并在事件触发时执行一个数据变更调度任务","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DataChangeTask","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/46c645877b540e5b4dc0a0198763d5b0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"codeinline","content":[{"type":"text","text":"DataChangeTask","attrs":{}}],"attrs":{}},{"type":"text","text":"内的主要逻辑就是遍历","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"队列,上边我们知道,这个队列中维护的是所有客户端的长轮询请求任务,从这些任务中找到包含当前发生变更的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"groupkey","attrs":{}}],"attrs":{}},{"type":"text","text":"的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ClientLongPolling","attrs":{}}],"attrs":{}},{"type":"text","text":"任务,以此实现数据更变推送给客户端,并从","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"allSubs","attrs":{}}],"attrs":{}},{"type":"text","text":"队列中剔除此长轮询任务。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc4b04499b1abd4a285eb83f42194067.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"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":"而我们在看给客户端响应","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"response","attrs":{}}],"attrs":{}},{"type":"text","text":"时,调用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"asyncContext.complete()","attrs":{}}],"attrs":{}},{"type":"text","text":"结束了异步请求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/801c446d177af3b53d897f7a633ce518.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"结束语","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上边只揭开了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"配置中心的冰山一角,实际上还有非常多重要的技术细节都没提及到,建议大家没事看看源码,源码不需要通篇的看,只要抓住核心部分就够了。就比如今天这个题目以前我真没太在意,突然被问一下子吃不准了,果断看下源码,而且这样记忆比较深刻(别人嚼碎了喂你的知识总是比自己咀嚼的差那么点意思)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"nacos","attrs":{}}],"attrs":{}},{"type":"text","text":"的源码我个人觉得还是比较","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"朴素","attrs":{}},{"type":"text","text":"的,代码并没有过多炫技,看起来相对轻松。大家不要对看源码有什么抵触,它也不过是别人写的业务代码而已,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"just so so","attrs":{}},{"type":"text","text":"!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule","attrs":{}},{"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","marks":[{"type":"strong","attrs":{}}],"text":"我是小富~","attrs":{}},{"type":"text","text":",如果对你有用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在看","attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"关注","attrs":{}},{"type":"text","text":"支持下,咱们下期见~","attrs":{}}]},{"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":"整理了几百本各类技术电子书,有需要的同学自取。技术群快满了,想进的同学可以加我好友,和大佬们一起吹吹技术。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/chengxy-nds/Springboot-Notebook","title":"","type":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"电子书地址","attrs":{}}]}]},{"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":"个人公众号: ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"程序员内点事","attrs":{}},{"type":"text","text":",欢迎交流","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章