分类 数据库 下的文章

更改索引方式
InfluxDB 默认的索引是在内存上维护的,不断地数据增长会使用越来越多的内存,并有一个参数 index-version 可以设置索引方式。如果是直接在主机上部署的可以修改配置文件,Docker 部署则需要通过环境变量设置:

environment:

INFLUXDB_DATA_INDEX_VERSION: tsi1

将索引数据存储在磁盘上,可以通过日志或者查看分片的目录结构验证修改是否生效,若索引方式为 tsi1,在分片目录下会有一个名为 index 的目录,inmem 索引则没有。

具体位置是 /var/lib/influxdb/data/<数据库>/<保留策略>/<分片>。

[root@izuf6czs1dw6siz3zokdiez 1_day]# ls
1 10 11 12 13 14 15 16 18 19 20 21 3 4 5 6 7 8 9
[root@izuf6czs1dw6siz3zokdiez 1_day]# cd 15
[root@izuf6czs1dw6siz3zokdiez 15]# ls
000000001-000000001.tsm fields.idx index

禁用状态监控
默认情况下 InfluxDB 会维护一个 _internal 库,来监控系统状态,但是会产生不必要的数据,官方不推荐在生产环境下使用 _internal 库。

InfluxData does not recommend using the _internal database in a production cluster. It creates unnecessary overhead, particularly for busy clusters, that can overload an already loaded cluster. Metrics stored in the _internal database primarily measure workload performance and should only be tested in non-production environments.

添加环境变量:
environment:

INFLUXDB_DATA_INDEX_VERSION: tsi1
INFLUXDB_MONITOR_STORE_ENABLED: "false"

  1. 基本概念

InfluxDB 和传统数据库(如:MySQL)的一些区别

InfluxDB 传统数据库中的概念
database 数据库
measurement 数据库中的表
points 表里面的一行数据
特有概念
tag–标签,在 InfluxDB 中,tag 是一个非常重要的部分,表名+tag 一起作为数据库的索引,是“key-value”的形式

field–数据,field 主要是用来存放数据的部分,也是“key-value”的形式

timestamp–时间戳,作为时序型数据库,时间戳是 InfluxDB 中最重要的部分,在插入数据时可以自己指定也可留空让系统指定

说明:在插入新数据时,tag、field 和 timestamp 之间用空格分隔

series–序列,所有在数据库中的数据,都需要通过图表来展示,而这个 series 表示这个表里面的数据,可以在图表上画成几条线。具体可以通过 SHOW SERIES FROM "表名" 进行查询

Retention policy–数据保留策略,可以定义数据保留的时长,每个数据库可以有多个数据保留策略,但只能有一个默认策略

Point–点,表示每个表里某个时刻的某个条件下的一个 field 的数据,因为体现在图表上就是一个点,于是将其称为 point。Point 由时间戳(time)、数据(field)、标签(tags)组成

Point 属性 传统数据库中的概念
time 每个数据记录时间,是数据库中的主索引 (会自动生成)
fields 表中的列(没有索引的属性)也就是记录的值:温度, 湿度
tags 表中的索引:地区,海拔

  1. 端口服务

8083:Web admin 管理服务的端口, http://localhost:8083
8086:HTTP API 的端口
8088:集群端口 (目前还不是很清楚, 配置在全局的 bind-address,默认不配置就是开启的)

  1. 数据库操作

创建
CREATE DATABASE {NAME};
删除
DROP DATABASE {NAME};
使用
DROP {DB};

数据表和数据操作
建库的操作可以发现非常类似于 MySQL 下的操作。而在 InfluxDB 下没有细分的表的概念,InfluxDB 下的表在插入数据库的时候自动会创建。可以通过 show measurements 命令查看所有的表,这个类似于 MySQL 下的show tables

显示所有表

show measurements

新建表(写数据)
标准格式,注意在写数据的时候如果不添加时间戳,系统会默认添加一个时间。InfluxDB 中没有显式的新建表的语句,只能通过 insert 数据的方式来建立新表。

语法格式
insert <measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [unix-nano-timestamp]
示例

INSERT cpu,host=serverA,region=us_west value=0.64

INSERT temperature,machine=unit42,type=assembly external=25,internal=37 1434067467000000000

删除表

drop measurement disk_free

读数据
查询语句与 SQL 一样,不用过多的学习

查询数据

SELECT "host", "region", "value" FROM "cpu"

每个表输出一行(支持 Go 语言的正则表达式、支持类似于 MySQL 中的 limit 语句)

SELECT FROM /./ LIMIT 1

修改和删除数据
InfluxDB 属于时序数据库,没有提供修改和删除数据的方法。

但是删除可以通过 InfluxDB 的数据保存策略(Retention Policies)来实现

update 更新语句没有,不过有 alter 命令,在 influxdb 中,删除操作用和更新基本不用到 。在针对数据保存策略方面,有一个特殊的删除方式,这个后面再提。

series 操作
series 表示这个表里面的数据,可以在图表上画成几条线,series 主要通过 tags 排列组合算出来。

我们可以查询表的 series,如下所示:

show series from mem

  1. 用户操作

显示用户

SHOW USERS

创建用户

CREATE USER "username" WITH PASSWORD 'password'

创建管理员权限的用户

CREATE USER "username" WITH PASSWORD 'password' WITH ALL PRIVILEGES

删除用户

DROP USER "username"
influxdb 的权限设置比较简单,只有读、写、ALL 几种。

  1. 数据保存策略(Retention Policies)

InfluxDB 每秒可以处理成千上万条数据,要将这些数据全部保存下来会占用大量的存储空间,有时我们可能并不需要将所有历史数据进行存储。InfluxDB 没有提供直接删除 Points 的方法,但是它提供了 Retention Policies,用来让我们自定义数据的保留时间。

  1. 查看
    SHOW RETENTION POLICIES ON "testDB"
  2. 创建
    CREATE RETENTION POLICY "rp_name" ON "db_name" DURATION 30d REPLICATION 1 DEFAULT

其中:

rp_name:策略名
db_name:具体的数据库名
30d:保存 30 天,30 天之前的数据将被删除
它具有各种时间参数,比如:h(小时),w(星期)
REPLICATION 1:副本个数,这里填 1 就可以了
DEFAULT 设为默认的策略

  1. 修改
    ALTER RETENTION POLICY "rp_name" ON db_name" DURATION 3w DEFAULT
  2. 删除
    DROP RETENTION POLICY "rp_name" ON "db_name"

常用函数
InfluxDB 提供了很多的有用的函数,这里列举了常用的三个维度函数,Use InfluxQL functions to aggregate, select, and transform data.

COUNT() 函数
返回一个(field)字段中的非空值的数量。
语法:
SELECT COUNT(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT COUNT(water_level) FROM h2o_feet

name: h2o_feet

time count
1970-01-01T00:00:00Z 15258
说明 water_level 这个字段在 h2o_feet 表中共有 15258 条数据。
注意:InfluxDB 中的函数如果没有指定时间的话,会默认以 epoch 0 (1970-01-01T00:00:00Z) 作为时间。
可以在 where 中加入时间条件,如下:

SELECT COUNT(water_level) FROM h2o_feet WHERE time >= '2015-08-18T00:00:00Z' AND time < '2015-09-18T17:00:00Z' GROUP BY time(4d)

DISTINCT() 函数
返回一个字段(field)的唯一值。
语法:
SELECT DISTINCT(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
使用示例

SELECT DISTINCT("level description") FROM h2o_feet

这个例子显示 level description 这个字段共有四个值,然后将其显示了出来,时间为默认时间。

MEAN() 函数
返回一个字段(field)中的值的算术平均值(平均值)。字段类型必须是长整型或 float64。
语法格式:
SELECT MEAN(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
使用示例

SELECT MEAN(water_level) FROM h2o_feet

name: h2o_feet

time mean
1970-01-01T00:00:00Z 4.286791371454075
说明 water_level 字段的平均值为4.286791371454075,时间为默认时间,当然,你也可以加入 where 条件。

MEDIAN() 函数
从单个字段(field)中的排序值返回中间值(中位数)。字段值的类型必须是长整型或 float64 格式。
语法:
SELECT MEDIAN(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
使用示例

SELECT MEDIAN(water_level) from h2o_feet

name: h2o_feet

time median
1970-01-01T00:00:00Z 4.124
说明表中 water_level 字段的中位数是 4.124

SPREAD() 函数
返回字段的最小值和最大值之间的差值。数据的类型必须是长整型或 float64。
语法:
SELECT SPREAD(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
使用示例

SELECT SPREAD(water_level) FROM h2o_feet

name: h2o_feet

time spread
1970-01-01T00:00:00Z 10.574

SUM() 函数
返回一个字段中的所有值的和。字段的类型必须是长整型或 float64。
语法:
SELECT SUM(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
使用示例:

SELECT SUM(water_level) FROM h2o_feet

name: h2o_feet

time sum
1970-01-01T00:00:00Z 67777.66900000002
此语句计算出了 h2o_feet 表中 所有 water_level 字段的和。

TOP() 函数
作用:返回一个字段中最大的 N 个值,字段类型必须是长整型或 float64 类型。
语法:
SELECT TOP( <field_key>[,<tag_key(s)>],<N> )[,<tag_key(s)>|<field_key(s)>] [INTO_clause] FROM_clause [WHERE_clause] [GROUP_BY_clause] [ORDER_BY_clause] [LIMIT_clause] [OFFSET_clause] [SLIMIT_clause] [SOFFSET_clause]
使用示例

SELECT TOP("water_level",3) FROM "h2o_feet"

name: h2o_feet
time top
---- ---
2015-08-29T07:18:00Z 9.957
2015-08-29T07:24:00Z 9.964
2015-08-29T07:30:00Z 9.954
这个例子返回表中 water_level 字段中最大的三个值。

BOTTOM() 函数
作用:返回一个字段中最小的 N 个值。字段类型必须是长整型或 float64 类型。
语法:
SELECT BOTTOM(<field_key>[,<tag_keys>],<N>)[,<tag_keys>] FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
使用示例

SELECT BOTTOM(water_level,3) FROM h2o_feet

name: h2o_feet

time bottom
2015-08-29T14:30:00Z -0.61
2015-08-29T14:36:00Z -0.591
2015-08-30T15:18:00Z -0.594
这个例子返回表中 water_level 字段中最小的三个值。
也可将关联 tag 放在一起查询,但如果 tag 值少于 N 的值,则返回的值的个数只会取 tag 中字段值少的那个。
如下所示:

SELECT BOTTOM(water_level,location,3) FROM h2o_feet

name: h2o_feet

time bottom location
2015-08-29T10:36:00Z -0.243 santa_monica
2015-08-29T14:30:00Z -0.61 coyote_creek
语句取最小的三个值,然而结果只返回了 2 个值,因为 location 这个 tag 只有 两个取值。

FIRST() 函数
作用:返回一个字段中最老的取值。
语法:
SELECT FIRST(<field_key>)[,<tag_key(s)>] FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT FIRST(water_level) FROM h2o_feet WHERE location = 'santa_monica'

name: h2o_feet

time first
2015-08-18T00:00:00Z 2.064
这个语句返回了 在 location 为 santa_monica 条件下,最旧的那个 water_level 字段的取值和时间。

LAST() 函数
作用:返回一个字段中最新的取值。
语法:
SELECT LAST(<field_key>)[,<tag_key(s)>] FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT LAST(water_level),location FROM h2o_feet WHERE time >= '2015-08-18T00:42:00Z' and time <= '2015-08-18T00:54:00Z'

name: h2o_feet

time last location
2015-08-18T00:54:00Z 6.982 coyote_creek

MAX() 函数
作用:返回一个字段中的最大值。该字段类型必须是长整型,float64,或布尔类型。
语法:
SELECT MAX(<field_key>)[,<tag_key(s)>] FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT MAX(water_level),location FROM h2o_feet

name: h2o_feet

time max location
2015-08-29T07:24:00Z 9.964 coyote_creek

MIN() 函数
作用:返回一个字段中的最小值。该字段类型必须是长整型,float64,或布尔类型。
语法:
SELECT MIN(<field_key>)[,<tag_key(s)>] FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT MIN(water_level),location FROM h2o_feet

name: h2o_feet

time min location
2015-08-29T14:30:00Z -0.61 coyote_creek

PERCENTILE() 函数
作用:返回排序值排位为 N 的百分值。字段的类型必须是长整型或 float64。
百分值是介于 100 到 0 之间的整数或浮点数,包括 100。
语法:
SELECT PERCENTILE(<field_key>, <N>)[,<tag_key(s)>] FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT PERCENTILE(water_level,5),location FROM h2o_feet

name: h2o_feet

time percentile location
2015-08-28T12:06:00Z 1.122 santa_monica
就是将 water_level 字段按照不同的 location 求百分比,然后取第五位数据。

DERIVATIVE() 函数
作用:返回一个字段在一个 series 中的变化率。
InfluxDB 会计算按照时间进行排序的字段值之间的差异,并将这些结果转化为单位变化率。其中,单位可以指定,默认为 1s。
语法:
SELECT DERIVATIVE(<field_key>, [<unit>]) FROM <measurement_name> [WHERE <stuff>]
其中,unit取值可以为以下几种:
u --microseconds
s --seconds
m --minutes
h --hours
d --days
w --weeks
DERIVATIVE() 函数还可以在 GROUP BY time() 的条件下与聚合函数嵌套使用,格式如下:
SELECT DERIVATIVE(AGGREGATION_FUNCTION(<field_key>),[<unit>]) FROM <measurement_name> WHERE <stuff> GROUP BY time(<aggregation_interval>)
示例:
假设 location = santa_monica 条件下数据有以下几条:

name: h2o_feet

time water_level
2015-08-18T00:00:00Z 2.064
2015-08-18T00:06:00Z 2.116
2015-08-18T00:12:00Z 2.028
2015-08-18T00:18:00Z 2.126
2015-08-18T00:24:00Z 2.041
2015-08-18T00:30:00Z 2.051
计算每一秒的变化率:

SELECT DERIVATIVE(water_level) FROM h2o_feet WHERE location = 'santa_monica' LIMIT 5

name: h2o_feet

time derivative
2015-08-18T00:06:00Z 0.00014444444444444457
2015-08-18T00:12:00Z -0.00024444444444444465
2015-08-18T00:18:00Z 0.0002722222222222218
2015-08-18T00:24:00Z -0.000236111111111111
2015-08-18T00:30:00Z 2.777777777777842e-05
计算每六分钟的变化率

SELECT DERIVATIVE(water_level,6m) FROM h2o_feet WHERE location = 'santa_monica' LIMIT 5

name: h2o_feet

time derivative
2015-08-18T00:06:00Z 0.052000000000000046
2015-08-18T00:12:00Z -0.08800000000000008
2015-08-18T00:18:00Z 0.09799999999999986
2015-08-18T00:24:00Z -0.08499999999999996
2015-08-18T00:30:00Z 0.010000000000000231
第一行数据的计算过程如下:(2.116 - 2.064) / (6m / 6m)
计算每 12 分钟的变化率:

SELECT DERIVATIVE(water_level,12m) FROM h2o_feet WHERE location = 'santa_monica' LIMIT 5

name: h2o_feet

time derivative
2015-08-18T00:06:00Z 0.10400000000000009
2015-08-18T00:12:00Z -0.17600000000000016
2015-08-18T00:18:00Z 0.19599999999999973
2015-08-18T00:24:00Z -0.16999999999999993
2015-08-18T00:30:00Z 0.020000000000000462
第一行数据计算过程为:(2.116 - 2.064 / (6m / 12m)
计算每 12 分钟最大值的变化率

SELECT DERIVATIVE(MAX(water_level)) FROM h2o_feet WHERE location = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' AND time < '2015-08-18T00:36:00Z' GROUP BY time(12m)

name: h2o_feet

time derivative
2015-08-18T00:12:00Z 0.009999999999999787
2015-08-18T00:24:00Z -0.07499999999999973

DIFFERENCE() 函数
作用:返回一个字段中连续的时间值之间的差异。字段类型必须是长整型或 float64。
最基本的语法:
SELECT DIFFERENCE(<field_key>) FROM <measurement_name> [WHERE <stuff>]
与 GROUP BY time() 以及其他嵌套函数一起使用的语法格式:
SELECT DIFFERENCE(<function>(<field_key>)) FROM <measurement_name> WHERE <stuff> GROUP BY time(<time_interval>)
其中,函数可以包含以下几个:
COUNT(), MEAN(), MEDIAN(), SUM(), FIRST(), LAST(), MIN(), MAX(), 和 PERCENTILE()
使用示例
例子中使用的源数据如下所示:

SELECT water_level FROM h2o_feet WHERE location='santa_monica' AND time >= '2015-08-18T00:00:00Z' and time <= '2015-08-18T00:36:00Z'

name: h2o_feet

time water_level
2015-08-18T00:00:00Z 2.064
2015-08-18T00:06:00Z 2.116
2015-08-18T00:12:00Z 2.028
2015-08-18T00:18:00Z 2.126
2015-08-18T00:24:00Z 2.041
2015-08-18T00:30:00Z 2.051
2015-08-18T00:36:00Z 2.067
计算water_level间的差异:

SELECT DIFFERENCE(water_level) FROM h2o_feet WHERE location='santa_monica' AND time >= '2015-08-18T00:00:00Z' and time <= '2015-08-18T00:36:00Z'

name: h2o_feet

time difference
2015-08-18T00:06:00Z 0.052000000000000046
2015-08-18T00:12:00Z -0.08800000000000008
2015-08-18T00:18:00Z 0.09799999999999986
2015-08-18T00:24:00Z -0.08499999999999996
2015-08-18T00:30:00Z 0.010000000000000231
2015-08-18T00:36:00Z 0.016000000000000014
数据类型都为 float 类型。

ELAPSED() 函数
作用:返回一个字段在连续的时间间隔间的差异,间隔单位可选,默认为 1 纳秒。
单位可选项如下:
Units Meaning
ns nanoseconds (1 billionth of a second)
u or µ microseconds (1 millionth of a second)
ms milliseconds (1 thousandth of a second)
s second
m minute
h hour
d day
w week
语法:
SELECT ELAPSED(<field_key>, <unit>) FROM <measurement_name> [WHERE <stuff>]
示例:
计算 h2o_feet 字段在纳秒间隔下的差异。

SELECT ELAPSED(water_level) FROM h2o_feet WHERE location = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' and time <= '2015-08-18T00:24:00Z'

name: h2o_feet

time elapsed
2015-08-18T00:06:00Z 360000000000
2015-08-18T00:12:00Z 360000000000
2015-08-18T00:18:00Z 360000000000
2015-08-18T00:24:00Z 360000000000
在一分钟间隔下的差异率:

SELECT ELAPSED(water_level,1m) FROM h2o_feet WHERE location = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' and time <= '2015-08-18T00:24:00Z'

name: h2o_feet

time elapsed
2015-08-18T00:06:00Z 6
2015-08-18T00:12:00Z 6
2015-08-18T00:18:00Z 6
2015-08-18T00:24:00Z 6
注意:如果设置的时间间隔比字段数据间的时间间隔更大时,则函数会返回 0,如下所示:

SELECT ELAPSED(water_level,1h) FROM h2o_feet WHERE location = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' and time <= '2015-08-18T00:24:00Z'

name: h2o_feet

time elapsed
2015-08-18T00:06:00Z 0
2015-08-18T00:12:00Z 0
2015-08-18T00:18:00Z 0
2015-08-18T00:24:00Z 0

MOVING_AVERAGE() 函数
作用:返回一个连续字段值的移动平均值,字段类型必须是长整形或者 float64 类型。
语法:
基本语法
SELECT MOVING_AVERAGE(<field_key>,<window>) FROM <measurement_name> [WHERE <stuff>]
与其他函数和 GROUP BY time() 语句一起使用时的语法
SELECT MOVING_AVERAGE(<function>(<field_key>),<window>) FROM <measurement_name> WHERE <stuff> GROUP BY time(<time_interval>)
此函数可以和以下函数一起使用:
COUNT(), MEAN(),MEDIAN(), SUM(), FIRST(), LAST(), MIN(), MAX(), and PERCENTILE().
示例:

SELECT water_level FROM h2o_feet WHERE location = 'santa_monica' AND time >= '2015-08-18T00:00:00Z' and time <= '2015-08-18T00:36:00Z'

name: h2o_feet

time water_level
2015-08-18T00:00:00Z 2.064
2015-08-18T00:06:00Z 2.116
2015-08-18T00:12:00Z 2.028
2015-08-18T00:18:00Z 2.126
2015-08-18T00:24:00Z 2.041
2015-08-18T00:30:00Z 2.051
2015-08-18T00:36:00Z 2.067

NON_NEGATIVE_DERIVATIVE() 函数
作用:返回在一个 series 中的一个字段中值的变化的非负速率。
语法:
SELECT NON_NEGATIVE_DERIVATIVE(<field_key>, [<unit>]) FROM <measurement_name> [WHERE <stuff>]
其中 unit 取值可以为以下几个:
Valid time specifications for unit are:
u microseconds
s seconds
m minutes
h hours
d days
w weeks
与聚合类函数放在一起使用时的语法如下所示:
SELECT NON_NEGATIVE_DERIVATIVE(AGGREGATION_FUNCTION(<field_key>),[<unit>]) FROM <measurement_name> WHERE <stuff> GROUP BY time(<aggregation_interval>)
此函数示例请参阅:DERIVATIVE()``函数

STDDEV() 函数
作用:返回一个字段中的值的标准偏差。值的类型必须是长整型或 float64 类型。
语法:
SELECT STDDEV(<field_key>) FROM <measurement_name> [WHERE <stuff>] [GROUP BY <stuff>]
示例:

SELECT STDDEV(water_level) FROM h2o_feet

name: h2o_feet

time stddev
1970-01-01T00:00:00Z 2.279144584196145
示例 2:

SELECT STDDEV(water_level) FROM h2o_feet WHERE time >= '2015-08-18T00:00:00Z' and time < '2015-09-18T12:06:00Z' GROUP BY time(1w), location

name: h2o_feet
tags: location = coyote_creek
time stddev
---- ------
2015-08-13T00:00:00Z 2.2437263080193985
2015-08-20T00:00:00Z 2.121276150144719
2015-08-27T00:00:00Z 3.0416122170786215
2015-09-03T00:00:00Z 2.5348065025435207
2015-09-10T00:00:00Z 2.584003954882673
2015-09-17T00:00:00Z 2.2587514836274414
name: h2o_feet
tags: location = santa_monica
time stddev
---- ------
2015-08-13T00:00:00Z 1.11156344587553
2015-08-20T00:00:00Z 1.0909849279082366
2015-08-27T00:00:00Z 1.9870116180096962
2015-09-03T00:00:00Z 1.3516778450902067
2015-09-10T00:00:00Z 1.4960573811500588
2015-09-17T00:00:00Z 1.075701669442093

连续查询
定义
InfluxDB 的连续查询是在数据库中自动定时启动的一组语句,语句中必须包含 SELECT关键词和GROUP BY time()关键词。
InfluxDB 会将查询结果放在指定的数据表中。

目的
使用连续查询是最优的降低采样率的方式,连续查询和存储策略搭配使用将会大大降低 InfluxDB 的系统占用量。
而且使用连续查询后,数据会存放到指定的数据表中,这样就为以后统计不同精度的数据提供了方便。

操作
只有管理员用户可以操作连续查询。

1)新建连续查询
新建连续查询的语法如下所示:
CREATE CONTINUOUS QUERY <cq_name> ON <database_name>
[RESAMPLE [EVERY <interval>] [FOR <interval>]]
BEGIN SELECT <function>(<stuff>)[,<function>(<stuff>)] INTO <different_measurement>
FROM <current_measurement> [WHERE <stuff>] GROUP BY time(<interval>)[,<stuff>]
END
查询部分被 CREATE CONTINUOUS QUERY […] BEGIN 和 END 所包含,主要的逻辑代码也是在这一部分。
使用示例:

CREATE CONTINUOUS QUERY cq_30m ON telegraf BEGIN SELECT mean(used) INTO mem_used_30m FROM mem GROUP BY time(30m) END
SHOW CONTINUOUS QUERIES

name: telegraf

name query
cq_30m CREATE CONTINUOUS QUERY cq_30m ON telegraf BEGIN
SELECT mean(used) INTO telegraf."default".mem_used_30m FROM telegraf."default".mem
GROUP BY time(30m) END

name: _internal

name query
示例在 telegraf 库中新建了一个名为 cq_30m 的连续查询,每三十分钟取一个 used 字段的平均值,加入 mem_used_30m 表中。使用的数据保留策略都是 default。

2)显示所有已存在的连续查询
查询所有连续查询可以使用如下语句:

SHOW CONTINUOUS QUERIES

name: telegraf

name query
cq_30m CREATE CONTINUOUS QUERY cq_30m ON telegraf
BEGIN SELECT mean(used) INTO telegraf."default".mem_used_30m FROM telegraf."default".mem
GROUP BY time(30m) END

name: _internal

name query
可以看到其连续查询的名称以及 语句等信息。

3)删除 Continuous Queries
删除连续查询的语句如下:
DROP CONTINUOUS QUERY <cq_name> ON <database_name>

在 InfluxDB 中,将连续查询与数据存储策略一起使用会达到最好的效果。
比如,将精度高的表的存储策略定为一个周,然后将精度底的表存储策略定的时间久一点,这要就可以实现高低搭配,以满足不同的工作需要。

连续查询语法
连续查询的语法如下:
CREATE CONTINUOUS QUERY <cq_name> ON <database_name> [RESAMPLE [EVERY <interval>] [FOR <interval>]] BEGIN SELECT <function>(<stuff>)[,<function>(<stuff>)] INTO <different_measurement> FROM <current_measurement> [WHERE <stuff>] GROUP BY time(<interval>)[,<stuff>] END
指定连续查询的时间范围
可以使用 RESAMPLE FOR 关键词来指定连续查询的时间范围,比如,每次执行都对 1 小时内的数据进行连续查询:
CREATE CONTINUOUS QUERY vampires_1 ON transylvania RESAMPLE FOR 60m BEGIN SELECT count(dracula) INTO vampire_populations_1 FROM raw_vampires GROUP BY time(30m) END
这个语句每次会将 1 小时的数据执行连续查询,也就是说,每次执行时,会将 now() 到 now()-30m 和 now()-30m 到 now()-60m 分别做连续查询,这样我们就可以手动指定连续查询的时间范围了。
指定连续查询的执行频次
可以使用 RESAMPLE EVERY 关键词来指定连续查询的执行频次,比如,指定连续查询的执行频次为每 15m 执行一次:
CREATE CONTINUOUS QUERY vampires ON transylvania RESAMPLE EVERY 15m BEGIN SELECT count(dracula) INTO vampire_populations FROM raw_vampires GROUP BY time(30m) END
这样,连续查询会每隔 15m 执行一次。
同时指定连续查询的范围和频次
将 RESAMPLE FOR 和 EVERY 关键词同时使用,可以同时指定连续查询的范围和频次,如下:
CREATE CONTINUOUS QUERY vampires_2 ON transylvania RESAMPLE EVERY 15m FOR 60m BEGIN SELECT count(dracula) INTO vampire_populations_2 FROM raw_vampires GROUP BY time(30m) END
这个语句指定连续查询每 15m 执行一次,每次执行的范围为 60m。

当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化:

单表优化
除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:

字段
尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
VARCHAR的长度只分配真正需要的空间
使用枚举或整数代替字符串类型
尽量使用TIMESTAMP而非DATETIME,
单表不要有太多字段,建议在20以内
避免使用NULL字段,很难查询优化且占用额外索引空间
用整型来存IP
索引
索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段
字符字段只建前缀索引
字符字段最好不要做主键
不用外键,由程序保证约束
尽量不用UNIQUE,由程序保证约束
使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引
查询SQL
可通过开启慢查询日志来找出较慢的SQL
不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
不用SELECT *`
OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
不用函数和触发器,在应用程序实现
避免%xxx式查询
少用JOIN
使用同类型进行比较,比如用'123'和'123'比,123和123比
尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大
引擎
目前广泛使用的是MyISAM和InnoDB两种引擎:

MyISAM
MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:

不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁
不支持事务
不支持外键
不支持崩溃后的安全恢复
在表有读取查询的同时,支持往表中插入新纪录
支持BLOB和TEXT的前500个字符索引,支持全文索引
支持延迟更新索引,极大提升写入性能
对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用
InnoDB
InnoDB在MySQL 5.5后成为默认索引,它的特点是:
支持行锁,采用MVCC来支持高并发
支持事务
支持外键
支持崩溃后的安全恢复
不支持全文索引 总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表
系统调优参数
可以使用下面几个工具来做基准测试:

sysbench:一个模块化,跨平台以及多线程的性能测试工具
iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具
tpcc-mysql:Percona开发的TPC-C测试工具
具体的调优参数内容较多,具体可参考官方文档,这里介绍一些比较重要的参数:

back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。可以从默认的50升至500
wait_timeout:数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时
max_user_connection: 最大连接数,默认为0无上限,最好设一个合理上限
thread_concurrency:并发线程数,设为CPU核数的两倍
skip_name_resolve:禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问
key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好
innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like 'Innodb_buffer_pool_read%',保证(Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好
innodb_additional_mem_pool_size:InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小
innodb_log_buffer_size:InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB
query_cache_size:缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大. 可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小
read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能
sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小
read_rnd_buffer_size:MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
record_buffer:每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值
thread_cache_size:保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的
table_cache:类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM
升级硬件
Scale up,这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能

读写分离
也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离

缓存
缓存可以发生在这些层次:

MySQL内部:在系统调优参数介绍了相关设置
数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object
应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object
Web层:针对web页面做缓存
浏览器客户端:用户端的缓存
可以根据实际情况在一个层次或多个层次结合加入缓存。这里重点介绍下服务层的缓存实现,目前主要有两种方式:

直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般。
回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。
表分区
MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码。

对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引。

用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,如下图5条记录落在两个分区上:

mysql> explain partitions select count(1) from user_partition where id in (1,2,3,4,5);+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+| 1 | SIMPLE | user_partition | p1,p4 | range | PRIMARY | PRIMARY | 8 | NULL | 5 | Using where; Using index |+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+1row in set (0.00 sec)
分区的好处是:

可以让单表存储更多的数据
分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
部分查询能够从查询条件确定只落在少数分区上,速度会很快
分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备
可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
可以备份和恢复单个分区
分区的限制和缺点:

一个表最多只能有1024个分区
如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
分区表无法使用外键约束
NULL值会使分区过滤无效
所有分区必须使用相同的存储引擎
分区的类型:

RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区
LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式
KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值
分区适合的场景有:

最适合的场景数据的时间序列性比较强,则可以按时间来分区,如下所示:
CREATE TABLE members ( firstname VARCHAR(25) NOT NULL, lastname VARCHAR(25) NOT NULL, username VARCHAR(16) NOT NULL, email VARCHAR(35), joined DATE NOT NULL)PARTITION BY RANGE( YEAR(joined) ) ( PARTITION p0 VALUES LESS THAN (1960), PARTITION p1 VALUES LESS THAN (1970), PARTITION p2 VALUES LESS THAN (1980), PARTITION p3 VALUES LESS THAN (1990), PARTITION p4 VALUES LESS THAN MAXVALUE);
查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容的批量删除。

如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存
另外MySQL有一种早期的简单的分区实现 - 合并表(merge table),限制较多且缺乏优化,不建议使用,应该用新的分区机制来替代

垂直拆分
垂直分库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。垂直分表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联

垂直拆分的优点是:

可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少)
可以达到最大化利用Cache的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起
数据维护简单
缺点是:

主键出现冗余,需要管理冗余列
会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力
依然存在单表数据量过大的问题(需要水平拆分)
事务处理复杂
水平拆分
概述
水平拆分是通过某种策略将数据分片来存储,分库内分表和分库两部分,每片数据会分散到不同的MySQL表或库,达到分布式的效果,能够支持非常大的数据量。前面的表分区本质上也是一种特殊的库内分表 库内分表,仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻MySQL服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库来解决

水平拆分的优点是:

不存在单库大数据和高并发的性能瓶颈
应用端改造较少
提高了系统的稳定性和负载能力
缺点是:

分片事务一致性难以解决
跨节点Join性能差,逻辑复杂
数据多次扩展难度跟维护量极大
分片原则
能不分就不分,参考单表优化
分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量
分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容
尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题
查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。
通过数据冗余和表分区赖降低跨库Join的可能
这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。

总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。

解决方案
由于水平拆分牵涉的逻辑比较复杂,当前也有了不少比较成熟的解决方案。这些方案分为两大类:客户端架构和代理架构。

通过修改数据访问层,如JDBC、Data Source、MyBatis,通过配置来管理多个数据源,直连数据库,并在模块内完成数据的分片整合,一般以Jar包的方式呈现

客户端架构的优点是:

应用直连数据库,降低外围系统依赖所带来的宕机风险
集成成本低,无需额外运维的组件
缺点是:

限于只能在数据库访问层上做文章,扩展性一般,对于比较复杂的系统可能会力不从心
将分片逻辑的压力放在应用服务器上,造成额外风险

代理架构
通过独立的中间件来统一管理所有数据源和数据分片整合,后端数据库集群对前端应用程序透明,需要独立部署和运维代理组件

这是一个代理架构的例子:
1736439-453dda3bf4b338d7.jpg
代理组件为了分流和防止单点,一般以集群形式存在,同时可能需要Zookeeper之类的服务组件来管理

代理架构的优点是:

能够处理非常复杂的需求,不受数据库访问层原来实现的限制,扩展性强
对于应用服务器透明且没有增加任何额外负载
缺点是:

需部署和运维独立的代理中间件,成本高
应用需经过代理来连接数据库,网络上多了一跳,性能有损失且有额外风险。

全量备份使用 mysqldump 工具

直接上脚本:

!/bin/bash

在使用之前,请提前创建以下各个目录

db_user="root"
db_passwd="123456"
db_host="192.168.1.101"
db_prod="3306"
db_name="test"
backup_dir="/home/leo/backup/bakmysql"
backup_old_dir="/home/leo/backup/bakmysqlold"

echo $backup_old_dir

进入备份目录将之前的移动到 old 目录

cd "$backup_dir"
echo "you are in bakmysql directory now"
mv $db_name* "$backup_old_dir"
echo "Old databases are moved to bakmysqlold folder"

时间格式

time=$(date +"%Y-%m-%d")

mysql 备份的命令,注意有空格和没有空格

mysqldump -h$db_host -P$db_prod -u$db_user -p$db_passwd --single-transaction --flush-logs $db_name | gzip >"$backup_dir/$db_name"-"$time.sql.gz"
echo "your database backup successfully completed"

这里将 7 天之前的备份文件删掉

SevenDays=$(date -d -7day +"%Y-%m-%d")
if [ -f "$backup_old_dir/$db_name-$SevenDays.sql.gz" ]; then
rm -rf "$backup_old_dir/$db_name-$SevenDays.sql.gz"
echo "you have delete 7days ago bak sql file "
else
echo "7days ago bak sql file not exist "
echo "bash complete"
fi
通过下面命令将脚本加入系统的计划任务:

分钟 小时 天 月 星期几

每天01点10分执行备份

crontab -e
10 01 * /usr/bin/bash /home/leo/backup/bakmysql.sh

保存后 crontab 会自动加载配置

增量备份 binlog

增量备份主要用到的是binlog, 需要开启 mysql binlog:

开启 binlog

vim /var/lib/mysql/mysql.conf.d/mysqld.cnf

[mysqld] 下添加:

[mysqld]
log_bin=mysql-bin
log_bin_index=mysql-bin.index
binlog_do_db=test
binlog_ignore_db=mysql

log 3天自动删除

expire_logs_days=3
备份脚本:

!/bin/bash

在使用之前,请提前创建以下各个目录

backupDir="/home/leo/backup/bakmysql"

增量备份时复制mysql-bin.00000*的目标目录,提前手动创建这个目录

mysqlDir="/var/lib/mysql"

mysql的数据目录

logFile="/home/leo/backup/bakmysql/bak.log"

mysql的index文件路径,放在数据目录下的

binFile="/var/lib/mysql/mysql-bin.index"

这个是用于产生新的 mysql-bin.00000* 文件

mysqladmin -uroot -p123456 flush-logs

wc -l 统计行数

awk 简单来说 awk 就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。

counter=$(wc -l $binFile | awk '{print $1}')
nextNum=0

这个 for 循环用于比对 $counter , $nextNum 这两个值来确定文件是不是存在或最新的

for file in $(cat $binFile); do

base=$(basename $file)
echo $base
#basename用于截取mysql-bin.00000*文件名,去掉./mysql-bin.000005前面的./
nextNum=$(expr $nextNum + 1)
if [ $nextNum -eq $counter ]; then
    echo $base skip! >>$logFile
else
    dest=$backupDir/$base
    if (test -e $dest); then #test -e用于检测目标文件是否存在,存在就写exist!到$logFile去
        echo $base exist! >>$logFile
    else
        cp $mysqlDir/$base $backupDir
        echo $base copying >>$logFile
    fi
fi

done
echo $(date +"%Y年%m月%d日 %H:%M:%S") $Next Bakup succ! >>$logFile
通过下面命令将脚本加入系统的计划任务:

分钟 小时 天 月 星期几

每两个小时执行备份

crontab -e
0 /2 /home/leo/backup/mysqlIncreBackup.sh

保存后 crontab 会自动加载配置

数据恢复

恢复整个db

gzip < db1.sql.gz | mysql -uroot -p123456
binlog 数据恢复需要用到 mysqlbinlog 工具,由于 binlog 记录的是 sql 的所有操作,请按 mysql-bin.index 顺序恢复

恢复增量备份

mysqlbinlog mysql-bin.0000xx | mysql -u用户名 -p密码 数据库名

清理备份脚本

!/bin/bash

删除备份文件

Started on 2019/11/8 Leo <xinlichao2016@gmail.com>

Last update 2019/11/8 Leo <xinlichao2016@gmail.com>

BAKINCRE_PATH=/home/leo/mysqlbackup/bakincre

删除7天前文件

find ${BAKINCRE_PATH} -mtime +7 -type f -name "." | xargs rm -f

通过下面命令将脚步加入系统的计划任务

crontab -e

分钟 小时 天 月 星期几

每天01点30分执行

crontab -e

30 01 * /usr/bin/bash /home/leo/scripts/clearBakMysql.sh >> /dev/null 2>&1

时间序列数据库 Time Series Database (TSDB)

随着分布式系统监控、物联网的发展,TSDB开始受到更多的关注。
维基百科上对于时间序列的定义是‘一系列数据点按照时间顺序排列’

时间序列数据就是历史烙印,具有不变性,、唯一性、时间排序性

时间序列数据跟关系型数据库有太多不同,但是很多公司并不想放弃关系型数据库。 于是就产生了一些特殊的用法,比如用 MySQL 的 VividCortex, 用 Postgres 的 Timescale。 很多人觉得特殊的问题需要特殊的解决方法,于是很多时间序列数据库从头写起,不依赖任何现有的数据库, 比如 Graphite,InfluxDB。

mysql 的引擎,除了常见的 innodb 和 myisam ,还有一个引擎叫 archive ,它的作用和 rrd 差不多,支持插入和查询操作。

时序数据是基于时间的一系列的数据。在有时间的坐标中将这些数据点连成线,往过去看可以做成多纬度报表,揭示其趋势性、规律性、异常性;往未来看可以做大数据分析,机器学习,实现预测和预警。

时序数据库就是存放时序数据的数据库,并且需要支持时序数据的快速写入、持久化、多纬度的聚合查询等基本功能。

数据写入的特点

写入平稳、持续、高并发高吞吐:时序数据的写入是比较平稳的,这点与应用数据不同,应用数据通常与应用的访问量成正比,而应用的访问量通常存在波峰波谷。时序数据的产生通常是以一个固定的时间频率产生,不会受其他因素的制约,其数据生成的速度是相对比较平稳的。
写多读少:时序数据上95%-99%的操作都是写操作,是典型的写多读少的数据。这与其数据特性相关,例如监控数据,你的监控项可能很多,但是你真正去读的可能比较少,通常只会关心几个特定的关键指标或者在特定的场景下才会去读数据。
实时写入最近生成的数据,无更新:时序数据的写入是实时的,且每次写入都是最近生成的数据,这与其数据生成的特点相关,因为其数据生成是随着时间推进的,而新生成的数据会实时的进行写入。数据写入无更新,在时间这个维度上,随着时间的推进,每次数据都是新数据,不会存在旧数据的更新,不过不排除人为的对数据做订正。

数据查询和分析的特点

按时间范围读取:通常来说,你不会去关心某个特定点的数据,而是一段时间的数据。
最近的数据被读取的概率高
历史数据粗粒度查询的概率搞
多种精度查询
多维度分析

数据存储的特点

数据量大:拿监控数据来举例,如果我们采集的监控数据的时间间隔是1s,那一个监控项每天会产生86400个数据点,若有10000个监控项,则一天就会产生864000000个数据点。在物联网场景下,这个数字会更大。整个数据的规模,是TB甚至是PB级的。
冷热分明:时序数据有非常典型的冷热特征,越是历史的数据,被查询和分析的概率越低。
具有时效性:时序数据具有时效性,数据通常会有一个保存周期,超过这个保存周期的数据可以认为是失效的,可以被回收。一方面是因为越是历史的数据,可利用的价值越低;另一方面是为了节省存储成本,低价值的数据可以被清理。
多精度数据存储:在查询的特点里提到时序数据出于存储成本和查询效率的考虑,会需要一个多精度的查询,同样也需要一个多精度数据的存储。

开源时间序列数据库

1999/07/16 RRDTool First release
2009/12/30 Graphite 0.9.5
2011/12/23 OpenTSDB 1.0.0
2013/05/24 KairosDB 1.0.0-beta
2013/10/24 InfluxDB 0.0.1
2014/08/25 Heroic 0.3.0
2017/03/27 TimescaleDB 0.0.1-beta
RRDTool 是最早的时间序列数据库,它自带画图功能,现在大部分时间序列数据库都使用Grafana来画图。

Graphite 是用 Python 写的 RRD 数据库,它的存储引擎 Whisper 也是 Python 写的, 它画图和聚合能力都强了很多,但是很难水平扩展。
OpenTSDB 使用 HBase 解决了水平扩展的问题
KairosDB 最初是基于OpenTSDB修改的,但是作者认为兼容HBase导致他们不能使用很多 Cassandra 独有的特性, 于是就抛弃了HBase仅支持Cassandra。
新发布的 OpenTSDB 中也加入了对 Cassandra 的支持。 故事还没完,Spotify 的人本来想使用 KairosDB,但是觉得项目发展方向不对以及性能太差,就自己撸了一个 Heroic。

InfluxDB 早期是完全开源的,后来为了维持公司运营,闭源了集群版本。 在 Percona Live 上他们做了一个开源数据库商业模型正面临危机的演讲,里面调侃红帽的段子很不错。 并且今年的 Percona Live 还有专门的时间序列数据库单元。

数据模型

时间序列数据可以分成两部分

序列 :就是标识符(维度),主要的目的是方便进行搜索和筛选
数据点:时间戳和数值构成的数组
行存:一个数组包含多个点,如 [{t: 2017-09-03-21:24:44, v: 0.1002}, {t: 2017-09-03-21:24:45, v: 0.1012}]
列存:两个数组,一个存时间戳,一个存数值,如[ 2017-09-03-21:24:44, 2017-09-03-21:24:45], [0.1002, 0.1012]
一般情况下:列存能有更好的压缩率和查询性能

基本概念

metric: 度量,相当于关系型数据库中的table。
data point: 数据点,相当于关系型数据库中的row。
timestamp:时间戳,代表数据点产生的时间。
field: 度量下的不同字段。比如位置这个度量具有经度和纬度两个field。一般情况下存放的是会随着时间戳的变化而变化的数据。
tag: 标签,或者附加信息。一般存放的是并不随着时间戳变化的属性信息。timestamp加上所有的tags可以认为是table的primary key。
如下图,度量为Wind,每一个数据点都具有一个timestamp,两个field:direction和speed,两个tag:sensor、city。它的第一行和第三行,存放的都是sensor号码为95D8-7913的设备,属性城市是上海。随着时间的变化,风向和风速都发生了改变,风向从23.4变成23.2;而风速从3.4变成了3.3。
5682416-1dac47600e7714d2.jpg

应用场景

所有有时序数据产生,并且需要展现其历史趋势、周期规律、异常性的,进一步对未来做出预测分析的,都是时序数据库适合的场景。

例:
在工业物联网环境监控方向,百度天工的客户就遇到了这么一个难题,由于工业上面的要求,需要将工况数据存储起来。客户每个厂区具有20000个监测点,500毫秒一个采集周期,一共20个厂区。这样算起来一年将产生惊人的26万亿个数据点。假设每个点50Byte,数据总量将达1P(如果每台服务器10T的硬盘,那么总共需要100多台服务器)。这些数据不只是要实时生成,写入存储;还要支持快速查询,做可视化的展示,帮助管理者分析决策;并且也能够用来做大数据分析,发现深层次的问题,帮助企业节能减排,增加效益。最终客户采用了百度天工的时序数据库方案,帮助他解决了难题。

时序数据库遇到的挑战

很多人可能认为在传统关系型数据库上加上时间戳一列就能作为时序数据库。数据量少的时候确实也没问题,但少量数据是展现的纬度有限,细节少,可置信低,更加不能用来做大数据分析。很明显时序数据库是为了解决海量数据场景而设计的。

可以看到时序数据库需要解决以下几个问题

时序数据的写入:如何支持每秒钟上千万上亿数据点的写入。

时序数据的读取:又如何支持在秒级对上亿数据的分组聚合运算。

成本敏感:由海量数据存储带来的是成本问题。如何更低成本的存储这些数据,将成为时序数据库需要解决的重中之重。

这些问题不是用一篇文章就能含盖的,同时每个问题都可以从多个角度去优化解决。在这里只从数据存储这个角度来尝试回答如何解决大数据量的写入和读取。

RRD

RRD (Round Robin Database)数据库是一个环形的数据库,数据库由一个固定大小的数据文件来存放数据,此数据库不会像传统数据库一样为随着数据的增多而文件的大小也在增加,RRD在创建好后其文件大小就固定,可以把它想像成一个圆,圆的众多直径把圆划分成一个个扇形,每个扇形就是可以存数据的槽位,每个槽位上被打上了一个时间戳,在圆心上有一个指针,随着时间的流逝,取回数据后,指针会负责把数据填充在相应的槽位上,当指针转了360度后,最开始的数据就会被覆盖,就这样RRD循环填充着数据。
5682416-cae4438fcade1a8d.jpg
源数据搜集:采用一些数据搜集工具,如脚本、shell命令、SNMP等工具在一定时间间隔里把数据搜集填充到rrd数据库中,这些需要数据搜集的对象叫DS,一个DS里在一个时间里可以搜集的数据可以有多个,比如一个时间点上对网卡来说有进来的流量,也有流出的流量,所以这是2个数据成为一组数据。
临时存储:源数据获取到后是存放在一个数据库的一个临时区域,这些源数据叫做PDP
分组-聚合:RRDTool把这些PDP数据作为数据源通过分组、再利用聚合函数计算后把计算后的结果放在RRD数据库的时间槽(time slot)上,这些数据叫做CDP,CDP才是RRDTool绘图时真正打交道的数据,
在从源数据中取数据做聚合计算时会有一个挑选数据的基准,也就是说是以几个源数据为一组做聚合,根据现实需求的不同,对源数据可以很灵活的选择不同的时间段提取源数据,再聚合提取不同的聚合值,这样就产生不同组别的CDP数据,这些有以相同时间段挑选源数据及相同聚合函数计算的结果组成的数据就叫RRA,所以根据挑选源数据的标准及采用的聚合函数的不同,RRA可以有多组。
DS:Data Source 数据源,用于定义搜集数据的工具所搜集数据的一些特性
Time Solt:时间槽,用于存放通过聚合后的数据区域
PDP:Primary Data Point 主数据节点,每个时间点产生的数据,即是搜集的源数据,没有做聚合的数据
CDP(Consolidation Data Point 聚合数据节点):通过对获取的源数据分组、聚合计算后得到的数据叫CDP,
RRA(Round Robin Archive 轮转归档):以相同的分组、聚合函数计算后的CDP数据组就组成了RRA
Resolution(解析度):这是一个时间跨度,表示在做聚合计算时是以几个连续的time slot里的数据做聚合,在默认时rrd是以300秒的间隔产生一个time slot。
CF:Consolidation Function,合并函数或聚合函数,以RRDTool中有AVERAGE、MAX、MIN、LAST4种
以一个图来说明PDP、CDP、RRA之间的关系:
5682416-8842723fd1c60214.jpg
PDP是以规定的时间间隔(默认为300秒)搜集的源数据,第一个RRA以4个PDP(即4*300秒)为一组做CF后组成的数据,第二个RRA则是以10个PDP为一组做CF后组成的数据。

InfluxDB

InfluxDB 在存储引擎上纠结了很久, leveldb, rocksdb, boltdb 都玩了个遍,最后决定自己造个轮子叫 Time Structured Merge Tree。

Time Structured Merge Tree (TSM) 和 Log Structured Merge Tree (LSM) 的名字都有点误导性,关键并不是树,也不是日志或者时间,而是 Merge。

写入的时候,数据先写入到内存里,之后批量写入到硬盘。
读的时候,同时读内存和硬盘然后合并结果。
删除的时候,写入一个删除标记,被标记的数据在读取时不会被返回。
后台会把小的块合并成大的块,此时被标记删除的数据才真正被删除
相对于普通数据,有规律的时间序列数据在合并的过程中可以极大的提高压缩比。

热点话题

  1. 存储 单机存储

如果只是存储起来,直接写成日志就行。但因为后续还要快速的查询,所以需要考虑存储的结构。
传统数据库存储采用的都是B tree,这是由于其在查询和顺序插入时有利于减少寻道次数的组织形式。我们知道磁盘寻道时间是非常慢的,一般在10ms左右。磁盘的随机读写慢就慢在寻道上面。对于随机写入B tree会消耗大量的时间在磁盘寻道上,导致速度很慢。我们知道SSD具有更快的寻道时间,但并没有从根本上解决这个问题。
对于90%以上场景都是写入的时序数据库,B tree很明显是不合适的。
业界主流都是采用LSM tree替换B tree,比如Hbase, Cassandra等nosql中。这里我们详细介绍一下。

  1. LSM tree

LSM tree 包括内存里的数据结构和磁盘上的文件两部分,分别对应Hbase里的MemStore和HLog;对应Cassandra里的MemTable和sstable
LSM tree操作流程如下:

数据写入和更新时首先写入位于内存里的数据结构。为了避免数据丢失也会先写到WAL文件中。

内存里的数据结构会定时或者达到固定大小会刷到磁盘。这些磁盘上的文件不会被修改。

随着磁盘上积累的文件越来越多,会定时的进行合并操作,消除冗余数据,减少文件数量。
5682416-1b1fae32f457217b.jpg

  1. 分布式存储

分布式存储首先要考虑的是如何将数据分布到多台机器上面,也就是 分片(sharding)问题

时序数据库的分片方法和其他分布式系统是相通的。

哈希分片:这种方法实现简单,均衡性较好,但是集群不易扩展。

一致性哈希:这种方案均衡性好,集群扩展容易,只是实现复杂。代表有Amazon的DynamoDB和开源的Cassandra。

范围划分:通常配合全局有序,复杂度在于合并和分裂。代表有Hbase。

结合时序数据库的特点,根据metric+tags分片是比较好的一种方式,因为往往会按照一个时间范围查询,这样相同metric和tags的数据会分配到一台机器上连续存放,顺序的磁盘读取是很快的。

考虑时序数据时间范围很长的情况,需要根据时间范围再分成几段,分别存储到不同的机器上,这样对于大范围时序数据就可以支持并发查询,优化查询速度。

如下图,第一行和第三行都是同样的tag(sensor=95D8-7913;city=上海),所以分配到同样的分片,而第五行虽然也是同样的tag,但是根据时间范围再分段,被分到了不同的分片。
5682416-7414889ca7ce4fc9.jpg
InfluxDB的单机存储
在单机上InfluxDB采取类似于LSM tree的存储结构TSM;而分片的方案InfluxDB先通过<database>+<timestamp>(事实上还要加上retentionPolicy)确定ShardGroup,再通过<metric>+<tags>的hash code确定到具体的Shard。

低延迟
时间序列数据库主要是用来分析的,所以提高响应速度对于诊断生产环境的问题是十分重要的。

把所有数据都放在内存
Facebook 写了叫 Gorilla 的纯内存时间序列数据库发表在 VLDB 上,现在已经开源,改名为 Beringei(都是猩猩…)

提前聚合
因为查询中经常需要对一个很长的时间区间取一些粗粒度的值,比如6月到8月每天的平均CPU使用率。 这些聚合值(均值,最大,最小) 都可以在存储数据的时候计算出来。BtrDB 和 Akumuli 都在内部节点中存储聚合值,这样在很多查询中底层的节点不需要被访问就可以得到结果。

处理旧数据
很多时间序列数据都没有多大用处,特别是当系统长时间正常运行时,完整的历史数据意义并不大。
所以有些数据库比如 RDDTool 和 Graphite 会自动删除高精度的数据,只保留低精度的。
但是对于很多新的时间序列数据库,在聚合和删除大量旧数据的同时保证系统正常运行并不像删除一个本地文件那样简单。
如果监控系统比被监控系统还不稳定就比较尴尬了。
元数据索引
时间序列的标识符是时间序列数据库里主要的元数据。
Heroic 使用 Elasticsearch 来存储元数据, 查询首先通过 Elasticsearch 来取得符合要求的序列标识符,之后从 Cassandra 根据标识符来读取对应的数据。
但是维护一个完整的搜索引擎带来的运维压力和增加的通信时间都是不能忽视的。
因此 InfluxDB 和 Prometheus 就自己写了倒排索引来索引元数据。
Tracing
InfluxDB 的人写了一篇博客 Metrics are dead, 起因是在一个关于监控的会议 Monitorama 上有人说单纯的监控数据已经不能满足他们复杂的微服务架构了。
于是 InfluxDB 的人反驳说并不是所有人都在使用大规模的分布式系统,对于很多简单的应用单纯的监控数据已经完全够用了。
我的看法是时间序列数据库是可以用来存 Trace 的。
Trace 是更加复杂的时间序列数据,把单纯的数值变成一个包含更多信息的对象,它就是一个 Trace。
并且很多流行的 Tracer 的存储也是使用 Cassandra, 比如 Zipkin, Uber 的 Jaeger。
InfluxDB 现在已经支持存储 Trace 了