1. Berkeley DB的简介
Berkeley DB(BDB)是一个高性能的嵌入式数据库编程库(引擎),它可以用来保存任意类型的键/值对 (Key/Value Pair),而且可以为一个键保存多个数据。Berkeley DB可以支持数千的并发线程同时操作数据库,支持最大256TB的数据。
BDB提供诸如C语言,C++,Java,Perl,Python,Tcl等多种编程语言的API,并且广泛支持大多数类Unix操作系统和Windows操作系统以及实时操作系统(如 VxWorks)。
1991年,Berkeley DB的第一个版发行(Linux系统也在这一年诞生),其最初的开发目的是以新的HASH访问算法来代替旧的hsearch函数和大量的dbm实现,该版本还包含了B+树数据访问算法。
1992年,BSD UNIX第4.4发行版中包含了Berkeley DB1.85版。基本上认为这是Berkeley DB的第一个正式版。
1996年,Sleepycat软件公司成立,提供对Berkeley DB的商业支持。
2006年,Sleepycat被Oracle收购,当时最新版本是4.7.25。
2. 直观了解Berkeley DB软件包
Berkeley DB是一款开源软件,我们可以从Oracle的官方网站得到其源代码包。其源代码目录是由一系列子目录组成,从BDB的实现角度按照功能层次可将它们简单归类,划分如下:
a. DB核心模块(db);
b. 各子系统模块(存储管理子系统:btree/hash/qam;内存池管理子系统:mp;事务子系统:txn;锁子系统:mutex;日志子系统:log);
c. 操作系统抽象层(os_brew/os_s60/os_windows等);
d. Build目录(build_brew/build_s60/build_windows等);
e. 工具程序(db_archive/db_checkpoint等);
f. 语言API支持;
g. 例子(examples_c/examples_csharp等);
h. 其它;
通过源代码编译安装BDB很简单,代码如下:
安装目标目录(/usr/local/BerkeleyDB.4.8)包含四个子目录:
A. bin
B. docs
C. include 包含了使用BDB库开发程序时的头文件
D. lib
3. 如何获得BDB的相关知识
BDB提供里非常详细的文档,可以官方网站获得html或pdf版本的文档。这里对pdf版本的一些文档简介如下:
BDB_Installation.pdf: BDB的安装文档,涵盖了不同操作系统,不同的编译工具,不同编程语言等多方面的详细信息;
BDB_Prog_Reference.pdf: 该文档是使用BDB的开发人员的参考手册,主要从BDB的各种功能和机制的原理进行阐述,供使用BDB作为存储引擎来编写程序的各类程序员(C、Java、C#、Perl)阅读;
BDB-Porting-Guide.pdf: 该文档是给需要将BDB移植到一个新的平台开发人员准备的;
InMemoryDBApplication.pdf: 基于内存的BDB应用的相关知识;
BDB-C_APIReference.pdf: C API参考手册,跟BDB_Prog_Reference.pdf结合使用;
BerkeleyDB-Core-C-GSG.pdf: 为C语言开发人员提供的BDB的入门手册;
BerkeleyDB-Core-C-Txn.pdf: 为C语言开发人员提供的BDB事务方面的手册;
Replication-C-GSG.pdf: 为C语言开发人员提供的BDB复制方面的手册;
4. 以上对源码目录的分类是从实现角度按照层次进行划分的,如果从BDB的功能模块,或者说是从系统结构角度进行划分,可将其分为几个子系统:
日志子系统 (Logging Subsystem)
5. 以上的五个子系统完成了BDB作为一个Database所需要的大部分功能,如何驾驭以上子系统来完成我们需要的任务是关键。像MySQL这种独立的RDBMS,我们可以通过配置和SQL语句来控制和使用它的各种功能。由于BDB是一个嵌入式的数据库,最终还是需要程序员通过调用API来完成。所以要使用好BDB,需要先了解其原理,然后在合适的位置上调用合适的API。
写一个BDB程序的一般步骤:
a. 创建、设置和打开Environment;b. 创建、设置和打开Database;c. 访问Database;d.关闭Database;e. 关闭Environment。
此处的Database是从属于Environment,即db是在env这个环境上面建立起来的。为了便于快速把握重点,可以用BDB跟一般的RDBMS做个类比,这里的Database相当于数据表,Environment相当于数据库。
数据文件:
一个BDB的实例会产生数据存储文件,数据文件的目录由dbenv->set_data_dir(dbenv, data_dir);这条语句来指定。涉及的文件类型有:Data Files,Log Files,Region Files,Temporary Files。
Data Files:数据文件,存储实际的数据;
Log Files:日志文件;
Region Files:是各个子系统保存信息的文件,如果在Env中设置了DB_PRIVATE选项,这些信息是被一个进程私有,即它们保存在内存中,这些文件在此种情况下不产生;
Temporary Files: 临时文件,特使情况会被创建;
数据的存数格式:
Berkeley DB提供了以下四种文件存储方法:哈希文件、B树、定长记录(队列)和变长记录(基于记录号的简单存储方式),应用程序可以从中选择最适合的文件组织结构。以上代码通过db->open函数中设置了DB_BTREE这个选项指定其使用B树方式存储。其它的三种存储格式对应的类型为:DB_HASH,DB_QUEUE,DB_RECNO。
事务提交:
BDB中的事务提交有两种方式:DB_AUTO_COMMIT和显式提交事务。如果设置为DB_AUTO_COMMIT,则每步操作多作为单独的事务自动提交;如果需要显示提交,则需要显示调用具体事务相关的begin/end API(相见文档BerkeleyDB-Core-C-Txn.pdf)。
BDB在事务提交时也是遵循先写日志并刷新到磁盘的方式,但是为了提高性能,其又引入了两个选项:DB_TXN_NOSYNC和DB_TXN_WRITE_NOSYNC。DB_TXN_NOSYNC的作用是使BDB在事务提交的时候不严格要求日志到磁盘,刷新与否取决于日志缓冲;DB_TXN_WRITE_NOSYNC会比DB_TXN_NOSYNC稍显严格,其含义是要求事务提交刷新日志,但只是刷到操作系统文件缓存当中。
BDB的事务隔离性级别有三个:READ UNCOMMITED、READ COMMITED、SERIALIZABLE
CheckPoint:
执行一个检查点会完成的工作有:Flushes all dirty pages from the in-memory cache to database files;Writes a checkpoint record;Flushes the log to log files;Writes a list of open databases.
调用API DB_ENV->txn_checkpoint(); 即可完成,如果是非DB_PRIVATE的Env,也可以使用BDB自带的工具db_checkpoint。为了避免出现一个检查点提交大量数据的情况,BDB还提供了轻量级刷新脏页的API:DB_ENV->memp_trickle();
Replication:
分区:
BDB的分区机制是从db-4.8.x之后刚引入的新功能,涉及到的API有两个:
DB->set_partition() 设置分区方式,包含了一个分区方式的回调函数,用户可以通过编写代码来自己实现分区方式,非常灵活。(详见API手册BDB-C_APIReference.pdf)
DB->set_partition_dirs() 设置分区目录。(详见API手册BDB-C_APIReference.pdf)
备份:
BDB有三种备份方式:
Offline Backups:离线备份,停服务拷贝数据目录;
Hot Backups:使用API或者BDB自带工具db_backup在DB在使用情况做备份;
Incremental Backups:增量备份;
具体细节详见BerkeleyDB-Core-C-Txn.pdf。
6. 以下是可能获取到Berkeley DB资源的链接:
官方主页:
产品下载:
官方开发者文档中心:
数据存储
Berkeley DB的数据存储可以抽象为一张表,其中第一列是key,剩余的n-1列(fields)是value。
BDB访问数据库的方式,或者套用MySQL数据库的说法是存储引擎,有四种:
- Btree 数据保存在平衡树里,key和value都可以是任意类型,并且可以有duplicated keys
- Hash 数据保存在散列表里,key和value都可以是任意类型,并且可以有duplicated keys
- Queue 数据以固定长度的record保存在队列里,key是一个逻辑序号。这种访问方式可以快速在队列尾插入数据,然后从队列头读取数据。它的优点在于可以提供record级别的锁机制,当需要并发访问队列的时候,可以提供很好性能。
- Recno 这种访问方式类似于Queue,但它可以提供变长的record。
BDB的数据容量是256TB,单个的key或value可以保存4GB数据。
BDB是为并发访问设计的,thread-safe,且良好的支持多进程访问。
少量或者中量数据都建议使用BTREE,尤其并发的场景下,BTREE支持 lock coupling 技术,可以提升并发性能。
BDB组成
Berkeley DB内含多个独立的子系统:
- Locking subsystem
- Logging subsystem
- Memory Pool subsystem
- Transaction subsystem
一般使用的时候,这些子系统都被整合在DB environment里,但它们也单独拿出来,配合BDB之外的数据结构使用。
所谓DB Environment就是一个目录,其中保存着Locking、Logging、Memory Pool等子系统的信息,不同的thread可以打开同一个目录读写DB environment,BDB通过这种方式实现多进程/线程共享数据库。
【注意】多进程共享一个环境时,必须要使用 DB_SYSTEM_MEM
,否则无法正常初始化环境。
关于DB environment的设置很多,一般没必要全部在代码里设置,也可以使用名为 DB_CONFIG 的配置文件来设置,该文件默认位于环境目录。
Concurrent Data Store (CDS)
CDS适用于多读单写的应用场景,当使用CDS的时候,仅需要 DB_INIT_MPOOL | DB_INIT_CDB
这两个子系统,不应该启用任何其他子系统,比如DB_INIT_LOCK
、DB_INIT_TXN
、DB_RECOVER
等。
由于CDS并不启动lock子系统,所以使用CDS无需检查deadlock,但下面的几种情况会导致线程永远阻塞:
- 混用DB handle和cursor(此时同一thread会有两个locker竞争)。
- 当打开一个write cursor的时候,在同一个线程里有其他的cursor开启。
- 不检查BDB的错误码(当一个cursor错误返回时,必须关闭这个cursor)。
其实CDS和DS的唯一区别就在于,当要写db的时候,应该使用DB_WRITECURSOR创建一个write cursor。当这样的write cursor 存在的时候,其他试图创建 write cursor 的线程将被阻塞,直到该 write cursor被关闭。当write cursor存在的时候,read cursor不会被阻塞;但是,所有实际的写操作,包括直接调用DB->put()或者DB->del()都将被阻塞,直到所有的read cursor关闭,才会真正的写入db。这就是multiple-reader/single-writer的具体工作机制。
CDS中的注意事项
如果使用secondary database,意味着会在同一个cursor下操作两个db,此时如果用CDS,也许必须设置DB_CDB_ALLDB,但这会严重影响性能。
所谓 DB_CDB_ALLDB
是一个非常粗粒度的锁,CDS的锁基于API-layer,默认per-database,但如果设置了DB_CDB_ALL
,则是per-environment,这意味着:
- 整个DB environment下只能有一个write cursor。
- 当写db的时候,整个DB environment下任何read cursor不可以打开。
读写CDS简单的做法是能用DB handle的地方直接使用DB handle,没有必要使用CURSOR handle,因为你用DB->put()或者DB->del()来修改数据库时,它内部也是调用了CURSOR handle。当然,如果你要使用CURSOR遍历数据库时,用于写的CURSOR必须设置DB_WRITECURSOR来创建:
DB->cursor(db, NULL, &dbc, DB_WRITECURSOR);
直接调用DB->put()
或者DB->del()
,或者先使用DB_WRITECURSOR
创建CURSOR handle,最终都进入__db_cursor()
函数,设置db_lockmode_t mode = DB_LOCK_IWRITE
,然后用该mode
加锁。但需要注意的是,不能在同一thread下混用DB和CURSOR handle,因为每个CURSOR会分配一个LOCKER,而DB handle也会分配一个LOCKER,两者可能导致self-deadlock。
如果在read lock或者write lock过程中,程序崩溃,这可能导致lock遗留在env中无法释放(可以用db_stat -CA
观察到),这种情况下该environment已经损坏,只能删除该environment(删除掉__db.001之类的文件即可),重新创建。
Transactional Data Store (TDS)
TDS是使用BDB的终极方式,它适用于多读多写,并且支持Recoveriablity等任何你能想到的常见数据库特性,或者不如说,只有当你确定需要这些特性的时候,你才应该使用BDB;如果你仅仅想要一个单纯的KV系统,那也许BDB并不适合你。
一般来说,创建TDS Environment的flag如下:
DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN
TDS的任何DB相关的操作都必须是事务性的,包括打开db时,都需要先创建txn:
DB_TXN* txn; int ret = env->txn_begin(env, NULL, &txn, 0); ret = db->open(db, txn, "test.db", NULL, DB_BTREE, DB_CREATE, 0); // 如果使用secondary database, 则associate()调用也需要包含在txn里 ret = db->get(db, txn, &key, &val, 0); ret = db->put(db, txn, &key, &val, 0); if(ret) txn->abort(txn); else txn->commit(txn, 0);
如果仅仅有读操作,其实可以无需调用commit,直接abort即可。
如果使用 DB_AUTO_COMMIT 打开db,则关于db handle的操作,不需要额外指定txn参数,此时使用了BDB的autocommit特性。
Write Ahead Logging
WAL是很多事务性数据库使用的技术,即在数据实际写入到数据库文件之前,先记录log,一旦log被写入到log文件,即认为该事务完成(并不会等待数据实际写入到数据库文件)。
这是因为log的写入始终是顺序写到文件末尾的,这比实际数据写入数据库文件(随机写入文件)要快2个数量级。
清理无用log的办法:
- 使用命令
db_archive -d
- 调用
ENV->set_flags
设置DB_LOG_AUTOREMOVE
Deadlock
使用TDS时,死锁原则上无法避免:
- 两个进程互相等待一块被对方锁住的资源则会发生死锁
- 甚至单一进程内试图获取一个已经被不同locker获取过的lock,也会发生死锁
采用BTREE/HASH访问方式下,并发操作时,无法避免死锁,因为page splits随时可能发生,见图:
死锁检测(原理是遍历wait-for图,发现环;如果有环出现,则打破它):
- 同步检测 DB_ENV->set_lk_detect(),在每个阻塞的锁上检测,好处是立即发现,坏处是cpu占用略高(insignificant)
- 异步检测 DN_ENV->lock_detect() 或者 db_deadlock,需要额外发起一个进程或线程,坏处是只有当运行该命令时才能检测,好处是cpu占用低
一般解决死锁的办法:同步检测 + 异步检测 + 设置锁超时
当environment没有被损坏时,可以使用 db_stat -Cl
查看死锁情况。
Degree isolation
degree 2 isolation 保证事务读到已经COMMIT的数据,但是在该读事务结束之前,其他事务可以修改该记录。degree 2 isolation适用于长时间的读取事务,比如遍历数据库等。
使用办法:使用 DB_TXN->txn_begin(), DB->get(), DBC->get() 等函数时,设置参数 DB_READ_COMMITTED
。
区别于degree 3 isolation,后者保证在一个读事务内,无论读取多少遍,都可以读到同样的记录。但这会拒绝该记录的任何写事务。所谓degree 1 isolation则更进一步,可以读取未COMMIT的数据,建议谨慎使用,容易导致数据不一致。
性能调优和参数设置
lock table size
lock table的大小依赖于以下三个参数:
- lock最大数量:ENV->set_lk_max_locks() 同时可以请求的锁的最大值,比如同时2进程并发,要锁11个对象,则需要2x11个锁。
- locker最大数量:ENV->set_lk_max_lockers() 同时发起锁请求的最大值,比如同时2进程并发,则最多2个locker。
- lock object最大数量:ENV->set_lk_max_objects() 同时需要锁住的object的最大值,比如同时2进程并发,如果5层BTREE,则需要锁住2x5=10个对象,此外再加上单独的DB handle。
实际上面的计算得到的最大值还要double,因为如果开启deadlock检测,对每个locker来说BDB会新增一个dd locker,用于检测死锁。
timeout
可以分别设置锁和事务的超时:
- ENV->set_timeout() 设置锁和事务的默认超时
- DB_TXN->set_timeout() 单独设置事务的超时
cachesize
使用db_stat查看cache命中情况:
$ db_stat -h var -m 125MB 8KB Total cache size 1410M Requested pages found in the cache (99%) 14 Requested pages not found in the cache
建议根据程序设置合理的cachesize,尽量保证所有数据都可以被cache命中。
bdb包括对构建基于复制(replication)的高可用性应用程序的支持。bdb replication组由一些独立配置的数据库环境组成。
组里只有一个master数据库环境和一个或多个client环境。Master环境支持读和写,client环境支持只读。如果master环境倒掉了,应用程序将可能提升一个client为新的master。数据库环境可能在单独的计算机上,在单独的硬件分区上(partitions)一个不统一的内存访问系统上,或在一个单独的server的一个磁盘上。唯一的约束就是,replication组的所有的参与者必须在一个字节序(endianness)相同的机器上(都是大数再前或都是小数在前的操作系统)。我们期望这个约束在以后的版本中会去掉。因为总是用bdb环境,任何数量的并发进程或线程可能访问一个数据库环境。在master环境中,多个线程可能读写这个环境。在client环境中,多个线程可能要读这个环境。
应用程序可能被编写成在master和clients间提供不同程度的稳固性。系统能同步的运行以便复制品(replicas)能保证是最新的,对应于所有已提交的事务。但是这样做可能回招致性能上的很大的下降。高性能解决方案有考虑全局的稳固性,允许clients的数据过时一个应用程序可控制的一段时间。
最后,bdb replication实现还有一个附加的特性去增强可靠性。bdb中的replication实现成执行数据库更新用一个不同的编码路径而不是用标
DB_ENV->rep_elect
DB_ENV->rep_process_message
DB_ENV->rep_stat
DB_ENV->rep_sync
DB_ENV->rep_set_config
DB_ENV->rep_start
DB_ENV->set_rep_limit
DB_ENV->set_rep_transport
Replication environment IDs
应用程序有责任去标志每个进来的传递给DB_ENV->rep_process_message的有适当标识符的replication消息。随后,bdb将用这些相同的标识符去标志发送函数发出去的消息。
Replication environment priorities
每个replication组中的数据库环境变量必须有一个优先权,它指定了在replication组中不同环境间的一个相对的顺序。这个顺序在币桓鰉aster倒掉,在决定选举哪个环境作为新master的时候的一个重要因素。优先权必须是一个非负的整数,但不必要replication组中是独一无二的。优先权为0意味着这个系统永远不能成为一个master,是被忽略的。数越大表示优先权越高,例如,如果一个replication组由3个数据库环境组成,两个由OC3 连接,第三个由T1连接,那么第三个数据库环境就应该被指定一个低点的优先权。
Building replicated applications
最简单的方法去构建一个replicatedbdb应用程序,是首先构建(和调试)这个应用程序的一个事务版本。然后,添加一个薄的replication层到这个应用程序。所有高可用性应用程序用下面的附加的四个bdb方法:DB_ENV->rep_elect, DB_ENV->rep_process_message, DB_ENV->rep_start 和 DB_ENV->set_rep_transport 还有可能用这个配置方法DB_ENV->set_rep_limit。
当在应用程序启动时调用DB_ENV->rep_start的时候,应用程序有两个选择:通过配置为组内指定一个master,或者,可选择,配置所有组内成员为clients然后调用一个选举方法,在它们之间选择一个master。每种方法都只正确的,完全取决于你的应用程序。DB_ENV->rep_start调用的结果,往往是发现了一个master,或者,声明本地环境作为master。如果在一段可以接受的时间内还没找出一个作为master,应用程序应该调用DB_ENV->rep_elect搞一次选举。
请注意,不是所有的运行在replicated环境中的进程都需要调用DB_ENV->set_rep_transport或DB_ENV->rep_start。
在master环境中运行的只读进程在任何情况下都不必要配置成replication。那些运行在clients环境中的进程在定义时就是只读的,所以也不必要配置成replication的(尽管,clients在一些情况下有变成master的可能,通常最简单的是,在进程启动的时候配置成replication的,而不是当一个clinets变成master的那个时候试图去配置)。很显然,在每个client上至少一个控制线程必须被配置成replication的,因为消息继续在master和client间被传递。
由于实现的原因,所有的进入的replication消息必须用同一个DB_ENV句柄处理。它不要求一个单一的控制线程处理所有的消息,只要求所有的控制线程用同一个句柄处理消息。
Building the communications infrastructure
应用程序具备replication支持,典型的被写成一个或多个控制线程循环在一个或多个通信通道上,接受和发送消息。这些线程为本地数据库环境从远程环境接受消息,和为远程环境从本地环境接受消息。远程环境消息通过DB_ENV->rep_process_message方法从远程环境被传递到本地数据库环境。本地环境的消息通过指定给DB_ENV->set_rep_transport方法的回调函数,被发送出去。
那些进程通过调用DB_ENV->set_rep_transport方法建立通信通道。而不管它是运行在client还server环境上。这个方法指定发送函数,一个bdb用来发送消息给组内的其它数据库环境的回调函数。这个函数携带一个环境id和两个不透明的数据对象。
发送函数有责任根据id传送两个数据对象里的信息到特定的数据库环境,而后,[那边的]接受应用程序调用DB_ENV->rep_process_message方法去处理这个消息。
传输机制的细节完全留给了应用程序;唯一的要求是,每个控制器(进程或线程)的数据缓冲区和大小,和发送站点上传送给发送函数的那些DBT数据结构,通过调用加以适当的参数调用DB_ENV->rep_process_message被如实的拷贝和交付到接受站点。被广播的消息(无论是被广播媒体广播还是当直接通过设置DB_ENV->set_rep_transport方法的参数DB_EID_BROADCAST),不应该被消息发送器处理。在所有的情况下,应用程序的传输媒体或软件必须确保,当打算把一个消息发送给不同的数据库环境的时候从不调用DB_ENV->rep_process_message,或者,从同一个环境发送的广播消息,在这个环境上DB_ENV->rep_process_message 将被调用。DB_ENV->rep_process_message方法是免线程的(free-threaded),它可以安全地同时地交付任意数量的消,这些消息可以是来自这个bdb环境中任意进程或线程的。
这儿有一些DB_ENV->rep_process_message方法返回的信息(返回值):
DB_REP_DUPMASTER:意味着组内的另一个数据库环境也相信它(另一个环境)自己将成为一个master。这个应用程序应该完成所有活动的事务,关闭所有打开的数据库句柄,用DB_ENV->rep_start方法重新配置它自己为一个client,然后通过调用DB_ENV->rep_elect号召一次选举。
DB_REP_HOLDELECTION:意味着组内的另一个环境已经号召了一个选举。这一应用程序应该调用DB_ENV->rep_elect参与选举。
DB_REP_IGNORE:意味着着个消息不能被处理。它一般暗示这个消息跟现在的replication状态不相关,就像一个迟到的过期的老消息。
DB_REP_ISPERM:意味着一个持久的消息,也许是一个以前返回的消息要作为一个记录被写入的。ret_lsnp包含这个永久记录的写入最大LSN。
DB_REP_NEWMASTER:意味着一个新的master已经被选举出来了。这个调用也返回master对应的本地id。如果这个master id改变了,这个应用程序可能需要重新配置自己(例如,更新数据时,直接询问新的master而不是旧的)。如果新master就是本地环境自己,那么这个应用程序必须调用DB_ENV->rep_start方法,重新配置自己作为一个replication组的master来支持bdb库。
DB_REP_NEWSITE:意味着收到了组内的一个未知成员的消息。应用程序应该重新配置它自己以便它能发送消息到那个站点。
DB_REP_NOTPERM:一个标志着DB_REP_PERMANENT的消息被成功处理,但是没有被写入磁盘。这通常是暗示一个或多个消息本应该在这个消息之前到达,但没有到。这个操作将被写入磁盘当丢失的消息到达的时候。参数ret_lsnp将包含这条记录的LSN 。这个应用程序应该施行所有认为必要的措施来保持它的可恢复性特征。
DB_REP_STARTUPDONE:意味着client已经完成了他的启动同步活动,现在正在处理来自master的活的日志消息。活的日志消息是指:master正在发送的,将要发送出来的消息,因为反对重新发送那些将由client请求的日志消息。
无论何时,当DB_ENV->rep_process_message返回DB_REP_NEWSITE的时候,连接新的站点到组内将会发生。这个应用程序应该分配给这个新站点一个本地环境id号,将来所有的来自这个站点的传递给DB_ENV->rep_process_message消息都应该包含那个环境id号。当然,在DB_ENV->rep_process_message返回之前应用程序就能知道一个新站点也是可能的(例如,应用次序使用面向连接的协议很有可能立即探测到一个新站点,然而,应用程序使用广播协议就不可能了)。
无论怎样,只要在应用程序中支持动态添加数据库环境到replication组,环境添加到一个已经存在的组可能需要提供联系信息(例如,在一个应用程序中使用TCP/IP套接字,一个域名或IP地址都将是一些需要提供的合理的值)。这些可以用DB_ENV->rep_start方法的cdata参数来完成。cdata所引用的信息被预先包装在这个新环境发送的初始的联系信息中,和通过使用DB_ENV->rep_process_message返回的rec 参数,被提供给组内已存在的成员。如果没有附加的信息被提供给bdb以转交给组内已存在的成员,那么在DB_ENV->rep_process_message返回DB_REP_NEWSITE之后,传递给DB_ENV->rep_process_message方法的rec参数的数据域将为NULL。
Elections
无论何时,当Clients与master环境失去联系的时候,当他们看到DB_ENV->rep_process_message方法返回DB_REP_HOLDELECTION 的时候,或当不管由于什么原因,使他们不知道谁是master的时候,他们都应该发起一次选举 。应用程序不必要在一开始启动的时候就立即举行选举,因为任何已经存在的master将会在调用DB_ENV->rep_start后被发现。如果在一小段等待时间后后没发现master,那么应用程序就应该发起一次选举。
为了使一个client去赢得选举,组内选择必须没有master,而且这个client必须有最多的最近的 log记录,有相同数量记录的,优先权高者获
胜。应用程序指定最小量的组内成员必须参与将要要公布胜出者的选举。我们推荐至少((N/2) + 1)个成员参加。如果少于或等于半数参加,将给予一个警告。
如果一个应用程序的策略是这样的:哪一个站点将赢得选举可以在数据库环境信息的条目里参数化的(也就是说,站点的数目,可用log记录,和相对优先权都是那些参数),那么bdb就可以透明的处理所有的选举。尽管如此,这儿还存在这种情况,就是应用程序完全知道而且需要去干涉选举结果。例如,应用程序可能被选定去处理master的挑选工作,明确的指明master和clients站点。应用程序在这些情况下,可能永远不需要发起一次选举。作为选择,应用程序可能会选择使用DB_ENV->rep_elect的参数,去强制正确的选举结果。也就是说,如果一个应用有3个站点,A, B, 和 C,当C倒掉后A必须成为胜出者,应用程序可以通过在选举后指定适当的优先权保证选举的结果:
on A: priority 100, nsites 2
on B: priority 0, nsites 2
用DB_ENV->rep_start方法配置多于一个以上的master是很危险的,应用程序应该小心的不这样做。应用程序应该只配置他们自己作为master,如果他们是仅有的可能成为master的站点,或者,如果他们赢得了选举。一个应用程序只能知道它赢得了选举,如果DB_ENV->rep_elect方法返回成功信息,和本地环境的id号成为新的master环境id,或者,如果DB_ENV->rep_process_message返回DB_REP_NEWMASTER和本地环境的id号成为新的master环境id。
为了添加一个数据库环境到组内,意图成为一个master,首先作为一个client添加它。因为它可能数据过时,还需要考虑现在的master,允许它从现在的master那里更新自己的数据。然后,关闭现在的master。推测,这刚添加的client将要赢得随后的选举。如果这个client没有赢得选举,很有可能是没有给它足够的时间从当前的master那去更新它自己的数据。
如果一个client不能找到一个master或赢得一场选举,那意味着,网络被隔离,这儿没有足够的环境一起参与这次选举使得其中一个参与者能够胜出。在着种情况下,应用程序应该重复地调用B_ENV->rep_start和DB_ENV->rep_elect,交互地尝试去发现一个已存在的master,和举行一次选举宣布一个新的master。在一个令人绝望的环境中,一个应用程序可能通过调用DB_ENV->rep_start简单的宣布自己成为master,或通过减少所需的参与者数目去赢得一个选举,直到这个选举胜出。
下面这些解决方案都是不推荐使用的:在网络被隔离的情况下,下面任何一个选择都可能导致组内有两个master,环境中的数据库可能会不可挽救的产生分歧,因为它们被那些masters以不同的方法修改了。在双系统replication组的情况下,应用程序可能想要求访问一个远程网络站点,或一些其它外部的加赛(tie-breaker)以允许系统宣布自己为master。
这个client和master增加负担。这儿有一些措施,一个应用程序可以用来减轻同步的负担。
任何client接受到一个它不能满足的请求,都将回复给请求的client,告诉它,自己不能够提供请求的信息,而那个最初的请求者,将重新请求这个信息。另外,如果这个最初的请求没有到达发要发送给的那个目标client端,这个最初的发送请求的client也将重新请求这个信息。这这些任意一种情况下,这个重新的请求将把传输函数的标志设置成DB_REP_REREQUEST 。应用程序可能通过把这个请求进一步传递给naster来响应这个DB_REP_REREQUEST 标志(因为是被这个消息的环境id指定的),或继续把这个请求传送给另一个client。
当一个client在响应另一个client的请求的信息的时候,如果批量传输被配置,记录将在批量缓冲区中堆积。批量缓冲区将发送给client,当缓冲区被装满了或当这个client的请求已经被满足了,再没有特别类型的记录将需要这缓冲区去发送了。
这个应用程序的client或master消息处理的循环应该适当的带些动作以确保在这种情况下的正确的事务保障。当丢失的记录到达,而且允许随后处理这些以前存储的永久记录,在clinet上调用DB_ENV->rep_process_message 方法将返回DB_REP_ISPERM和返回永曾经被刷新到磁盘中的久记录的最大的LSN。Client 应用次序可以用这些LSN决定性的知道是否某些特定的LSN被永久存储了。
考虑两个有网络连接的的系统。一个作为master,一个作为只读的client。如果master倒掉了client取代之,在这次故障后,master又重新加入到replication组。master 和client都被配置成当事务提交的时候不同步刷新日志(也就是说DB_TXN_NOSYNC 在两个系统上都被配置)。这个应用程序的发送函数从不返回失败到bdb库,只个简单的传递消息到这个client(或许基于一个广播机制),而且总是返回成功。在client上,由client的DB_ENV->rep_process_message方法返回的DB_REP_NOTPERM也将被忽略。这个系统配置有优秀的性能,但在某些失败的模式下有可能丢失数据。
编写应用程序的发送函数,使它们直到一个或多个client承认收到了这个消息时才返回到bdb。这个clients的数目的选择是由应用程序决定的:你将很可能会想网络隔离(确保每个物理站点的client收到这个消息)和地理上多样性(确保一个client在每条线路“each coast ”上都收到这个消息)。
Network partitions
bdb replication 的实现可能被网络隔离的问题影响。
例如,考虑replication组有n个成员。网络隔离让master在一边,多于一半(n/2)的站点在另外一边。和master在一边的站点将继续前进,master继续接受数据库的写请求。不幸的是,隔离在另一边的站点,意思到他们的master不在了,将举行一个选举。这个选举将取得成功,因为这儿有总数n/2以上的站点在这边,然后这个组内将会有两个master。既然两个master都可能潜在地接受写请求,那么数据库将可能产生分歧,使得数据不一致。
如果曾经在一个组内发现了多个master,一个master检测到这个问题的时候将会返回DB_REP_DUPMASTER。如果一个应用程序看到这个返回,它应该重新配置自己作为一个client(通过调用ENV->rep_start),然后发起一场选举(通过调用DB_ENV->rep_elect)。赢得这次选举的可能是先前的两个master之一,也可能完全就是另外的站点。无论如何,这个胜出的系统将引导其它系统达到一致。
作为另外一个例子,考虑一个replication组有一个master环境和两个client,A和B,在那A可能会升级为master地位而B不可能。然后,假设client A从其他的两个数据库环境中被隔离出来了,它的数据变的过期。然后假设这个master倒掉了,而且不再上线。随后,网络隔离被修复了,client A和B进行了一次选举。因为client B不能赢得选举,client A将会默认地赢得这次选举,为了重新和B同步,可能在B上提交的事务将不能回滚直到这两个站点能再次地一起前进。
在这两个例子中,都有一步就是新选举出的master引导组内的成员和它自己一致,以便它可以开始发送新信息给它们。这可能会丢失信息,因为以前提交的事务没有回滚。
在体系结构上网络隔离是个问题,应用程序可能想实现一个心跳协议以最小化一个糟糕的网络隔离的影响。只要一个master至少可以和组内一半的站点通信的时候,就不可能出现两个master。如果一个master不再能和足够的站点取得联系的时候,它应该重新配置自己作为一个client,和举行一次选举。
这儿有另外一个工具应用程序可以用来最小化网络隔离情况下的损失。通过指定一个 nsites 参数给DB_ENV->rep_elect ,也就是说,比组内的实际成员的数目大,应用程序可以阻止系统宣布他们自己成为master,除非它们可以和组内绝大部分站点通话。例如,如果组内有20个数据库环境,把参数30指定给DB_ENV->rep_elect方法,那么这个系统至少要和16个站点通话才可以宣布自己为master。
指定一个小于组内世界成员数目的nsites参数给DB_ENV->rep_elect,也有它的用处。例如,考虑一个组有只有两个数据库环境。如果他们被隔离了,其中任何一个都不能取得足够的选票数成为master。一个合理的选择是,指定一个系统的nsites 参数为2,另一个为1。那样,当被隔离的时候,其中一个系统可以赢得选举权,而另一个不能。这能允许当网络被隔离的时候其中一个系统能继续接受写请求。
这些关卡强调了bdb replicated环境中好的网络底层构造的重要性。当replicating数据库环境在严重丢包的网络环境中,最好的解决可能是拣选一个单一的master,只有当人工干涉决定这个被选择的master不能再恢复上线时,才举行选举。
Replication FAQ
Does Berkeley DB provide support for forwarding write queries from clients to masters?
No, it does not. The Berkeley DB RPC server code could be modified to support this functionality, but in general this protocol is left entirely to the application. Note, there is no reason not to use the communications channels the application establishes for replication support to forward database update messages to the master, since Berkeley DB does not require those channels to be used exclusively for replication messages.
Can I use replication to partition my environment across multiple sites?
No, this is not possible. All replicated databases must be equally shared by all environments in the replication group.
I'm running with replication but I don't see my databases on the client.
This problem may be the result of the application using absolute path names for its databases, and the pathnames are not valid on the client system.
How can I distinguish Berkeley DB messages from application messages?
There is no way to distinguish Berkeley DB messages from application-specific messages, nor does Berkeley DB offer any way to wrap application messages inside of Berkeley DB messages. Distributed applications exchanging their own messages should either enclose Berkeley DB messages in their own wrappers, or use separate network connections to send and receive Berkeley DB messages. The one exception to this rule is connection information for new sites; Berkeley DB offers a simple method for sites joining replication groups to send connection information to the other database environments in the group (see Connecting to a new site for more information).
How should I build my send function?
This depends on the specifics of the application. One common way is to write the rec and control arguments' sizes and data to a socket connected to each remote site. On a fast, local area net, the simplest method is likely to be to construct broadcast messages. Each Berkeley DB message would be encapsulated inside an application specific message, with header information specifying the intended recipient(s) for the message. This will likely require a global numbering scheme, however, as the Berkeley DB library has to be able to send specific log records to clients apart from the general broadcast of new log records intended for all members of a replication group.
Does every one of my threads of control on the master have to set up its own connection to every client? And, does every one of my threads of control on the client have to set up its own connection to every master?
This is not always necessary. In the Berkeley DB replication model, any thread of control which modifies a database in the master environment must be prepared to send a message to the client environments, and any thread of control which delivers a message to a client environment must be prepared to send a message to the master. There are many ways in which these requirements can be satisfied.
The simplest case is probably a single, multithreaded process running on the master and clients. The process running on the master would require a single write connection to each client and a single read connection from each client. A process running on each client would require a single read connection from the master and a single write connection to the master. Threads running in these processes on the master and clients would use the same network connections to pass messages back and forth.
A common complication is when there are multiple processes running on the master and clients. A straight-forward solution is to increase the numbers of connections on the master -- each process running on the master has its own write connection to each client. However, this requires only one additional connection for each possible client in the master process. The master environment still requires only a single read connection from each client (this can be done by allocating a separate thread of control which does nothing other than receive client messages and forward them into the database). Similarly, each client still only requires a single thread of control that receives master messages and forwards them into the database, and which also takes database messages and forwards them back to the master. This model requires the networking infrastructure support many-to-one writers-to-readers, of course.
If the number of network connections is a problem in the multiprocess model, and inter-process communication on the system is inexpensive enough, an alternative is have a single process which communicates between the master the each client, and whenever a process' send function is called, the process passes the message to the communications process which is responsible for forwarding the message to the appropriate client. Alternatively, a broadcast mechanism will simplify the entire networking infrastructure, as processes will likely no longer have to maintain their own specific network connections.
(HA 部分结束)
来源:http://blog.youkuaiyun.com/whycold/article/category/1830811