假如让你来设计数据库中间件

pkbj1425 6年前
   <p>13年底负责数据库中间件设计时的设计文档,拿出来和大家分享:</p>    <ul>     <li> <p>可以了解下 数据库中间件技术</p> </li>     <li> <p>可以了解下架构师 系统 设计 的思路</p> </li>    </ul>    <p>一、总体目标</p>    <p>数据库中间层项目背景不再展开,根据前期的调研以及和公司同事的讨论,中间层的核心目标主要有两个:</p>    <ul>     <li> <p>db虚拟化 :让db对业务线透明(本文的db均指mysql),业务线不再需要知道db的真实ip,port,主从关系,读写关系,高可用等</p> </li>     <li> <p>分库的支持 :让db的分库对业务线透明</p> </li>    </ul>    <p>二、实现的功能</p>    <p>上述目标相对比较宽泛,具体来说,数据库中间层需要实现以下功能。</p>    <p>(1)统一接入入口</p>    <p>如果统一接入入口,从今以后, <strong>不再有</strong></p>    <p>db1.58.com:3306</p>    <p>db2.58.com:3306</p>    <p>im.58.com:3306</p>    <p>jiaoyou.58.com:3306</p>    <p>而 <strong>只有</strong></p>    <p>db.58.com:3306</p>    <p>所有的业务线,对db的访问,都只有一个入口 ,由数据库中间层来进行权限验证,由中间件来路由请求,这是一种完美的情况。</p>    <p>当然,统一一个总入口目标有点宏大,可以循序渐进, 先各业务线统一读写访问入口 ,故折衷的目标可以是,从今以后,不再有</p>    <p>im.read.db1.58.com:3306</p>    <p>im.read.db2.58.com:3306</p>    <p>im.write.db.58.com:3306</p>    <p>而只有</p>    <p>im.58.com:3306</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e5e0818ea6ca818905cf1d0b43d2b95c.png"></p>    <p>im业务对db的访问,统一到一个入口上来了 ,由中间层来对请求进行智能路由。</p>    <p>更简化的,甚至可以初期同一个业务线的db读写都不对业务线透明,数据库中间层只做简单的请求转发,先初步的把数据库访问入口收拢到数据库中间层来,为后续的统一,再统一打下基础。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/366ff3acc4785aa1b3216f01220c1ca7.png"></p>    <p>ROAD-MAP规划如下:</p>    <ul>     <li> <p>业务线入口统一(中转请求)</p> </li>     <li> <p>业务线入口统一(智能路由)</p> </li>     <li> <p>全局入口统一</p> </li>    </ul>    <p>(2)保持访问接口</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/92466636a38ef783950116e4d055648c.png"></p>    <p>原来db的访问方式主要有以上三种:</p>    <ul>     <li> <p>手工用 mysql客户端 连mysql,直连数据库执行命令</p> </li>     <li> <p>java使用 jdbc 连接数据库</p> </li>     <li> <p>c/c++使用 libmysqlclient.a 来对mysql进行访问</p> </li>    </ul>    <p>所谓保持访问接口 ,是指上游对数据库的访问接口完全不用变, 中间件服务对上游来说,就是数据库 。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ab8ad9536b85eae0828cfa8621968960.png"></p>    <p>由于SQL协议是非常复杂的,在db的客户端与服务器插入了一个中间层之后,不一定能对所有的SQL功能都进行支持,支持哪些SQL是需要慎重考虑的。</p>    <p>(3)屏蔽读写分离</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/33c684b74d5c3de9b29d6c0cfea13d1c.png"></p>    <p>业务层不需要在关注读写分离 ,由中间件来进行读写请求路由。</p>    <p>(4) 支持的 分库</p>    <p>58的db的水平扩展,基本是用的分库的方式(分库比较好,很容易实现实例的扩容),即:</p>    <p>db.table会水平拆分为:</p>    <p>db1.table</p>    <p>db2.table</p>    <p>db3.table</p>    <p>db4.table</p>    <p>这样的话,dao层对于table就只有一个table实例了,比较方便。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/07e3f923eef51ea61c4fefe3c5bf0c2d.png"></p>    <p>根据前期与各业务线同学的沟通,58在分库上的业务访问需求为(这个调研的周期比较长,和很多业务线进行了沟通):</p>    <ul>     <li> <p>patition key普通查询</p> </li>     <li> <p>patition key上的IN查询</p> </li>     <li> <p>非patition key上的查询</p> </li>     <li> <p>有限功能的排序+分页查询</p> </li>    </ul>    <p>故对分库上的分布式SQL功能,数据库中间层只需要支持上上述四项即可。</p>    <p>(5)高可用性的支持</p>    <p>高可用的支持又分为两个部分:</p>    <p>第一部分, 故障自动发现 :下游数据库挂了,能够自动发现问题,并报警周知相关人员。</p>    <p>第二部分, 故障自动转移 :</p>    <ul>     <li> <p>主库挂了,能够自动切换,或者屏蔽写请求</p> </li>     <li> <p>从库挂了,能够自动自动切换读请求量流量</p> </li>     <li> <p>中间件挂了,自动切换中间件流量,高可用</p> </li>    </ul>    <p>(6)可运维性的支持</p>    <ul>     <li> <p>支持一些统计数据的展现</p> </li>     <li> <p>支持一些管理命令</p> </li>     <li> <p>支持页面话的运维</p> </li>    </ul>    <p>however,只要总的框架设计具备可扩展性,这些功能可以循序渐进,逐步添加。</p>    <p>三、设计折衷</p>    <p>(1)协议与整体架构</p>    <p>既然选择了mysql client server protocol作为业务层与中间层之间的协议,那么 <strong>中间层必然是作为mysql-server接收上游的请求</strong> , <strong>作为mysql-client向真正的mysql发送请求</strong> 的,中间层的整体结构如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e41061b3f761fbd3f60abbdab0b0e19d.png"></p>    <p>这样的话, 需要对mysql client server protocol做详尽的研究 ,了解:</p>    <ul>     <li> <p>连接的建立过程</p> </li>     <li> <p>权限认证的过程</p> </li>     <li> <p>压缩解压缩的过程</p> </li>     <li> <p>请求响应二进制协议各种细节</p> </li>     <li> <p>…</p> </li>    </ul>    <p>协议这一块的掌握必须详尽,好在官方文档相对比较全面:</p>    <p>http://dev.mysql.com/doc/internals/en/client-server-protocol.html</p>    <p>(2)架构细节</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d7f1627bf1610dbac31e7ac28811e4e5.jpg"></p>    <p>总体架构细节图如上。</p>    <p>(2.1)上游</p>    <ul>     <li> <p>mysql客户端,java使用jdbc作为上游连接,c/c++使用libmysql.a作为上游连接,使用的是Mysql Client Server协议</p> </li>     <li> <p>DBA亦可以向中间件发送一些管理命令,或者查看一些统计信息,使用的是自己定义的内部协议</p> </li>    </ul>    <p>(2.2)下游</p>    <p>处于系统体系结构中的最后端,系统中间件的下游就是mysql集群了,中间件与mysql之间使用的也是Mysql Client Server协议。</p>    <p>(2.3)中间层-ConfigMgr</p>    <p><strong>中间层配置文件管理组件 </strong> ConfigMgr是中间层中非常重要的一个部分,请求的转发,读写分离,分库功能的支持,都需要通过配置来完成。</p>    <p><mysql></p>    <p><db id=0 logic_db="im"type=1></p>    <p><item ip="10.58.1.100" port=3306 name="im" /></p>    <p></db></p>    <p><db id=1 logic_db="umc"type=2 patition_count=2 key="uid" hash="mod" ></p>    <p><patition id=0></p>    <p><item ip="10.58.1.100" port=3306 role="w" /></p>    <p><item ip="10.58.1.101" port=3306 role="r" /></p>    <p><item ip="10.58.1.102" port=3306 role="r" /></p>    <p></patition></p>    <p><patition id=1></p>    <p><item ip="10.58.1.100" port=3316 role="w" /></p>    <p><item ip="10.58.1.101" port=3316 role="r" /></p>    <p><item ip="10.58.1.102" port=3316 role="r" /></p>    <p></patition></p>    <p></db></p>    <p></mysql></p>    <p>从配置文件可以看出,ConfigMgr需要管理的mysql配置类型有两种:</p>    <p>type=1请求转发</p>    <p><db id=0 logic_db="im"type=1></p>    <p><item ip="10.58.1.100" port=3306 name="im" /></p>    <p></db></p>    <p>配置的含义是,上游如果访问逻辑数据库logic_db=”im”,中间件则将请求转发到实际的后端数据库item,item中配置了后端数据库的ip/port/name。</p>    <p>type=2分库支持</p>    <p>解释分库支持的配置之前,先说明一下数据库的层次结构LOGIC_DB、PARTITION、ITEM。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ebe9457c497b996d0baddd578d679b89.jpg"></p>    <p>LOGIC_DB :逻辑数据库,面向上游,例如umc</p>    <p>PARTITION :数据库分区,可以理解为分库,例如umc0和umc1,这个对上游是透明的</p>    <p>ITEM :数据库项,可以理解为一个分区上的一个读库或者写库,这个对上游也是透明的</p>    <p>上例中对应的配置文件为:</p>    <p><db id=1 logic_db="umc"type=2 patition_count=2 key="uid" hash="mod" ></p>    <p><patition id=0></p>    <p><item ip="10.58.1.100" port=3306 role="w" /></p>    <p><item ip="10.58.1.101" port=3306 role="r" /></p>    <p><item ip="10.58.1.102" port=3306 role="r" /></p>    <p></patition></p>    <p><patition id=1></p>    <p><item ip="10.58.1.100" port=3316 role="w" /></p>    <p><item ip="10.58.1.101" port=3316 role="r" /></p>    <p><item ip="10.58.1.102" port=3316 role="r" /></p>    <p></patition></p>    <p></db></p>    <ul>     <li> <p>LOGIC_DB :需要关注partition-key-column,也需要关注partition算法,它要实现对PARTITION的请求路由以及结果集的汇总</p> </li>     <li> <p>PARTITION :需要关注ITEM的读写特性,它要实现对ITEM的读写分离</p> </li>     <li> <p>ITEM :是最终的数据库,和它相关的配置是数据库ip/port/name/wr-type</p> </li>    </ul>    <p>(2.4)中间层-MysqlServerPart</p>    <p><strong>中间层服务端组件 </strong> MysqlServerPart是中间层中非常重要的一个部分,它负责端口的监听+请求接收与返回(服务端网络IO),MysqlProtocol的解析。根据其功能,MysqlServerPart组件又主要分为两个组件ServerIOMgr组件(服务端IO管理),MysqlProtocolAnalyzer组件(Mysql协议分析)。</p>    <p>这一层次面临这些细节:</p>    <ul>     <li> <p>server 网络框架 的选取:建议使用异步server</p> </li>     <li> <p>并发模型 的选取:建议使用IO-thread + multi-work-thread的并发模型</p> </li>     <li> <p>内存管理 模型的选取:建议使用内存池</p> </li>     <li> <p>连接 上下文管理 ,最容易想到的上下文,一个数据库连接是和一个逻辑库LOGIC_DB绑定的</p> </li>     <li> <p>Mysql如何建立 数据库连接 :需要考察Mysql协议</p> </li>     <li> <p>Mysql 协议的细化解析 :需要考察Mysql协议</p> </li>     <li> <p>…</p> </li>    </ul>    <p>(2.5)中间层-MysqlClientPart</p>    <p><strong>中间层客户端组件 </strong> MysqlClientPart是中间层中非常重要的一个部分,它负责中间件对mysql的连接池管理,以及返回结果集的解析。根据其功能,MysqlClientPart组件又主要分为两个组件ClientConnPoolMgr组件(客户端连接池管理),ResultSetAnalyzer组件(返回结果集分析)。</p>    <p>这一层次面临这些细节:</p>    <ul>     <li> <p>数据库 连接池 的实现</p> </li>     <li> <p>数据库 连接模型 的选型:建议前期使用同步模型</p> </li>     <li> <p>连接 上下文管理 ,最容易想到的上下文,一个数据库连接是和一个ITEM绑定的</p> </li>     <li> <p>Mysql 结果集的细化解析 :需要考察Mysql协议</p> </li>     <li> <p>…</p> </li>    </ul>    <p>(2.6)中间层-SqlParser</p>    <p><strong>中间层Sql分析组件 </strong> SqlParser是中间层中非常重要的一个部分,它负责对sql语句的语法分析与语义分析。</p>    <p>为什么要进行Sql语法语义分析?需要解析出什么东东?</p>    <p>分为两种情况:</p>    <p>type=1请求转发</p>    <p>对于请求的中转,上游一个数据库连接对应一个逻辑库LOGIC_DB,由ConfigMgr可以知道对应下游一个真实的ITEM(ip/port/db),此时直接转发请求即可,无需解析Sql语句。</p>    <p>type=2分库支持</p>    <p>对于分库的支持,解析Sql语句可能需要得到这些问题的答案:Sql是否带了partition-key-column?partition-key-column的值是多少?</p>    <p>例如一条Sql语句: <em> select * from user where uid=123456; </em></p>    <p>就必须将“uid”列属性,以及uid的列属性值“123456”解析出来,以用作后续请求路由。</p>    <p>注意:更细的情况是,针对每个表,分库partition-key-column都是不一样的,上例中还需要将表名user也解析出来。</p>    <p>这一层次面临这些细节:</p>    <ul>     <li> <p>如何解析Sql语句:可以参考mysql源码对SQL语句的解析,亦可参照cober对SQL语句的解析方法;</p> </li>    </ul>    <p>注:由于我们只需要支持多库,数据库库名信息是在“连接”这一层获取的,又我们支持的分布式Sql的种类有限,故只需解析partition-key-column,offset/limit等少数信息即可。</p>    <p>(2.7)中间层-SqlModifier</p>    <p><strong>中间层Sql修改组件 </strong> SqlModifier是中间层中非常重要的一个部分,它负责对sql语句改写。</p>    <p>为什么要对Sql语句进行改写?</p>    <p>type=1的请求转发,无需修改Sql,但对于type=2的分库支持,有些Sql语句就必须进行改写。</p>    <p>例如: <em>select * from user where uid in(1,2,3,4,5,6);</em></p>    <p>假设PARTITION分了0和1奇偶两个分区,则sql应该分别被改写为:</p>    <p><em>select * from user where uid in(2,4,6); </em> => 路由给0库;</p>    <p><em>select * from user where uid in(1,3,5); </em> => 路由给1库;</p>    <p>又例如: <em> select * from user limit 1000,10; </em></p>    <p>则sql可能会被改写为:</p>    <p><em>select * from user limit 0,1010; </em> => 分别路由到两个库,收集完结果集共2020条记录,再排序取其中1000-1010这10条。</p>    <p>哪些Sql需要改写,如何改写?</p>    <p>结合我们需要实现的四类分布式Sql:</p>    <ul>     <li> <p>patition key普通查询</p> </li>     <li> <p>patition key上的IN查询</p> </li>     <li> <p>非patition key上的查询</p> </li>     <li> <p>有限功能的排序+分页查询</p> </li>    </ul>    <p>只有(2)和(4)两项需要改写,改写方法上文已述,其中(4)的改写效率较低,使用起来要谨慎。</p>    <p>(2.8)中间层-SqlRouter</p>    <p><strong>中间层Sql路由组件 </strong> SqlRouter是中间层中非常重要的一个部分,它负责对sql语句进行路由。</p>    <p>哪些Sql需要路由,如何路由?</p>    <p>结合我们需要实现的四类分布式Sql:</p>    <ul>     <li> <p>patition key普通查询</p> </li>     <li> <p>patition key上的IN查询</p> </li>     <li> <p>非patition key上的查询</p> </li>     <li> <p>有限功能的排序+分页查询</p> </li>    </ul>    <p>只有(1)和(2)两项需要路由,(3)和(4)需要将请求分发至所有的PARTITION。</p>    <p>(2.9)中间层-ResultSetMerger</p>    <p>中间层结果集合并组件 ResultSetMerger是中间层中非常重要的一个部分,它负责对结果集进行合并,筛选。</p>    <p>哪些Sql需要合并结果集,筛选结果集?</p>    <p>结合我们需要实现的四类分布式Sql:</p>    <ul>     <li> <p>patition key普通查询</p> </li>     <li> <p>patition key上的IN查询</p> </li>     <li> <p>非patition key上的查询</p> </li>     <li> <p>有限功能的排序+分页查询</p> </li>    </ul>    <p>其中(2)和(3)类查询需要将结果集进行合并,(4)不但要合并结果集,还需要将结果集在本地进行排序,然后再筛选出真正的结果集。</p>    <p>(2.10)其他组件</p>    <ul>     <li> <p><strong>AdminServer </strong> :监听一个新端口,接收数据库管理员命令的server</p> </li>     <li> <p><strong>AdminMgr </strong> :实现管理员命令的组件</p> </li>     <li> <p><strong>MonitorMgr </strong> :实现监控报警的组件</p> </li>     <li> <p><strong>StatisticsMgr </strong> :实现数据统计功能的组件</p> </li>    </ul>    <p>上述组件可循序渐进,逐步添加,故一期需要实现的组件及架构图为:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4e74459ddc9dbf7df77c1b3a06e1f759.jpg"></p>    <p>感谢看完,说明你对数据库中间件感兴趣,建议在PC上细看3遍,一定更有收获。</p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s/6kuVgdO7RBs9gs229wG3wA</p>    <p> </p>