做者:Shashank Tiwarihtml
2011 年 2 月发布java
“NoSQL”是在开发人员、架构师甚至技术经理中新流行的一个词汇。尽管这个术语最近很流行,但使人惊讶的是,它并无一个广泛承认的定义。git
一般来讲,任何非 RDBMS 且遵循无模式结构的数据库通常都不能彻底支持 ACID 事务,而且因高可用性的承诺以及在横向伸缩环境中支持大型数据集而广泛被归类为“NoSQL 数据存储”。鉴于这些共同特征(与传统的 RDBMS 的特征造成鲜明对比),有人提议非关系(或者简称为 NonRel)是比 NoSQL 更为恰当的术语。github
尽管定义冲突仍然存在,但不少人已经意识到将 NoSQL 加到其应用程序体系中的好处。其余人正保持密切关注并评估 NoSQL 是否适合他们。算法
NoSQL 做为一个类别的发展还致使了大量新数据存储的出现。其中某些新的 NoSQL 产品擅长持久保存像 JSON 这样的文档,某些按照列家族存储排序,其余的则持久保存分布式键值对。尽管更新的产品使人兴奋而且提供了不少好用的功能,但一些现有产品也在奋起直追履行 新的承诺。sql
Oracle Berkeley DB 就是这样一个数据存储。在文本中,我将解释并说明为何能够将 Berkeley DB 做为 NoSQL 解决方案包括在体系中以及具体实现方式。本文重点关注 Berkeley DB 围绕 NoSQL 的特性,所以不会详尽涵盖 Berkeley DB 的全部功能和特性。数据库
基本上,键值存储 Berkeley DB 有三种不一样风格:数组
Berkeley DB — 用 C 编写的键值存储。(Berkeley DB 官方文档使用术语键-数据 代替键值。)这是“经典”风格。缓存
Berkeley DB Java 版 (JE) — 用 Java 从新编写的键值存储。能够轻松包含在 Java 堆栈中。网络
Berkeley DB XML — 用 C 编写,此版本将键值存储进行包装,使其行为相似于一个已创建索引而且通过优化的 XML 存储系统。
(注意:尽管本文没有明确涉及 Berkeley DB JE 或 Berkeley DB XML,可是包括了一些使用 Java API 和基于 Java 的持久性框架来讲明 Berkeley DB 功能的示例。)
Berkeley DB 的核心可能很简单,能够将它配置为提供并行非阻塞访问或支持事务,横向扩展为一个主从副本的高可用集群或者以多种其余方式横向扩展。
Berkeley DB 是一个纯存储引擎,不对键值对的隐式模式或结构作任何假设。所以,Berkeley DB 轻松容许在底层键值存储上实现更高级别的 API、查询和建模抽象。这有助于快速高效地存储应用程序特定数据,而不会产生将其转换为抽象数据格式的开销。这种简单却精致的设计所提供的灵活性可以在 Berkeley DB 中同时存储结构化和半结构化数据。
Berkeley DB 可做为内存中存储来运行,以保存少许数据,也可经过快速的内存中缓存配置为大型数据存储。在更高级别抽象(称做环境)的帮助下,能够在一个物理安装中配置多个数据库。一个环境能够有多个数据库。您须要打开一个环境,而后打开一个数据库,向其中写入数据或者从中读取数据。建议您在完成交互后关闭数据库和环境,从而以最佳方式使用资源。
数据库中的每一项都是一个键值对。键一般是惟一的,可是您能够有重复的项。值是经过键来访问的。能够更新检索值并将其保存回到数据库。经过游标对多个值进行访问和迭代。游标使您能够循环遍历值的集合以及同时操纵整个值集合。另外,还支持事务和并发访问。
键值对的键几乎老是充当创建索引的主键。值中的其余属性可充当次索引。在辅助数据库中单独维护次索引。所以,具备键值对的主要数据库有时候也被称做主数据库。
Berkeley DB 做为一个进程中数据存储运行,所以在使用 C、C++、C#、Java 或脚本语言 API 从相应程序中访问它时,您会以静态或动态方式连接到它。
简要介绍以后,下面将就 Berkeley DB 围绕 NoSQL 的特性进行介绍。
NoSQL 存储的第一个优点是其对定义明确的数据库模式的宽松态度。咱们来看看 Berkeley DB 如何实现此特性。
为了理解 Berkeley DB 的功能,建议您试用一下。所以,建议将 Berkeley DB 和 Berkeley DB JE 下载并安装到您的计算机上,这样您能亲自尝试一些示例并跟随本文中其他例证的操做。此处在 线提供了下载连接和安装说明。(在本文中,我使用 --enable-java、--enable-sql 和 --prefix=/usr/local 对 Berkeley DB 进行了编译。)与存储、访问机制和 API 有关的基本概念在 Berkeley DB 和 Berkeley DB JE 之间没有太大区别,所以我后面涉及到的大部份内容一样适用于这二者。
除了数据项必须是键值对集合以外,Berkeley DB 自己对数据项的限制很是少。这就使得应用程序能够灵活地使用 Berkeley DB 管理各类格式的数据,包括 SQL、XML 和 Java 对象。您能够经过基础 API、SQL API、Java Collections API 以及 Java Direct Persistence Layer (DPL) 访问 Berkeley DB 中的数据。它容许几种不一样存储配置:B 树、散列、队列和 Recno。(Berkeley DB 文档将不一样存储机制称做“访问方法”。散列、队列和 Recno 访问方法仅在 Berkeley DB 中可用,在 Berkeley DB JE 或 Berkeley DB XML 中不可用。)
您能够根据具体用例来选择访问机制和存储配置。选择特定的访问方法和存储配置会影响模式。要了解您的选择所形成的影响,您须要先了解您所选的内容。我接下来要谈到访问方法和存储配置。
基 础 API 是低级别 API,使您能够存储、检索和更新数据(即键值对)。这种 API 在几种不一样语言绑定之间是相似的。所以,C、C++ 和 Java 的基础 API 是彻底相同的。另外一方面,DPL 和 Java Collections API 仅做为抽象在 Java API 中提供。
基 础 API 可放置、获取和删除键值对。键和值均为字节数组。在存储全部键和数据值以前,会将其序列化为字节数组。您可使用 Java 的内置序列化程序或 Berkeley DB 的 BIND API 将各类数据类型序列化为字节数组。Java 的内置序列化程序一般执行速度较慢,所以用户一定更喜欢 BIND API。(jvm-serializers 项目对各类替代序列化程序进行基准测试,是用于在 JVM 的不一样序列化机制之间分析相对性能的一个很好的参照点。)BIND API 可经过每一个序列化类来避免冗余存储类信息,将该信息放在单独的数据库中。经过编写您本身的自定义字节组绑定来提升 BIND API 性能,您能够潜在地提升速度。
做为一个基本示例,您能够定义以下数据值:
import java.io.Serializable; public class DataValue implements Serializable { private long prop1; private double prop2; DataValue() { prop1 = 0; prop2 = 0.0; } public void setProp1(long data) { prop1 = data; } public long getProp1() { return prop1; } public void setProp2(double data) { prop2 = data; } public double getProp2() { return prop2; } }
如今,您可使用两个数据库来存储此数据值,一个数据库存储带有键的值,另外一个数据库存储类信息。
使用四个不一样步骤来存储数据:
首先,除了用于存储键值对的数据库以外的另外一个数据库配置为存储类数据,以下所示:
Database aClassDB = new Database("classDB", null, aDbConfig);
StoredClassCatalog storedClassCatalog = new StoredClassCatalog(aClassDb);
EntryBinding binding = new SerialBinding(storedClassCatalog, DataValue.class);
DataValue val = new DataValue(); val.setProp1(123456789L); val.setProp2(1234.56789);
使用您刚建立的绑定映射到 Berkeley DB DatabaseEntry(充当键和值的包装器),以下所示:
DatabaseEntry deKey = new DatabaseEntry(aKey.getBytes("UTF-8")); DatabaseEntry deVal = new DatabaseEntry(); binding.objectToEntry(val, deVal);
如今,您能够将键值对放入 Berkeley DB 中。
基础 API 支持 put 和 get 方法的几种变体,以容许或不容许重复项和覆盖。(该示例以及本文都不是为了要教您有关如何使用基础 API 的详细语法或语义,所以我将不会涉及更多细节;请参阅这里的文档)。一个要点是,基础 API 容许就存储、检索和删除键值对进行低级操做和自定义序列化。
若是偏向于使用更高级的 API 与 Berkeley DB 进行交互,那么您应使用 DPL。
直 接持久层 (DPL) 提供了熟悉的 Java 持久性框架语义来操纵对象。您能够将 Berkeley DB 视做一个实体存储,对象在其中持久保存,并可对其中的对象进行检索以便更新和删除。DPL 使用批注将类标记为 @Entity。使用实体进行存储的相关联的类被注释为 @Persistent。特定属性或变量能够注释为 @PrimaryKey 和 @SecondaryKey。一个简单的实体可能以下所示:
@Entity public class AnEntity { @PrimaryKey private int myPrimaryKey; @SecondaryKey(relate=ONE_TO_ONE) private String mySecondaryKey; ... }
DPL 将类定义用做定义明确的模式。经过基础 API,咱们知道 Berkeley DB 不要求必须符合模式。但对于某些用例,正式的实体定义颇有帮助并可为数据建模提供结构化方法。
正如前面所提到的,能够经过四种不一样类型的数据结构存储键值对:B 树、散列、队列和 Recno。咱们来看看它们的效果如何。
尽 管 B 树和散列均支持复杂键,可是当数据集远超过可用内存大小时,散列数据库的性能一般优于 B 树。这是由于 B 树比散列保存更多的元数据,更大的数据集意味着 B 树元数据可能没法存储在内存中缓存内。在这种极端状况下,B 树元数据以及实际数据记录自己一般必须取自文件,而这会致使每一个操做有多个 I/0。散列访问方法旨在最大程度减小访问数据记录所需的 I/O 数量,所以在这些极端状况下,性能可能会优于 B 树。
不 同配置使您能够在集合中存储任意类型的数据。与 NoSQL 相似,没有固定模式(除了您的模型实施的模式)。在极端状况中,您能够在集合中针对两个键分别存储不一样的值类型。值类型能够是复杂类,就参数而言,能够表 示 JSON 文档、复杂数据结构或结构化数据集。真正的惟一限制是,值应该序列化为字节数组。单个键或单个值最大可达 4GB。
次索引的出现容许根据值属性进行筛选。主数据库不会以表格格式来存储数据,所以不会为稀疏数据集存储非现有属性。若是键值对缺乏用于建立索引的属性,次索引会跳过全部此类键值对。通常来讲,这种存储方式既紧凑又高效。
Berkeley DB 是一个很是灵活的数据库,能够打开和关闭许多特性。Berkeley DB 能够在不支持事务的状况下运行,也能够编译为支持 ACID 事务完整性。也许,Berkeley DB 的可塑性使其成为很是适合许多状况的数据存储。在典型的 NoSQL 数据存储中,对事务完整性的支持最差。在不指望 ACID 事务合规性的具备较高可用性的系统中,Berkeley DB 能够关闭事务,像典型的 NoSQL 产品那样工做。可是在其余系统中,它可能很灵活而且支持事务完整性。
尽管我并不打算涉及有关事务的细节,但值得注意的是,像 传统 RDBMS 系统同样,支持事务的 Berkeley DB 容许定义事务边界。一旦提交,数据会持久保存到磁盘。为提升性能,您可使用非持久性提交,这会将写操做提交到内存中日志文件,随后与底层文件系统进行同 步。还支持隔离级别和锁定机制。
在数据库关闭以前,同步操做可保证持久文件副本在系统中具备最新的内存中信息。这种同步操做与 Berkeley DB 的事务恢复子系统的组合(假定您已启用了事务)可确保数据库始终返回到一致的事务状态,即便是在应用程序或系统发生故障时。
理 论上,Berkeley DB 具备 256TB 的上限,但实际上,一般受运行 Berkeley DB 的计算机的大小限制。截至撰写本文时,Berkeley DB 未证明可在分布式文件系统的帮助下支持跨多台计算机的极大文件。(可借助 Hadoop 分布式文件系统 (HDFS) 等分布式文件系统的帮助管理超过单个节点大小的文件。)Berkeley DB 在本地文件系统上的性能优于在网络文件系统上的性能。更准确地说,Berkeley DB 依赖文件系统的 POSIX 兼容属性。例如,当 Berkeley DB 调用 fsync() 而且文件系统返回时,Berkeley DB 假定数据已写入到持久介质。出于性能缘由,分布式文件系统一般不保证自始至终完成向持久介质的写入。
所支持的最大 B 树深度为 255。键和值的长度一般受可用内存的限制。
Berkeley DB 复制遵循主从模式。在此类模式中,有一个主节点和多个从属节点(或副本)。可是,主节点的选择不是静态的,而且不建议手动选择。复制集群中的全部参与节点 都要经历一个选举过程以选出主节点。具备最新日志记录的参与节点将成为获胜者。若是具备绑定,那么优先级用于选择主节点。选举过程基于行业标准的符合 Paxos 的算法。
复制具备不少好处,包括:
提升读性能 — 可从多个副本节点中读取数据极大提升了读性能。
提升可靠性 — 有了副本实例,就能够在发生节点故障和数据损坏时提供更好的故障转移选择。
提升持久性 — 您能够放宽对主节点的持久性保证以免过多地对磁盘进行写入操做,这一般须要昂贵的 I/O。在集群环境中,经过将写入提交到多个节点(即便未写入到磁盘)这一事实加强了持久性。
提升可用性 — 因为有多个节点而且对磁盘进行异步写入,即便在主节点负载太高的状况下,副本节点仍可继续提供服务。
毫无疑问,Berkeley DB 做为一个强健、可伸缩的 NoSQL 键值存储很是合格;Amazon 的 Dynamo、Project Voldemort、MemcacheDB 和 GenieDB 使用 Berkeley DB 做为底层存储就是支持这一观点的进一步证据。围绕 Berkeley DB 性能一直存在一些恐惧,尤为是下面这两个在线发布的比较基准测试:
http://www.dmo.ca/blog/benchmarking-hash-databases-on-large-data/
http://stackoverflow.com/questions/601348/berkeleydb-vs-tokyo-cabinet
可是,不少运行中的系统证实了 Berkeley DB 的强大。其中许多系统通过了仔细的调整和应用程序编码改进,已经得到了出色的可伸缩性、吞吐量和可靠性结果。效法这些系统,Berkeley DB 无疑可用做可伸缩的 NoSQL 解决方案。
Shashank Tiwari 是 Treasury of Ideas(一 家技术驱动的创新和价值优化公司)的创始人兼 CEO。做为一名经验丰富的软件开发人员和架构师,他精通大量技术。他是国际承认的演讲者、做者和导师。做为数种 JCP (Java Community Process) 规范的专家组成员,他一直积极参与规划 Java 的将来。他还表明了 NoSQL 和云计算领域的心声,是 RIA 社区公认的专家。