简介
C- JDBC是位于在数据库和数据访问层之间的一层黏合剂。对于数据库层,它支持Raid0(表空间分割),Raid1(完全镜像),Raid2(Raid0 和Raid1的混合)等几种Raidb(廉价数据库冗余阵列)。在大数据访问的情况下,C-JDBC能够均衡在这些节点之间的查询负载,提高性能。
基本原理
通 过XML配置,设置一个虚拟的数据库服务器,在这个虚拟的数据库服务器下可动态配置多个不同厂商的物理数据库(如 mysql,DB2,Orcale,sqlserver等)。外部Sql请求均发向这个虚拟数据库,JDBC控制器的RequestManager负责接 受该Sql请求,根据XML配置中的Request Scheduler(Raidb)类型,进行相应处理。
编 程实现上,C-JDBC提供一个JDBC驱动器,可以完全与传统的JDBC编程无缝融合(驱动器名称 为"org.objectweb.cjdbc.driver.Driver")。Java代码中,只需出虚拟数据库的名称、用户名、密码信息,不受物理数 据库变化的影响。因而,降低了数据库和数据库访问层代码之间的耦合性。也就是说,对于历史性的代码,只需更改数据访问层代码中的驱动器名称 和数据库链接字符串 ,加上一个XML配置文件即可。
载体
一个 JDBC 驱动器, C-JDBC 控制, XML 配置文件,后台管理工具。
优点
降低耦合性,高效,扩展性,架构灵活,免费开源。
缺点
1. 后台管理工具不是很健壮,在搭建例子的时候经常莫名其妙地出错。
2. C-JDBC 对数据库的管理还不是很聪明。数据库在加入到 C-JDBC 的虚拟服务器中之前,需要手动进行统一初始化数据库的操作;之后,不能再进行手工更改某个数据库,否则会使数据不同步,而 C-JDBC 对此一无所知。
下一步计划
以上只是我这几天来对 C-JDBC 的初步印象,目前搭建了 HyperSonic 和 Mysql 两种数据库集群,简单测试一下了,效果不错。下一步计划有:
1. 优点都是抄文档的,需要进一步证实 C-JDBC 的可行性。
2. 花一些时间仔细研读文档,进一步了解 C-JDBC 。
3. 有选择性地阅读 C-JDBC 代码,然后可以根据需求,进行一些修改。
名词解释
Virtual database: Sequoia 对外公开的虚拟的单一数据库接口 , 可能由多个物理数据库组成。
Backend: 属于某个 Virtual database 下的数据单元,封装了一个物理数据库。
Scheduler: 负责制定具体策略来调度 sql 请求,实现串行执行。 Sequoia 提供 SingleDB 和 RAIDb-x 等几种。
Recovery Log: 日志, 记录所有通过 Sequoia 进行的写操作和事务。
Load Balancer: 根据 RAIDb 配 置 , 负责将 sql 请求在 backend 之间进行分发。
Controller: 由 几个模块组成 ,负责 virtual databases 管理。
Console: 管理 Controller 的字符界面。
进展
在了解 C-JDBC 的基本原理的基础上 , 针对性地深入学习基本框架中的几个核心模块。首先解释一下 Sequoia 与 C-JDBC 的关系 : Sequoia 是 C-JDBC 项目的扩展。 Sequoia 的出现也说明了 C-JDBC 的成熟性 , 目前 Sequoia 是由一家法兰西公司 INRIA 的一个 Team 维护(有 8 个 fulltime 的工程师)!
Sequoia 2.10.6 针对于 C-JDBC 的改进主要有
a) 一系列的 bugfixed
b) Jgroup 升级
c) Recovery 速度提升
d) PreparedStatement 序列化改进
e) 重新设计事务,提高并行性
故以后不再使用 C-JDBC ,代之 Sequoia 。( userguide-4.1. What is new with Sequoia ) Sequoia 提供的文档较之 C-JDBC 更为详细,主要有 userguide.htm , installGuide.pdf 和 adminGuide.pdf 三个。下图摘自文档,近期主要了解了 Load Balancer , Scheduler , Recovery Log 三个模块, virtual database 的 backend 使用 mysql 。
具体实验 & 体会
1 Sequoia 备份注意事项
Sequoia 的 Controller 需要管理多个 virtual database 及下属的 backend 。 Sequoia 提供一个字符控制台 Console ,管理员可以通过 Console 对 virtual database 进行管理。以下是我的一次实验日志 ( userguide-7.5. Recovery Log ) :
1. 编写数据库生成 / 卸载脚本 dbinit.sql
2. 执行 dbinit.sql
3. 启动 Sequoia 服务(有两个 backend A,B )
4. 启动 Sequoia console
5. 插入数据 MySql_GenerateData
6. disable A
7. 插入数据 MySql_GenerateData
8. backup B mydump Octopus backup\ ( 备份数据库 )
9. restore backend A mydump
注意: Sequoia 可以使用多种 Backuper 来做数据库备份,如果使用 Octopus 备份数据库时, Octopus 会在 controller.bat( 不是 console.bat) 中 ,查找目标数据库的 jdbc 驱动器并借此调用该数据库的备份服务。故需将相应数据库 jdbc 的所在文件路径添加到 classpath 中,防止找不到驱动器而导致备份失败。
例如: MySql 的 jdbc 驱动器 mysql-connector-java-5.0.5-bin.jar 在 E:\sequoia\drivers\ 下,那么在 controller.bat 中需要
SET CLASSPATH=E:\sequoia\drivers\mysql-connector-java-5.0.5-bin.jar;...
2 搭建“全 mysql ”环境
Sequoia 需要一个 Recovery Log ,其保证数据的完整性和可恢复性。 Recovery Log 需存储在一个数据库中(默认提供的例子使用 hypersonic )。 Recovery Log 的配置信息放在 virtualdatabase.xml 中,在 RecoveryLog 子节点下,有 RecoveryLogTable , CheckpointTable , BackendTable 和 DumpTable 四个标签,这些表标签供在目标数据库上创建相应表的 schemca 使用。
如果不设置这四个标签, Sequoia 会在目标数据库上创建默认规则 (hypersonic) 的 Schema 。值得注意的是,默认设置中有一个字段定义为“ sql varchar not null ”。如果使用默认设置,则无法在 mysql 上搭建 Recovery Log 。因为在 Mysql 上(我使用的是 MySql5.0 ):
1. 如果使用了 sql 关键字(如 int 、 sql 、 select 等)相同的文字作为变量,需要在变量两边加上 `` ,以示区分。 Sequoia 似乎在创建脚本时没有考虑这个因素,故在生成 createlogtable 时,创建字段 sql 出错。
2.mysql 不支持 varchar 后面不加长度 ,mysql 一般是 varchar(4),varchar(20)…
目前的解决方案:在更改 virtualdatabase.xml 中更改 sqlColumnName , sqlParamColumnType 等标签。如 sqlColumnName="mysql" sqlColumnType="VARCHAR(200) NOT NULL" sqlParamColumnType="VARCHAR(2) NULL"
( installGuide.pdf-5.1.3 virtual database configuration files p35 )
下一步计划
1. 进行 RAIDb-1 测试,之所以先进行 RAIDb-1 是因为其相对比较简单,而且 Sequoia 还提供了可以运行的例子 ( 还是基于 hypersonic 的 ) 。计划通过 RAIDb-1 的测试,进一步掌握 Squoia 的基本性能参数。
2. 进行RAIDb-0测试,也就是我们的方向——表数据空间分割。
压力测试结果
1. 测试场景
我使用两种测试方式,一是简单稳定性测试,使用多线程模拟数据库访问,如下图,每个线程不定期间进行随机的读写操作。运行12小时后,检查数据库完整性,发现数据完备,性能稳定,没有逻辑错误。而是压力测试,进行集中大规模的数据读写。
稳定性测试场景:
/ -------------------------- (select 100)
/ -------------------------- (insert 50)
< .....
\ -------------------------- (select 100)
\ -------------------------- (delete 1,2)
压力测试场景:
UserDALHelper.AddTest(r);//随机生成10条数据插入
UserDALHelper.AddTest(r);
UserDALHelper.DeleteTest(r);//随机删除两条数据
UserDALHelper.AddTest(r);
UserDALHelper.DeleteTest(r);
2 测试数据
结果如下
RAIDb-1 adhoc db operations
单机-original jdbc opertaion avg 210
单机-2backend : avg 177 sec
单机-single DB withour sequoia: avg 188 secs
双机-2backend : Sequoia 1000 time adhoc db operation on a dual-machiche : avg 245 sec
双机-3backend : Sequoia 1000 time adhoc db operation on a dual-machiche : avg 274 sec
Sequoia架构分析
Sequoia 架构还是比较复杂的,代码总行数达178072行,所以这里简要介绍一下Sequoia的内核。首先提供两幅关于Sequoia多线程 C/S(Driver/Server)架构的示意图,以期大家能快速得到一个Sequoia大体印象,随后是一段以程序启动为线索的详细描述(描述看起来 可能会有点罗嗦,不过信息是最详实的)。
1 Sequoia架构示意图
图元素说明
|| 线程同步对象 / ---- |
-------- 线程图示甲 == 线程弱主从关系 ----|----- 线程强主从关系图示
~~~~ 线程图示乙 \ ---- |
<----|----> 线程任务移交
@ 连接请求 socket
图 1:controllerserverthread和controllerworkthread是一种“弱”线程池结构,所谓“弱”是 指,controllerserverthread没有维护一个controllerworkthread列表,也就是说 controllerserverthread不知道当前下属有几个controllerworkerthread。他们之间的线程同步通过 serverthread_pendingqueue来完成。Controllerworkerthread得到从 controllerserverthread中发来的socket后,读取少量信息后马上转交给相应的 VirtualDatabaseWorkerThread。相反,VirtualDatabaseWorkerThread和 VirtualDatabase是“紧凑”线程池,因为VirtualDatabase中VirtualDatabaseWorkerThread有列 表。
图 2:在图1的基础上,引入C/S(Driver/Server)的交互,以客户端driver发送请求开始.0,Sequoia的jdbc driver连接数据库;1,controllerserverthread监听到请求 2将请求socket放入等待队列,然后唤醒所有沉睡的controllerworkerthread;3,某个被唤醒的 controllerworkerthread线程A取出请求socket,并进行相应初始化工作;4,A将后续的数据库工作转交给相应 VirtualDatabase的controllerserverthread线程B;5,B从请求socket中读取sql语句,执行,将结果写回该 socket;6,Sequoia的jdbc driver从socket中解析出返回结果,重构出ResultSet等JDBC对象返回给上层应用。
2 详细描述
以下是对Sequioa的详细描述,其中蓝色字体表示类名。
2.1 程序入口点,xml解析和 org.continuent.sequoia.controller.core.Controller
Controller和VitualDatabase的具体解析工作分别由ControllerParser和DatabasesParser两个解析器完成。
这个两个解析器的位置都在package org.continuent.sequoia.controller.xml。
这个解析工作是通过org.continuent.sequoia.controller.core.ControllerConfiguration来做衔接,
扩展参数通过第三方库完成org.apache.commons.cli。
sequoia 使用ControllerConfiguration负责解析Controller和下属的Virtualdatabases。 ControllerConfiguration本质上是一个哈希表,同时还有几个“桥”方法,通过一系列方法链调用获得控制器相关对象。 ControllerConfiguration使用getController方法返回Controller。
Controller 是一个包含所有控制器信息的实体类。方法溯源ControllerConfiguration.getController方法-->setup方 法-->setUpByXml方法,在setUpByXml方法中通过ControllerParser来负责具体的从xml文件中反序列化出 Controller的所有Dirty Work。
ControllerParser在初始化Controller的主要属性后,还负 责 org.continuent.sequoia.controller.core.security.ControllerSecurityManager 反序列化,下属org.continuent.sequoia.controller.core.ReportManager反序列化还有返回下属 Virtualbases的基本信息(name,autoLoad,checkPoint等),然后通过方法溯源 ControllerConfiguration.setUpVirtualDatabase方法 -->Controller.loadXmlConfiguration-->Controller.addVirtualDatabases 方法负责Virtauldatabases的解析工作。同样,具体的反序列化工作通过DatabasesParser完成。
ps:sequoia中有些类写得很幽默,有个类叫XmlTools(Nicolas Modrzyk写的),其中有个方法为prettyXml ,直译为“好看的xml”;这个方法将xml进行缩进处理,适合人类阅读。呵呵,为什么不叫sexyXml呢?indented xml多么hot啊.;O
2.2 xml解析工作告一段落后,launch()开始启动线程池(还是线程树?)
首 先启动一个org.continuent.sequoia.controller.core.ControllerServerThread,负责监听所 有连接控制器的套接字。如果有人连接控制器,ControllerServerThread就会判断下属ControllerWorkerThread列 表是否有空闲,如果没有就创建一个新的ControllerWorkerThread并启动它;否则就notifyAll所有放在等待处理列表 controllerServerThreadPendingQueue中的所有套接字。
这里需要进一步解释一下:
1. ControllerServerThread和ControllerWorkerThread是一种松散的从属结构,姑且称之为弱主从关系。这也就是为 什么不知该用线程池还是线程树来定义它。ControllerServerThread内并没有维护一个下属所有 ControllerWorkerThread的列表引用,相反,使用包含所有连接套接字的等待处理列表 controllerServerThreadPendingQueue,借此达到Server和Worker之间的同步和通讯。
2. notifyAll是一种更为安全的方案。由于Server并不知道每个下属Worker的具体状态,所以Server无法指定notify某个 Worker,只能通知全部因为controllerServerThreadPendingQueue而阻滞的Worker.
在ControllerWorkerThread中,主要做了以下几个工作
1. 从controllerServerThreadPendingQueue取出一个等待处理的套接字
2. 从套接字取出版本信息,并针对版本进行兼容处理
3. 从套接字取出虚拟数据库信息,根据相应的活跃线程数进行VirtualDatabaseWorkerThread创建处理
4. 创建套接字输出流,输入流,并加入到该虚拟数据库的等待处理列表中
那 么,这个套接字的处理工作就移交到相应的VirtualDatabase来负责,同样具体的dirty work由VirtualDatabaseWorkerThread来处理。类似的VirtualDatabase- VirtualDatabaseWorkerThread关系和ControllerServerThread- ControllerWorkerThread很相似。不同的是:
1. VirtualDatabase是一个实体类而非线程
VirtualDatabase维护强主从关系。其中有两个重要的列表,activeThreads维护当前空闲的线程,pendingConnections维护当前等待处理的连接请求
2.ControllerWorkerThread是最终的协议解析者。run方法中做了以下几步工作:
a) 从Virtualdatabase等待队列上获取一个套接字连接
b) 读取用户信息并进行验证
c) 通过该套接字连接获取所有sql请求
d) 解析请求,并将请求封装成AbstractRequest的派生类,转交给所有的Virtualdatabase执行。
解析请求工作由requestFactory负责,Sequoia根据配置文件中的信息创建实例,目前只有一个SequoiaRequestFactory,其通过SequoiaRequestRegExp来定义sql正则解析规则。
3. 在backend上执行sql,并将结果输出到套接字上。在获取和传送执行结果上,Sequoia使用ControllerResultSet存储 ResultSet中的数据。ControllerResultSet是一个轻量级的结果集,使用ArrayList来存储数据。
CJDBC研究5--Raidb-0x扩展
在 研读Sequoia代码的过程中,我发现我们表分割需求跟Raidb-1很相近。Raidb-0是把某一个表放在某一个具体的物理数据库上,这样用户在请 求某个表的数据时实际上只连接了某个数据库节点,不过Raidb-0中的表是不可以再进行分割。而我们需要把一个表再进一步打碎,将数据散落在几个数据库 中。我将表分割定义为Raidb-0x,根据Raidb-0,扩展了从xml解析,数据库处理到异常处理一系列的类。具体操作过程如下:
1. 定义节点 在./xml/ sequoia.dtd 中定义一些xml节点
<!ELEMENT RAIDb-0xScheduler EMPTY>
<!ATTLIST RAIDb-0xScheduler
level (passThrough) #REQUIRED
>
<!ELEMENT RAIDb-0x (MacroHandling?, IterPolicy?)>
<!ELEMENT IterPolicy EMPTY>
<!ATTLIST IterPolicy
policy (random | roundRobin | weightedroundRobin) #REQUIRED
>
2 .定义并实现类 org.continuent.sequoia.controller.loadbalancer.raidb0x
3. 解析节点 更新org.continuent.sequoia.controller.xml. DatabasesParser,支持新定义的xml节点解析4. 进行操作
4 .Query 支持查询请求广播,并且对结果集进行归并。
5.UnitSeedID 目前不解决分布式统一索引问题,可以考虑的三种解决方案
1. 每次插入记录时,由接口服务器负责调用统一索引服务获得一个在当前表格内的全局唯一索引。并且在插入数据时主动设置其索引值。称为PKS(Primary Key Server)。(用户提供编码方法或直接提供值,如果直接提供值需要进行唯一性检测,适用于编码式主键标识列)
2.在数据插入时并不特意指定其全局索引(by gashero ), 而是在chunk server上自动生成索引。这样在各个chunk server之间是可能出现相同的索引的,但是,我们在统计数据时,将指令发送到接口服务器IS(Interface Server),并且 由IS取回所有记录集时,在IS上给各个记录分配索引。这种方式简化了索引分配方式,但是不利于全局索引的生成。数据库完整性的指定也有待考虑。 (如果在插入时插入垃圾数据,就很难在此分辨出来)
3. 将一个大整数根据backend的个数分成几段值域,比如1~7589237924分配给第一个backend,7589237924~ 9589237924分配给第二个backend,以此类推(已验证可行,适用于自增长式主键标识列)
我的问题?
为什么VirtualDatabase-VirtualDatabaseWorkerThread关系和ControllerServerThread-ControllerWorkerThread在线程从属处理上风格不一致呢?难道是因为
是由不同的程序员写的吗? (前者的作者是Emmanuel Cecchet,后者的作者为Jean-Bernard van Zuylen)还是其它原因。