Kingshard架构设计和功能实现
文章目录
kingshard简介
kingshard是一个由Go开发高性能MySQL Proxy项目,kingshard在满足基本的读写分离的功能上,致力于简化MySQL分库分表操作;能够让DBA通过kingshard轻松平滑地实现MySQL数据库扩容。 kingshard的性能是直连MySQL性能的80%以上。
主要功能:
1. 基础功能
- 支持SQL读写分离。
- 支持透明的MySQL连接池,不必每次新建连接。
- 支持平滑上线DB或下线DB,前端应用无感知。
- 支持多个slave,slave之间通过权值进行负载均衡。
- 支持强制读主库。
- 支持主流语言(java,php,python,C/C++,Go)SDK的mysql的prepare特性。
- 支持到后端DB的最大连接数限制。
- 支持SQL日志及慢日志输出。
- 支持SQL黑名单机制。
- 支持客户端IP访问白名单机制,只有白名单中的IP才能访问kingshard(支持IP 段)。
- 支持字符集设置。
- 支持last_insert_id功能。
- 支持热加载配置文件,动态修改kingshard配置项(具体参考管理端命令)。
- 支持以Web API调用的方式管理kingshard。
- 支持多用户模式,不同用户之间的表是权限隔离的,互不感知。
2. sharding功能
- 支持按整数的hash和range分表方式。
- 支持按年、月、日维度的时间分表方式。
- 支持跨节点分表,子表可以分布在不同的节点。
- 支持跨节点的count,sum,max和min等聚合函数。
- 支持单个分表的join操作,即支持分表和另一张不分表的join操作。
- 支持跨节点的order by,group by,limit等操作。
- 支持将sql发送到特定的节点执行。
- 支持在单个节点上执行事务,不支持跨多节点的分布式事务。
- 支持非事务方式更新(insert,delete,update,replace)多个node上的子表。
kingshard架构设计和功能实现
1. 整体架构
kingshard采用Go开发,充分地利用了Go语言的并发特性。Go语言在并发方面,做了很好的封装,这大大简化了kingshard的开发工作。kingshard的整体工作流程如下所述:
- 读取配置文件并启动,在配置文件中设置的监听端口监听客户端请求。
- 收到客户端连接请求后,启动一个goroutine单独处理该请求。
- 首选进行登录验证,验证过程完全兼容MySQL认证协议,由于用户名和密码在配置文件中已经设置好,所以可以利用该信息验证连接请求是否合法。当用户名和密码都正确时,转入下面的步骤,否则返回出错信息给客户端。
- 认证通过后,客户端发送SQL语句。
- kingshard对客户端发送过来的SQL语句,进行词法和语义分析,识别出SQL的类型和生成SQL的路由计划。如果有必要还会改写SQL,然后转发到相应的DB。也有可能不做词法和语义分析直接转发到相应的后端DB。如果转发SQL是分表且跨多个DB,则每个DB对应启动一个goroutine发送SQL和接收该DB返回的结果。
- 接收并合并结果,然后转发给客户端。
kingshard工作整体流程可参考下面这幅图。

kingshard整体架构图如下所示

2. 词法和语义分析
要将kingshard的功能做的足够强大,就不得不进行SQL的词法和语义分析。SQL语句的词法分析指的是将SQL语句切分成一个一个的关键字。例如对SQL语句:select name from stu where id < 13进行词法分析,得到的结果是:{"select","name","from","stu","where","id","<","13"}。这样做的目的主要为了生成一棵抽象语法树,也就是大家常说的AST(abstract syntax tree),语义分析就是基于这棵语法树来操作的。语义分析的目的主要有以下几个方面:
- 读写分离,只有识别出SQL语句的类型,才能进行正确的读写分离操作。
- 数据分片,解析出表名和查询条件,将SQL路由到正确的DB。
- SQL黑名单,通过词法和语义分析,也可以快速识别出需要屏蔽的SQL语句。例如,检测到delete语句不带where操作,就可以直接阻断该SQL的转发。
kingshard并没有考虑完全兼容MySQL所有语法,因为完全兼容MySQL语法会使得词法和语义分析模块变得异常复杂,而且低效。对于DDL语句其实没必要解析,只要能正确转发到后端相应的DB上就可以。
kingshard只对部分DML语句(select,update,insert,delete,replace)进行了解析,这样可以满足绝大部分的分表操作。对于其他语句,kingshard会将其发送到一个默认的DB,或者通过kingshard特有的方式将其发送到指定的DB上,例如:/*node2*/insert into stu(id,name) values(12,'xiaoming'),对于这种带有注释的的sql语句,kingshard能够识别出,然后将这条sql语句发送到node2节点的Master DB上。
3. 负载均衡
用户使用Mysql Proxy目的很大一部分就是为了降低单台DB的负载,将读压力分担到多台DB上。kingshard支持多个slave,不同的slave可以配置不同的读权重,权重越大分担的读请求越多。kingshard读请求负载均衡采用的是权重轮询调度算法。
大部分系统采用该算法时,都是转发SQL语句时,动态地计算出本次选取DB的序号。然后将读请求的SQL语句发送到该DB。仔细分析一下,这样做其实是没有必要的。因为DB的权重是相对固定的,不会经常变动,所以完全可以计算出一个固定的轮询序列,然后将这个序列保存在一个数组中。这样不需要动态计算,每次读取数组就可以。举个例子来说,在kingshard的node配置项中配置slave选项:
slave:192.168.0.12@2,192.168.0.13@3
kingshard在读取配置信息初始化系统的时候,就生成了一个轮询数组:[0,0,1,1,1]。在kingshard中会将这个数组打乱顺序,变成:[0,1,1,0,1]。这样就避免了动态计算DB下标的问题,对性能提升有一定帮助。
4.sharding实现
首选需要介绍两个概念:
-
子表,在kingshard中一张逻辑上的大表由若干张小的子表组成。例如:将stu表分成stu_0000,stu_0001,stu_0002,stu_0003。 在数据库中stu表是不存在的,它只是一张逻辑上的表。数据库中只存在四张子表(stu_0000,stu_0001,stu_0002,stu_0003)。 发送SQL语句时,kingshard会识别出需要分表的SQL语句,并改写该SQL。例如,客户端发送过来的SQL语句是
:select name from stu where id =10;kingshard收到该SQL语句后,从配置信息中识别出该表是一个Hash类型的分表。根据分表规则,将该SQL改写成:select name from stu_2 where id =10;然后发送给对应的DB。 -
Node,子表分布在各个node上,每个node包含一个maser server和若干个slave server(slave个数可以为0)。写请求会发往该node中master server,读请求会发往该node中的slave server。
kingshard的sharding采用了两级映射的思想,首选根据SQL语句的分表条件计算出这条SQL语句落在哪个子表上,然后再根据配置信息找到该子表落在哪个node上。采用两级映射的思想,对于MySQL的扩容和缩容都能很方便地支持。目前kingshard sharding支持insert, delete, select, update和replace语句, 所有这五类操作都支持跨子表。但写操作仅支持单node上的跨子表,select操作则可以跨node,跨子表。
对于有些表没有分表,操作该表的SQL语句会发往default node。或者用户可以选择在SQL语句前面加上注释,指定该SQL要发往的node,kingshard接收到语句后,识别出注释中指定的node,然后将该SQL发往对应node中合适的DB。例如用户发送/*node1*/select * from member where id=100,kingshard接收到该SQL后会将其发送到node1的salve上。这样kingshard就能很好地兼容分表和不分表的各种应用场景了。
5. 事务的实现
所有proxy支持shard后都会面临一个问题:支不支持分布式事务?出于性能和可用性考虑, kingshard只支持单台DB上的事务,不允许跨DB的事务。kingshard处理单DB上的事务流程如下:
- 用户发送begin语句。
- kingshard接收到SQL语句后,将该连接的状态设置为事务。
- 用户发送DML语句,kingshard识别出语句需要发送到的DB,然后kingshard新建一个与后端DB的连接,利用该连接发送语句。
- 收取SQL语句的结果后,将连接放回。
- kingshard收到下一条SQL语句,判断该SQL是不是发往同一个DB,如果不是则报错。如果是发往同一个DB,则利用该连接发送语句。
- 收到用户发送的commit语句,将该连接的状态设置为非事务,事务结束。
6. 后端DB存活检测
kingshard每个node启动了一个goroutine用于检测后端master和slave的状态。当goroutine持续一段时间(由配置文件中down_after_noalive参数设置)ping不通后端的DB后,会将该DB的状态设置为down,后续kingshard就不会将sql语句发往该DB了。
7. 客户端白名单机制
有时候用户为了安全考虑,希望只只允许某几台server连接kingshard。在kingshard的配置文件中有一个参数:allow_ips,用于实现客户端白名单机制。当管理员设置了该参数,则意味着只有allow_ips指定的IP能够连接kingshard,其他IP都会被kingshard拒绝连接。如果不设置该参数,则连接kingshard的客户端不受限制。
8. 管理端设计和实现
kingshard的管理端口复用了工作端口,通过特定的关键字(admin)来标示。kingshard是通过对管理端特定的SQL进行词法和语义分析,将SQL语句解析为一条kingshard可以识别的命令。目前支持平滑上下线master和slave,和查看kingshard配置和后端DB状态。后续打算将web页面集成到管理端,这样用户就可以不用输入命令行操作,而是在网页上操作。大大降低用户使用kingshard的门槛。
kingshard性能优化网络篇
1. 发现kingshard的性能瓶颈
首选,对kingshard进行性能优化,我们必须要找到kingshard的性能瓶颈在哪里。Go语言在性能优化支持方面做的非常好,借助于go语言的pprof工具,我们可以通过简单的几个步骤,就能得到kingshard在转发SQL请求时的各个函数耗时情况。
1.1 环境搭建
根据kingshard使用指南搭建一个kingshard代理环境。我是用macbook搭建的环境,硬件参数如下所示:
|
|
1.2 性能测试步骤
具体步骤如下所述:
1.获取一个性能分析的封装库
|
|
2.在工程内import这个组件
3.在kingshard/cmd/kingshard/main.go的main函数开始部分添加CPU监控的启动和停止入口
|
|
4.重新编译工程, 运行kingshard
|
|
5.kingshard启动后会在终端输出下面一段提示:
|
|
后面的路径就是pprof性能分析文件的位置,Ctrl+C中断服务器
6.这时候用sysbench对kingshard进行压力测试,得到QPS(有关sysbench的安装和使用,请自行Google解决)。具体的代码如下所示:
|
|
得到如下结果:
|
|
-
按照上述步骤测试三次(16552.58,16769.72,16550.16)取平均值,得到优化前kingshard的QPS是:16624.15
-
按照上述步骤,直连MySQL。测试直连MySQL的QPS,同样测试三次QPS(27730.90,28499.05,27119.20),得到直连MySQL的QPS是:27783.05。
-
从上述数据可以计算出kingshard转发SQL的性能是直连MySQL的59%左右。
7.将cpu.prof拷贝到bin/kingshard所在位置
8.调用go tool工具制作CPU耗时的PDF文档
|
|

2. 性能测试报告分析
通过上述命令,可以生成压测期间主要函数耗时情况。从report来看,主要的耗时在TCP层数据包的收发上面。那我们应该主要考虑如何优化TCP层数据的收发方面。优化TCP传输效率,我首先想到了减少系统调用,每个数据包传输尽量多的数据。
在通过 TCP socket 进行通信时,数据都拆分成了数据块,这样它们就可以封装到给定连接的 TCP payload(指 TCP 数据包中的有效负荷)中了。TCP payload 的大小取决于几个因素(例如最大报文长度和路径),但是这些因素在连接发起时都是已知的。为了达到最好的性能,我们的目标是使用尽可能多的可用数据来填充每个报文。当没有足够的数据来填充 payload 时(也称为最大报文段长度(maximum segment size) 或 MSS),TCP 就会采用 Nagle 算法自动将一些小的缓冲区连接到一个报文段中。这样可以通过最小化所发送的报文的数量来提高应用程序的效率,并减轻整体的网络拥塞问题。
由于这种算法对数据进行合并,试图构成一个完整的 TCP 报文段,因此它会引入一些延时。但是这种算法可以最小化在线路上发送的报文的数量,因此可以最小化网络拥塞的问题。但是在需要最小化传输延时的情况中,GO语言中Sockets API 可以提供一种解决方案。就是通过:
|
|
这个函数在Go中默认情况下,是设置为true,也就是未开启延迟选项。我们需要将其设置为false选项,来达到每个数据包传输尽量多的数据,减少系统调用的目的。
2.1 代码修改和性能测试
发现了性能瓶颈以后,修改proxy/server/server.go文件中的newClientConn函数和backend/backend_conn.go中的ReConnect函数,分别设置client与kingshard之间的连接和kingshard到MySQL之间的连接为最小化传输延时。具体的代码修改可以查看这个

修改后我们利用sysbench重新测试,测试命令和上述测试一致。得到的结果如下所示:
|
|
测试三次得到的QPS为:21291.68,21670.85,21463.44。 相当于直连MySQL性能的77%左右,通过这个优化性能提升了18%左右。
总结
通过这篇文章,介绍了通过Go语言提供的pprof对kingshard进行性能分析的详细步骤。对于其他Go语言项目也可以通过类似步骤生成性能报告文档。性能优化的关键是发现性能瓶颈,再去找优化方案。有时候简单的优化,就可以达到预想不到的效果,希望本文能给Go开发者在性能优化方面提供一个思路。
kingshard的性能测试报告
1.测试环境
1.1服务器配置
| 类别 | 名称 |
|---|---|
| OS | 云主机 Ubuntu 14.04 LTS |
| CPU | Common KVM CPU @ 2.40GHz *4 |
| RAM | 8GB |
| DISK | 500GB |
| kingshard | master分支 |
| Mysql | v5.6.25 |
| Sysbench | v0.5 |
2.性能需求
- 测试通过kingshard转发SQL请求与直连DB发送SQL请求这两种情形下的性能差距。
- 测试配置文件中max_conns_limit参数对kingshard的影响,并找出最优值。
3.测试准备工作
3.1 kingshard性能测试环境搭建
利用上述配置的4台服务器搭建了一个kingshard性能测试环境:
- 服务器A运行着sysbench
- 服务器B运行着kingshard系统
- 服务器C运行着主库
- 服务器D运行着从库
四台服务器处在同一个网段中。具体的拓扑图如下所示:

有关kingshard系统安装与配置,请参考文档安装kingshard 有关sysbench v0.5的安装与命令选项,请参考sysbench
4.测试过程
执行下面的命令准备测试数据
|
|
上述命令会创建表sbtest1,数据量为100w,建表语句如下:
|
|
4.1 kingshard与直连DB性能比较
利用sysbench测试通过kingshard转发SQL请求和直连DB发送SQL请求这两种情况下,kingshard和Mysql系统的两项数据指标:QPS和每条SQL请求平均处理时间。
通过sysbench发送四类SQL请求:select、update、insert和delete,场景分别为只读、读写4比1和只写。
4.1.1 kingshard与直连DB只读比较
通过修改host、port执行下面的命令分别测试kingshard转发SQL或直连DB:
|
|
利用sysbench测试了并发线程个数不同的情况下,分别执行最大请求次数为100w的 select操作,通过修改–num-threads可以获得不同并发线程数。
测试连接 kingshard 和直连 DB 这两种情况下的 QPS(QPS越大,系统性能越好), 测试连接 kingshard 和直连 DB 这两种情况下的 执行sql平均时延(时延越小,系统性能越好), 每组数据重复测试三次后取平均值,具体数据对比如下表所示:

上表对应的QPS折线图如下所示:

上表对应的时延折线图如下所示:

4.1.2 kingshard与直连DB读写4:1比较
通过修改host、port执行下面的命令分别测试kingshard转发SQL或直连DB:
|
|
利用sysbench测试了并发线程个数不同的情况下,分别执行最大请求次数为100w的 select、update、insert、delete等混合操作,通过修改–num-threads可以获得不同并发线程数。
测试连接 kingshard 和直连 DB 这两种情况下的 QPS(QPS越大,系统性能越好),
测试连接 kingshard 和直连 DB 这两种情况下的 执行sql平均时延(时延越小,系统性能越好),
每组数据重复测试三次后取平均值,具体数据对比如下表所示:

上表对应的QPS折线图如下所示:

上表对应的时延折线图如下所示:

4.1.3 kingshard与直连DB只写比较
通过修改host、port执行下面的命令分别测试kingshard转发SQL或直连DB:
|
|
利用sysbench测试了并发线程个数不同的情况下,分别执行最大请求次数为100w的insert操作,通过修改–num-threads可以获得不同并发线程数。
测试连接 kingshard 和直连 DB 这两种情况下的 QPS(QPS越大,系统性能越好),
测试连接 kingshard 和直连 DB 这两种情况下的 执行sql平均时延(时延越小,系统性能越好),
每组数据重复测试三次后取平均值,具体数据对比如下表所示:

上表对应的QPS折线图如下所示:

上表对应的时延折线图如下所示:

从QPS折线图可以看出,当sysbench的并发测试线程较少时,连接kingshard和直连DB的QPS差距较大。 这主要是因为当sysbench并发线程少时,kingshard的性能没有得到充分的发挥, sysbench只有很少的线程向kingshard发送请求,此时网络延迟对QPS的影响是最主要的。
当sysbench的并发测试线程较大时,此时kingshard的性能就得到了充分的发挥, QPS的对比是连接kingshard与直连DB性能对比的真实的反应,网络延迟对QPS的影响作用就显得很小了。
从以上几个表个的比例数据来看,通过kingshard转发select请求时的QPS是直连DB时80%左右, 而update和insert请求对应的QPS则更高一些,相当于直连DB时的85%左右,甚至在并发更高的情况下直连mysql的性能低于通过kingshards转发的性能。 由此看来利用kingshard转发SQL请求带来的性能下降虽有下降,但完全可以接受,甚至高并发场景下kingshard的性能优于直连DB的性能。这也是得益于kingshard在高并发的时候,充分利用了连接池的作用,降低了高并发带来的竞争消耗。
sysbench并发线程高于512的数据并没有给出,因为直连DB已经不能正常完成测试,但是kingshard可以完成。
4.2 max_conns_limit参数对kingshard性能影响
max_conns_limit 是 kingshard 初始化时创建的与后端mysql长连接个数,这个值设置的不同对 kingshard 性能也有比较明显的影响。
我们猜测max_conns_limit除了与kingshard所在主机CPU核心数有关外还与后端mysql能接纳的连接数有关。 我们分别测试将 max_conns_limit 设置为16、32、64、128、256、512时,kingshard转发select,update和insert三类操作请求时的 QPS, SQL的混合情况为读写4比1,且sysbench的不同sql分别处于不同的事务中,具体对比结果如下所述:
上数测试对应的QPS折线图如下所示:

从kingshard处理三类SQL操作的QPS对比来看,将max_conns_limit参数设置为128左右较为合理, 高于128后通过提高max_conns_limit值并没有显著效果。
max_conns_limit参数值与kingshard所在主机核心数并没有必然的联系,与后端mysql主机可承受连接数关系密切。
5.测试结论
本文主要测试了通过kingshard转发SQL请求与直连DB发送SQL请求这两种情形下的性能差距,和max_conns_limit值对kingshard的性能影响。
ks的读写性能平均可以达到原生mysql性能的80%,一定条件下可以达到90%,随着并发数的增加甚至能超越mysql本身。
ks可以对mysql形成保护,增加了ks后,db层对外表现出可以接收更高的并发数,且执行时间长短不同的sql使用各自的资源,形成了资源隔离,mysql不会出现性能毛刺。
综合以上测试结果来看,kingshard性能表现较为优秀,并没有明显的性能下降。同时在测试中发现kingshard系统属于CPU密集型任务,相对于磁盘IO和内存占用率而言,kingshard对CPU消耗显得最为明显,所以建议在部署kingshard的时候需要优先考虑服务器的CPU性能。
转载:https://github.com/flike/kingshard/blob/master/README_ZH.md
文章作者 Forz
上次更新 2020-03-24