BookKeeper(BK)啓動流程
BK的啓動入口類是Main,Main有一個靜態代碼塊,在執行main方法之前,會限執行靜態代碼塊的內容:
static final Options BK_OPTS = new Options();
static {
BK_OPTS.addOption("c", "conf", true, "Configuration for Bookie Server");
BK_OPTS.addOption("withAutoRecovery", false,
"Start Autorecovery service Bookie server");
BK_OPTS.addOption("r", "readOnly", false,
"Force Start a ReadOnly Bookie server");
BK_OPTS.addOption("z", "zkserver", true, "Zookeeper Server");
BK_OPTS.addOption("m", "zkledgerpath", true, "Zookeeper ledgers root path");
BK_OPTS.addOption("p", "bookieport", true, "bookie port exported");
BK_OPTS.addOption("j", "journal", true, "bookie journal directory");
Option indexDirs = new Option ("i", "indexdirs", true, "bookie index directories");
indexDirs.setArgs(10);
BK_OPTS.addOption(indexDirs);
Option ledgerDirs = new Option ("l", "ledgerdirs", true, "bookie ledgers directories");
ledgerDirs.setArgs(10);
BK_OPTS.addOption(ledgerDirs);
BK_OPTS.addOption("h", "help", false, "Print help message");
}
這部分內容是構建出Options,供後續的啓動參數解析使用,然後執行main()方法。
public static void main(String[] args) {
int retCode = doMain(args);
Runtime.getRuntime().exit(retCode);
}
核心啓動邏輯都在doMain()方法裏,
static int doMain(String[] args) {
ServerConfiguration conf;
// 0. parse command line, 解析命令行參數
try {
conf = parseCommandLine(args);
} catch (IllegalArgumentException iae) {
return ExitCode.INVALID_CONF;
}
// 1. building the component stack: 構建componnet stack
LifecycleComponent server;
try {
server = buildBookieServer(new BookieConfiguration(conf));
} catch (Exception e) {
log.error("Failed to build bookie server", e);
return ExitCode.SERVER_EXCEPTION;
}
// 2. start the server, 啓動server
try {
ComponentStarter.startComponent(server).get();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
// the server is interrupted
log.info("Bookie server is interrupted. Exiting ...");
} catch (ExecutionException ee) {
log.error("Error in bookie shutdown", ee.getCause());
return ExitCode.SERVER_EXCEPTION;
}
return ExitCode.OK;
}
根據註釋內容,邏輯是比較清晰的:
- 解析命令行參數,並將參數保存到ServerConfiguration裏;
- 創建LifecycleComponent,實際類型是一個LifecycleComponentStack,LifecycleComponentStack中會保存多個LifecycleComponent的實例,通過LifecycleComponentStack統一進行啓動、停止以及停止的操作,這裏保存的每一個LifecycleComponent都是一種服務;
- 啓動所有的服務
下面逐步分析每一步的流程
解析命令行參數
命令行解析的內容主要在parseCommandLine()方法,實際執行解析的過程是在parseArgs()方法:
private static ServerConfiguration parseArgs(String[] args)
throws IllegalArgumentException {
try {
BasicParser parser = new BasicParser();
CommandLine cmdLine = parser.parse(BK_OPTS, args);
if (cmdLine.hasOption('h')) {
throw new IllegalArgumentException();
}
ServerConfiguration conf = new ServerConfiguration();
if (cmdLine.hasOption('c')) {
String confFile = cmdLine.getOptionValue("c");
loadConfFile(conf, confFile);
}
if (cmdLine.hasOption("withAutoRecovery")) {
conf.setAutoRecoveryDaemonEnabled(true);
}
if (cmdLine.hasOption("r")) {
conf.setForceReadOnlyBookie(true);
}
boolean overwriteMetadataServiceUri = false;
String sZkLedgersRootPath = "/ledgers";
if (cmdLine.hasOption('m')) {
sZkLedgersRootPath = cmdLine.getOptionValue('m');
log.info("Get cmdline zookeeper ledger path: {}", sZkLedgersRootPath);
overwriteMetadataServiceUri = true;
}
String sZK = conf.getZkServers();
if (cmdLine.hasOption('z')) {
sZK = cmdLine.getOptionValue('z');
log.info("Get cmdline zookeeper instance: {}", sZK);
overwriteMetadataServiceUri = true;
}
// command line arguments overwrite settings in configuration file
if (overwriteMetadataServiceUri) {
String metadataServiceUri = "zk://" + sZK + sZkLedgersRootPath;
conf.setMetadataServiceUri(metadataServiceUri);
log.info("Overwritten service uri to {}", metadataServiceUri);
}
if (cmdLine.hasOption('p')) {
String sPort = cmdLine.getOptionValue('p');
log.info("Get cmdline bookie port: {}", sPort);
Integer iPort = Integer.parseInt(sPort);
conf.setBookiePort(iPort.intValue());
}
if (cmdLine.hasOption('j')) {
String sJournalDir = cmdLine.getOptionValue('j');
log.info("Get cmdline journal dir: {}", sJournalDir);
conf.setJournalDirName(sJournalDir);
}
if (cmdLine.hasOption('i')) {
String[] sIndexDirs = cmdLine.getOptionValues('i');
log.info("Get cmdline index dirs: ");
for (String index : sIndexDirs) {
log.info("indexDir : {}", index);
}
conf.setIndexDirName(sIndexDirs);
}
if (cmdLine.hasOption('l')) {
String[] sLedgerDirs = cmdLine.getOptionValues('l');
log.info("Get cmdline ledger dirs: ");
for (String ledger : sLedgerDirs) {
log.info("ledgerdir : {}", ledger);
}
conf.setLedgerDirNames(sLedgerDirs);
}
return conf;
} catch (ParseException e) {
log.error("Error parsing command line arguments : ", e);
throw new IllegalArgumentException(e);
}
}
解析的最終結果是將配置信息保存在ServerConfiguration中,這裏的邏輯比較清晰,不再贅述,只需要注意一點,命令行傳遞的參數優先級高於配置文件配置的參數。
構建bookie所需的服務
這個過程是將bookie 所需的服務統一添加到LifecycleComponentStack中,然後通過LifecycleComponentStack統一啓動。
public static LifecycleComponentStack buildBookieServer(BookieConfiguration conf) throws Exception {
LifecycleComponentStack.Builder serverBuilder = LifecycleComponentStack.newBuilder().withName("bookie-server");
// 1. build stats provider,指標服務
StatsProviderService statsProviderService =
new StatsProviderService(conf);
StatsLogger rootStatsLogger = statsProviderService.getStatsProvider().getStatsLogger("");
serverBuilder.addComponent(statsProviderService);
log.info("Load lifecycle component : {}", StatsProviderService.class.getName());
// 2. build bookie server,bookie服務
BookieService bookieService =
new BookieService(conf, rootStatsLogger);
serverBuilder.addComponent(bookieService);
log.info("Load lifecycle component : {}", BookieService.class.getName());
//一致性檢查任務
if (conf.getServerConf().isLocalScrubEnabled()) {
serverBuilder.addComponent(
new ScrubberService(
rootStatsLogger.scope(ScrubberStats.SCOPE),
conf, bookieService.getServer().getBookie().getLedgerStorage()));
}
// 3. build auto recovery,自動恢復服務
if (conf.getServerConf().isAutoRecoveryDaemonEnabled()) {
AutoRecoveryService autoRecoveryService =
new AutoRecoveryService(conf, rootStatsLogger.scope(REPLICATION_SCOPE));
serverBuilder.addComponent(autoRecoveryService);
log.info("Load lifecycle component : {}", AutoRecoveryService.class.getName());
}
// 4. build http service,rest服務
if (conf.getServerConf().isHttpServerEnabled()) {
BKHttpServiceProvider provider = new BKHttpServiceProvider.Builder()
.setBookieServer(bookieService.getServer())
.setServerConfiguration(conf.getServerConf())
.setStatsProvider(statsProviderService.getStatsProvider())
.build();
HttpService httpService =
new HttpService(provider, conf, rootStatsLogger);
serverBuilder.addComponent(httpService);
log.info("Load lifecycle component : {}", HttpService.class.getName());
}
// 5. build extra services,其他服務
String[] extraComponents = conf.getServerConf().getExtraServerComponents();
if (null != extraComponents) {
try {
List<ServerLifecycleComponent> components = loadServerComponents(
extraComponents,
conf,
rootStatsLogger);
for (ServerLifecycleComponent component : components) {
serverBuilder.addComponent(component);
log.info("Load lifecycle component : {}", component.getClass().getName());
}
} catch (Exception e) {
if (conf.getServerConf().getIgnoreExtraServerComponentsStartupFailures()) {
log.info("Failed to load extra components '{}' - {}. Continuing without those components.",
StringUtils.join(extraComponents), e.getMessage());
} else {
throw e;
}
}
}
return serverBuilder.build();
}
上述代碼可以看到:
- 首先創建 StatsProviderService,這個服務的主要目的是採集指標信息,默認是一個空的實現
- 創建BookieService 服務,會傳入第一步StatsProviderService 的 StatsLogger,然後會初始化一個BookieServer
- 如果isLocalScrubEnabled爲true的話,開啓一個本地一致性檢查的服務
- 創建http服務
- 創建配置中指定的其他的服務
構建狀態(指標)服務
// 1. build stats provider
StatsProviderService statsProviderService =
new StatsProviderService(conf);
StatsLogger rootStatsLogger = statsProviderService.getStatsProvider().getStatsLogger("");
...
public StatsProviderService(BookieConfiguration conf) throws Exception {
//指定component的名稱、配置信息以及StatsLogger,這裏 StatsLogger 是 NullStatsLogger
super(NAME, conf, NullStatsLogger.INSTANCE)S
//根據配置配置文件內容初始化 StatsProvider, 默認是 NullStatsProvider
Class<? extends StatsProvider> statsProviderClass =
conf.getServerConf().getStatsProviderClass();
this.statsProvider = ReflectionUtils.newInstance(statsProviderClass);
}
可以看到一個StatsProviderService都有一個名稱、StatsLogger以及StatsProvider。下面介紹一下這兩個類:
StatsProvider的作用是爲不同的scope提供不同的 StatsLogger。
public interface StatsProvider {
// 初始化providewr
void start(Configuration conf);
// 停止provider
void stop();
// 向writer寫入指標信息
default void writeAllMetrics(Writer writer) throws IOException {
throw new UnsupportedOperationException("writeAllMetrics is not implemented yet");
}
// 獲取指定scope的stats logger
StatsLogger getStatsLogger(String scope);
/**
* Return the fully qualified stats name comprised of given <tt>statsComponents</tt>.
*
* @param statsComponents stats components to comprise the fully qualified stats name
* @return the fully qualified stats name
*/
default String getStatsName(String...statsComponents) {
return StringUtils.join(statsComponents, '/');
}
}
StatsLogger的作用是對外提供兩個有用的接口,一個是爲 OpState提供 OpStatsLogger,一個是爲stat提供StatsLogger。
public interface StatsLogger {
// 爲name指定的OpStat提供logger
OpStatsLogger getOpStatsLogger(String name);
// 獲取name指定的Counter指標
Counter getCounter(String name);
// 註冊一個Gauge指標
<T extends Number> void registerGauge(String name, Gauge<T> gauge);
// 卸載一個Gauge指標
<T extends Number> void unregisterGauge(String name, Gauge<T> gauge);
// 獲取name指定的StatsLogger
StatsLogger scope(String name);
// 移除scope對應的StatsLogger
void removeScope(String name, StatsLogger statsLogger);
}
構建BookieService
啓動BookieService,指定component的名稱、配置、以及一個從StatsProvider中獲取的StatsLogger。
public BookieService(BookieConfiguration conf,
StatsLogger statsLogger)
throws Exception {
super(NAME, conf, statsLogger);
this.server = new BookieServer(conf.getServerConf(), statsLogger);
}
接下來構造一個BookieServer,這是BookKeeper服務的核心。
public BookieServer(ServerConfiguration conf, StatsLogger statsLogger)
throws IOException, KeeperException, InterruptedException,
BookieException, UnavailableException, CompatibilityException, SecurityException {
this.conf = conf;
// 驗證用戶是否在配置中有權限
validateUser(conf);
String configAsString;
try {
configAsString = conf.asJson();
LOG.info(configAsString);
} catch (ParseJsonException pe) {
LOG.error("Got ParseJsonException while converting Config to JSONString", pe);
}
// 初始化內存分配器
ByteBufAllocator allocator = getAllocator(conf);
this.statsLogger = statsLogger;
// 初始化NettyServer
this.nettyServer = new BookieNettyServer(this.conf, null, allocator);
try {
// 初始化Bookie
this.bookie = newBookie(conf, allocator);
} catch (IOException | KeeperException | InterruptedException | BookieException e) {
// interrupted on constructing a bookie
this.nettyServer.shutdown();
throw e;
}
final SecurityHandlerFactory shFactory;
shFactory = SecurityProviderFactoryFactory
.getSecurityProviderFactory(conf.getTLSProviderFactoryClass());
this.requestProcessor = new BookieRequestProcessor(conf, bookie,
statsLogger.scope(SERVER_SCOPE), shFactory, bookie.getAllocator());
// 爲 NettyServer 指定RequestProcessor
this.nettyServer.setRequestProcessor(this.requestProcessor);
}
這裏的流程比較長:
- 校驗用戶權限
- 構造內存分配器
- 構造NettyServer
- 構造Bookie實例
- 爲NettyServer指定RequestProcessor
這裏跳過用戶權限校驗,重點說明一下後續的部分。
構造內存分配器
private ByteBufAllocator getAllocator(ServerConfiguration conf) {
return ByteBufAllocatorBuilder.create()
// 分配 buffer 的策略
.poolingPolicy(conf.getAllocatorPoolingPolicy())
// 分配器的池化並行度
.poolingConcurrency(conf.getAllocatorPoolingConcurrency())
// oom是的策略,包括兩種一種是直接拋出異常,一種退化到heap
.outOfMemoryPolicy(conf.getAllocatorOutOfMemoryPolicy())
// oom listener,分配失敗時,打印提示信息
.outOfMemoryListener((ex) -> {
try {
LOG.error("Unable to allocate memory, exiting bookie", ex);
} finally {
if (uncaughtExceptionHandler != null) {
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), ex);
}
}
})
// Netty 內存泄露的探測策略
.leakDetectionPolicy(conf.getAllocatorLeakDetectionPolicy())
.build();
}
---
// 定義分配buffer的策略
public enum PoolingPolicy {
// 從Heap區分配,不池化。
// 這個策略內存佔用最小,因爲在heap上,JVM GC會自動回收內存,但是可能會影響吞吐
UnpooledHeap,
// 分配buffer是使用直接內存,並且池化。
// 直接內存可以避免GC的消耗,並且可以避免讀寫channel的內存拷貝。
// 池化會增加內存空間的消耗,每個線程爲了避免競爭會保存一部分內存作爲thread-local內存,分配器匯中存在內存碎片
PooledDirect
}
---
// 定義內存泄漏的探測策略
public enum LeakDetectionPolicy {
// 不探測,沒有開銷
Disabled,
// 使用已分配buffer的%1來跟蹤泄露
Simple,
// 使用已分配buffer的%1來跟蹤泄露,並且報告buffer使用未知的堆棧信息
Advanced,
// 使用已分配buffer的%100來跟蹤泄露,並且報告buffer使用未知的堆棧信息, 消耗比較高
Paranoid,
}
構造NettyServer
構造NettyServer之前,初始化eventLoopGroup和監聽端口,然後就是Netty的標準ServerBootstrap的配置流程。
private void listenOn(InetSocketAddress address, BookieSocketAddress bookieAddress) throws InterruptedException {
if (!conf.isDisableServerSocketBind()) {
ServerBootstrap bootstrap = new ServerBootstrap();
// 設置Accept事件循環和work事件循環中的內存分配器
bootstrap.option(ChannelOption.ALLOCATOR, allocator);
bootstrap.childOption(ChannelOption.ALLOCATOR, allocator);
// 設置Accept事件循環組和work事件循環組
bootstrap.group(eventLoopGroup, eventLoopGroup);
// 是否立即發送
bootstrap.childOption(ChannelOption.TCP_NODELAY, conf.getServerTcpNoDelay());
// Timeout to drain the socket on close. 即socket 關閉時可以等待的時間
bootstrap.childOption(ChannelOption.SO_LINGER, conf.getServerSockLinger());
// 設置自適應的ReceiveBuffer, 真正分配buffer時也是用的上面的allocator
bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,
new AdaptiveRecvByteBufAllocator(conf.getRecvByteBufAllocatorSizeMin(),
conf.getRecvByteBufAllocatorSizeInitial(), conf.getRecvByteBufAllocatorSizeMax()));
// 高低水位,用於限制客戶端限流
bootstrap.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(
conf.getServerWriteBufferLowWaterMark(), conf.getServerWriteBufferHighWaterMark()));
if (eventLoopGroup instanceof EpollEventLoopGroup) {
bootstrap.channel(EpollServerSocketChannel.class);
} else {
bootstrap.channel(NioServerSocketChannel.class);
}
// 添加channel 添加 hanlder pipeline
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
synchronized (suspensionLock) {
while (suspended) {
suspensionLock.wait();
}
}
BookieSideConnectionPeerContextHandler contextHandler =
new BookieSideConnectionPeerContextHandler();
ChannelPipeline pipeline = ch.pipeline();
// For ByteBufList, skip the usual LengthFieldPrepender and have the encoder itself to add it
// Outbound
pipeline.addLast("bytebufList", ByteBufList.ENCODER_WITH_SIZE);
// Inbound
pipeline.addLast("lengthbaseddecoder", new LengthFieldBasedFrameDecoder(maxFrameSize, 0, 4, 0, 4));
// Outbound
pipeline.addLast("lengthprepender", new LengthFieldPrepender(4));
// Inbound
pipeline.addLast("bookieProtoDecoder", new BookieProtoEncoding.RequestDecoder(registry));
// Outbound
pipeline.addLast("bookieProtoEncoder", new BookieProtoEncoding.ResponseEncoder(registry));
// Inbound
pipeline.addLast("bookieAuthHandler", new AuthHandler.ServerSideHandler(
contextHandler.getConnectionPeer(), authProviderFactory));
// 正常情況下,channel初始化時,isRunning會被設置成true,並且requestProcessor
// 會被設置爲BookieRequestProcessor
ChannelInboundHandler requestHandler = isRunning.get()
? new BookieRequestHandler(conf, requestProcessor, allChannels)
: new RejectRequestHandler();
// Inbound
pipeline.addLast("bookieRequestHandler", requestHandler);
// Inbound
pipeline.addLast("contextHandler", contextHandler);
}
});
// Bind and start to accept incoming connections
Channel listen = bootstrap.bind(address.getAddress(), address.getPort()).sync().channel();
if (listen.localAddress() instanceof InetSocketAddress) {
if (conf.getBookiePort() == 0) {
conf.setBookiePort(((InetSocketAddress) listen.localAddress()).getPort());
}
}
}
...
}
主要配置是在ChannelInitializer中設置Handler。
構建Bookie
bookie是BK的核心,構建Bookie的過程如下:
protected Bookie newBookie(ServerConfiguration conf, ByteBufAllocator allocator)
throws IOException, KeeperException, InterruptedException, BookieException {
return conf.isForceReadOnlyBookie()
? new ReadOnlyBookie(conf, statsLogger.scope(BOOKIE_SCOPE), allocator)
: new Bookie(conf, statsLogger.scope(BOOKIE_SCOPE), allocator);
}
根據配置指定是ReadOnlyBookie還是普通的bookie,以普通bookie爲例:
public Bookie(ServerConfiguration conf, StatsLogger statsLogger, ByteBufAllocator allocator)
throws IOException, InterruptedException, BookieException {
super("Bookie-" + conf.getBookiePort());
this.statsLogger = statsLogger;
this.conf = conf;
// 從配置文件中獲取journal 目錄 list,然後在在每個目錄下創建一個current目錄
this.journalDirectories = Lists.newArrayList();
for (File journalDirectory : conf.getJournalDirs()) {
this.journalDirectories.add(getCurrentDirectory(journalDirectory));
}
// 初始化DiskChecker,有兩個參數 diskUsageThreshold 和 diskUsageWarnThreshold
// diskUsageThreshold表示磁盤的最大使用率,默認是0.95,目錄列表中的所有目錄都超過限制之後
// 如果bookie配置可以以readonly模式運行,就會轉化爲readonly狀態,否則會停止;
// diskUsageWarnThreshold 表示磁盤使用的告警閾值,默認是0.90,超過這個值會拋出
// DiskWarnThresholdException,並且會觸發gc,當使用率低於這個值時,目錄重新變爲開寫狀態
DiskChecker diskChecker = createDiskChecker(conf);
// 爲ledger和index創建LedgerDirsManager,用來管理ledger和index的目錄列表
this.ledgerDirsManager = createLedgerDirsManager(conf, diskChecker, statsLogger.scope(LD_LEDGER_SCOPE));
this.indexDirsManager = createIndexDirsManager(conf, diskChecker, statsLogger.scope(LD_INDEX_SCOPE), this.ledgerDirsManager);
this.allocator = allocator;
// 初始化zk 客戶端
this.metadataDriver = instantiateMetadataDriver(conf);
checkEnvironment(this.metadataDriver);
try {
if (this.metadataDriver != null) {
// 初始化ledgerManagerFactory,用於生成ledgerManager
ledgerManagerFactory = metadataDriver.getLedgerManagerFactory();
LOG.info("instantiate ledger manager {}", ledgerManagerFactory.getClass().getName());
// ledgerManager負責和zk等元數據存儲交互,用來管理ledger的元數據信息
ledgerManager = ledgerManagerFactory.newLedgerManager();
} else {
ledgerManagerFactory = null;
ledgerManager = null;
}
} catch (MetadataException e) {
throw new MetadataStoreException("Failed to initialize ledger manager", e);
}
// 初始化狀態管理器
stateManager = initializeStateManager();
// register shutdown handler using trigger mode
stateManager.setShutdownHandler(exitCode -> triggerBookieShutdown(exitCode));
// LedgerDirsMonitor, 監控所有配置的目錄,如果發現磁盤錯誤或者所有的leger 目錄都滿,就拋出異常,
// bookie啓動失敗
List<LedgerDirsManager> dirsManagers = new ArrayList<>();
dirsManagers.add(ledgerDirsManager);
if (indexDirsManager != ledgerDirsManager) {
dirsManagers.add(indexDirsManager);
}
this.dirsMonitor = new LedgerDirsMonitor(conf, diskChecker, dirsManagers);
try {
this.dirsMonitor.init();
} catch (NoWritableLedgerDirException nle) {
// start in read-only mode if no writable dirs and read-only allowed
if (!conf.isReadOnlyModeEnabled()) {
throw nle;
} else {
this.stateManager.transitionToReadOnlyMode();
}
}
// instantiate the journals, 初始化journal
journals = Lists.newArrayList();
for (int i = 0; i < journalDirectories.size(); i++) {
journals.add(new Journal(i, journalDirectories.get(i),
conf, ledgerDirsManager, statsLogger.scope(JOURNAL_SCOPE), allocator));
}
this.entryLogPerLedgerEnabled = conf.isEntryLogPerLedgerEnabled();
CheckpointSource checkpointSource = new CheckpointSourceList(journals);
// 初始化ledgerStore,默認是一個 SortedLedgerStorage
ledgerStorage = buildLedgerStorage(conf);
boolean isDbLedgerStorage = ledgerStorage instanceof DbLedgerStorage;
/*
* with this change https://github.com/apache/bookkeeper/pull/677,
* LedgerStorage drives the checkpoint logic.
*
* <p>There are two exceptions:
*
* 1) with multiple entry logs, checkpoint logic based on a entry log is
* not possible, hence it needs to be timebased recurring thing and
* it is driven by SyncThread. SyncThread.start does that and it is
* started in Bookie.start method.
*
* 2) DbLedgerStorage
*/
// 一般都是由 LedgerStorage來驅動checkpoint 邏輯,但是有兩個例外:
// 1. 有多個entry logs,cp邏輯不能依賴於一個entry log,應該是一個基於時間的循環,有SyncThread驅動
// 2. DbLegerStorage
if (entryLogPerLedgerEnabled || isDbLedgerStorage) {
syncThread = new SyncThread(conf, getLedgerDirsListener(), ledgerStorage, checkpointSource) {
@Override
public void startCheckpoint(Checkpoint checkpoint) {
/*
* in the case of entryLogPerLedgerEnabled, LedgerStorage
* dont drive checkpoint logic, but instead it is done
* periodically by SyncThread. So startCheckpoint which
* will be called by LedgerStorage will be no-op.
*/
}
@Override
public void start() {
executor.scheduleAtFixedRate(() -> {
doCheckpoint(checkpointSource.newCheckpoint());
}, conf.getFlushInterval(), conf.getFlushInterval(), TimeUnit.MILLISECONDS);
}
};
} else {
syncThread = new SyncThread(conf, getLedgerDirsListener(), ledgerStorage, checkpointSource);
}
// 初始化LedgerStorage
ledgerStorage.initialize(
conf,
ledgerManager,
ledgerDirsManager,
indexDirsManager,
stateManager,
checkpointSource,
syncThread,
statsLogger,
allocator);
// HandleFactoryImpl 用來獲取 handle,這裏的 hanlde 是 LedgerDescriptor,是 ledger 的實現
// 主要負責向 ledger addEntry 和或者從ledger readeEntry
handles = new HandleFactoryImpl(ledgerStorage);
// Expose Stats
this.bookieStats = new BookieStats(statsLogger);
}
這裏的邏輯比較多,主要包括內容是:
- 磁盤管理:爲journal、ledger和index目錄創建 LedgerDirsMonitor,用來監控 目錄的使用情況
- 元數據管理: 創建ZK 客戶端,用於ledger 元數據的管理
- 數據存儲管理:初始化ledgerStore,用於將ledger數據持久化到磁盤
- SyncThread:初始化SyncTread,用於數據刷盤