shima-park / agollo Goto Github PK
View Code? Open in Web Editor NEWAn elegant Go client for Ctrip Apollo
License: Apache License 2.0
An elegant Go client for Ctrip Apollo
License: Apache License 2.0
url := fmt.Sprintf("http://%s/configs/%s/%s/%s?releaseKey=%s&ip=%s", configServerURL, url.QueryEscape(appID), url.QueryEscape(cluster), url.QueryEscape(c.getNamespace(namespace)), options.ReleaseKey, c.IP, )
这里不需要直接制定一个http
我用viper来远程获取Apollo配置中心参数,使用了remote.SetAgolloOptions(agollo.Cluster("test"))添加了cluster信息,debug源码后发现请求的url地址都正确,但无法返回正确的配置信息,请问能给点建议吗
获取属性为json的配置 无法获取到
你好!监听application.properties时watch到变化时获取的namespace是application而不是application.properties,请问这个有什么办法让wathc拿到的namesapce和new时传入到namespace保持一致嘛,谢谢
当程序启动时如无法连接上配置中心,则后续即使配置中心启动或恢复正常,程序也无法获取到配置。通过代码走读,发现问题是由于a.cache和a.initialized这两个map的LoadOrStore调用不太合理导致。我做了一点小改动以适应这种场景。请查阅一下我的修改是否有问题
logo306142054@d48c747?diff=split
非常简洁实用的库,感谢作者。
发现貌似不支持watch灰度发布的变更,需要重启。
如果支持的话请问要怎样操作呢,非常感谢,或者有没有qq交流群之类的。
It can not detect the changes, codes:
package main
import (
"fmt"
"log"
"time"
remote "github.com/shima-park/agollo/viper-remote"
"github.com/spf13/viper"
)
func main() {
remote.SetAppID("vip-service")
namespaces := []string{"application", "database"}
vipers := make(map[string]*viper.Viper)
for _, ns := range namespaces {
v := viper.New()
v.SetConfigType("prop")
err := v.AddRemoteProvider("apollo", "localhost:8080", ns)
if err != nil {
log.Fatal(err)
}
err = v.ReadRemoteConfig()
if err != nil {
log.Fatal(err)
}
v.WatchRemoteConfigOnChannel() // not working
vipers[ns] = v
}
for {
for ns, v := range vipers {
fmt.Println(ns, v.AllSettings()) // every time gets the same configs
}
time.Sleep(5 * time.Second)
}
}
It works if I switch to use WatchRemoteConfig
:
for {
for ns, v := range vipers {
err := v.WatchRemoteConfig()
if err != nil {
log.Fatal(err)
}
fmt.Println(ns, v.AllSettings())
}
time.Sleep(5 * time.Second)
}
func (a *agollo) GetNameSpace(namespace string) Configurations {
config, found := a.cache.LoadOrStore(namespace, Configurations{})
if !found && a.opts.AutoFetchOnCacheMiss {
err := a.initNamespace(namespace)
if err != nil {
a.log("Action", "InitNamespace", "Error", err)
}
return a.getNameSpace(namespace)
}
return config.(Configurations)
}
这里initNamespace之后的err只是打了log,是不是可以返回出来呢?我遇到的场景是不小心配置错了apollo地址,然后第一次服务启动去读apollo配置时被重定向到了登录页,因为http.Client中自动处理了重定向,导致拿到了200的响应,但是读不到配置,最后返回了空配置。
我尝试使用agollo.WithDoer
替换掉默认http client然后增加CheckRedirect,但是抛出的错误在上述位置没有返回出来。希望可以返回出来,这样在启动时就可以判断有没有正确获取到配置。
1.本地已存在具有所有namespace数据的备份文件
2.伪代码如下
a, _ := agollo.New("http://localhost:8080", "app",
agollo.AutoFetchOnCacheMiss(),
agollo.Cluster("default"),,
agollo.FailTolerantOnBackupExists(),
)
a.GetNameSpace("test") // 正常调用
// 此时会删除备份文件的其它namespace数据并写入test的配置数据
a.GetNameSpace("test2") // 此时网络故障, 得到空的配置结果
// 在这里理论上应该能从备份文件中获取配置数据, 但是这个数据在上一步被删掉了
点赞作者,简单的核心API留足了扩展能力,很棒的一个库~
Apollo架构上将Config、Meta、Eureka打在同一进程来做服务发现,咱们是否考虑利用MetaServer做客户端负载均衡,进一步提高可用性(对齐Java客户端)
我apollo的数据是这样的:
appsalt = some appsalt
database.driver = mysql
database.host = mysql
database.port = 3306
database.user = root
database.password = 123456
database.dbname = chat
然后我的 struct 是这样定义的
type Config struct {
AppSalt string `json:"appsalt"`
DB DatabaseConfig `json:"database"`
...
}
type DatabaseConfig struct {
Driver string `json:"driver"`
Host string `json:"host"`
Port string `json:"port"`
User string `json:"user"`
Password string `json:"password"`
DBName string `json:"dbname"`
}
apollo获取到的数据是这样的
map[appsalt:testaaaaa database.dbname:chat database.driver:mysql database.host:mysql database.password:123456 database.port:3306 database.user:root]
我怎么才能把获取到的这种数据映射到我的 struct中去呢?
客户端启动监听某个namespace(尚未创建),当 namespace 创建时,客户端并未监听到,只能在 namespace 创建好后,更新配置才能被监听。望修复,谢谢。
使用预加载一个namespace,启动期间没有配置发生变更,但依然在监听配置变更收到变更.
下面是打印的日志
[Agollo] ConfigServerUrl http://192.168.36.175:28080 AppID test Cluster default Namesapce Kafka From cache
Watch Apollo: &{Kafka map[brokers:192.168.36.175:9020,192.168.36.176:9020 version:1.1.0] <nil>}
要怎么避免这种情况?
提供一个选项用于自定义签名方法
apollo目前不支持全局token或支持不友好, 所以我前面挂了一个nginx, 在nginx上添加基础认证.
我可以通过WithDoer拦截修改req的Header方式来迂回的解决他, 但是我想要更好的方式, 谢谢!
这个选项有点奇怪,缓存接口并不是这么用。带缓存配置接口用途,是用于配置不敏感的应用,按一个特定的周期,循环获取,不需要notificaiton。比如每30秒更新一遍。
不知道这个AutoFetchOnCacheMiss选项的意图是什么?
用于当程序Get了一个app.properties不存在的namespace?
c, err := agollo.New("http://127.0.0.1:8080", "test",
agollo.AutoFetchOnCacheMiss(),
// agollo.FailTolerantOnBackupExists(), // 关闭容灾
)
failOnError(err, "初始化失败")
defer c.Stop()
_ = c.Start()
func() {
for {
time.Sleep(1e9)
fmt.Println("a:", c.Get("a"), "b:", c.Get("b"))
}
}()
结果如下:
a: 1 b: 2
a: b:
a: b:
a: b:
a: b:
a: b:
...
`
func main() {
ch := make(chan string)
a, err := agollo.New("localhost:8080",
"hdy", agollo.Cluster("hdy-cluster"),
agollo.DefaultNamespace("hdy.dev"),
agollo.AutoFetchOnCacheMiss())
if err != nil {
panic(err)
}
a.Start()
d := time.Duration(time.Second * 2)
t := time.NewTicker(d)
defer t.Stop()
go func() {
for {
<-t.C
fmt.Println(
"user_max_count",a.Get("user_max_count",agollo.WithDefault("defaultValue")),
)
}
}()
ch <- ""
}
`
输出
user_max_count 3000000
user_max_count defaultValue
在不改配置的情况的,第一次能取到值,后面都是defaultValue
在Apollo页面改一次user_max_count的值,就能再取到数据不再是defaultValue
go的初学者,也是第一次用Apollo,请问这个事如何解决?
原来的代码里面,没有注册通知 ID,会导致通知收不到
a, err := agollo.New("localhost:8080", "your_appid", agollo.AutoFetchOnCacheMiss())
// error handle...
errorCh := a.Start() // Start后会启动goroutine监听变化,并更新agollo对象内的配置cache
// 或者忽略错误处理直接 a.Start()
// ** 这里需要注册通知ID **
watchCh := a.Watch()
// ...
修改后:
a, err := agollo.New("localhost:8080", "your_appid", agollo.AutoFetchOnCacheMiss())
// error handle...
errorCh := a.Start() // Start后会启动goroutine监听变化,并更新agollo对象内的配置cache
// 或者忽略错误处理直接 a.Start()
// 这里需要注册通知ID
a.GetNameSpace("application")
a.GetNameSpace("test.json")
watchCh := a.Watch()
// ...
目前是v.AddRemoteProvider("apollo", Endpoint, NameSpace)
namespace只支持传入一个, 如果同一个app下有多个yaml或者json的配置文件 ,是否可以一次性获取多个namespace下的配置?
我看viper的AddRemoteProvider 是支持多次加入的, 放到了 v.remoteProviders ,获取的时候ReadRemoteConfig 也是支持循环remoteProviders的
如果项目支持 namespace传入切片就很棒了
https://github.com/xordataexchange/crypt/blob/master/backend/backend.go
实现这些接口就可以了吧,像etcd一样。
新建client时假如option中没有默认填入namespace application会自动添加到local storage中
if len(options.PreloadNamespaces) == 0 {
options.PreloadNamespaces = []string{defaultNamespace}
} else {
if !stringInSlice(defaultNamespace, options.PreloadNamespaces) {
PreloadNamespaces(defaultNamespace)(&options)
}
}
如果使用longpolling接口,并且所监听的app-cluster中没有发布过"application" namespace会导致客户端按照默认的每秒访问一次 longpolling的几个相关接口
panic: open .agollo: no such file or directory
goroutine 1 [running]:
你好。我在代码中测试apollo的设置的时候,我使用了这里例子里的自动更新。但两种模式中,自动更新的方法,打印的结果始终是服务启动时候的内容,不会随着我在apollo界面的更改而更改。而10秒轮询的方法,可以成功访问到改了的和增加的数据,但是如果我删除了某个字段,在代码中仍然会打印该字段原先的值。
请问是需要什么额外的配置么?
hi, 您好,我在apollo后台配置yaml文件类型的配置, 在客户端 .SetConfigType("yaml") 读不到配置
是不是我写法不对?
github.com/shima-park/agollo/viper-remote imports
github.com/xordataexchange/crypt/config imports
github.com/xordataexchange/crypt/backend/etcd imports
github.com/coreos/etcd/client tested by
github.com/coreos/etcd/client.test imports
github.com/coreos/etcd/integration imports
github.com/coreos/etcd/proxy/grpcproxy imports
google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.37.0), but does not contain package google.golang.org/grpc/naming
首先我个人想表扬作者,这个 agollo 实现不知道比官方推荐前两个实现好多少倍。前两个实现在我看来实在是太烂,过度的设计导致可读性和实用性变得极差。作者写简单和直接,也是 golang 官方倡导的。
其次我想提一些问题和建议:
viper-remote部分会在第一次读配置时new一个agollo对象,然后启动notification监听。但是这个agollo目前好像没有停止的方式
请问一下env可以设置嘛,比如我在fat和pro环境中有同样名字的集群,我该怎么区分它们呐
可以通过 SDK 获取 一个命名空间下的配置信息,如端口号等。可以获取对应的机器列表吗?
json yml 格式的配置需要如何获取,用获取properties 方式返回404?
agolloClient, err := agollo.New(
"xx",
"xx",
agollo.BackupFile(backupFile),
agollo.AutoFetchOnCacheMiss(),
agollo.PreloadNamespaces("xx.json"),
)
errChan := agolloClient.Start()
watchChan := agolloClient.Watch()
ticker := time.NewTicker(1 * time.Second)
for {
select {
case <-ticker.C:
ret := agolloClient.GetNameSpace("xx.json")
base.Test_debug(ret, 0)
case err := <-errChan:
base.Test_debug(err, 0)
case resp := <-watchChan:
base.Test_debug(resp, 0)
}
为什么只在启动时打印出了一次xx.json的值,后续打印都是空。任何修改变动,都没有任何打印。
感谢你的开源。
我在使用过程中发现无论是使用agollo.Watch()还是agollo.WatchNamespace(),配置变更时返回的ApolloResponse中OldValue与NewValue除了变更的部分还包括了未变更的,这给我带来了一些不便,因为我还要对比OldValue与NewValue两个map找出两者的不同,感觉有点多余,我希望的是OldValue与NewValue只有变更的部分。
这是我打印的ApolloResponse,其中m_db是变更的部分:
&{Mysql
map[m_db:testdb m_host:localhost m_password:123456 m_port:3307 m_username:root]
map[m_db:testdb1 m_host:localhost m_password:123456 m_port:3307 m_username:root]
<nil>
}
配置监听多个cluster,但只有当最后一个cluster 有变更生效才会监听到~~
func main() {
initConfig("ini", "goApollo.ini")
for _, cluster := range appConfig.Clusters {
// 数组
err := agollo.Init(
appConfig.ConfigServerUrl,
appConfig.AppId,
agollo.Cluster(cluster),
agollo.PreloadNamespaces(appConfig.NamespaceName),
agollo.AutoFetchOnCacheMiss(),
agollo.FailTolerantOnBackupExists(),
agollo.WithLogger(agollo.NewLogger(agollo.LoggerWriter(os.Stdout))),
)
if err != nil {
panic(err)
}
file, err := os.Open(cluster)
if err != nil && os.IsNotExist(err) {
file, _ = os.Create(cluster)
defer file.Close()
}
fount, err := os.OpenFile(cluster, os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
//defer fount.Close()
res := agollo.GetNameSpace(appConfig.NamespaceName)
for k, v := range res {
data := fmt.Sprintf("%s=%s\n", k, v)
//fmt.Println(data)
if _, err := fount.WriteString(data); err != nil {
panic(err)
}
}
fmt.Printf("%s write Success\n",cluster)
}
errorCh := agollo.Start()
watchCh := agollo.Watch()
go func() {
for {
select {
case err := <-errorCh:
fmt.Println("Error:", err)
case update := <-watchCh:
fmt.Println("Apollo Update:", update.NewValue)
fmt.Println(reflect.TypeOf(update))
for _, cluster := range appConfig.Clusters {
//writeNamespaceInfo(cluster)
do something
}
//default:
// fmt.Println("Apollo not update")
}
}
}()
select {}
}
RT.
另外一个问题是:.agollo 文件可以指定目录吗?
请问agollo如何与viper接合实现监听,我调用viper.WatchRemoteConfig方法后,发现从viper读出来的值一直没有变化,而根目录下的.agollo文件却实时更新了。
// agollo.Get 可以正常讀取
agollo.Init(
appConfig.ConfigServerUrl,
appConfig.AppId,
agollo.Cluster(cluster),
agollo.PreloadNamespaces("applications"),
)
// agollo.Get 完全讀不到值
agollo.Init(
appConfig.ConfigServerUrl,
appConfig.AppId,
agollo.Cluster(cluster),
agollo.DefaultNamespace("applications"),
)
此项目应有掌声3000,小哥加油!
按照样例,不能监听所有 namespace 配置变更,从代码上看,创建客户端时,如果未填写 namespace 将会使用默认的 namespace,因此只能监听默认 namespace 的变更,这块能修复下吗?
看文档应该不支持吧?什么时候支持呢?
应该是考虑到网络设置因人而异,apollo 通过ApolloClientOption选项允许用户自主设置客户端ip,但是在结合viper使用的时候内部均屏蔽了这些选项,统一采用默认配置,导致在docker环境下或者多网卡等复杂情况获取ip地址不准确,是否可以提供接口允许用户设置一些全局配置项,比如客户端ip?
// ....
for {
select {
case err := <-errorCh:
fmt.Println(err)
case resp := <-watchCh:
fmt.Println(
"Namesapce:", resp.Namesapce, // 这里是 Namespace
"OldValue:", resp.OldValue,
"NewValue:", resp.NewValue,
"Error:", resp.Error,
)
}
}
error trace:
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:63:14: z.HasExtensions undefined (type codec.genHelperEncoder has no field or method HasExtensions)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:63:35: z.EncExt undefined (type codec.genHelperEncoder has no field or method EncExt)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:75:6: r.WriteArrayElem undefined (type codec.genHelperEncDriver has no field or method WriteArrayElem)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:83:6: r.WriteMapElemKey undefined (type codec.genHelperEncDriver has no field or method WriteMapElemKey)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:84:6: r.EncodeString undefined (type codec.genHelperEncDriver has no field or method EncodeString)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:85:6: r.WriteMapElemValue undefined (type codec.genHelperEncDriver has no field or method WriteMapElemValue)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:94:6: r.WriteArrayElem undefined (type codec.genHelperEncDriver has no field or method WriteArrayElem)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:99:7: r.EncodeString undefined (type codec.genHelperEncDriver has no field or method EncodeString)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:102:6: r.WriteMapElemKey undefined (type codec.genHelperEncDriver has no field or method WriteMapElemKey)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:103:6: r.EncodeString undefined (type codec.genHelperEncDriver has no field or method EncodeString)
../../../../pkg/mod/github.com/coreos/[email protected]+incompatible/client/keys.generated.go:103:6: too many errors
想问一下……什么场景会出现多应用和多集群的监听
按目前的设计,agollo只对应1个应用1个集群,是很常规的设定;
我看到了已经closed issue,所以会好奇,到底需不需要把这部分需求也考虑进去设计。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.