小米开源数据库中间件Gaea实战(有彩蛋)

曹东瑜 2019-07-10 09:45:35

​本文根据dbaplus社群第191期线上分享整理而成,文末还有好书送哦~

 

讲师介绍
 


市面上的数据库中间件有很多,如MyCAT、Atlas、Sharding Sphere、Kingshard、Vitess等等,它们各有各的特点和使用场景。

 

在公司内部,有较多的系统在使用内部基于社区1.6版改造的MyCAT,主要做分库分表和读写分离,涉及的业务包括订单,活动等核心系统和一些周边系统。

 

然而在使用过程中,开发工程师和DBA工程师都遇到了一些困难:

 

  • 内部使用的MyCAT代码改动较多,且项目代码处于“遗留代码”的状态,遇到bug时难以修复; 

  • 缺少必要的监控指标,遇到连接超时,负载过高,慢SQL等问题时,开发和DBA难以定位和排查;

  • 修改配置由DBA直接通过命令行操作,给DBA运维带来负担。

 

几经权衡,我们最终选择设计研发一款新的数据库中间件,用来替换公司内部正在使用的MyCAT。这就是Gaea最初的定位:MySQL数据库中间件,兼容MyCAT核心特性,提供读写分离,分库分表等功能。

 

综合考虑了内部的技术体系以go语言生态为主,组内开发同学的技术背景,go语言在编写网络应用方面的易用性、可靠性,我们选择用go语言实现Gaea。

 

一、概念与功能

 

Gaea是一款数据库中间件,准确的说是MySQL中间件。它实现了MySQL协议,将自己伪装成一个MySQL服务器,应用程序通过MySQL客户端访问Gaea,向Gaea发起SQL请求,Gaea将SQL请求转发到后端MySQL执行,得到响应结果后再返回给客户端。 

 

 

使用中间件可以集中管理用户和数据库配置信息,当用户和数据库实例规模增长时,可以有效减轻DBA的运维负担。

 

Gaea抽象出namespace, user, slice这几个概念。namespace对应于一个业务,是Gaea划分资源的基本单位。user基本等同于MySQL的用户,user通过username和password连接到Gaea。username和password可以唯一确定一个namespace,一个namespace可以有多个用户。用户有权限的区别,存在只读用户,读写用户。

 

slice对应于MySQL实例资源,一个slice必须包含一个主库,可以包含0-n个从库,可实现读写分离。namespace中可以包含多个slice,通过制定路由规则可实现分库分表的功能。图中绿色部分就是Gaea中的逻辑概念。

 

 

那么Gaea是如何管理以上这些配置信息的呢?这里要提到Gaea系统的3个组成部分,Gaea系统由Proxy,CC和Web组成。

 

 

上面几张图中展示的Gaea中间件准确来说叫作Gaea Proxy,负责MySQL流量接入。Gaea CC是中控服务,通过Proxy提供的管理接口与Proxy交互,主要用于配置管理和下发。

 

Gaea Web提供了一个可视化的管理控制台,用于DBA管理配置信息和开发工程师查看配置信息。Gaea Web通过操作Gaea CC来控制配置下发到Gaea Proxy。 

 

 

目前我们在生产环境中将配置数据存放在etcd中,Gaea CC和Gaea Proxy通过etcd进行配置数据交互。

 

Gaea的主要功能:

 

  • 非分片表支持大部分SQL;

  • 分片表支持MySQL路由,Kingshard路由;

  • 聚合函数支持常用的max, min, sum, count, group by, order by

  • 支持多个分片表join(需满足条件),分片表和全局表的join。

 

 

二、快速使用

 

1、编译安装
 

 

使用Gaea比较简单,源码编译需要依赖go 1.12。Gaea使用go module管理依赖,克隆代码仓库后,进入Gaea目录执行make即可下载依赖并编译生成gaea的二进制文件。

 

2、编译配置
 

 

启动Gaea Proxy需要两份配置,一份是Proxy本身配置,如监听端口,日志路径,namespace配置方式,等等。另一份是namespace配置,主要包含用户信息、DB列表、实例配置信息等。

 

在生产环境,我们采用etcd存储namespace配置,使用一个可视化管理控制台进行配置查看和变更。为方便单机使用和测试,Gaea也支持基于文件的namespace配置,这样就不需要依赖etcd了。

 

3、启动Proxy
 

 

配置好这两份配置以后,执行bin/gaea -config=etc/gaea.ini,进程启动后查看默认监听端口13306如果正常监听,则说明启动成功。

 

4、发送请求
 

 

使用MySQL客户端用namespace中的用户名和密码,即可像连接到MySQL一样连接到Gaea。连接到Gaea后可以执行常用SQL,对于非分片表来说,大部分SQL都是支持的。

 

分片表目前仅支持DML和一些常用管理命令,对分片表执行DDL会在Gaea层面报错,请求不会发往后端MySQL实例。

 

5、查看监控
 

 

Gaea提供了较为完善的监控指标,方便开发和DBA根据监控指标排查问题。目前内部使用prometheus作为监控后端,用grafana作数据展示。

 

 

监控项主要包含了两个方面,一个是业务层面也就是SQL请求,主要包括QPS、请求耗时、会话数、慢SQL、错误SQL等指标。

 

另一个是机器层面,主要包括CPU使用率、内存使用率、协程数、GC耗时等指标。业务监控指标采用namespace和proxy两个维度,方便从不同的维度进行统计,从而获得更为直观的监控数据。

 

 

除了grafana监控面板之外,Gaea还提供了SQL反查功能。通过grafana面板上获取到的SQL范式的MD5,用户可以查看自己namespace下面的慢SQL和错误SQL的具体的SQL范式,方便用户定位问题。

 

三、实现细节

 

1、整体架构
 

 

Gaea的整体架构比较简洁,从上到下包括协议解析、会话管理、SQL解析、路由调度、SQL执行这几个模块。

 

 

用户建立会话后,通过MySQL协议与Gaea交互,并且该会话接受Gaea的管理。发起的SQL请求经协议解析后得到具体的SQL,经过SQL解析器解析成语法树,交由路由调度层做进一步处理,并得到最终发往后端执行的SQL,也就是Gaea的执行计划。

 

SQL执行模块根据执行计划,从后端MySQL连接池中拿到对应实例的连接,执行SQL并获取执行结果。如果是分片表的SQL,还需要对多个执行结果做聚合。最后将执行结果按照MySQL协议返回给客户端。

 

 

2、网络协议
 

 

Gaea支持MySQL协议,包含MySQL文本协议和MySQL二进制prepare协议。文本协议的实现主要参考了另外一款开源数据库中间件Kingshard,二进制prepare协议则是按照MySQL的官方文档实现。

 

 

二进制prepare协议由前端会话保存prepare的stmt,执行时首先由客户端发起COM_STMT_PREPARE请求,Gaea处理请求成功后会返回给客户端StmtId,随后客户端再发起COM_STMT_EXECUTE请求,Gaea会用参数替换SQL中的占位符,然后以文本协议向后端MySQL发起请求并得到结果集,再将结果集转换成二进制协议后写回客户端。

 

 

3、SQL执行
 

 

Gaea使用TiDB的SQL解析器解析SQL。也有一些其他开源项目在使用该解析器,如小米云平台开源的SQL优化工具soar。

 

 

当时我们调研了Vitess, Kingshard, TiDB这几款中间件的解析器,发现Vitess和Kingshard的解析器处理复杂SQL会遇到问题,解析得到的语法树抽象以及提供的语法树接口在实现分库分表的路由计算和SQL改写时会比较复杂。

 

TiDB的解析器SQL兼容性较好,能够满足我们的使用需求,解析得到的语法树接口提供了一个Restore()方法,方便对分片表SQL进行改写。

 

计算路由需要使用SQL解析后得到的语法树 (AST)。我们借助AST提供了Visitor机制,根据SQL中存在的表名判断是否包含分片表。如果只要包含一个分片表,就会走到分片表逻辑,计算路由,改写SQL。

 

而如果不包含任何一个分片表,则将该SQL直接发往默认slice执行。计算路由时,只需要找到对应的AST Node,改写SQL时只需要把对应的Node替换成一个装饰器Node,这样只需要对原AST的根节点调用Restore(),即可得到改写后的SQL。

 

 

 

4、连接管理
 

 

Gaea的客户端会话支持超时清理,默认清理1小时内无任何请求的会话。超时清理采用时间轮实现。

 

后端与MySQL实例的连接采用连接池实现,连接池通过空闲连接数、最大连接数、空闲时间、超时时间等几个参数进行配置。

 

连接池机制可以保证后端MySQL的连接数可控,即使前端有大量的长连接,也不会对后端MySQL造成不良影响。

 

Gaea连接池支持从库负载均衡和容错,当某台从库宕机,获取连接失败时,Gaea会自动找到下一台从库获取连接,当所有从库均失败时,Gaea会从主库获取连接。

 

这里有一点需要注意,一些session级别的系统变量设置需要保证前后端连接一致。

 

Gaea处理session变量是拦截前端的SQL请求,将这些值保存到session中,当发起SQL查询请求,获取后端连接执行时,拿到后端连接后先将session中的这些变量拼接成一个SET SQL执行一次,再执行SQL查询。

 

目前Gaea仅支持有限的几个session变量,后面根据需要可能还会考虑增加。

 

 

5、配置热加载
 

 

对数据库中间件来说,配置热加载可以说是一个强需求。面对多租户场景,大量的namespace配置需要在线修改,如果每次修改都需要重启才能生效,会给DBA带来非常大的运维负担。Gaea在设计时考虑到以上问题,实现了namespace级别的配置热加载。

 

配置热加载过程分为3个阶段,对Gaea Proxy来说分为两个阶段。

 

 

第1阶段,修改etcd中的namespace配置。第2阶段,向集群中的所有Gaea Proxy发起prepare请求,触发Gaea Proxy读取etcd中新的namespace配置,并初始化namespace的资源。如果所有prepare请求均成功, 则可以进入第3阶段commit。

 

 

Gaea Proxy以无锁方式切换到新的namespace,并在延迟1分钟后关闭旧的namespace。这主要是考虑到有一些长耗时的SQL请求还未完成,延迟1分钟尽量保证这些请求能够完成。配置热加载的过程对前端会话来说是无感知的,最大限度降低配置修改对业务的影响。

 

四、未来规划

 

Gaea从去年下半年开始立项,开发,内部上线,到今年5月份开源,相比其他老牌的数据库中间件来说还比较年轻,功能上还有待完善,一些细节还需要打磨。

 

我们初步计划在未来支持事务追踪功能,分布式事务功能,进一步扩宽Gaea的应用场景。内部使用的Gaea Web也会在适配后尽快开源,方便大家通过可视化界面管理和操作Gaea。

 

另外一个重要组件Gaea Agent目前还在内部开发中,主要用于MySQL实例管理,分片表自动扩容,缩容等操作,相信在不久的将来也会开源。

 

细节方面,目前有些SQL还不能支持,未来也会不断改进和完善,这里也希望大家在使用Gaea遇到问题时可以反馈给我们,我们会尽力解决。

 

性能也是另一个需要不断改进的方面,在内部使用sysbench初步测试结果来看,目前Gaea Proxy的性能在点查询场景QPS比MyCAT高20%左右,虽然我们已经在协议层,SQL执行层做了一部分优化,但一定还有性能提升的空间。

 

Gaea项目现已开源,并且我们内部使用的版本与开源版本完全一致。因此该项目会长期维护,欢迎大家试用,目前已经有社区的伙伴为Gaea贡献代码,期待您的参与。

 

Github地址:https://github.com/XiaoMi/Gaea

 

互动问答
 

 

 

Q1:Gaea对库进行分库后最多支持多少次跨库的表join?

 

A1:路由规则相同的分片表,支持任意join,改写表名的SQL会转发到对应的分片上。跨库join目前是不支持的。

 

Q2:怎么将分片表聚合查询呢?

 

A2:普通查询,将多个分片上的结果集合并就行。带聚合函数的查询,需要对聚合列计算后返回。

 

>>>>

直播回放

https://m.qlchat.com/topic/details?topicId=2000005152447834

彩蛋来了



在本文微信订阅号(dbaplus)评论区留下足以引起共鸣的真知灼见,小编将在本文发布后的隔天中午12点根据留言精彩程度选出1位幸运读者,送出以下好书一本~

注:同一月份里,已获赠者将不可重复拿书。

 

特别鸣谢博文视点为活动提供图书赞助。

活动预告