扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go工程化规范设计

    • 前言
    • 开源规范
    • 文档规范
    • 版本规范
    • Git规范
    • 目录结构
    • 编码规范
    • 代码测试
    • 性能分析
    • API 设计
    • 项目管理
    • 研发流程
    • 参考资料
  • Go工程化标准实践

    • 前言
    • 项目结构
    • API 设计
    • 配置管理
    • 模块管理
    • 测试
    • 参考资料

扫码订阅《 》或入驻星球,即可阅读文章!

配置管理


Kyle

通常包括以下内容:

  • 环境配置:Region、Zone、Cluster、Environment、Color、Discovery、AppID、Host 等之类的环境变量信息,通过在线运行时平台打入到容器或物理机,供 kit 库读取使用。比如 Dev、UAT、Preprod、Prod、DR 等环境。
  • 静态配置:即资源需要初始化的配置信息,比如 HTTP/gRPC server、Redis、MySQL 等,通常不建议运行时变更(很可能会导致业务出现不可预期的事故),变更静态配置和发布 bianry app 没有区别,应该走迭代发布流程。在设计上应考虑 协议卸载:将有状态、需要运行时变更的业务逻辑下沉,而避免安排在接入节点层(比如TCP Server,无状态)。
  • 动态配置:应用程序可能需要比较简单的在线开关控制业务策略,会频繁的调整和使用,这类用于动态变更业务流的(比如 AB Test 的 flag,一般是基础类型 int、bool 等)配置可收归在一起,考虑结合 expvar (opens new window) 使用,与配置中心打通。
  • 全局配置:通常各类依赖组件、中间件都有大量默认配置或指定配置,在各个项目里大量复制容易出现意外。所以使用配置模板来定制化常用组件,在特化应用进行局部替换。

配置传参先参考 net/http 库:

func main() {
    s := &http.Server{
        Addr: ":8080",
        Handler: nil,
        ReadTimeout: 10 * time.Second,
        WriteTimeout: 10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    log.Fatal(s.ListenAndServe())
}
1
2
3
4
5
6
7
8
9
10

缺点是无法获知修改公共字段是否会有副作用,字段的含义也要自行查阅文档。

改进是自行设计 config struct,建议使用 functional options:

  • 符合编程直觉,可实现高度的可配置化,容易维护和扩展。
  • 自文档描述,代码可读、容易上手。
  • 代码直观,无歧义(比如空值)。
type Server struct {
    Addr     string        // required
    Port     int           // required
    Protocol string        // not null, default TCP
    Timeout  time.Duration // not null, default 30
    MaxConn  int           // not null, default 1024
    TLS      *tls.Config   //
}

type Option func(*Server)

func Protocol(p string) Option {
    return func(s *Server) {
        s.Protocol = p
    }
}
func Timeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.Timeout = timeout
    }
}
func MaxConn(maxConn int) Option {
    return func(s *Server) {
        s.MaxConn = maxConn
    }
}
func TLS(tls *tls.Config) Option {
    return func(s *Server) {
        s.TLS = tls
    }
}

func NewServerFP(addr string, port int, options ...Option) (*Server, error) {
    // 有一个可变参数 options 可以传出多个上面的函数,for-loop 设置 Server 对象。
    srv := Server{
        Addr:     addr,
        Port:     port,
        Protocol: "tcp",
        Timeout:  30 * time.Second,
        MaxConn:  1000,
        TLS:      nil,
    }
    for _, option := range options {
        option(&srv)
    }
    //...
    return &srv, nil
}

func TestFunctionalOptions(t *testing.T) {
    s1, _ := NewServerFP("localhost", 1024)
    s2, _ := NewServerFP("localhost", 2048, Protocol("udp"))
    s3, _ := NewServerFP("0.0.0.0", 8080, Timeout(300*time.Second), MaxConn(1000))
    fmt.Println(s1, s2, s3)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

在实践中应注意配置文件到配置数据之间映射的解耦:

  • 仅保留 options API。
  • config file 和 options struct 解耦:比如利用 gRPC 的 Protobuf 的强 schema 约束定义 Config 对象,实现语义验证、语法高亮和 lint、格式化。
[Config Web UI] <----+---------+
                     |         ↓
[Config API] --------+--> [Config Data] ----> [System]
                     |         ↑
[Config Language] <--+---------+
1
2
3
4
5

YAML:需要先转换成 JSON,再转成 Protobuf。Protobuf 的 Config 对象不能直接扩展方法,所以还需要加一个 Options 方法。

func ApplyYAML(s *redis.Config, yml string) error {
    js, err := yaml.YAMLToJSON([]byte(yml))
    if err != nil {
        return err
    }
    return ApplyJSON(s, string(js))
}
// Options apply config to options.
func Options(c *redis.Config) []redis.Options {
    return []redis.Options{
        redis.DialDatabase(c.Database),
        redis.DialPassword(c.Password),
        redis.DialReadTimeout(c.ReadTimeout),
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Protobuf:使用 wrap struct 区分是否有值。

syntax = "proto3";
import "google/protobuf/duration.proto";
package config.redis.v1;

// redis config.
message redis {
    string network = 1;
    string address = 2;
    int32 database = 3;
    string password = 4;
    google.protobuf.Duration read_timeout = 5;
}
1
2
3
4
5
6
7
8
9
10
11
12

最终实现配置注入:

func main() {
    // load config file from yaml.
    c := new(redis.Config)
    _ = ApplyYAML(c, loadConfig())
    r, _ := redis.Dial(c.Network, c.Address, Options(c)...)
}
1
2
3
4
5
6

# 最佳实践

实现代码变更系统功能是冗长且复杂的过程,往往还涉及 CR、测试等流程。而更改单个配置选项也可能对功能产生重大影响,且通常情况下修改配置还容易被忽略、未经测试就上线。

配置管理的目标:

  • 避免复杂:依赖的通用基础中间件使用配置中心支持的全局配置化模板。
  • 多样的配置:配置模板通过覆盖某些字段实现多样化。
  • 区分必选项和可选项,向简单化努力:尽可能减少必要的配置项(最佳实践)。
  • 以基础设施 -> 面向用户进行转变。
  • 配置的防御编程。
  • 权限和变更跟踪。
  • 配置的版本和应用对齐。
  • 安全的配置变更:逐步部署、回滚更改、自动回滚。
  • 最佳实践