打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Berkeley DB
userphoto

2012.08.23

关注
在网上看到不少介绍Berkeley DB的文章,几乎所有的中文文章都是介绍完入门就再也没了。大都是个概括。最近做这个,所以想系统的由浅入深的介绍一下。不清楚的地方可以和我讨论,或参照官方网站sleepycat上的文档。我用的是最新版本 db-4.4.16.NC.tar.gz,这个包中含有详细的英文文档。

为什么要使用Berkeley DB,它适合什么场合应用?
Berkeley DB并不适合所有的应用,因为简单,专一所以高效。
嵌入式数据库,的“嵌入”是指它内嵌在程序中,而不是说他只应用在嵌入式系统上。它的特点很适合应用于嵌入式系统上。当然在我们的pc机集群或大型服务器上,也可以灵活的配置,完成更艰巨的任务。

它适合于管理海量的,简单的数据。Google用Berkeley DB HA (High Availability) 来管理他们的帐户信息. Motorola在他的无线产品中用Berkeley DB跟踪移动单元。hp,microsoft,Sun Microsystems...等也都是它的大客户。它不能完全取代关系数据库,但在某些方面,它却有他们望尘莫及的高效性。

性能测试,在如下的配置上:
Linux – SuSE Linux 9.1 running on an AMD Athlon 64 processor 3200+ at 1GHz system with 1GB of RAM。
每秒钟,单条记录读操作 1,002,200次。单条记录写操作 766,034次。 用bulk APIs能进行读操作 13,501,800次。当然这些都是发生在内存中的操作,因为bdb使用了cache。 性能测试具体数据可以参考官方网站的Performance Metrics & Benchmarks: Berkeley DB。 

Berkeley DB简介
Berkeley DB可以说是一个专为程序员准备的数据库。我的文章中只针对c程序员介绍的。它还支持C++、Java、Perl、Tcl、Python和PHP等。原理和接口都差不多。它的安装很简单。

cd build_unix
../dist/configure
make
make install

这几步就ok了,其实也就是把头文件和编译好的db库放到指特定位置。甚至可以不用make install,直接在编译你的程序时用-I -L -ldb 指定头文件和连接库的位置。可以完全把它当作一个函数库来用。由db库透明的来完成对数据的管理。无论是系统中的多个进程,或者是相同进程中的多个线程,都可以在同一时间调用访问数据库的函数。而底层的数据加锁、事务日志和存储管理等都在Berkeley DB函数库中实现。他不像传统的数据库那样有client和server,还专门跑几个进程。所以应用程序不需要事先同数据库服务建立起网络连接,而是通过内嵌在程序中的Berkeley DB函数库来完成对数据的保存、查询、修改和删除等操作。 

Berkeley DB函数库本身虽然只有300KB左右,但却能够用来管理多达256TB的数据,并且在许多方面的性能还能够同商业级的数据库系统相抗衡。就拿对数据的并发操作来说,Berkeley DB能够很轻松地应付几千个用户同时访问同一个数据库的情况。因而,在资源受限的嵌入式系统上进行数据库管理,Berkeley DB也是一个不错的选择。

Berkeley DB为何高效?
Berkeley DB作为一种嵌入式数据库系统在许多方面有着独特的优势。首先,由于其应用程序和数据库管理系统运行在相同的进程空间当中,进行数据操作时可以避免繁琐的进程间通信包括建立socket连接等,因此耗费在通信上的开销自然也就降低到了极低程度。其次,Berkeley DB使用简单的函数调用接口来完成所有的数据库操作,而不是在数据库系统中经常用到的SQL语言。这样就避免了对结构化查询语言进行解析和处理所需的开销。 




基本概念
关键字/数据 是Berkeley DB用来进行数据库管理的基础。每个 Key/Data 对构成一条记录。而整个数据库实际上就是由许多这样的结构单元所构成的。通过使用这种方式,开发人员在使用Berkeley DB提供的API来访问数据库时,只需提供关键字就能够访问到相应的数据。当然也可以也可以提供 Key 和部分Data来查询符合条件的相近数据。

一个例子来完成入门
使用过rdb的人相信都能看的懂下面的例子。简要的说一下下面持续完成的功能。作为一个简单的例子environment部分可以不是必要的,我把它的用法也一起加了进来。创建一个environment指明要把数据库文件创建到哪个目录下面。创建数据库,打开数据库,写一个记录进去,然后读出记录,然后将写入的记录删除,然后关闭environment和数据库。会了这些基本操作,你就可以使用bdb完成简单的应用了。

....................................................................

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//only this head should include for use bdb.
#include <db.h>   
#define DATABASE "yangjian.db"

int main()
{
    DB_ENV *myEnv;
    DB *dbp;
    DBT key, data;
    int ret,t_ret;
    u_int32_t env_flags;
    //........... Create an environment object and initialize it for error reporting
    ret = db_env_create(&myEnv, 0);
    if (ret != 0)
    {
          fprintf(stderr, "Error creating env handle: %s\n", db_strerror(ret));
          return -1;
    }
    //........If the environment does not exist create it. Initialize the in-memory cache.
    env_flags = DB_CREATE | DB_INIT_MPOOL;
    //........Open the environment.
    ret = myEnv->open(myEnv,"/home/yangbin1/yangjian/my/db/testevn",env_flags,0);
    if (ret != 0)
    {
          fprintf(stderr, "Environment open failed: %s", db_strerror(ret));
          return -1;
    }

    if ((ret = db_create(&dbp, myEnv, 0)) != 0)
    {
          fprintf(stderr, "db_create: %s\n", db_strerror(ret));
          exit (1);
    }

    if ((ret = dbp->open(dbp, NULL, DATABASE, NULL, DB_BTREE, DB_CREATE, 0664)) != 0)
    {
          dbp->err(dbp, ret, "%s", DATABASE);
          exit (1);
    }
    memset(&key, 0, sizeof(key));
    memset(&data, 0, sizeof(data)); key.data = "sport";
    key.size = sizeof("sport");
    data.data = "football";
    data.size = sizeof("football");
/*
    //......put data
    if ((ret = dbp->put(dbp, NULL, &key, &data, 0)) == 0)
    {
          printf("db: %s: key stored.\n", (char *)key.data);
    }
      else
    {
          dbp->err(dbp, ret, "DB->put");
    }
*/

    //........put data NOOVERWRITE
    if ((ret = dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE)) == 0)
    printf("db: %s: key stored.\n", (char *)key.data);
    else dbp->err(dbp, ret, "DB->put");

    //.......get data
    if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) == 0)
    printf("db: %s: key retrieved: data was %s.\n", (char *)key.data, (char *)data.data);
    else
    dbp->err(dbp, ret, "DB->get");

    //......del data
    if((ret = dbp->del(dbp, NULL, &key, 0)) == 0)
    printf("db: %s: key was deleted.\n", (char *)key.data);
    else
    dbp->err(dbp, ret, "DB->del");

    //.........close, only when the db successful closed,the data can real write to the disk.
    //if ((t_ret = dbp->close(dbp, 0)) != 0 && ret == 0)
    //ret = t_ret;
    //exit(ret);
    if (dbp != NULL)
    dbp->close(dbp, 0);
    //.........close evn
    //........When you are done with an environment, you must close it.
    //........Before you close an environment, make sure you close any opened databases
    if (myEnv != NULL)
    myEnv->close(myEnv, 0);

    return 0;
}


Berkeley DB -- Access Method Configuration




Hash or Btree?

Hash 和 Btree方法应该被用于当逻辑记录号不是用来做主键对数据访问的情况。(如果逻辑记录号是一个secondary key,用来对数据进行访问,Btree方法是一个可能的选择,因为它支持通过一个键和一个记录号来同时的访问。)

Btrees中的键是按一定的秩序来存放的。Btrees应该被用于那些keys存在某种关系的时候。例如用时间做keys,当现在访问8AM时间戳的时候,可能下一个就访问9AM时间戳。也就是在排列顺序中附近的(near)。再比如,用names做keys,我们也许要访问那些有相同last name的,Btrees仍然是一个不错的选择。

在小的数据设置上,Hash 和 Btree在性能表现上没什么差别。在那儿,所有的,或大部分数据设置被放在了cache里面。

尽管如此,当一个一数据设置足够大的时候,会有一些重要的数据页再也装不进cache里了。这种情况下,我们上面讨论的btree在性能表现上就很重要了。
例如,因为在hash中没有排列顺序中附近的机制。所以,cache在Btree中通常比Hash中更有效。Btree方法将产生更少的I/O调用。

尽管如此,当一个数据设置更大的时候,hash访问方法能赢过btree方法。原因是btree比hash数据库包含了更多的元数据页。
数据设置可以变的非常大,以至于元数据开始支配整个cache。如果这种事情发生,Btree将不得不对每次请求都进行一次I/O操作。Cache中几乎没有地方再放置那些真正的数据页了,失去了cache的意义。而因为hash有很少的元数据,可以它的cache照样可以用来放置那些数据页,起到cahche的作用。

当一个数据更更大的时候,以至于每个随机请求,hash和btree几乎都要进行一次I/O操作的时候。在这中情况下,实际上hash只要遍历少树几个内部页(internal pages)就差不多能找到,所以这也是hash在性能上的一个优势。

应用程序对数据的访问式样也极大的影响这些行为。例如,延着光标往下遍历的话,每次I/O操作到cache中的数据,将满足接下来的很多数据请求。

如果数据设置只是比cache大一点,我们还是条件使用Btree,如果你实在有太大的数据设置,hash也许会更好一些。db_stat公用程序是一个有用的工具,用来监视,你的cache表现的怎么样。

总结:
其实到这你应该能看出来,btree是在数据不是很大的时候是很优秀的,在更大的时候,由于元数据占用太多cache的原因,导致性能下降,落后与hash了,而不是说hash能超过它。所以能在元数据占用cache不是太多以前,也就是你的cache足够大,使用btree只最好的选择。当然,如果每次访问的数据都是随机的没有什么次序,也不是near的,那用btree也没什么优势了。

针对我们的应用我只讨论了 Hash or Btree?。Queue or Recno?我就不再讨论了。



选择一个页的大小:
太大了会产生很多不必要的i/o,而且影响并发性,因为Btree, Hash and Recn都是对页上锁。太小了会使用溢出页,大量使用溢出页会严重影响性能。所以一般
页的大小都选择和文件系统的I/O块,大小相等。



选择一个cache大小:
要设置的足够大,至少能满足一次操作的数据。如果你的cache设的太小,每个新页将要强迫换出least-recently-used page。
Berkeley DB将要重新读一次树的root page对于每次数据库请求。当然cache也不是越大越好,当cache大小增长到一个特定的点时,再增加就不会对性能有什么提高了。当到达这个点时,两件事情发生了。Cache足够大以至于,几乎所有的请求都不用再访问磁盘了就能从cache中得到信息。或则是你的应用程序做一些确实很随机的访问,因此再增加cache对于下一个请求也不会有什么性能上的提高了。第二种情况发生的概率很小,因为几乎所有的应用,都显示了一些,请求的相关联性。
如果cache设定的超过了操作系统的能力,将会使用交换分区,频繁换入换出,会很影响性能。


Berkeley DB -- Access Method Wrapup



觉得有必要先把DBT结构放在这。方便后面看。
typedef struct {
void *data;
u_int32_t size;
u_int32_t ulen;
u_int32_t dlen;
u_int32_t doff;
u_int32_t flags;
} DBT;

1. 数据对齐
Berkeley DB没有为以DBT为参数的,返回的data/key对,或回调函数的字节对齐提供任何保证。
应用程序有责任对齐任何需要对齐的。DB_DBT_MALLOC, DB_DBT_REALLOC 和 DB_DBT_USERMEM标志可能被用来对齐存储在内存中的返回项。

2. 在bulk中取回数据
当从数据库中取回大量记录的时候,那些方法调用经常影响性能。Berkeley DB提供bulk取数据接口,它能有效的提高一些应用持续的性能要使用bulk,必须先为DB->get或DBcursor->c_get指定一个buffer。这个在c api中的实现是通过设置DBT结构的data和ulen域还有flag域被设为DB_DBT_USERMEM来引用应用程序的buffer。DB_MULTIPLE或DB_MULTIPLE_KEY 需要指定给DB->get或 DBcursor->c_get方法, 以使多条记录被返回到指定的buffer中。这两个标志的区别请看手册。
下面函数只看红色标出部分就可以了。示范如何使用bulk。
...................................................................................
int rec_display(DB *dbp)
{
DBC *dbcp;
DBT key, data;
size_t retklen, retdlen;
char *retkey, *retdata;
int ret, t_ret;
void *p;
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
/* Review the database in 5MB chunks. */
#define BUFFER_LENGTH (5 * 1024 * 1024)
if ((data.data = malloc(BUFFER_LENGTH)) == NULL)
return (errno);
data.ulen = BUFFER_LENGTH;
data.flags = DB_DBT_USERMEM;
/* Acquire a cursor for the database. */
if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
dbp->err(dbp, ret, "DB->cursor");
free(data.data);
return (ret);
}
for (;;) {
/*
  * Acquire the next set of key/data pairs. This code does
  * not handle single key/data pairs that won't fit in a
  * BUFFER_LENGTH size buffer, instead returning DB_BUFFER_SMALL
  * to our caller.
  */
if ((ret = dbcp->c_get(dbcp,
    &key, &data, DB_MULTIPLE_KEY | DB_NEXT)) != 0) {
  if (ret != DB_NOTFOUND)
  dbp->err(dbp, ret, "DBcursor->c_get");
  break;
}
for (DB_MULTIPLE_INIT(p, &data);;) {
  DB_MULTIPLE_KEY_NEXT(p,
    &data, retkey, retklen, retdata, retdlen);
  if (p == NULL)
  break;
  printf("key: %.*s, data: %.*s\n",
    (int)retklen, retkey, (int)retdlen, retdata);
}
}
if ((t_ret = dbcp->c_close(dbcp)) != 0) {
dbp->err(dbp, ret, "DBcursor->close");
if (ret == 0)
  ret = t_ret;
}
free(data.data);
return (ret);
}
................................................................................................


3. 记录的部分的存储和取回
在Berkeley DB的访问方法中,可以只存储或取回数据项的某一部分。这个通过设置DBT结构的DB_DBT_PARTIAL 标志来实现。
同时还要设置DBT的其他几个值。
doff 数据开始处
dlen 数据长度
例如,如果数据项是ABCDEFGHIJKL, doff的值为3是指从字节D开始。dlen为4,是指随后的4个字节DEFG。
取回记录:
当从一个数据库中取回一个数据项时,从doff位置开始的dlen字节,被返回。如果被指定的那些字节不存在,其他存在的字节将被返回。
存储记录:
下面的例子初始化数据项字节长度都是20: ABCDEFGHIJ0123456789
1,
size = 20
doff = 0
dlen = 20
data = abcdefghijabcdefghij
Result: The 20 bytes at offset 0 are replaced by the 20 bytes of data;
that is, the entire record is replaced.
ABCDEFGHIJ0123456789 -> abcdefghijabcdefghij
2,
size = 10
doff = 2
dlen = 15
data = abcdefghij
Result: The 15 bytes at offset 2 are replaced by the 10 bytes of data.
ABCDEFGHIJ0123456789 -> ABabcdefghij789
2,
size = 10
doff = 25
dlen = 0
data = abcdefghij
Result: The 0 bytes at offset 25 are replaced by the 10 bytes of data;
that is, 10 bytes are inserted into the record past the end of the
current data (\0 represents a nul byte).
ABCDEFGHIJ0123456789 -> ABCDEFGHIJ0123456789\0\0\0\0\0abcdefghij
其实就是字符串替换,把数据库中某条记录的某部分替换成指定的字符串,长度可以自动根据被替换的字符串大小进行调整。可伸缩的。

Berkeley DB -- DB Architecture


The big picture

前面几章讲了用访问方法快速的存储和取回数据。后面主要讲任何访问方法的应用,它们是线性的和可恢复的在面对系统故障时。
Berkeley DB 底层体系结构:



如上图,应用程序调用访问方法,而访问方法使用底层的共享内存cache放置最近用过的文件页面。

当应用程序需具备恢复能力的时候,它们调用的访问方法必须预先封装在事务字系统中。程序告诉bdb事务的开始和结束点。必须准备面对特殊情况下可能的失败,导致事务异常终止。
一个例子说明具有事务保护的代码的大致样子:
for (fail = 0;;) {
    /* Begin the transaction. */
    if ((ret = dbenv->txn_begin(dbenv, NULL, &tid, 0)) != 0) {
        dbenv->err(dbenv, ret, "dbenv->txn_begin");
        exit (1);
    }

    /* Store the key. */
    switch (ret = dbp->put(dbp, tid, &key, &data, 0)) {
    case 0:
        /* Success: commit the change. */
        printf("db: %s: key stored.\n", (char *)key.data);
        if ((ret = tid->commit(tid, 0)) != 0) {
              dbenv->err(dbenv, ret, "DB_TXN->commit");
              exit (1);
        }
        return (0);
    case DB_LOCK_DEADLOCK:
    default:
        /* Failure: retry the operation. */
        if ((t_ret = tid->abort(tid)) != 0) {
              dbenv->err(dbenv, t_ret, "DB_TXN->abort");
              exit (1);
        }
        if (fail++ == MAXIMUM_RETRY)
              return (ret);
        continue;
    }
}   
Berkeley DB由五个主要的子系统构成.包括: 存取管理子系统、内存池管理子系统、事务子系统、锁子系统以及日志子系统。其中存取管理子系统作为Berkeley DB数据库进程包内部核心组件,而其他子系统都存在于Berkeley DB数据库进程包的外部。每个子系统支持不同的应用级别。

1.数据存取子系统 数据存取(Access Methods)子系统为创建和访问数据库文件提供了多种支持。Berkeley DB提供了以下四种文件存储方法:哈希文件、B树、定长记录(队列)和变长记录(基于记录号的简单存储方式),应用程序可以从中选择最适合的文件组织结构。程序员创建表时可以使用任意一种结构,并且可以在同一个应用程序中对不同存储类型的文件进行混合操作。   在没有事务管理的情况下,该子系统中的模块可单独使用,为应用程序提供快速高效的数据存取服务。数据存取子系统适用于不需事务只需快速格式文件访问的应用。 

2.内存池管理子系统   内存池(Memory pool)子系统对Berkeley DB所使用的共享缓冲区进行有效的管理。它允许同时访问数据库的多个进程或者进程的多个线程共享一个高速缓存,负责将修改后的页写回文件和为新调入的页分配内存空间。它也可以独立于Berkeley DB系统之外,单独被应用程序使用,为其自己的文件和页分配内存空间。内存池管理子系统适用于需要灵活的、面向页的、缓冲的共享文件访问的应用。

3.事务子系统 事务(Transaction)子系统为Berkeley DB提供事务管理功能。它允许把一组对数据库的修改看作一个原子单位,这组操作要么全做,要么全不做。在默认的情况下,系统将提供严格的ACID事务属性,但是应用程序可以选择不使用系统所作的隔离保证。该子系统使用两段锁技术和先写日志策略来保证数据库数据的正确性和一致性。它也可以被应用程序单独使用来对其自身的数据更新进行事务保护。事务子系统适用于需要事务保证数据的修改的应用。   

4.锁子系统   锁(Locking)子系统为Berkeley DB提供锁机制,为系统提供多用户读取和单用户修改同一对象的共享控制。数据存取子系统可利用该子系统获得对页或记录的读写权限;事务子系统利用锁机制来实现多个事务的并发控制。     该子系统也可被应用程序单独采用。锁子系统适用于一个灵活的、快速的、可设置的锁管理器。   

5.日志子系统   日志(Logging)子系统采用的是先写日志的策略,用于支持事务子系统进行数据恢复,保证数据一致性。它不大可能被应用程序单独使用,只能作为事务子系统的调用模块。   以上几部分构成了整个Berkeley DB数据库系统。各部分的关系如下图所示:     



在这个模型中,应用程序直接调用的是数据存取子系统和事务管理子系统,这两个系统进而调用更下层的内存管理子系统、锁子系统和日志子系统。     由于几个子系统相对比较独立,所以应用程序在开始的时候可以指定哪些数据管理服务将被使用。可以全部使用,也可以只用其中的一部分。例如,如果一个应用程序需要支持多用户并发操作,但不需要进行事务管理,那它就可以只用锁子系统而不用事务。有些应用程序可能需要快速的、单用户、没有事务管理功能的B树存储结构,那么应用程序可以使锁子系统和事务子系统失效,这样就会减少开销。   
Programming model

它直接链接到应用程序中,与应用程序运行于同样的地址空间中。

Programmatic APIs

DB为多种编程语言提供了API接口,其中包括C、C++、Java。
值得一提的是bdb提供dbm样式的接口,以前使用unix Dbm/Ndbm的,只需要换个头文件#include <db.h>,重新编译一下,db效率将成倍的提高。当然我们也可以使用dbm样式的接口编写简单的应用程序,这种接口比较简洁。

也为脚本语言Perl、Tcl、Python和PHP提供了接口。
对apache也以module的方式提供了接口,安装后,可以在写apache api时候直接调用。除了几个函数不一样外,其他都相同。


bdb提供的公用程序:

db_archive
打印出不再使用的日志文件路径名
db_checkpoint
监视和检查数据库日志的守护进程
db_deadlock
当死锁发生时,退出锁定要求
db_dump
把数据库文件转换成db_load能认出的文本文件
db_load
从db_dump产生的文本文件中创建出数据库文件
db_printlog
把数据库日志文件转换成人能读懂的文本
db_recover
在发生错误后,把数据库恢复到一致的状态
db_stat
显示数据库环境统计
db_upgrade
把数据库文件转换成新版本的Berkley DB格式
db_verify
对数据库文件进行一致性检查
db库
许多程序中与db相关的函数都将使db库。

Berkeley DB -- DB Environment


Database environment introduction

Berkeley DB 环境用来封装一个或多个数据库,日志文件和区域文件。区域文件是共享内存区,它里面包括数据库环境信息像内存池cache页等。只有数据库文件可以在不同的字节序机器间移动,日志文件只能在相同的字节序机器间移动。而区域文件(Region files)常常对于一个特定的机器来说是独一无二的,可能只能在指定的操作系统的某个版本上移动间移动。


一个环境可以被很多进程和线程共享。一个环境包含其它目录的资源也是可能的。应用程序经常选择把资源分布到其他目录或磁盘来提高性能或其他原因。尽管如此,默认的,数据库,共享区(锁,日志,内存池和事务共享内存区域)和日志文件将存储在同一个同层次目录中。

意识到所有应用程序共享一个数据库环境默认的相信彼此非常重要。他们能访问对方的数据,因为那些数据在同一个共享内存区,他们也共享资源像buffer空间和锁。与此同时,任何应用程序使用同一个数据库,必须共享一个环境,如果想在他们之间保持一致性的话。
Creating a database environment

环境对于bdb的可移植性和灵活性是非常重要的。为了增强可移植性,和快速灾难恢复,建议尽量使用相对路径。建议使用配置文件放置环境参数而不要直接写到程序里,可以避免每次移植时修改和编译源文件。

bdb环境由db_env_create 和 DB_ENV->open接口创建和描述,再需要定制的地方,比如把log文件存储到不同的磁盘驱动器里,或选择一个特殊cache大小, 应用程序描述这些定制信息通过创建配置文件,或者传参数给其他DB_ENV 处理函数。

一旦一个环境被创建,被指定相对路径的数据库文件,都将相对与环境的home目录来创建。用相对目录允许整个环境轻易的移动。简化了在不同目录和不同系统中重建和恢复的步骤。

应用程序首先通过db_env_create方法获得一个环境句柄,然后调用DB_ENV->open来创建或合并数据库环境。这儿有很多选项你可以在调用DB_ENV->open时设置来定制你的环境。这些选项大致可以分为四类:

子系统初始化选项: 这些标志指明哪些bdb子系统将因为环境被初始化,和哪些操作将自动发生当数据库在环境中被访问的时候。这些标志包括DB_INIT_CDB, DB_INIT_LOCK, DB_INIT_LOG, DB_INIT_MPOOL, and DB_INIT_TXN。The DB_INIT_CDB标志为bdb并发数据存储做初始化工作。其他标志初始化单个子系统;也就是说,当DB_INIT_LOCK被指定,应用程序读写在这个环境中打开的数据库时,将使用locking子系统以确保它们不覆盖对方的对数据的改动。 

恢复选项:这些包括DB_RECOVER 和 DB_RECOVER_FATAL选项,他们表明在环境被打开要作正常用途使用前,恢复(recovery)将要进行。

命名选项:这包括DB_USE_ENVIRON 和 DB_USE_ENVIRON_ROOT,修改如何在环境中给文件命名。

混杂选项:例如DB_CREATE选项使底层数据库文件被创建是必需的。更多的应用还指定
仅仅DB_INIT_MPOOL标志或者指定其它所有4个子系统的初始化标志(DB_INIT_MPOOL, DB_INIT_LOCK, DB_INIT_LOG, and DB_INIT_TXN)。

以前的配置只是想简单的用一些基本的访问方法接口用一个共享底层缓冲池,但是没有关心当应用程序或系统出现故障时的可恢复性。以后是一些需要提供可恢复性的应用。也有一些很稀少的情况下,其它的初始化标志组合成为可能。


DB_RECOVER在当应用程序想在运行的时做一些必需的数据库恢复的时候被指定。也就是说,是否在上次运行时,系统或应用程序出现了故障,想在再次运行前使数据恢复到可用状态。不过,在没有任何数据需要恢复的情况下,指定这个标志也不为错。

DB_RECOVER_FATAL标志有更特殊的用途。它执行灾难性的数据库恢复,通常需要做一些初始化的安排;也就是归档log文件被带回到文件系统。应用程序通常不指定这个标志,取而代之的是,在这种很稀有的情况下,db_recover 公用程序将会派上用场不用你自己写。
下面是一个简单的为事务程序打开一个数据库环境的例子:


DB_ENV *
db_setup(home, data_dir, errfp, progname)
char *home, *data_dir, *progname;
FILE *errfp;
{
DB_ENV *dbenv;
int ret;
/*
* Create an environment and initialize it for additional error
* reporting.
*/
if ((ret = db_env_create(&dbenv, 0)) != 0) {
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
return (NULL);
}
dbenv->set_errfile(dbenv, errfp);
dbenv->set_errpfx(dbenv, progname);
/*
* Specify the shared memory buffer pool cachesize: 5MB.
* Databases are in a subdirectory of the environment home.
*/
if ((ret = dbenv->set_cachesize(dbenv, 0, 5 * 1024 * 1024, 0)) != 0) {
dbenv->err(dbenv, ret, "set_cachesize");
goto err;
}
if ((ret = dbenv->set_data_dir(dbenv, data_dir)) != 0) {
dbenv->err(dbenv, ret, "set_data_dir: %s", data_dir);
goto err;
}
/* Open the environment with full transactional support. */
if ((ret = dbenv->open(dbenv, home, DB_CREATE |
  DB_INIT_LOG | DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_TXN, 0)) != 0) {
dbenv->err(dbenv, ret, "environment open: %s", home);
goto err;
}
return (dbenv);
err: (void)dbenv->close(dbenv, 0);
return (NULL);

Opening databases within the environment

一旦环境被创建,数据库句柄将可能在这个环境中打开,这由db_create函数通过指定特定的环境作为参数来实现。
文件命名,数据库操作,和错误处理等都将因为这个指定的环境而被做。例如,如果DB_INIT_LOCK 或 DB_INIT_CDB 标志被指定,当环境被创建或被合并时,数据库操作将为应用程序自动的执行所有必要的锁操作。
下面是一个简单的例子,在一个环境中打开两个数据库:
DB_ENV *dbenv;
DB *dbp1, *dbp2;
int ret;
dbenv = NULL;
dbp1 = dbp2 = NULL;
/*
* Create an environment and initialize it for additional error
* reporting.
*/
if ((ret = db_env_create(&dbenv, 0)) != 0) {
fprintf(errfp, "%s: %s\n", progname, db_strerror(ret));
return (ret);
}
dbenv->set_errfile(dbenv, errfp);
dbenv->set_errpfx(dbenv, progname);
/* Open an environment with just a memory pool. */
if ((ret =
  dbenv->open(dbenv, home, DB_CREATE | DB_INIT_MPOOL, 0)) != 0) {
dbenv->err(dbenv, ret, "environment open: %s", home);
goto err;
}
/* Open database #1. */
if ((ret = db_create(&dbp1, dbenv, 0)) != 0) {
dbenv->err(dbenv, ret, "database create");
goto err;
}
if ((ret = dbp1->open(dbp1,
  NULL, DATABASE1, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {
dbenv->err(dbenv, ret, "DB->open: %s", DATABASE1);
goto err;
}
/* Open database #2. */
if ((ret = db_create(&dbp2, dbenv, 0)) != 0) {
dbenv->err(dbenv, ret, "database create");
goto err;
}
if ((ret = dbp2->open(dbp2,
  NULL, DATABASE2, NULL, DB_HASH, DB_CREATE, 0664)) != 0) {
dbenv->err(dbenv, ret, "DB->open: %s", DATABASE2);
goto err;
}
return (0);
err: if (dbp2 != NULL)
(void)dbp2->close(dbp2, 0);
if (dbp1 != NULL)
(void)dbp2->close(dbp1, 0);
(void)dbenv->close(dbenv, 0);
return (1);
}

Error support

db_strerror能根据一个bdb的一个错误返回值返回一个指向错误信息的指针。它可以处理系统的错误返回值也能处理bdb特有的返回值。
例如:
int ret;
if ((ret = dbenv->set_cachesize(dbenv, 0, 32 * 1024, 1)) != 0) {
fprintf(stderr, "set_cachesize failed: %s\n", db_strerror(ret));
return (1);
}

这儿也有两个附加的错误处理函数:DB_ENV->err 和 DB_ENV->errx。
DB_ENV->err函数追加标准错误字符串到已构造好的信息,而DB_ENV->errx不那样。
错误信息可以通过DB_ENV->set_errpfx被配置成总包含一个固定的东西,例如,应用程序名称。还可以把错误信息输入到一个指定的文件中,例如:
int ret;
dbenv->set_errfile(dbenv, errfp);
dbenv->set_errpfx(dbenv, program_name);
if ((ret = dbenv->open(dbenv, home,
  DB_CREATE | DB_INIT_LOG | DB_INIT_TXN | DB_USE_ENVIRON, 0))
  != 0) {
dbenv->err(dbenv, ret, "open: %s", home);
dbenv->errx(dbenv,"contact your system administrator: 
  session ID was %d",session_id);
return (1);
}
例如应用程序名为"my_app", 环境的home目录为 "/tmp/home",出错信息将是这样的:
my_app: open: /tmp/home: Permission denied.
my_app: contact your system administrator: session ID was 2

DB_CONFIG configuration file

几乎所有可以指定给DB_ENV那些方法的配置信息,也都能通过一个配置文件来指定。如果一被命名为DB_CONFIG的文件存在于数据库hone目录下,它将会一行行的按NAME VALUE的格式读入。
NAME和VALUE之间用一个或者多个空格来分割。凡是那一行的开头是空格或#的,都将被忽略为注释。
NAME VALUE具体值可以在对用的方法中查到例如DB_ENV->set_data_dir。

DB_CONFIG配置文件的目的是允许管理员定制不依赖于应用程序的环境。例如,可以移动数据库log文件和数据文件到不同的地方,而不用重新编译应用程序。另外,因为DB_CONFIG文件是当数据库环境被打开时读取的,它可以用来覆盖在那以前配置的规则。例如,可以定义一个更合理的cache大小,来覆盖以前已经编译到程序中的值。


File naming

下面介绍几种可能的为bdb指定文件命名信息的方法。
db_home: 为DB_ENV->open的db_home参数指定一个非NULL值,它的值将会用来作为数据库的home,以后的文件命名都是相对这个路径的。

DB_HOME:为环境变量DB_HOME指定值,DB_ENV->open被调用时候,读取这个值,把它作为数据库home,以后的文件命名都是相对这个路径的。

DB_ENV方法:这有三个方法可以影响文件命名。DB_ENV->set_data_dir可以为数据库文件指定一个目录。DB_ENV->set_lg_dir方法可以为log文件指定目录。DB_ENV->set_tmp_dir为创建的临时文件指定一个目录。例如,一个应用程序可以将数据文件,日志文件等分别放在不同的目录下。

DB_CONFIG文件:相同的指定给DB_ENV 方法的信息,也可以用DB_CONFIG 配置文件来指定。

我觉得指定的优先级从高到低应该是这样的:DB_ENV,DB_CONFIG,db_home,DB_HOME,default。如果以上的值为绝对路径,那么home就是那个绝对路径。如果以上的值为相对路径,那么将根据当前的工作目录算出home路径。如果什么都没指定,那么默认的是现在的工作目录为home。
例子:
情况一:把所有的文件都放在目录/a/database下:
dbenv->open(dbenv, "/a/database", flags, mode);

情况二:把临时文件放在/b/temporary,把所有其他文件放在/a/database:
dbenv->set_tmp_dir(dbenv, "/b/temporary");
dbenv->open(dbenv, "/a/database", flags, mode);

情况三:把数据文件放在/a/database/datadir,日志文件放在/a/database/logdir所有其他文件放在/a/database:
dbenv->set_lg_dir(dbenv, "logdir");
dbenv->set_data_dir(dbenv, "datadir");
dbenv->open(dbenv, "/a/database", flags, mode);

情况四:
把数据文件放在/a/database/data1和/b/data2,所有其他文件放在/a/database. 
任何数据文件将被创建在/b/data2目录下,因为它是第一个被指定的数据目录:
dbenv->set_data_dir(dbenv, "/b/data2");
dbenv->set_data_dir(dbenv, "data1");
dbenv->open(dbenv, "/a/database", flags, mode);

。。。。。

Shared memory regions


每个在环境中的bdb子系统都被一个或多个区域(regions),或大块的内存来描述。区域包括所有的每进程和每线程共享信息,(包括互斥)
,这些组成了bdb环境。这些区域将下列在三种内存类型中的一种中被创建,这取决于指定给DB_ENV->open方法的标志:

DB_PRIVATE:如果这个标志被指定,区域将在每进程的堆内存中被创建;也就是说由malloc()返回的内存。
这个标志最好不要指定当一个以上的内存访问环境的时候。因为它很有可能引起数据库腐烂(corruption)和一些不可预知的行为,例如,当
server应用程序和bdb公用程序,(例如:db_archive, db_checkpoint or db_stat)都有可能访问这个环境的时候,B_PRIVATE标志最好别指
定。

DB_SYSTEM_MEM:如果这个标志被指定,共享区域将在系统内存中创建而不是在文件中。这是一个可选的机制,为了在多个进程和一个进程中的
多线程间共享bdb环境。bdb所使用的系统内存潜在地很有用,陪任何特殊的进程度过一生。因此附加的清除将是必要的当一个应用程序出现故
障后,因为bdb没有办法去确认,支撑共享内存区的系统资源是不是还给了系统。
系统内存的使用是根据计算机体系结构而定的。例如,在一个支持 X/Open样式共享内存的系统上,像UNIX系统,shmget(2) 和相近的
系统V IPC接口被使用。在VxWorks 系统中使用系统内存。在这些情况下,一个初始的段id必须在应用程序中被指定,以确保应用程序不互相覆
盖对方的数据库环境。因此,段创建的数量不是无限制的增长。可以参考DB_ENV->set_shm_key方法得到跟多的信息。
在windows平台上DB_SYSTEM_MEM标志问题多多,我就不说了.

default:如果没有内存相关的标志被指定给DB_ENV->open,被文件系统支撑的内存(觉得应该可以理解为虚拟内存)将用来存储这区域(regions)。在unix系统上,bdb库将将使用POSIX mmap接口。如果mmap不可用,那么unix shmget接口将可能被使用,如果它可用的话。
任何在文件系统中创建用来支撑区域的文件,将在环境的home目录下被创建。这些文件命名为__db.###(例如,_db.001, __db.002等等)。
当区域文件被文件系统来支撑的时候,每个区域对应一个文件被创建。当区域文件被系统内存来支撑的时候,只有一个文件将仍然被创建,因为这儿必须有一个熟知的名字在文件系统中,以便多进程能定位到环境所使用的系统共享内存。
统计在环境中的共享内存区域可以用db_stat的-e选项来显示。


Security

下面是当你在写bdb应用程序的时候需要考虑的安全问题:

数据库环境许可:
被bdb数据库环境使用的目录,应该有它自己的许可设置,以确保那些没有适当权限的用户不能访问环境里的文件。应用程序,那些添加到用户
的许可(例如,unix的setuid 或 setgid程序), 应该细心的检查,不允许违法的使用这些许可,例如访问在环境中的文件。

环境变量:
设置 DB_USE_ENVIRON 和 DB_USE_ENVIRON_ROOT标志 和允许在文件命名时使用环境变量都是危险的。在bdb应用程序中用附加的许可(例如,
unix的setuid 或 setgid程序)设置这些标志,将潜在地允许那些正常情况下没有权限的用户读写数据库。

文件许可:
默认地,bdb总是创建所有者和所在组可读写的文件(也就是,S_IRUSR, S_IWUSR, S_IRGRP 和 S_IWGRP; 或八进制模式 0660 在历史性的UNIX
系统上),创建文件的组的所有权,是基于系统和目录默认的,不被bdb进一步的指定。

临时支撑(backing)文件:
如果一个没有被命名的数据库被创建,而cache太小以至于不能在内存中控制这个数据库,bdb将创建一个临时的物理文件使它能把数据库的cache页放到磁盘上,当需要的时候。
在这种情况下,环境变量,像TMPDIR可能被用来指定用以定位那个临时文件。尽管临时支撑文件被创建被只有所有者可读写的。(S_IRUSR 和 S_IWUSR, 或八进制模式 0660 在历史性的UNIX系统上),一些文件系统可能不能充分的保护被创建在随机目录中的临时文件。为了绝对安全,应用程序存储敏感数据在未命名的数据库中,应该用DB_ENV->set_tmp_dir方法用已知的许可(known permissions)指定一个临时目录。

Encryption

bdb可选择的用Rijndael/AES算法支持加密和解密。这个加密只是文件级的,如果侵入者能够访问你系统的内存,那么这种加密就不能提供保障
了。与我的应用无关我就不多说了。自己看手册。

Remote filesystems

最好别使用远程文件系统,像nfs等。因为区域文件要映射到内存,远程文件系统不能很好的支持某些语义。数据库文件,的日志文件,临时文
件,还勉强可以放在远程文件系统上,如果远程文件系统完全支持标准POSIX文件系统语义的话。总之,不用最好。


Environment FAQ

我使用多进程访问bdb数据库环境,这儿有什么方法可以确保两个进程不同时执行数据恢复(recovery )操作吗?或者说,确保其他所有的进程都退出了,可以运行数据恢复了?

其实重点要说明的是,当执行数据恢复的时候要确保没有别的进程在使用这个环境。
很多应用程序组,写一个小的监视程序,来恢复数据库环境,然后执行那些实际上用数据库环境工作的进程。监视程序然后监视工作的进程,如果任何工作进程发生故障推出或其他原因,监视程序将kill所有仍然存活的其他进程,然后执行恢复任务,然后重新这个循环。

Berkeley DB -- 补充一些东西


前面漏掉的一些东东。

腐烂数据的处理或者说数据库文件的瘦身:
当你从Btree或Hash数据库删除key/data对时,它并不把这个返回给文件系统,这使得数据重用成为可能。也就是说Btree和Hash数据库都是只增的。当你删除大量key/data对时,你可能想使数据库文件也缩减,你应该建立一个新的数据库文件,把记录从旧文件复制过去。应该是导入导出记录,而不是直接copy文件。


字节序的问题:

例如:数字254~257。在一个小数在前(little-endian)的系统上是:
254 fe 0 0 0
255 ff 0 0 0
256 0 1 0 0
257 1 1 0 0
如果你把他们当成字符串处理那么他们的排序是糟糕的:
256
257
254
255
在一个大数在前(big-endian)系统上是:
254 0 0 0 fe
255 0 0 0 ff
256 0 0 1 0
257 0 0 1 1
and so, if you treat them as strings they sort nicely. Which means, if you use steadily increasing integers as keys on a big-endian system Berkeley DB behaves well and you get compact trees, but on a little-endian system Berkeley DB produces much less compact trees. To avoid this problem, you may want to convert the keys to flat text or big-endian representations, or provide your own Btree comparison function.

Berkeley DB -- DB Replication (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方法发起一次选举。

Berkeley DB -- DB Replication (HA)中部


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”那一章。
Berkeley DB -- DB Replication (HA)下部


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 部分结束)
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Berkeley DB 使用经验总结
MySQL主主复制 MMM实现高可用
在Yarn上运行spark
BerkeleyDB库简介
memcachedb使用
检测mysql同步状态实现代码
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服