前言

利用kubernetes部署应用越来越流行,而运行在kubernetes中的服务需要的各种各样的配置如何才能实现热更新?难道需要在kubernetes中再部署zookeeper或者etcd之类的服务么?本文采用的方案是利用ConfigMap作为服务配置的持久化方案,并利用kubernetes提供的watch能力主动发现ConfigMap更新并及时更新到服务的配置中。这样运维人员只需要利用kubernetes的控制台(cli或者web)修改线上服务的配置,比如修改日志等级、降级、调整阈值等等。

本文引用源码https://github.com/jindezgm/konfig/blob/master/konfig.go

实现

konfig是利用kubernetes的一个ConfigMap实现的一个配置树,虽然ConfigMap.Data是map[string]string类型,但是konfig会对ConfigMap.Data中的值做进一步(递归)yaml解析,前提条件是值是以"---\n"开头。这样设计的目的是让konfig支持多级配置,而ConfigMap只有一级在一些使用场景并不友好。当然,利用一级也是可以实现多级配置,只是把多级体现在key上,例如:"a.b.c", "c.d",笔者认为视觉上不太优雅。

konfig支持以多种类型获取相同的配置,可以根据需要转换成指定类型,如下接口定义所示:

// 所有接口都会返回配置的版本号,即ConfigMap.ResourceVersion,keys是多级的key,当keys为空时表示根,即整个ConfigMap.
type Interface interface {// 如果keys已经被注册某种类型(参看下面的RegValue接口),则返回指定类型的值,否则返回原生类型的值。Get(keys ...string) (interface{}, int64)// 获取布尔型GetBool(keys ...string) (bool, int64)// 获取64、32位整型、GetInt64(keys ...string) (int64, int64)GetInt(keys ...string) (int, int64)GetInt32(keys ...string) (int32, int64)// 获取64、32位浮点型GetFloat64(keys ...string) (float64, int64)GetFloat32(keys ...string) (float32, int64)// 获取字符串GetString(keys ...string) (string, int64)// 将指定keys下的值注册为一种类型,配合Get()接口使用可以将keys下的值转换为注册的类型返回,其中tag是成员变量的tag名称,比如jsonRegValue(ptr interface{}, tag string, keys ...string) (interface{}, int64)// 获取指定类型的value,konfig会将map[string]interface{}转换为value对象,其中tag是成员变量的tag名称,比如json.GetValue(ptr interface{}, tag string, keys ...string) int64// 将指定的keys下值挂载/卸载到环境变量,MountEnv(keys ...string)UnmountEnv()// 获取版本号Revision() int64
}

konfig在Get指定类型的配置时,除了原生类型外,尽最大努力将原生类型转为指定类型,以下是konfig支持的类型转换:

  • int,int32,int64:支持浮点型转整型、支持字符串转整型(string->float64->intxx)、支持布尔型转整型(true:1,false:0)
  • float32,float64:支持字符串转浮点型
  • bool:支持整型、浮点型转布尔型(非零:true,零:false),支持字符串转布尔型(不区分大小写的"True":true,不区分大小写的"False":false)
  • string:支持所有类型转字符串,采用fmt.Sprintf("%v")返回

konfig保证了单个接口的原子性,但是如果连续调用两次接口可能会返回两个不同的版本,说明在两次调用接口之间ConfigMap发生了变化。如果两次调用接口获得的配置参数对于版本一致性要求比较高的话,就需要重新调用,直到所有的配置的版本相同为止。这种情况发生的概率比较低,并且绝大部分重新调用一次就可以解决,因为ConfigMap的更新频率极低。但是,这种方法貌似有点丑陋,使用者可以将此类的配置定义一种struct,然后通过GetValue()一次性拿到所有的配置。笔者常用的方法就是把整个ConfigMap定义为一个类型,然后一次性读取所有的配置。如下代码所示:

import "github.com/jindezgm/konfig"// 定义自己的配置类型
type MyConfig struct {Bool   bool   `json:"bool"`Int    int    `json:"int"`String string `json:"string"`
}kfg, _ := konfig.NewWithClientset(...)
var my MyStruct
var rev int64// 应用引用配置的功能实现
for {// 版本发生更新时重新获取配置if r := kfg.Revision(); r > rev {// 此处的keys为空,这是将整个ConfigMap.Data映射为MyStructrev = kfg.GetValue(&my, "json")}// 使用配置...
}

因为GetValue()接口会执行一次类似Unmarshal的过程,所以是有一定开销的,适用于调用频率不高的场景。如果需要高频调用,建议应用缓存配置(如上代码),并根据revision决定是否调用该接口。如果应用想省去这些麻烦的操作,那就调用RegValue()接口将类型注册到konfig,由konfig按需解析,在配合Get()接口就可以满足高频调用的场景了。如下代码所示:

// 将"my"下的所有值注册为MyConfig类型
kfg.RegValue(&MyConfig{}, "json", "my")
for {// 每次引用直接调用Get,konfig保证一致性、隔离性以及原子性value, _ = kfg.Get("my")my := value.(*MyConfig)...
}

在一些场景,某个功能点只需要引用一个配置项,使用者每次引用时可以直接调用接口,不用在自己的代码中缓存配置(当revision变大再读取配置更新缓存),因为konfig的读取性能还是有保证的。如下代码所示,按配置打印:

for {if p, _ := kfg.GetBool("print"); p {fmt.Println("Hello world")}
}

当然,如果习惯读取环境变量的方法获取配置,而容器更新环境变量又会造成容器重启,那么可以用MountEnv()接口将配置挂载到环境变量,如下代码所示:

// keys为空,将ConfigMap.Data挂载到环境变量. 需要注意的是,MountEnv()的keys下应该只有一级配置,如果是多级,konfig会用fmt.Sprintf("%v")进行格式化
kfg.MountEnv()
defer kfg.UnmountEnv()
for {if strings.ToLower(os.Getenv("print")) == "true" {fmt.Println("Hello world")}
}

konfig支持两种创建方式:1.利用Clientset;2.利用SharedInformerFactory。前者适用于应用无需不访问kubernetes场景,需要为konfig单独创建一次clientset,这部分可以参考官方实例代码;后者适用于应用需要访问kubernetes的场景,那么konfig与应用共享Informer。无论哪一种情况,都需要授权pod读取ConfigMap的权限。

那么,问题来了,为什么不用ConfigMap挂在成文件的方式?答:时效性不好,因为ConfigMap更新到Pod内文件更新可能需要数秒钟(我记忆中好像是10秒),如果线上需要紧急更新配置(比如降级处理)不是很好用,而konfig就没有这个问题。

不足

  1. 当前konfig递归解析只支持yaml格式,其实json也很容易支持,感觉不是很必要;
  2. konfig不支持回调,即ConfigMap更新后,将变化的部分回调给用户,当前的解决方式是通过revision解决;
  3. GetValue()虽然能够一次获取多个配置,但是需要所有的配置都在一个键下,如果一次获取配置树不同分支的多个配置,konfig还不支持,感觉可以用flag.FlagSet实现;
  4. MountEnv()不会比较两次配置的差异,然后删除新ConfigMap中没有的配置,他只是简单的将每次ConfigMap中的值设置到环境变量中;而UnmountEnv()也不会删除环境变量,只是不再更新环境变量而已;这些都可以实现,只是暂时还没有看到必要性;

konfig:采用ConfigMap实现线上配置热更新相关推荐

  1. java 热更新class_线上java热更新代码实现

    游戏上线后难免会有功能性bug,这些bug很多只做一些小的改动即可修复.设想假如每次有bug修复之后,都要重启服务器,势必会导致部分玩家流失,对游戏产生不好的影响.在这个背景下,代码热更新还是很有必要 ...

  2. Nacos配置管理-配置热更新

    配置热更新 我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新. 要实现配置热更新,可以使用两种方式: 方式一 在@Value注入的变量所在类上添加注解@Re ...

  3. 基于选项模式实现.NET Core的配置热更新

    作者 | 秦元培 出品 | CSDN(ID:CSDNnews) 头图 | CSDN 下载自东方 IC 最近在面试的时候,遇到了一个关于 .NET Core 配置热更新的问题,顾名思义,就是在应用程序的 ...

  4. Nacos配置热更新两种方式。

    1:目的: 修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新 2:方式 (1)在@Value注入的变量所在类上添加注解@RefreshScope:(在这里呢应该辉出现空指针异 ...

  5. Nacos配置管理——配置热更新

    文章目录 Nacos配置热更新 1.方式一 2.方式二 Nacos配置热更新 我们引入Nacos配置中心的最终目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新. 要实 ...

  6. IDEA SpringBoot项目配置热更新,无需每次手动重启服务器

    IDEA SpringBoot项目配置热更新的步骤 在pom.xml中添加依赖: <dependency><groupId>org.springframework.boot&l ...

  7. Mysql服务器线上配置主从同步

    我们一般在线上搭建MYSQL都会部署一套主从同步方案: 当master(主)库的数据发生变化的时候,变化会实时的同步到slave(从)库. 主从复制的过程: Mysql同步过程的第一部分就是maste ...

  8. YARP+AgileConfig 5分钟实现一个支持配置热更新的代理网关

    YARP 是微软开源的一个反向代理项目,英文名叫 Yet Another Reverse Proxy .所谓反向代理最有名的那就是 nginx 了,没错 YARP 也可以用来完成 nginx 的大部分 ...

  9. flutter已经支持安卓热更新_flutter 在 android 上的热更新

    热更新是一种需求吧. 自然会想到flutter 是否支持热更新. 然后一些群里问了问普遍反映不可以热更新,还说咸鱼的文章写了不支持热更新. 然后我表示很怀疑. 我的结论可以做到热更新 1.你需要把fl ...

最新文章

  1. java websocket 上传大文件,使用java websocket API和Javascript上传文件
  2. 回文树(回文自动机) - URAL 1960 Palindromes and Super Abilities
  3. 10 过滤器和监听器
  4. 《恐怖小说在中国》之四:恐怖小说的流行与陷阱?
  5. 电商系统下单时商品库存和销售状态如何处理
  6. Deepin 下安装 LAMP
  7. 戴尔网站的服务器,PowerEdge 11G R610机架式服务器
  8. bugscan泄露代码解密
  9. tshark过滤并保存包特定字段
  10. 一行代码安装xgboost
  11. windows10电脑发现不了网络计算机,Win10电脑无法开启网络发现怎么解决?
  12. 什么是遥远的相似性?
  13. 如何成为一个游戏制作人——第5.5章一个小游戏的框架
  14. cocos2dx android obb,cocos2dx 实现obb包读取 quick2.2.6
  15. python基础学习-反射
  16. xposed hook之360加固的APP过模拟器检测
  17. window7 下调试PTAM,PTAMM
  18. 浅谈敏捷思想-08.从产品愿景到用户故事地图
  19. tcpdump+wireshark双剑合璧
  20. mysql连接与嵌套查询_数据库之嵌套查询与连接查询

热门文章

  1. 满江红票房破6亿,你觉得好看吗?
  2. AD域批量的导入账号
  3. 小说作者推荐:不问三九合集
  4. 信息学奥赛一本通2066
  5. PWA之 Service worker
  6. 樱花飘落,3D效果,抖音热门樱花飘落html
  7. 如何写好一份测试用例
  8. CSS里的 no-repeat 是什么意思
  9. spark进行数据清洗时,如何读取xlsx表格类型文件
  10. Go开发 之 容器(数组Array、切片slice、映射map、列表list)