Nacos Client Config
Nacos Server Config
结构:
type NacosClient struct {
client config_client.IConfigClient
}
// ClientConfigOptions 存储Nacos ClientConfig的部分配置项
type ClientConfigOptions struct {
NamespaceId string `json:"namespaceId"`
TimeoutMs uint64 `json:"timeoutMs"`
NotLoadCacheAtStart bool `json:"notLoadCacheAtStart"`
LogLevel string `json:"logLevel"`
AppendToStdout bool `json:"appendToStdout"`
LogDir string `json:"logDir"`
CacheDir string `json:"cacheDir"`
}
type ServerConfigOptions struct {
IpAddr string `json:"ipAddr"`
Port uint64 `json:"port"`
ContextPath string `json:"contextPath"`
Scheme string `json:"scheme"`
}
初始化
// Nacos Client Config
namespace := "3ac59d8c-8213-4619-859a-a00477496ae4"
ccOpts := nacos.ClientConfigOptions{
NamespaceId: namespace,
TimeoutMs: 100000000,
NotLoadCacheAtStart: true,
LogLevel: "debug",
AppendToStdout: true,
LogDir: "./config",
CacheDir: "./config",
}
cfg.NacosClient = &ccOpts
// Nacos Server Config
scOpt := nacos.ServerConfigOptions{
IpAddr: "nacos.dev.surreal-ai.com",
Port: 443,
ContextPath: "/nacos",
Scheme: "https",
}
cfg.NacosServer = &scOpt
func NewNacosClient(ccOpts *ClientConfigOptions, scOpt *ServerConfigOptions) (*NacosClient, error) {
//new 返回指针
sc := []constant.ServerConfig{
{
IpAddr: scOpt.IpAddr,
Port: scOpt.Port,
ContextPath: scOpt.ContextPath,
Scheme: scOpt.Scheme,
},
}
cc := constant.ClientConfig{
NamespaceId: ccOpts.NamespaceId,
TimeoutMs: ccOpts.TimeoutMs,
NotLoadCacheAtStart: ccOpts.NotLoadCacheAtStart,
LogDir: ccOpts.LogDir,
CacheDir: ccOpts.CacheDir,
LogLevel: ccOpts.LogLevel,
AppendToStdout: ccOpts.AppendToStdout,
}
// a more graceful way to create config client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
},
)
if err != nil {
panic(err)
}
// 创建新的NacosClient实例并将config_client.IConfigClient包装在其中
nacosClient := &NacosClient{
client: client,
}
return nacosClient, nil
}
func (c *NacosClient) GetString(dataid string, group string) (string, error) {
content, err := c.client.GetConfig(vo.ConfigParam{
DataId: dataid,
Group: group,
})
if err != nil {
return content, err
}
return content, nil
}
GetConfig
func (client *ConfigClient) GetConfig(param vo.ConfigParam) (content string, err error) {
content, err = client.getConfigInner(param)
if err != nil {
return "", err
}
return client.decrypt(param.DataId, content)
}
每次请求获取配置
getConfigInner
func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string, err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.GetConfig] param.dataId can not be empty")
return "", err
}
if len(param.Group) <= 0 {
err = errors.New("[client.GetConfig] param.group can not be empty")
return "", err
}
clientConfig, _ := client.GetClientConfig()
cacheKey := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
content, err = client.configProxy.GetConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
logger.Errorf("get config from server error:%+v ", err)
if _, ok := err.(*nacos_error.NacosError); ok {
nacosErr := err.(*nacos_error.NacosError)
if nacosErr.ErrorCode() == "404" {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, "")
logger.Warnf("[client.GetConfig] config not found, dataId: %s, group: %s, namespaceId: %s.", param.DataId, param.Group, clientConfig.NamespaceId)
return "", nil
}
if nacosErr.ErrorCode() == "403" {
return "", errors.New("get config forbidden")
}
}
content, err = cache.ReadConfigFromFile(cacheKey, client.configCacheDir)
if err != nil {
logger.Errorf("get config from cache error:%+v ", err)
return "", errors.New("read config from both server and cache fail")
}
} else {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, content)
}
return content, nil
}
*nacos_error.NacosError
cache.ReadConfigFromFile()
方法从缓存中获取配置内容。err
为 nil
)
cache.WriteConfigToFile()
方法。func (c *NacosClient) GetObject(dataid string, group string, obj interface{}) error {
val, err := c.client.GetConfig(vo.ConfigParam{
DataId: dataid,
Group: group,
})
if err != nil {
return err
}
// 假设配置值是JSON格式的,可以使用json.Unmarshal将其解析到传入的obj中
err = json.Unmarshal([]byte(val), obj)
if err != nil {
return err
}
return nil
}
// PublishConfig is used to publish a configuration to Nacos.
func (c *NacosClient) PublishConfig(dataId, group, content string) error {
_, err := c.client.PublishConfig(vo.ConfigParam{
DataId: dataId,
Group: group,
Content: content,
})
if err != nil {
return err
}
return nil
}
func (c *NacosClient) Listen(dataid string, group string, callback func(namespace, group, dataId, data string)) error {
err := c.client.ListenConfig(vo.ConfigParam{
DataId: dataid,
Group: group,
OnChange: func(namespace, group, dataId, data string) {
callback(namespace, group, dataId, data)
},
})
if err != nil {
return err
}
return nil
}
func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.ListenConfig] DataId can not be empty")
return err
}
if len(param.Group) <= 0 {
err = errors.New("[client.ListenConfig] Group can not be empty")
return err
}
clientConfig, err := client.GetClientConfig()
if err != nil {
err = errors.New("[checkConfigInfo.GetClientConfig] failed")
return err
}
key := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
var cData cacheData
if v, ok := client.cacheMap.Get(key); ok {
cData = v.(cacheData)
cData.isInitializing = true
} else {
var (
content string
md5Str string
)
if content, _ = cache.ReadConfigFromFile(key, client.configCacheDir); len(content) > 0 {
md5Str = util.Md5(content)
}
listener := &cacheDataListener{
listener: param.OnChange,
lastMd5: md5Str,
}
cData = cacheData{
isInitializing: true,
dataId: param.DataId,
group: param.Group,
tenant: clientConfig.NamespaceId,
content: content,
md5: md5Str,
cacheDataListener: listener,
taskId: client.cacheMap.Count() / perTaskConfigSize,
}
}
client.cacheMap.Set(key, cData)
return
}
func (client *ConfigClient) listenConfigExecutor() func() error {
return func() error {
// 计算当前监听器的数量
listenerSize := client.cacheMap.Count()
// 计算总共需要的任务数量,每个任务处理的监听器数量为 perTaskConfigSize
taskCount := int(math.Ceil(float64(listenerSize) / float64(perTaskConfigSize)))
// 获取当前正在执行的任务数量
currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
// 根据任务数量的比较,进行任务的启动和停止
if taskCount > currentTaskCount {
// 有新的监听器加入,需要启动新的任务来处理新的监听器
for i := currentTaskCount; i < taskCount; i++ {
// 设置任务状态为运行中
client.schedulerMap.Set(strconv.Itoa(i), true)
// 创建新的定时器,定时触发长轮询操作 client.longPulling(i)
go client.delayScheduler(time.NewTimer(1*time.Millisecond), 10*time.Millisecond, strconv.Itoa(i), client.longPulling(i))
}
// 更新当前任务数量为 taskCount
atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
} else if taskCount < currentTaskCount {
// 有监听器停止监听,需要停止相应的任务
for i := taskCount; i < currentTaskCount; i++ {
// 检查相应任务是否在 schedulerMap 中,如果存在则将任务状态设置为停止
if _, ok := client.schedulerMap.Get(strconv.Itoa(i)); ok {
client.schedulerMap.Set(strconv.Itoa(i), false)
}
}
// 更新当前任务数量为 taskCount
atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
}
return nil
}
}
// longPulling 是一个长轮询监听配置变化的方法。
// 参数 taskId 表示监听任务的标识,用于区分不同的监听任务。
// 该方法返回一个函数,该函数用于执行长轮询操作,监听配置变化。
func (client *ConfigClient) longPulling(taskId int) func() error {
return func() error {
var listeningConfigs string
initializationList := make([]cacheData, 0)
// 遍历缓存中的所有配置信息,根据 taskId 筛选出当前监听任务的配置信息
for _, key := range client.cacheMap.Keys() {
if value, ok := client.cacheMap.Get(key); ok {
cData := value.(cacheData)
if cData.taskId == taskId {
// 如果配置数据正在初始化中,则将其加入 initializationList 列表
if cData.isInitializing {
initializationList = append(initializationList, cData)
}
// 构建监听配置列表listeningConfigs,用于发送给配置服务器进行监听
if len(cData.tenant) > 0 {
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG
} else {
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG
}
}
}
}
// 如果有要监听的配置信息,则继续进行长轮询
if len(listeningConfigs) > 0 {
clientConfig, err := client.GetClientConfig()
if err != nil {
logger.Errorf("[checkConfigInfo.GetClientConfig] 获取客户端配置失败 err: %+v", err)
return err
}
// 构建监听配置请求参数params,用于发送给配置服务器进行监听
params := make(map[string]string)
params[constant.KEY_LISTEN_CONFIGS] = listeningConfigs
var changed string
// 发送监听配置的请求,进行长轮询
changedTmp, err := client.configProxy.ListenConfig(params, len(initializationList) > 0, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err == nil {
changed = changedTmp
} else {
// 如果监听配置出现错误,尝试处理错误情况
if _, ok := err.(*nacos_error.NacosError); ok {
// 如果返回的错误是NacosError类型,则将监听结果设为变更信息(changedTmp)
changed = changedTmp
} else {
// 否则,记录错误日志并返回错误
logger.Errorf("[client.ListenConfig] 监听配置错误 err: %+v", err)
}
return err
}
// 对于初始化中的配置数据,将其isInitializing字段设为false,表示配置数据已经初始化完毕
for _, v := range initializationList {
v.isInitializing = false
client.cacheMap.Set(util.GetConfigCacheKey(v.dataId, v.group, v.tenant), v)
}
// 根据监听结果changed,判断是否有配置发生了变更,如果有则通知相应的监听器进行处理
if len(strings.ToLower(strings.Trim(changed, " "))) == 0 {
logger.Info("[client.ListenConfig] 配置无变更")
} else {
logger.Info("[client.ListenConfig] 配置发生变更: " + changed)
client.callListener(changed, clientConfig.NamespaceId)
}
}
// 返回nil,表示长轮询监听成功完成
return nil
}
}
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG
// Execute the Listener callback func()
// 执行监听器的回调函数
// 当配置发生变更时,通过该方法通知相应的监听器进行处理。
func (client *ConfigClient) callListener(changed, tenant string) {
// 解码配置变更字符串
changedDecoded, _ := url.QueryUnescape(changed)
// 使用分隔符 "\u0001" 拆分配置变更信息,得到一个 changedConfigs 切片,每个元素表示一个配置项的变更内容。
changedConfigs := strings.Split(changedDecoded, "\u0001")
// 遍历 changedConfigs,每个元素代表一个配置项的变更信息。
for _, config := range changedConfigs {
// 使用分隔符 "\u0002" 拆分配置变更项,得到一个 attrs 切片,包含了配置项的 DataId 和 Group 信息,以及其他可能的变更内容。
attrs := strings.Split(config, "\u0002")
// 如果 attrs 的长度大于等于 2,表示配置项的 DataId 和 Group 信息是有效的,可以根据这些信息从缓存中获取相应的配置数据。
if len(attrs) >= 2 {
// 从缓存中获取配置数据
if value, ok := client.cacheMap.Get(util.GetConfigCacheKey(attrs[0], attrs[1], tenant)); ok {
cData := value.(cacheData)
// 获取配置内容,并计算新的 MD5 值
content, err := client.getConfigInner(vo.ConfigParam{
DataId: cData.dataId,
Group: cData.group,
})
if err != nil {
// 获取配置内容出错,记录错误日志并继续处理下一个配置变更项
logger.Errorf("[client.getConfigInner] DataId:[%s] Group:[%s] Error:[%+v]", cData.dataId, cData.group, err)
continue
}
// 更新配置数据
cData.content = content
cData.md5 = util.Md5(content)
// 如果 MD5 值与之前的不同,则表示配置发生了变更,调用监听器的回调函数处理配置变更。
if cData.md5 != cData.cacheDataListener.lastMd5 {
go cData.cacheDataListener.listener(tenant, attrs[1], attrs[0], cData.content)
cData.cacheDataListener.lastMd5 = cData.md5
client.cacheMap.Set(util.GetConfigCacheKey(cData.dataId, cData.group, tenant), cData)
}
}
}
}
}
type cacheData struct {
isInitializing bool
dataId string
group string
content string
tenant string
cacheDataListener *cacheDataListener // 回调函数的封装
md5 string
appName string
taskId int
}
type cacheDataListener struct {
listener vo.Listener //type Listener func(namespace, group, dataId, data string)
lastMd5 string
}