网站框架指的是什么,wordpress导航网站模板下载,做网站常用工具,石河子规划建设局网站讲师介绍张亮 当当架构部总监 负责分布式中间件和私有云平台建设 目前主导开源项目#xff1a;Elastic-Job及Sharding-JDBC 主题简介#xff1a; 1、关系型数据库中间件核心功能介绍 2、Sharding-JDBC架构及内核解析 3、Sharding-JDBC未来展望 一、关系型数据库中间件核心功…讲师介绍 张亮 当当架构部总监 负责分布式中间件和私有云平台建设 目前主导开源项目Elastic-Job及Sharding-JDBC 主题简介 1、关系型数据库中间件核心功能介绍 2、Sharding-JDBC架构及内核解析 3、Sharding-JDBC未来展望 一、关系型数据库中间件核心功能介绍 关系型数据库凭借灵活查询的SQL和稳定的存储及事务引擎一直以来是业务存储领域的首选。而在规模越来越大的互联网年代单一的关系型数据库却已难满足需求。开发人员不愿放弃SQL查询的灵活度及对之前代码的兼容性而又无法承受数据量过大时所带来的性能瓶颈。因此NoSQL和NewSQL分别产生而NoSQL的不兼容性和NewSQL的不成熟也使得关系型数据库仍将长期存在下去。所以继续使用关系型数据库并能解决互联网规模所带来的冲击数据库中间层这个既不保守也不激进的平衡方案被大多数互联网公司所接受。关系型数据库中间件采用单体向分布式透明转化的方案来解决数据量和访问量巨大这两个互联网场景的核心问题。 关系型数据库在大于自身数据量阀值的情况下性能会急剧下降。在面对互联网海量数据情况时所有数据都存于单表显然会轻易超过数据库表可承受的范围。这个单表可承受的数据量阀值需根据数据库和并发量的差异通过实际测试获得。 通过分库和分表拆分数据来使得各个表的数据量保持在阀值以下。拆分方式为垂直拆分和水平拆分。垂直拆分是根据业务将单库表拆分为多库表。如将常用的字段和不常用的字段拆分至不同的库表中。但垂直拆分需要对架构和设计进行调整往往来不及应对互联网业务需求的快速变化而且也并不能真正的解决单点瓶颈。水平拆分则是根据分片算法将一个库表拆分为多个库表。如根据ID的最后一位以10取余尾数是0的放入0库表尾数是1的放入1库表。水平拆分从理论上突破了单机数据量处理的瓶颈并且扩展相对自由是分库分表的标准解决方案。 分库和读写分离疏导流量是应对高访问量的常见手段。分表虽然可以解决海量数据导致的性能问题但无法解决过多请求访问同一数据库导致其响应变慢的问题。所以水平拆分通常要采取分库的方式一并解决数据量和访问量巨大的问题。读写分离是另一个疏导流量的办法但读写数据间的延迟是架构设计时需要考虑的问题。 虽然分库可以解决上述问题但分布式架构在获得了收益的同时也带来了新的问题。 跨库事务是分布式数据库要面对的棘手事情。合理采用分表可以在降低单表数据量的情况下尽量使用本地事务善于使用同库不同表可有效避免分布式事务带来的麻烦。在不能避免跨库事务的场景有些业务仍然需要保持事务的一致性。而基于XA的分布式事务由于性能低下无法被互联网公司所采纳它们大多采用最终一致性的柔性事务代替分布式事务。 因此最佳实践是合理地配合使用分库分表。在实现上分表的难度远大于分库它需要对SQL解析并且对表名改写而分库则不需要。 另一个分布式衍生的问题是主键生成。它必须可以保证分布式唯一。分布式主键的生成方式分为中心化和去中心化两大类。中心化可以继续采用数据库生成自增主键的方式为每个不同的分库设置不同的初始值并将步长设置为分片的个数即可这种方式对分片个数有依赖一旦再次水平扩展原有的分布式主键不易迁移。还有一种中心化生成分布式主键的方式即采用Redis在内存中生成自增序列但此种方式新增加了一个外部组件的依赖一旦Redis不可用则整个数据库将无法在插入可用性会大大下降另外Redis的单点问题也需要解决部署复杂度较高。去中心化方式无需额外部署可扩展性也很好因此更推荐使用。UUID是去中心化生成分布式主键较为常见的一种方式但它的主键很长而且无序通过主键排序时对数据库的性能影响较大不建议使用。目前较为完美的方案是使用snowflake算法生成分布式唯一和基本有序的主键。 综上所述分布式数据库中间件的功能模块非常清晰有其存在的必要和市场价值。但与旺盛的需求形成鲜明对比成熟的关系型数据库中间件凤毛麟角。主要原因是各公司均开发各自的中间件没有形成统一的标准及规范也没有动力从公司现有的系统中解耦并独立开源。当当同样自研并决定将其开源命名为Sharding-JDBC。从2016年开源至今已发布了15个版本其中包含5个里程碑版本升级。在经历了整体架构的数次精炼以及稳定性打磨后如今它已积累了足够的底蕴相信可以成为开发者选择技术组件时的一个参考。 二、Sharding-JDBC架构及内核解析 Sharding-JDBC完整的实现了分库分表读写分离和分布式主键功能并初步实现了柔性事务。 它直接实现JDBC接口旧代码迁移成本几乎为零可适用于任何基于Java的ORM框架如JPA、Hibernate、Mybatis、SpringJDBC Template等理论上可以支持所有实现JDBC协议的数据库但由于各种数据库的SQL方言差别较大每种SQL都需要独立的解析器Sharding-JDBC目前仅支持MySQL、PostgreSQL、Oracle和SQL Server这4种最主流的数据库。由于柔性事务与JDBC没有直接关系因此正在考虑将它拆分为一个独立的项目。 Sharding-JDBC与基于MySQL等数据库协议实现的Proxy中间层在部署架构上差别很大但在代码的核心逻辑上差别并不大。Sharding-JDBC作为lib库是与业务代码部署在一起的而基于Proxy的中间层则是架在数据库的前方与应用代码在部署上隔绝。无论使用哪种架构它们的核心逻辑均极为相似都会分为分片规则配置、SQL解析、SQL路由、SQL改写、SQL执行以及结果归并模块区别仅在于协议实现层的不同JDBC或数据库协议。 Sharding-JDBC架构图如下 左边部分是部署架构图右边部分则是核心逻辑架构图。 使用Sharding-JDBC性能是大家最关心的问题。 在数据量一致的情况下使用Sharding-JDBC和原生JDBC的性能测试报告如下 查询操作Sharding-JDBC的TPS为JDBC的TPS的99.8%。 插入操作Sharding-JDBC的TPS为JDBC的TPS的90.2%。 更新操作Sharding-JDBC的TPS为JDBC的TPS的93.1%。 可以看到Sharding-JDBC在查询中的性能损失非常低插入和更新略高。 将单表的数据拆分为二放入两个表中使用Sharding-JDBC和原生JDBC的性能测试报告如下 查询操作TPS双库比单库可以增加大约94%的性能。 插入操作TPS双库比单库可以增加大约60%的性能。 更新操作TPS双库比单库可以增加大约89%的性能。 结果表明Sharding-JDBC可有效利用水平扩展大幅度提升性能。 下面我将按照模块深度剖析Sharding-JDBC的详细功能和主要实现请大家和我一起探索与评估它的水有多深。 分片规则配置 Sharding-JDBC的分片策略配置是自定义的因此可以通过编程的方式最大限度的灵活调整。它并不仅支持运算符分片可支持BETWEEN和IN的运算符分片支持将一条逻辑SQL最终散落至多个数据节点。同时支持多分片键例如根据用户ID分库订单ID分表这种分库分表结合的分片策略或根据年分库月份用户区域ID分表这样的多片键分片。 通过编程的方式定制分片规则虽然灵活但配置起来略显繁琐。因此Sharding-JDBC又提供了Inline表达式编写分片策略的方式用于配置集中化以避免配置散落在配置文件和代码中的情况。此外它还提供了定制化的Spring命名空间和YAML进一步简化配置。 JDBC规范重写 Sharding-JDBC对JDBC规范的重写思路是针对DataSource、Connection、Statement、PreparedStatement和ResultSet这5个核心接口封装将多个实现类集合纳入Sharding-JDBC实现类管理。分布式主键也属于JDBC协议的一部分。 Sharding-JDBC尽量最大化实现JDBC协议但分布式毕竟与原生JDBC不同所以目前仍有未实现的接口包括游标存储过程、SavePoint以及向前遍历和修改ResultSet等不太常用的功能。此外为了保证兼容性并未实现JDBC 4.1及其后发布的接口如DBCP 1.x版本不支持JDBC 4.1。 SQL解析 SQL解析作为分库分表类产品的核心性能和兼容性是最重要的衡量指标。目前常见的SQL解析器主要有fdbjsqlparser和Druid。Sharding-JDBC1.4.x之前的版本使用Druid作为SQL解析器经实际测试它的性能远超其它解析器。 从1.5.x版本开始Sharding-JDBC采用完全自研的SQL解析引擎。由于目的不同它并不需要将SQL转为AST语法树也无需通过Visitor的方式二次遍历。它采用对SQL“半理解”的方式仅提炼分片需要关注的上下文因此SQL解析的性能和容错性得到了进一步的提高。 SQL解析模块由Lexer和Parser两个模块组成。Lexer用于将SQL拆解为Token并将其归类为关键词表达式字面量和操作符。Parser则用于理解SQL和提炼分片上下文并标记可能需要改写的位置。分片上下文包含SELECTItems、表信息、分片条件、自增主键信息、排序信息、分组信息和Limit信息。一次解析过程是不可逆的一个个Token的依次解析因此解析性能很高。由于各种数据库的SQL差异很大因此在解析模块对每种数据库提供方言的支持。 Sharding-JDBC支持各种连接、聚合、排序、分组以及分页的解析并且可以有限度的支持子查询。 SQL路由 SQL路由是根据分片规则配置以及解析上下文中的分片条件将SQL定位至真正的数据源。它又分为直接路由、简单路由和笛卡尔积路由。 满足直接路由的条件比较苛刻如果通过Hint通过HintAPI直接指定路由至库表方式分片且仅分库则无需SQL解析和结果归并。因此它的SQL兼容性最好可以执行包括子查询、OR、UNION等复杂情况的任意SQL。 简单路由是Sharding-JDBC最推荐使用的分片方式它是指不包含JOIN或仅包含Binding表JOIN的SQL。Binding表是指使用同样的分片键和分片规则的一组表也就是说任何情况下Binding表的分片结果应与主表一致。例如order表和order_item表都根据order_id分片结果应是order_1与order_item_1成对出现。这样的关联查询和单表查询复杂度和性能相当。如果分片条件不是等于而是BETWEEN或IN则路由结果不一定落入单库表因此一条逻辑SQL最终可能拆分为多条SQL语句。 笛卡尔积查询最为复杂因为无法根据Binding关系定位分片规则的一致性所以非Binding表的关联查询需要拆解为笛卡尔积组合执行。查询性能较低而且数据库连接数较高需谨慎使用。 SQL改写 SQL改写模块的用途是将逻辑SQL改写为可以分布式执行的SQL。在Sharding-JDBC 1.5.x版本SQL改写进行了调整和大量优化。1.4.x及之前版本SQL改写是在SQL路由之前完成的在1.5.x中调整为SQL路由之后因为SQL改写可以根据路由至单库表还是多库表而进行进一步优化。SQL改写分为正确性改写和优化改写两部分。 正确性改写包括将分表的逻辑表名称替换为真实表名称修正分页信息和增加补列。举两个例子 AVG计算。分布式场景以avg1 avg2 avg3 / 3计算平均值并不正确需要改写为 (sum1 sum2 sum3) / (count1 count2 count3)。这就需要将包含AVG的SQL改写为SUM和COUNT并在结果归并时重新计算平均值。 分页。假设每10条数据为一页取第2页数据。在分片环境下获取LIMIT 10, 10归并之后再根据排序条件取出前10条数据是不正确的结果。正确的做法是将分条件改写为LIMIT 0, 20取出所有前两页数据再结合排序条件计算出正确的数据。因此越是获取靠后数据分页的效率就会越低。有很多方法可避免使用LIMIT进行分页。比如构建记录行记录数和行偏移量的二级索引或使用上次分页数据结尾ID作为下次查询条件的分页方式。 优化改写是1.5.x重点提升的部分实现的功能比较零散这里同样举两个例子 单路由拒绝改写。这是将SQL改写挪到SQL路由之后的原因。当获得路由结果之后单路由的情况因为不涉及到结果归并因此分页、补列等改写都无需存在。尤其是分页无需将数据从第1条开始取节省了网络带宽。 流式归并改写。一会讲到归并时会说这里先提一句将仅包含GROUPBY的SQL改写为GROUPBY ORDERBY。 SQL执行 路由至真实数据源后Sharding -JDBC将采用多线程并发执行SQL。它用3种执行引擎分别对应处理StatementPreparedStatement和AddBatchPreparedStatement。Sharding-JDBC线程池放在一个名为ShardingContext的对象中它的生命周期同ShardingDataSource保持一致。如果一个应用中创建了多个Sharding-JDBC的数据源它们将持有不同的线程池。 结果归并 Sharding-JDBC支持的结果归并从功能上分为遍历、排序、分组和分页4种类型它们是组合而非互斥的关系。从结构划分可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的装饰者归并可以在流式归并和内存归并之上做进一步的处理。 流式归并是将数据游标与结果集的游标保持一致顺序的从结果集中一条条的获取正确的数据。遍历和排序都是流式归并分组比较复杂分为流式分组和内存分组。内存归并则是需要将结果集的所有数据都遍历并存储在内存中再通过内存归并后将内存中的数据伪装成结果集返回。 遍历类型最为简单只需将多结果集组成链表遍历完成当前结果集后将链表位置后移继续遍历下一个结果集即可。 排序类型稍微复杂由于ORDER BY的原因每个结果集自身数据是有序的因此只需要将结果集当前游标指向的值排序即可。Sharding-JDBC在排序类型归并时将每个结果集的当前排序数据实现了比较器并将其放入优先级队列。每次JDBC调用next时将队列顶端的结果集出队并next然后获取新的队列顶端的结果集供JDBC获取数据。 分组类型最为复杂分组归并已经不属于OLTP范畴而更面向OLAP但由于遗留系统使用很多因此Sharding-JDBC还是将其实现。分组归并分成流式分组归并和内存分组归并。流式分组归并节省内存但必须要求排序和分组的数据保持一致。如果GROUPBY和ORDER BY的内容不一致则必须使用内存分组归并。由于数据不是按照分组需要的顺序取出因此需要将结果集中的所有数据全部加载至内存。在SQL改写时提到的仅有GROUP BY的SQL会优化增加ORDER BY语句即使将内存分组归并优化为流式分组归并的提升。 无论是流式分组还是内存分组对聚合的处理都是一致的。聚合分为比较、累加和平均值3种类型。比较聚合包括MAX和MIN只返回最大小结果。累加聚合包括SUM和COUNT需要将结果累加后返回。平均值聚合则是通过SQL改写的SUM和COUNT计算相关内容已在SQL改写涵盖不再赘述。 最后再聊一下装饰者归并他是对所有的结果集归并进行统一的功能增强目前装饰者归并只有分页一种类型。 上述的所有归并类型都可能分页或不分页因此可以通过装饰者模式来增加分页的能力。分页归并会将改写的LIMIT中不需要获取的数据过滤掉。Sharding-JDBC的分页很容易产生误解很多人认为分页会占用大量内存因为Sharding-JDBC会因为分布式正确性的考量将LIMIT 100000, 10改写为LIMIT 0, 100010产生Sharding-JDBC会将100010数据都加载到内存的错觉。通过上面分析可知会全部加载到内存的只有内存分组归并这一种情况。其他情况都是通过流式获取结果集数据的方式因此Sharding-JDBC会通过结果集的next方法将无需取出的数据全部跳过并不会将其存入内存。 分布式主键 分布式主键在这里单独提炼出一个章节因为它是贯穿于Sharding-JDBC整个生命周期的。 分布式主键最独立的部分是生成策略Sharding-JDBC提供灵活的配置分布式主键生成策略方式。在分片规则配置模块可配置每个表的主键生成策略默认使用snowflake。 通过策略生成的分布式主键可以无缝的融入JDBC协议它实现了Statement的getGeneratedKeys方法将其返回改写后的Result和ResultMetaData将Sharding-JDBC生成的分布式主键伪装为数据库生成的自增主键返回。 SQL解析时需要根据分布式主键配置策略判断是否在逻辑SQL中已包含主键列如果未包含则需要将INSERTItems和INSERT Values的最后位置写入解析上下文。 SQL改写时将根据解析上下文中的位置改写SQL增加未包含的主键列名称和值。如果是Statement则在INSERT Values后追加生成后的分布式主键如果是PreparedStatement则在INSERT Values后追加并在传入的参数后追加生成后的分布式主键。 受限于篇幅读写分离、柔性事务就不在此说明了。 三、Sharding-JDBC未来展望 首先请和我一同回顾下Sharding-JDBC每个里程碑版本的历程。 1.0.x分库分表 1.1.x配置简易化 1.2.x柔性事务 1.3.x读写分离 1.4.x分布式主键 1.5.x自研解析引擎 多数据库支持 通过这5个版本的迭代可以看到Sharding-JDBC的精力主要集中在透明化分布式数据库这部分因此经常有人问Sharding-JDBC和基于Proxy的数据库中间层有什么区别和NewSQL数据库又有什么区别 尽管部署架构不同但功能上确实差异不明显。不过结构的不同终会将它们推向不同的方向。Sharding-JDBC与业务代码部署在一起的架构非常适合作为微服务的数据访问层基础开发组件。Proxy和NewSQL是面向运维的数据库而Sharding-JDBC的定位与当当一并开源的DubboX、Elastic-Job一样是面向开发的微服务基础类库它始终以云原生的基础开发套件为目标。 Sharding-JDBC 1.6.x到来将会愈加明显的划清界限。Sharding-JDBC 1.6.x的目标是配置动态化和数据库治理通过将配置存入注册中心达到治理分库分表读写分离的数据库的目的。在应用端进行数据库发现、流量疏导、故障转移、熔断等功能向治理服务一样治理数据库。 Sharding-JDBC将作为面向OLTP在线业务的分片化的数据库治理微服务基础组件积极的发展下去。真诚邀请感兴趣的人关注和参与。 来源中生代技术 原文链接