berkeley db--入门介绍
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很简单,代码如下:
cd ./db-4.8.30/build_unix
../disk/configure --prefix=
make && make install
安装目标目录(/usr/local/BerkeleyDB.4.8)包含四个子目录:
A. bin 一些实用工具
B. docs 文档
C. include 包含了使用BDB库开发程序时的头文件
D. lib 包含了使用BDB库开发程序时需要连接的库文件
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的功能模块,或者说是从系统结构角度进行划分,可将其分为几个子系统:
存储管理子系统 (Storage Subsystem)
内存池管理子系统 (Memory Pool Subsystem)
事务子系统 (Transaction Subsystem)
锁子系统 (Locking Subsystem)
日志子系统 (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相当于数据库。
DB_ENV *dbenv;
DB *dbp;
int ret;
if ((ret = db_env_create(&dbenv, 0)) != 0) {
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
return (1);
}
dbenv->set_errfile(dbenv, errfp);
dbenv->set_errpfx(dbenv, progname);
if ((ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0)) != 0) {
dbenv->err(dbenv, ret, "set_cachesize");
dbenv->close(dbenv, 0);
return (1);
}
(void)dbenv->set_data_dir(dbenv, data_dir);
if ((ret = dbenv->open(dbenv, home, DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN, 0644)) != 0) {
dbenv->err(dbenv, ret, "environment open: %s", home);
dbenv->close(dbenv, 0);
return (1);
}
if ((ret = db_create(&dbp, dbenv, 0)) != 0){
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
return (1);
}
if ((ret = dbp->open(dbp, NULL, "exenv_db1.db", NULL, DB_BTREE, DB_CREATE,0644)) != 0){
fprintf(stderr, "database open: %s\n", db_strerror(ret));
return (1);
}
if ((ret = dbp->close(dbp, 0)) != 0) {
fprintf(stderr, "database close: %s\n", db_strerror(ret));
return (1);
}
if ((ret = dbenv->close(dbenv, 0)) != 0) {
fprintf(stderr, "DB_ENV->close: %s\n", db_strerror(ret));
return (1);
}
return (0);
数据文件:
一个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中提供了两种方式来支持复制技术:Replication Base API和Replication Manager。可以说Replication Base API是最基础的API,实现方式灵活,功能强大,但是编码量大;Replication Manager相当于框架,使用方式简单,编码量小。Replication Manager可能能够满足大部分用户的需求,但不是所有需求,所以灵活性不足。如果您的需要是Replication Manager不能满足的,请使用Replication Base API自己实现复制策略。Replication Manager的主从策略有两种:指定主从、自动推举主从。
分区:
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资源的链接:
官方主页:
http://www.oracle.com/database/berkeley-db/db/index.html
产品下载:
http://www.oracle.com/technology/software/products/berkeley-db/index.html
官方开发者文档中心:
http://www.oracle.com/technology/documentation/berkeley-db/db/index.html
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的具体工作机制。
参考:Berkeley DB 产品对比
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命中。
Berkeley DB -- 主从复制(HA)
Introduction
bdb包括对构建基于复制(replication)的高可用性应用程序的支持。bdb replication组由一些独立配置的数据库环境组成。
组里只有一个master数据库环境和一个或多个client环境。Master环境支持读和写,client环境支持只读。如果master环境倒掉了,应用程序将可能提升一个client为新的master。数据库环境可能在单独的计算机上,在单独的硬件分区上(partitions)一个不统一的内存访问系统上,或在一个单独的server的一个磁盘上。唯一的约束就是,replication组的所有的参与者必须在一个字节序(endianness)相同的机器上(都是大数再前或都是小数在前的操作系统)。我们期望这个约束在以后的版本中会去掉。因为总是用bdb环境,任何数量的并发进程或线程可能访问一个数据库环境。在master环境中,多个线程可能读写这个环境。在client环境中,多个线程可能要读这个环境。
应用程序可能被编写成在master和clients间提供不同程度的稳固性。系统能同步的运行以便复制品(replicas)能保证是最新的,对应于所有已提交的事务。但是这样做可能回招致性能上的很大的下降。高性能解决方案有考虑全局的稳固性,允许clients的数据过时一个应用程序可控制的一段时间。
尽管bdb包括必要的构建高可用性数据库环境的底层基础,应用程序仍然必须提供一些鉴定的(critical)组成部分:
应用程序有责任提供通信下部构造。应用程序可能用任何适当的通信协议。例如RPC, TCP/IP, UDP, VI或底板(backplane)消息传递。
应用程序有责任命名。bdb涉及到一个replication组成员的时候是靠一个应用程序提供的id,应用程序必须映射那个id到一个特殊的数据库环境中或通信通道中。
应用程序有责任监视master和clients的状态,和识别任何不可用的(unavailable)数据库环境。应用程序必须提供所有的需要的安全策略。
例如,应用程序可能选择去加密数据,用一个安全的套接层,或什么也不做。
最后,bdb replication实现还有一个附加的特性去增强可靠性。bdb中的replication实现成执行数据库更新用一个不同的编码路径而不是用标
准的。这意味着,有bug的软件的操作可能会毁坏replication master,但不会把clients也毁坏。
Replication和相近的方法的描述:
DB_ENV->rep_elect 举行一个replication竞选
DB_ENV->rep_process_message 处理一个replication消息
DB_ENV->rep_stat Replication统计
DB_ENV->rep_sync Replication同步
Replication 配置:
DB_ENV->rep_set_config 配置replication系统
DB_ENV->rep_start 为replication配置一个环境
DB_ENV->set_rep_limit 限制在响应但个消息时的数据发送
DB_ENV->set_rep_transport 配置replication传输
Replication environment IDs
每个在replication组中的数据库环境必须有一个独一无二的标识符,为它自己和其它replication组中的成员都分配一个不同标识符。这些标识符不必要是全局的,也就是说,每个数据库环境可以分配本地化的标识符给replication组的成员。就是在每数据库环境中都能区分出其他成员就行了,当然全局统一给指定标识符也不为错,只是非必要的。
应用程序有责任去标志每个进来的传递给DB_ENV->rep_process_message的有适当标识符的replication消息。随后,bdb将用这些相同的标识符去标志发送函数发出去的消息。
负标识符被bdb保留使用,不应该被应用程序指定给那些环境。有两个保留的标识符准备给应用程序使用的是:
DB_EID_BROADCAST:指定一个消息应该被广播给所有replication组中的成员。
DB_EID_INVALID:是一个无效的环境id,可能被用于初始化一些环境id变量,那些变量随后被检查合法性。
Replication environment priorities
每个replication组中的数据库环境变量必须有一个优先权,它指定了在replication组中不同环境间的一个相对的顺序。这个顺序在币桓鰉aster倒掉,在决定选举哪个环境作为新master的时候的一个重要因素。优先权必须是一个非负的整数,但不必要replication组中是独一无二的。优先权为0意味着这个系统永远不能成为一个master,是被忽略的。数越大表示优先权越高,例如,如果一个replication组由3个数据库环境组成,两个由OC3 连接,第三个由T1连接,那么第三个数据库环境就应该被指定一个低点的优先权。
决定哪个client将当选为master时,先看谁有最多的近期的log记录。log记录一样多时,将参考优先权。如果log和优先权一样多时候,将随即
选择一个。
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->set_rep_transport:配置replication系统的通信底层构造。
DB_ENV->rep_start:配置,或重新配置一个已经存在的数据库环境成为一个master或client.
DB_ENV->rep_process_message:处理从replication组中别的环境进来的一个消息。对clients来说,它负责接受log记录和根据master过来的消息更新本地数据库。对master和clients来说,都有责任处理一些行政的功能(例如,处理消息丢失的协议),和允许新的clients加入到活动的replication组。这个方法只应该在环境已经通过DB_ENV->rep_start被配置成一个master或client后被调用。
DB_ENV->rep_elect:使replication组选举一个新的master,它在clients与master失去联系的时候和应用程序想在剩余的站点中选择一个新master的时候被调用。
DB_ENV->set_rep_limit:在响应一个单独的调用DB_ENV->rep_process_message时,将要发送的数据量上强加一个上限。当一个客户执行恢复的时候,也就是说,当一个replica站点是同去同步master的时候,clients可能找master要大量的log记录,那样它将使它们之间在未来一段时间内忙于循环发送大量数据,那么这时候可能就想在失去控制和接受其他正常消息前,用DB_ENV->set_rep_limit限制master将发送的数据量。
为了把replication添加到应用程序,应用程序初始化必须改变,还有应用程序的通信底层构造必须编写。应用程序初始化改变相对简单,通信底层构造编码可能会很复杂。
由于实现的的原因,所有replicated的数据库必须放在环境指定的数据目录中。如果你的数据库放在默认的环境home目录中,他们必须就放在本目录中,而不是子目录中。必须注意那些使用相对路径和在打开环境后更改工作目录的应用程序。在这中应用程序中,replication初始化代码将找不到数据库。而那些改变工作目录的可能需要使用绝对路径。
在应用程序初始化期间,应用程序执行三个附加的任务:第一,当打开它的数据库环境的时候,它必须指定DB_INIT_REP标志。第二,它必须提供关于它的通信底层构造bdb信息。第三,它必须启动bdb replication系统。通常,一个replicated应用程序将做正常的bdb恢复和配置,非常像其他的事务应用程序。然而,一旦数据库环境被打开,它将调用DB_ENV->set_rep_transport方法去配置bdb的replication,
and 然后将调用DB_ENV->rep_start方法去加入或创建replication组。
当在应用程序启动时调用DB_ENV->rep_start的时候,应用程序有两个选择:通过配置为组内指定一个master,或者,可选择,配置所有组内成员为clients然后调用一个选举方法,在它们之间选择一个master。每种方法都只正确的,完全取决于你的应用程序。DB_ENV->rep_start调用的结果,往往是发现了一个master,或者,声明本地环境作为master。如果在一段可以接受的时间内还没找出一个作为master,应用程序应该调用DB_ENV->rep_elect搞一次选举。
考虑到多进程或多环境句柄修改在replicated环境中的数据库的情况。所有的修改必须在master环境中完成。第一个进程加入或创建master环境必须调用DB_ENV->set_rep_transport方法和DB_ENV->rep_start方法,随后的一些replication进程必须至少DB_ENV->set_rep_transport方法。这些进程有可能调用DB_ENV->rep_start方法(只要他们使用相同的master或者client参数)。如果多个进程正在修改master环境,这儿必须有一个统一标准的通信底层构造,以便到达clients的消息有一个单一的master ID。附加地,应用程序必须被构造成以便于所有进来的消息能被一个单一的DB_ENV句柄处理。
请注意,不是所有的运行在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句柄处理。它不要求一个单一的控制线程处理所有的消息,只要求所有的控制线程用同一个句柄处理消息。
没有附加的要求去调用一个方法关闭一个由多方参加的replication组中数据库环境。应用程序应该像平时一样通过调用DB_ENV->close关闭环境。
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请求的日志消息。
Connecting to a new site
为了添加一个新站点到replication组,所有需要做的就是,这个client成员的加入。bdb将从master到client自动地执行一个内部初始化,然后,将为刚加入的成员执行恢复,以使它的数据能和master同步。
无论何时,当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
应用程序有责任发起选举。举行一次选举将没有任何风险,因为bdb选举步骤保证这儿没有一个以上的master数据库环境。
无论何时,当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。
如果好多系统同时倒掉,一个非首选的数据库环境赢得选举也是可能的。因为一个选举的胜出者很快就会被宣布,如果有足够多的环境参与这场选举的话 。在一个速度慢,但是连接良好的机器上的环境,可能会和一个速度快但是连接很糟糕的环境失去联系。在好多环境同时倒掉的情况下(例如,一批replicated机器在同一个机房),应用程序一开始应该引导这些数据库环境作为clients上线(那样将允许它们立即去处理读请求),然后等足够的时间后,等稍微慢些的机器都都追赶上之后就举行一场选举。如果,不管什么原因,一个非首选的数据库环境成为了master,在一个replicated环境中更换master是可能的。例如,首选的master倒掉了,组内一个client成员成为了新master。为了使首选的master再恢复到它master的地位,跟着下面的做就可以:
一,首选的master应该重新启动,重新作为一个client加入到组内。
二,一旦首选的master追赶上了组内的进度(数据同步的进度),当前的master应该完成所有当前的活动的事务,然后重新用DB_ENV->rep_start方法配置自己成为一个client上线。
三,然后,当前的master,或那个首选的master应该用DB_ENV->rep_elect方法发起一次选举。
Synchronizing with a master
当一个client探测到replication组内一个新的master后,在它能去处理新的数据库变化之前,这个client必须去同步这个新的master。同步是一个重量及操作,它能同时给
这个client和master增加负担。这儿有一些措施,一个应用程序可以用来减轻同步的负担。
延迟client同步:
当组内有了一个新的master,不论是被应用程序指定的还是因为选举的结果,所有的clients必须去同步这个新的master。这会使新的master的资源过度损耗,因为很多clients可能都试图去和它通信和从它那儿取得记录。clients应用程序如果想延迟client的同步,应该用DB_REP_CONF_DELAYCLIENT标志去调用DB_ENV->rep_set_config 方法。这个配置使得client总从DB_ENV->rep_process_message方法返回DB_REP_NEWMASTER,但是这个client将不会继续去同步这个新的master。Client端应用程序选择延迟同步,用这种方法,那么将来再用DB_ENV->rep_sync方法去同步将是可靠的。
client-to-client的同步:
Clients可以接受和服务其他clients的请求。Clients请求记录调用这个Client应用程序的传输回调函数。可以由其他Clients满足的请求,传输函数的标志被设置成DB_REP_ANYWHERE。应用程序可以选择发送这些请求到任意client,或者忽略这个标志,而把请求发送到这个消息的环境id所指定的站点。
Client应用程序可以用不管什么算法,它们选择来负载均衡到其它clients的请求。
任何client接受到一个它不能满足的请求,都将回复给请求的client,告诉它,自己不能够提供请求的信息,而那个最初的请求者,将重新请求这个信息。另外,如果这个最初的请求没有到达发要发送给的那个目标client端,这个最初的发送请求的client也将重新请求这个信息。这这些任意一种情况下,这个重新的请求将把传输函数的标志设置成DB_REP_REREQUEST 。应用程序可能通过把这个请求进一步传递给naster来响应这个DB_REP_REREQUEST 标志(因为是被这个消息的环境id指定的),或继续把这个请求传送给另一个client。
这延迟的同步和client-to-client的同步特性允许应用程序在replication组内做负载均衡。例如,考虑到一个组内有5个站点, A, B, C, D 和 E,站点E刚刚倒掉了,站点A被选举为master。站点C 和 D 被配置成延迟同步的,当B注意到站点A是一个新master,它立即进行同步。当B完成和master的同步的时候,站点C 和 D 上的应用程序调用 DB_ENV->rep_sync 方法,使它们也进行同步。站点C 和 D (和 E, 当它完成了重新引导) 可以发送他们的请求到站点 B, 而B可以负担因为同步而产生的工作和网络流量的冲击, 使的master,站点A,有空去处理正常的应用程序工作量和由于选举而暂停的写请求。
阻塞client的操作:
Clients在处理和master的同步的时候将阻塞bdb其它的操作。默认的,许多bdb方法将阻塞,直到client同步完成,然后,这些被阻塞的方法才继续调用。
那些不能等待的Client应用程序,将更愿意立即得到一个错误返回而不是阻塞,那么就应该用DB_REP_CONF_NOWAIT 标志调用DB_ENV->rep_set_config方法。如果这个clinet现在正在同步一个master,这个配置使得bdb方法调用立即返回一个DB_REP_LOCKOUT 错误,而不是阻塞。
Clients太落伍以至于不能同步:
Clients试图去和master同步的时候可能会发现,同步是不可能的了,因为client和master 已经失去联系很久了。默认地,client和master自动的检测这个状态和执行clinet内部初始化。因为内部初始化需要传输整个数据库到clinet,这可能会发费一个相对较长的时间,可能需要clinet应用程序的数据库句柄重新被打开。
那些不能等待的应用程序,更愿意推后内部初始化直到一个更加便利的时间,或者更希望做一个热备份而不是执行内部初始化,应该用REP_CONF_NOAUTOINIT 标志来调用DB_ENV->rep_set_config方法。这个配置使得bdb返回DB_REP_JOIN_FAILURE到应用程序而不是执行内部初始化。
Client应用程序如果选择了延迟同步,这样就有责任在将来的时间和master进行同步。这可以通过关闭DB_REP_CONF_NOAUTOINIT标志和调用DB_ENV->rep_sync 方法来完成,或通过执行一个热备份。
Initializing a new site
默认地,添加一个新站点到一个replication组,只需要这个client去加入。bdb将自动从master到client给它执行内部初始化,引导这个client和master同步。尽管如此,还依赖于网络和底层结构,在一些场合下使用“热备份”在组内去初始化一个client还是比较有利的。Clients不想自动执行内部初始化应该用DB_REP_CONF_NOAUTOINIT标志调用DB_ENV->rep_set_config 方法。这个配置使得bdb返回DB_REP_JOIN_FAILURE到应用程序的DB_ENV->rep_process_message方法,而不是执行内部初始化。
为了使用热备份去初始化一个到replication组的clinet,执行下面的步骤:
做个一master环境的档案备份,就像在“数据库和日志文件档案”(Database and log file archival)中描述的那样。这个备份可以是常规的备份或一个热备份。
拷贝这个档案备份到client的一个干净的环境目录中。在client的新环境里执行灾难恢复,就像在恢复程序中描述的那样。
重新配置和重新打开这个环境作为组内的一个client成员。如果拷贝这个备份到client,相对于用db_archive公用程序或用DB_ENV->log_archive方法再造日志文件的周期来说,发费较长的时间,它可能需要抑制日志再造(reclamation),直到这个新启动的client已经追上(caught up)和应用了所有在停工期间产生的log记录。
就像任何bdb应用程序一样,当应用程序启动的时候数据库环境必须在一个一致的(consistent)状态。这是可以通过在一个线程或一个进程启动时执行恢复来最简单的确定的。这是对clients和masters来说无害的,即使它们不是严格的必需的,也就是说即使他们实际上没什么需要恢复的。
Bulk transfer
组内的站点们可能通过用DB_REP_CONF_BULK标志调用DB_ENV->rep_set_config 方法,而把配置成使用批量传输的。当被配置成批量传输,站点们将在一个缓冲区内积聚记录,然后在一次单一的网络传输中把它们传送给另一个站点。配置批量传输对master来说当然很有意义。另外,使用client-to-client同步的应用程序可能会发现,配置成批量传输对clients站点们来说也是很有帮助的。
当一个master正在产生新的log记录时,或者,或请求任何master的信息时,如果批量传输被配置,记录将堆积在一个批量缓冲区中。当缓冲区被装满了或一个永久的记录(例如,一个事务提交或一个检查点记录)被这个client排队等待,批量缓冲区将发送给client。
当一个client在响应另一个client的请求的信息的时候,如果批量传输被配置,记录将在批量缓冲区中堆积。批量缓冲区将发送给client,当缓冲区被装满了或当这个client的请求已经被满足了,再没有特别类型的记录将需要这缓冲区去发送了。
批量缓冲区的大小,它自己已经内定了不能被配置。尽管如此,在一次传输数据的总的大小可以通过使用DB_ENV->set_rep_limit方法来限制。
Transactional guarantees
在总的数据库环境的事务保证的上下文中去考虑replication是和重要的。作为简要的回顾,在一个非复制(non-replicated )中事务保证,是基于写log文件记录到稳定的存储设备上,通常是一个磁盘驱动器。如果应用程序回系统出了故障,bdb的日志信息将在恢复时被回顾,而且数据库被更新,以使已提交的事物的改变部分出现,所有未提交的而改变的部分不出现。在这种情况下,没有信息将会丢失。
如果一个数据库环境不要求当事务提交的时候把log写到稳定存储设备上(使用DB_TXN_NOSYNC标志能以牺牲事务的经久性为代价来增加性能),bdb恢复将只能把这个存储系统恢复到最后一个提交了的被保存在磁盘上了的状态。在这种情况下,信息可能已经丢失(例如,一些已经由事物提交了的改变可能在恢复之后不会出现在数据库中)。
更进一步。如果这儿有数据库或log文件丢失或腐烂(例如,如果一个磁盘驱动器出了故障),然后灾难恢复将是必要的,bdb恢复将只能把这个系统重新存储到最后一次归档log那儿。在这中情况下,信息仍然可能被丢失。
复制(Replicating)这个数据库环境通过添加一个新的组成到“稳定存储”上扩展了这个模式:这个clinet的replicated信息。如果一个数据库环境是replicated,在数据库或log丢失的情况下,这儿也不会有信息丢失,因为这个复制系统可以被配置成包含数据库和log记录直到故障点的全部的设置。一个数据库丢失了一个磁盘驱动器能使这个磁盘被复制,然后,它有能重新加入这个复制(replication)组。
由于这个新的稳定存储的组成部分,指定DB_TXN_NOSYNC到一个复制组将不再牺牲耐久性了,只要具备一个或多个clients有公认的master所发送消息的收条。因为网络连接通常比本地同步磁盘写的快,replication成为大幅度提高它们的性能和可靠性的一个方法。
应用程序发送函数的返回状态必须被应用程序设置成确保应用程序想要提供的事务保障。无论什么时候,发送函数返回失败,本地数据库环境的log将被刷新(flushed),以确保任危急到到数据库完整性的信息不被丢失。因为这个刷新是一个昂贵的开销,会影响数据库性能,如果可能的话,应用程序应该避免从发送函数那返回一个错误信息。
replication事务保障唯一感兴趣的消息类型是当应用程序的发送函数指定了DB_REP_PERMANENT 标志被调用。如果发送函数曾经返回失败这儿将是没有原因的,除非
DB_REP_PERMANENT 标志被指定--消息没有DB_REP_PERMANENT被指定,不会使数据库有明显的改变,而且发送函数可以成功返回到bdb,只要这个消息被发送到client(s),或仅仅被拷贝到本地应用程序内存中预备发送。
当一个client收到一个DB_REP_PERMANENT 消息,这个client将在返回前刷新它的log到稳定存储设备上(除非这个client环境曾经用DB_TXN_NOSYNC 选项配置过)。
如果这个client不能刷新一个全部的事务记录到磁盘,不管什么原因(例如,在被标志的消息前丢失了一个log记录),在client上调用DB_ENV->rep_process_message方法将返回DB_REP_NOTPERM 和在ret_lsnp参数中返回这个记录的LSN到应用程序。
这个应用程序的client或master消息处理的循环应该适当的带些动作以确保在这种情况下的正确的事务保障。当丢失的记录到达,而且允许随后处理这些以前存储的永久记录,在clinet上调用DB_ENV->rep_process_message 方法将返回DB_REP_ISPERM和返回永曾经被刷新到磁盘中的久记录的最大的LSN。Client 应用次序可以用这些LSN决定性的知道是否某些特定的LSN被永久存储了。
一个应用程序依赖于client的能力去成为master,而且保证没有数据曾经被丢失,将需要编写发送函数返回一个错误,无论什么时候它都不能保证将赢得下一次选举的站点会有这个记录。
应用程序不要求这个级别的事务保证将不需要让这发送函数返回失败(除非master的数据库环境用DB_TXN_NOSYNC配置过),因为任何危急到数据库完整性的信息在发送被调用前已经被刷新到本地log文件。
总的来说,发送函数要返回失败的唯一的原因是当master的数据库环境被配置成当事务提交的时候不去刷新log(也就是说,在master上DB_TXN_NOSYNC被配置了), DB_REP_PERMANENT 标志被指定给消息, 而且发送函数不能决定哪些clients收到了当前的消息(和所有在当前消息之前的消息)。多少clients在发送函数成功返回之前需要接收这个消息那是应用程序的选择(可能不是过多的依赖于特定数目的cleints报告成功作为一个或多个物理上分布式的 clients)。
如果,尽管如此,master上的应用程序确实需要在磁盘上的耐久性,那么这个应用程序应该被配置成当事务提交的时候同步的刷新记录。如果一个client没被配置成同步刷新log,也就是说,一个client运行的时候DB_TXN_NOSYNC是被被配置的,当它变成master的时候,那么它将取决于应用程序去适当地重新配置那个client。也就是说,这个应用程序必须明确的调用DB_ENV->set_flags去重新配置这个新成为master的client,使异步log刷新无效。
当然,确保replicated master和client环境确实是互相独立的很重要。例如,如果master和clients在同一个能源供给上(on the same power supply),一个client承认收到一个消息对事情是没什么帮助的,因为能源供给的失败仍然潜在地会使信息丢失。
配置的你基于replication的应用程序,能事务和性能上找到最佳结合点,是很复杂的。简言之,这儿有一些应用程序可以设置的地方以调节:为master环境指定DB_TXN_NOSYNC;为client环境指定DB_TXN_NOSYNC;不同的站点一起参加选举的那个优先权;应用程序发送函数的行为。
首先,在一个replication client中当一个事物提交的时候,写和同步刷新log几乎没什么用处。如果这些系统共享一个资源或多个系统同时出故障也许写和同步刷新log还有点用。默认地,所有的bdb环境,不管是master还是client,当事务提交的时候或预先地,都同步地刷新log。
考虑两个有网络连接的的系统。一个作为master,一个作为只读的client。如果master倒掉了client取代之,在这次故障后,master又重新加入到replication组。master 和client都被配置成当事务提交的时候不同步刷新日志(也就是说DB_TXN_NOSYNC 在两个系统上都被配置)。这个应用程序的发送函数从不返回失败到bdb库,只个简单的传递消息到这个client(或许基于一个广播机制),而且总是返回成功。在client上,由client的DB_ENV->rep_process_message方法返回的DB_REP_NOTPERM也将被忽略。这个系统配置有优秀的性能,但在某些失败的模式下有可能丢失数据。
如果这个master和client都一下子到掉了,就有可能丢失已经提交了的事务,也就是说,事务的耐久性没有被维持。有责任去提高系统的能源供给的独立性和把他们放在物理上隔离的地方。
如果这两个系统间的连接出了故障(或者仅仅是一些消息丢失了),并且随后这个master倒掉了,也有可能失去已提交的事务。又是因为事务的耐久性没有被维护。下面的一些方法中可以提高可靠性:
使用可靠的网络协议(例如,tcp而不是udp)。
增加clients和网络路径的数目,以使消息不太可能被丢失。在这种情况下,确保 client确实收到了这消息以赢得任何随后的选举也是很重要的。 如果一个client没有受到这个消息而赢得了随后的选举,那么数据仍然可能被丢失。
进一步的,系统可能想保证消息交付给了client(s)(例如,去阻止网络连接简单的丢弃消息)。一些系统可能想确保clients从来不返回已过期的信息,也就是说,一旦事务提交后在master上返回成功,将没有client在响应只读请求时返回旧的信息。下面的改变可能会用来阐述这些观点:
编写应用程序的发送函数,使它们直到一个或多个client承认收到了这个消息时才返回到bdb。这个clients的数目的选择是由应用程序决定的:你将很可能会想网络隔离(确保每个物理站点的client收到这个消息)和地理上多样性(确保一个client在每条线路“each coast ”上都收到这个消息)。
编写client的消息处理循环,使直到DB_ENV->rep_process_message返回成功的时候才承认收到这条信息。当DB_ENV->rep_process_message 方法返回DB_REP_NOTPERM 时意味着这个消息不能被刷新到client的磁盘。如果这个client不向master承认收到这个消息直到随后调用的DB_ENV->rep_process_message方法返回DB_REP_ISPERM而且这个返回的LSN至少和这个消息的LSN一样大,然后这个master的发送函数将不会返回success到bdb库。这意味着,直到那些经由挑选的clients已经收到了这个消息而且认为完成了,mastet上正在提交事务的这个线程将才被允许继续下去。
作为选择,client的消息处理循环可以向master承认收到了这个消息,但是用一个错误代码指明那个应用程序的发送函数不要返回到bdb库,直到一个随后的来自同一个client的确认成功。
应用程序的发送回调函数被bdb调用包括一个记录的LSN被发送(如果对那个记录来说是适当的)。当DB_ENV->rep_process_message 返回指示一个永久的记录已经被写入,然后它也回返回这个被写入的永久记录的最大LSN 。
这儿最后的两关需要考虑。首先,当应用程序的发送函数被调用的后,事务是不可被中途中断的,因为这个 master可能已经把事物提交日志记录写入磁盘,所以中途中断将不再是一个可选择项。第二,一个相近的问题是,如果发送函数返回失败,尽管这个master将试图去刷新本地log,这个刷新操作将可能失败(例如,当本地磁盘是满的)。此外,事务不能被中途中断,因为一个或多个clients可能已经提交了这个事务即使发送函数返回失败。杰出的应用程序可能也忍受不了这些不太可能的失败方式。在那中情况下应用程序可能想:
1,配置这master总去同步本地提交的事务(关闭DB_TXN_NOSYNC 配置项)。这样将对性能有很大的影响,当然(使用replication的其中的一个原因是避免本地磁盘的写操作),在这种配置下,不管什么情况,写本地日志失败将使事务中途中断。
2,在任何情况下都不要从应用程序的发送函数中返回,除非挑选的那些client已经承认了收到那个消息。直到这个发送函数返回到这个bdb库,master上这个正在提交事务的线程都将等待,所以没有应用程序能表现出承认事务已经被提交了。
最后可供牵扯到这些类型的失败的应用程序选择的是使用分布式事务,保证完全的一致性实现全局的事务管理和垮过多bdb环境履行两阶段提交。更多的了解这个可以参见分“the Distributed Transactions”那一章。
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.csdn.net/whycold/article/category/1830811