rocketmq之CommitLog學習筆記

一臂容、CommitLog

RocketMQ 通過使用內(nèi)存映射文件來提高IO 訪問性能牛欢,無論是CommitLog 风题、ConsumeQueue 單個文件都被設計為固定長度像棘,如果一個文件寫滿以后再創(chuàng)建一個新文件蹄梢,文件名就為該文件第一條消息對應的全局物理偏移量疙筹。
CommitLog邏輯存儲如下:

image.png

二、MappedFileQueue

(一) MappedFileQueue核心屬性

MappedFileQueue巳是MappedFile 的管理容器, MappedFileQueue 是對存儲目錄的封裝而咆,例如CommitLog 文件的存儲路徑{ ROCKET_HOME} /store/commitlog/ 霍比,該目錄下會存在多個內(nèi)存映射文件(MappedFile)

MappedFileQueue 的核心屬性:

  • String storePath :存儲目錄。

  • int mappedFileSize : 單個文件的存儲大小暴备。

  • CopyOnWriteArrayList<MappedFile> mappedFiles: MappedFile 文件集合悠瞬。

  • AllocateMappedFileService allocateMappedFileService :創(chuàng)建MappedFile 服務類。

  • long flushedWhere = 0 : 當前刷盤指針涯捻, 表示該指針之前的所有數(shù)據(jù)全部持久化到磁盤浅妆。

  • long committedWhere = 0 : 當前數(shù)據(jù)提交指針,內(nèi)存中ByteBuffer 當前的寫指針障癌,該值大于等于flushedWhere 凌外。

(二) 根據(jù)消息存儲時間戳來查找MappdFile

根據(jù)消息存儲時間戳來查找MappdFile 。從MappedFile 列表中第一個文件開始查找涛浙,找到第一個最后一次更新時間大于待查找時間戳的文件康辑,如果不存在,則返回最后一個MappedFile 文件蝗拿。

    public MappedFile getMappedFileByTime(final long timestamp) {
        Object[] mfs = this.copyMappedFiles(0);
        if (null == mfs)
            return null;
?
        for (int i = 0; i < mfs.length; i++) {
            MappedFile mappedFile = (MappedFile) mfs[i];
            if (mappedFile.getLastModifiedTimestamp() >= timestamp) {
                return mappedFile;
            }
        }
        return (MappedFile) mfs[mfs.length - 1];
    }

(三) 根據(jù)偏移量獲取文件

根據(jù)offset 定位MappedFile 的算法為:( int) ((offset / this.mappedFileSize) -(mappedFile.getFileFromOffset() / this . MappedFileSize ))

    /**
     * Finds a mapped file by offset.
     *
     * @param offset Offset.
     * @param returnFirstOnNotFound If the mapped file is not found, then return the first one.
     * @return Mapped file or null (when not found and returnFirstOnNotFound is <code>false</code>).
     */
    public MappedFile findMappedFileByOffset(final long offset, final boolean returnFirstOnNotFound) {
        try {
            MappedFile firstMappedFile = this.getFirstMappedFile();
            MappedFile lastMappedFile = this.getLastMappedFile();
            if (firstMappedFile != null && lastMappedFile != null) {
                // 是否存在file
                if (offset < firstMappedFile.getFileFromOffset() || offset >= lastMappedFile.getFileFromOffset() + this.mappedFileSize) {
                    LOG_ERROR.warn("Offset not matched. Request offset: {}, firstOffset: {}, lastOffset: {}, mappedFileSize: {}, mappedFiles count: {}",
                        offset,
                        firstMappedFile.getFileFromOffset(),
                        lastMappedFile.getFileFromOffset() + this.mappedFileSize,
                        this.mappedFileSize,
                        this.mappedFiles.size());
                } else {
                    int index = (int) ((offset / this.mappedFileSize) - (firstMappedFile.getFileFromOffset() / this.mappedFileSize));
                    MappedFile targetFile = null;
                    try {
                        targetFile = this.mappedFiles.get(index);
                    } catch (Exception ignored) {
                    }
?
                    if (targetFile != null && offset >= targetFile.getFileFromOffset()
                        && offset < targetFile.getFileFromOffset() + this.mappedFileSize) {
                        return targetFile;
                    }
?
                    for (MappedFile tmpMappedFile : this.mappedFiles) {
                        if (offset >= tmpMappedFile.getFileFromOffset()
                            && offset < tmpMappedFile.getFileFromOffset() + this.mappedFileSize) {
                            return tmpMappedFile;
                        }
                    }
                }
?
                if (returnFirstOnNotFound) {
                    return firstMappedFile;
                }
            }
        } catch (Exception e) {
            log.error("findMappedFileByOffset Exception", e);
        }
        return null;
    }

(四) 獲取最后一個MappedFile

  public MappedFile getLastMappedFile(final long startOffset, boolean needCreate) {
        long createOffset = -1;
        MappedFile mappedFileLast = getLastMappedFile();
        // 是否為空
        if (mappedFileLast == null) {
            createOffset = startOffset - (startOffset % this.mappedFileSize);
        }
        // 不為空晾捏,但是已經(jīng)滿
        if (mappedFileLast != null && mappedFileLast.isFull()) {
            createOffset = mappedFileLast.getFileFromOffset() + this.mappedFileSize;
        }
        // 創(chuàng)建mappedfile
        if (createOffset != -1 && needCreate) {
            String nextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset);
            String nextNextFilePath = this.storePath + File.separator
                + UtilAll.offset2FileName(createOffset + this.mappedFileSize);
            MappedFile mappedFile = null;
            // 通過AllocateMappedFileService創(chuàng)建MappedFile,后面小節(jié)進行具體分析
            if (this.allocateMappedFileService != null) {
                mappedFile = this.allocateMappedFileService.putRequestAndReturnMappedFile(nextFilePath,
                    nextNextFilePath, this.mappedFileSize);
            } else {
                // 直接創(chuàng)建MappedFile
                try {
                    mappedFile = new MappedFile(nextFilePath, this.mappedFileSize);
                } catch (IOException e) {
                    log.error("create mappedFile exception", e);
                }
            }
?
            if (mappedFile != null) {
                if (this.mappedFiles.isEmpty()) {
                    mappedFile.setFirstCreateInQueue(true);
                }
                this.mappedFiles.add(mappedFile);
            }
            return mappedFile;
        }
        return mappedFileLast;
    }

三哀托、MappedFile

(一) MappedFile 核心屬性

MappedFile是RocketMQ 內(nèi)存映射文件的具體實現(xiàn)惦辛,相關(guān)屬性如下:

  • int OS_PAGE_SIZE :操作系統(tǒng)每頁大小,默認4k 仓手。

  • AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY : 當前JVM 實例中MappedFile虛擬內(nèi)存胖齐。

  • Atomiclnteger TOTAL_MAPPED_FILES :當前JVM 實例中MappedFile 對象個數(shù)。

  • Atomiclnteger wrotePosition : 當前該文件的寫指針嗽冒,從0 開始(內(nèi)存映射文件中的寫指針) 呀伙。

  • Atomiclnteger committedPosition :當前文件的提交指針,如果開啟transientStore PoolEnable添坊, 則數(shù)據(jù)會存儲在TransientStorePool 中剿另, 然后提交到內(nèi)存映射ByteBuffer 中, 再刷寫到磁盤贬蛙。

  • Atomiclnteger flushedPosition :刷寫到磁盤指針雨女,該指針之前的數(shù)據(jù)持久化到磁盤中。

  • int fileSize :文件大小阳准。

  • FileChannel fileChannel : 文件通道氛堕。

  • ByteBuffer writeBuffer :堆內(nèi)存ByteBuffer , 如果不為空野蝇,數(shù)據(jù)首先將存儲在該Buffer 中讼稚, 然后提交到MappedFile 對應的內(nèi)存映射文件Buffer 括儒。transientStorePoolEnable為true 時不為空。

  • TransientStorePool transientStorePool :堆內(nèi)存池锐想, transientStorePoolEnable 為true時啟用帮寻。

  • String fileName :文件名稱。

  • long fileFromOffset :該文件的初始偏移量痛倚。

  • File file :物理文件规婆。

  • MappedByteBuffer mappedByteBuffer :物理文件對應的內(nèi)存映射Buffer 。

  • volatile long storeTimestamp = 0 :文件最后一次內(nèi)容寫入時間蝉稳。

  • boolean firstCreatelnQueue :是否是MappedFileQueue 隊列中第一個文件抒蚜。

(二) MappedFile 初始化

    private void init(final String fileName, final int fileSize) throws IOException {
        this.fileName = fileName;
        this.fileSize = fileSize;
        this.file = new File(fileName);
        this.fileFromOffset = Long.parseLong(this.file.getName());
        boolean ok = false;
        // 確保目錄存在
        ensureDirOK(this.file.getParent());
        try {
            this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
            this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
            TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);
            TOTAL_MAPPED_FILES.incrementAndGet();
            ok = true;
        } catch (FileNotFoundException e) {
            log.error("create file channel " + this.fileName + " Failed. ", e);
            throw e;
        } catch (IOException e) {
            log.error("map file " + this.fileName + " Failed. ", e);
            throw e;
        } finally {
            if (!ok && this.fileChannel != null) {
                this.fileChannel.close();
            }
        }
    }

(三) MappedFile 提交

內(nèi)存映射文件的提交動作由MappedFile 的commit 方法實現(xiàn),具體實現(xiàn)如下:

    public int commit(final int commitLeastPages) {
        if (writeBuffer == null) {
            //no need to commit data to file channel, so just regard wrotePosition as committedPosition.
            return this.wrotePosition.get();
        }
        if (this.isAbleToCommit(commitLeastPages)) {
            if (this.hold()) {
                commit0(commitLeastPages);
                this.release();
            } else {
                log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get());
            }
        }
?
        // All dirty data has been committed to FileChannel.
        if (writeBuffer != null && this.transientStorePool != null && this.fileSize == this.committedPosition.get()) {
            this.transientStorePool.returnBuffer(writeBuffer);
            this.writeBuffer = null;
        }
?
        return this.committedPosition.get();
    }

commitLeastPages為本次最小提交頁數(shù)耘戚,如果提交頁數(shù)不滿足commitLeastPages嗡髓,則不執(zhí)行提交操作,writeBuffer 如果為空收津,直接返回wrotePosition 指針饿这,無須執(zhí)行commit操作, 表明commit 操作主體是writeBuffer撞秋。

最終調(diào)用commit0()方法长捧,實現(xiàn)真正的數(shù)據(jù)提交,具體實現(xiàn)如下:

    protected void commit0(final int commitLeastPages) {
        int writePos = this.wrotePosition.get();
        int lastCommittedPosition = this.committedPosition.get();
?
        if (writePos - this.committedPosition.get() > 0) {
            try {
                ByteBuffer byteBuffer = writeBuffer.slice();
                byteBuffer.position(lastCommittedPosition);
                byteBuffer.limit(writePos);
                this.fileChannel.position(lastCommittedPosition);
                this.fileChannel.write(byteBuffer);
                this.committedPosition.set(writePos);
            } catch (Throwable e) {
                log.error("Error occurred when commit data to FileChannel.", e);
            }
        }
    }

首先創(chuàng)建writeBuffer 的共享緩存區(qū)吻贿,然后將新創(chuàng)建的position 回退到上一次提交的位置( committedPosition ) 串结, 設置limit 為wrotePosition (當前最大有效數(shù)據(jù)指針),然后把commitedPosition 到wrotePosition 的數(shù)據(jù)復制(寫入)到FileChannel中舅列, 然后更新committedPosition 指針為wrotePosition.commit 的作用就是將MappedFile#writeBuffer中的數(shù)據(jù)提交到文件通道FileChannel 中肌割。

ByteBuffer 使用技巧: slice () 方法創(chuàng)建一個共享緩存區(qū), 與原先的ByteBuffer 共享內(nèi)存但維護一套獨立的指針( position 帐要、mark 把敞、limit) 。

(四) MappedFile 刷新

MappedFile調(diào)用flush方法榨惠,將內(nèi)存中的數(shù)據(jù)刷新到磁盤中奋早,具體實現(xiàn)如下:

    /**
     * @return The current flushed position
     */
    public int flush(final int flushLeastPages) {
        if (this.isAbleToFlush(flushLeastPages)) {
            if (this.hold()) {
                int value = getReadPosition();
?
                try {
                    //We only append data to fileChannel or mappedByteBuffer, never both.
                    if (writeBuffer != null || this.fileChannel.position() != 0) {
                        this.fileChannel.force(false);
                    } else {
                        this.mappedByteBuffer.force();
                    }
                } catch (Throwable e) {
                    log.error("Error occurred when force data to disk.", e);
                }
?
                this.flushedPosition.set(value);
                this.release();
            } else {
                log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());
                this.flushedPosition.set(getReadPosition());
            }
        }
        return this.getFlushedPosition();
    }

刷寫磁盤,直接調(diào)用mappedByteBuffer 或fileChannel 的force 方法將內(nèi)存中的數(shù)據(jù)持久化到磁盤赠橙,那么flushedPosition 應該等于MappedByteBuffer 中的寫指針伸蚯;

如果writeBuffer不為空, 則flushedPosition 應等于上一次commit 指針简烤;因為上一次提交的數(shù)據(jù)就是進入到MappedByteBuffer 中的數(shù)據(jù);

如果writeBuffer 為空摇幻,數(shù)據(jù)是直接進入到MappedByteBuffer的wrotePosition 代表的是MappedByteBuffer 中的指針横侦,故設置flushedPosition 為wrotePosition 挥萌。

(五) 獲取MappedFile 最大讀指針

RocketMQ 文件的一個組織方式是內(nèi)存映射文件,預先申請一塊連續(xù)的固定大小的內(nèi)存枉侧, 需要一套指針標識當前最大有效數(shù)據(jù)的位置引瀑,獲取最大有效數(shù)據(jù)偏移量的方法由MappedFile 的getReadPosition 方法實現(xiàn)

    /**
     * @return The max position which have valid data
     */
    public int getReadPosition() {
        return this.writeBuffer == null ? this.wrotePosition.get() : this.committedPosition.get();
    }

獲取當前文件最大的可讀指針。如果writeBuffer 為空榨馁, 則直接返回當前的寫指針憨栽;如果writeBuffer 不為空, 則返回上一次提交的指針翼虫。在MappedFile 設計中屑柔,只有提交了的數(shù)據(jù)(寫入到MappedByteBuffer 或FileChannel 中的數(shù)據(jù))才是安全的數(shù)據(jù)。

四珍剑、 AllocateMappedFileService

(一) AllocateMappedFileService核心屬性

  • ConcurrentMap<String, AllocateRequest> requestTable:key是filepath掸宛,value是分配請求

  • PriorityBlockingQueue<AllocateRequest> requestQueue:分配請求的 隊列,注意是優(yōu)先級隊列

  • int waitTimeOut:生成對應請求到創(chuàng)建MappedFile,可以等待5s

(二) MappedFile創(chuàng)建

通過調(diào)用putRequestAndReturnMappedFile方法招拙,往隊列里面添加創(chuàng)建MappedFile的請求

    public MappedFile putRequestAndReturnMappedFile(String nextFilePath, String nextNextFilePath, int fileSize) {
        // 處理兩個分配MappedFile請求
        int canSubmitRequests = 2;
        if (this.messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
            if (this.messageStore.getMessageStoreConfig().isFastFailIfNoBufferInStorePool()
                && BrokerRole.SLAVE != this.messageStore.getMessageStoreConfig().getBrokerRole()) { //if broker is slave, don't fast fail even no buffer in pool
                canSubmitRequests = this.messageStore.getTransientStorePool().remainBufferNumbs() - this.requestQueue.size();
            }
        }
        
        AllocateRequest nextReq = new AllocateRequest(nextFilePath, fileSize);
        boolean nextPutOK = this.requestTable.putIfAbsent(nextFilePath, nextReq) == null;
?
        if (nextPutOK) {
            if (canSubmitRequests <= 0) {
                log.warn("[NOTIFYME]TransientStorePool is not enough, so create mapped file error, " +
                    "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.getTransientStorePool().remainBufferNumbs());
                this.requestTable.remove(nextFilePath);
                return null;
            }
            boolean offerOK = this.requestQueue.offer(nextReq);
            if (!offerOK) {
                log.warn("never expected here, add a request to preallocate queue failed");
            }
            canSubmitRequests--;
        }
?
        AllocateRequest nextNextReq = new AllocateRequest(nextNextFilePath, fileSize);
        boolean nextNextPutOK = this.requestTable.putIfAbsent(nextNextFilePath, nextNextReq) == null;
        if (nextNextPutOK) {
            if (canSubmitRequests <= 0) {
                log.warn("[NOTIFYME]TransientStorePool is not enough, so skip preallocate mapped file, " +
                    "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.getTransientStorePool().remainBufferNumbs());
                this.requestTable.remove(nextNextFilePath);
            } else {
                boolean offerOK = this.requestQueue.offer(nextNextReq);
                if (!offerOK) {
                    log.warn("never expected here, add a request to preallocate queue failed");
                }
            }
        }
?
        if (hasException) {
            log.warn(this.getServiceName() + " service has exception. so return null");
            return null;
        }
?
        AllocateRequest result = this.requestTable.get(nextFilePath);
        try {
            if (result != null) {
                boolean waitOK = result.getCountDownLatch().await(waitTimeOut, TimeUnit.MILLISECONDS);
                if (!waitOK) {
                    log.warn("create mmap timeout " + result.getFilePath() + " " + result.getFileSize());
                    return null;
                } else {
                    this.requestTable.remove(nextFilePath);
                    return result.getMappedFile();
                }
            } else {
                log.error("find preallocate mmap failed, this never happen");
            }
        } catch (InterruptedException e) {
            log.warn(this.getServiceName() + " service has exception. ", e);
        }
?
        return null;
    }

異步通過調(diào)用mmapOperation方法唧瘾,異步創(chuàng)建MappedFile

    /**
     * Only interrupted by the external thread, will return false
     */
    private boolean mmapOperation() {
        boolean isSuccess = false;
        AllocateRequest req = null;
        try {
            req = this.requestQueue.take();
            AllocateRequest expectedRequest = this.requestTable.get(req.getFilePath());
            if (null == expectedRequest) {
                log.warn("this mmap request expired, maybe cause timeout " + req.getFilePath() + " "
                    + req.getFileSize());
                return true;
            }
            if (expectedRequest != req) {
                log.warn("never expected here,  maybe cause timeout " + req.getFilePath() + " "
                    + req.getFileSize() + ", req:" + req + ", expectedRequest:" + expectedRequest);
                return true;
            }
?
            if (req.getMappedFile() == null) {
                long beginTime = System.currentTimeMillis();
?
                MappedFile mappedFile;
                if (messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
                    try {
                        mappedFile = ServiceLoader.load(MappedFile.class).iterator().next();
                        mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
                    } catch (RuntimeException e) {
                        log.warn("Use default implementation.");
                        mappedFile = new MappedFile(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
                    }
                } else {
                    mappedFile = new MappedFile(req.getFilePath(), req.getFileSize());
                }
?
                long eclipseTime = UtilAll.computeEclipseTimeMilliseconds(beginTime);
                if (eclipseTime > 10) {
                    int queueSize = this.requestQueue.size();
                    log.warn("create mappedFile spent time(ms) " + eclipseTime + " queue size " + queueSize
                        + " " + req.getFilePath() + " " + req.getFileSize());
                }
?
                // pre write mappedFile
                if (mappedFile.getFileSize() >= this.messageStore.getMessageStoreConfig()
                    .getMapedFileSizeCommitLog()
                    &&
                    this.messageStore.getMessageStoreConfig().isWarmMapedFileEnable()) {
                    mappedFile.warmMappedFile(this.messageStore.getMessageStoreConfig().getFlushDiskType(),
                        this.messageStore.getMessageStoreConfig().getFlushLeastPagesWhenWarmMapedFile());
                }
?
                req.setMappedFile(mappedFile);
                this.hasException = false;
                isSuccess = true;
            }
        } catch (InterruptedException e) {
            log.warn(this.getServiceName() + " interrupted, possibly by shutdown.");
            this.hasException = true;
            return false;
        } catch (IOException e) {
            log.warn(this.getServiceName() + " service has exception. ", e);
            this.hasException = true;
            if (null != req) {
                requestQueue.offer(req);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException ignored) {
                }
            }
        } finally {
            if (req != null && isSuccess)
                req.getCountDownLatch().countDown();
        }
        return true;
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市别凤,隨后出現(xiàn)的幾起案子饰序,更是在濱河造成了極大的恐慌,老刑警劉巖规哪,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件求豫,死亡現(xiàn)場離奇詭異,居然都是意外死亡由缆,警方通過查閱死者的電腦和手機注祖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來均唉,“玉大人是晨,你說我怎么就攤上這事√蚣” “怎么了罩缴?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長层扶。 經(jīng)常有香客問我箫章,道長,這世上最難降的妖魔是什么镜会? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任檬寂,我火速辦了婚禮,結(jié)果婚禮上戳表,老公的妹妹穿的比我還像新娘桶至。我一直安慰自己昼伴,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布镣屹。 她就那樣靜靜地躺著圃郊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪女蜈。 梳的紋絲不亂的頭發(fā)上持舆,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音伪窖,去河邊找鬼逸寓。 笑死,一個胖子當著我的面吹牛惰许,可吹牛的內(nèi)容都是我干的席覆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼汹买,長吁一口氣:“原來是場噩夢啊……” “哼佩伤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晦毙,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤生巡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后见妒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孤荣,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年须揣,在試婚紗的時候發(fā)現(xiàn)自己被綠了盐股。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡耻卡,死狀恐怖疯汁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卵酪,我是刑警寧澤幌蚊,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站溃卡,受9級特大地震影響溢豆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘸羡,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一漩仙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦讯赏、人聲如沸垮兑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雀哨,卻和暖如春磕谅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雾棺。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工膊夹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捌浩。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓放刨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親尸饺。 傳聞我的和親對象是個殘疾皇子进统,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 通道(Channel)是java.nio的第二個主要創(chuàng)新。它們既不是一個擴展也不是一項增強浪听,而是全新螟碎、極好的Jav...
    橋頭放牛娃閱讀 3,040評論 0 9
  • 0.前言 RMQ對于消息持久化的方式是順序?qū)懙奖镜卮疟P文件,相對于持久化到遠程的數(shù)據(jù)庫或者KV來說迹栓,往本地磁盤文件...
    lambdacalculus閱讀 2,625評論 1 6
  • 1 概述2 MappedFileQueue掉分、MappedFile類介紹3 MappedFile的獲取4 Trans...
    persisting_閱讀 3,154評論 0 4
  • 4.8.2.Broker異步刷盤 異步刷盤根據(jù)是否開啟transientStorePoolEnable機制酥郭,刷盤實...
    AKyS佐毅閱讀 955評論 0 1
  • 在許多公公婆婆還在挑剔媳婦生男生女的時候祠乃,有些人卻辛苦備孕也總是懷不上灾茁。 南希和老公結(jié)婚的時候剛剛博士畢業(yè)校套,也就二...
    散月的月閱讀 619評論 6 4