配置管理(五行)
五行
1 项目概述
1.1 背景介绍及目标
1.1.1 背景
(1) 配置管理是 PAAS 服务的基础组件,是业务迭代的重要组成部分,目前存在如下问题:
无配置分发机制
无配置检查流程
无配置变更记录
(2) 巡检(监控)数据存储,目前巡检存储在 Redis 中,目前存在如下问题:
无数据存储历史(每天巡检时清空之前数据,然后存储当天数据)
查询数据时,数据列表索引存储在 set 类型中,目前是全量拉取数据,前端渲染耗时长
1.1.2 目标
解决配置 / 巡检(监控)等多种业务场景数据存储问题
解决配置的管理、存储、发布问题
针对配置的版本管理、存储、查询、修改进行设计
提供配置的文件级的依赖管理,提供配置文件变更的触发机制
1.2 名词说明
section: 存储数据模板
instance: 存储数据记录
item: 存储数据值
1.3 Roadmap
一期:(2021-01-30)
(1) 实现数据服务抽象
{
"namespace1": {
"instance_name1": {
"item_name1": {"item_type":"bool", "item_default": "true",
"item_value":True, "item_description": "switch", ...},
"item_name2": {..},
},
"instance_name2": {
"item_name1": {..},
"item_name2": {..},
},
}
}
(2) 实现数据存储 / 查询基本功能
(3) 可追溯
操作轨迹可追踪
历史配置可查询和回溯
二期:
section 与状态机绑定,item 变更触发事件
通过星桥触发配置更新及配置分发操作
2 需求分析
2.1 功能需求
(1) 多种业务场景(含有巡检(监控)类数据,配置类数据)
(2) 数据类型多
(3) 可以查询数据变更情况(巡检类数据)
(4) 可以通过配置进行反查对应服务
2.2 非功能需求
2.3 调研
2.3.1 业界方案
Facebook 配置中心
2.3.2 OSP 平台
配置创建
所谓配置其实就是 "应用程序的变量", 和应用程序息息相关
当我们发布了程序包时,也会有对应需要的配置文件生成,即配置是和发布包绑定的
确定了应用程序包对应配置的默认值
确定了应用程序包涉及哪些配置(不同的版本间可能会增减)
通过 name
, program
, version
联合主键来标记配置唯一性
config_item
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| name | varchar(128) | NO | PRI | NULL | |
| description | varchar(512) | YES | | NULL | |
| type | varchar(16) | NO | | NULL | |
| default | varchar(128) | YES | | NULL | |
| program | varchar(64) | NO | PRI | NULL | |
| version | varchar(32) | NO | PRI | NULL | |
+-------------+--------------+------+-----+---------+-------+
三个联合组件来标识不同的 version 的 program 的配置项 name
此表是存储的部署包的配置项
例子 (twemproxy)
+-------------------------+-----------------------------------------------------------------------------------------------------+------+---------+-----------+----------+
| name | description | type | default | program | version |
+-------------------------+-----------------------------------------------------------------------------------------------------+------+---------+-----------+----------+
| auto_eject_hosts | set if server should be ejected temporarily when it fails consecutively server_failure_limit times. | bool | true | twemproxy | 1.0.0.0 |
| client_connections | set the maximum number of client connections can be opened to each server pool | int | 0 | twemproxy | 1.0.0.0 |
| mbuf | set size of mbuf chunk in bytes (default: 16384 bytes) | int | 16384 | twemproxy | 1.0.0.0 |
| server_failure_interval | set time in usecs before we reset the server failures counter | int | 100000 | twemproxy | 1.0.0.0 |
| server_failure_limit | set the number of consecutive failures on a server that would lead to it being temporarily ejected | int | 3 | twemproxy | 1.0.0.0 |
| server_retry_timeout | set timeout value in msec to wait for before retrying on a temporarily ejected server | int | 2000 | twemproxy | 1.0.0.0 |
| timeout | set timeout in msec that we wait for to establish a connection for receive a response | int | 20 | twemproxy | 1.0.0.0 |
| verbosity | set logging level (default: 5, min: 0, max: 11) | int | 4 | twemproxy | 1.0.0.0 |
+-------------------------+-----------------------------------------------------------------------------------------------------+------+---------+-----------+----------+
配置存储
redis
HGETALL "config:{service}:normal"
1) "config_info"
2) "{\"access_rule\":\"\",\"adapter_timeout\":\"1000\",\"auto_eject_hosts\":\"true\",\"client_connections\":\"0\",\"conn_num\":\"500\",\"conn_retry\":\"1\",\"distribution\":\"random\",\"env\":\"online\",\"hash\":\"crc16\",\"log_level\":\"4\",\"max_response_size\":\"65535\",\"mbuf\":\"16384\",\"msg_max_length_limit\":\"6553600\",\"preconnect\":\"true\",\"redis\":\"true\",\"rediscluster\":\"true\",\"server_failure_limit\":\"3\",\"server_retry_timeout\":\"2000\",\"slowlog\":\"true\",\"slowlog_slower_than\":\"100\",\"thread_num\":\"4\",\"timeout\":\"1000\",\"user_name\":\"meetbill\",\"user_tk\":\"meetbill\",\"verbosity\":\"4\"}"
3) "service"
4) "{service}"
5) "mtime"
6) "1538967701"
7) "type"
8) "normal"
每个 service 的配置使用 hash 结构进行存储
即默认配置和部署包(镜像)关联,创建 service 时,将会和 service 关联,并将配置存储在 redis 中
类比 Python 语法的话,部署包就是“类”,service 是实例化后的对象,配置就是“类”的属性
直接获取配置(所有配置 value 都是字符串形式)
{
"access_rule": "",
"adapter_timeout": "1000",
"auto_eject_hosts": "true",
"client_connections": "0",
"conn_num": "500",
"conn_retry": "1",
"distribution": "random",
"env": "online",
"hash": "crc16",
"log_level": "4",
"max_response_size": "65535",
"mbuf": "16384",
"msg_max_length_limit": "6553600",
"preconnect": "true",
"redis": "true",
"rediscluster": "true",
"server_failure_limit": "3",
"server_retry_timeout": "2000",
"slowlog": "true",
"slowlog_slower_than": "100",
"thread_num": "4",
"timeout": "1000",
"user_name": "meetbill",
"user_tk": "meetbill",
"verbosity": "4"
}
配置生成
服务端 KV + 实例本地的程序
程序中含有初始默认配置,通过程序进行生成默认配置和修改配置
2.3.3 SCS 简单配置生成
template + (user config)
部署包(package) 关联 template(版本)
部署包版本与配置版本不是一一对应
配置修改
发起变更
{
"app_id": "scs-bj-xxxxxxxxx",
"config_param": [{
"app_id": "scs-bj-xxxxxxxx",
"type": "proxy",
"name": "scsproxy_auto_auth_address",
"value": "192.168.0.0/16"
}]
}
生成配置
{
"Product": "",
"action": "update_config_new",
"callback_url": "",
"params": {
"meta": {
"engine": "bdrpproxy",
"engine_version": "3",
"basedir": "/root",
"port": 7099,
"account_name": "default",
"password": "xxxxxxxxxxx" // 密文
},
"pkgs_to_install": [{
"download_url": "",
"md5": "",
"name": "proxy-slot",
"version": "",
"deploy_path": "/root",
"no_need_execute": true,
"rendered_confs": [{ // 由服务端进行渲染生成
"conf_path": "whitelist.ip",
"conf_content": "*.*.*.* rwx\n"
}, {
"conf_path": "user.acl",
"conf_content": "user default on \u003e<password> +@all ~*" // password
}, {
"conf_path": "nutcracker_.yml",
"conf_content": "<nutcracker_yaml>" // nutcracker_yaml 配置文件
}, {
"conf_path": "whitelist.cidr",
"conf_content": ""
}, {
"conf_path": "start_nutcracker.sh",
"conf_content": "/root/supervise.centos -p /root/status/nutcracker -f \"/root/agent/bin/nutcracker -c /root/agent/conf/nutcracker_.yml ...\""
}]
}],
"config_list": [{
"name": "auto_auth_address",
"value": "192.168.0.0/16"
}],
"agent_recovers": { // agent 依赖的配置文件, 文件中格式为 json
"enable_slow_log": { // 配置文件名为配置项 + ".txt", 如 enable_slow_log.txt
"enable_slow_log_flag": 0
},
"hotkey": {
"enable_hotkey": 0
},
"auth": {
"client_auth": "<password>", // password
"redis_auth": "",
"meta_auth": "",
"auth_sign": 0
},
"id": {
"uuid": "1.scs-bj-wawaqrszyvcy-bj_wbdpxvkdglus_itf_0-AZONE-bjdd.scs",
"user_id": "a11602e1c4c24e59865b9bb9209ff16d",
"cluster_id": 200009010,
"node_id": ""
},
"proxy_conf": {
"appid": 200009010,
"cluster_entry": "192.168.96.21:6379",
"conn_num": 16,
"disabled_command": [],
"engine": 1,
"instance_type": 0,
"ip_tag": 1,
"mbuf_size": 4096,
"mcpack_port": 8099,
"node_info": [{
"node": "172.17.35.69:6754:1",
"node_id": "bj_wbdpxvkdglus_0",
"server_name": "bj_wbdpxvkdglus_0",
"uuid": "0.scs-bj-wawaqrszyvcy-bj_wbdpxvkdglus_0-AZONE-bjdd.scs"
}],
"node_show_id": "scs-bj-wawaqrszyvcy_proxy_2",
"pool_name": "bj_wbdpxvkdglus",
"port": 7099,
"proxy_id": 200009010,
"proxy_instance_id": 58623,
"proxy_meta_master_ip": "<meta_ip>", // meta IP
"proxy_meta_master_port": 7500,
"proxy_version": 1,
"qps": 200000,
"sentinel_ip": "",
"sentinel_port": 0,
"stat_port": 9099,
"store_type": 0,
"white_ip_list": [{
"ip": "*.*.*.*",
"mode": "rwx"
}]
},
"instance_metadata": {
"port": 7099,
"type": "proxy",
"proc_name": "nutcracker",
"engine": 1,
"kernel_version": "6.0",
"resource_type": "container"
},
"bcm_conf": {
"cycle": 5,
"refactoring_v1": 1
},
"blb": {
"elb_ip": "172.17.35.97",
"elb_port": 6379
},
"monitor": {
"hash_name": "",
"hash_id": "",
"version": 5001,
"store_type": 0,
"noah_bns": "58623.cluster-200009010-proxysandbox.BCE.all",
"noah_endpoint": "<noah_endpoint>", // noah endpoint
"forbid_push": false,
"proxy_instance_id": 58623
}
},
"force_restart": false
},
"task_name": "scs",
"task_type": "async",
"timeout": 60
}
Config
服务配置文件(可能有多个配置文件)
Agent 配置文件(监控、日志采集等 agent)
Auth
ACL 文件
Whitelist 文件(IP、CIDR) 等
Auth 配置
配置项 value 限制
MySQL:user_conf_list-------------------------------表
id: 103
conf_name: repl-semi-sync-timeout
conf_module: 1
conf_desc: 5Y2K5ZCM5q2l6LaF5pe25pe26Ze077yM5Y2V5L2N5Li6IG1zPGJyLz4=
conf_type: 2
conf_range: 1-2147483647
conf_default: 10000
conf_cache_version: 10001
conf_redis_version: 6.0
conf_user_visible: 1
need_reboot: 0
engine:
lowest_full_version: 6.2.8
* 可以通过 conf_type + conf_range 限制 value 范围
CONF_TYPE
CONF_TYPE_SINGLE_SELECT = 1, 如 conf_range 为 true|false,conf_default 为 true,若 conf_range 为空,则不限制
CONF_TYPE_NUMBER = 2, 如 conf_range 为 0-100000,conf_default 为 2000
CONF_TYPE_MULTI_SELECT = 3, 如 conf_range 为 flushall|flushdb|keys|hgetall|scan,conf_default 为 flushall,flushdb
* conf_user_visible 设置了配置是否用户可见,1 不可见,0 可见
* need_reboot 提示配置是否需要重启
* lowest_full_version 设置了最低的版本
配置项 value 存储
读取 user_conf_list(默认配置) + conf_record_list(用户修改过的配置)
---
conf_history_list(配置修改历史)
MySQL:conf_record_list---------------------------表
id: 19104
cluster_id: 200011114
conf_name: always-propagate-del
conf_module: 1
value: yes
effected: 0
模板渲染
conf_tpl + conf_tpl_item
* conf_tpl: 标记模板状态
* conf_tpl_item: 模板内容
MySQL:conf_tpl_item------------------------------表
id: 743
tpl_id: sync-agent-1-default-20230908155723
deploy_path: sync_.conf
render_type:
created_at: 2023-09-08 07:56:32
updated_at: 2023-09-08 07:57:24
content: self_cluster={{.cluster_show_id}}
src_redis_ip={{.host}}
src_redis_port={{.port}}
模板使用
MySQL:package------------------------------------表
package_id --> tpl_id
3 总体设计
3.1 系统架构
+----------------+
| wuxing |
+---+---------+--+
| |
+---V---+ +--V--+
| MySQL | |Redis|
+-------+ +-----+
3.2 模块简介
五行整体分为三个模块
wuxing 为业务逻辑模块
MySQL 存储 wuxing 数据
Redis 存储 wuxing 缓存
3.3 设计与折衷
3.3.1 配置发布方式
(1) 基本的 KV + 模板生成配置
UI 编写模板(模板以.template 为后缀),值
触发上线时,通过本地配置派生工具生成配置
例如可以使用 butterfly 自带的模板功能渲染文件
(2) 基本的 KV + 本地的生成脚本进行生成配置
程序内编写模板
配置不会主动生成,通过 config-get 获取值 (json),然后通过 conf-parser 把模板 + 值一起生成配置,这样用户可以加入一些自定义元素
五行偏向于数据的管理,弱化配置管理的模板功能,故选择方案二
五行中可以新增 template 表,存储配置模板
模板与 section 关联,可以添加一个参数判断是否获取渲染文件
如果需要获取渲染文件,则根据 section 关联的 template 进行渲染生成
3.3.2 配置版本管理
(1) 版本号方式
添加版本号字段
配置表只追加不更新
(2) 操作历史方式
添加一个操作历史表
对配置的创建,更新,删除时,都对操作历史表进行新增一条记录
折衷分析:使用方案一时,遇到列表展示等需求时不太好实现,故使用"操作历史方式"
3.3.3 存储 key 的类型
zabbix 的 key 类型
show tables like 'history%';
+--------------------------+
| Tables_in_rtm (history%) |
+--------------------------+
| history | 存放信息类型为浮点数的监控项历史数据
| history_log | 存放信息类型为日志的监控项历史数据
| history_str | 存放信息类型为字符的监控项历史数据
| history_text | 存放信息类型为文本的监控项历史数据
| history_uint | 存放信息类型为数字(无正负)的监控项历史数据
+--------------------------+
类似的配置 / 属性中也可以存储下浮点数,字符,数字三种类型
wuxing_float
wuxing_uint
wuxing_str
这样可以直接使用数据用于统计使用,变更历史可以为:
wuxing_history_float
wuxing_history_int
wuxing_history_str
wuxing_history_bool
折衷考虑,item 存储在一张表中方便管理,故 item value 数据会存储在 wuxing_instance_item 中,item 的历史记录会存储在 wuxing_history_{value type}
中
3.3.4 item 与状态机关联
(1) section 模板中指定状态机
比如:
section_template:
{
"item_name1": {
...
"fsm": "xxxxx",
}
}
然后实例化 instance 时,会转为
instance_template:
{
"item_name1": {
...
"fsm": "xxxxx",
}
}
优势:section item 与 fsm 强绑定,约束性好
(2) instance_update_item 时,指定状态机
instance_update_item(req, namespace, instance_name, item_name, item_value):
改为
instance_update_item(req, namespace, instance_name, item_name, item_value, item_fsm_name=None):
当传指定 item_fsm_name 时,根据指定的状态机获取到 item 对象,然后变更 item_name
优势:可灵活指定 fsm
劣势:item 与 fsm 关联性差,需要业务配置其关联关系
(3) instance_update_item 时,指定是否使用状态机
instance_update_item(req, namespace, instance_name, item_name, item_value):
改为
instance_update_item(req, namespace, instance_name, item_name, item_value, use_item_fsm=False):
当 use_item_fsm 为 True 时,根据一定命名规则找对应 fsm,然后根据指定的状态机获取到 item 对象,然后变更 item_name
比如可以通过 namespace_sectionname_item_name 进行命名在状态机 map 中进行查找
优势:item 与 fsm 松耦合
根据约定优于配置,配置优于实现原则,(3) > (2) > (1) 故选择方案 (3)
实现方式还在调研中
3.3.5 item 与触发器关联
状态机模式关联 item 后,可以限制 item_value 在有限 value 间进行流转
如果存储的是巡检类数据的话,则数据不是固定的,需要在特定条件下进行触发事件,这个业界可参考的方案很多,比如 Zabbix/Nagios/Open-Falcon/Prometheus
(1) zabbix 方式
当前语法
{<server>:<key>.<function>(<parameter>)}<operator><constant>
eg: {host:key.func(params)}=0
后续语法 (5.4 版本以后)
func(/host/key, params)
func(/host1/key1, /host2/key2)
3.3.6 value 限制
1(单选)
yes|no|partial
yes
2(数字)
1-500
20
3(多选)
flushall|flushdb|keys|hgetall|scan
flushall,flushdb
4(正则)
通过正则进行比较
规则1 若type==1单选
用|切分conf-range形成list1
判断目标值是否在list1中
规则2 若type==2 数字
判断目标参数值是否为数字
用-切分conf-range,与目标值比大小(左闭右闭)
规则3 若type==3 多选
用|切分conf-range形成list1
用,切分目标值形成list2
判断list2中元素都在list1中
3.3.7 配置分发机制
TODO: service 对应多个配置文件
3.4 潜在风险
4 详细设计
4.1 交互流程
4.1.1 section
4.1.1.1 增
创建 section(section_create)
开始
|
V
输入 (namespace, section_name, section_version)
|
V 是
检查 section 是否存在 --------> ERR_SECTION_IS_EXIST
| 否
V 否
创建 section 记录是否成功 ----> ERR_SECTION_CREATE_FAILED
| 是
V
OK
4.1.1.2 删
删除 section(section_delete)
开始
|
V
输入 (namespace, section_name, section_version)
|
V 否
检查 section 是否存在 --------> ERR_SECTION_IS_NOT_EXIST
| 是
V 否
删除 section 记录是否成功 ----> ERR_SECTION_DELETE_FAILED
| 是
V
OK
4.1.1.3 改
section 模板中添加 item(section_item_add)
开始
|
V
输入 (namespace, section_name, section_version, item_name, item_default, item_description)
|
V 否
要添加的 item 前缀是否在 ["i|", "s|", "f|", "b|"] 中 -------------> ERR_SECTION_ITEM_TYPE_FAILED
| 是
V 否
section 是否存在 ------------------------------------> ERR_SECTION_IS_NOT_EXIST
| 是
V 否
section 是否为未启用状态 --------------------------------> ERR_SECTION_IS_ENABLED
| 是
V
获取 section 模板
|
V 是
要添加的 item 是否已在模板中 -----------------------------> ERR_SECTION_ITEM_IS_EXIST
| 否
V
添加 item 到 section 模板中
|
V
计算 section 模板 md5 值
|
V 否
是否成功更新数据到存储中 -------------------------------> ERR_SECTION_ITEM_ADD_FAILED
| 是
V
OK
section 模板中删除 item(section_item_delete)
开始
|
V
输入 (namespace, section_name, section_version, item_name)
|
V 否
section 是否存在 ------------------------------------> ERR_SECTION_IS_NOT_EXIST
| 是
V 否
section 是否为未启用状态 --------------------------------> ERR_SECTION_IS_ENABLED
| 是
V
获取 section 模板
|
V 否
要添加的 item 是否在模板中 -------------------------------> ERR_SECTION_ITEM_IS_NOT_EXIST
| 是
V
从 section 模板中移除 item
|
V
计算 section 模板 md5 值
|
V 否
是否成功更新数据到存储中 -------------------------------> ERR_SECTION_ITEM_DELETE_FAILED
| 是
V
OK
启用 section(section_enable)
开始
|
V
输入 (namespace, section_name, section_version)
|
V 否
检查 section 是否存在 --------> ERR_SECTION_IS_NOT_EXIST
| 是
V 否
启用 section 是否成功 --------> ERR_SECTION_ENABLE_FAILED
| 是
V
OK
4.1.1.4 查
section 列表 (section_list)
开始
|
V
设置需要获取的字段 (namespace, section_name, section_version, section_md5, is_enabled, u_time)
|
V
添加搜索条件
|
V
进行获取 section 分页列表及总数
|
V
OK
section 详情 (section_get)
开始
|
V
输入 (namespace, section_name, section_version)
|
V 否
检查 section 是否存在 --------> ERR_SECTION_IS_NOT_EXIST
| 是
V
转换 section 数据为可读方式
|
V
OK
4.1.2 instance
4.1.2.1 增
开始
|
V
输入:namespace, instance_name, section_name, section_version, items_data=None
|
V 是
检查是否有此 instance-----------------> ERR_INSTANCE_IS_EXIST
|
V
获取 section 模板
|
V 否
section 是否存在 ---------------------> ERR_SECTION_IS_NOT_EXIST
| 是
V 否
section 是否已启用 -------------------> ERR_SECTION_IS_NOT_ENABLED
| 是
V Exception
增加 item 记录 -----------------------> ERR_ITEM_XX
|
V
增加 instance 记录
|
V
结束
4.1.2.2 删
开始
|
V
输入:namespace, instance_name
|
V 否
检查 instance 是否存在 -------------> ERR_INSTANCE_IS_NOT_EXIST
| 是
V Exception
删除 instance 关联的所有 item -----------> ERR_INSTANCE_IS_NOT_EXIST
|(此时不从 instance_template 中获取 item_id 进行逐个删除,
| 而是直接删除 instance_item 表中所有 instance 相关的 item)
V Exception
删除 instance ---------------------> ERR_INSTANCE_DELETE_FAILED
|
V
结束
4.1.2.3 改
更新 item 值
开始
|
V
输入:namespace, instance_name, item_name, item_value
|
V 否
检查是否有此 instance-----------------------> ERR_INSTANCE_IS_NOT_EXIST
| 是
V
获取 instance 模板
|
V 否
instance_template 中是否存在此 item_name----------> ERR_ITEM_IS_NOT_EXIST
| 是
V Exception
更新 item 记录 ------------------------------> ERR_ITEM_XX
|
V
结束
更新 section 版本
开始
|
V
输入:namespace, instance_name, section_version
|
V 否
检查是否有此 instance-----------------> ERR_INSTANCE_IS_NOT_EXIST
|
V
获取 instance 当前模板
|
V 否
新 section 是否存在 -------------------> ERR_SECTION_IS_NOT_EXIST
| 是
V 否
新 section 是否已启用 -----------------> ERR_SECTION_IS_NOT_ENABLED
| 是
V
新 section_template 与当前 instance_template 的 item_name 做 diff
|
V Exception
对当前 instance 增加 item 记录 ------------> ERR_ITEM_XX
|
V
对当前 instance 删除 item 记录 ------------> ERR_ITEM_XX
|(删除比 section_template 多的 item)
V
更新 instance 记录 (md5, 模板等)
|
V
结束
4.1.2.4 查
列表
详情
4.1.3 item
4.1.3.1 增
备注
在 instance_item 中新增 item 仅由 instance 接口触发创建,无法直接创建 item
4.1.3.2 删
备注
在 instance_item 中新增 item 仅由 instance 接口触发删除,无法直接删除 item
4.1.3.3 改
4.1.3.4 查
4.2 数据库设计
4.2.1 表之间关系
+namespace1-------------------------------------------------------+
| +section1-----------------+ |
| |+section_template-------+| |
| ||+-----+ +-----+ +-----+|| |
| |||item1| |item2| |item3||| version:1.0.1 |
| ||+-----+ +-----+ +-----+|| |
| |+-----------------------+| |
| +------+---------+--------+ |
| | | |
| | +---------+ |
| | | |
| | | |
| +instance1-------------V--+ +instance2----V-----------+ |
| |+instance_template------+| |+instance_template------+| |
| ||+id:1-+ +id:2-+ +id:3-+|| ||+id:4-+ +id:5-+ +id:6-+|| |
| |||item1| |item2| |item3||| |||item1| |item2| |item3||| |
| ||+----++ +---+-+ +---+-+|| ||+--+--+ +---+-+ +---+-+|| |
| |+-----|------|-------|--+| |+---|--------|-------|--+| |
| +------|------|-------|---+ +----|--------|-------|---+ |
| | | | | | | |
| +item--|------|-------|-----------|--------|-------+---+ |
| | +id:1V--+ | | | | | | |
| | +-------+ | | | | | | |
| | +id:2V--+ | | | | | |
| | +-------+ | | | | | |
| | +id:3-V-+ | | | | |
| | +-------+ | | | | |
| | +id:4-V-+ | | | |
| | +-------+ | | | |
| | +id:5-V-+ | | |
| | +-------+ | | |
| | +id:6-V-+ | |
| | +-------+ | |
| +------------------------------------------------------+ |
+-----------------------------------------------------------------+
(1) wuxing_section 表存储 section 记录
(2) wuxing_instance 表存储 instance 记录
『创建』 创建 instance 时会涉及如下行为:
1> 关联 section 及对应版本,继承 section 模板
2> 根据 item value 数据类型,在 item 表中以模板默认值创建记录
(3) wuxing_instance_item 表存储 instance 的 item 具体 value 值
+history----------------------------------------------------------+
|+------------------------+ +-------------------------+ |
||wuxing_history_bool | |wuxing_history_float | |
|+------------------------+ +-------------------------+ |
|+------------------------+ +-------------------------+ | 此五个表记录 instance item 的 value 变更历史
||wuxing_history_int | |wuxing_history_string | |
|+------------------------+ +-------------------------+ |
|+------------------------+ |
||wuxing_history_text | |
|+------------------------+ |
+-----------------------------------------------------------------+
备注:
(1) namespace 表是命名空间,同一个 namespace 下 (section_name, section_version) 实例以及 instance 实例 是唯一的
(2) instance 只会关联一个 (section_name, section_version)
(3) item 的类型是固定的
(4) section-instance-instance_item 关系
section 类似 Python 中的类
instance 类似 Python 中类实例化后的对象,会继承 section 这个类的属性
instance_item 类似 Python 中类实例化后对象的属性,会根据 item 的数据类型存储在不同的 value 字段中
4.2.2 表详情
4.2.2.1 section
表名:wuxing_section
+------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------+---------------+------+-----+---------+-------+
| namespace | varchar(16) | NO | PRI | NULL | |
| section_name | varchar(64) | NO | PRI | | |
| section_version | varchar(16) | NO | PRI | NULL | |
| section_template | varchar(4096) | NO | | NULL | |
| section_md5 | varchar(64) | NO | MUL | NULL | |
| is_enabled | tinyint(1) | NO | MUL | NULL | |
| user | varchar(32) | NO | MUL | NULL | |
| u_time | datetime | NO | | NULL | |
| c_time | datetime | NO | | NULL | |
+------------------+---------------+------+-----+---------+-------+
9 rows in set (0.00 sec)
4.2.2.2 instance
表名:wuxing_instance
+-------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------+------+-----+---------+-------+
| namespace | varchar(16) | NO | PRI | NULL | |
| instance_name | varchar(64) | NO | PRI | NULL | |
| instance_template | varchar(4096) | NO | | NULL | |
| section_name | varchar(64) | NO | MUL | NULL | |
| section_version | varchar(64) | NO | MUL | NULL | |
| section_md5 | varchar(64) | NO | MUL | NULL | |
| is_valid | tinyint(1) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
| u_time | datetime | NO | MUL | NULL | |
+-------------------+---------------+------+-----+---------+-------+
9 rows in set (0.00 sec)
4.2.2.3 item
表名:wuxing_instance_item
desc wuxing_instance_item;
+-------------------+----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+----------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| namespace | varchar(16) | NO | MUL | NULL | |
| section_name | varchar(64) | NO | MUL | NULL | |
| instance_name | varchar(64) | NO | MUL | NULL | |
| item_name | varchar(64) | NO | MUL | NULL | |
| item_type | varchar(64) | NO | MUL | NULL | |
| item_value_bool | tinyint(1) | YES | MUL | NULL | |
| item_value_float | double | YES | MUL | NULL | |
| item_value_int | int(11) | YES | MUL | NULL | |
| item_value_string | varchar(128) | YES | MUL | NULL | |
| item_value_text | varchar(20000) | YES | | NULL | |
| user | varchar(32) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
| u_time | datetime | NO | MUL | NULL | |
+-------------------+----------------+------+-----+---------+----------------+
14 rows in set (0.00 sec)
4.2.2.4 item history
表名:wuxing_history_bool
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | MUL | NULL | |
| item_value | tinyint(1) | NO | | NULL | |
| cmd | varchar(8) | NO | | NULL | |
| user | varchar(32) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
+------------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
表名:wuxing_history_float
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | MUL | NULL | |
| item_value | double | NO | | NULL | |
| cmd | varchar(8) | NO | | NULL | |
| user | varchar(32) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
+------------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
表名:wuxing_history_int
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | MUL | NULL | |
| item_value | int(11) | NO | | NULL | |
| cmd | varchar(8) | NO | | NULL | |
| user | varchar(32) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
+------------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
表名:wuxing_history_string
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | MUL | NULL | |
| item_value | varchar(128) | NO | | NULL | |
| cmd | varchar(8) | NO | | NULL | |
| user | varchar(32) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
+------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
表名:wuxing_history_text
+------------+----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | MUL | NULL | |
| item_value | varchar(20000) | NO | | NULL | |
| cmd | varchar(8) | NO | | NULL | |
| user | varchar(32) | NO | | NULL | |
| c_time | datetime | NO | MUL | NULL | |
+------------+----------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
4.3 接口
5 使用实践
5.1 命名方式
namespace(最多 16 个字符): 会表明是哪个模块在使用,此数据是监控类数据还是配置类数据,可以通过 "app_分类_功能" 进行划分,比如:
m(监控 / 巡检类数据),c(配置类数据),s(统计类数据)
例子
bq_m_conf:(表示扁鹊配置检查类数据)
bq_m_state:(表示扁鹊运行状态类数据)
bq_m_entry:(表示扁鹊入口检查类数据)
qn_c_global:(表示青囊全局配置)
qn_c_cluster:(表示青囊集群配置)
xh_s_resource:(表示星河资源统计)
section(最多 64 个字符): 可以通过 "level_module" 进行划分
例子
pool_redis(全局)
group_redis(redis 集群)
service_proxy(proxy 组件)
service_redis(redis 组件)
unit_proxy(redis 实例)
section level
level: 池 / 集群 / 组件 / 实例
+pool---------------------------+
|+group---------------+ |
||+service------+ | |
|||+unit--+ | | |
|||| | ... | ... | ... |
|||+------+ | | |
||+-------------+ | |
|+--------------------+ |
+-------------------------------+
5.2 已有表如何进行转换为五行方式
表 + 单个主键
原方式:
(table_name + primary_key field name) + field1_name + field2_name
新方式:
(namespace + section_name) + section_template(item1 + item2 + item3)
| |
V
(namespace + instance_name) + instance_template(item1 + item2 + item3)
5.3 section 与 instance 列表展示
instance 列表
+-----------+--------------+-------------+---------------+-----------+------+-------------------+
|namespace |instance_name |section_name |section_version|section_md5|item1 | u_time |
+-----------+--------------+-------------+---------------+-----------+------+-------------------+
|namespace1 |instance_name1|section_name1|1.0.1 |xxxxxxxx |xxxx |2021-01-16 10:02:02|
|namespace1 |instance_name2|section_name1|1.0.1 |xxxxxxxx |xxxx |2021-01-16 10:02:02|
|namespace1 |instance_name3|section_name1|1.0.1 |xxxxxxxx |xxxx |2021-01-16 10:02:02|
|namespace1 |instance_name4|section_name1|1.0.1 |xxxxxxxx |xxxx |2021-01-16 10:02:02|
|namespace1 |instance_name5|section_name1|1.0.1 |xxxxxxxx |xxxx |2021-01-16 10:02:02|
|namespace1 |instance_name6|section_name1|1.0.1 |xxxxxxxx |xxxx |2021-01-16 10:02:02|
+-----------+--------------+-------------+---------------+-----------+------+-------------------+
资源池可用资源 (namespace + instance_name 要确定唯一记录)
# 方案一 (X)
+-----------+--------------+-------------+---------------+-----------+----------------+-------------------+
| namespace |instance_name |section_name |section_version|section_md5| available_count| u_time |
+-----------+--------------+-------------+---------------+-----------+----------------+-------------------+
|xh_s_1pool | bj | idc | 1.0.1 |xxxxxxxx | 20 |2021-01-16 10:02:02|
|xh_s_2pool | nj | idc | 1.0.1 |xxxxxxxx | 250 |2021-01-16 10:02:02|
+-----------+--------------+-------------+---------------+-----------+----------------+-------------------+
# 方案二
+-------------+-------------+------------+---------------+-----------+-----+-----+-----+-------------------+
| namespace |instance_name|section_name|section_version|section_md5|pool1|pool2|poolX| u_time |
+-------------+-------------+------------+---------------+-----------+-----+-----+-----+-------------------+
|xh_s_resource| bj | idc | 1.0.1 |xxxxxxxx | 20 | 50 | X |2021-01-16 10:02:02|
|xh_s_resource| nj | idc | 1.0.1 |xxxxxxxx | 250 | 15 | X |2021-01-16 10:02:02|
+-------------+-------------+------------+---------------+-----------+-----+-----+-----+-------------------+
5.4 表限制
namespace: (string) 16 个字节限制
section_name: (string) 64 个字节限制
section_version: (string): 16 个字节
instance_name: (string) 64 个字节
section_template: (string) 4k 字节
instance_template: (string) 4k 字节
单个 item 基本是 100 个字节左右
也就是单个 instance 可以有 40 个 item
PS: instance 当做一条记录的话,也就是可以有 40 个字段用于存储数据
5.5 cache
# section
规则:wuxing_section@{namespace}@{section_name}@{section_version}
eg.: cache:wuxing_section@group_qingnang@group_appid@1.0.1
备注:cache: 前缀为框架自动添加
# item
规则:wuxing_item@{item_id}
eg.: cache:wuxing_item@3
备注:cache: 前缀为框架自动添加
6 部署
https://github.com/meetbill/butterfly/wiki/wuxing_ha
7 接入五行例子
7.1 【星河】统计各机房资源池信息
namespace:section_name:section_version(xh_s_resource:idc:1.0.1)
instance 是机房 idc 名称
item 是机房的各个资源池剩余资源 (item_name 为资源池标识)
7.2 【青囊】青囊集群级别以及全局级别开关
7.2.1 集群级别开关
namespace:section_name:section_version(qn_c_cluster:group_redis:1.0.1)
instance_name 是各集群唯一标识 id(instance_name 会以字符串方式进行存储)
item 是集群的各个开关及自愈相关的配置信息
7.3 【扁鹊】扁鹊 group/service/unit 级别巡检数据
bianque// 待完善
8 五行 echarts
8.1 访问五行 DB
8.1.1 查询 item 数据
item 行数据需要转换为列数据
SELECT instance_name,
MAX(CASE item_name WHEN '{s|item_name1}' THEN item_value_string ELSE 0 END ) item_name1,
MAX(CASE item_name WHEN '{i|item_name2}' THEN item_value_string ELSE 0 END ) item_name2
FROM wuxing_instance_item
WHERE namespace='{namespace}' and section_name = '{section_name}'
GROUP BY instance_name
LIMIT 20;
例子
SELECT instance_name,
MAX(CASE item_name WHEN 's|app_name' THEN item_value_string ELSE 0 END ) app_name,
MAX(CASE item_name WHEN 'i|unit_appid' THEN item_value_int ELSE 0 END ) appid,
MAX(CASE item_name WHEN 's|unit_matrixid' THEN item_value_string ELSE 0 END ) container_id,
MAX(CASE item_name WHEN 's|unit_version' THEN item_value_string ELSE 0 END ) version
FROM wuxing_instance_item
WHERE namespace='bq_m_state' and section_name = 'unit_proxy'
GROUP BY instance_name
LIMIT 20;
8.1.2 行转列参考
举个学生成绩信息的例子
8.1.2.1 建表及插入数据
-- 创建表
drop table if EXISTS t_score;
create table t_score(
id int auto_increment primary key,
u_name varchar(32),
c_name varchar(32),
score double
);
-- 插入数据
insert into t_score(u_name, c_name, score) values
('张三', 'JavaSE', 80),
('张三', 'JDBC', 90),
('张三', 'Servlet', 85),
('李四', 'JavaSE', 70),
('李四', 'JDBC', 80),
('李四', 'Servlet', 80),
('王五', 'JavaSE', 90),
('王五', 'JDBC', 90),
('王五', 'Servlet', 60);
8.1.2.2 行转列
使用多表连接的方式
-- 1. 使用多表连接查询的方式
select
t1.u_name, t2.score as 'JavaSE', t3.score as 'JDBC', t4.score as 'Servlet'
from t_score t1
join (select u_name, score from t_score where c_name = 'JavaSE')t2 on t2.u_name = t1.u_name
join (select u_name, score from t_score where c_name = 'JDBC')t3 on t3.u_name = t3.u_name
join (select u_name, score from t_score where c_name = 'Servlet')t4 on t4.u_name = t4.u_name
group by t1.u_name;
使用 MySQL 中 case when then else end 结构
-- 2. 使用 case when 条件 then 字段值 else 值 end
select u_name
,sum(case when c_name = 'JavaSE' then score else 0 end ) as 'JavaSE'
,sum(case when c_name = 'JDBC' then score else 0 end ) as 'JDBC'
,sum(case when c_name = 'Servlet' then score else 0 end ) as 'Servlet'
from t_score
group by u_name;
8.1.3 peewee join 例子
You can also order across joins. Assuming you want to order tweets by the username of the author, then by created_date:
query = (Tweet
.select()
.join(User)
.order_by(User.username, Tweet.created_date.desc()))
SQL
SELECT t1."id", t1."user_id", t1."message", t1."is_published", t1."created_date"
FROM "tweet" AS t1
INNER JOIN "user" AS t2
ON t1."user_id" = t2."id"
ORDER BY t2."username", t1."created_date" DESC
join on
recursive = (RTerm
.select(RTerm.id, RTerm.name, RTerm.parent, rlevel, rpath)
.join(base_case, on=(RTerm.parent == base_case.c.id)))
# The recursive CTE is created by taking the base case and UNION ALL with
# the recursive term.
cte = base_case.union_all(recursive)
8.2 图表
8.2.1 需求
+--------- item1 value
|
|
instance -------+--------- item2=value2 (条件)
|
|
+--------- item3=value2 (条件)
统计某个 item2=value2, item3=value3 的条件下,item1 的各个 value 的统计数
而图表的下钻可以通过增加 item=value 来进行条件约束
8.2.2 SQL
(1) 直接获取某个 item_name 的 value 分布
SELECT `t1`.`item_value_string` AS `item_value`, COUNT(*) AS `count`
FROM `wuxing_instance_item` AS `t1`
WHERE ((`t1`.`namespace` = 'bq_m_state'
AND `t1`.`section_name` = 'unit_redis')
AND `t1`.`item_name` = 's|unit_idc')
GROUP BY `t1`.`item_value_string`
(2) condition1
SELECT `t1`.`item_value_string` AS `item_value`, COUNT(*) AS `count`
FROM `wuxing_instance_item` AS `t1`
INNER JOIN (SELECT `condition1`.`instance_name`
FROM `wuxing_instance_item` AS `condition1`
WHERE (((`condition1`.`namespace` = 'bq_m_state'
AND `condition1`.`section_name` = 'unit_redis')
AND `condition1`.`item_name` = 's|app_name')
AND `condition1`.`item_value_string` = 'xxx_app_name'))
AS `condition1` ON (`t1`.`instance_name` = `condition1`.`instance_name`)
WHERE ((`t1`.`namespace` = 'bq_m_state'
AND `t1`.`section_name` = 'unit_redis')
AND `t1`.`item_name` = 's|unit_idc')
GROUP BY `t1`.`item_value_string`
(3) condition1 + condition2
SELECT `t1`.`item_value_string` AS `item_value`, COUNT(*) AS `count`
FROM `wuxing_instance_item` AS `t1`
INNER JOIN (SELECT `condition1`.`instance_name`
FROM `wuxing_instance_item` AS `condition1`
WHERE (((`condition1`.`namespace` = 'bq_m_state'
AND `condition1`.`section_name` = 'unit_redis')
AND `condition1`.`item_name` = 's|app_name')
AND `condition1`.`item_value_string` = 'xxx_app_name'))
AS `condition1` ON (`t1`.`instance_name` = `condition1`.`instance_name`)
INNER JOIN (SELECT `condition2`.`instance_name`
FROM `wuxing_instance_item` AS `condition2`
WHERE (((`condition2`.`namespace` = 'bq_m_state'
AND `condition2`.`section_name` = 'unit_redis')
AND `condition2`.`item_name` = 's|unit_ip')
AND `condition2`.`item_value_string` = 'xxx_ip'))
AS `condition2` ON (`t1`.`instance_name` = `condition2`.`instance_name`)
WHERE ((`t1`.`namespace` = 'bq_m_state'
AND `t1`.`section_name` = 'unit_redis')
AND `t1`.`item_name` = 's|unit_idc')
GROUP BY `t1`.`item_value_string`
8.3 例子
某个 Redis 机房有断电风险,需要看下此机房可能影响的集群,如果有此机房的主库则需要重点关注下
region
|
V
idc
/ \
| |
role app
| (条件:idc)
app
(条件:idc+role)
9 传送门
Facebook 配置中心
OvineData
9.1 其他
Last updated