kingshard最佳实践(一)

一、kingshard简介

kingshard是一个Go开发的mysql中间件,可以实现读写分离、分库分表、连接池等功能

1.1  kingshard工作流程

kingshard采用Go开发,充分地利用了Go语言的并发特性。Go语言在并发方面,做了很好的封装,这大大简化了kingshard的开发工作。kingshard的整体工作流程如下所述:

  • 读取配置文件并启动,在配置文件中设置的监听端口监听客户端请求。
  • 收到客户端连接请求后,启动一个goroutine单独处理该请求。
  • 首选进行登录验证,验证过程完全兼容MySQL认证协议,由于用户名和密码在配置文件中已经设置好,所以可以利用该信息验证连接请求是否合法。 当用户名和密码都正确时,转入下面的步骤,否则返回出错信息给客户端。
  • 认证通过后,客户端发送SQL语句。
  • kingshard对客户端发送过来的SQL语句,进行词法和语义分析,识别出SQL的类型和生成SQL的路由计划。如果有必要还会改写SQL,然后转发到相应的DB。也有可能不做词法和语义分析直接转发到相应的后端DB。如果转发SQL是分表且跨多个DB,则每个DB对应启动一个goroutine发送SQL和接收该DB返回的结果。
  • 接收并合并结果,然后转发给客户端。

kingshard工作整体流程可参考下面这幅图。

kingshard整体架构图如下所示 

1.2  kingshard应用场景

现在很多互联网公司还是在大量使用MySQL来存储各种类型的关系型数据。随着访问量和数据量的增长,开发者不得不考虑一些MySQL相关的新问题:

  • 读写分离问题。由于前端应用访问量增加,单台MySQL不足以支撑整个系统的写入和查询操作。这时候,我们不得不将一些耗时的查询操作分散到多个slave上。
  • 单表容量问题。如果在系统设计之初,没有考虑到分表问题。随着数据量的增长,单表容量越来越大。作者见过单表容量5亿条记录,然后一个简单的delete操作都会引起系统慢日志,而且有可能导致MySQL IO瞬发性的飙升。很多同学可能会想到,在查询的字段上加上索引,但当数据量增长到这么大的时候,即使加上索引效果也不明显了。归根结底,就是单表数据量太大,导致MySQL即使通过索引定位数据,仍然需要扫描很多记录。
  • 数据库的运维问题。如果在代码中配置主库和从库host,系统运行当然也是没问题的。但这样大大增加了运维工作的压力,比如:MySQL数据库IO压力由于访问量的增加居高不下,DBA需要添加一台slave,这时候就不得不修改代码,然后打包并上线。还有很多非常实际的例子,在这就不一一列举。
  • 连接池。前端应用频繁连接MySQL,由此给MySQL带来的额外性能消耗也是不容忽视的。如果通过增加一个连接池,每个DB缓存一定数量的MySQL连接,当有应用需要连接后端的MySQL,直接从连接池里取出一个已建好的连接来发送SQL请求,这样会大大加快数据查询速度。而且可以降低MySQL的性能消耗。
  • SQL日志。在程序出现问题时,我们希望得到一些SQL日志,比如,什么时刻哪条SQL发送到哪一台DB上了。通过查看这种日志能够帮助我们快速定位问题。

面对这些问题,我们可以在客户端代码中逐一实现。但这样也会使得客户端越来越重,不那么灵活。作者一直从事数据库相关工作的开发,正是基于数据库开发的痛点,设计和实现了kingshard数据库中间件。kingshard对上述5类问题都有比较合适的解决方案。

二、kingshard的安装配置

2.1  kingshard的安装

kingshard安装比较简单,具体如下

1、安装Go语言环境(Go版本1.6以上),具体步骤请Google。
2、git clone https://github.com/flike/kingshard.git $GOPATH/src/github.com/flike/kingshard
3、cd src/github.com/flike/kingshard
4、source ./dev.sh
5、make
6、设置配置文件
7、运行kingshard。
./bin/kingshard -config=etc/ks.yaml

注意:

  • kingshard采用的是yaml方式解析配置文件,需要注意的是yaml配置文件不允许出现tab键,且冒号后面需要跟一个空格。配置文件编写完成后,可以在yaml lint网站验证是否有格式错误。
  • kingshard会响应SIGINT,SIGTERM,SIGQUIT这三个信号,平滑退出。在部署kingshard机器上应避免产生这三个信号,以免造成kingshard非正常退出!后台运行kingshard建议使用supervisor工具

2.2  kingshard的配置

kingshard配置文件详解:

# kingshard的地址和端口
addr : 0.0.0.0:9696

# 连接kingshard的用户名和密码
user :  kingshard
password : kingshard
#kingshard的web API 端口
web_addr : 0.0.0.0:9797
#调用API的用户名和密码
web_user : admin
web_password : admin

# log级别,[debug|info|warn|error],默认是error
log_level : debug
# 打开SQL日志,设置为on;关闭SQL日志,设置为off
log_sql : on
#如果设置了该项,则只输出SQL执行时间超过slow_log_time(单位毫秒)的SQL日志,不设置则输出全部SQL日志
slow_log_time : 100
#日志文件路径,如果不配置则会输出到终端。
log_path : /Users/flike/log
# sql黑名单文件路径
# 所有在该文件中的sql都会被kingshard拒绝转发
#blacklist_sql_file: /Users/flike/blacklist
# 只允许下面的IP列表连接kingshard,如果不配置则对连接kingshard的IP不做限制。
allow_ips: 127.0.0.1,10.0.0.8,10.0.0.9
# kingshard使用的字符集,如果不设置该选项,则kingshard使用utf8作为默认字符集
#proxy_charset: utf8mb4

# 一个node节点表示mysql集群的一个数据分片,包括一主多从(可以不配置从库)
nodes :
-
    #node节点名字
    name : node1

    # 连接池中最大空闲连接数,也就是最多与后端DB建立max_conns_limit个连接
    max_conns_limit : 16

    # kingshard连接该node中mysql的用户名和密码,master和slave的用户名和密码必须一致
    user :  kingshard
    password : kingshard

    # master的地址和端口
    master : 127.0.0.1:3306

    # slave的地址、端口和读权重,@后面的表示该slave的读权重。可不配置slave
    #slave : 192.168.0.12:3306@2,192.168.0.13:3306@3
    #kingshard在300秒内都连接不上mysql,kingshard则会下线该mysql
    down_after_noalive : 300
-
    name : node2
    max_conns_limit : 16
    user :  kingshard
    password : kingshard

    master : 192.168.59.103:3307
    slave :
    down_after_noalive: 100

# 分表规则
schema :
    #分表分布的node名字
    nodes: [node1,node2]
	#所有未分表的SQL,都会发往默认node。
    default: node1
    shard:
    -
        #分表使用的db
        db : kingshard
		#分表名字
        table: test_shard_hash
        #分表字段
        key: id
        #分表分布的node
        nodes: [node1, node2]
        #分表类型
        type: hash
        #子表个数分布,表示node1有4个子表,
        #node2有4个子表。
        locations: [4,4]

    -
		#分表使用的db
        db : kingshard
		#分表名字
        table: test_shard_range
	    #分表字段
        key: id
		#分表类型
        type: range
	    #分表分布的node
        nodes: [node1, node2]
		#子表个数分布,表示node1有4个子表,
		#node2有4个子表。
        locations: [4,4]
        #表示每个子表包含的最大记录数,也就是说每
	    #个子表最多包好10000条记录。即子表1对应的id为[0,10000),子表2[10000,20000)....
        table_row_limit: 10000

这里着重说一下分表的配置规则:

  • kingshard支持两种类型的分表规则:hash和range。
  • kingshard分表涉及到的子表,需要用户在各个db手动创建好,并且格式是:table_name_%4d,也就是说子表下标由4位数组成。例如:table_name_0000,table_name_0102。
  • 所有操作未分表的SQL语句都将发送到默认节点。

三、kingshard测试

3.1  环境介绍

mysql IP server_id
master 10.0.0.6 63307
slave 10.0.0.7 73307
slave 10.0.0.8 83307

3.2  读写分离的测试

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      73307 |
+-------------+
1 row in set (0.00 sec)

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      83307 |
+-------------+
1 row in set (0.00 sec)

结论:读写分离正常,不设置权重的时候按1:1轮询

3.3  读负载均衡的测试

用户使用Mysql Proxy目的很大一部分就是为了降低单台DB的负载,将读压力分担到多台DB上。kingshard支持多个slave,不同的slave可以配置不同的读权重,权重越大分担的读请求越多。kingshard读请求负载均衡采用的是权重轮询调度算法。

大部分系统采用该算法时,都是转发SQL语句时,动态地计算出本次选取DB的序号。然后将读请求的SQL语句发送到该DB。仔细分析一下,这样做其实是没有必要的。因为DB的权重是相对固定的,不会经常变动,所以完全可以计算出一个固定的轮询序列,然后将这个序列保存在一个数组中。这样不需要动态计算,每次读取数组就可以。举个例子来说,在kingshard的node配置项中配置slave选项:slave:10.0.0.7@1,10.0.0.8@3 kingshard在读取配置信息初始化系统的时候,就生成了一个轮询数组:[0,0,1,1,1]。在kingshard中会将这个数组打乱顺序,变成:[0,1,1,0,1]。这样就避免了动态计算DB下标的问题,对性能提升有一定帮助。

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      73307 |
+-------------+
1 row in set (0.00 sec)

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      83307 |
+-------------+
1 row in set (0.00 sec)

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      83307 |
+-------------+
1 row in set (0.00 sec)

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      83307 |
+-------------+
1 row in set (0.00 sec)

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      73307 |
+-------------+
1 row in set (0.00 sec)

权重功能正常

3.4  强制读主库的测试

有时候我们对读请求的实时性要求比较高,可以强制某些读流量走主库(这个不是很友好,如果有参数可以直接配置就好了)。

在kingshard中由于读写分离的原因,select默认会发送到相应node的从库上。但是只需要在select语句中加入相应的注释项(/*master*/),就可以将select语句发送到主库。连接MySQL时需要加上-c选项,避免客户端过滤掉注释

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      73307 |
+-------------+
1 row in set (0.00 sec)

mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|      83307 |
+-------------+
1 row in set (0.00 sec)

mysql> select/*master*/ @@server_id;
+-------------+
| @@server_id |
+-------------+
|      63307 |
+-------------+
1 row in set (0.00 sec)

功能正常。

四、kingshard的其他功能

4.1  后端DB存活探测

kingshard每个node启动了一个goroutine用于检测后端master和slave的状态。当goroutine持续一段时间(由配置文件中down_after_noalive参数设置)ping不通后端的DB后,会将该DB的状态设置为down,后续kingshard就不会将sql语句发往该DB了。

4.2  白名单功能

有时候用户为了安全考虑,希望只只允许某几台server连接kingshard。在kingshard的配置文件中有一个参数:allow_ips,用于实现客户端白名单机制。当管理员设置了该参数,则意味着只有allow_ips指定的IP能够连接kingshard,其他IP都会被kingshard拒绝连接。如果不设置该参数,则连接kingshard的客户端不受限制。

4.3  SQL黑名单功能

4.3.1  黑名单功能应用场景:

  • DBA定义一些比较危险的SQL,放在SQL黑名单文件中。可以避免前端应用发过来的SQL对数据库造成危害。这种SQL有可能是开发者粗心编写的,也有可能是被SQL注入生成的SQL。例如:delete from table,这种不带where条件的SQL,会把整个表删除。
  • 在kingshard项目上线后,通过log发现存在大量某条SQL给DB造成了很大的压力。这时候可以动态地将这条SQL加入黑名单,阻止该SQL的执行,从而使数据库压力降低。例如:select count(*) from table where xxxx,这类SQL如果没有优化得当,是很容易造成系统的IO过高的。

4.3.2  功能介绍

在kingshard如果想使用SQL黑名单功能,只需要在配置中添加:

blacklist_sql_file: /Users/flike/blacklist

然后我们在blacklist定义SQL黑名单,这样kingshard在转发的时候,就会阻止黑名单中SQL的转发。

黑名单SQL以正则表达式的形式定义。对于SQL中的值用?或?+代替。为保证黑名单有效,最好手动验证一下,kingshard是否正确拦截了黑名单中的SQL。定义规则(上一条是原SQL,对应的下一条是黑名单形式的SQL)可以参考下列例子:

SELECT c FROM t WHERE id=1
select c from t where id=?

SELECT * FROM prices.rt_5min where id=1
select * from prices.rt_5min where id=?

select null, 5.001, 5001. from foo
select ?, ?, ? from foo

select 'hello', '\nhello\n', \"hello\", '\\'' from foo
select ?, ?, ?, ? from foo

select 'hello'\n
select ?

select * from t where (base.nid IN  ('1412', '1410', '1411'))
select * from t where (base.nid in(?+))

select * from foo where a in (5) and b in (5, 8,9 ,9 , 10)
select * from foo where a in(?+) and b in(?+)

select * from foo limit 5
select * from foo limit ?

select * from foo limit 5, 10
select * from foo limit ?, ?

select * from foo limit 5 offset 10
select * from foo limit ? offset ?

INSERT INTO t (ts) VALUES (NOW())
insert into t (ts) values(?+)

insert into foo(a, b, c) values(2, 4, 5)
insert into foo(a, b, c) values(?+)

CALL foo(1, 2, 3)
call foo

LOAD DATA INFILE '/tmp/foo.txt' INTO db.tbl
load data infile ? into db.tbl

administrator command: Init DB
administrator command: Init DB

use `foo`
use ?

4.3.3  功能演示

在blacklist加入如下SQL:

use ?
delete from ?
update ? set ?

连接kingshard,执行SQL显示如下:

进入库:
mysql> use test
Database changed

创建表:
CREATE TABLE `test_shard_hash_0000` (
  `id` bigint(64) unsigned NOT NULL,
  `str` varchar(256) DEFAULT NULL,
  `f` double DEFAULT NULL,
  `e` enum('test1','test2') DEFAULT NULL,
  `u` tinyint(3) unsigned DEFAULT NULL,
  `i` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

执行插入操作:
mysql> insert into test_shard_hash_0000(id,str,f,e,u,i) values(15,"flike",3.14,'test2',2,3);
Query OK, 1 row affected (0.00 sec)

mysql> insert into test_shard_hash_0000(id,str,f,e,u,i) values(7,"chen",2.1,'test1',32,3);
Query OK, 1 row affected (0.00 sec)

mysql> insert into test_shard_hash_0000(id,str,f,e,u,i) values(17,"github",2.5,'test1',32,3);
Query OK, 1 row affected (0.00 sec)

执行update操作:
mysql> select * from test_shard_hash_0000;
+----+--------+------+-------+------+------+
| id | str    | f    | e     | u    | i    |
+----+--------+------+-------+------+------+
|  7 | chen   |  2.1 | test1 |   32 |    3 |
| 15 | flike  | 3.14 | test2 |    2 |    3 |
| 17 | github |  2.5 | test1 |   32 |    3 |
+----+--------+------+-------+------+------+
3 rows in set (0.00 sec)

mysql> update test_shard_hash_0000 set i=5;
Query OK, 3 rows affected (0.00 sec)

mysql> select * from test_shard_hash_0000;
+----+--------+------+-------+------+------+
| id | str    | f    | e     | u    | i    |
+----+--------+------+-------+------+------+
|  7 | chen   |  2.1 | test1 |   32 |    5 |
| 15 | flike  | 3.14 | test2 |    2 |    5 |
| 17 | github |  2.5 | test1 |   32 |    5 |
+----+--------+------+-------+------+------+
3 rows in set (0.00 sec)

执行delete操作:
mysql> select * from test_shard_hash_0000;
+----+--------+------+-------+------+------+
| id | str    | f    | e     | u    | i    |
+----+--------+------+-------+------+------+
|  7 | chen   |  2.1 | test1 |   32 |    5 |
| 15 | flike  | 3.14 | test2 |    2 |    5 |
| 17 | github |  2.5 | test1 |   32 |    5 |
+----+--------+------+-------+------+------+
3 rows in set (0.00 sec)

mysql> delete from test_shard_hash_0000;
Query OK, 3 rows affected (0.00 sec)

mysql> select * from test_shard_hash_0000;
Empty set (0.00 sec)

验证失败。

重新在blacklist加入如下SQL

use ?
delete from ?
update ? set ?
select ?
select * from test_shard_hash_0000

连接kingshard,执行SQL显示如下:

mysql> select 'time';
ERROR 1105 (HY000): sql in blacklist.

mysql> select @@port;
+--------+
| @@port |
+--------+
|   3307 |
+--------+
1 row in set (0.00 sec)

mysql> select * from test_shard_hash_0000;
ERROR 1105 (HY000): sql in blacklist.
mysql> select * from test_shard_hash_0000 where id=17;
+----+--------+------+-------+------+------+
| id | str    | f    | e     | u    | i    |
+----+--------+------+-------+------+------+
| 17 | github |  2.5 | test1 |   32 |    3 |
+----+--------+------+-------+------+------+
1 row in set (0.00 sec)

结论:kingshard SQL黑名单功能是严格匹配的,对于很多sql支持不是很友好(例如第一次测试的那几个SQL),总体感觉这个功能比较鸡肋,不建议使用。

4.4  kingshard支持SQL的范围

4.4.1  简要说明

kingshard在非分表的情况下支持绝大部分MySQL语法和协议,包括类似SHOW DATABASES, SHOW TABLES, 以及各种DML语句和DDL语句。在分表的情况下,目前只支持有限的DML语句,主要包含:SELECT,UPDATE,INSERT,REPLACE, DELETE这五种SQL操作。并且不支持自动建子表功能。以及有限的kingshard自定义管理端命令。在分表和非分表的情况下,都不支持以下情形:

  • 暂不支持用户自定义数据类型、自定义函数。
  • 暂不支持视图、存储过程、触发器、游标。
  • 暂不支持类似 BEGIN…END,LOOP...END LOOP,REPEAT...UNTIL...END REPEAT,WHILE...DO...END WHILE 等的复合语句。
  • 暂不支类似 IF,WHILE 等流程控制类语句。 下面分两部分介绍kingshard支持SQL的情况:非分表情况下SQL支持范围和分表情况下SQL支持范围。

4.4.2  非分表情况下SQL的支持范围

数据库DDL语法

CREATE TABLE Syntax
CREATE INDEX Syntax
DROP TABLE Syntax
DROP INDEX Syntax
ALTER TABLE Syntax
TRUNCATE TABLE Syntax

数据库DML语法

INSERT Syntax
INSERT DELAYED Syntax 暂不支持
REPLACE Syntax
UPDATE Syntax
DELETE Syntax
Subquery Syntax
Scalar Subquery
Comparisons Subquery
Subqueries with ANY, IN, or SOME
Subqueries with ALL
Row Subqueries
Subqueries with EXISTS or NOT EXISTS
Subqueries in the FROM Clause
SELECT Syntax
SELECT INTO OUTFILE/INTO DUMPFILE/INTO var_name 暂不支持
Last_insert_id特性

事务的支持

START TRANSACTION, COMMIT, and ROLLBACK Syntax
暂不支持transaction_characteristic定义
暂不支持savepoint嵌套事务的相关语法
暂不支持XA事务的相关语法
支持set autocommit=0/1方式设置事务.
支持begin/commit方式设置事务
支持start transaction方式设置事务
SET TRANSACTION Syntax
暂不支持对global的事务隔离级别进行调整

预处理的支持

Prepared Statements 支持主流语言(java,php,python,C/C++,Go)SDK的MySQL的Prepare语法。

数据库管理语法的支持

SET Syntax 只支持字符集和set autocommit相关语法,其他set语法未测试过。
Show Syntax 默认show操作会转发到默认DB,需要查看其他DB的内容,通过在SQL中加注释的方式。
KILL Syntax 目前不支持KILL QUERY processlist_id
DESCRIBE Syntax
EXPLAIN Syntax
USE Syntax

数据库系统函数默认都支持(未经测试)

4.4.3  分表的情况下SQL的支持范围

 数据库DDL语法

CREATE TABLE Syntax
CREATE INDEX Syntax
DROP TABLE Syntax
DROP INDEX Syntax
ALTER TABLE Syntax
TRUNCATE TABLE Syntax

分表的情况下支持这些语法,但需要在SQL中加注释,例如: /*node1*/create table stu_0000(id int, name char(20)); 这样kingshard就会将该SQL转发到node1节点的Master上。

注: truncate如果不指定节点注释则会将所有分表都清空,例如:truncate stu

数据库DML语法

INSERT Syntax
INSERT DELAYED Syntax 不支持
INSERT INTO SELECT 不支持
REPLACE Syntax
UPDATE Syntax //分表使用的字段无论何种分表类型都不能作为被更新的字段。
UPDATE SET xx=REPLACE(xx,'a','b') Syntax 不支持
DELETE Syntax
Subquery Syntax
SELECT Syntax 对于UPDATE,DELETE和SELECT三种SQL中WHERE后面的条件不能包含子查询,函数等。只能是字段名。

数据库管理语法的支持

DESCRIBE Syntax 通过SQL语句hint方式支持,例如:/*node2*/describe table_name
EXPLAIN Syntax 通过SQL语句hint方式支持,例如:/*node2*/explain select * from xxxx
USE Syntax

分表聚合函数的支持

sum函数
max函数
count函数
min函数 不支持distinct后聚合,例如:select count(distinct id) from xxxx

分表group by,order by,limit支持

其他情形说明

不支持分布式事务,支持以非事务的方式更新多node上的数据。
不支持预处理。
不支持数据库管理语法。

参考地址:https://github.com/flike/kingshard/

为了方便大家交流,本人开通了微信公众号和QQ群,QQ群:291519319,喜欢技术的一起来交流吧

猜你喜欢

转载自my.oschina.net/u/3023401/blog/1807015