Ensemble: Set of Bookies across which a ledger is striped,一个 Ledger 所涉及的 Bookie 集合,初始化 Ledger 时,需要指定这个 Ledger 可以在几台 Bookie 上存储;
Write Quorum Size: Number of replicas,要写入的副本数;
Ack Quorum Size: Number of responses needed before client’s write is satisfied,当这么多副本写入成功后才会向 client 返回成功,比如副本数设置了 3,这个设置了2,client 会同时向三副本写入数据,当收到两个成功响应后,会认为数据已经写入成功;
LAC: Last Add Confirmed,Ledger 中已经确认的最近一条数据的 entry id。
这里先看下 BookKeeper 的目录结构,跟其他分布式系统也类似,命令在 bin 目录下,配置文件在 conf 目录下,lib 是其依赖的相关 jar 包,如下所示:
1 2 3 4 5 6 7 8 9
[matt@XXX2 bookkeeper]$ ll total 64 drwxr-xr-x 2 matt matt 4096 Sep 20 18:35 bin drwxr-xr-x 2 matt matt 4096 Sep 20 18:35 conf drwxrwxr-x 9 matt matt 4096 Oct 9 21:41 deps drwxrwxr-x 2 matt matt 12288 Oct 9 21:41 lib -rw-r--r-- 1 matt matt 24184 Sep 20 18:35 LICENSE -rw-r--r-- 1 matt matt 5114 Sep 20 18:35 NOTICE -rw-r--r-- 1 matt matt 4267 Sep 20 18:35 README.md
[matt@XXX2 bookkeeper]$ ll bin/ total 56 -rwxr-xr-x 1 matt matt 2319 Sep 20 18:35 bkctl -rwxr-xr-x 1 matt matt 5874 Sep 20 18:35 bookkeeper -rwxr-xr-x 1 matt matt 2869 Sep 20 18:35 bookkeeper-cluster.sh -rwxr-xr-x 1 matt matt 4590 Sep 20 18:35 bookkeeper-daemon.sh -rwxr-xr-x 1 matt matt 7785 Sep 20 18:35 common.sh -rwxr-xr-x 1 matt matt 4575 Sep 20 18:35 dlog -rwxr-xr-x 1 matt matt 1738 Sep 20 18:35 standalone -rwxr-xr-x 1 matt matt 5128 Sep 20 18:35 standalone.docker-compose -rwxr-xr-x 1 matt matt 1854 Sep 20 18:35 standalone.process
[matt@XXX2 bookkeeper]$ ll conf/ total 84 -rw-r--r-- 1 matt matt 1804 Sep 20 18:35 bk_cli_env.sh -rw-r--r-- 1 matt matt 2448 Sep 20 18:35 bkenv.sh -rwxr-xr-x 1 matt matt 42269 Sep 20 18:35 bk_server.conf -rw-r--r-- 1 matt matt 1211 Sep 20 18:35 jaas_example.conf -rw-r--r-- 1 matt matt 2311 Sep 20 18:35 log4j.cli.properties -rw-r--r-- 1 matt matt 2881 Sep 20 18:35 log4j.properties -rw-r--r-- 1 matt matt 1810 Sep 20 18:35 log4j.shell.properties -rw-r--r-- 1 matt matt 1117 Sep 20 18:35 nettyenv.sh -rwxr-xr-x 1 matt matt 1300 Sep 20 18:35 standalone.conf -rw-r--r-- 1 matt matt 3275 Sep 20 18:35 zookeeper.conf -rw-r--r-- 1 matt matt 843 Sep 20 18:35 zookeeper.conf.dynamic
2018-10-13 21:05:40,674 - ERROR [main:Main@221] - Failed to build bookie server org.apache.bookkeeper.bookie.BookieException$InvalidCookieException: instanceId 406a08e5-911e-4ab6-b97b-40e4a56279a8 is not matching with null at org.apache.bookkeeper.bookie.Cookie.verifyInternal(Cookie.java:142) at org.apache.bookkeeper.bookie.Cookie.verify(Cookie.java:147) at org.apache.bookkeeper.bookie.Bookie.verifyAndGetMissingDirs(Bookie.java:381) at org.apache.bookkeeper.bookie.Bookie.checkEnvironmentWithStorageExpansion(Bookie.java:444) at org.apache.bookkeeper.bookie.Bookie.checkEnvironment(Bookie.java:262) at org.apache.bookkeeper.bookie.Bookie.<init>(Bookie.java:646) at org.apache.bookkeeper.proto.BookieServer.newBookie(BookieServer.java:133) at org.apache.bookkeeper.proto.BookieServer.<init>(BookieServer.java:102) at org.apache.bookkeeper.server.service.BookieService.<init>(BookieService.java:43) at org.apache.bookkeeper.server.Main.buildBookieServer(Main.java:299) at org.apache.bookkeeper.server.Main.doMain(Main.java:219) at org.apache.bookkeeper.server.Main.main(Main.java:201)
org.apache.bookkeeper.client.BKException$BKNotEnoughBookiesException: Not enough non-faulty bookies available at org.apache.bookkeeper.client.SyncCallbackUtils.finish(SyncCallbackUtils.java:83) at org.apache.bookkeeper.client.SyncCallbackUtils$SyncCreateCallback.createComplete(SyncCallbackUtils.java:106) at org.apache.bookkeeper.client.LedgerCreateOp.createComplete(LedgerCreateOp.java:238) at org.apache.bookkeeper.client.LedgerCreateOp.initiate(LedgerCreateOp.java:142) at org.apache.bookkeeper.client.BookKeeper.asyncCreateLedger(BookKeeper.java:891) at org.apache.bookkeeper.client.BookKeeper.createLedger(BookKeeper.java:975) at org.apache.bookkeeper.client.BookKeeper.createLedger(BookKeeper.java:930) at org.apache.bookkeeper.client.BookKeeper.createLedger(BookKeeper.java:911) at com.matt.test.bookkeeper.ledger.LedgerTest.createLedgerSync(LedgerTest.java:110) at com.matt.test.bookkeeper.ledger.LedgerTest.main(LedgerTest.java:25) Exception in thread "main" java.lang.NullPointerException at com.matt.test.bookkeeper.ledger.LedgerTest.main(LedgerTest.java:26)
The ledger API is a lower-level API that enables you to interact with ledgers directly,第一种是一种较为底层的 API 接口,直接与 Ledger 交互,见 The Ledger API;
The Ledger Advanced API is an advanced extension to Ledger API to provide more flexibilities to applications,第二种较高级的 API,提供了一些较高级的功能,见 The Advanced Ledger API;
The DistributedLog API is a higher-level API that provides convenient abstractions,这种是关于 DistributedLog 的一些操作 API,见 DistributedLog。
/** * read entry from startId to endId * * @param ledgerHandle the ledger * @param startId start entry id * @param endId end entry id * @return the entries, if occur exception, return null */ public Enumeration<LedgerEntry> readEntry(LedgerHandle ledgerHandle, int startId, int endId){ try { return ledgerHandle.readEntries(startId, endId); } catch (InterruptedException e) { e.printStackTrace(); } catch (BKException e) { e.printStackTrace(); } returnnull; }
/** * read entry from 0 to the LAC * * @param ledgerHandle the ledger * @return the entries, if occur exception, return null */ public Enumeration<LedgerEntry> readEntry(LedgerHandle ledgerHandle){ try { return ledgerHandle.readEntries(0, ledgerHandle.getLastAddConfirmed()); } catch (InterruptedException e) { e.printStackTrace(); } catch (BKException e) { e.printStackTrace(); } returnnull; }
/** * read entry form 0 to lastEntryIdExpectedToRead which can larger than the LastAddConfirmed range * * @param ledgerHandle the handle * @param lastEntryIdExpectedToRead the last entry id * @return the entries, if occur exception, return null */ public Enumeration<LedgerEntry> readEntry(LedgerHandle ledgerHandle, long lastEntryIdExpectedToRead){ try { return ledgerHandle.readUnconfirmedEntries(0, lastEntryIdExpectedToRead); } catch (InterruptedException e) { e.printStackTrace(); } catch (BKException e) { e.printStackTrace(); } returnnull; }
删除 Ledger
Ledger 的删除实现也很简洁,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/** * delete the ledger * * @param ledgerId the ledger id * @return if occur exception, return false */ publicbooleandeleteLedger(long ledgerId){ try { bkClient.deleteLedger(ledgerId); returntrue; } catch (Exception e) { e.printStackTrace(); } returnfalse; }
/** * add the msg to the ledger on the special entryId * * @param ledgerHandleAdv ledgerHandleAdv * @param entryId the entry id * @param msg msg */ publicvoidaddEntry(LedgerHandleAdv ledgerHandleAdv, long entryId, String msg){ try { ledgerHandleAdv.addEntry(entryId, msg.getBytes()); } catch (InterruptedException e) { e.printStackTrace(); } catch (BKException e) { e.printStackTrace(); } }
关于这个 API,社区官方文档有如下介绍:
The entry id has to be non-negative.
Clients are okay to add entries out of order.
However, the entries are only acknowledged in a monotonic order starting from 0.
首先,说下我对上面的理解:entry.id 要求是非负的,client 在添加 entry 时可以乱序,但是 entry 只有 0 开始单调顺序增加时才会被 ack。最开始,我以为是只要 entry.id 单调递增就可以,跑了一个测试用例,第一个 entry 的 id 设置为 0,第二个设置为 2,然后程序直接 hang 在那里了,相应日志信息为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:662 ] - [ DEBUG ] Got Add response from bookie:XXX.230:3181 rc:EOK, ledger:8:entry:0 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:663 ] - [ DEBUG ] Got Add response from bookie:XXX.247:3181 rc:EOK, ledger:8:entry:0 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:663 ] - [ DEBUG ] Submit callback (lid:8, eid: 0). rc:0 2018-10-19 16:58:34 [ main:663 ] - [ DEBUG ] Adding entry [50, 32, 109, 97, 116, 116, 32, 116, 101, 115, 116] 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:673 ] - [ DEBUG ] Got Add response from bookie:XXX.247:3181 rc:EOK, ledger:8:entry:2 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:673 ] - [ DEBUG ] Got Add response from bookie:XXX.230:3181 rc:EOK, ledger:8:entry:2 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:673 ] - [ DEBUG ] Head of the queue entryId: 2 is not the expected value: 1 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:673 ] - [ DEBUG ] Got Add response from bookie:XXX.146:3181 rc:EOK, ledger:8:entry:0 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:673 ] - [ DEBUG ] Head of the queue entryId: 2 is not the expected value: 1 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:681 ] - [ DEBUG ] Got Add response from bookie:XXX.146:3181 rc:EOK, ledger:8:entry:2 2018-10-19 16:58:34 [ BookKeeperClientWorker-OrderedExecutor-0-0:681 ] - [ DEBUG ] Head of the queue entryId: 2 is not the expected value: 1 2018-10-19 16:58:37 [ main-SendThread(zk01:2181):3702 ] - [ DEBUG ] Got ping response for sessionid: 0x3637dbff9e7486c after 0ms 2018-10-19 16:58:40 [ main-SendThread(zk01:2181):7039 ] - [ DEBUG ] Got ping response for sessionid: 0x3637dbff9e7486c after 0ms 2018-10-19 16:58:43 [ main-SendThread(zk01:2181):10374 ] - [ DEBUG ] Got ping response for sessionid: 0x3637dbff9e7486c after 0ms 2018-10-19 16:58:47 [ main-SendThread(zk01:2181):13710 ] - [ DEBUG ] Got ping response for sessionid: 0x3637dbff9e7486c after 0ms 2018-10-19 16:58:50 [ main-SendThread(zk01:2181):17043 ] - [ DEBUG ] Got ping response for sessionid: 0x3637dbff9e7486c after 0ms
可以看到有这样的异常日志 Head of the queue entryId: 2 is not the expected value: 1,期望的 entry id 是 1,这里是 2,乱序了,导致程序直接 hang 住(hang 住的原因推测是这个 Entry 没有被 ack),该异常信息出现地方如下:
voidsendAddSuccessCallbacks(){ // Start from the head of the queue and proceed while there are // entries that have had all their responses come back PendingAddOp pendingAddOp;
while ((pendingAddOp = pendingAddOps.peek()) != null && blockAddCompletions.get() == 0) { if (!pendingAddOp.completed) { if (LOG.isDebugEnabled()) { LOG.debug("pending add not completed: {}", pendingAddOp); } return; } // Check if it is the next entry in the sequence. if (pendingAddOp.entryId != 0 && pendingAddOp.entryId != pendingAddsSequenceHead + 1) { if (LOG.isDebugEnabled()) { LOG.debug("Head of the queue entryId: {} is not the expected value: {}", pendingAddOp.entryId, pendingAddsSequenceHead + 1); } return; }