自1970年提出关系数据模型至今, 数据库的发展依次经历了传统的关系型数据库系统, 具有高可用和良好拓展性的NoSQL系统(如HBase[1])以及同时兼顾可拓展性和事务支持的NewSQL(如VoltDB[2])新型数据库系统3个阶段.目前针对新硬件(如NVM[3])下的数据库优化技术也在如火如荼地发展中.随着云端应用的急速发展, 为了满足不同的目的数据库系统爆炸式增长, 由此有效的数据库测试是提高数据库开发效率的重要手段.
按照测试目的分类, 数据库测试可主要分为功能测试、性能测试和数据库系统测试三大类.功能测试即验证数据库所提供的功能接口是否正确即是否符合设计文档, 如验证基本SQL操作, 二级索引和多行事务操作等; 性能测试即指数据库在一定硬件配置和系统配置下运行一类负载的性能, 常见的性能指标如TPS、QPS和百分比延迟等; 系统测试则是验证数据库系统对外提供的系统服务接口, 如分布式数据库中对集群切换和节点上下线控制等.
大多数测试工具对功能测试支持地较好, 但在性能测试方面, 它们在功能支持、负载模式、可拓展性上仍存在较大限制.如MySQL Test Framework[4]测试框架只支持基本的SQL功能测试, 不支持性能测试; 多线程性能测试工具Sysbench[5]使用Lua脚本语言编写测试负载, 但仅可对主键列的访问分布进行简单的控制, 缺乏多样性与灵活性; 开源的通用性能测试框架OLTP-Bench[6]实现了大多数负载模式固定的标准Benchmark, 但不支持自定义性能测试负载, 故无法准确反应特定应用下的性能; Woodpecker[7]通用测试框架虽然提供了灵活的测试定义语言描述测试任务, 在性能测试模块中实现了增删改查五类负载, 但负载与逻辑代码紧密耦合, 可拓展性上存在不足.
由此可见, 现有框架或工具要么不支持自定义性能测试负载, 要么在支持程度上较弱, 且均未考虑对性能测试结果影响重大的因素——数据特征.主要表现为:
(1) 非主键列上非重复值的个数, 当数据表有非常大的规模, 且在某些非主键列上建有二索引, 当在那些列上进行查询时, 非重复值的个数直接影响查询结果集的大小以及遍历结果集的时间, 从而影响二级索引的效率;
(2) 字符串的平均长度和最大长度, 不同的字符串长度导致不同的查询结果集大小, 它将影响磁盘I/O、网络传输时间等, 这都会影响性能测试的结果;
(3) 不同的访问分布, 高Skew的访问分布相对于均匀的访问分布缓存效果会更好, 因为Skew分布涉及的数据量相对于均匀分布集中, 所以内存缓存效果好.另外, 对于具有高冲突的负载来说, 高Skew增加了并发控制的代价, 故高Skew的性能将低于平均访问分布.
这篇文章中呈现的工作就是去试图解决上述问题, 并集成到Woodpecker框架下.本文提供了一个基于数据特征的自定义负载性能评测工具——Woodpecker+, 主要贡献点为:
(1) 设计并实现了一套描述数据特征的关键字;
(2) 支持表、事务和操作中对数据特征定义;
(3) 支持灵活的性能测试负载定义和组织;
(4) 保证负载的生成具有良好的可拓展性.
1 相关工作目前开源的数据库系统测试工具较少, 流行的测试工具主要有以几种: MySQL Test Framework、Sysbench、OLTP-Bench, 小众的测试框架如Woodpecker.
MySQL Test Framework[4]是MySQL数据库开源的一套数据库测试框架.主要针对MySQL系统进行语法和功能适配, 它有着强大的SQL功能测试、简洁高效的测试语法和测试成本低等优点, 但该工具不支持性能测试, MySQL的性能测试需借助第三方测试工具.目前官方维护了1 200多个涉及不同功能模块的可回归的测试案例.
Sysbench[5]是一个开源的、模块化的、可跨平台的多线程性能测试工具, 不仅支持CPU、内存、磁盘I/O、操作系统线程开销的测试, 还支持数据库的性能测试.针对性能测试, 测试人员使用Lua脚本语言编写测试逻辑, 可指定多组测试线程数、测试时长、统计信息间隔等参数.该工具在主键列上创建了若干个分布函数, 也即自定义负载中只支持主键上访问分布的自定义, 数据特征定义缺乏灵活性.
OLTP-Bench[6]是卡耐基梅隆大学开源的性能测试框架, 该框架融合并实现了19个OLTP类型的Benchmark(TPC-C[8]、YCSB[9]、SmallBank[10]等), 针对不同的DBMS, 该工具将SQL和数据库进行适配, 使数据库之间的语法差异透明化.该工具支持测试过程中动态的负载变化和细粒度的统计信息收集, 但该工具不支持自定义性能测试负载.
对于小众的测试框架——Woodpecker[7], 是由华东师范大学数据学院自主研发的一套通用的数据库系统测试框架, 有着良好的测试case复用性和支持高压环境下的功能测试等优点.但其性能测试仅支持增、删、改、查等固定负载且与逻辑代码耦合, 可拓展性较弱.
2 Woodpecker测试框架[7]Woodpecker测试框架是第一个同时支持数据库功能测试、性能测试和系统功能测试的通用数据库测试框架, 相比于功能单一的测试工具, 该框架测试覆盖面更广; 此外, 它还支持简单快速的自动化回归测试, 减少测试人员的干预; 以及能够自动地收集测试结果并生成综合报告, 对于分析测试案例的执行过程和发现系统瓶颈提供有效信息.
Woodpecker设计并实现了一套基于关键字的语义丰富、易用、可高效编写测试任务的测试定义语言(TDL).考虑到当前的许多测试工具使用高级编程语言来描述测试任务, 这其中存在大量重复性工作, 以及不同的数据库系统在所支持的SQL语法上存在差异, 测试案例在不同的数据库上运行需要进行额外的适配工作, 这都将影响测试效率.所以关键字旨在降低测试人员构造测试案例的成本, 相比于测试任务与具体编程语言绑定的方式, 关键字拥有更好的透明性, 测试人员无需关注不同数据库系统之间的差异, 工具本身支持测试案例与多种数据库系统的适配, 使得测试案例做到``一次编写, 到处运行''.
本文的性能测试描述关键字基于Woodpecker的测试定义语言并进行了大量拓展.
3 性能测试关键字定义Woodpecker+的性能测试关键字是要抽象出影响数据库性能的数据特征, 表 1中的关键字支持细粒度控制数据特征的数据生成与导入.
Table用于详细描述表模式. Column用来指定某属性的数据特征, 数据特征包括:空值比例、非重复值个数(基数)、最大值和最小值(对于数值型)、平均长度和最大长度(对于字符串型). IMPORT_TBL负责完成表的创建和数据生成, 可一次操作多张表. CLEAR_TBLv用来删除已创建的表和清空表数据.
表 2展示了事务描述关键字, 这些关键字可以很好的满足测试人员对不同性能测试场景的描述需求.在给出示例之前, 将讨论关键字中重要的参数——操作的数据访问分布(distribution_type).
针对OLTP应用, 操作的不同访问分布对性能结果有很大影响. Woodpecker+的关键字支持多种访问分布来模拟事务的读写比例分布和冲突强度.目前工具支持4种访问分布类型: unique(min, max), uniform(min, max), normal(min, max, sigma), zipfian (min, max, size, skew). 4种分布中的min和max确定生成数据的范围(阈值).
(1) unique为唯一值分布, 它顺序生成min和max间的所有值.
(2) uniform为随机分布, 它在min和max间随机产生数据.
(3) normal为正态分布(高斯分布), X~N(μ, σ2)中的参数μ(均数)默认为0, σ(标准差)需要由测试人员指定.
(4) zipfian分布为齐夫分布, size为给定的元素数量, skew为指数, 该值越大说明访问分布越倾斜, 实际使用中size和skew一般取较小的值(如size=10, skew=2).
表 3展示了如何利用给定的关键字定义表数据特征的案例. Student表为学生基本信息, 该表有100条数据记录、4个属性列、其中主键为STU_ID.为了简化, score为某门课程的全体学生成绩, 主键为STU_ID. COLUMN为指定列的数据特征, 对于COLUMN的前4个参数, 分别为表名、属性名、空值比例和基数(非重复值个数).最后两个参数为属性范围, 对于字符型属性, 分别为最短长度和最大长度; 对于数值型属性, 分别为最小值和最大值.
表 4包含两个事务, 每个事务均指定了执行比例和事务名, 案例中涉及的表为score.行2——4将随机执行SELECT_FOR_UPDATE若干次(MULTIPLE), 次数为1~100之间的随机数. SELECT_FOR_UPDATE操作以STU_ID为过滤条件, 按照正态分布(NORMAL最小值为1, 最大值100, μ默认为0, σ取1)访问score表, 并将查询结果按照SC_AVG降序排列.行9将对score表所有SC_AVG大于90分的学生等级设置为A, 访问分布为UNIQUE的唯一值分布.行10——13的分布结构包含两个分支(BRANCH), 每个分支的执行概率分别为0.8和0.2, 行15以异步的方式加载事务(TXN_LOADING), 在一台负载机上开启10个数据库链接, 每个链接执行100次.
4 Woodpecker+架构如图 1所示, Woodpecker+总体上可以分为两个部分, 客户端和数据库服务器端.其中客户端模块有5个核心组件:解析器、数据库适配器、负载分发器、负载机和统计信息收集, 数据库服务器端即为待测的DBMS服务器和资源监控器.
测试流程大致如下:解析器首先读取测试环境的配置文件和测试案例, 生成负载任务(可能有多个)传递给负载分发器.解析的过程中数据库适配器将测试案例中的某些数据类型或语法适配为目标DBMS所支持的类型.接着负载机(可能有多个)接收负载分发器发送的负载任务并以多线程的方式将数据库操作发送给DBMS执行.执行过程中, 统计信息收集模块实时收集负载机端计算的性能测试结果和数据库服务器端的硬件资源消耗指标, 并在测试结束时生成综合性能报告.负载管理器在发送新的负载任务前, 需确保上一个负载任务全部执行完成, 统计信息收集模块给通知负载管理器上一个负载任务是否已完成, 负载分发器再决定是否发送新负载.
解析器和负载分发器是客户端中重要的模块.解析器读取测试人员定义的测试案例(包括表模式、具体事务操作、加载事务的线程数、每个线程执行次数、负载个数等)和测试环境配置文件(包括DBMS端IP地址和端口号、负载机个数及IP地址集合、Netty通信端口号等), 使用JavaCC[11]完成词法、语法解析生成负载任务并传递给负载分发器.负载分发器负载任务逐个分发给负载机, 由于负载机可能为多个, 且不同负载机的完成情况是异步的, 所以统计信息收集模块采用同步操作来保证所有负载机均成功反馈上一个负载任务的执行结果, 并通知负载分发器上一个负载任务已完成, 这时负载分发器才发送新的负载任务.最后, 当所有的负载任务均执行结束, 负载分发器通知关闭所有负载机上的监听进程, 结束本地客户端进程.若发生异常, 则提前结束.
4.2 负载机实体性能测试的目的是在高压环境下测试系统的性能峰值.负载机是连接负载分发器和DBMS系统的中间模块, 用来完成高并发的数据请求操作.为了不让发送操作请求的速度成为测试瓶颈, 选择开启多台负载机, 每台负载机以多线程的方式同时发送数据库操作请求.在实现时每个线程对应一个数据库链接.并不意味着更多的数据库链接就可以测试出更高数据库性能.实验发现, 一旦数据库链接数超过数据库固有的最大链接数(尽管可手动将该值调大), 系统的吞吐量下降, 延迟大幅度增加.原因为当某一时刻的请求速率使得数据库达到最佳性能后, 之后大量的请求将阻塞在请求队列中, 这时数据库处理能力已达到上限, 大量未处理的操作增加了操作延迟, 导致系统的吞吐量下降.此外, 负载机还负责计算操作执行的性能指标(如吞吐量和百分比延迟), 并实时反馈给统计信息收集模块.
4.3 统计信息收集模块由于性能测试可能耗时较长, 如果在整个测试任务的执行过程中, 测试人员无法知道测试进展, 这对测试人员是难以忍受的.所以统计信息收集模块负责汇总各个负载机计算的性能指标, 如当前正在执行的事务ID、负载完成进度(100%为完成)、瞬时TPS、测试时长、50%、90%、95%、99%延迟、每个操作的平均延迟等; 该模块还收集数据库服务器端的硬件资源使用情况, 由于测试任务可能包含多个负载, 而每个负载由所有负载机同时执行, 这将导致各个负载机的统计信息到达是异步的, 所以统计信息模块收集到所有负载机的统计信息才通知负载管理器发送下一个负载.
4.4 数据库适配器由于数据库产品的多样性, 许多DBMS在支持的数据类型和数据定义语言(DDL)上存在很大差异, 为了使关键字编写的测试案例能够以尽可能小的代价复用于多种数据库系统, 工具提供了数据库适配器模块, 该模块自动将测试案例中的一些数据类型和DDL操作转换为目标DBMS支持的类型和正确的语法格式.这样测试人员只需专注于构造测试案例, 而无需考虑底层数据库系统某些细节上的差异.这极大地提高了测试效率.
4.5 资源监控器对于DBMS所在的服务器, 负载任务的高并发执行导致系统硬件资源处于紧张占用状态, Woodpecker+使用轻量级的开源系统监控工具——nmon[12], 对系统的CPU、内存、磁盘读写速度、磁盘I/O和网络带宽等资源进行监控.这些系统指标能够更好地帮助测试人员理解负载的执行过程并为发现系统瓶颈提供有效信息.资源监控器在测试结束时将所有的统计信息发送给客户端的统计信息收集模块.
5 实验 5.1 实验设置首先, 以几种常用的标准OLTP类型的Benchmark作为实验对象, 比较Woodpecker+与OLTP-Bench工具[6]对这些Benchmark实现的代码行数, 展示本文工作的高效性和用户友好性.其次, 通过比较Woodpecker+与Sysbench工具[5]对测试案例的组织方法, 展现工具的灵活性.再者, 展示Woodpecker+对分布式数据库系统测试方面支持, 表现其测试功能支持的全面性.最后, 展示Woodpecker+在负载生成上的可扩展性.
实验使用了3种数据库产品, 分别是开源集中式数据库MySQL 5.7.18和PostgreSQL 9.6和分布式开源数据库CBase 1.2[13].它们部署在同一集群环境下的4台服务器上, 每台机器CPU为两个Intel Xeon(R) E5-2620, 共12核24线程, 内存128 GB, 1 000 Mbps以太网, 操作系统CentOS 6.8.其中MySQL和PostgreSQL部署在同一台服务器, CBase由于是分布式系统, 且为了降低控制节点(RootServer)和事务处理节点(UpdateServer)的压力, 它们分别部署在两台服务器上, 基线数据存储节点(ChunkServer)和接口服务节点(ChunkServer)部署于同一台服务器.
5.2 TDL测试案例构造代价对比以几种OLTP类型的Benchmark作为实验对象, 比较OLTP-Bench中Java实现这些负载所需要的代码行数和Woodpecker+中TDL实现所需的代码行数, 发现Java需要上千行代码控制负载的执行与信息统计, 而TDL编写的测试案例只需几十行代码便可清楚描述负载任务, 实验结果对比见表 5, 大约降低了两个数量级的工作量.相比于Java任务描述于代码绑定的方式, Woodpecker+中TDL的测试案例具有更好的复用性和灵活性.这极大地降低了测试人员构造测试案例的代价.
该组实验对比了Woodpecker+中的TDL与Sysbench提供的方法对读密集型负载组织的差别, 见表 6和表 7. SmallBank共有6组事务, 其中有1组纯读事务, 另外5组事务仅包含Select和Update操作, 其中有4组事务仅有一条Update操作且均为最后一条操作.为了设计读密集型负载, 该组实验移除了SmallBank中所有事务中的Update操作, 并保持原有事务的执行比例, 通过Woodpecker+定义的关键字组织SmallBank中的负载实现对读密集型任务的定义, 如表 6.其中所有的事务均以预编译的方式执行, 3张表共导入300万条记录, 每条操作的数据访问方式为unique(0~999 999), 过滤条件为账户表主键.
在表 6的测试案例中, 定义了3张表和6组事务, 表模式和事务操作均可自定义, 表模式可设定表大小、属性名、数据类型和主外键列等信息.事务可设定执行比例, 事务中操作可包含表名、是否预编译执行、访问分布类型和过滤条件.
表 7为相同负载针对MySQL语法在Sysbench中的实现的部分代码, 行2——23创建账户表, 剩下的两张表和创建账户表相似.行25——36执行第一组事务, 事务的3条操作对应3个独立实现的函数. Sysbench默认只实现了Uniform随机分布, 其他的访问分布需额外实现.该方式测试案例的语法针对DBMS定制, 比如在表 7中, CREATE TABLE ACCOUNT(custid INTEGER NOT NULL, name VARCHAR DEFAULT '0' NOT NULL, PRIMARY KEY(custid))中的数据类型适用于MySQL数据库.在多DBMS的适配上存在缺陷, 而且功能代码冗余, 难以复用在其他负载上. Woodpecker+提供的案例组织手段则可方便地直接迁移到其他数据库上进行测试, 提供了负载任务定义灵活性的同时降低了测试代价.
5.4 分布式系统性能测试表达能力分布式事务是分布式数据库设计和实现的难点之一.目前许多的NewSQL系统声称支持分布式事务[14], 但分布式事务的复杂性使得简单的SQL功能测试不一定能完成全面的分布式DBMS的测试. Woodpecker+同样提供了丰富的关键字以支持分布式系统的分布式事务性能测试, 通过在创建表模式时使用PARTITION_BY关键字与哈希分区函数绑定来实现数据分布, 通过向分区规则表和Paxos系统表插入分区函数和Paxos组信息来实现事务分组, 通过设置INSERT、UPDATE和REPLACE操作的访问分布来提高分布式事务的发生率.
该组实验对象为CBase1.2分布式数据库系统, 该版本拓展单事务处理架构(单Paxos组)为多事务处理结点架构(多Paxos组), 本文关注多事务处理结点架构下的分布式事务测试.分布式事务测试往往需要将数据和负载进行映射[15], 原始测试方法需手工准备测试环境, 包括分区集群部署、分区规则创建、表模式创建和数据导入, 然后才运行负载, 测试过程难以自动化和回归. Woodpecker+可自动化分布式事务测试, 测试案例中可同时兼容SQL功能性语句和事务操作, SQL功能性语句的支持能解决测试人员的介入问题, 提高了测试灵活性.
为了验证CBase系统的分布式事务处理性能, 该实验构造了一个混合负载, 负载包括一组事务, 事务包含4条操作:主键的单点查询、二级索引列上的范围查询、主键的单点更新、随机主键的替换操作.负载分别在双Paxos组和三Paxos组情况下测试, 为了提高分布式事务发生的概率, 数据均匀的分布在Paxos组中, 事务中操作的访问分布与Paxos组数据的分区保持一致.
表 8为双Paxos组情况下的分布式事务, 行1创建一个数据库链接, 行3向Paxos系统表插入Paxos组信息, 行7向分区规则表插入哈希函数.行11中PARTITION_BY关键字使USERTABLE表的数据按照相应的哈希函数进行分区, 行13导入数据, 行15——20执行事务操作, 行18——19中操作的访问分布均分了0 200万行的数据, 即UPDATE和REPLACE两条操作分别发送到两个Paxos组的主系统上处理, CBase使用两阶段提交协议处理分布式事务[16], 分布式事务的比例达到100%.若同样的分布式事务在Sysbench或者OLTP-Bench上执行, 需要先手动创建哈希规则、向Paxos组系统表中插入分区记录和导入数据(这一步骤对应表 8的行1——13), 最后再执行测试负载, 不支持事务操作和基本SQL操作的混合, 测试过程无法自动化.
为了验证负载的可拓展性, 该组实验使用YCSB读写混合负载作为实验负载, 在MySQL、PostgreSQL和CBase1.2上测试.为了使负载机所能创建的最大链接数不影响测试结果, 该实验使用多台负载机, 每台负载机均分总的链接数并限制每台最多建立40个链接.通过不断的增加负载机和DBMS之间的链接数, 统计负载在稳定后所能达到的最大TPS, 以及此时DBMS服务器端CPU的使用率.测试案例定义如表 9, 性能结果如图 2.
表 9为负载可拓展定义测试案例, 行1创建表模式、行5导入数据, 行6——11执行事务操作, 行14是该组测试的重点, 当测试人员指定了上述3个参数后, Wood-pecker+从最小线程数10开始, 逐次增加10个线程(该间隔可修改)直到等于最大线程数为止运行多次测试负载, 线程数指定后, 线程的执行次数等于表总数除以线程数, 负载机个数则根据线程数动态调整.每一次的运行结果将单独打印出来, 该组实验打印12组测试结果. Sysbench虽然支持多组线程指定, 但是在负载机上均分线程时易出现异常, 即负载机上建立的链接数容易达到链接上从而限导致测试结果不准确, 该现象可能由Sysbench的线程池策略导致. OLTP-Bench不支持多组线程指定.
图 2左为吞吐量和链接数的关系, CBase1.2和MySQL在使用50个链接数时, TPS基本已达到上限, 之后再增加链接数, 性能均无明显变化. PostgreSQL在链接数增加的过程中, TPS逐渐上升, 在链接数为90时, 达到最大吞吐量.总之集中式数据的性能要高于分布式系统.这是因为分布式数据库除了要保证数据的正确性外, 还要保证数据的可靠性, 如多日志同步机制, 多副本备份等, 这都增加了系统的开销.
图 2右为CPU利用率和链接数的关系, 结合图 2左可知, 随着链接数的增加数据库的吞吐量逐渐提升, 该过程CPU的利用率变化符合预期, 即CPU利用率与链接数成正比, 并在数据库达到最大吞吐时维持稳定.
吞吐量和CPU利用率是衡量数据库性能的重要指标, 相比于手动的统计信息收集, 自动化的统计信息收集显得尤为重要, 该组实验所有的统计结果均来自Woodpecker+的统计信息收集模块, 该模块生成结构化日志, 对分析负载执行过程及发现系统瓶颈提供帮助.
6 总结本文提出了可基于数据特征的自定义负载评测工具——Woodpecker+, 提供了语义丰富, 简便易于的测试关键字, 以及支持灵活负载组织方式, 测试人员编写的测试案例复用性高, 工具拓展性良好.通过实验验证了TDL测试案例的构造代价对比和读密集型负载组织和分布式事务性能测试以及验证了两种类型数据库的可拓展性.
[1] |
GEORGE L. HBase:The Definitive Guide:Random Access to Your Planet-Size Data[M]. CA: O'Reilly Media, 2011.
|
[2] |
STONEBRAKER M, WEISBERG A. The VoltDB Main Memory DBMS[J]. IEEE Data Eng Bull, 2013, 36(2): 21-27. |
[3] |
NVM[EB/OL].[2019-01-14]. https://en.wikipedia.org/wiki/NVM.
|
[4] |
ORACLE. The MySQL Test Framework[EB/OL].[2017-07-12]. https://dev.mysql.com/doc/mysqltest/2.0/en/.
|
[5] |
SysBench: A system performance benchmark[EB/OL].[2019-01-14]. https://sysbench.sourceforge.net.
|
[6] |
DIFALLAH D E, PAVLO A, CURINO C, et al. OLTP-Bench:An extensible testbed for benchmarking relational databases[J]. Proceedings of the VLDB Endowment, 2013, 7(4): 277-288. DOI:10.14778/2732240.2732246 |
[7] |
ECNU[EB/OL]. Woodpecker.[2019-01-14]. https://github.com/Gizing/Woodpecker.
|
[8] |
Transaction Processing Performance Council (TPC)[EB/OL].[2019-01-14]. http://www.tpc.org.2011.
|
[9] |
COOPER B F, SILBERSTEIN A, TAM E, et al. Benchmarking cloud serving systems with YCSB[C]//Proceedings of the 1st ACM symposium on Cloud computing, New York: ACM, 2010: 143-154.
|
[10] |
CAHILL M J, ROHM U, FEKETE A D. Serializable isolation for snapshot databases[J]. ACM Transactions on Database Systems, 2009, 34(4): 1-42. |
[11] |
KODAGANALLUR V. Incorporating language processing into Java applications:A JavaCC tutorial[J]. IEEE Software, 2004, 21(4): 70-77. DOI:10.1109/MS.2004.16 |
[12] |
Nmon for Linux[EB/OL].[2019-01-14]. http://nmon.sourceforge.net/pmwiki.php.
|
[13] |
CBASE. Bank of Communications[EB/OL].[2019-01-14]. https://github.com/BankOfCommunications/CBASE.
|
[14] |
YAN C, CHEUNG A. Leveraging lock contention to improve OLTP application performance[J]. Proceedings of the Vldb Endowment, 2016, 9(5): 444-455. DOI:10.14778/2876473.2876479 |
[15] |
杨传辉. 大规模分布式存储系统原理解析与架构实现[M]. 北京: 机械工业出版社, 2013.
|
[16] |
Two-phase commit protocol[EB/OL].[2019-01-14]. https://en.wikipedia.org/wiki/Two-phasecommitprotocol.
|