配置管理(五行)

五行

1 项目概述

1.1 背景介绍及目标

1.1.1 背景

(1) 配置管理是 PAAS 服务的基础组件,是业务迭代的重要组成部分,目前存在如下问题:

  • 无配置分发机制

  • 无配置检查流程

  • 无配置变更记录

(2) 巡检(监控)数据存储,目前巡检存储在 Redis 中,目前存在如下问题:

  • 无数据存储历史(每天巡检时清空之前数据,然后存储当天数据)

  • 查询数据时,数据列表索引存储在 set 类型中,目前是全量拉取数据,前端渲染耗时长

1.1.2 目标

  • 解决配置 / 巡检(监控)等多种业务场景数据存储问题

  • 解决配置的管理、存储、发布问题

  • 针对配置的版本管理存储查询修改进行设计

  • 提供配置的文件级的依赖管理,提供配置文件变更的触发机制

Drawing

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 业界方案

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 限制

conf_type
conf_range
conf_default

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

peewee 文档

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 传送门

9.1 其他

Last updated