1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 20:52:22 +00:00

add dist config

This commit is contained in:
Xargin 2018-08-21 16:32:11 +08:00
parent 88e778b788
commit 08d8597721
3 changed files with 52 additions and 30 deletions

View File

@ -59,7 +59,8 @@
* [6.3 延时任务系统](ch6-cloud/ch6-03-delay-job.md) * [6.3 延时任务系统](ch6-cloud/ch6-03-delay-job.md)
* [6.4 分布式搜索引擎](ch6-cloud/ch6-04-search-engine.md) * [6.4 分布式搜索引擎](ch6-cloud/ch6-04-search-engine.md)
* [6.5 负载均衡](ch6-cloud/ch6-05-load-balance.md) * [6.5 负载均衡](ch6-cloud/ch6-05-load-balance.md)
* [6.6 补充说明](ch6-cloud/ch6-06-ext.md) * [6.6 分布式配置管理](ch6-cloud/ch6-06-config.md)
* [6.7 补充说明](ch6-cloud/ch6-07-ext.md)
* [附录](appendix/readme.md) * [附录](appendix/readme.md)
* [附录A: Go语言常见坑](appendix/appendix-a-trap.md) * [附录A: Go语言常见坑](appendix/appendix-a-trap.md)
* [附录B: 有趣的代码片段](appendix/appendix-b-gems.md) * [附录B: 有趣的代码片段](appendix/appendix-b-gems.md)

View File

@ -50,10 +50,12 @@ cfg := client.Config{
} }
``` ```
直接用 etcd client 包中的结构体初始化,没什么可说的。
### 配置获取 ### 配置获取
```go ```go
resp, err = kapi.Get(context.Background(), "/name", nil) resp, err = kapi.Get(context.Background(), "/path/to/your/config", nil)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} else { } else {
@ -62,6 +64,8 @@ if err != nil {
} }
``` ```
获取配置使用 etcd KeysAPI 的 Get 方法,比较简单。
### 配置更新订阅 ### 配置更新订阅
```go ```go
@ -76,6 +80,8 @@ go func() {
}() }()
``` ```
通过订阅 config 路径的变动事件,在该路径下内容发生变化时,客户端侧可以收到变动通知,并收到变动后的字符串值。
### 整合起来 ### 整合起来
```go ```go
@ -87,22 +93,24 @@ import (
"time" "time"
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/coreos/etcd/client" "github.com/coreos/etcd/client"
) )
func watchAndUpdate() { var configPath = `/configs/remote_config.json`
var kapi client.KeysAPI
type ConfigStruct struct {
Addr string `json:"addr"`
AesKey string `json:"aes_key"`
HTTPS bool `json:"https"`
Secret string `json:"secret"`
PrivateKeyPath string `json:"private_key_path"`
CertFilePath string `json:"cert_file_path"`
} }
func set() error { var appConfig ConfigStruct
return nil
}
func get() (string, error) { func init() {
return "", nil
}
func main() {
cfg := client.Config{ cfg := client.Config{
Endpoints: []string{"http://127.0.0.1:2379"}, Endpoints: []string{"http://127.0.0.1:2379"},
Transport: client.DefaultTransport, Transport: client.DefaultTransport,
@ -110,40 +118,51 @@ func main() {
} }
c, err := client.New(cfg) c, err := client.New(cfg)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
kapi := client.NewKeysAPI(c) kapi = client.NewKeysAPI(c)
w := kapi.Watcher("/name", nil) }
func watchAndUpdate() {
w := kapi.Watcher(configPath, nil)
go func() { go func() {
// watch 该节点下的每次变化
for { for {
resp, err := w.Next(context.Background()) resp, err := w.Next(context.Background())
fmt.Println(resp, err) if err != nil {
log.Fatal(err)
}
fmt.Println("new values is ", resp.Node.Value) fmt.Println("new values is ", resp.Node.Value)
err = json.Unmarshal([]byte(resp.Node.Value), &appConfig)
if err != nil {
log.Fatal(err)
}
} }
}() }()
}
log.Print("Setting /name to alex") func getConfig() {
resp, err := kapi.Set(context.Background(), "/name", "alex", nil) resp, err = kapi.Get(context.Background(), configPath, nil)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} else {
log.Printf("Set is done. Metadata is %q\n", resp)
} }
log.Print("Getting /name key value") err := json.Unmarshal(resp.Node.Value, &appConfig)
resp, err = kapi.Get(context.Background(), "/name", nil)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} else {
log.Printf("Get is done. Metadata is %q\n", resp)
log.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value)
} }
time.Sleep(time.Minute) }
func main() {
// init your app
} }
``` ```
如果业务规模不大使用本节中的例子就可以实现功能了。TODO 如果业务规模不大,使用本节中的例子就可以实现功能了。
这里只需要注意一点我们在更新配置时进行了一系列操作watch 响应json 解析,这些操作都不具备原子性。当单个业务请求流程中多次获取 config 时,有可能因为中途 config 发生变化而导致单个请求前后逻辑不一致。因此,在使用类似这样的方式来更新配置时,需要在单个请求的生命周期内使用同样的配置。具体实现方式可以是只在请求开始的时候获取一次配置,然后依次向下透传等等,具体情况具体分析。
## 配置膨胀 ## 配置膨胀
@ -159,6 +178,8 @@ func main() {
在配置进行更新时,我们要为每份配置的新内容赋予一个版本号,并将修改前的内容和版本号记录下来,当发现新配置出问题时,能够及时地回滚回来。 在配置进行更新时,我们要为每份配置的新内容赋予一个版本号,并将修改前的内容和版本号记录下来,当发现新配置出问题时,能够及时地回滚回来。
常见的做法是,使用 MySQL 来存储配置文件/字符串的不同版本内容,在需要回滚时,只要进行简单的查询即可。
## 客户端容错 ## 客户端容错
在业务系统的配置被剥离到配置中心之后,并不意味着我们的系统可以高枕无忧了。当配置中心本身宕机时,我们也需要一定的容错能力,至少保证在其宕机期间,业务依然可以运转。这要求我们的系统能够在配置中心宕机时,也能拿到需要的配置信息。哪怕这些信息不够新。 在业务系统的配置被剥离到配置中心之后,并不意味着我们的系统可以高枕无忧了。当配置中心本身宕机时,我们也需要一定的容错能力,至少保证在其宕机期间,业务依然可以运转。这要求我们的系统能够在配置中心宕机时,也能拿到需要的配置信息。哪怕这些信息不够新。

View File

@ -1,4 +1,4 @@
# 6.6 补充说明 # 6.7 补充说明
分布式是很大的领域,本章中的介绍只能算是对领域的管中窥豹。因为大型系统流量大,并发高,所以往往很多朴素的方案会变得难以满足需求。人们为了解决大型系统场景中的各种问题,而开发出了各式各样的分布式系统。有些系统非常简单,比如本章中介绍的分布式 id 生成器,而有一些系统则可能非常复杂,比如本章中的分布式搜索引擎(当然,本章中提到的 es 不是 Go 实现)。 分布式是很大的领域,本章中的介绍只能算是对领域的管中窥豹。因为大型系统流量大,并发高,所以往往很多朴素的方案会变得难以满足需求。人们为了解决大型系统场景中的各种问题,而开发出了各式各样的分布式系统。有些系统非常简单,比如本章中介绍的分布式 id 生成器,而有一些系统则可能非常复杂,比如本章中的分布式搜索引擎(当然,本章中提到的 es 不是 Go 实现)。