随着业务的发展,应用系统中的配置通常会越来越多,常见的一些应用配置大致会有数据源配置,数据源组件配置,业务组件配置等,对于这类配置都会比较稳定且较少变化,通常会放在文件中随应用一起发布。但实际中会有某些配置信息变化有一定频率和规律,并且希望能够做到尽量实时,比如一些营销类,或活动类应用系统,若使用传统的配置文件,加上重新发布应用可能会有些不方便,因此,才有了分布式配置管理平台,旨在能更好地解决这类问题。本文将介绍相关细节,及一个轻量的开源实现(https://github.com/ihaolin/diablo)。
分布式配置平台的一些应用场景
分布式配置,也即配置中心。通常有以下的场景或需求,可以需要考虑使用分布式配置:
对某些配置的更新,不想要重启应用,并且能近似实时生效;
希望将配置进行统一管理,而非放入各应用的配置文件中;
对于某些应用系统,其某些配置变更比较频繁,规律;
通常配置中心也可作为在其他分布式应用中的感知组件,比如典型的Zookeeper;
...。
分布式配置平台需要满足的一些基本特性
对于一个可靠的分布式配置平台,大致应该满足一些基本特性,如:
高可用性:服务器集群应该无单点故障,即只要集群中还有存活的节点,就能提供服务;
容错性:容错性主要针对客户端,应保证即便在配置平台不可用时,也不影响客户端的正常运行;
高性能:对于配置平台,主要操作则是获取配置,不能因为获取配置给应用带来不可接受的损失;
可靠的存储:这包括数据的备份容灾,一致性等,通过数据库和一些运维手段可以解决;
近似实时生效:对于配置的变更,客户端应用能够及时感知;
负载均衡:为了尽量提升服务器集群的性能及稳定性,应尽量保证客户端的请求能尽量均衡负载到各服务器节点;
扩展性:服务器集群应该保证做到无感扩容,以提升集群服务能力;
...。
分布式配置平台Diablo的设计与实现
Diablo架构设计
Diablo的架构设计比较简单轻量,如图:
Apps:及各类业务应用实例;
Servers:为客户端提供获取配置服务的Server集群;
Redis Storage Service:用作数据存储。
Diablo模型设计
应用(App):通常,对于一个用于各外部系统的平台,都可以抽象这些系统为应用,用于标识或是分组。对于应用环境,个人觉得应交由应用去处理,而不是在平台为各应用提供不同环境,比如可以通过应用名称就能区分不同环境。除此外,通常需要让应用提供一些安全方面的配置,如签名Key,加密Key等,可用于在与外部应用交互中作一些安全处理(如签名,加密等);
配置项(Config):对于配置平台,其数据模型比较简单,核心数据就是配置项。配置项除了基本的配置名称和配置值外,通常还需要有用于判定配置项是否变更的字段,可以用MD5值等。
对等的服务器集群
Diablo集群中的Server被视为是对等的,即各节点没有主从(Master/Slave)关系,是逻辑相等的,这样就避免了Master/Slave架构带来的问题,如数据同步延迟,数据丢失等问题。
高性能处理
客户端应用获取配置时,仅会从本地缓存中获取,开发人员在控制台更改配置后,会通知客户端刷新缓冲;
使用Redis作存储
配置平台本身并没有太多复杂的关联关系,因此使用NoSQL也能满足常用的查询。在设计存储Key上,因尽量保证Key的简洁清晰,比如,存储应用记录,可以以apps:1来标识一条记录,而存储结构最好使用hash,而不是使用JSON字符串。在使用Redis时,diablo避免使用了一些特殊函数,如管道,事务等,因为这些函数在一些Redis的高可用解决方案(如Redis Cluster,Redis Proxy等)中通常不支持,这样用户可以自由使用不同的Redis高可用方案。
客户端的实现
重试等待:diablo会通过重试等待等机制保证,在服务端集群不可用时,也不会影响客户端应用的正常运行,而是等待集群恢复;
请求负载:为了使服务器集群的各节点的负载尽量均衡,在客户端进行请求处理前,服务端会为客户端分配一个可用的Server节点,后续的请求将在该节点上,这里使用的负载算法是一致性哈希;
额外参数:通常对于客户端实现,在与服务器交互过程中,除了必要的数据外,还会携带一些额外参数,如客户端版本号(为后期作兼容性处理),语言类型(后端可针对不同语言进行处理)等。
配置更新实时生效
配置更新实时生效是配置平台的核心功能之一,对于获取配置的方式,大致有两种模式:
Pull模式:即客户端主动向服务端拉取配置信息,通常是客户端定时轮询拉取。这种方式最简单稳定,但是存在时间间隔问题,间隔太长,配置延迟更新越大,间隔太短对服务器会造成过多的压力;
Push模式:即当配置发生变更时,服务端主动推送更新至各个客户端。这种方式能保证配置更新实时生效,但需要在客户端/服务端建立长链接,服务端需要处理各种异常情况和协议规范,保证更新能实时推送成功,实现起来相对麻烦些。
diablo使用了特殊Pull模式,即(长轮询)Long Pulling。当客户端发起Http请求后,服务端接收处理完请求,并不会立即返回客户端,而是等待一定条件发生或超时后才返回客户端,这里的一定条件就是当有配置发生变更时,这样就有效减少了客户端请求,也达到了实时生效的目的。对于Java长连接的实现,主要使用了Servlet规范中的AsyncContext,使得服务端接收到请求后,并不会让Servlet容器立即返回客户端,而是当调用AsyncContext.complete()方法时,才会返回。
客户端语言类型
diablo默认实现了Java语言的客户端,希望以后能支持node,go,python等语言。由于diablo仅通过Http接口提供服务,不同语言只要遵循API接口,即可实现不同的语言版本。若读者有意实现,可参考该客户端规范,下图为客户端与服务端的交互过程:
diablo实践建议
服务器集群部署:对于一个高可用的服务器集群,建议可以nginx等代理服务器作转发,这样在服务器集群发生变化时,客户端应用也不用更新任何配置,如:
应用环境区分:对于需要区分不同环境的应用,可以通过不同的应用名称作区分,如app_dev,app_test。但对于生产环境,都建议与其他环境隔离,独立部署。
总结
以上,则是有关分布式配置管理平台设计与实现的相关细节,也欢迎issue和fork项目diablo。
这里主要使用到disconf分布式配置管理平台 支持window和linux下面是大家window环境步骤和一些操作总结。
所需环境:Windows、nginx1.8.1、redis3.0.5、zookeeper3.4.6、mysql5.7 、python2.7.11、Git-2.6.4-64-bit.exe
之前一直采用properties文件管理配置信息,若是集群则每个机器上都要拷贝一份,每次修改也需要依次修改。一直在寻找统一修改,实时生效,方便修改,分环境分系统的配置管理,自己也在整理设计,若找不到合意的就准备自己写一个,可以根据自己需求慢慢改进。通过开源中国微博知道了360的配置管理,看了下没大搞明白,貌似管理不太方便,反正不是我想要的,后来知道了百度的disconf,淘宝也有一个配置管理。我先看了百度的disconf,这就是我想要的,所以没看淘宝那个配置管理。
首先这是一个开源项目,托管在github上,地址: https://github.com/knightliao/disconf,官方的文档还是很丰富的,地址:https://github.com/knightliao/disconf/wiki 。建议先看官方文档,文档很实用,花不了多少时间,我这里仅就官方没说,但刚接触这个的人常见的部分问题说说自己的解决方案,下面是一张运行效果图。
要看这个项目,需要的知识:java相关技术、前端、git、mysql、tomcat、redis、zookeper、nginx,后面几个简单度一下就能了解个大概。
由于官方文档比较详细,这里基本没有提到disconf本身的用途,使用方式。建议到处问人前先仔细看看官方教程。
l 简单易用,用户体验良好
l 支持配置(KV配置项+配置文件)的分布式化管理
l 配置发布、更新统一化:用户统一在平台上进行发布、更新配置。
l 配置更新自动化:用户在平台更新配置,使用该配置的系统会自动发现该情况,并应用新配置。
系统结构图如下:
初始化时,业务流程图如下:
配置更新时,业务流程图如下:
系统模块架构图如下:
配置管理模块:统一管理用户实例中本地配置文件和配置项
下载模块:restful风格的下载配置文件和配置项
watch模块:监控远程配置文件和配置项的变化
配置管理模块:支持配置模板(配置项或配置文件)的上传、下载、更新
配置存储模块:管理所有配置的存储和读取,根据appName、version、environment来区分项目配置
通知模块:当配置更新后,实时通知使用这些配置的所有实例
权限控制:web用户的权限控制
在app.config或者web.config中的configSections节点下添加配置
<section name=”clientConfig” type=”Disconf.Net.Client.ClientConfigSection,Disconf.Net.Client”/>
然后在appSettings同级别的节点上添加clientConfig配置,示例如下
<configSections> <section name=”clientConfig” type=”Disconf.Net.Client.ClientConfigSection,Disconf.Net.Client”/> > <appSettings file=”appSettings.config”/>=”clientConfig.config”/>
具体示例如下:
<clientConfig webApiHost=”http://192.168.1.100:8088/” enableRemote=”true”>=”consoletest” environment=”Dev” version=”1.0.0.0” clientName=”Console_1”/> ="notdown.txt" itemIgnores="aa,bb,cc " startedSync="true" retryTimes="3" retryIntervalSeconds="10" /> <preservation absolutePath="false" tmpRootDirectory="Tmp\Download\Configs" factRootDirectory="" tmpItemsLocalName="~items.xml" tmpFilesLocalName="~files.txt"/> clientConfig>
节点名称 |
必配 |
默认值 |
节点描述 |
|
webApiHost |
|
Y |
|
Rest服务器域名地址 |
enableRemote |
|
N |
true |
是否启用远程配置,默认true,设为false的话表示不从远程服务器下载配置 |
clientInfo |
appName |
Y |
|
客户端程序名称,注意大小写要与服务端一致 |
environment |
Y |
|
当前客户端程序所处环境,注意大小写要与服务端一致 |
|
version |
Y |
|
当前客户端程序版本,注意大小写要与服务端一致 |
|
clientName |
N |
|
客户端标识,用于服务端查看已更新客户端,如果不设置则默认获取客户端电脑名称 |
|
updateStrategy |
fileIgnores |
N |
|
要忽略更新的文件配置,以,分割,注意大小写要与服务端一致 |
itemIgnores |
N |
|
要忽略更新的键值对配置,以,分割,注意大小写要与服务端一致 |
|
startedSync |
N |
true |
启动时是否同步加载,默认同步 |
|
retryTimes |
N |
3 |
当获取失败时的重试次数 |
|
retryIntervalSeconds |
N |
10 |
每次重试时间间隔,单位秒 |
|
preservation |
absolutePath |
N |
false |
是否绝对路径,默认false。当false时,表示默认以 AppDomain.CurrentDomain.BaseDirectory为比较点,注意:该配置同时适用于TmpRootDirectory、 FactRootDirectory,即要么都只能绝对路径,要么都只能相对路径 |
tmpRootDirectory |
N |
Tmp/Download/Configs |
下载下来的配置临时保存文件夹根目录 |
|
factRootDirectory |
N |
Configs |
配置文件实际所在的根目录 |
|
tmpItemsLocalName |
N |
~items.xml |
在临时目录下用于保存所有键值对的文件名,设置为空表示不保存,文件保存在TmpRootDirectory目录下,所以注意不要与 实际配置文件名字冲突 |
|
|
tmpFilesLocalName |
N |
~files.txt |
在临时目录下用于保存所有文件配置名的文件名,设置为空表示不保存,文件保存在TmpRootDirectory目录下,所以注意不要与实际配置文件名字冲突 |
除了配置外,还需要设置更新策略,客户端才能进行配置更新。目前,Rules设置仅支持编码的方式进行,Rule分两种:FileRule,ItemRule,下面分别进行描述:
FileRule:用于设置如何更新文件类型配置,其包含以下方法
方法名 |
描述 |
IFileRule MapTo(string refreshSectionName) |
注册Rule规则,设置默认的文件配置映射 参数refreshSectionName表示更新回调时, ConfigurationManager.RefreshSection要刷新的节点名称,默认采用远程配置的configName |
IFileRule RefreshIgnores() |
不自动调用ConfigurationManager.RefreshSection方法更新配置 |
IFileRule CallBack(Action action) |
当文件下载完成并且替换本地对应文件后回调,注意此处将采用委托链的方式,即多次调用均会被执行 |
ItemRule:用于设置如何更新键值对类型配置,其包含以下方法
方法名 |
描述 |
IItemRule MapTo(string propName) |
注册Rule规则,设置默认的属性映射参数 propName表示要赋值的属性名,默认采用远程的configName |
IItemRule SetProperty |
更新指定实体的属性值,按默认方式获取实例属性,注意此处多次调用均会被执行 |
IItemRule SetProperty(object entity, PropertyInfo prop, Func |
更新指定实体的属性值,注意此处多次调用均会被执行 |
IItemRule SetStaticProperty |
更新静态属性的值,按默认方式获取静态属性,注意此处多次调用均会被执行 |
IItemRule SetStaticProperty(PropertyInfo prop, Func |
更新静态属性的值,注意此处多次调用均会被执行 |
IItemRule CallBack(Action |
当值发生变更时如何进行回调,注意此处将采用委托链的方式,即多次调用均会被执行 |
该类为Client配置入口,通过Singleton提供唯一实例,除了提供Rules的配置入口外,还提供异常通知的事件
要使Disconf.Net.Client工作,必须显示执行指定方法manager.Init(),而在init之前,还需设置Rule和Fault,可以通过ConfigManager.Instance来获取该类的实例对象,然后通过对应的Rule进行相关Rule设定,示例如下:
//要更新的文件 ConfigManager.Instance.FileRules.For("appSettings.config").CallBack(() => { Console.WriteLine("File changed notice twice"); }); //要更新的键值对 ConfigManager.Instance.ItemRules.For("Dai").MapTo("Person").SetStaticProperty().CallBack(v =>{ Console.WriteLine("Now item value:{0}", v); Console.WriteLine("Program.Person is {0} now", Program.Person); if (v.Length > 3) { throw new Exception("Too Long"); } }); //忽略更新到本地的键值对 ConfigManager.Instance.ItemRules.For("Peng").CallBack(v =>{ Console.WriteLine("Now item value:{0}", v); }); //异常处理 ConfigManager.Instance.Faulted+=Manager_Faulted; //Config初始化,包括ZooKeeper、scan等 ConfigManager.Instance.Init();
需要特别说明的是:
1、File因为属于下载后覆盖指定位置文件的方式,所以对于Rule可以设置默认规则,如例子中的appSettings.config,其对应的就是config文件中的appSettings部分,此时如果不需要进行CallBack调用,且文件名称(去除后缀)部分与Section一致,那么这部分Rule设置可以忽略,程序会在初始化时自动进行默认设置,而对于Item,因为无法确认更新策略,所以如果不设置Rule,那么就算从服务端获取到了值,该部分也只能被忽略。
2、对于异常部分,程序只是简单的通过Faulted事件来传递异常信息,该事件只有一个Exception类型的参数。
配置步骤:
1、 创建具体应用(项目)
2、 创建应用的配置模板(1~n个配置,如appSetting.config、redisconfig.config、rabbitMQConfig.config等配置模板)
3、 创建应用的环境(如:开发环境、测试环境、仿真环境等),修改相关的配置
4、 启用对应的配置
5、 至此,client端就可以获取应用环境对应的所有配置
登陆进入配置管理界面
【新建】:填写应用名称,应用描述保存完成新建,返回可返回应用管理首页。
【初始化ZooKeeper】:第一次启动时Zookeeper初始化。
【编辑】:与新建界面一致,可修改应用名称,应用描述,保存即返回应用管理首页。
【编辑环境】:进入环境环境配置管理首页。
【删除】:删除对应应用记录。
显示所有模板,操作环境配置前,需要先配置模板,根据模板对相应环境的配置进行操作。
【新建】:新增模板,填写模板名称、描述、类型、默认值版本号等,如选择文件类型。可上传文件读取文件内容,版本号可以选择已经有的版本号,或者新建版本号。
【编辑】:操作同新建模板,可对模板内容进行修改。
【删除】:点击删除可删除对应模板记录,如该模板在环境中存在配置项,则该模板不允许删除,需删除对应该模板的配置项,才可以删除对应模板。
【新增环境】点击加号可以新增环境,填写环境名称,描述保存即可。
【编辑环境】在对应环境上点击鼠标右键即可弹出编辑菜单,点击Edit即可编辑环境,可以修改名称内容等。
【配置首页】:配置首页根据版本进行分类,默认显示头部第一个版本,点击其他版本可以进行切换,显示的配置项是模板默认配置项,点击启用即可个性化赋值,针对不同环境进行不同的赋值。编辑可编辑相应配置,禁用等同于删除配置。
【启用配置】:名称默认值不能修改,可以点击使用默认值,直接赋值,也可以上传文件使用文件内容,保存即可。
【编辑配置】:操作同启用配置,保存即可修改值。
【禁用配置】:禁用等同于删除配置,删除对应模板配置项,可删除对应模板。
【角色首页】:
Ø 角色首页展示角色列表,角色分为超级管理员和非超级管理员;
Ø 超级管理员角色不展示;
Ø 超级管理员可以看到所有非超级管理员角色,非超级管理员只可以看到当前角色用户创建的角色;
Ø 可以新增角色,也可以对角色进行编辑,只有在创建用户时勾选是否为系统管理员才可以进行角色管理。
【新建角色】:
Ø 新建角色输入角色名称,可以勾选的权限为当前用户所拥有的权限;
Ø 新建的角色作为该用户的下属角色,可分配给当前用户新建的用户;
Ø 父级权限为新建应用所增加的权限,以后每增加一个环境,就相应的增加该应用下的该环境权限,除超级管理员外的角色需对应勾选该权限才能看到该应用或者该权限,保存角色即可。
【编辑角色】:操作同新建角色,可以对该角色进行名称修改,权限修改。
管理用户首页,显示所有用户,可进行新建,编辑用户等操作。
【新建用户】:填写姓名,用户名,密码,选择角色(拥有对应角色权限、且可以选择的角色为当前登陆用户新建的角色),选择是否为系统管理员(系统管理员拥有新建用户、新建角色权限),保存即可。
【编辑用户】:操作同新建用户,保存即可修改。
案例三、
XXL-CONF 是一个分布式配置管理平台,其核心设计目标是“为分布式业务提供统一的配置管理服务”。现已开放源代码,开箱即用。
why not properties
常规项目开发过程中, 通常会将配置信息位于在项目resource目录下的properties文件文件中, 配置信息通常包括有: jdbc地址配置、redis地址配置、活动开关、阈值配置、黑白名单……等等。使用properties维护配置信息将会导致以下几个问题:
why XXL-CONF
git.oschina地址
最新Release版本: v1.3.0 最新Beta版本: v1.3.0
com.xuxueli
xxl-conf-core
1.3.0
请下载项目源码并解压,获取 "调度数据库初始化SQL脚本" 并执行即可。脚本位置如下:
xxl-conf/db/xxl-conf.sql
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下图所示:
项目:xxl-conf-admin
作用:管理线上配置信息
配置文件位置:
xxl-conf/xxl-conf-admin/src/main/resources/xxl-config-admin.properties
配置项目说明:
# xxl-conf, zk address (配置中心zookeeper集群地址,如有多个地址用逗号分隔)
xxl.conf.zkserver=127.0.0.1:2181
# xxl-conf, jdbc (配置中心mysql地址)
xxl.conf.jdbc.driverClass=com.mysql.jdbc.Driver
xxl.conf.jdbc.url=jdbc:mysql://localhost:3306/xxl-conf?Unicode=true&characterEncoding=UTF-8
xxl.conf.jdbc.username=root
xxl.conf.jdbc.password=root_pwd
# xxl-conf, admin login (管理中心登录账号密码)
xxl.conf.login.username=admin
xxl.conf.login.password=123456
项目:xxl-conf-example
作用:供用户参考学习如何接入XXL-CONF
com.xuxueli
xxl-conf-core
${xxl.conf.version}
可参考配置文件:
/xxl-conf/xxl-conf-example/src/main/resources/spring/applicationcontext-xxl-conf.xml
配置项说明
可参考配置文件:
/xxl-conf/xxl-conf-example/src/main/resources/xxl-conf.properties
配置项说明
# xxl-conf, zk address (配置中心zookeeper集群地址,如有多个地址用逗号分隔)
xxl.conf.zkserver=127.0.0.1:2181
该配置文件,除了支持配置ZK地址,还可以配置一些本地配置。 XXL-CONF 加载配置时会优先加载 "xxl-conf.properties" 中的配置, 然后才会加载ZK中的配置。可以将一些希望存放本地的配置存放在该文件。
每个配置分组对应一个唯一的GroupName,作为该分组下配置的统一前缀。在“分组管理”栏目可以创建并管理配置分组信息,系统已经提供一个默认分组.
登录"配置管理中心"
进入"配置管理界面",点击"新增配置"按钮
在弹出界面,填写配置信息
至此, 一条配置信息已经添加完成.
通过client端,可以实时获取配置信息, 通过本地已经加载过得配置将会接受Zookeeper的更新推送, 如下如日志:
项目: xxl-conf-example: (可以参考 com.xxl.conf.example.controller.IndexController.index() )
作用: 接入XXl-CONF的Demo项目
方式1: XML文件中的占位符方式
特点:
方式2: API方式
String paramByClient = XxlConfClient.get("default.key02", null);
特点:
系统配置信息以K/V的形式存在, "配置项" 属性如下:
每条配置,将会生成全局唯一标示GroupKey,在client端使用时,需要通过该值匹配对应的配置信息;
ZK之watcher普及(来源官方文档,以及网络博客)
1、可以注册watcher的方法:getData、exists、getChildren。
2、可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失。
3、一个Watcher实例是一个回调函数,被回调一次后就被移除了。如果还需要关注数据的变化,需要再次注册watcher。
4、New ZooKeeper时注册的watcher叫default watcher,它不是一次性的,只对client的连接状态变化作出反应。(推荐ZK初始化时, 主动Watcher如exists)
5、实现永久监听: 由于zookeeper是一次性监听,所以我们必须在wather的process方法里面再设置监听。
6、getChildren("/path")监视/path的子节点,如果(/path)自己删了,也会触发NodeDeleted事件。
《操作--事件》 | event For “/path” | event For “/path/child” |
---|---|---|
create(“/path”) | EventType.NodeCreated | 无 |
delete(“/path”) | EventType.NodeDeleted | 无 |
setData(“/path”) | EventType.NodeDataChanged | 无 |
create(“/path/child”) | EventType.NodeChildrenChanged(getChild) | EventType.NodeCreated |
delete(“/path/child”) | EventType.NodeChildrenChanged(getChild) | EventType.NodeDeleted |
setData(“/path/child”) | 无 | EventType.NodeDataChanged |
《事件--Watch方式》 | Default Watcher | exists(“/path”) | getData(“/path”) | getChildren(“/path”) --- | --- | --- | --- EventType.None | 触发 | 触发 | 触发 | 触发 EventType.NodeCreated | | 触发 | 触发 |
EventType.NodeDeleted | | 触发 | 触发 | EventType.NodeDataChanged | | 触发 | 触发 | EventType.NodeChildrenChanged | | | | 触发
ZooKeeper的一个性能测试
测试数据来自阿里中间件团队
ZK集群情况: 3台ZooKeeper服务器。8核64位jdk1.6;log和snapshot放在不同磁盘;
场景一: pub创建NODE,随后删除
场景二: pub创建NODE, sub订阅并获取数据
场景三: pub创建NODE,随后设置数据
总结: 由于一致性协议带来的额外网络交互,消息开销,以及本地log的IO开销,再加上ZK本身每1000条批量处理1次的优化策略,写入的平均响应时间总会在50-60ms之上。但是整体的TPS还是可观的。单个写入数据的体积越大,响应时间越长,TPS越低,这也是普遍规律了。压测过程中log文件对磁盘的消耗很大。实际运行中应该使用自动脚本定时删除历史log和snapshot文件。
"配置管理中心" 是 "配置中心" 的上层封装, 提供Web界面供用户对配置信息进行配置查询、配置新增、配置更新和配置删除等操作;
API方式加载配置: 客户端主要分为三层:
(API方式加载配置, 因为底层做了配置本地缓存, 因此可以放心应用在业务代码中, 不必担心并发压力。完整的支持配置实时推送更新)
Bean方式加载配置:
系统会在Spring容器中追加一个"PropertyPlaceholderConfigurer"属性解析器, 内部通过自定义的"StringValueResolver"解析器解析配置占位符 "${...}", 匹配到的配置信息将调用"XXL-CFONF"的API客户端加载最新配置信息进行Bean对象的属性赋值,最终完成实例化过程。
(Bean方式加载配置,仅仅在实例化时加载一次; 考虑都实例化后的对象通常为持久化对象, 如数据库连接池对象, 不建议配置的太灵活, 因此Bean类型配置更新需要重启机器)
XXL-CONF托管在Github上,如有问题可在 ISSUES 上提问,也可以加入上文技术交流群;
更多接入公司,欢迎在github 登记
案例四、
Ø 超级管理员角色不展示;
Ø 超级管理员可以看到所有非超级管理员角色,非超级管理员只可以看到当前角色用户创建的角色;
Ø 可以新增角色,也可以对角色进行编辑,只有在创建用户时勾选是否为系统管理员才可以进行角色管理。
【新建角色】:
Ø 新建角色输入角色名称,可以勾选的权限为当前用户所拥有的权限;
Ø 新建的角色作为该用户的下属角色,可分配给当前用户新建的用户;
Ø 父级权限为新建应用所增加的权限,以后每增加一个环境,就相应的增加该应用下的该环境权限,除超级管理员外的角色需对应勾选该权限才能看到该应用或者该权限,保存角色即可。
【编辑角色】:操作同新建角色,可以对该角色进行名称修改,权限修改。
管理用户首页,显示所有用户,可进行新建,编辑用户等操作。
【新建用户】:填写姓名,用户名,密码,选择角色(拥有对应角色权限、且可以选择的角色为当前登陆用户新建的角色),选择是否为系统管理员(系统管理员拥有新建用户、新建角色权限),保存即可。
【编辑用户】:操作同新建用户,保存即可修改。
案例三、
XXL-CONF 是一个分布式配置管理平台,其核心设计目标是“为分布式业务提供统一的配置管理服务”。现已开放源代码,开箱即用。
why not properties
常规项目开发过程中, 通常会将配置信息位于在项目resource目录下的properties文件文件中, 配置信息通常包括有: jdbc地址配置、redis地址配置、活动开关、阈值配置、黑白名单……等等。使用properties维护配置信息将会导致以下几个问题:
why XXL-CONF
git.oschina地址
最新Release版本: v1.3.0 最新Beta版本: v1.3.0
com.xuxueli
xxl-conf-core
1.3.0
请下载项目源码并解压,获取 "调度数据库初始化SQL脚本" 并执行即可。脚本位置如下:
xxl-conf/db/xxl-conf.sql
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下图所示:
项目:xxl-conf-admin
作用:管理线上配置信息
配置文件位置:
xxl-conf/xxl-conf-admin/src/main/resources/xxl-config-admin.properties
配置项目说明:
# xxl-conf, zk address (配置中心zookeeper集群地址,如有多个地址用逗号分隔)
xxl.conf.zkserver=127.0.0.1:2181
# xxl-conf, jdbc (配置中心mysql地址)
xxl.conf.jdbc.driverClass=com.mysql.jdbc.Driver
xxl.conf.jdbc.url=jdbc:mysql://localhost:3306/xxl-conf?Unicode=true&characterEncoding=UTF-8
xxl.conf.jdbc.username=root
xxl.conf.jdbc.password=root_pwd
# xxl-conf, admin login (管理中心登录账号密码)
xxl.conf.login.username=admin
xxl.conf.login.password=123456
项目:xxl-conf-example
作用:供用户参考学习如何接入XXL-CONF
com.xuxueli
xxl-conf-core
${xxl.conf.version}
可参考配置文件:
/xxl-conf/xxl-conf-example/src/main/resources/spring/applicationcontext-xxl-conf.xml
配置项说明
可参考配置文件:
/xxl-conf/xxl-conf-example/src/main/resources/xxl-conf.properties
配置项说明
# xxl-conf, zk address (配置中心zookeeper集群地址,如有多个地址用逗号分隔)
xxl.conf.zkserver=127.0.0.1:2181
该配置文件,除了支持配置ZK地址,还可以配置一些本地配置。 XXL-CONF 加载配置时会优先加载 "xxl-conf.properties" 中的配置, 然后才会加载ZK中的配置。可以将一些希望存放本地的配置存放在该文件。
每个配置分组对应一个唯一的GroupName,作为该分组下配置的统一前缀。在“分组管理”栏目可以创建并管理配置分组信息,系统已经提供一个默认分组.
登录"配置管理中心"
进入"配置管理界面",点击"新增配置"按钮
在弹出界面,填写配置信息
至此, 一条配置信息已经添加完成.
通过client端,可以实时获取配置信息, 通过本地已经加载过得配置将会接受Zookeeper的更新推送, 如下如日志:
项目: xxl-conf-example: (可以参考 com.xxl.conf.example.controller.IndexController.index() )
作用: 接入XXl-CONF的Demo项目
方式1: XML文件中的占位符方式
特点:
方式2: API方式
String paramByClient = XxlConfClient.get("default.key02", null);
特点:
系统配置信息以K/V的形式存在, "配置项" 属性如下:
每条配置,将会生成全局唯一标示GroupKey,在client端使用时,需要通过该值匹配对应的配置信息;
ZK之watcher普及(来源官方文档,以及网络博客)
1、可以注册watcher的方法:getData、exists、getChildren。
2、可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失。
3、一个Watcher实例是一个回调函数,被回调一次后就被移除了。如果还需要关注数据的变化,需要再次注册watcher。
4、New ZooKeeper时注册的watcher叫default watcher,它不是一次性的,只对client的连接状态变化作出反应。(推荐ZK初始化时, 主动Watcher如exists)
5、实现永久监听: 由于zookeeper是一次性监听,所以我们必须在wather的process方法里面再设置监听。
6、getChildren("/path")监视/path的子节点,如果(/path)自己删了,也会触发NodeDeleted事件。
《操作--事件》 | event For “/path” | event For “/path/child” |
---|---|---|
create(“/path”) | EventType.NodeCreated | 无 |
delete(“/path”) | EventType.NodeDeleted | 无 |
setData(“/path”) | EventType.NodeDataChanged | 无 |
create(“/path/child”) | EventType.NodeChildrenChanged(getChild) | EventType.NodeCreated |
delete(“/path/child”) | EventType.NodeChildrenChanged(getChild) | EventType.NodeDeleted |
setData(“/path/child”) | 无 | EventType.NodeDataChanged |
《事件--Watch方式》 | Default Watcher | exists(“/path”) | getData(“/path”) | getChildren(“/path”) --- | --- | --- | --- EventType.None | 触发 | 触发 | 触发 | 触发 EventType.NodeCreated | | 触发 | 触发 |
EventType.NodeDeleted | | 触发 | 触发 | EventType.NodeDataChanged | | 触发 | 触发 | EventType.NodeChildrenChanged | | | | 触发
ZooKeeper的一个性能测试
测试数据来自阿里中间件团队
ZK集群情况: 3台ZooKeeper服务器。8核64位jdk1.6;log和snapshot放在不同磁盘;
场景一: pub创建NODE,随后删除
场景二: pub创建NODE, sub订阅并获取数据
场景三: pub创建NODE,随后设置数据
总结: 由于一致性协议带来的额外网络交互,消息开销,以及本地log的IO开销,再加上ZK本身每1000条批量处理1次的优化策略,写入的平均响应时间总会在50-60ms之上。但是整体的TPS还是可观的。单个写入数据的体积越大,响应时间越长,TPS越低,这也是普遍规律了。压测过程中log文件对磁盘的消耗很大。实际运行中应该使用自动脚本定时删除历史log和snapshot文件。
"配置管理中心" 是 "配置中心" 的上层封装, 提供Web界面供用户对配置信息进行配置查询、配置新增、配置更新和配置删除等操作;
API方式加载配置: 客户端主要分为三层:
(API方式加载配置, 因为底层做了配置本地缓存, 因此可以放心应用在业务代码中, 不必担心并发压力。完整的支持配置实时推送更新)
Bean方式加载配置:
系统会在Spring容器中追加一个"PropertyPlaceholderConfigurer"属性解析器, 内部通过自定义的"StringValueResolver"解析器解析配置占位符 "${...}", 匹配到的配置信息将调用"XXL-CFONF"的API客户端加载最新配置信息进行Bean对象的属性赋值,最终完成实例化过程。
(Bean方式加载配置,仅仅在实例化时加载一次; 考虑都实例化后的对象通常为持久化对象, 如数据库连接池对象, 不建议配置的太灵活, 因此Bean类型配置更新需要重启机器)
XXL-CONF托管在Github上,如有问题可在 ISSUES 上提问,也可以加入上文技术交流群;
更多接入公司,欢迎在github 登记
案例四、
Ø 超级管理员角色不展示;
Ø 超级管理员可以看到所有非超级管理员角色,非超级管理员只可以看到当前角色用户创建的角色;
Ø 可以新增角色,也可以对角色进行编辑,只有在创建用户时勾选是否为系统管理员才可以进行角色管理。
【新建角色】:
Ø 新建角色输入角色名称,可以勾选的权限为当前用户所拥有的权限;
Ø 新建的角色作为该用户的下属角色,可分配给当前用户新建的用户;
Ø 父级权限为新建应用所增加的权限,以后每增加一个环境,就相应的增加该应用下的该环境权限,除超级管理员外的角色需对应勾选该权限才能看到该应用或者该权限,保存角色即可。
【编辑角色】:操作同新建角色,可以对该角色进行名称修改,权限修改。
管理用户首页,显示所有用户,可进行新建,编辑用户等操作。
【新建用户】:填写姓名,用户名,密码,选择角色(拥有对应角色权限、且可以选择的角色为当前登陆用户新建的角色),选择是否为系统管理员(系统管理员拥有新建用户、新建角色权限),保存即可。
【编辑用户】:操作同新建用户,保存即可修改。
案例三、
XXL-CONF 是一个分布式配置管理平台,其核心设计目标是“为分布式业务提供统一的配置管理服务”。现已开放源代码,开箱即用。
why not properties
常规项目开发过程中, 通常会将配置信息位于在项目resource目录下的properties文件文件中, 配置信息通常包括有: jdbc地址配置、redis地址配置、活动开关、阈值配置、黑白名单……等等。使用properties维护配置信息将会导致以下几个问题:
why XXL-CONF
git.oschina地址
最新Release版本: v1.3.0 最新Beta版本: v1.3.0
com.xuxueli
xxl-conf-core
1.3.0
请下载项目源码并解压,获取 "调度数据库初始化SQL脚本" 并执行即可。脚本位置如下:
xxl-conf/db/xxl-conf.sql
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下图所示:
项目:xxl-conf-admin
作用:管理线上配置信息
配置文件位置:
xxl-conf/xxl-conf-admin/src/main/resources/xxl-config-admin.properties
配置项目说明:
# xxl-conf, zk address (配置中心zookeeper集群地址,如有多个地址用逗号分隔)
xxl.conf.zkserver=127.0.0.1:2181
# xxl-conf, jdbc (配置中心mysql地址)
xxl.conf.jdbc.driverClass=com.mysql.jdbc.Driver
xxl.conf.jdbc.url=jdbc:mysql://localhost:3306/xxl-conf?Unicode=true&characterEncoding=UTF-8
xxl.conf.jdbc.username=root
xxl.conf.jdbc.password=root_pwd
# xxl-conf, admin login (管理中心登录账号密码)
xxl.conf.login.username=admin
xxl.conf.login.password=123456
项目:xxl-conf-example
作用:供用户参考学习如何接入XXL-CONF
com.xuxueli
xxl-conf-core
${xxl.conf.version}
可参考配置文件:
/xxl-conf/xxl-conf-example/src/main/resources/spring/applicationcontext-xxl-conf.xml
配置项说明
可参考配置文件:
/xxl-conf/xxl-conf-example/src/main/resources/xxl-conf.properties
配置项说明
# xxl-conf, zk address (配置中心zookeeper集群地址,如有多个地址用逗号分隔)
xxl.conf.zkserver=127.0.0.1:2181
该配置文件,除了支持配置ZK地址,还可以配置一些本地配置。 XXL-CONF 加载配置时会优先加载 "xxl-conf.properties" 中的配置, 然后才会加载ZK中的配置。可以将一些希望存放本地的配置存放在该文件。
每个配置分组对应一个唯一的GroupName,作为该分组下配置的统一前缀。在“分组管理”栏目可以创建并管理配置分组信息,系统已经提供一个默认分组.
登录"配置管理中心"
进入"配置管理界面",点击"新增配置"按钮
在弹出界面,填写配置信息
至此, 一条配置信息已经添加完成.
通过client端,可以实时获取配置信息, 通过本地已经加载过得配置将会接受Zookeeper的更新推送, 如下如日志:
项目: xxl-conf-example: (可以参考 com.xxl.conf.example.controller.IndexController.index() )
作用: 接入XXl-CONF的Demo项目
方式1: XML文件中的占位符方式
特点:
方式2: API方式
String paramByClient = XxlConfClient.get("default.key02", null);
特点:
系统配置信息以K/V的形式存在, "配置项" 属性如下:
每条配置,将会生成全局唯一标示GroupKey,在client端使用时,需要通过该值匹配对应的配置信息;
ZK之watcher普及(来源官方文档,以及网络博客)
1、可以注册watcher的方法:getData、exists、getChildren。
2、可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失。
3、一个Watcher实例是一个回调函数,被回调一次后就被移除了。如果还需要关注数据的变化,需要再次注册watcher。
4、New ZooKeeper时注册的watcher叫default watcher,它不是一次性的,只对client的连接状态变化作出反应。(推荐ZK初始化时, 主动Watcher如exists)
5、实现永久监听: 由于zookeeper是一次性监听,所以我们必须在wather的process方法里面再设置监听。
6、getChildren("/path")监视/path的子节点,如果(/path)自己删了,也会触发NodeDeleted事件。
《操作--事件》 | event For “/path” | event For “/path/child” |
---|---|---|
create(“/path”) | EventType.NodeCreated | 无 |
delete(“/path”) | EventType.NodeDeleted | 无 |
setData(“/path”) | EventType.NodeDataChanged | 无 |
create(“/path/child”) | EventType.NodeChildrenChanged(getChild) | EventType.NodeCreated |
delete(“/path/child”) | EventType.NodeChildrenChanged(getChild) | EventType.NodeDeleted |
setData(“/path/child”) | 无 | EventType.NodeDataChanged |
《事件--Watch方式》 | Default Watcher | exists(“/path”) | getData(“/path”) | getChildren(“/path”) --- | --- | --- | --- EventType.None | 触发 | 触发 | 触发 | 触发 EventType.NodeCreated | | 触发 | 触发 |
EventType.NodeDeleted | | 触发 | 触发 | EventType.NodeDataChanged | | 触发 | 触发 | EventType.NodeChildrenChanged | | | | 触发
ZooKeeper的一个性能测试
测试数据来自阿里中间件团队
ZK集群情况: 3台ZooKeeper服务器。8核64位jdk1.6;log和snapshot放在不同磁盘;
场景一: pub创建NODE,随后删除
场景二: pub创建NODE, sub订阅并获取数据
场景三: pub创建NODE,随后设置数据
总结: 由于一致性协议带来的额外网络交互,消息开销,以及本地log的IO开销,再加上ZK本身每1000条批量处理1次的优化策略,写入的平均响应时间总会在50-60ms之上。但是整体的TPS还是可观的。单个写入数据的体积越大,响应时间越长,TPS越低,这也是普遍规律了。压测过程中log文件对磁盘的消耗很大。实际运行中应该使用自动脚本定时删除历史log和snapshot文件。
"配置管理中心" 是 "配置中心" 的上层封装, 提供Web界面供用户对配置信息进行配置查询、配置新增、配置更新和配置删除等操作;
API方式加载配置: 客户端主要分为三层:
(API方式加载配置, 因为底层做了配置本地缓存, 因此可以放心应用在业务代码中, 不必担心并发压力。完整的支持配置实时推送更新)
Bean方式加载配置:
系统会在Spring容器中追加一个"PropertyPlaceholderConfigurer"属性解析器, 内部通过自定义的"StringValueResolver"解析器解析配置占位符 "${...}", 匹配到的配置信息将调用"XXL-CFONF"的API客户端加载最新配置信息进行Bean对象的属性赋值,最终完成实例化过程。
(Bean方式加载配置,仅仅在实例化时加载一次; 考虑都实例化后的对象通常为持久化对象, 如数据库连接池对象, 不建议配置的太灵活, 因此Bean类型配置更新需要重启机器)
XXL-CONF托管在Github上,如有问题可在 ISSUES 上提问,也可以加入上文技术交流群;
更多接入公司,欢迎在github 登记
案例五、
为了更好的解决分布式环境下多台服务实例的配置统一管理问题,本文提出了一套完整的分布式配置管理解决方案(简称为disconf[4],下同)。首先,实现了同构系统的配置发布统一化,提供了配置服务server,该服务可以对配置进行持久化管理并对外提供restful接口,在此基础上,基于zookeeper实现对配置更改的实时推送,并且,提供了稳定有效的容灾方案,以及用户体验良好的编程模型和WEB用户管理界面。其次,实现了异构系统的配置包管理,提出基于zookeeper的全局分布式一致性锁来实现主备统一部署、系统异常时的主备自主切换。通过在百度内部以及外部等多个产品线的实践结果表明,本解决方案是有效且稳定的。
在一个分布式环境中,同类型的服务往往会部署很多实例。这些实例使用了一些配置,为了更好地维护这些配置就产生了配置管理服务。通过这个服务可以轻松地管理成千上百个服务实例的配置问题。
王阿晶提出了基于zooKeeper的配置信息存储方案的设计与实现[1], 它将所有配置存储在zookeeper上,这会导致配置的管理不那么方便,而且他们没有相关的源码实现。淘宝的diamond[2]是淘宝内部使用的一个管理持久配置的系统,它具有完整的开源源码实现,它的特点是简单、可靠、易用,淘宝内部绝大多数系统的配置都采用diamond来进行统一管理。他将所有配置文件里的配置打散化进行存储,只支持KV结构,并且配置更新的推送是非实时的。百度内部的BJF配置中心服务[3]采用了类似淘宝diamond的实现,也是配置打散化、只支持KV和非实时推送。
同构系统是市场的主流,特别地,在业界大量使用部署虚拟化(如JPAAS系统,SAE,BAE)的情况下,同一个系统使用同一个部署包的情景会越来越多。但是,异构系统也有一定的存在意义,譬如,对于“拉模式”的多个下游实例,同一时间点只能只有一个下游实例在运行。在这种情景下,就存在多台实例机器有“主备机”模式的问题。目前国内并没有很明显的解决方案来统一解决此问题。
disconf是一套完整的基于zookeeper的分布式配置统一解决方案。
它的功能特点是
它的设计理念是:
disconf服务集群模式:
disconf的模块架构图:
每个模块的简单介绍如下:
运行流程详细介绍:
与2.0版本的主要区别是支持了:主备分配功能/主备切换事件。
disconf-web提供了前后端分离的web架构,具体可见:https://github.com/knightliao/disconf/tree/master/disconf-web
本部分会重点介绍disconf-client的实现方式。
本实现会涉及到 配置仓库容器模块、扫描模块、下载模块、watch模块,
使用AOP拦截的一个好处是可以比较轻松的实现配置控制,比如并发环境下的配置统一生效。关于这方面的讨论可以见这里。
特别地,本方式提供的编程模式非常简单,例如使用以下配置类的程序在使用它时,可以直接@Autowired进来进行调用,使用它时就和平常使用普通的JavaBean一样,但其实它已经分布式化了。配置更新时,配置类亦会自动更新。
@Service
@DisconfFile(filename = "redis.properties")
public class JedisConfig {
// 代表连接地址
private String host;
// 代表连接port
private int port;
/**
* 地址, 分布式文件配置
*
* @return
*/
@DisconfFileItem(name = "redis.host", associateField = "host")
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
/**
* 端口, 分布式文件配置
*
* @return
*/
@DisconfFileItem(name = "redis.port", associateField = "port")
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
本实现提供了无任何代码侵入方式的分布式配置。
ReloadablePropertiesFactoryBean继承了Spring Properties文件的PropertiesFactoryBean类,管理所有当配置更新时要进行reload的配置文件。对于被管理的每一个配置文件,都会通过 配置仓库容器模块、扫描模块、下载模块、watch模块 进行配置获取至配置仓库里。
ReloadingPropertyPlaceholderConfigurer继承了Spring Bean配置值控制类PropertyPlaceholderConfigurer。在第一次扫描spring bean里,disconf会记录配置文件的配置与哪些bean有关联。
ReloadConfigurationMonitor是一个定时任务,定时check本地配置文件是否有更新。
当配置中心的配置被更新时,配置文件会被下载至实例本地,ReloadConfigurationMonitor即会监控到此行为,并且通知 ReloadingPropertyPlaceholderConfigurer 对相关的bean类进行值更新。
特别的,此种方式无法解决并发情况下配置统一生效的问题。
在实现中,为每个配置提供主备选择的概念。用户实例在获取配置前需要先进行全局唯一性竞争才能得到配置值。在这里,我们采用基于zookeeper的全局唯一性锁来实现。
淘宝Diamond[2] | Disconf | 比较 | |
---|---|---|---|
数据持久性 | 存储在mysql上 | 存储在mysql上 | 都持久化到数据库里,都易于管理 |
推拉模型 | 拉模型,每隔15s拉一次全量数据 | 基于Zookeeper的推模型,实时推送 | disconf基于分布式的Zookeeper来实时推送,在稳定性、实效性、易用性上均优于diamond |
配置读写 | 支持实例对配置读写。支持某台实例写配置数据,并广播到其它实例上 | 只支持实例对配置读。通过在disconf-web上更新配置到达到广播写到所有应用实例 | 从目前的应用场景来看,实例对配置的写需求不是那么明显。disconf支持的中心化广播方案可能会与人性思考更加相似。 |
容灾 | 多级容灾模式,配置数据会dump在本地,避免中心服务挂机时无法使用 | 多级容灾模式,优先读取本地配置文件。 | 双方均支持在中心服务挂机时配置实例仍然可以使用 |
配置数据模型 | 只支持KV结构的数据,非配置文件模式 | 支持传统的配置文件模式(配置文件),亦支持KV结构数据(配置项) | 使用配置文件的编程方式可能与程序员的编程习惯更为相似,更易于接受和使用。 |
编程模型 | 需要将配置文件拆成多个配置项,没有明显的编程模型 | 在使用配置文件的基础上,提供了注解式和基于XML的两种编程模型 | 无 |
并发性 | 多条配置要同时生效时,无法解决并发同时生效的问题 |