BookKeeper源碼解析之Bookie啓動流程(一)

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,用於數據刷盤
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章