Zookeeper事务日志预分配空间解析
admin
2024-03-14 23:57:10
0

前言:

Zookeeper的通过快照日志和事务日志将内存信息保存下来,记录下来每次请求的具体信息。

尤其是其事务日志,每次处理事务请求时都需要将其记录下来。

Zookeeper事务日志的默认存储方式是磁盘文件,那么Zookeeper的总体性能就受限与磁盘文件的写入速度。

针对这个瓶颈,Zookeeper做了什么优化操作呢,本文我们就一起来了解下。

1.事务日志的预分配

事务日志的添加,我们需要从FileTxnLog.append()方法看起

public class FileTxnLog implements TxnLog {volatile BufferedOutputStream logStream = null;volatile OutputArchive oa;volatile FileOutputStream fos = null;// 追加事务日志public synchronized boolean append(TxnHeader hdr, Record txn)throws IOException{if (hdr == null) {return false;}if (hdr.getZxid() <= lastZxidSeen) {LOG.warn("Current zxid " + hdr.getZxid()+ " is <= " + lastZxidSeen + " for "+ hdr.getType());} else {lastZxidSeen = hdr.getZxid();}// 默认logStream为空if (logStream==null) {if(LOG.isInfoEnabled()){LOG.info("Creating new log file: " + Util.makeLogName(hdr.getZxid()));}// 以下代码为创建事务日志文件// 根据当前事务ID来创建具体文件名,并写入文件头信息logFileWrite = new File(logDir, Util.makeLogName(hdr.getZxid()));fos = new FileOutputStream(logFileWrite);logStream=new BufferedOutputStream(fos);oa = BinaryOutputArchive.getArchive(logStream);FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId);fhdr.serialize(oa, "fileheader");// Make sure that the magic number is written before padding.logStream.flush();filePadding.setCurrentSize(fos.getChannel().position());streamsToFlush.add(fos);}// 预分配代码在这里filePadding.padFile(fos.getChannel());byte[] buf = Util.marshallTxnEntry(hdr, txn);if (buf == null || buf.length == 0) {throw new IOException("Faulty serialization for header " +"and txn");}Checksum crc = makeChecksumAlgorithm();crc.update(buf, 0, buf.length);oa.writeLong(crc.getValue(), "txnEntryCRC");Util.writeTxnBytes(oa, buf);return true;}
}

创建FileTxnLog对象时,其logStream属性为null,所以当第一次处理事务请求时,会先根据当前事务ID来创建一个文件。

1.1 事务日志预分配

public class FilePadding {long padFile(FileChannel fileChannel) throws IOException {// 针对新文件而言,newFileSize=64Mlong newFileSize = calculateFileSizeWithPadding(fileChannel.position(), currentSize, preAllocSize);if (currentSize != newFileSize) {// 将文件扩充到64M,全部用0来填充fileChannel.write((ByteBuffer) fill.position(0), newFileSize - fill.remaining());currentSize = newFileSize;}return currentSize;}// size计算public static long calculateFileSizeWithPadding(long position, long fileSize, long preAllocSize) {// If preAllocSize is positive and we are within 4KB of the known end of the file calculate a new file size// 初始时候position=0,fileSize为0,preAllocSize为系统参数执行,默认为64Mif (preAllocSize > 0 && position + 4096 >= fileSize) {// If we have written more than we have previously preallocated we need to make sure the new// file size is larger than what we already have// Q:这里确实没看懂...if (position > fileSize) {fileSize = position + preAllocSize;fileSize -= fileSize % preAllocSize;} else {fileSize += preAllocSize;}}return fileSize;}
}

预分配的过程比较简单,就是看下当前文件的剩余空间是否<4096,如果是,则扩容。

Q:

这里有一个不太明白的问题,position > fileSize的场景是怎样的呢?

2.创建新的事务日志文件时机

通过上述代码分析我们知道,当logStream=null时,就会创建一个新的事务日志文件,那么logStream对象什么时候为空呢?

搜索代码,只看到FileTxnLog.rollLog()方法会主动将logStream设置为null

public class FileTxnLog implements TxnLog {public synchronized void rollLog() throws IOException {if (logStream != null) {this.logStream.flush();this.logStream = null;oa = null;}}
}

那么根据这个线索,我们来搜索下rollLog的调用链

SyncRequestProcessor.run() -> ZKDatabase.rollLog() -> FileTxnSnapLog.rollLog() -> FileTxnLog.rollLog()

最终看到是在SyncRequestProcessor.run()方法中发起调用的,而且只有这一条调用链,我们来分析下

2.1 SyncRequestProcessor.run()

public class SyncRequestProcessor extends ZooKeeperCriticalThread implements RequestProcessor {public void run() {try {int logCount = 0;setRandRoll(r.nextInt(snapCount/2));while (true) {...if (si != null) {// 追加事务日志if (zks.getZKDatabase().append(si)) {logCount++;if (logCount > (snapCount / 2 + randRoll)) {setRandRoll(r.nextInt(snapCount/2));// 注意:在这里发起了rollLogzks.getZKDatabase().rollLog();...}} else if (toFlush.isEmpty()) {...}toFlush.add(si);if (toFlush.size() > 1000) {flush(toFlush);}}}} catch (Throwable t) {handleException(this.getName(), t);running = false;}LOG.info("SyncRequestProcessor exited!");}
}

需要注意下rollLog()方法执行的条件,就是logCount > (snapCount / 2 + randRoll)

snapCount是一个系统参数,System.getProperty("zookeeper.snapCount"),默认值为100000

randRoll是一个随机值

那么该条件触发的时机为:处理的事务请求数至少要大于50000。

这时就出现了一个笔者无法理解的情况:

通过对事务日志的观察可以看到其都是64M,而至少处理50000次事务请求后,Zookeeper才会分配一个新的事务日志文件,那么这个snapCount是一个经验值嘛?

如果事务请求的value信息都很大,那么可能到不了50000次,就会超过64M,理论上应该要创建一个新的文件了,但是貌似并没有,这个该怎么处理呢?

如果事务请求value信息都很小,那么即使到了50000次,也不会超过64M,那么之前预分配的文件大小就浪费了一部分。

总结:

希望有比较懂的小伙伴给点意见,感谢!

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...