Nacos Server 源码分析

版本

Nacos 2.5.1

启动方式

console模块下的com.alibaba.nacos.Nacos,启动spring应用
使用Intellij IDEA启动时,可以使用VM options-Dnacos.standalone=true,启动单机模式

配置文件位于console/src/main/resources/application.properties
sql建表语句位于config/src/main/resources/META-INF/mysql-schema.sql

开发的集群模式启动
application.properties中配置集群列表"nacos.member.list=192.168.3.10:8648,192.168.3.10:8748,192.168.3.10:8848"
注意不要写127.0.0.1

启动命令VM options填写"-Dserver.port=8848
-Dnacos.home=E:\Gits\nacos\nacos-cluster-0"

nacos.home的相关代码位于com.alibaba.nacos.sys.env.EnvUtil#getNacosHome

启动流程

com.alibaba.nacos.core.code.SpringApplicationRunListener调用StartingApplicationListener加了新的配置源EnvUtil.getApplicationConfFileResource()

接受请求

配置列表 模糊查询

fuzzySearchConfig:419, ConfigController (com.alibaba.nacos.config.server.controller)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:205, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:150, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:903, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:809, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1072, DispatcherServlet (org.springframework.web.servlet)
doService:965, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:529, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:623, HttpServlet (javax.servlet.http)
internalDoFilter:199, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:51, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:42, XssFilter (com.alibaba.nacos.console.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:91, CorsFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:84, ParamCheckerFilter (com.alibaba.nacos.core.paramcheck)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:109, NacosHttpTpsFilter (com.alibaba.nacos.core.control.http)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:69, AuthFilter (com.alibaba.nacos.core.auth)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:67, NacosWebFilter (com.alibaba.nacos.config.server.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:218, FilterChainProxy (org.springframework.security.web)
doFilter:190, FilterChainProxy (org.springframework.security.web)
invokeDelegate:354, DelegatingFilterProxy (org.springframework.web.filter)
doFilter:267, DelegatingFilterProxy (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:96, WebMvcMetricsFilter (org.springframework.boot.actuate.metrics.web.servlet)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:57, HttpRequestContextFilter (com.alibaba.nacos.core.context.remote)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
invoke:168, StandardWrapperValve (org.apache.catalina.core)
invoke:90, StandardContextValve (org.apache.catalina.core)
invoke:482, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:130, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
invoke:660, AbstractAccessLogValve (org.apache.catalina.valves)
service:346, CoyoteAdapter (org.apache.catalina.connector)
service:396, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:937, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

HttpRequestContextFilter
ThreadLocal RequestContext中填入http请求等信息,RequestContext类是nacos自定义的类。

AuthFilter
鉴权功能,@Secured注解相关鉴权功能

ParamCheckerFilter
@ExtractorManager.Extractor注解解析请求参数,使用AbstractParamChecker对请求参数进行通用的检查。

XssFilter
response设置"Content-Security-Policy"header,xss相关

NacosWebFilter
"/v1/cs/*"专属的filter
request设置encoding UTF-8,response设置content type header 为 json

NacosHttpTpsFilter
"/v1/ns/", "/v2/ns/", "/v1/cs/", "/v2/cs/"专属的filter
@Secured注解相关鉴权功能
应该是tps流量控制插件,但是默认实现是无管控

查询配置

比如配置列表的模糊查询接口ConfigController#fuzzySearchConfig,调用ConfigInfoPersistService对数据库进行查询。

默认是derby数据库,通过EmbeddedConfigInfoPersistServiceImpl实现类查询,
内部逻辑就是根据参数组装sql,调用PaginationHelper.fetchPageLimit。然后是BaseDatabaseOperate,使用JdbcTemplate对数据库进行查询,先查count,再查列表数据。

配置为mysql后使用的是ExternalConfigInfoPersistServiceImpl实现类,与derby的流程一致,唯一区别是PaginationHelper.fetchPageLimit内直接调用JdbcTemplate对数据库进行查询。

获取配置详情接口也是普通的查询mysql

发布修改配置

com.alibaba.nacos.config.server.service.ConfigOperationService#publishConfig
先是一些aop

CapacityManagementAspect
检查配置文件大小是否超过限制,默认关闭,不检查。
大小限制数据来自于group_capacity tenant_capacity表,有rest接口可以写入数据,但没看到webui界面有相关设置。

ConfigChangeAspect
为config change plugin提供aop接入点,默认没有插件

RequestLogAspect
MetricsMonitor统计publish次数,就写config耗时

业务逻辑:

  1. 修改mysql内的配置时,使用事务
    事务内进行更新配置,插入配置历史(his_config_info表)两个操作
    其中更新配置语句是UPDATE config_info SET ... WHERE data_id=? AND group_id=? AND tenant_id=? AND (md5=? OR md5 IS NULL OR md5='')
    where语句额外使用md5作为判断条件。md5是根据配置文件内容计算出来的,web界面打开配置时会从后端得到当前配置的md5,调用配置更新接口时会传入就旧配置的md5,这样后端可以确保这期间没有其他人修改过这个配置文件。

  2. 发布配置修改事件ConfigDataChangeEvent
    NotifyCenter.publishEvent(event);进行事件发布,NotifyCenter会找到该消息对应的DefaultPublisher线程异步处理事件
    默认ConfigDataChangeEvent事件有两个监听器AsyncNotifyServiceExternalDumpService。我们先向下看,后面再描述这个异步流程。

  3. 紧接着的同步操作是向"com.alibaba.nacos.config.traceLog"logger中写入操作日志,这里只记录日志内容的md5,操作者ip等,没有日志的具体内容

  4. 继续讲解ConfigDataChangeEvent事件的异步操作

其实此事件就是让nacos集群的所有节点执行ExternalDumpService#dump。本地会监听该事件,执行ExternalDumpService#dumpAsyncNotifyService是向其它nacos节点发送grpc消息,让其他节点执行ExternalDumpService#dump

AsyncNotifyService
MetricsMonitor统计配置变更次数
ServerMemberManager获取其它nacos实例地址
com.alibaba.nacos.config.server.utils.ConfigExecutor#executeAsyncNotify再次进行异步执行
通过grpc向nacos内的其他节点发送ConfigChangeClusterSyncRequest消息。
grpc服务定义于nacos_grpc_service.proto文件内,是一个通用的消息服务,
其它nacos节点的GrpcRequestAcceptor是处理该消息的服务实现类。GrpcRequestAcceptor#request内部进行消息分发,最终由ConfigChangeClusterSyncRequestHandler#handle处理,这里的内部也是调用ExternalDumpService#dump进行处理。

ExternalDumpService#dump
TaskManager增加新的任务DumpTask,再次进行异步处理。
名为"com.alibaba.nacos.server.DumpTaskManager"的TaskManager.ProcessRunnable线程处理这些任务。任务key为"dataid+groupid+namespaceid"。任务交由com.alibaba.nacos.config.server.service.dump.processor.DumpProcessor#process处理。
DumpProcessor#process先去库中查询最新的配置信息,然后调用com.alibaba.nacos.config.server.service.dump.DumpConfigHandler#configDump直接处理填充好的ConfigDumpEvent事件。com.alibaba.nacos.config.server.service.ConfigCacheService#dump
如果内存中该配置文件的md5发生了变化,将会
写入本地文件缓存,位于"{nacos.home}/data/config-data/"下
更新内存中缓存的md5值

NotifyCenter发布LocalDataChangeEvent事件,事件对应的EventPublisher中有LongPollingServiceRpcConfigChangeNotifier两个subscriber。
转到对应的EventPublisher线程,开始执行两个subscriber。

LongPollingService
ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey));
通知LongPolling的客户端配置有变化。旧版本使用长轮询的方式,这里会拿到空的客户端列表,也就没有任何操作。

RpcConfigChangeNotifier
在本机查找关心这个配置文件的所有nacos客户端,
RpcPushTask中,向客户端异步发送ConfigChangeNotifyRequest请求,ConfigChangeNotifyRequest只是一个普通Java类,经由GrpcUtils.convert()函数转化为Payloadgrpc消息。这个消息只发送配置文件名称,没有具体配置内容。如果推送失败,会使用指数退避的时间持续重试,直到达到重试次数。

发布灰度配置逻辑与上面的类似,一个事务内,插入config_info_gray表,插入his_config_info表。事务成功后,发布ConfigDataChangeEvent事件。

表结构

  1. config_info表
    主键id
    tenant_id,groupid,dataid 一起是一个唯一索引,其中tenant_id就是namespace

配置文件直接原封不动存入content字段
md5判断表内容是否有变更
插入时唯一索引避免重复插入
变更时where语句加上md5字段,确保只有一个人变更成功,同时旧配置插入历史记录表his_config_info。

  1. config_info_gray 表
    主键id
    data_id,group_id,tenant_id,gray_name 一起是一个唯一索引,其中tenant_id就是namespace

局限

灰度发布 beta发布

每个配置文件,只能同时存在一种beta。不过看后端代码应该是支持多个灰度,但是web页面不支持。
beta区分实例是能到ip地址,一个ip起两个服务无法区分,同时生效
灰度发布生效期间,无法更改主配置,无法再次修改beta配置

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容