掘金 后端 ( ) • 2024-06-15 09:28

一 前言

1.1背景说明

目前, Kitex 用户修改客户端/服务器的配置时,需要调用option.go的函数手动生成Option配置类,进行初始化:

cli, err := somService.NewClient(svcName, client.WithHostPorts(addr))*

如果需要修改配置,就需要直接修改代码并重新编译项目。

在查阅资料的过程中,我发现在Kitex框架中已经存在更改运行时状态的扩展config-file,可以从json或yml文件中读取熔断、请求超时、失败重置的运行时状态。

1.2 代码结构


├── CODE_OF_CONDUCT.md

├── CONTRIBUTING.md

├── LICENSE-APACHE

├── README.md

├── README_CN.md

├── _typos.toml

├── check_branch_name.sh

├── client

│   ├── circuit_breaker.go

│   ├── client.go

│   ├── retry.go

│   ├── rpc_timeout.go

│   └── suite.go

├── example

│   ├── client

│   │   ├── doc.go

│   │   ├── kitex_client.json

│   │   └── main.go

│   ├── go.mod

│   ├── go.sum

│   └── server

│       ├── kitex_server.json

│       └── main.go

├── filewatcher

│   └── filewatcher.go

├── go.mod

├── go.sum

├── mock

│   └── filewatcher_mock.go

├── monitor

│   ├── monitor.go

│   └── monitor_test.go

├── parser

│   ├── client.go

│   ├── parser.go

│   └── server.go

├── profile

│   └── README.md

├── server

│   ├── limit.go

│   ├── server.go

│   └── suite.go

├── testdata

│   └── test.json

└── utils

    ├── set.go

    ├── set_test.go

└── utils.go

Ÿ   client 目录:包含客户端相关的 Go 代码文件,如 circuit_breaker.go(可能包含断路器的实现)、client.go(可能包含客户端的主要逻辑)、retry.go(可能包含重试逻辑)、rpc_timeout.go(可能包含 RPC 超时设置)和 suite.go(可能包含一些客户端的辅助函数或方法)。

Ÿ   example 目录:包含示例代码,包括一个客户端和一个服务器,每个都有一个主要的 main.go 文件和一个 JSON 配置文件。

Ÿ   filewatcher 目录:包含 filewatcher.go,可能是一个用于监视文件变化的工具。

Ÿ   mock 目录:包含 filewatcher_mock.go,可能是用于测试的模拟文件监视器。

Ÿ   monitor 目录:包含 monitor.go 和 monitor_test.go,可能是用于监控和测试监控功能的代码。

Ÿ   parser 目录:包含 client.go、parser.go 和 server.go,可能是用于解析客户端和服务器配置的代码。

Ÿ   profile 目录:包含 README.md,可能是关于性能分析的文档。

Ÿ   server 目录:包含服务器相关的 Go 代码文件,如 limit.go(可能包含限流逻辑)、server.go(可能包含服务器的主要逻辑)和 suite.go(可能包含一些服务器的辅助函数或方法)。

Ÿ   testdata 目录:包含 test.json,可能是用于测试的数据。

Ÿ   utils 目录:包含 set.go、set_test.go 和 utils.go,可能是一些通用的工具函数和测试。

Ÿ   go.mod 和 go.sum:这些是 Go 项目的依赖管理文件。


二 原理分析

2.1 监听:fsnotify

fsnotify 是一个 Go 语言库,用于文件系统的通知。它是一个跨平台的库,可以在 Linux、BSD、OS X 和 Windows 等操作系统上使用。fsnotify 可以监视文件或目录的创建、删除、移动、重命名和修改等事件。

在本项目中,fsnotify 库被用于监视特定文件的变化。具体实现在 filewatcher.go 文件中的 fileWatcher 结构体和相关方法。  fileWatcher 结构体中有一个 *fsnotify.Watcher 类型的字段 watcher,这是 fsnotify 的核心组件,用于监视文件系统事件。在 NewFileWatcher 函数中,创建了一个新的 fsnotify.Watcher 实例,并将其赋值给 fileWatcher 结构体的 watcher 字段。在 StartWatching 方法中,调用了 fsnotify.Watcher 的 Add 方法,将需要监视的文件路径添加到监视器中,然后在一个新的 goroutine 中启动了文件监视。具体代码段如下:

image.png 在 start 方法中,使用了一个无限循环来接收和处理 fsnotify.Watcher 的事件。当接收到文件写入事件(fsnotify.Write)时,会调用 CallOnceAll 方法,该方法会读取文件内容,并将内容传递给所有注册的回调函数。如果接收到文件被删除的事件(fsnotify.Remove),则会停止文件监视。

2.2 加载:基于mapstructure的字节流解码

mapstructure 是一个 Go 语言库,用于将通用的 map 结构解码到 Go 语言的结构体中。它主要用于在处理 JSON、YAML 或其他类似的数据结构时,将这些数据结构解码到具体的 Go 语言结构体中。在使用 mapstructure 进行解码时,首先需要定义一个 Go 语言的结构体,该结构体的字段名和 map 中的键名对应。然后,可以使用 mapstructure.Decode 函数将 map 解码到这个结构体中。

以本项目中的ClientFileConfig为例:

image.png

其中,rpctimeout.RPCTimeout、retry.Policy、circuitbreak.CBConfig均为Kitex库中已定义好的类,因此此处不需要重新声明。如果用户需要使用自定义的配置项结构,则需要按照该格式为字段打上Tag。然后,我们可以使用 mapstructure.Decode 将数据解码到这个结构体中。

转存失败,建议直接上传图片文件

image.png     例如 map[string]interface{}{"timeout": value},使用 mapstructure 库将这个 map 解码到 ClientFileConfig 结构体时,value 会被解码到 Timeout 字段。client和server的Config是通过monitor内不同的manager区分。例如client/suite.go在Options()中设置了client的configManager:

image.png

image.png

三 模块结构

3.1 Parser:文件的编码与解码

Parser 是一个在项目中用于处理配置文件编码和解码的模块。Parser 模块的作用是提供一个通用的接口,用于将不同格式的配置数据解码到 Go 语言的结构体中。它定义了一个 ConfigParser 接口和一个 Parser 结构体,这个结构体实现了 ConfigParser 接口。

ConfigParser 接口定义了一个 Decode 方法,这个方法接收三个参数:配置类型(kind)、配置数据(data)和一个空接口(config)。这个方法的作用是将 data 中的数据解码到 config 中,解码的方式取决于 kind。

image.png     Parser 结构体实现了 ConfigParser 接口的 Decode 方法。在这个方法中,根据 kind 的值,选择不同的解码方式。如果 kind 是 JSON,则使用 sonic.Unmarshal 方法进行解码;如果 kind 是 YAML,则使用 yaml.Unmarshal 方法进行解码。如果 kind 不是这两种类型,那么会返回一个错误。

DefaultConfigParser 函数返回一个默认的 ConfigParser 实例,这个实例是 Parser 结构体的一个实例。

DefaultConfigParam 函数返回一个默认的 ConfigParam 实例,这个实例的 Type 字段的值是 JSON。

3.2 Filewatcher:文件监听

FileWatcher 是一个在项目中用于监视文件变化的模块。它的主要功能是监视指定文件的变化,并在文件发生变化时执行注册的回调函数。在 filewatcher.go 文件中,定义了一个 FileWatcher 接口和一个 fileWatcher 结构体,这个结构体实现了 FileWatcher 接口。

Ÿ   FileWatcher 接口定义了以下方法:

Ÿ   FilePath():返回当前对象正在监听的文件地址。

Ÿ   CallbackSize():返回回调函数的数量。

Ÿ   RegisterCallback(callback func(data []byte)) int64:设置回调函数,当文件发生变化时,这些回调函数会被执行。

Ÿ   DeregisterCallback(uniqueID int64):移除回调函数。

Ÿ   StartWatching() error:开始监视文件变化。

Ÿ   StopWatching():停止监视文件变化。

Ÿ   CallOnceAll() error:一次性调用所有的回调函数。

Ÿ   CallOnceSpecific(uniqueID int64) error:通过唯一ID一次性调用特定的回调函数。

fileWatcher 实现了FileWatcher接口,其结构体中有以下字段:

Ÿ   filePath:需要监视的文件路径。

Ÿ   callbacks:注册的回调函数,当文件发生变化时,这些函数会被执行。

Ÿ   watcher:fsnotify.Watcher 实例,用于监视文件系统事件。

Ÿ   done:一个用于通知监视器停止的通道。

Ÿ   lock:一个读写锁,用于保护 callbacks 的并发访问。

Ÿ   counter:一个原子计数器,用于生成回调函数的唯一ID。

image.png NewFileWatcher 函数用于创建一个新的 FileWatcher 实例。在这个函数中,创建了一个新的 fsnotify.Watcher 实例,并将其赋值给 fileWatcher 结构体的 watcher 字段。

StartWatching 方法用于开始监视文件变化。在这个方法中,将需要监视的文件路径添加到 fsnotify.Watcher 中,并在一个新的 goroutine 中启动文件监视。

start 方法用于处理 fsnotify.Watcher 的事件。当接收到文件写入事件时,会调用 CallOnceAll 方法,该方法会读取文件内容,并将内容传递给所有注册的回调函数。如果接收到文件被删除的事件,则会停止文件监视。

3.3 ConfigMonitor:配置管理中心

ConfigMonitor 是一个在项目中用于管理配置的模块。它的主要职责是处理所有与配置相关的操作,包括读取配置文件,解析配置数据,提供配置数据的访问接口,以及监视配置文件的变化。

在 monitor.go 文件中,定义了一个 ConfigMonitor 接口和一个 configMonitor 结构体,这个结构体实现了 ConfigMonitor 接口。

ConfigMonitor 接口定义了以下方法:

Ÿ   Key():返回配置文件的键。

Ÿ   Config():返回配置详情。

Ÿ   CallbackSize():返回回调函数的数量。

Ÿ   Start():开始监视文件变化。

Ÿ   WatcherID():返回文件监视器的唯一ID。

Ÿ   Stop():停止监视文件变化。

Ÿ   SetManager(manager parser.ConfigManager):设置配置文件的管理器。

Ÿ   SetParser(parser parser.ConfigParser):设置配置文件的解析器。

Ÿ   SetParams(params *parser.ConfigParam):设置配置文件的参数,例如文件类型。

Ÿ   ConfigParse(kind parser.ConfigType, data []byte, config interface{}):调用配置解析器的 Decode 方法。

Ÿ   RegisterCallback(callback func()) int64:添加回调函数,当文件发生变化时,这些回调函数会被调用。

Ÿ   DeregisterCallback(uniqueID int64):移除回调函数

configMonitor 结构体中有以下字段:

Ÿ   parser:配置文件的解析器。

Ÿ   params:配置文件的参数。

Ÿ   manager:配置文件的管理器。

Ÿ   config:配置详情。

Ÿ   fileWatcher:本地配置文件的监视器。

Ÿ   callbacks:当配置文件发生变化时,会被调用的回调函数。

Ÿ   key:配置文件的键。

Ÿ   id:文件监视器的唯一ID。

Ÿ   lock:互斥锁,用于保护 callbacks 的并发访问。

Ÿ   counter:一个原子计数器,用于生成回调函数的唯一ID。

NewConfigMonitor 函数用于创建一个新的 ConfigMonitor 实例。在这个函数中,创建了一个新的 fileWatcher 实例,并将其赋值给 configMonitor 结构体的 fileWatcher 字段。

Start 方法用于开始监视文件变化。在这个方法中,将需要监视的文件路径添加到 fileWatcher 中,并在一个新的 goroutine 中启动文件监视。

parseHandler 方法用于解析配置文件并调用每个回调函数。当配置文件发生变化时,这个方法会被调用。

总的来说,ConfigMonitor 模块的作用是提供一个中心化的配置管理服务,使得其他模块可以方便地获取和使用配置数据,同时也能够响应配置数据的变化。


四 源代码分析

4.1 文件监听与回调函数触发

文件监听和回调函数触发的流程如下:

1.   通过 NewFileWatcher 函数创建一个新的 FileWatcher 实例。在这个函数中,创建了一个新的 fsnotify.Watcher 实例,并将其赋值给 fileWatcher 结构体的 watcher 字段。

image.png 2.   使用 StartWatching 方法开始监视文件变化。在这个方法中,将需要监视的文件路径添加到 fsnotify.Watcher 中,并在一个新的 goroutine 中启动文件监视。

image.png 3.   在 start 方法中,处理 fsnotify.Watcher 的事件。当接收到文件写入事件时,会调用 CallOnceAll 方法,该方法会读取文件内容,并将内容传递给所有注册的回调函数。如果接收到文件被删除的事件,则会停止文件监视。

image.png

4.   CallOnceAll 方法会一次性调用所有的回调函数。首先,它会读取文件的内容,然后遍历 callbacks 字段中的所有回调函数,并将文件内容作为参数传递给这些函数。

image.png

5.   如果需要对特定的回调函数进行一次性调用,可以使用 CallOnceSpecific 方法。这个方法接收一个唯一ID作为参数,然后找到对应的回调函数并调用它。

image.png

6.   如果需要停止文件监视,可以使用 StopWatching 方法。这个方法会关闭 done 通道,从而停止文件监视。

image.png

4.2 配置信息导入与回调函数定义、注册与注销

通过阅读Kitex的option.go部分源码得知,实现了suite接口的struct,都可以通过func WithSuite(suite Suite) Option,调用suite.Options()将配置传入client。

image.png

在client/suite中,定义了Options()方法,根据ConfigMonitor中的配置信息,生成对应的Option数组并返回。

image.png

每次触发回调函数时,重新读取一边数据存入data字节流,并执行注册的回调函数重新加载配置项。配置项的回调函数定义,以circuitBreak为例,定义onChange函数将回调函数注册到Monitor。

image.png


五 结语

通过分析config-file的源码,我们对Kitex的配置项、各个组件的功能有了直观的认识。原理分析深入探讨了文件监听和配置加载的机制,特别是fsnotify和mapstructure库的应用,为动态配置管理提供了技术支撑。模块结构的详细介绍,让我们对Parser、FileWatcher和ConfigMonitor等核心组件的工作方式有了清晰的理解。这些模块相互协作,共同实现了配置文件的读取、解析、监控和更新。源代码分析部分则进一步深入到具体的实现细节,通过文件监听与回调函数的触发流程,以及配置信息的导入和回调函数的定义、注册与注销,展示了配置中心在实际工作中的应用。