最近运维在全量推配置项上apollo,看了一下,golang的apollo使用,大概记录了一下;
感觉上apollo在spring上直接@enableapolloconfig注解比go的方便;
golang的需要自己流程式的client拉配置;还要自己实现OnNewestChange的实现;
golang如何获取apollo中保存的配置呢,eg
package main
import (
"fmt"
"github.com/apolloconfig/agollo/v4"
"github.com/apolloconfig/agollo/v4/component/log"
"github.com/apolloconfig/agollo/v4/env/config"
)
func main() {
//注意golang默认本地配置文件为app.properties
//也可通过设置环境变量AGOLLO_CONF设置路径
c := &config.AppConfig{
AppID: "testApplication_yang",
Cluster: "dev",
IP: "http://106.54.227.205:8080",
NamespaceName: "dubbo",
IsBackupConfig: true,
Secret: "6ce3ff7e96a24335a9634fe9abca6d51",
}
agollo.SetLogger(&log.DefaultLogger{})
client, _ := agollo.StartWithConfig(func() (*config.AppConfig, error) {
return c, nil
})
fmt.Println("初始化Apollo配置成功")
//Use your apollo key to test
cache := client.GetConfigCache(c.NamespaceName)
value, _ := cache.Get("key")
fmt.Println(value)
}
agollo源码过程
1 开始初始化apollo
此处会初始化各种默认控制器
func init() {
extension.SetCacheFactory(&memory.DefaultCacheFactory{})
extension.SetLoadBalance(&roundrobin.RoundRobin{})
extension.SetFileHandler(&jsonFile.FileHandler{})
extension.SetHTTPAuth(&sign.AuthSignature{})
// file parser
extension.AddFormatParser(constant.DEFAULT, &normal.Parser{})
extension.AddFormatParser(constant.Properties, &properties.Parser{})
extension.AddFormatParser(constant.YML, &yml.Parser{})
extension.AddFormatParser(constant.YAML, &yaml.Parser{})
}
1.1 代码初始化
c := &config.AppConfig{
AppID: "testApplication_yang",
Cluster: "dev",
IP: "http://106.54.227.205:8080",
NamespaceName: "dubbo",
IsBackupConfig: true,
Secret: "6ce3ff7e96a24335a9634fe9abca6d51",
}
client, _ := agollo.StartWithConfig(func() (*config.AppConfig, error) {
return c, nil
})
1.2 如果依据默认配置初始化
agollo.Start()
// Start 根据默认文件启动
func Start() (Client, error) {
return StartWithConfig(nil)
}
其中,如果设置了配置文件的环境变量AGOLLO_CONF,会获取对应的AGOLLO_CONF的值作为配置文件;
如果没有设置AGOLLO_CONF,默认本地配置文件为app.properties;
其中app.properties需为json格式,不然默认json解析有问题;
{
"appId": "test",
"cluster": "dev",
"namespaceName": "application,abc1",
"ip": "localhost:8888",
"backupConfigPath":""
}
1.3 StartWithConfig根据配置启动
开始根据配置启动
c := create()创建internalClient客户端
storage.CreateNamespaceConfig,创建storage.Cache结构,其中Cache{apolloConfigCache sync.Map , changeListeners *list.List},apolloConfigCache为namespace,默认缓存的map结构,changeListeners是一个监听链表结构;
// StartWithConfig 根据配置启动
func StartWithConfig(loadAppConfig func() (*config.AppConfig, error)) (Client, error) {
c := create()
//c := create()创建internalClient客户端
c.cache = storage.CreateNamespaceConfig(appConfig.NamespaceName)
//创建storage.Cache结构
其中Cache{apolloConfigCache sync.Map , changeListeners *list.List},
apolloConfigCache为namespace,默认缓存的map结构,
changeListeners是一个监听链表结构
appConfig.Init()
serverlist.InitSyncServerIPList(c.getAppConfig)
//Sync开始同步apollo远端保存的配置
configs := syncApolloConfig.Sync(c.getAppConfig)
if len(configs) == 0 && appConfig != nil && appConfig.MustStart {
return nil, errors.New("start failed cause no config was read")
}
for _, apolloConfig := range configs {
c.cache.UpdateApolloConfig(apolloConfig, c.getAppConfig)
}
log.Debug("init notifySyncConfigServices finished")
//start long poll sync config
configComponent := ¬ify.ConfigComponent{}
configComponent.SetAppConfig(c.getAppConfig)
configComponent.SetCache(c.cache)
go component.StartRefreshConfig(configComponent)
log.Info("agollo start finished ! ")
return c, nil
}
InitSyncServerIPList 初始化同步服务器信息列表,其中InitSyncServerIPList如下,component.StartRefreshConfig真实调用func (s *SyncServerIPListComponent) Start() ,SyncServerIPList同步远程apollo ip及其上服务信息;并持续更新
func InitSyncServerIPList(appConfig func() config.AppConfig) {
go component.StartRefreshConfig(&SyncServerIPListComponent{appConfig})
}
//SyncServerIPListComponent set timer for update ip list
//interval : 20m
type SyncServerIPListComponent struct {
appConfig func() config.AppConfig
}
//Start 启动同步服务器列表
func (s *SyncServerIPListComponent) Start() {
SyncServerIPList(s.appConfig)
log.Debug("syncServerIpList started")
t2 := time.NewTimer(refreshIPListInterval)
for {
select {
case <-t2.C:
SyncServerIPList(s.appConfig)
t2.Reset(refreshIPListInterval)
}
}
}
SyncServerIPList获得远端apollo ip,服务主页,服务信息对应map
func SyncServerIPList(appConfigFunc func() config.AppConfig) (map[string]*config.ServerInfo, error) {
if appConfigFunc == nil {
panic("can not find apollo config!please confirm!")
}
appConfig := appConfigFunc()
c := &env.ConnectConfig{
AppID: appConfig.AppID,
Secret: appConfig.Secret,
}
if appConfigFunc().SyncServerTimeout > 0 {
duration, err := time.ParseDuration(strconv.Itoa(appConfigFunc().SyncServerTimeout) + "s")
if err != nil {
return nil, err
}
c.Timeout = duration
}
serverMap, err := http.Request(appConfig.GetServicesConfigURL(), c, &http.CallBack{
SuccessCallBack: SyncServerIPListSuccessCallBack,
AppConfigFunc: appConfigFunc,
})
if serverMap == nil {
return nil, err
}
m := serverMap.(map[string]*config.ServerInfo)
server.SetServers(appConfig.GetHost(), m)
return m, err
}
其中appConfig.GetServicesConfigURL()为获取远程服务信息url,eg格式为“”,
Request 建立网络请求,可以简单的看出在http.StatusOK下读取返回值,使用回调函数callBack.SuccessCallBack(responseBody, *callBack),解析数据到([]config.ServerInfo)中,即得到远端apollo各服务主页对应的服务信息
func SyncServerIPListSuccessCallBack(responseBody []byte, callback http.CallBack) (o interface{}, err error) {
log.Debug("get all server info:", string(responseBody))
tmpServerInfo := make([]*config.ServerInfo, 0)
err = json.Unmarshal(responseBody, &tmpServerInfo)
...
}
2 Sync开始同步apollo远端保存的配置
2.1 获取远端保持的配置至 []*config.ApolloConfig
var syncApolloConfig = remote.CreateSyncApolloConfig() 创建syncApolloConfig
SyncWithNamespace获取对应namespace的apolloConfig;
func (a *syncApolloConfig) Sync(appConfigFunc func() config.AppConfig) []*config.ApolloConfig {
appConfig := appConfigFunc()
configs := make([]*config.ApolloConfig, 0, 8)
config.SplitNamespaces(appConfig.NamespaceName, func(namespace string) {
apolloConfig := a.SyncWithNamespace(namespace, appConfigFunc)
if apolloConfig != nil {
configs = append(configs, apolloConfig)
return
}
configs = append(configs, loadBackupConfig(appConfig.NamespaceName, appConfig)...)
})
return configs
}
RequestRecovery中loadBalance(appConfig)负载均衡的获取apollo主页url;组成requestURL,发起Request得到response结果,使用回调处理函数得到最后结果;其中回调函数调用的是func processJSONFiles(b []byte, callback http.CallBack)函数,解析返回值至config.ApolloConfig.Configurations
//RequestRecovery 可以恢复的请求
func RequestRecovery(appConfig config.AppConfig,
connectConfig *env.ConnectConfig,
callBack *CallBack) (interface{}, error) {
format := "%s%s"
var err error
var response interface{}
for {
host := loadBalance(appConfig)
if host == "" {
return nil, err
}
requestURL := fmt.Sprintf(format, host, connectConfig.URI)
response, err = Request(requestURL, connectConfig, callBack)
if err == nil {
return response, nil
}
if host == appConfig.GetHost() {
return response, err
}
server.SetDownNode(host, appConfig.GetHost())
}
}
发起Request得到response结果
func Request(requestURL string, connectionConfig *env.ConnectConfig, callBack *CallBack) (interface{}, error) {
client := &http.Client{}
for {
retry++
if retry > retries {
break
}
req, err := http.NewRequest("GET", requestURL, nil)
res, err := client.Do(req)
//not modified break
switch res.StatusCode {
case http.StatusOK:
responseBody, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Errorf("Connect Apollo Server Fail,url : %s ,Error: %s ", requestURL, err)
// if error then sleep
time.Sleep(onErrorRetryInterval)
continue
}
...
}
2.2 根据远端换回内容更新内存;并写备份
UpdateApolloConfigCache更新新的配置至cache缓存,并在返回changeList中标记哪些是create,Modify,Deleted;
GetNotificationsMap返回对应空间的监听值;
pushNewestChanges 推送最新的消息至listener.OnNewestChange(e);其中listener是自己写的listener插件;
createConfigChangeEvent创建事件类型,并将事件event推送至listener.OnChange;
如果需要备份文件appConfig.GetIsBackupConfig(),func (fileHandler *FileHandler) WriteConfigFile到对应configPath,如果configPath没有设置,则将apolloConfig写入到"appID-namespace.json"文件中;
func (c *Cache) UpdateApolloConfig(apolloConfig *config.ApolloConfig, appConfigFunc func() config.AppConfig) {
appConfig := appConfigFunc()
appConfig.SetCurrentApolloConfig(&apolloConfig.ApolloConnConfig)
// get change list
changeList := c.UpdateApolloConfigCache(apolloConfig.Configurations, configCacheExpireTime, apolloConfig.NamespaceName)
notify := appConfig.GetNotificationsMap().GetNotify(apolloConfig.NamespaceName)
// push all newest changes
c.pushNewestChanges(apolloConfig.NamespaceName, apolloConfig.Configurations, notify)
if len(changeList) > 0 {
// create config change event base on change list
event := createConfigChangeEvent(changeList, apolloConfig.NamespaceName, notify)
// push change event to channel
c.pushChangeEvent(event)
}
if appConfig.GetIsBackupConfig() {
// write config file async
apolloConfig.AppID = appConfig.AppID
go extension.GetFileHandler().WriteConfigFile(apolloConfig, appConfig.GetBackupConfigPath())
}
}
3 监控配置变化
定时拉取远程配置,并更新至缓存cache,推送消息至OnChange,OnNewestChange
OnChange,OnNewestChange方法的实现需要根据自身配置而定
configComponent := ¬ify.ConfigComponent{}
configComponent.SetAppConfig(c.getAppConfig)
configComponent.SetCache(c.cache)
go component.StartRefreshConfig(configComponent)
持久获取配置更新
//Start 启动配置组件定时器
func (c *ConfigComponent) Start() {
t2 := time.NewTimer(longPollInterval)
instance := remote.CreateAsyncApolloConfig()
//long poll for sync
for {
select {
case <-t2.C:
configs := instance.Sync(c.appConfigFunc)
for _, apolloConfig := range configs {
c.cache.UpdateApolloConfig(apolloConfig, c.appConfigFunc)
}
t2.Reset(longPollInterval)
}
}
}