前言 Zookeeper的通过快照日志和事务日志将内存信息保存下来,记录下来每次请求的具体信息。 尤其是其事务日志,每次处理事务请求时都需要将其记录下来。 Zookeeper事务日志的默认存储方式是磁盘文件,那么Zookeeper的总体性能就受限与磁盘文件的写入速度。 针对这个瓶颈,Zookeeper做了什么优化操作呢,本文我们就一起来了解下。1。事务日志的预分配 事务日志的添加,我们需要从FileTxnLog。append()方法看起publicclassFileTxnLogimplementsTxnLog{volatileBufferedOutputStreamlogSvolatileOutputAvolatileFileOutputS 追加事务日志publicsynchronizedbooleanappend(TxnHeaderhdr,Recordtxn)throwsIOException{if(hdrnull){} if(hdr。getZxid()lastZxidSeen){LOG。warn(Currentzxidhdr。getZxid()islastZxidSeenforhdr。getType());}else{lastZxidSeenhdr。getZxid();} 默认logStream为空if(logStreamnull){if(LOG。isInfoEnabled()){LOG。info(Creatingnewlogfile:Util。makeLogName(hdr。getZxid()));} 以下代码为创建事务日志文件根据当前事务ID来创建具体文件名,并写入文件头信息logFileWritenewFile(logDir,Util。makeLogName(hdr。getZxid()));fosnewFileOutputStream(logFileWrite);logStreamnewBufferedOutputStream(fos);oaBinaryOutputArchive。getArchive(logStream);FileHeaderfhdrnewFileHeader(TXNLOGMAGIC,VERSION,dbId);fhdr。serialize(oa,fileheader);Makesurethatthemagicnumberiswrittenbeforepadding。logStream。flush();filePadding。setCurrentSize(fos。getChannel()。position());streamsToFlush。add(fos);}预分配代码在这里filePadding。padFile(fos。getChannel());byte〔〕bufUtil。marshallTxnEntry(hdr,txn);if(bufnullbuf。length0){thrownewIOException(Faultyserializationforheaderandtxn);}ChecksumcrcmakeChecksumAlgorithm();crc。update(buf,0,buf。length);oa。writeLong(crc。getValue(),txnEntryCRC);Util。writeTxnBytes(oa,buf); }} 创建FileTxnLog对象时,其logStream属性为null,所以当第一次处理事务请求时,会先根据当前事务ID来创建一个文件。1。1事务日志预分配publicclassFilePadding{longpadFile(FileChannelfileChannel)throwsIOException{针对新文件而言,newFileSize64MlongnewFileSizecalculateFileSizeWithPadding(fileChannel。position(),currentSize,preAllocSize);if(currentSize!newFileSize){将文件扩充到64M,全部用0来填充fileChannel。write((ByteBuffer)fill。position(0),newFileSizefill。remaining());currentSizenewFileS}returncurrentS} size计算publicstaticlongcalculateFileSizeWithPadding(longposition,longfileSize,longpreAllocSize){IfpreAllocSizeispositiveandwearewithin4KBoftheknownendofthefilecalculateanewfilesize初始时候position0,fileSize为0,preAllocSize为系统参数执行,默认为64Mif(preAllocSize0position4096fileSize){IfwehavewrittenmorethanwehavepreviouslypreallocatedweneedtomakesurethenewfilesizeislargerthanwhatwealreadyhaveQ:这里确实没看懂。。。if(positionfileSize){fileSizepositionpreAllocSfileSizefileSizepreAllocS}else{fileSizepreAllocS}} returnfileS}} 预分配的过程比较简单,就是看下当前文件的剩余空间是否4096,如果是,则扩容。 Q: 这里有一个不太明白的问题,positionfileSize的场景是怎样的呢?2。创建新的事务日志文件时机 通过上述代码分析我们知道,当logStreamnull时,就会创建一个新的事务日志文件,那么logStream对象什么时候为空呢? 搜索代码,只看到FileTxnLog。rollLog()方法会主动将logStream设置为nullpublicclassFileTxnLogimplementsTxnLog{publicsynchronizedvoidrollLog()throwsIOException{if(logStream!null){this。logStream。flush();this。logS}}} 那么根据这个线索,我们来搜索下rollLog的调用链 SyncRequestProcessor。run()ZKDatabase。rollLog()FileTxnSnapLog。rollLog()FileTxnLog。rollLog() 最终看到是在SyncRequestProcessor。run()方法中发起调用的,而且只有这一条调用链,我们来分析下2。1SyncRequestProcessor。run()publicclassSyncRequestProcessorextendsZooKeeperCriticalThreadimplementsRequestProcessor{publicvoidrun(){try{intlogCount0; setRandRoll(r。nextInt(snapCount2));while(true){。。。if(si!null){追加事务日志if(zks。getZKDatabase()。append(si)){logCif(logCount(snapCount2randRoll)){setRandRoll(r。nextInt(snapCount2));注意:在这里发起了rollLogzks。getZKDatabase()。rollLog();。。。}}elseif(toFlush。isEmpty()){。。。}toFlush。add(si);if(toFlush。size()1000){flush(toFlush);}}}}catch(Throwablet){handleException(this。getName(),t);}LOG。info(SyncRequestProcessorexited!);}} 需要注意下rollLog()方法执行的条件,就是logCount(snapCount2randRoll) snapCount是一个系统参数,System。getProperty(zookeeper。snapCount),默认值为100000 randRoll是一个随机值 那么该条件触发的时机为:处理的事务请求数至少要大于50000。 这时就出现了一个笔者无法理解的情况: 通过对事务日志的观察可以看到其都是64M,而至少处理50000次事务请求后,Zookeeper才会分配一个新的事务日志文件,那么这个snapCount是一个经验值嘛? 如果事务请求的value信息都很大,那么可能到不了50000次,就会超过64M,理论上应该要创建一个新的文件了,但是貌似并没有,这个该怎么处理呢? 如果事务请求value信息都很小,那么即使到了50000次,也不会超过64M,那么之前预分配的文件大小就浪费了一部分。总结 以上为个人经验,希望能给大家一个参考,也希望大家多多支持。