转自:
https://my.oschina.net/whchsh/blog/102505
下一篇预告,VoltDB官方例程Voter(实时高并发投票)代码分析和性能测试
最近开始研究voltdb,先分享一些粗浅的安装和使用经验
“Time is the enemy of data”。我觉得VoltDB官网的这句话相当有道理。大数据时代,最关键的技术问题并不是如何存储大数据,而是如何最快处理大数据,包括在线事务和实时分析。内存数据库可能是我们目前最理想的选择。
简单来说VoltDB是一个高性能的内存关系数据库,支持SQL规范、ACID事务、备份容灾,以及分区扩展。当然,既不能把它简单看作一个性能更好的MySQL,也不能看作增加了SQL接口的NoSQL数据库。
VoltDB | MySQL | Cassandra | |
SQL支持 | SQL 99子集, 支持create view、index、跨分区join,暂不支持alter | SQL 99 | CQL,一种类SQL的查询语言,限制很多(例如不支持非主键条件) |
原子性 | 所有事务封装在存储过程中,执行完成后再提交到数据库 | 基于事务日志 | 仅限rowkey级别 |
一致性 | 主备强一致,采用时钟误差算法保障… | 面向用户的强一致,内部可配置,通常是弱一致 | 基于时间戳,支持面向用户的强一致,以及内部最终一致 |
隔离性 | 指定key的写操作局部串行执行,跨key操作全表串行,实现最高Serializable隔离级别 | 支持多种隔离性级别 | 每次操作都具有时间戳 |
持久性 | 快照+日志 | 文件持久化+数据日志 | 文件持久化+数据日志 |
数据备份 | DC内同步(multi-master), 跨DC异步(企业版功能) | master-slave架构,通常使用异步备份 | NWR策略,读写都必须至少访问指定个数的节点 |
分区扩展 | 一致性Hash,支持跨分区join | 应用控制,跨分区join困难 | Hash或顺序 |
显然VoltDB在一些方案上的设定肯定会带来性能的取舍:
抛开以上局限性,VoltDB的使用场景仍然相当丰富,官方更提出了"No Limits"这样的极端说法。高性能、实时性是VoltDB最关键的特性。如果说在线游戏、社交这样目前已经几乎完全依赖内存存储的应用,还可以使用Key-Value缓存。而在线投票、实时交易分析(并非Hadoop漫长的MR数据分析)这类应用,既需要高可靠的ACID事务,又需要近乎实时的数据分析,可以说必须采用VoltDB这类内存关系数据库了。
获取官方程序包:http://voltdb.com/tao-volt/downloads-home.php
目前的开源版本(GPL)有2.8和3.0 beta提供下载
我们也可以从源码编译: https://github.com/VoltDB/voltdb
由于VoltDB是混合编程的(在后续文章中会介绍),编译需要的环境还比较多,主要包括Java 6+、Ant 1.7.1+、GCC 4.2+、 Python 2.4+,具体参考https://github.com/VoltDB/voltdb/wiki/Building-VoltDB,这些环境一般的开发机上面都有,但需要注意版本,我之前在CentOS 5.4上编译就遇到各种问题,很多需要的软件版本都无法安装比如Ant。
安装方法是在voltdb-master目录,执行
ant dist
由于ant同时编译了官方代码自带的三个例程,我们可以通过运行例程检验编译是否成功,例如
/cd exsamples/voter
./run.sh server
如果最终提示:Server completed initialization.表明编译正确,服务正常启动
如果提示缺少库文件或class,表明编译出错 如果提示端口被占用,需要修改运行脚本中的端口,具体参照下一节。
代码目录分析
官方提供了HelloWord例子,这里挑出一些关键代码分析,就不全部贴出来了,参见http://voltdb.com/docs/GettingStarted/HWsourcefiles.php#projectfig
其中
project.xml定义VoltDB实例包含哪些表,以及预先定义哪些SQL操作,以下表示使用helloworld.sql初始化表Schema
<schemas> <schema path='helloworld.sql' /> </schemas>
读取Insert和Select两个Java类作为预定义的存储过程
<procedures> <procedure class='Insert' /> <procedure class='Select' /> </procedures>
deployment.xml定义VoltDB的集群信息, 以下表示集群主机初始数量为1,每个主机使用2个CPU内核执行存储引擎任务。
<cluster hostcount="1" sitesperhost="2" />
开启http服务和json接口
<httpd enabled="true"> <jsonapi enabled="true" />
helloworld.sql中包含一个建表语句
创建HELLOWORLD表,该示例中建表语句的语法与SQL完全一致
CREATE TABLE HELLOWORLD
需要扩展到多节点时,按照DIALECT字段(Hash值)自动进行数据分区
PARTITION TABLE HELLOWORLD ON COLUMN DIALECT;
Insert.java和Select.java定义了两个存储过程,即客户端可以调用的SQL
public class Select extends VoltProcedure 告知数据库Select类是存储过程
@ProcInfo(partitionInfo = "HELLOWORLD.DIALECT: 2", singlePartition = true)
定义一个带绑定参数的SQL查询语句对象
public final SQLStmt sql = new SQLStmt(
"SELECT HELLO, WORLD FROM HELLOWORLD " +
" WHERE DIALECT = ?;"
);
每个存储过程都需要定一个run函数,参数是绑定参数的数据类型,返回类型是VoltTable,即封装查询结果的数据结构,voltQueueSQL将sql语句和绑定参数添加到执行队列,voltExecute函数执行所有提交到队列的操作并返回结果。
public VoltTable[] run( String language) throws VoltAbortException {
voltQueueSQL( sql, language );
return voltExecuteSQL();
}
一个run里面的操作是默认是一次事务,即可以多次voltQueueSQL之后,再执行voltExecuteSQL,这样多个SQL就组成了一个事务。
Client.java是一个客户端程序
创建了一个客户端对象,并连接到本机地址
myApp = ClientFactory.createClient();
myApp.createConnection("localhost");
调用Insert存储过程,绑定参数为("Hello", "World", "English"),相当于执行INSERT INTO HELLOWORLD VALUES('Hello', 'World', 'English');
调用了Select存储过程,绑定参数为"Spanish",相当于执行SELECT HELLO, WORLD FROM HELLOWORLD WHERE DIALECT = 'Spanish';
存储过程
myApp.callProcedure("Insert", "Hello", "World", "English");
final ClientResponse response = myApp.callProcedure("Select", "Spanish");
最后,编译写好的java存储过程和客户端
javac Client.java
javac Insert.java
javac Select.java
使用ant编译好的voltcompiler编译元数据,使用voltdb启动客户端(需要指定voltdb目录或配置执行文件路径)
voltcompiler ./ project.xml helloworld.jar
voltdb catalog helloworld.jar \
deployment deployment.xml \
host localhost \
license /opt/voltdb/voltdb/license.xml
运行客户端程序
java Client
最终将得到以下输出
Hola, Mundo!