Description
1. 背景与需求
在用户的应用场景中,存在对配置批次监听的需求,例如需要监听同一个 group 下的部分或所有的配置变化。目前,用户只能通过精确监听单个配置。
为了解决这一问题,引入了模糊监听的概念,允许用户监听符合Pattern
的配置变化(新增、删除、不包含更新)。通过模糊监听,用户可以获取到符合指定Pattern
的配置元数据,再结合精确监听,可以更加灵活地实现配置变化的监听与处理。
因此,用户可以通过以下方式实现对指定 group 下配置的监听:
使用模糊监听,监听符合指定Pattern
的配置变化(新增、删除、不包含更新),例如匹配某个 group 下所有配置的模式。
结合精确监听,对特定的配置进行精确监听(新增、删除、更新),以获取对单个配置变化的实时通知。
从而实现整个配置的模糊监听。
2. 实现方案
2.1. 整体流程图
2.2. Pattern存储规则
使用Pattern
实现在特定命名空间和组中对一组具有特定前缀的配置进行监听,这使得配置中心能够更灵活地处理不同场景下的配置需求。
Pattern
存储规则图:
由于客户端的ConfigServer
与namespace
绑定,而服务端可能接收到不同namespace
客户端的模糊订阅请求,所以服务端存储需要带上namespace
,而客户端对namespace
不进行存储。
上图中的通配符,匹配规则如下:
- 订阅
namespace= public
,group = group
下dataId
前缀为test
的所有配置
2.3. 客户端模糊监听上下文实体说明
FuzzyListenContext
:模糊监听上下文实体(后续该实体均用“上下文”描述。),与模糊监听配置表达式一对一关联关系,并且包含了以下属性:
envName
:环境名称,表示监听配置所在的环境。taskId
:任务编号,主要用于分片处理,将模糊监听上下文分配给不同的任务。dataIdPattern
:模糊监听表达式。group
:配置的分组信息。tenant
:租户信息,用于区分不同租户的配置。isConsistentWithServer
:是否与服务端同步,表示此模糊监听表达式是否已同步到服务端。isInitializing
:是否正在初始化模糊监听上下文列表。isDiscard
:是否丢弃,当listeners
为空时,isDiscard = true
,表示此模糊监听上下文可以丢弃,即通知服务端取消对该上下文的监听。dataIds
:此上下文所匹配的配置数据ID列表。listeners
:监听器列表,用于存储与此上下文关联的监听器。
该实体描述了客户端中上下文的各种属性和状态,以及与之关联的配置数据ID列表和监听器列表。
2.4. 客户端初始化模糊监听上下文
SDK
端需要存储当前ClientWorker
中所有的Context
,并且提供<KepPattern, Context>
的Map映射关系,主要是方便索引,所以ClientWorker
中新增AtomicReference<Map<String, FuzzyListenContext>> fuzzyListenContextMap
。
客户端初始化模糊监听上下文流程图:
当新增FuzzyListrener
时,将会通过Pattern
判断是否存在上下文,其实就是判断是否重复监听,如果是监听,则直接获取到Context
中的配置回调Listener
即可。
注意:当重复监听时,这里直接回调Listener
,但是不能回调所有的Listener
,所以Listener
需要UUID
标识
2.5. 客户端执行模糊监听上下文
在 ClientWorker
中新增了 fuzzyListenContextMap
,该 Map 包含了所有的上下文。
执行配置模糊监听的流程图:
说明:
- 客户端定时(每5秒)执行配置模糊监听,或者新增上下文。
- 根据所有的上下文,判断是否需要进行同步,具体判断逻辑包括模糊监听上下文的同步标识以及是否达到全量同步的条件。
- 根据丢弃标识判断出上下文是需要创建模糊监听还是取消模糊监听,分别构造批次模糊监听请求。
- 发起批次模糊监听请求。
2.5.1. 批次模糊监听请求字段
ConfigBatchFuzzyListenRequest
:配置批次模糊监听请求对象,包含以下字段:- contexts:上下文集合,每个上下文包含以下字段:
tenant
:租户信息,用于区分不同租户的配置。group
:配置的分组信息。dataIdPattern
:数据ID模式,用于匹配配置的数据ID。dataIds
:客户端已有的配置数据ID集合,用于与服务端已有的配置进行匹配。listen
:指示是创建监听还是取消监听。isInitializing
:是否初始化中,用于区分上下文是否第一次执行模糊监听。该字段在通知客户端时,分别对应LISTEN_INIT
和ADD_CONFIG
事件,目前并没有实际区别,但为了方便扩展,在后续可能需要对第一次执行模糊监听进行特殊处理时,可以通过该字段来实现。
- contexts:上下文集合,每个上下文包含以下字段:
2.5.2. 模糊监听上下文发送分批
模糊监听上下文分批,主要沿用精确监听的分批实现,实现如下:
通过PER_TASK_CONTEXT_SIZE_KEY
配置指定每个Task
所能容纳的上下文大小,上下文中存在taskId
字段,每初始化上下文时,计算该配置属于哪个Task
。
针对属于同一个Task
的模糊监听上下文,将其进行分批合并。合并后的批次包含指定数量的上下文,根据需要将批次发送给服务端。
2.6. 服务端处理批次模糊监听上下文
服务端处理批次模糊监听流程图:
说明:
服务端处理 ConfigBatchFuzzyListenRequest
的流程分为两步,通过事件 ConfigBatchFuzzyListenEvent
进行解耦:
- 维护
ConfigChangeListenContext#keyPatternContext
:服务端维护ConfigChangeListenContext
中的keyPatternContext
,用于存储模糊监听表达式和其对应的Connection
的映射关系。 - 计算变更的配置并推送至客户端:根据客户端已有的配置和服务端Cache的配置,计算出变更的配置,并将变更的配置推送至客户端。
2.6.1. 模糊监听通知请求字段
FuzzyListenNotifyDiffRequest
:用于通知配置差异的模糊监听请求对象,包含以下字段:groupKeyPattern
:用于匹配配置组键的模式字符串。- contexts:包含配置信息的上下文集合,每个上下文包含以下字段:
tenant
:与配置关联的租户。group
:与配置关联的组。dataId
:配置的数据ID。type
:配置变更事件的类型。
2.6.2. 分批通知
为了有效处理同一模糊监听下的大量配置,我们采用了分批通知的策略。具体流程如下:
- 批次大小配置: 我们通过配置项
nacos.config.push.batchSize
来指定每个批次的大小。这个配置决定了每次通知中处理的配置数量。 - 分批处理: 当需要发送通知时,我们将大量配置按照指定的批次大小进行划分。每个批次中包含的配置数量不会超过配置的批次大小。
- 发送通知: 对于每个批次的配置通知,我们将它们分别发送给客户端。在完成每个批次的通知后,会发送一个特殊的完成请求,通知客户端该批次的处理已经完成。
2.7. 服务端关于配置变更的推送
服务端关于配置变更的推送流程图:
说明:当服务端发生配置变更时,即发布了 LocalDataChangeEvent
事件。处理过程如下:
- 根据变更的配置的唯一标识
GroupKey
,与服务端的缓存进行匹配,确定该配置是否存在于服务端的缓存中。 - 如果配置存在于缓存中,则通知客户端进行相应的处理。
2.7.1. 模糊监听变更通知字段说明
FuzzyListenNotifyChangeRequest
:用于通知模糊监听配置变更的请求对象,包含以下字段:tenant
:配置变更的租户信息。group
:配置变更的组信息。dataId
:配置变更的数据ID。isExist
:指示配置是否存在的布尔值。
2.8 客户端接收配置变更与差异通知
当服务端发生配置变更时,或者有配置差异需要通知客户端时,客户端接收通知并处理的流程如下:
- 服务端向客户端发送相应的请求(
FuzzyListenNotifyChangeRequest
或FuzzyListenNotifyDiffRequest
)。 - 客户端根据接收到的请求以及当前已存在的配置信息,计算出需要添加或删除的配置,并相应地发布事件(如
FuzzyListenNotifyEvent
)。 FuzzyListenNotifyEvent
的订阅者接收事件去回调Listener
。
3.0 问题
3.0.1 模糊监听是否需要监听配置内容的变更?
@hujun-w-2 提出以这种方案实现,真正的监听逻辑还是依赖精确监听实现的,在监听配置过多时,客户端启动、长连接断开时,会对服务端造成较大消耗,模糊监听应该支持配置内容变更的监听。