当前位置: 首页 > 工具软件 > VoltDB > 使用案例 >

VoltDB上手介绍

公冶谦
2023-12-01

转自:

https://my.oschina.net/whchsh/blog/102505

 

下一篇预告,VoltDB官方例程Voter(实时高并发投票)代码分析和性能测试

最近开始研究voltdb,先分享一些粗浅的安装和使用经验

1)介绍

“Time is the enemy of data”。我觉得VoltDB官网的这句话相当有道理。大数据时代,最关键的技术问题并不是如何存储大数据,而是如何最快处理大数据,包括在线事务和实时分析。内存数据库可能是我们目前最理想的选择。 
简单来说VoltDB是一个高性能的内存关系数据库,支持SQL规范、ACID事务、备份容灾,以及分区扩展。当然,既不能把它简单看作一个性能更好的MySQL,也不能看作增加了SQL接口的NoSQL数据库。

 VoltDBMySQLCassandra
SQL支持SQL 99子集, 支持create view、index、跨分区join,暂不支持alterSQL 99CQL,一种类SQL的查询语言,限制很多(例如不支持非主键条件)
原子性所有事务封装在存储过程中,执行完成后再提交到数据库基于事务日志仅限rowkey级别
一致性主备强一致,采用时钟误差算法保障…面向用户的强一致,内部可配置,通常是弱一致基于时间戳,支持面向用户的强一致,以及内部最终一致
隔离性指定key的写操作局部串行执行,跨key操作全表串行,实现最高Serializable隔离级别支持多种隔离性级别每次操作都具有时间戳
持久性快照+日志文件持久化+数据日志文件持久化+数据日志
数据备份DC内同步(multi-master), 
跨DC异步(企业版功能)
master-slave架构,通常使用异步备份NWR策略,读写都必须至少访问指定个数的节点
分区扩展一致性Hash,支持跨分区join应用控制,跨分区join困难Hash或顺序

显然VoltDB在一些方案上的设定肯定会带来性能的取舍:

  1. 支持但不适合指定key取值范围的操作,一方面它会导致全表锁,阻塞其它操作。另一方面由于只支持Hash分区,对于key范围的操作,需要很低效率的分布到所有节点执行。 因此VoltDB适合指定key或其离散区间的操作。
  2. 修改无法修改Schema是VoltDB目前一个主要限制,另外查询必须使用预先定义的Procedule(java代码,支持绑定参数),如果突然想到要增加一类查询,大概~~~就要重新编译(局部的)和重启服务了。总的来说,VoltDB目前版本,需要你提前把表结构和查询语句都想好,上线后就最好别修改了……
    1. 另外补充一下,为什么要把查询写成java代码硬code进去呢?当然是为了性能,即使用C++解析文本(或用文本hash做cache)也是有较大计算开销的。更何况VoltDB的这一层是用Java开发的。有什么既兼顾性能有不影响在线业务变更的办法呢?有的,使用LLVM,目前许多商用数据库都已经这么做了,当然开发代价比较大,目前GCC也还不支持。
  3. 与传统依靠本地日志的容灾不同,而VoltDB则主要依赖备份节点恢复数据,次选本地日志恢复,最次选择本地的内存快照。
    1. VoltDB支持各种日志可靠性级别,高可靠的日志配置会影响VoltDB性能,通常需要在写入性能和磁盘fsync频率之间作折中,或配置SSD缓解写日志I/O导致VoltDB性能下降。
    2. 从数据可靠性来看,开启日志的VoltDB与传统关系数据库并无差异。
    3. 从服务可用性来看,局部节点失效后,VoltDB比MySQL的切换成本更低,并且不会发生切换造成的数据时延问题。
    4. 从节点恢复速度来看,VoltDB节点一般性恢复(单节点)的速度主要依赖网络速度,通常场景下比日志恢复快。不过在复杂情况下的则相反,当master多节点失效则需要从本地日志和快照把所有数据全部加载到内存,速度可能很慢。

抛开以上局限性,VoltDB的使用场景仍然相当丰富,官方更提出了"No Limits"这样的极端说法。高性能、实时性是VoltDB最关键的特性。如果说在线游戏、社交这样目前已经几乎完全依赖内存存储的应用,还可以使用Key-Value缓存。而在线投票、实时交易分析(并非Hadoop漫长的MR数据分析)这类应用,既需要高可靠的ACID事务,又需要近乎实时的数据分析,可以说必须采用VoltDB这类内存关系数据库了。

2)编译配置

获取官方程序包: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,表明编译出错  如果提示端口被占用,需要修改运行脚本中的端口,具体参照下一节。

 

代码目录分析

  • lib/ 一些外部jar包:TCP服务接口(jzmq)、消息服务(jetty、protobuf、snappy)、命令行工具(jline),日志(slf4j)
  • src/catalog/目录包含使用python开发的系统Catalog的生成器
  • src/frontend/org/voltcore/ 内嵌zookeeper的集群管理服务
  • src/frontend/org/voltdb/ 所有存储引擎以上功能,包括sql解析、jdbc、客户端管理工具、数据恢复、元数据等
  • rc/ee/ 存储引擎C++代码

3)还是从Hello World开始

官方提供了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!

 

 类似资料: