3月14日,爱可生开源社区联合IT168发布了一期《MyCat的坑如何在分布式中间件DBLE上改善》的直播,根据反馈,现将直播内容节选成文,以供你们回顾重温。算法
Tips:考虑到你们的不一样口味,开源社区官网上线了完整版录播视频,不管是喜欢文字,爱好图文,青睐于完整版视频的同窗都能找到本身喜欢的打开方式!数据库
直播视频回顾请点击“阅读原文”,一键直达。后端
“ 如下为分享内容的正文部分 ”数组
背景安全
近年来,随着移动互联网、物联网、人工智能等技术的兴起,须要处理的数据愈来愈多,做为存储架构核心的关系型数据库不可避免的引起了须要扩容的问题,在这个过程当中分库分表被发明出来。session
分库分表最初不须要中间件,由各自应用的开发人员本身来负责,应用除了要了解业务逻辑之外,还须要明确完整的拆分规则,成本较高,对开发人员要求也很高,而且不利于任务和逻辑的解耦。所以,中间件应运而生。架构
分布式系统架构基本分红三层,最上面是一层是APP层,中间是中间件层,下面是数据存储层。app
今天分享的内容主要为中间件,那么一个理想的中间件应该是什么样的?运维
第一,透明性,理想的中间件会向应用开发人员屏蔽后面具体拆分的细节。数据存储的工做被独立出来,应用开发人员能够更关注业务逻辑而不是存储方式。分布式
第二,兼容性,理想的中间件最好是不要自定义一套规则,而是去兼容当前你们熟悉的规则,对咱们来讲,这个熟悉的规则就是MySQL。因此中间件的语法也好,协议也好,对于使用者来讲,最好的是用户使用时就像使用原生MySQL同样,而不是须要花很长时间学习一套新的规则,不然不管是学习成本或者迁移成本都很高。
兼容性还有一个好处,现有的JDBC或者是其余的一些驱动均可以用,不须要再去定制开发一个驱动。
第三,性能,通常对性能的考量是延迟和吞吐量。由于中间件多了一层,单个查询的response会多一个RTT延迟,因此延迟方面不必定有优点。主要看吞吐量是否是变得比原来更强。
第四,安全,不能由于有了中间件而将原来无缺的密码管理规则变成名不副实的存在,这种作法也是不妥的。
最后,运维性,好比与中间件配套的备份扩容工具等,这方面组件也是很重要的。
开源社区里MyCat的是比较著名的。咱们深度研究了MyCat,加上咱们在分布式中间件上既有的一些经验,结合起来,就是造成了咱们新的一个分布式中间件DBLE。DBLE的结构大体如图,内部主要有协议,解析,路由和运算模块。
那么DBLE跟MyCat相比解决了哪些问题?如下将从DBA与研发两个方面介绍:
1.DBA的角度,站在DBA的角度,如何实现他们并非太关心,对能用,好用十分关注,即:正确性,安全性,稳定性,可运维性等。本次分享主要关注于正确性,由于这是最大的坑,其余方面鉴于时长有限,不在此次分享中详细讲述了。
2.开发测试的角度,从开发测试的角度来看最关注的是代码质量,是否可维护,代码管理是否科学,可否持续报纸质量,保证项目健康发展。
首先咱们从DBA角度分享一下在MyCat上踩的坑,固然,这些坑DBLE都填了,具体的实现方式欢迎你们关注咱们正在陆续释放的公开课,会有更多的内容揭秘。
一.DBA角度看中间件
咱们主要从两方面来讨论,一部分是SQL语言实现:包括select,insert,set等语句来讲明正确性的问题,另外一部分将举个运维管理的例子来讲明安全性的问题。
1. SQL语言实现
如下案例都采用最新版的MyCat 1.6.7举例,在此以前分享过的一些MyCat的bug和坑,这次查看已经修了一部分,不过坑仍是太多。
1.1数据查询
简单查询对比案例
从拆分规则来看,最经常使用的hash拆分,用ID值对1024求模,求出的结果0~1023按照每256个数拆分,拆成4份,0~255在结点1;256~511在结点2,以此类推。咱们准备用10条数据,覆盖到各个分片上,都经过中间件写入。
准备完成以后,用查询语句案例select * from employee where id between 511 and 1791 order by id,加order by主要是为了更容易看出问题。
如图所示,在查询结果中MyCat丢了三条数据,缘由是由于计算路由错误。像这样大范围查询的SQL应该下发给全部后端结点,而实际上MyCat下发的少了。
聚合函数查询案例
聚合函数查询案例的准备数据与简单查询相似,在此不赘述了,咱们计算出ID的方差,能够看到MyCat返回的是四个数,而且这4个数不管如何也不可能捏回标准差,而DBLE的结果是正确的。固然,有同窗验证的话会发现有细微的精度偏差,这是由于二进制存储会损失一些精度,分布式的算法又会损失一些精度,所以会有精度上的偏差。
数据查询-函数嵌套查询案例
继续举例,准备数据不变,SQL变成了复杂一点的表达式,对count的结果取绝对值。能够看到MyCat是支持count的,可是前面去嵌套了一个其余的函数,MyCat就不认识了,它把整个语句下发给各个节点,而后对各个节点作了简单合并,这个合并无加起来,只是简单的堆积在一块儿,而后回到了应用;而DBLE结果正确无误。
数据查询 - union 查询案例
union查询案例的结果,数据准备如图是简单的两张表,一张表hotnews分为四个节点,规则也很简单,就是对四求模,按照求模的结果拆分到了四个节点上。另外一张表travelrecord稍微复杂一点,是两个节点,它的规则是按1024次求模,而后0到511分到第一个节点,512到1023分到第二个节点。第一张表是四行数据,第二张表是五行数据。这个例子已经能说明问题了,现实生活中状况可能更复杂一些。
查询:select id from hotnews union select id from travelrecord 语句,即用ID作一个union,如图所示,MyCat的结果并无去重,把全部的结果都拿到了。对比之下DBLE的结果则是正确的。
数据查询 - union all 查询案例
在union all的查询案例中,MyCat的查询结果仍是和union同样。由于MyCat在union查询时是将union语句总体下发到各个节点上,而在计算时则是按照hotnews这张表来计算节点,因为MyCat只把查询下发给两个节点,拿到的结果实际上是不全的。
数据查询 - 子查询
子查询对比结果有三个,MyCat会直接hang住。看代码hang住的缘由是MyCat内部死锁。中间件在作子查询任务时,实际上是拿到子查询结果之后再拼出新的SQL来,而后再下发第二句SQL。
在这个过程当中,MyCat 固定大小的线程池被占满了,形成了死锁,而DBLE结果仍是正确的。
数据查询 - Join案例
重点讨论一下Join,MyCat解决跨表Join的方式有3种:配置global表,配置ER表,使用hint,下面一一剖析,看看是不是真的能解决全部问题。
数据查询 - Join案例 剖析global表
对于数据量不大的字典表能够采用global表。举例,超市的几十万商品表,销售详单很是多,拆表时每每选择拆数据最多的销售详单表,假设按照日期,将销售详单拆分,按天将详单表拆成N片,在每一片的schema中有一个全量的商品表,即全局表。
当进行销售详单和商品表的Join查询的时,之因此用Join,是由于详单里面只有ID没有商品名称,进行Join查询时才能拿到名称,Join查询时Join语句下发到各个节点,而各个节点上的全局表都是全量数据,所以Join能够拿到正确的数据,这就是全局表的做用。
举一个具体例子,将商品表和销售详单表经过商品ID来关联,在必定时间范围内,根据group by日期和商品名,查看订单量。
这样一句Join,由于group by中包含了拆分列,因此这条语句能够下推给全部节点,这些节点获得的结果,直接简单的进行合并,返回到客户端就是正确的数据,这是global表的正确用法。
global表能不能解决全部的问题呢?答案是不行。
举例说明,在这个case中,在query里,首先group by并非按照拆分列去分组,其次select row里面有count distinct的过程,这句SQL,若是下发到各个节点,会发生什么样的状况?
如图,第一个分片上获得的日用品和文具是一和二,第二个节点上获得的也是。
但若是把左边的图不当作拆分表,你们应该对distinct都很是熟悉,能够本身试着用group by作一下,结论应该会是日用品一文具三,经过两个节点获得的结果分别是一和二,不管怎么合并,也没法合出第三个这样的结果。
因此这就是global表解决不了的问题,当碰到这样的查询时global表就没法解决,所以它不能解决全部问题。
数据查询 - Join案例 剖析ER表
ER表能够简单地理解为两张表有逻辑外键关系,按照这列来拆分,几张表均可以按照一样一个规则拆分。涉及到了关联列的Join,也能够一样下发到各个节点上。
注意,外键列须要依赖于拆分列,不能有拆分列和外键列是1比N的关系。
再举例,按照销售单的日期拆分,流水号和日期有一一对应关系,不会出现一个流水号有两个日期。根据流水号去拆分另外一张表,拆分完以后,若是这两张表经过流水号关联作Join,能够直接到下发到各个节点。
ER表是万能的吗?
假如不是全部表的关联关系都是同一列,当关联关系比较复杂,A表和B表是经过关联列COLUMN1来关联,B表和C表是经过COLUMN2来关联,会发现不管用哪一种方式去作拆分,都没法获得一个完美的拆分方案,必定会有一张表被打散。
打散以后再作Join,就又回到了跨节点Join的问题。
跨节点Join的问题,把语句直接分发到各个节点是不正确的。
所以,ER表也不能解决全部问题。
数据查询 - Join案例 剖析Hint
MyCat解决跨表Join的第三个方法:注解。
举例说明,A表和B表在作Join的时候,前面加了一部分hint,在里面写好用哪一个类来处理。
这其实就是next loopJoin的方式。若是经过MySQL的general log,或是根据debug去调试,就会发现这句Join在MyCat解析之后是分红两句下发的。
先从第一张表中select出结果集,再按照关联关系把结果集放在第二个表中拼接成新语句,而后再下发第二句SQL,MyCat实际是这样一个过程。
MyCat这种操做方式存在什么问题?
第一,解决不了多于两个表Join的问题。
第二,没法解决复杂Join语句的问题,只能解决A.id等于B.id这两个表格列关系直接相等的状况,稍微改变形式就不行。
第三,侵入性。应用的开发须要在每一个Join下的每一个查询前拼接这样一个hint,而且须要改应用,侵入性比较强。
因此hint表也解决不了全部的问题。
有趣的是MyCat 1.6.5以后,将hint方式直接固化到代码里,这样的处理方式实在不像是工程级别的代码,反而会引入更多的问题。
举例说明: 这个Join内部其实偷偷在代码中加了hint,若是是MyCat 1.6.1版本,直接结果不争取,加了hint之后有部分改善。根据测试, MyCat的反馈结果并不稳定,有时会返回NP异常而且这个NP异常会影响当前session的正确性。
将SQL语句调整为查询:select a.id,a.description,b.title from travelrecord a inner join hotnews b on a.id =b.id;,B.id变成B.id+1,这句SQL,就没法返回正确结果了。受到前一个例子的影响,MyCat的查询结果很是不稳定,即便使用新的链接,也会只返回空集,由于MyCat自己只是把hint固化到代码里,并无良好的跨表Join的实现。
Tips:
MyCat的内部实现十分粗糙,它判断是否要本身加hint采用的依据是拆分关的规则不同。可是是否能作成ER关系有2个条件,是拆分规则以及分片结点的彻底一致。
若是拆分规则相同,结点或结点顺序不一样,返回来也是空集,此处就不举例说明了,感兴趣的同窗可自行尝试验证。
1.2 数据操做
DBLE与MyCat的Insert对比
在Insert的处理上MyCat的insert必须将列名彻底写清楚,不然会报列名没有提供。而DBLE则更良好的兼容了MySQL的语法。
MyCat某些时候会报告不正确的返回,好比insert拼写错误,它报错不会是语法错误,而是默默经过SQL语句,若是不仔细看行的影响数甚至都没法发现拼写错误。
MyCat的全局序列自定义了一个语法,必须是nextvalue for sth才能够插入。
这个语法,对应用的业务开发者而言侵入性是很是强的,须要对应用作不少没法兼容的改造。
一样是全局序列,DBLE的实现则比较优雅,支持不带自增列的插入,由中间件来生成自增列数据。
1.3 DBLE与MyCat的上下文变量
除了select和insert,如下将再列举部分系统变量的例子。
如图,表格中原来包含4条数据,现插入一行数据,而后将session的状态设置为只读,显示再继续增长一条数据也能够经过。
虽然可以经过select筛选出来,但实际上MyCat对于set read only并不支持而且没有任何报错。若是事先并无了解MyCat这个功能缺陷或进行测试,这个问题是很难被发现的。
一样的案例,在DBLE中设置为只读后,再插入数据DBLE将会报错,如此才真正符合设置session级别变量的含义。
MyCat为何会出现这种状况?
再举一个有趣的例子,如图MyCat对于 set you =me,set 1=2 也返回OK,彷佛无所不能。 而DBLE则会诚实的告诉你,这个变量不支持。
在使用过程当中,若是存在不当心写错的状况DBLE会提供明确的报错,而MyCat什么set都返回ok的问题根因后面将详述。
2.运维管理-用户权限
以管理端用户权限为例,任何数据库用户均可登陆MyCat管理端进行高级操做,如:服务下线,修改配置等。由于缺少对用户的分级,致使应用开发者本应只能进行查询或DML等基本权限,但却也能够进行服务下线相似的不安全操做,究其根源是项目开发者没有从权限管理的角度思考问题,也埋下了安全隐患。
在DBLE中,咱们将此问题进行改进,对不一样用户进行划分,普通用户不能直接登陆管理端口进行操做,如图所示,普通用户尝试管理端口会遭到拒绝,更有利于安全。
以上的诸多案例都是站在DBA的角度来验证MyCat的正确性及其存在的问题,做为MyCat的加强版,DBLE更多的以使用者的视角对一款中间件应当具有的正确性,安全性,稳定性,可运维性等方面进行了深度系统性的考量并持续完善相应功能特性,同时,咱们也吸收经验对MyCat既存的问题也进行了加强与改进。
二.开发者角度
下面将从开发者的角度来分析MyCat的代码质量,让你们对于这个开源项目有更充分的认识。
归纳而言,MyCat存在如下四个问题:
代码修复质量差
代码半成品残留
部分提交者有灌水嫌疑
伪造实现
1.bug修复质量
首先,bug修复质量。MyCat bug #1194:在旧内存管理模式下,查询两个avg,会报超索引超出界限异常。
上图为MyCat bug #1194在GitHub上的截图,bug提供者发现bug和重现bug,包括描述bug的逻辑都很是正确,实际上在for循环里删除了数据元素,而后致使下一个去处理的时候报错越界。
在修复上,如图,红色部分为删除的代码,绿色部分为对应增长的代码,仔细观察可发现中间部分被注释起来,没有实际做用,最关键的部分在最下方,仍然是在for循环中remove某一个索引的值。
为何这个修复结果倒是修复成功?
细敲其逻辑,其实是不正确的。缘由在于for循环里采用的是int类型的包装类,此时从数组中remove的不是某个索引的值,而是remove这个包装类对象,数组中根本不存在这个对象,所以实际上没有remove任何内容,而真正生效的是标记黄色的部分,将它的size减了一。
这种操做歪打正着,好比原有四个数组,正常状况下是将第三和第四数组remove掉,但如今没有remove成功,而后经过size 4-1-1结果变成了2。这时再去遍历此数组是经过field count来遍历的,序号为第三和第四的数组尽管没有删掉,但效果却和已经删掉的相同了。
bug #1194的修复若是只进行测试会发现这个问题已经完美的解决了,可是做为开发者,咱们对代码质量进行管理时会发现这样代码的存在十分奇怪,不但难以读懂难以理解而且极可能存在:为了性能放弃包装类改为Java的基本类型、int类型,bug就会被reopen。
2.代码半成品残留
上图为MyCat启动类的部分代码截图,从图中可了解这是一个switch case语句,case=0和case等于1。其中case 0初始化了一个buffer pool,而后初始化了total buffer size,作了这两件事情;而case 1除了大段注释外,只初始化了total buffer size,并无初始化buffer pool。这会发生什么状况呢?
当pufferpooltype设置为1时,会发现MyCat启动之后,客户端根本连不上,而后日志里面也全是NP异常。做为著名开源软件,在它的启动类上就存在这样的残留代码,咱们可以相信它的质量吗?
咱们相信MyCat当初设计时应该也设计了不一样的实现,但没完成,这至少说明了没有一个固定的开发团队就没有人去处理相似很容易被发现的问题。
3.代码灌水
咱们在对MyCat作测试的时候,发现有部分代码覆盖率很低,因而去查看这部分代码实现了哪些功能。结果发现:代码质量很是高,但整个package都是从其余著名开源项目的某个版本copy过来的,固然也不算彻底copy,仍是有加部分注释的。
这部分代码除了被贡献者本身的单元测试使用外,没有被任何其余人使用。即便把整个package连带测试彻底删掉,也不影响软件的任何功能。
可能这位贡献者把MyCat项目看成本身学习笔记的笔记本或是可以展现本身贡献了不少代码?具体缘由不得而知,不过这样的代码贡献也能被合并到项目里来,实在匪夷所思。
4.伪造实现
前面咱们列举了一个较为夸张的例子,写set you = me也显示成功执行。
set语句为何会出现这种状况?从源码角度来看,MyCat枚举了几个特殊处理,好比 set names= utf8确实进行了处理。但除了枚举的几个特殊的例子,其余不管set什么,MyCat都直接返回OK,所以你会看到前面set you=me也会获得OK的结果,这对于应用端而言是至关不负责任的。
尤为是遇到真的有意义的set语句,但却没有实现其语义,很容易形成开发事故。
DBLE的自动化工具的引入
最后分享一下DBLE是如何进行代码管理和保证质量的,除了正常的review机制外,咱们引入了不少自动化的工具,包括静态代码的分析工具,用于作代码规范的工具,可持续集成工具等。社区的travis CI会自动跑单元测试,若是代码变动发生错误,那么工具就会报错,这样也能够提升代码质量。
内外部使用的工具备稍许不一样,咱们内部用的可持续集成工具是go cd,自动化的测试方面咱们用behave作了一些行为的比较测试,以后可能也会开源出来。还有测试代码覆盖率的工具,帮助咱们发现测试的薄弱环节等等。