背景java
数据库做为一个很是基础的系统,任何一家互联网公司都会使用,数据库产品也不少,有Oracle、SQL Server 、MySQL、PostgeSQL、MariaDB等,像SQLServer/Oracle 这类数据库在初期能够帮业务搞定不少棘手的事情,咱们能够花更多的精力在业务自己的发展上,但众所周知也得交很多钱。react
涉及到钱的事情在公司发展壮大之后老是会回来从新审视这个事情的,在京东早期发展的过程当中确实有一些业务的数据就是直接存在oracle或者sqlserver中。nginx
后来随着业务的发展以及数据量访问量的不断增长及成本等方面的考虑,从长远考虑须要把这些业务用免费的MySQL来存,但单机的MySQL每每没法直接抗住这些业务,天然而然的咱们就须要考虑引入分布式的MySQL解决方案帮助业务去SQLServer/Oracle以及支撑将来的发展。redis
方案选型对比及京东实现方案sql
说到分布式MySQL的解决方案通常来讲解决方案主要就两种,客户端的方案或者中间代理的方案,以下图所示。数据库
这两种方案各有各的优缺点:客户端的方案是指会给业务提供一个专门的客户端的包,这种方案在实现上会更容易一点,若是公司须要快速出一个相对通用的解决方案,客户端的方案能够优先考虑。编程
客户端方案须要为不一样的语言提供不一样的客户端的包,这点有所局限。客户端方案只须要走一段网络,理论上性能会更好一点。后端
客户端方案对业务有侵入,有一些系统部署及实现方面的可能能够控制得更好,但对业务自己不友好,客户端包升级等方面比较麻烦。性能优化
中间代理的方案是指采用一个兼容MySQL协议的代理的方式,业务可使用任何语言的MySQL客户端的包,对业务自己无侵入的,这种方案相对来讲是最友好的。网络
中间代理方案开发难度上来讲门槛会更高一点,须要考虑先后端的东西,尤为是与MySQL端交互时本身解析协议的状况下会更复杂一些。中间代理方案多走一段TCP,对性能理论上会有一些影响。
上述两种方案有一个很是重要的因素没有说起,在实际生产环境中面临一个很是现实的问题是MySQL能支持的链接数是有限的。以MySQL5.5来讲假设一个MySQL实例配置1000个链接,业务应用实例部署了100个,每一个应用实例的数据库链接池配置20个,采用客户端方案这个MySQL实例都无法正常工做了。
大多数状况下并非每一个应用实例的每条链接都是活跃的,中间代理的方案能够很好的解决这个问题,应用实例能够有不少链接打到代理上,代理只须要维护较少的与MySQL的链接便可知足需求,代理与MySQL之间的链接会被业务打过来的访问重复使用。
另外关于多走一次TCP对性能的影响,从咱们的实际经验来看其实能够忽略不计,业务实例一多优先遇到的是MySQL链接数的问题,从这个角度来讲中间代理的方案会更优。
咱们采用的就是中间代理的方案,京东的分布式MySQL方案由不少部分组成,有JManager、 JProxy、 JTransfer、JMonitor、JConsole、MySQL,在实际部署的时候还涉及到LVS以及域名系统等。
JManager是中心管理节点,这个节点负责统一管理系统的元信息,元信息包括路由信息、权限管理信息、资源相关的信息等。
JProxy就是一个兼容MySQL协议的代理,负责把客户端发送过来的SQL按照路由规则发送到相应的数据库节点上,再把返回的结果进行合并并返回给客户端。JProxy在启动的时候会先去JManager中拉取相关的元信息,并在本身的内存中维护一份,平时使用的时候只用自身内存维护的这一份就能够了。
JProxy的内部实现原理如图所示。
JTransfer是在线迁移系统,咱们针对业务的数据进行拆分之后,好比某个MySQL实例上有32个库,等到业务数据量继续增大之后在这个实例上就放不下了,咱们就须要往整个集群中加MySQL实例,将以前的32个库中一部分迁移到这个新增长的实例上,如何在不停业务的状况完成数据的在线迁移就是JTransfer这个系统来保证的。
JConsole系统能够理解为将多个业务的中心管理节点整合起来的一个后台管理控制系统,这个系统能够与每一个JManager交互。在具体使用的时候,业务方须要申请建立库表、拆分规则、什么权限、对哪些IP受权,咱们会经过JConsole系统与JManager交互完成元数据的配置。
JMonitor系统会将各个业务的jproxy以及MySQL相关的信息采集起来,整合到一块儿造成一个统一的监控系统,完成对系统的全面详尽的监控。
网络模型
JProxy做为一个很是典型的代理服务,程序自己的性能很是关键,具体在实现的时候咱们参考了Nginx的网络模型。
你们都知道Nginx的性能很是高,根据机器核数配置相应的worker数就能够,每一个worker能够理解为围绕一个epoll把先后端的链接以彻底基于事件驱动的方式串在一块儿,避免了上下文切换避免了锁等待等各类可能阻塞或者耗时的操做。
一样的网络模型也能够参考一下Redis的实现,redis虽然不像nginx须要考虑先后端链接的处理,但redis的模型也是一种很是相似的经典的实现方式。
JProxy整个网络模型如图所示,采用一个全局的nioacceptor以及多个nioreactor,由nioacceptor统一accept链接,以后把链接分给某个nioreactor。
nioreactor能够理解为底层就是一个epoll(java nio实现),先后端的链接都是注册在这个epoll上,咱们只须要根据事件是读事件或写事件调用相应的回调函数便可。这种模型的特色是系统几乎没有太多的上下文切换,并且性能很高。
基于事件驱动的网络模型的好处是性能很高,但问题也很明显,编写时复杂度很是高,一条SQL发送过来到收到结果的上下文被切成不少片断,同一时刻有来自不少不一样上下文的不一样的片断要处理,全程只有一个进(线)程来处理这些片断(暂且假设NIOReactor只配置成一个),因此在实现的过程当中要求把全部的细节都考虑很是周全,一旦某个片断的处理有阻塞或者耗时,整个程序都将阻塞,我的以为这种编程方式有点反人类思惟。
关于分布式事务的思考
另外关于分布式事务的支持也是一个你们可能比较感兴趣的点,基于MySQL的方式来作分布式数据库的时候分布式事务是不可能知足严格的分布式事务语义的。
数据库事务有ACID四个属性,分别是原子性、一致性、隔离性、持久性。
原子性(Atomicity)的意思是整个事务最终只能是要么成功要么失败,不能存在中间状态,若是发生错误了就须要回滚回去,就像这个事务历来没有执行过同样。
一致性(Consistency)是指系统要处于一个一致的状态,不能由于并发事务的多少影响到系统的一致性,举个典型的例子就是转账的状况,假设有ABC三个账号各有100元,那么无论这三个账号之间怎么转帐,整个系统总的额度是300元这一点是应该是不变的。其实ACID里的一致性更多的是应用程序须要考虑的问题,和分布式系统里的CAP里的一致性彻底不是一个概念。
隔离性(Isolation),本质上是解决并发执行的事务如何保证数据库状态是正确的,抽象描述叫可串行化,就是并发的事务在执行的时候效果要求达到看起来像是一个个事务串行执行的效果。有冲突的事务之间的隔离性若是保证不了会引发前面的一致性(consistency)也没法知足。
每一个事务包含多个动做,这些动做若是按照事务自己的顺序依次执行就是所谓的串行执行,这些动做也能够从新排列,排列完之后的动做若是效果能够等价于事务串行执行的效果咱们就叫作可串行化调度。
实际实现的时候每每采用的是冲突可串行化,这个条件比可串行化要求会更高一点,规定了一些读写顺序规定了一些访问冲突的状况规定了哪些状况两个事物的动做能够调换哪些是不能够的,能够理解为冲突可串行化是可串行化的充分条件。
持久性(Durability),在事务完成之后全部的修改能够持久的保存在数据库中,通常会采用WAL的方式,会把操做提早记录到日志中来保证即便操做尚未刷到磁盘就宕机的状况下有日志能够恢复。
介绍完事务的ACID属性之后,咱们再来分析为何基于MySQL没法提供严格的分布式事务语义的支持。
若是客户端发送的SQL只涉及到一个节点,那天然是能够保证事务的,可是若是客户端发送的SQL涉及到两个及以上节点的SQL,那就没法保证事务语义了。
缘由主要是两个,一是原子性没法保证,另外一个是隔离性没法保证。在一个节点commit成功之后,在另外的节点commit失败了,这个事务就处在一个中间状态,此时原子性被打破。
引发的另外一个问题就是隔离性,这个事务的一部分提交了,另外一部分未提交,此时该事务正常是不应被读取到的,可是提交成功的部分会被其余事务读到,此时就没法保证隔离性了。
另外就算是涉及多个节点的操做都是成功的,理论上来讲也是没法保证隔离性的。由于假设A事务的一个节点先commit成功,其余的节点后commit成功,而此时B事务在读取的时候可能会读取到了A事务最先commit成功的那部份内容,却没有读到后来commit成功的内容,此时依然没法保证隔离性。
更本质一点的缘由是MySQL的事务都是每一个实例维护自身的事务ID,而基于MySQL集群的分布式方案没有一个全局的事务ID来标识每一个MySQL实例上的事务以及全局事务的元信息的管理,因此没法作到严格的分布式事务语义。
但实际上绝大多数业务对这个需求未必那么强烈,由于绝大多数的业务逻辑都是能够拆分的,拆成一个个只落在一个分库里的操做在绝大多数场景下是彻底可行的,并且拆分完之后也会更可控,因此这个问题在咱们支撑业务的过程当中也不是一个特别大的问题。
生产环境监控很关键
在实际生产环境中有不少方面都很是重要,高可用高可靠可扩展等,可是除了这些以外还有一个很是关键的是监控。
一个再健壮再牛x的系统都须要配备完善的监控系统,监控系统是生产环境中很是重要的一道防线,没有监控的系统就像是在裸奔,线上突发情况不少完善的监控系统能够作到第一时间发现问题及时定位以及解决问题。
物理机监控。
咱们在生产环境中会对系统所在物理机进行监控,京东有一个专门的物理机监控系统,能够监控包括CPU、内存、网卡、TCP链接数、磁盘使用状况、机器load等不少基础指标,针对这些指标能够设置相应的报警阈值,当超过必定阈值时会以邮件及短信的方式报警。
存活监控
但物理机的监控对于具体的系统的来讲是远远不够的,咱们还须要关注不少系统自己的信息,首先要有存活监控,这是最基本的。一个系统在线上运行的时候服务自己宕掉必定要求是能够第一时间监控到的。
但除了物理机监控之外,还有一个很是关键的是存活监控。系统的一切前提是能够活着,咱们在每一个模块都会提供相应的http接口,接入公司的统一监控平台,一旦有异常统一监控平台会及时通知相应的负责人。
系统内部状态可视化监控。
除了活下来之后,如何活得更好也是很关键的,因此咱们还有专门针对分布式MySQL集群的JMonitor系统,该系统会整合各个模块的内部详细状态信息,包括慢查询、用户访问状况以及数据分布状况等。
一句话一个稳定健壮的系统必定要配备相应的完善的监控系统。今天个人分享就是这些,主要就是介绍一些分布式MySQL的相关方案以及京东是怎么作的,讨论了一下分布式事务的问题,最后是一小部分生产实践经验,谢谢你们。
Q&A
问题1:请介绍下分布式事务保证数据最终一致性的具体方案例子。
首先分布式事务涉及到的一致性和CAP中一致性是两个概念,事务ACID属性中的一致性不涉及最终一致性,对于关系型数据库中事务的概念,个人理解都是强一致的(经过原子性和隔离型保证)。只有涉及到某一个节点(内容是相同的状况)多副本之间的复制问题才会涉及到弱一致性或者最终一致性(CAP中C)的问题。而分布式事务自己若是保证了原子性和隔离性,数据库层面就提供了一致性保证,其他的是应用逻辑层面保证。若是问的是数据库主从复制之间的一致性问题,这个事情本质上和事务(ACID的C)的一致性就没有关系了,因此这个问题自己可能有待商榷。
问题2:分布式事务如何支持,如今能够支持多大规模的集群。
基于Mysql的分布式集群方案没法保证严格的分布式事务语义,可是在实际使用的时候看业务状况,若是事务之间不怎么冲突的状况下也是ok的,若是能够改为只涉及一个分库的状况下那就绕开分布式事务的问题了。另外支持的集群,咱们实际上是根据业务来划分资源的,目前总体资源不能说特别大,千台规模。
问题3:JProxy是否能够支持全部复杂sql查询,主要是夸库的关联查询,具体内部逻辑能否介绍下?
咱们目前不支持夸库关联查询,从业务层面来解决。由于大表之间分库之后若是要支持跨库关联查询的话,做为一个OLTP系统在实际生产环境估计就无法用了。
问题4:请介绍下MySQL实际应用中主从复制的方案,以及主从的数据差别会在什么程度,谢谢!
这个其实更多的是主从之间关注的问题,通常会采用基于mix的模式。另外主从差别这个不一样业务不同,加上严格的监控,正常访问的状况下通常不会出现延迟,可是若是涉及到业务倒数据或者突增的访问量可能会引发延迟,因此这个不太好参考,若是有异常咱们都会第一时间及时介入处理。
问题5:长时间SQL不会形成堵塞吗?
主要看这条SQL具体是作什么的,若是是抽数据,就正常抽就能够了。若是有阻塞基本都是由于在MySQL端由于锁冲突等缘由形成阻塞,最终多是这个事务被abort掉或者最终抢到锁成功作完了这个事务。
问题6:请介绍一下JTransfers的工做机制,以及实现过程当中最难的部分。
迁移确实是比较刺手的一件事情,要考虑的细节不少。大致的步骤是:咱们提交迁移计划,指定什么时间开始迁移,到时间点之后JTransfer就会自动迁移。JTransfer一开始是dump源分库的数据,而后将这些数据恢复到目标实例上,可是在这个期间业务是正常访问的,须要将增量数据迁移完,因此会有追增量过程。当增量追到必定程度,咱们会阻塞这个库的访问,最后将剩余的少许数据迁移完。由于最后剩余数据量很少的时候,阻塞过程其实很短暂,因此对业务影响很是小。
最难的部分是:整个迁移过程当中的路由变动,要保证路由变动的过程当中数据不能写花,且变动之后的路由要准确的推送到JProxy中,由JManager和多个JProxy之间在变动路由的时候采用相似两阶段提交的协议,从而保证路由的变动是正确的。
问题7:能够分享一下JProxy的并发性能优化,以及JProxy中间状态的异常与恢复机制吗?谢谢!
并发性能优化咱们主要是经过采用基于事件驱动的网络模型,这种方式的特色是避免上下文切换避免锁的开销,可是代价的话刚才也说了须要考虑得很是周全,把一个上下文切成不少片断,不太符合人类思惟。
JProxy中间件状态的异常与恢复机制这个我不是太理解什么含义哈,个人理解是若是jproxy运行过程当中访问出异常了怎么处理,若是是某个链接过来的sql出了问题咱们的作法是将整个链接涉及到的资源都关闭,把该次查询涉及到的先后端资源清理干净,这样就不会影响到其余客户端的访问。正常来讲不该该出现这种状况,因此也须要完善的日志信息以及监控信息。
做者:meng_philip123 来源:http://www.jianshu.com/p/abb82c1a0657