Skip to content

中文 系统实现

domino-succ edited this page Nov 1, 2014 · 5 revisions

系统架构

现有的分布式事务引擎架构设计可以分为两类,集成于客户端,如Percolator、HBaseSI、HAcid等;亦或将事务控制模块独立出来,形成一个事务控制系统,如ReTSO、CloudTPS等。

将事务处理完全放置于客户端会带来更大的不可控性,客户端软硬件环境的稳定性与性能都不可预知,因此事务系统的处理性能不得而知;同时将事务处理逻辑完全放在客户端将大大增加客户端与服务端的通信成本,很多不必要的信息,如数据行和事务表中包含的事务信息都必须通过网络传递给客户端,还有,在事务处理过程,一个读写请求可能包含的对同一行数据的多次读写,而这些全部都需要客户端与服务端进行多次远程通信才能完成,因此是非常浪费的。

独立的事务处理系统也有很多弊端。对于事务处理系统本身,其可扩展性及可用性都需要经过长时间大规模测试才能被验证,而现有的事务引擎如ReTSO、CloudTPS等,要么是本身的设计就缺乏可扩展性,要么则是没有经过长时间大规模测试以及广泛的应用。同时,独立的事务处理系统除事务本身的并发控制之外,还带来了大量其他的设计、开发及维护开销,如事务处理集群的分布式管理、维护与升级等。

在Domino的系统架构设计中,我们使用HBase Coprocessor框架实现了一类全新的事务处理集群——依附于HBase RegionServer的Domino Endpoint Transaction System(DETS),DETS扩展性与HBase一样强大,路由协议也完全基于RowKey,省去了大量开发与维护成本的同时,还增强了系统可控性与可靠性,免去了绝大部分不必要的客户端与服务端的网络通信,从而大大提升了事务系统的性能。同时,Domino还利用Coprocessor框架实现了事务ID的生成模块(Domino Timestamp Oracle,DTO)以及事务元数据管理模块(Transaction Metadata Endpoint,TME),下图展示了Domino的系统架构设计。

<IMG SRC=http://g.hiphotos.bdimg.com/album/s%3D1400%3Bq%3D90/sign=63d157e652da81cb4ae687c96256eb67/14ce36d3d539b600f44cb0a5ea50352ac65cb726.jpg>

接口设计与使用(API)

Domino接口设计的原则是尽可能接近HBase原生接口,以降低使用者的学习成本,同时为使用者提供出显式的事务控制。目前,Domino只提供Java版本的接口。

客户端初始化

Domino提供了两种客户端的初始化方式:以ZooKeeper集群地址作为参数,或以Configuration对象作为参数,Domino对象封装了数据表管理以及获得事务句柄的接口:

  • public Domino(String zookeeperAddress) throws IOException;
  • public Domino(Configuration config) throws IOException;

Domino句柄:数据表管理与事务开始

1)表的创建,使用HBase的HTableDescriptor作为参数传递给Domino Client,在真正创建表之前,Domino Client会将Domino内置列簇加入Descriptor中,内置列簇附带In Memory属性,尽可能地提高事务元数据的存取性能;与此同时,Domino还提供了带有split参数的表创建接口:

  • public void createTable(HTableDescriptor table) throws IOException;
  • public void createTable(HTableDescriptor table, byte[][] splitKeys) throws IOException;
  • public void createTable(HTableDescriptor table, byte[] startKey, byte[] endKey, int numRegions) throws IOException;

2)表的删除:

  • public void dropTable(byte[] name) throws IOException;
  1. 开始一个事务,返回一个事务句柄,用来进行事务操作:
  • public Transaction startTransaction() throws IOException;

事务操作

事务的提交、回滚以及存取操作都被封装在Transaction类中。

  1. 读取一条记录,使用HBase的数据结构Get作为参数,返回HBase Result数据结构返回数据:
  • public Result get(Get get, byte[] table) throws IOException;
  1. 写入一条记录,使用HBase的数据结构Put作为参数,通过这个接口写入的数据被认为是无状态更新的数据,即put中的值不应该是根据任何之前在本事务中读取过的数据得来的:
  • public void put(Put put, byte[] table) throws IOException;
  1. 写入一条有状态更新的记录,若参数put中的值基于任何之前在本事务中读取过的数据,则必须使用这个接口来保证数据的完整性:
  • public void putStateful(Put put, byte[] table) throws IOException;
  1. 删除一条记录,指定Rowkey,删除整行数据:
  • public void delete(byte[] row, byte[] table) throws IOException;
  1. 扫描一段记录,使用HBase的数据结构Scan作为参数,返回一个ResultScanner句柄:
  • public ResultScanner scan(Scan scan, byte[] table) throws IOException;

scan返回的句柄是Domino实现了ResultScanner的实例,由于scan方法的特殊性,无法把scan的处理完全放在DETS中,因此,客户端在scan时会对扫描得到的数据进行简单的处理,若遇到.status数据,Domino Client会将本行记录发回DETS做单行读取的处理,保证了scan读取数据的一致性。

  1. 事务的提交:
  • public void commit() throws IOException;
  1. 事务的回滚:
  • public void rollback() throws IOException;

具体程序示例可参考:"https://github.com/domino-succ/domino/blob/master/example.java".

系统部署

Domino的部署非常简单,先决条件只有一个,就是具备一个可以运行的、版本高于0.94的HBase集群。Domino有4个库文件:

  • domino-client-.jar,Domino的客户端类库;
  • domino-common-.jar,Domino的Common类库;
  • domino-core-.jar,Domino的核心Endpoint类库;
  • domino-id-service-.jar,Domino的事务ID服务类库。

在启动HBase集群前,需要将4个Domino的库文件放入所有HBase部署目录的lib文件夹下,放置完毕后,启动HBase,Domino的服务端就全部部署完成了。

客户端在使用Domino时,只需将domino-client、domino-common及domino-id-service三个库文件包含到Java Build Path中即可。 Domino的所有内置表都会在HBase初始化时自行创建好,在使用时,需要用到Domino事务特性的表全部都要使用Domino提供的表创建接口进行。

另外,由于Domino的设计思想用到了HBase数据模型的Timestamp维度,所有数据的Timestamp维度都由Domino管理,所以任何设置了Timestamp的Get、Put和Scan参数都会被Domino重写,通过Domino进行管理的数据在使用者角度来看将失去Timestamp特性,数据模型更加类似于传统的关系模型

系统测试

TPC-C

TPC-C是测试在线事务处理系统的标准benchmark,由于传统的TPC-C应用于关系数据库的接口测试,即数据库必须提供标准SQL接口才能使TPC-C运行起来,我们开发了一套可以应用于Domino的key-value接口的TPC-C测试集,详见:https://github.com/domino-succ/tpcc-hbase。实际上,TPC-C的最大特点是针对交易货单的增删减操作,均可看做有状态更新,其特点并不能体现SSCC在处理无状态更新的性能优势。当然,由于Domino/SSCC去掉了两阶段提交(2PC),以及无回滚IO和辅助提交,这些优化机制能够提升整体性能。从测试结果看,在TPC-C测试集上已经和Oracle专有的数据库盘阵服务器的性能不相上下。

Microbenchmark

Microbenchmark是Google提出的一套测试集,其动机是测试事务引擎与底层存储引擎的性能对比。事务引擎一般都架设在某个存储引擎之上,例如Mysql的存储引擎可以是innodb也可以为MyISAM,那么事务引擎必然对存储引擎带来性能损耗。由于Google Percolator是建立在Bigtable上的,Microbenchmakr的直接目的是测试Percolator对bigtable的性能损失程度,测试结果为Percolator的读写性能与单独使用Bigtable的读写性能的比值R。直观上理解,事务引擎设计的越合理,其对存储引擎的性能损失就越小,那么R的值就越接近于1。

在Microbenchmark中,一个事务仅包含一条读操作或一条写操作,完成操作后即提交。数据库中的数据全部是随机生成的,读写操作的内容也是完全随机的,在进行一定量的读写测试后计算出系统的TPS(每秒处理事务数),对Percolator测试完毕后,以同样的方式测量出直接使用Bigtable接口进行读写的TPS,两者相除得到的比值R,就是测试的结果。

Microbenchmark的操作最大特点为短事务,并且是短事务的极端情形,即一个事务只有一条读或写操作。这样做的目的是为了和底层存储引擎的读写操作相比较,即每秒事务数与每秒操作个数的比值。虽然Percolator的读操作必须等待写操作完成,但是在短事务的情形内,读等待时间会很小。更加重要的一点,由于Bigtable本身实现了单行事务,因此即使发生读写冲突,由于短事务的原因,Percolator的读写冲突基本等同于Bigtable的单行事务操作,因此在Micorbenchamark下,percolator的读操作性能很高(R=0.95)。因此和实际事务情形相比,Micorbench的事务构造有较大局限性。在实际测试中,可以对Microbenchmark进行适当调整,特别是增加事务内包含的操作数量。和存储引擎相比时,不使用TPS,而使用事务包含的操作数量。

下一步测试

对于TPC-C,设置不同的有状态更新概率;对于Microbenchmark,增加事务内的操作数量。对比系统选用目前比较流行的事务系统或者工业界系统。