Permalink
Browse files

first commit

  • Loading branch information...
1 parent f73c033 commit dc525114ea6a726bb85a8cba05de630a36812528 zhouxiao committed Dec 6, 2016
@@ -0,0 +1 @@
+metadata
@@ -0,0 +1,86 @@
+# 一种直观记录表结构变更历史的方法
+
+## 1. Story
+在没有形成自己的数据库管理平台以前,数据库实例一多(包括生产和测试环境),许多表要执行DDL会变得异常繁杂。
+
+说个自己的经历,需要改现网的一个索引来看优化的效果,因为存在风险,不会一次全改,先只改1个库,然后逐步放开。前后验证效果可能花上一两周的时间,除非实现完整的记录了当时的ddl语句和对应的库,否则根本难以记得。这就完全依赖于个人的习惯及能力。
+
+又比如现网出了个问题,开发追查到一个时间点,想确认那个时候有没有对库表进行过更改操作,如果没有记录表结构变更的历史,也就难以提供需要的信息。
+<!-- more -->
+
+记录差异,很早就思考过能不能用git来做。终于花了一天时间来实现,并验证、修改达到预期的效果,还算满意。
+
+## 2. Concept
+思路很简单,就是利用 `mydumper` 导出表时会把各表(结构)单独导成一个文件的特性,每天低峰期导出所有对象元数据:表、视图、存储过程、事件、触发器。需要过滤掉 `AUTO_INCREMENT` 值。
+
+结构内容存放在一个git仓库下,通过shell脚本提交到 gitlab。所有DDL更改由原来依赖于DBA的主动记录,变成被动采集。
+
+测试环境和生产环境表结构总会有些差异,为了兼顾同时收集两个环境的数据,设置了 `environment` 选项,根据当前所在运行的机器,自动判断采集哪些实例信息。
+
+## 3. Usage
+首先你需要能够存放表结构信息的git仓库,如gitlab,而且建议设置为私有。
+
+1. 安装 git 和 mydumper
+mydumper 0.9.1 版本需要编译安装,可以参考这里 [file-mydumper-install-ubuntu14-04-sh](https://gist.github.com/nicksantamaria/66726bca586d152a3a01#file-mydumper-install-ubuntu14-04-sh)。当然 yum 或 apt-get 安装其他版本也是一样的。
+脚本会尝试自动获取 `mydumper` 命令的路径。
+
+注意配置git权限的时候,最好不允许其它用户手动提交修改仓库内容。
+
+2. 配置db实例地址
+
+`settings.ini`示例:
+```
+[environment]
+production=puppetmaster
+test=puppettestmaster
+
+[production]
+production_auth=your_defaultuser:yourpassword
+
+db_name1=192.168.1.100:3306
+db_name2=192.168.1.101:3306
+db_name3=name3.dbhost.com:3306
+db_name4=192.168.1.100:3306:myuser:mypassword
+
+[test]
+test_auth=user1:password1
+
+db_name1=10.0.100.1:3306
+db_name2=10.0.100.1:3307
+db_name3=10.0.100.2:3306
+
+db_name4=10.0.100.3:3306:myuser1:mypassword1
+```
+
+- 上面的配置采集 `production``test`两个环境的表结构,识别两个环境是根据 hostname 来决定的。这样做的好吃就是这个脚本在两个环境下运行不需要做任何修改。
+- `[production]`节的名字就是 `[environment]`节指定的名字 *production=xx*
+- `dbname1=`就是配置各个db,地址+端口的形式。用户名和密码可以继续用 `:` 跟上
+- `production_auth=`表示 production 环境下,如 `dbname1`没有配置用户名时,默认采用这个用户名和密码。这样设计主要是简化配置。
+ 该数据库用户需要 select,show view,event,trigger,procedure 权限。
+
+`settings_parser.py` 用于解析上面的配置文件,输出`collect_tableMeta.sh`易处理的格式。
+
+3. 每天运行
+可使用 `python settings_parser.py` 测试解析配置是否正常。
+
+在配置文件里两个环境下(一般网络不互通)分别加上定时任务:
+```
+# Puppet Name: collect_DBschema
+5 5 * * * /opt/DBschema/collect_tableMeta.sh >> /tmp/collect_DBschema.log 2>&1
+
+```
+
+4. 展示效果
+![mysql_schema_info](http://7q5fot.com1.z0.glb.clouddn.com/mysql-schema-structure1.png)
+
+`A` 是新增,`M` 是修改,`D` 是删除,一目了然。点开可以前后对比。
+
+## 4. More
+思路和实现都不难,主要是意识,和如何快速找到解决当前需求的办法。一切都是为了效率 :)
+
+目前所能想到更多的:
+1. 有内容push到git仓库后,使用 web hook 发出邮件。
+2. 根据A,B两个表的结构,快速得到A修改成B的样子的DDL。
+3. event 权限问题。event权限没有所谓的读和修改之分,阿里云RDS就把它从 *只读* 账号里拿除了,导致收集不到事件定义。所以它的高权限账号管理模式还是很有作用的。
+4. 密码明文。
+最近公司邀请了一个安全公司给做培训,数据库安全里面,密码明文配置在文件里面是广泛存在的,难搞。
@@ -0,0 +1,41 @@
+# !/bin/bash
+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+export PATH
+
+mydumper=`which mydumper`
+if [ "$mydumper" = "" ]; then
+ echo "Error: can not find mydumper"
+ exit $?
+fi
+
+cd `dirname $0`
+outdir='./DBschema'
+curdate=$(date +%Y%m%d-%H%M%S)
+db_env=$(python settings_parser.py env)
+git pull origin master
+
+python settings_parser.py | while read line
+do
+ # event not
+ MYDUMPER_CMD="$mydumper -k -v 1 -G -R -d "
+ db_instance="${line}"
+ eval $(echo ${db_instance} | awk '{print "db_id="$1; print "db_host="$2; print "db_port="$3; print "db_user="$4; print "db_pass="$5;}')
+ #echo $db_id $db_host $db_port $db_user $db_pass
+ if [ "$db_id" != "" ]; then
+ outdir="${db_env}/${db_id}"
+ if [ ! -d "$outdir" ]; then
+ mkdir -p $outdir
+ fi
+ MYDUMPER_CMD="$MYDUMPER_CMD -h $db_host -P $db_port -u $db_user -p $db_pass --outputdir $outdir"
+ echo $MYDUMPER_CMD
+ rm -f $outdir/*
+ $MYDUMPER_CMD
+ sed -i 's/AUTO_INCREMENT=[0-9]* //g' $outdir/*
+ fi
+done
+
+
+git add -A
+# git commit -m "($db_env) DB schema snapshot: $curdate | big change"
+ git commit -m "($db_env) DB schema snapshot: $curdate | $(git status -s)"
+git push origin master
@@ -0,0 +1,20 @@
+[environment]
+production=puppetmaster
+test=puppettestmaster
+
+[production]
+production_auth=your_defaultuser:yourpassword
+
+db_name1=192.168.1.100:3306
+db_name2=192.168.1.101:3306
+db_name3=name3.dbhost.com:3306
+db_name4=192.168.1.100:3306:myuser:mypassword
+
+[test]
+test_auth=user1:password1
+
+db_name1=10.0.100.1:3306
+db_name2=10.0.100.1:3307
+db_name3=10.0.100.2:3306
+
+db_name4=10.0.100.3:3306:myuser1:mypassword1
@@ -0,0 +1,71 @@
+#coding:utf8
+from ConfigParser import ConfigParser
+from socket import gethostname
+import sys
+
+"""
+Author: seanlook7@gmail.com
+Date: 2016-11-25 written
+Description: 读取settings.ini配置,获取当前环境、组合DB列表
+"""
+SETTINGS_INI = "settings.ini"
+
+# 形式格式化后的db列表,交给shell处理
+def get_setttings(sect='', opt=''):
+ cf = ConfigParser()
+ cf.read(SETTINGS_INI)
+
+ env = get_env()
+
+ default_u_p = cf.get(env, env + "_auth").split(":")
+
+ db_instances = []
+
+ dblist = cf.items(env)
+ for dbid, dbinfo_str in dblist:
+ if dbid.startswith("db_"):
+ db_one = [dbid]
+ dbinfo = dbinfo_str.split(":")
+ try:
+ if 0 in (len(dbinfo[2]), len(dbinfo[3])):
+ dbinfo[2] = default_u_p[0]
+ dbinfo[3] = default_u_p[1]
+ except IndexError:
+ dbinfo.insert(2, default_u_p[0])
+ dbinfo.insert(3, default_u_p[1])
+
+ db_one.extend(dbinfo)
+ db_instances.append(db_one)
+
+ dblist_output = ""
+ for db in db_instances:
+ dblist_output += " ".join(db) + "\n"
+
+ print dblist_output
+
+# 根据当前主机名,获取对应的环境
+def get_env():
+ cf = ConfigParser()
+ cf.read(SETTINGS_INI)
+ envs = cf.items("environment")
+ this_host = gethostname()
+
+ env = ""
+ for e in envs:
+ if e[1] == this_host:
+ env = e[0]
+ break
+ if env == "":
+ print "Error can not find the enviroment for '%s'" % this_host
+ sys.exit(-1)
+ else:
+ return env
+
+if __name__ == '__main__':
+ if len(sys.argv) == 2:
+ if sys.argv[1] == 'env':
+ print get_env()
+ else:
+ print 'Error wrong usage'
+ else:
+ get_setttings()
@@ -0,0 +1,109 @@
+# 监控MySQL你还应该收集表信息
+
+## 1. Story
+也许你经常会被问到,库里某个表最近一年的内每个月的数据量增长情况。当然如果你有按月分表比较好办,挨个 `show table status`,如果只有一个大表,那估计要在大家都休息的时候,寂寞的夜里去跑sql统计了,因为你只能获取当前的表信息,历史信息追查不到了。
+
+除此以外,作为DBA本身也要对数据库空间增长情况进行预估,用以规划容量。我们说的表信息主要包括:
+
+1. 表数据大小(DATA_LENGTH)
+2. 索引大小(INDEX_LENGTH)
+3. 行数(ROWS)
+4. 当前自增值(AUTO_INCREMENT,如果有)
+
+目前是没有看到哪个mysql监控工具上提供这样的指标。这些信息不需要采集的太频繁,而且结果也只是个预估值,不一定准确,所以这是站在一个全局、长远的角度去监控(采集)表的。
+
+本文要介绍的自己写的采集工具,是基于组内现有的一套监控体系:
+- `InfluxDB`:时间序列数据库,存储监控数据
+- `Grafana`:数据展示面板
+- `Telegraf`:收集信息的agent
+ 看了下 telegraf 的最新的 [mysql 插件](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mysql),一开始很欣慰:支持收集 Table schema statistics 和 Info schema auto increment columns。试用了一下,有数据,但是如前面所说,除了自增值外其他都是预估值,telegraf收集频率过高没啥意义,也许一天2次就足够了,它提供的 `IntervalSlow`选项固定写死在代码里,只能是放缓 global status 监控频率。不过倒是可以与其它监控指标分开成两份配置文件,各自定义收集间隔来实现。
+ 最后打算自己用python撸一个,上报到influxdb里 :)
+
+## 2. Concept
+完整代码见 GitHub项目地址:[DBschema_gather](https://github.com/seanlook/DBschema_gather)
+实现也特别简单,就是查询 `information_schema` 库的 `COLUMNS``TABLES` 两个表:
+
+```
+SELECT
+ IFNULL(@@hostname, @@server_id) SERVER_NAME,
+ %s as HOST,
+ t.TABLE_SCHEMA,
+ t.TABLE_NAME,
+ t.TABLE_ROWS,
+ t.DATA_LENGTH,
+ t.INDEX_LENGTH,
+ t.AUTO_INCREMENT,
+ c.COLUMN_NAME,
+ c.DATA_TYPE,
+ LOCATE('unsigned', c.COLUMN_TYPE) COL_UNSIGNED
+ # CONCAT(c.DATA_TYPE, IF(LOCATE('unsigned', c.COLUMN_TYPE)=0, '', '_unsigned'))
+FROM
+ information_schema.`TABLES` t
+LEFT JOIN information_schema.`COLUMNS` c ON t.TABLE_SCHEMA = c.TABLE_SCHEMA
+AND t.TABLE_NAME = c.TABLE_NAME
+AND c.EXTRA = 'auto_increment'
+WHERE
+ t.TABLE_SCHEMA NOT IN (
+ 'mysql',
+ 'information_schema',
+ 'performance_schema',
+ 'sys'
+ )
+AND t.TABLE_TYPE = 'BASE TABLE'
+```
+
+关于 `auto_increment`,我们除了关注当前增长到哪了,还会在意相比 `int / bigint` 的最大值,还有多少可用空间。于是计算了 `autoIncrUsage` 这一列,用于保存当前已使用的比例。
+
+然后使用 InfluxDB 的python客户端,批量存入influxdb。如果没有InfluxDB,结果会打印出json —— 这是Zabbix、Open-Falcon这些监控工具普遍支持的格式。
+
+最后就是使用 Grafana 从 influxdb 数据源画图。
+
+## 3. Usage
+1. 环境
+在 python 2.7 环境下编写的,2.6,3.x没测。
+运行需要`MySQLdb``influxdb`两个库:
+```
+$ sudo pip install mysql-python influxdb
+```
+
+2. 配置
+`settings_dbs.py` 配置文件
+ - `DBLIST_INFO`:列表存放需要采集的哪些MySQL实例表信息,元组内分别是连接地址、端口、用户名、密码
+ 用户需要select表的权限,否则看不到对应的信息.
+ - `InfluxDB_INFO`:influxdb的连接信息,注意提前创建好数据库名 `mysql_info`
+ 设置为 `None` 可输出结果为json.
+
+3. 创建influxdb上的数据库和存储策略
+存放2年,1个复制集:(按需调整)
+```
+CREATE DATABASE "mysql_info"
+CREATE RETENTION POLICY "mysql_info_schema" ON "mysql_info" DURATION 730d REPLICATION 1 DEFAULT
+```
+看大的信息类似于:
+![schema-influxdb-data][1]
+
+4. 放crontab跑
+可以单独放在用于监控的服务器上,不过建议在生产环境可以运行在mysql实例所在主机上,安全起见。
+一般库在晚上会有数据迁移的动作,可以在迁移前后分别运行 `mysql_schema_info.py` 来收集一次。不建议太频繁。
+```
+40 23,5,12,18 * * * /opt/DBschema_info/mysql_schema_info.py >> /tmp/collect_DBschema_info.log 2>&1
+```
+
+5. 生成图表
+
+导入项目下的 `grafana_table_stats.json` 到 Grafana面板中。效果如下:
+![表数据大小和行数][2]
+*表数据大小和行数*
+
+![每天行数变化增量,auto_increment使用率][3]
+*每天行数变化增量,auto_increment使用率*
+
+## 4. More
+1. 分库分表情况下,全局唯一ID在表里无法计算 autoIncrUsage
+2. 实现上其实很简单,更主要的是唤醒收集这些信息的意识
+3. 可以增加 Graphite 输出格式
+
+
+ [1]: http://7q5fot.com1.z0.glb.clouddn.com/mysql-schema-statistics.png
+ [2]: http://7q5fot.com1.z0.glb.clouddn.com/mysql-schema-statistics2.png
+ [3]: http://7q5fot.com1.z0.glb.clouddn.com/mysql-schema-statistics3.png
Oops, something went wrong.

0 comments on commit dc52511

Please sign in to comment.