我们接下来讲一下Go的配置管理
Go配置管理其实我会分两个部分
首先作为一个kit库
比方基础库
你要提供一些功能给它做你的库的一些(初始化)
你这个包的一些初始化
那么它需要从外部加载一些配置
来驱动它初始化
这是一部分
那么第二部分是配置本身怎么管理
怎么定义
这两个部分我会重点来介绍
希望这些(内容)
这一节讲的内容能够实战到最终你们的项目里面
我认为它是一种经过了我们很多时间的一些思考
它非常符合我们工程化的一种实践
因为我们之前也是踩了很多坑
那么这里面会围绕这些方面我们去展开讲
首先从配置的种类来说
它会分为几大类
第一大类叫环境变量
什么是环境变量
环境变量大家知道Linux有环境变量
跟环境相关的配置信息或者变量信息
我们认为叫环境变量或者叫环境配置
举个例子来说
像一个服务它所处的机房的Region
哪个区域 对吧
我们知道阿里云上有什么华南 华东 华北 对吧
它有Zone
比方说它是华东区域
杭州01 杭州02 杭州03
它会有这样的一个Zone
然后小到我们到机房里面
我们之前讲微服务多集群的时候
Cluster信息它是归属于哪个集群的
之前我们说过是由我们的发布平台
或者我们的k8s这种平台
通过注入环境变量告诉我们的应用
让我们的应用有个有个地方可以获取到它自己归属是哪个机群
所以Cluster也是一种信息
还有就是所谓的它部署的一个环境
它到底是UAT FAT还是生产prd还是个预发布pre
就是它处于一个什么样的deploy environment
还有就是它的染色
我们也叫租户信息
它到底做染色发布还是做全链路压测
还有等等一系列
它的应用的名称
还有Hostname这些信息等之类的
通过在线运行时平台打入到我们容器或者物理机
这种我们叫环境配置
它们最终这些环境配置谁会使用呢
一般来说是我们的基础库
我们基础库会尝试从这些环境变量或者环境配置里面
读取这些信息来做一些决策
比方说我们多租户可能会环境变量获取之后
我们会干嘛
我们会注册到注册中心 对吧
通过租户或者是通过我们的归属的集群Cluster
也是会这样的方式注册的注册中心
我认为这个是第一类
它是通过环境变量带进来的
那么第二类我们叫静态配置
因为很多资源它需要初始化的一些配置信息
最简单来说像http/gRPC server redis或者mysql这类资源
你去在线变更
比方说我要reload http或者说gRPC的server
我知道大家能找到类似说graceful的一些http或者gRPC的一些库
我仍然不鼓励大家叫on-the-fly的变更
就是运行时去变更
风险很大
这个只是http的
那么有可能还涉及到什么
redis mysql
你去动态切一个Host
其实风险非常大
万一有一个SQL它是一连串连续的操作
你可能是一开始飘的a库
然后接下来又飘到b库
这种很可能导致业务出现一些不可预期的bug或者事故
所以我认为静态配置和我们本身发布的可运行的二进制没有任何区别
就是静态发布和发二进制没有任何区别
应该完完整整的走一次迭代发布的一个流程是最安全的
因为静态配置你要经过配置文件的code review
然后再提交
然后再走灰度
再走全量发布
是相对来说是安全的多的
我鼓励静态配置
不要去做什么reload
举个例子来说 大家可能会经常问
我的一个业务要支持去替换一些线上的一些这种配置
怎么办
如果你的本身你的微服务能做到平滑的下线和平滑的上线
那么其实你改了代码这些逻辑
或者说改了配置
你不就是走一次完整的一个迭代发布
把所有的老节点都替换成新版本的节点
不就完成整个发布过程了吗
没有任何影响可能大家也会纠结
如果我这个业务它是一个长连接的
那么怎么去做配置的发布
这里面有几个非常重要的点
第一
你做的是一个类似像im一样的TCP的一个server
首先在架构设计层面不应该让带状态带业务
带所有需要运行时变更的这些东西
做到我们的TCP server层
就不应该做到这种所谓的接入节点层
业务逻辑应该全部下沉到下游
那么下游应该是无状态的一些服务
上游是有状态的服务
它做接入节点的 处理连接的
我们叫什么
叫协议卸载
协议现在它是有状态的
那么到下游无状态的时候
你不就是走一个迭代发布就搞定了吗
所以连接入层和业务网关逻辑层
应该是要从架构设计上区分
所以说你不存在我说的这种需求
你提的需求可能是一种伪需求
仍然有存在TCP server代码需要升级的 对吧
这种情况下也是有些办法的
比方说你可以配合我们的终端我们的APP
我们的web端或者我们的PC端
是有办法配合起来
做一些联动的配合到你远端接入节点的一个发布 也是有办法的
你可以重新到其他节点下线再上线
这个其实也是做得到的
我认为不是特别大的问题
这种情况下多半还是走迭代发布是相对安全的
静态配置
那么它是围绕资源的一些配置信息
但是的的确确我们线上是有一些应用程序的在线开关的
那么这一些应用程序的在线开关AB test的一些flag
这些控制业务的一些策略的会频繁的调整和使用
我们怎么办
当然这些开关或者一些简单的配置
通常都不是非常复杂的一些结构
通常都是一些基础类型
比方说int bool等等
我比较喜欢说把这种动态变更业务策略业务流的收归为一起
同时可以考虑结合类似像我们标准库的一个expvar这种库来结合使用
相当于你通过expvar暴露出去
通过http的方法
你可以访问到它有哪些动态配置
当前动态配置值是什么
你也可以通过这个界面去把它的动态的一些flag把它改掉
这一类通常做一些简单的业务控制
或者说一些灰度策略或者是AB test的一些策略
你可以通过这样的方式去下发下去
可能跟我们的配置中心其实也是可以打通的
但是它的这种简单的一些控制策略
它不会影响我们的业务的资源的
这种很重型的一些初始化
它不是一些很重型的资源初始化
像redis mysql
它可能是内部的一些策略的逻辑
我认为这种情况下相对来说是可控的
就是走动态配置
具体的话这一块我到时候对这也是一个todo
我们可以把这块的
我们内部的一些动态配置的一些下发策略
怎么结合flag去实现
可以到时候再讲一讲
这里记个todo
然后最后的话其实是全局配置
我们知道
我们经常会出现什么
以前出现过类似有一个同学把一个配置文件模板写错了
上线完之后其他人的服务都把错的配置拷来拷去
直到有一天出现一个bug
发现怎么配文件配错了
结果一查类似的配置错误一大片 为什么
大家都相互抄来抄去
怎么避免这种手动的copy
你手动copy的时候
这边ctrl+c那边ctrl+v
手一抖然后字母都敲了一下
可能最后一行就多了一个字母
它可能就会带来一些意外的配置错误
其实真的很常见的
所以我们依赖的大量的一些组件中间件
有大量的默认配置或者说指定的一些配置
这种配置你如果拷来拷去的话
真的容易出错
怎么办呢
一般配置中心它可以针对一些组件 一些中间件
像redis mysql
它是支持所谓的全局配置模板的
你可以用这种方式去替换掉它
mysql的一个通用的配置模板
那么某一个字段你可能可以大部分沿用有模板的
部分的你可以替换掉
既可以支持定制
也可以使用公共模板
那么简化我们的配置文件大量的配置内容
因为简单化是我们一直要努力的一个目标
一个业务服务来说
配置文件越简单越不会出错
基础设施配置文件越复杂越强大
请记住这一点
这个是很重要的一个设计
一个思想和理念
那么关于配置文件分类以后
我们比方说
举个例子就叫readis的一个client的example
它属于某种静态的配置
它属于某种静态配置
那我们现在在设计这个库的时候如何跟配置文件结合呢
我们先看一下
假设我现在有个需求就是要连接redis
那我连接redis需要什么
网络 TCP的还是什么别的什么UNIX socket协议的
需要一个地址
地址里面还带上端口
这样的话我就会设计一个Dial的方法
然后返回一个collection和error
但是它不满足需求
为什么
为什么不满足需求
它可能需要自定义超时时间
可能需要设定database是用db0还是db1
可能需要控制一下连接池的策略 对吧
我们知道redis client内部可能有连接池
可能需要安全是redis
要加一下password 加密码
就要配置一下慢查询
就超过多少秒的那种redis请求需要记录一下slowlog
可以设定这些
这些策略非常之多
我们通常怎么做
添加功能
扩展方法就好了
加DialTimeout 加DialDatabase 加DialPool等等一系列签名
像这种修改是可以扩展的 对吧
通过新加方法
但是我们一般在库设计的时候
大家做这种决策的时候一定记住一点
你作为一个package的owner
一定要考虑你的公共方法越多
意味着你的package越脆弱
因为你暴露的整个包的表面积
暴露的API的表面积越大
就越容易被破坏契约
请注意这一点
因为你被人用的太多
你有各种姿势的话你是很脆弱的
这是非常不好的一个
既然行不通 怎么办呢
我们通常说思路是什么
看看开源怎么做的
看看标准库怎么做的
这其实也是我的一个解决方案
我们看看他们怎么做
我们先看一下net/http
标准库的net/http怎么做
我们看一下这个代码很简单
我们new了一个http.Server
把一些配置信息传参传进 去比方超时啊 各种
然后最终就把这个服务启动起来
那么这样的一个方式是有什么好处呢
或者有什么不好
首先来说我可以通过文档的形式
把每一个参数要传什么东西写得清清楚楚
那么它如果有默认值
我是通过文档的方式告诉你
比方说这个地方不填 对吧
我就会监听什么类似说随便选一个端口
比方说你不填这个Addr
我可能就是监听一个随机端口
可以通过这样的文档的方式来描述它可选和必选
参数应该填什么规范
如果不填它的默认值是什么样的 对吧
比方说这个地方不填是nil 对吧
你可以这样的方式去描述
这是一种姿势
但是看上去其实也不那么好
这个里面有什么问题
首先假设我这个server启动以后
因为对象啊
server对象它是对外暴露出去的 对吧
返回的http.Server这个s对象被暴露出去
意味着我线上可以拿到这个s对象去改里面的ReadTimeout和WriteTimeout
可以在线改
还有MaxHeaderBytes
甚至我可以去改它的Handle还有Addr
那么这些改动会产生什么副作用
不知道
有可能会出什么副作用
不知道
但是你的的确确是可以改
但是它有什么副作用不知道
毕竟它是一个公共的public的一个字段你可以随便改
我也不知道会产生什么副作用
所以它这个不是特别好
而且它默认只是策略
怎么讲 非常隐晦
是靠文档去描述的
怎么办
我们有一个需求
我们现在有个需求是什么
需要从配置文件解析拿到一个对象
然后对redis做一个初始化
我们当时就这么设计的
既然要解析配置文件
我就会定义一个Config的结构体
包含它的一些基础的一些配置信息
然后我可能Dial刚刚那种方式
刚刚Dial的一种方式它字段组合非常多
你通过扩展方法肯定不行
比方说我既要database设定
又要设置timeout
你feature交叉一下出来
有多少个方法不知道
也无法满足这种需求
我们最容易想到就是参考http.Server
把它们全部打包在一起
所以我们就这样的打包在一起
打包一起我现在加了一个NewConn的方法
它常常是Config对象它是个指针
我们看一下这样的做法是什么呢
一样的拿到一个redis.Config对象
然后NewConn
它的副作用是什么
这个地方
这个c.Addr如果在线改它的副作用是什么
还是不知道 对吧
和前面提的net/http.Server这种方式它的副作用一模一样
因为为什么
Config对象它的字段全部是公共字段
它可以直接随便改的
所以你不知道副作用是什么 对吧
这个不好
我们再来看几种签名方式
我们是不是可以把它变成一个只读的
那么变成只读的
还有这种方式我们知道 ...
那么它变成一个可选参数
因为如果这个时候假设我们想使用默认配置
有一个基础库
它可能想使用默认配置
我比方说我们当时kratos内部的一个代码
log.Init初始化一个nil
nil的话我们内部写好了
它的一个配置文件可能就是默认配置
我们就会传递一个nil
但是这个地方我也再提醒一次
我们作为一个包设计者
通常不建议去传递nil作为参数
就是要么传一个值
要么你就没有这个参数
你不能说传一个nil表示的是一种特定的含义
使用默认配置不是特别好
你比方说下面这种方式
虽然它是个可选参数
所以说你可以不传这个值
但是它有一个副作用是什么
... 可以传多个Config 对吧
到底是哪个生效不知道
因为你可以传三四个Config 对吧
所以这两种都不是很好
就第二种和第三种都不是很好
因为第二种用nil的这种方式来使用默认配置真的是不是很好
我们再看一下Dave的这段英文
"I believe that we. as Go programmers.
should work hard to ensure that nil is never a parameter that needs to be passed to any public function."
简单来说我们相信作为一个Go程序员
我们应该努力的保障
你也不应该作为一个参数传递到我们的一个public function
公共的一个函数里面去
不是特别好
我们就看到这个
这种姿势好不好
这种默认的不带指针的有什么坏处呢
这个有什么办法
这个其实也是有问题的
它会有什么问题的
它内部的字段级的是可选参数还是必选参数
就是这个方法比下面的两种会稍微好一点
至少它没有不存在说你可以这样去模改 对吧
你不可能这样去模改
因为你Config是做了一个deep copy传进去的
你是做了一个deep copy传进去的
你外面的人是无法改的
Config本身如果你必须要传一个值的时候
假设我有一些字段
比方Config内部有一个字段我想要用它默认值
请问你这个时候是怎么搞
因为你比方用的基础类型string 对吧
你填一个copy的字段不填写它的zero value等于空
万一是用户手动设定的空
你是无法区分 对吧
就是零值和没设定你是无法区分的
它是一个undefined behavior
它无法定义这个行为
所以也不好用
思来想去找了两篇文章
一个是Rob Pike的
一个是Dave的两篇文章
这两篇文章都非常经典
一定要去看一下所谓的一种设计思想
它这种设计思想叫什么
叫Functional options
就是函数级别的一个可选项怎么做
这个地方我们看一下它的姿势就比较帅了
它先定义了一个DialOption的结构体
它内部实际上是包含了一个f的
叫f的一个callback一个函数指针
它的传参是个小写的dialOptions
那么它定义结构体以后
我们看一下它的方法签名
方法签名就有network和address和还有options
这个方法什么意思呢
表示哪些参数是可选
这个绝对是必选
因为它必须要填
后面的options就是可选
所以这个方法签名既告诉你
可选是什么必选是什么
都搞清楚了
那么这一个编程模型它还可以使用
很容易去使用默认值
怎么做呢
我们看一下
它可以在什么这个地方 我们看一下
options它如果为空表示没传
那么你这个地方就不会去替换掉它的值
如果你传了其中的一个值
那么它很容易什么
就把do里面的某一个字段给替换掉
在这个部分你是可以放默认值的
在这个地方是做外面传入的默认值的替换
那么你就可以做到一个什么
你不传的option就会被替换掉
对吧
就是用默认值替换掉
那么你传统的话就可以覆盖掉
我认为这个是比较爽的一个点
我们再看一下demo
这个时候我们的代码就改成这样了
redis.Dial network address
然后后面一坨可选的一些参数
比方说database呀 password呀 read timeout超时
那么我只要扩展每一个配置项
我加一个类似的DialXxxx的方法
就可以扩大它的所有的可选项的一个字段信息了
当然了也可以把 dialOptions写的简单一点
我们不想把这个f写出来
我可以直接把它定义为一个函数指针
这个地方也就不叫option.f了
前面这个地方是f
它叫option.f
这样如果定义成函数指针的话
这个地方就是直接option
调一下它就好了
非常舒服
那么这样还有一个好处什么
这个dialOptions注意
dialOptions是小写
如果它是小写
意味着没有任何人能够改里面的值
只能通过扩展新增一个dialOption的方法
也就是说只有通过包级别的owner
去扩展方法才能扩展它的option
别的人是无法在运行时序改里面的dialOption的
就是只有高级别的owner
你才能去想办法做内部的dialOptions的修改
也就是说你必须新增一个DialPassword DialDatabase
DialXxxx这种feature的新增
这种方法你才能去扩展修改里面的options
所以Functional options的思想其实非常好
我们再举个demo
我们有一些情况下
option的一些在改之前和改之后
我们可能需要改之后
可能某些场景下需要把它还原
比方说我们有一个叫这个(Verbosity)一个字段
表示的是日志级别的一个可见性
那么我就可以先获取之前的值
然后把它覆盖成最新的值
然后再调用同样的方法把之前的值返回去
也就说这个方法最终会返回一个option
它这个地方的option的定义
是定义的一个函数指针
但是它会返回以前的option
我们看到之前是不返回任何值的
那么它是会返回之前的
那么这样的话我就可以在先获取到prev
以前的option是什么
就设定一个新值
然后我在一个defer再把它改回去
然后在这个里面可以做一些do something
这样的话改之前和改之后
我做单元测试的时候
其实我就可以把它切回去
这也是一种小姿势
我们再来看一下开源的
比方说像gRPC
我们看一下它的设计
它一个叫GreeterClient定了一个interface
我们看它interface里面其实也是一样的
SayHello这个方法
首参数是context
第二个是输入参数
第三个就是option
client call时候的option
然后返回HelloReply和error
那么看一下它的CallOptions它怎么实现的
它实际上定义了一个interface
它内部有一个before和after的两个扩展的方法
因为我们前面的redis必须是包级别的作者才能去扩展
那么对于gRPC它的玩法
它自己内部有个叫EmptyCallOption struct
它是个空struct
它实现了CallOption的这两个小写的方法
内部我们这虽然看不到
它实现了这两个方法
这个时候如果我想自定义一个option怎么办
我是不是只需要包一个
把EmptyCallOption包含进去
然后我自己扩展一些字段
因为包含了EmptyCallOption
EmptyCallOption实现了CallOption的interface
那么意味着TimeoutCallOption
就可以传参给CallOption
因为它实现了这个接口
因为它通过包含实现这个接口
所以gRPC它是通过定义interface
你可以自己去扩展
它也是比较好用的一个做法
是不是问题就解决了
我们现在看一下
我们现在两个签名一个是Dial
我们知道使用DialOption这种方式比较舒服
那么第二种
还有一个问题
我的JSON/YAML文件怎么配置怎么加载
因为你无法映射到一个option
你不可能你手动把配置文件解出来之后然后自己去拼option
那很难写的
我们当时犯了一些错误
犯了一些错误
或者叫我们走一些弯路
怎么去加载配置
我们就所谓的叫Hybrid APIs
我们设计了两种API
一种就是说作为库的一个提供者
使用的标准的一个DialOption的方式
这是一种
第二种我们加了一个NewConn
它是传Config
这个Config实际上是可以通过json.Marshal去生成Config对象
然后再初始化连接的
我们当时是这样设计的
但是其实它不是特别好
因为你对一个东西一个资源的初始化变成了两个方法
我到底选哪个
然后你的签名表面积也变大了
你的方法签名表面积
你的包级别的表面积变大了
也不好
直到我们有一次看到
我去看到SRE第二本书叫
the-site-reliability-workbook 2
第二本
它里面就提到了一段话
就是说避免数据format
就是我们叫JSON数据和内部的数据
也就是说data这个JSON的数据
和你内部的需要初始化结构体资源需要的option那个对象
避免他们之间的紧密耦合
那么怎么办呢
避免之间的紧密耦合
我们再看一下
实际上一个用户
他是可以通过Web UI
通过Configuration API或者通过某种配置语言等等
就比方说通过配置中心
通过一个Python的脚本
它可以生成一个Configuration的Data
那么这三块其实都是一个接口层
生成的配置文件和我的系统其实应该要解耦的
怎么解耦呢
我们方法最终只保留了一种
就是用DialOption的这种方式
仅保留options API
那么config file和 option的结构体怎么去解耦呢
我们前面是不是讲到了gRPC的Protobuf这个文件
它是一种强schema的一种方式
就是说它是一种强约束的一种定义
很符合我们对配置文件的一种要求
那么也可以通过pb的定义
也可以知道我到底配置文件需要哪些字段
我们当时就想是不是就用Protobuf
然后我们比较喜欢用YAML
当然JSON也可以
我们最终的想法是说引入Protobuf来做语义的验证
在配置初始前
配置文件被我的库使用前
在配置中心或者在等等
这一路的这三种interface前使用这个pb前
我就可以做validation做验证
通过Protobuf我就知道它什么类型我就可以做高亮
让我的配置的文件填起来可以高亮
并且知道哪些字段是错的
或者哪些字段是对的
第三我通过pb也可以做Lint可以做定义
做一些code style check
或者是做一些这种schema的check
第四可以做格式化
因为我用YAML文件我用JSON文件
我的配置文件是比较好看的
那怎么解耦
我们想到一个办法
我们先看一下这个main函数
我们以前想的是什么
用Config
定义个这样的Config对象
直接把它加一个扩展一个新方法叫NewConn去初始化
那我们现在换一个思路
这个Config对象是通过Protobuff定义出来的
这个Config的对象
我瞄准的这个地方
它是通过Protobuff定义出来的
那么
我只要去加一个方法叫Options
让它返回一个redis.Options的数组
让它返回一个数组
redis.Options就假设是这样的
就apply或者是一个函数指针
我是不是只要把原始的是我们叫data format
就是JSON data format或者YAML data format
需要的结构体给它加一个方法
把它能够转换成redis需要初始化的Options
我们看一下 main函数
这个是原Config对象
但我这个地方是直接手填的那些值
其实你可以用load yaml file
你可以从yaml里面load出来
最终我只要c.Network c.Addr. c.Options
c.Options就这个方法
你可以返回这个数组
是不是就两个问题都解决了
通过Config加一个方法
还原出Options对象
然后再做对象的初始化
不就把对象的初始化和配置文件本身依赖的结构体彻底解耦了
中间加了一个桥梁的方法就解决了
做了个adapter 对吧
做了个试配器
那么这样的一个方式我们再改吧该吧
我们看一下
我们怎么结合YAML
比方说我这个地方
怎么结合YAML
传入的一个是redis.Config
这个是个Protobuf
YAML文本内容在这里
我可以调YAMLToJSON
先把YAML转成JSON
要么转成JSON之后再把JSON转成Protobuf
因为Protobuf和JSON之间可以互转
YAML不行
要用JSON做胶水做中间语言
先把YAML还原JSON
JSON再转成Protobuf
你就可以得到Protobuf的内存结构
得到内存结构以后我再写一个方法
我们Protobuf reids.Config对象你无法直接扩展方法
所以你得加一个这样的方法叫Options
传入的就是ridis.Config
得到的就是redis.Options
redis.Options是我真正redis client这个API库需要的
需要的它的配置参数信息
那么这里面还有一个问题
比方我这个地方定了义个redis.Config对象 对吧
它有一些
我们知道Protobuf v3无法区分可选值还是必选值 对吧
怎么办
我们前面有讲过
我们API设计里面有一个怎么区分零值和可选必选
以前v2用optional这个字段 对吧
去掉了
v3去掉了
那么v3你是不是可以用wrap struct
把它用一个包装类可以解决
我是不是可以用一个包装类 对吧
用一个string value
用string int32 value
用一个谷歌官方提供的一个wrap struct
在我们前面应该讲过
把这些替换你就可以区分它是否是有值
那么你这个数组的返回就可以要先做一个值判定
要决定要不要append这个数组 对吧
其实就非常简单了
我认为就非常简单了
最终这个代码就非常简单
你看就几行代码
这样的话就彻底解决了
你就把两个行为给解耦了
那么最终回溯一下
配置文件都有一些什么样的实践
可用性里面非常重要的就是讲变更管理
平常不发版
发版就必炸
因为你不变更系统好好的
它就不容易出错
所以代码变更为什么经过一系列的
我们内部的工程化演进
它不容易出锅呢
因为代码变更是一个冗长且复杂的过程
可能涉及到code review
可能涉及到测试
可能还涉及到灰度发布
等等一系列流程
最终推上线
但是配置文件大家从来不会去这么认真对待
什么看看code review
然后合到主干什么灰度各种测
不会对吧
所以配置文件其实很容易出现bug
那么怎么办
第一就是配置文件本身要避免复杂
本身要避免复杂
因为越多配置下越容易出锅
那么怎么避免复杂
依赖的通用的基础中间件
走全局配置化模板
很多配置中心都支持
自己去研究一下
第二多样的配置是什么
多样的配置是指你的配置模板
是其实可以通过覆盖其中的一些字段
来达到一个多样化的
这个也是通过模板加你的自定义配置可以做一个合并
做一个merge
第三向简单化努力
简单化努力就是如果你的基础库
大部分一些东西的初始化不需要配
那么你的redis.Options就不用传值
里面有一个最佳的default Options
它有一个默认配置
里面都是最佳的实践的配置值
那是不是你的配置文件就越来越少了
平常不需要配
这是一个很好的点
还有说以基础设施和面向业务代码的配置文件会有区别
业务的一定是越少越简单越好
基础设施的一定是越多越好
为什么你可以去针对一些特定场景去优化里面的参数
还有我们的配置一定是要区分可选和必选的
可选就是如果你没填
默认值应该是一个最佳实践的值
不要用什么零
我们以前jetty
用Java jetty的时候
它有一个值没配 默认是等于0
就是永不超时
然后它还有一个重试策略是86400
就是重试个8万次
简直想抽它
因为它出问题的时候
重试个8万次不就什么都打死了吗
所以默认值应该是个科学的合理的最佳实践的值
下一个是什么
叫配置的防御编程
因为配置文件有些用户手一抖
一秒钟停成了100秒
或者一秒钟填成了一分钟
我们一般在基础库对于这种不合理的配置
不要抛warming直接抛一个panic
让它彻底挂掉
因为带危害的配置如果被带到线上
哪天出锅的时候就是个大锅
如果你提前让在事前让panic掉
你就知道配置不合理
我赶紧去修正一下
所以我认为是配置的防御编程也是很重要的
另外我们配置文件一定要做权限和变更追踪的一个体系
所以有些同学觉得用git托管其实也是一种方案
或者用配置中心支持版本的回溯和回滚
这也是非常好的
还有一个点就是说我们配置文件和应用程序二进制版本
它们应该是要版本对齐的
某一个二进制版本用的配置文件的版本应该一一映射
因为否则你回滚应用你不回滚配置
是不是有可能不兼容就炸掉了
那么这个要怎么做呢
产生一个deploy的时候
你在回滚的时候
一般来说我们的发布平台或者说我们的k8s平台
应该是你的应用和配置版本同时回滚
它们应该是映射的一个版本关系
否则你有可能会出现diff
然后就会炸
最后就是说我们配置的变更应该跟面向代码一样的
就是要逐步部署
就是灰度1个2个5个节点
去看有没有出问题
如果一旦出问题要立马回滚整个更改
或者是自动回滚
因为你一把梭(哈) 一下推到线上
直接一个on-the-fly
动态的一个配置推出去直接全炸了 对吧
肯定不行所以也是要
我们叫什么
逐步部署的这种方式去应对配置的一个变更
这些其实就是配置文件的一些最佳实践
我希望你用Protobuf加我们的dial.Options这种思路
引进到你的项目工程里面
能够解决你很多问题
接下来我们后面就要开始讲
关于Go的包管理
这个也是大家吐槽比较多的一点