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,用于数据刷盘