版本
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耗时
业务逻辑:
修改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,这样后端可以确保这期间没有其他人修改过这个配置文件。发布配置修改事件
ConfigDataChangeEvent
NotifyCenter.publishEvent(event);
进行事件发布,NotifyCenter
会找到该消息对应的DefaultPublisher
线程异步处理事件
默认ConfigDataChangeEvent
事件有两个监听器AsyncNotifyService
和ExternalDumpService
。我们先向下看,后面再描述这个异步流程。紧接着的同步操作是向"com.alibaba.nacos.config.traceLog"logger中写入操作日志,这里只记录日志内容的md5,操作者ip等,没有日志的具体内容
继续讲解
ConfigDataChangeEvent
事件的异步操作
其实此事件就是让nacos集群的所有节点执行ExternalDumpService#dump
。本地会监听该事件,执行ExternalDumpService#dump
。AsyncNotifyService
是向其它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
中有LongPollingService
和RpcConfigChangeNotifier
两个subscriber。
转到对应的EventPublisher
线程,开始执行两个subscriber。
LongPollingService
ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey));
通知LongPolling的客户端配置有变化。旧版本使用长轮询的方式,这里会拿到空的客户端列表,也就没有任何操作。
RpcConfigChangeNotifier
在本机查找关心这个配置文件的所有nacos客户端,
在RpcPushTask
中,向客户端异步发送ConfigChangeNotifyRequest
请求,ConfigChangeNotifyRequest
只是一个普通Java类,经由GrpcUtils.convert()
函数转化为Payload
grpc消息。这个消息只发送配置文件名称,没有具体配置内容。如果推送失败,会使用指数退避的时间持续重试,直到达到重试次数。
发布灰度配置逻辑与上面的类似,一个事务内,插入config_info_gray表,插入his_config_info表。事务成功后,发布ConfigDataChangeEvent事件。
表结构
- config_info表
主键id
tenant_id,groupid,dataid 一起是一个唯一索引,其中tenant_id就是namespace
配置文件直接原封不动存入content字段
md5判断表内容是否有变更
插入时唯一索引避免重复插入
变更时where语句加上md5字段,确保只有一个人变更成功,同时旧配置插入历史记录表his_config_info。
- config_info_gray 表
主键id
data_id
,group_id
,tenant_id
,gray_name
一起是一个唯一索引,其中tenant_id就是namespace
局限
灰度发布 beta发布
每个配置文件,只能同时存在一种beta。不过看后端代码应该是支持多个灰度,但是web页面不支持。
beta区分实例是能到ip地址,一个ip起两个服务无法区分,同时生效
灰度发布生效期间,无法更改主配置,无法再次修改beta配置