介绍
在RocketMQ中,这些服务主要负责消息的持久化和相关维护工作,以确保消息存储系统的可靠性、一致性和高效性。以下是对这几个服务的简要解释:
-
FlushCommitLogService:
这个服务负责定期或按需将内存中的CommitLog(消息主日志)刷盘到磁盘,保证消息的持久化。通过定时任务或触发机制来执行刷盘操作,防止因系统崩溃导致内存中的数据丢失。 -
CommitRealTimeService:
它致力于实现消息实时提交到CommitLog并尽可能减少消息丢失的风险。但在新版RocketMQ中,这部分功能已经整合到其他模块或者进行了优化。 -
GroupCommitService:
这个服务是针对批量消息进行刷盘的优化策略,它将多个待刷盘的消息请求收集起来,然后一次性批量地将这批消息写入磁盘,以此提高磁盘I/O效率并降低延迟。 -
FlushRealTimeService:
同样,在RocketMQ较早版本中可能存在此服务,用于处理实时刷盘需求,即当消息到达时尽快将其刷。 -
GroupCheckService:
这个服务可能是对Consumer消费位点或者Broker内部状态的一种检查服务,例如检查某个消费组的消费进度是否正常,或者检测队列是否有积压消息等,以便于维护整个消息队列系统的健康状况。然而,在给出的代码片段中并未提及这个服务,因此这里对其具体功能的描述是基于一般理解推测的,实际的功能可能会有所不同,需要根据RocketMQ的具体版本及源码分析确定。
FlushCommitLogService
FlushCommitLogService
是RocketMQ中用于管理消息刷盘的服务。在RocketMQ的Broker端,接收到生产者发送的消息后,会先将消息存储到内存中的CommitLog(主日志文件)。为了确保数据的持久化和可靠性,需要定期或按需将内存中的消息数据刷写到磁盘。
FlushCommitLogService
通常采用定时任务的方式工作,负责监控并触发CommitLog的刷盘操作。具体实现上,服务可能包含如下逻辑:
- 定时检查:根据预设的时间间隔或者消息数量阈值,判断是否应该进行刷盘操作。
- 刷盘处理:当满足刷盘条件时,调用底层存储系统API将CommitLog中的部分或全部数据同步到磁盘,保证即使在系统崩溃的情况下,这部分消息也不会丢失。
- 错误处理与重试:如果在刷盘过程中遇到错误,比如IO异常等,服务应当有能力进行错误处理并尝试重新刷盘。
class FlushRealTimeService extends FlushCommitLogService {
private long lastFlushTimestamp = 0;
private long printTimes = 0;
@Override
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();
int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();
int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();
int flushPhysicQueueThoroughInterval =
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();
boolean printFlushProgress = false;
// Print flush progress
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {
this.lastFlushTimestamp = currentTimeMillis;
flushPhysicQueueLeastPages = 0;
printFlushProgress = (printTimes++ % 10) == 0;
}
try {
if (flushCommitLogTimed) {
Thread.sleep(interval);
} else {
this.waitForRunning(interval);
}
if (printFlushProgress) {
this.printFlushProgress();
}
long begin = System.currentTimeMillis();
CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
long past = System.currentTimeMillis() - begin;
CommitLog.this.getMessageStore().getPerfCounter().flowOnce("FLUSH_DATA_TIME_MS", (int) past);
if (past > 500) {
log.info("Flush data to disk costs {} ms", past);
}
} catch (Throwable e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
this.printFlushProgress();
}
}
// Normal shutdown, to ensure that all the flush before exit
boolean result = false;
for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
result = CommitLog.this.mappedFileQueue.flush(0);
CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
}
this.printFlushProgress();
CommitLog.log.info(this.getServiceName() + " service end");
}
@Override
public String getServiceName() {
if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) {
return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + FlushRealTimeService.class.getSimpleName();
}
return FlushRealTimeService.class.getSimpleName();
}
private void printFlushProgress() {
// CommitLog.log.info("how much disk fall behind memory, "
// + CommitLog.this.mappedFileQueue.howMuchFallBehind());
}
@Override
public long getJoinTime() {
return 1000 * 60 * 5;
}
}
FlushRealTimeService
类是 RocketMQ 中用于实时刷盘的组件,它继承自 FlushCommitLogService
。主要任务是在RocketMQ Broker端根据配置定时地将内存中的消息持久化到磁盘。
-
成员变量:
lastFlushTimestamp
:记录最后一次完整刷盘操作的时间戳。printTimes
:用于控制打印刷盘进度信息的频率计数器。
-
run() 方法:
- 在服务运行期间,会检查是否开启了定时刷盘(
flushCommitLogTimed
)以及相关的刷盘间隔时间、至少需要刷盘的物理队列页数和彻底刷盘间隔等参数。 - 根据系统当前时间和上一次完整刷盘的时间戳判断是否需要打印刷盘进度,并决定是否执行全面刷盘(设置
flushPhysicQueueLeastPages
为0)。 - 如果配置为定时刷盘,则线程休眠指定的间隔时间;否则调用
waitForRunning()
等待一段时间。 - 执行刷盘操作,调用
mappedFileQueue.flush(flushPhysicQueueLeastPages)
,并更新存储时间戳和检查点。 - 记录并统计刷盘耗时,并在耗时超过500ms时输出日志。
- 当服务正常关闭时,确保所有数据都已刷盘后退出。
- 在服务运行期间,会检查是否开启了定时刷盘(
-
getServiceName() 方法:
返回服务名称,如果Broker运行在容器中则返回带有Broker标识符的服务名称。 -
printFlushProgress() 方法:
虽然方法体为空,但注释表示这里应该输出磁盘落后于内存的消息数量。 -
getJoinTime() 方法:
返回服务启动后多久可以接受新的刷盘请求,默认为5分钟。
综上所述,FlushRealTimeService
类的核心作用是保证消息系统的稳定性和可靠性,通过定时或者按需将内存中的消息及时持久化到磁盘,避免因系统异常导致的数据丢失。同时,该类还提供了一些监控和调试功能,如打印刷盘进度和统计刷盘耗时等。
CommitRealTimeService
class FlushRealTimeService extends FlushCommitLogService {
private long lastFlushTimestamp = 0;
private long printTimes = 0;
@Override
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();
int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();
int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();
int flushPhysicQueueThoroughInterval =
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();
boolean printFlushProgress = false;
// Print flush progress
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {
this.lastFlushTimestamp = currentTimeMillis;
flushPhysicQueueLeastPages = 0;
printFlushProgress = (printTimes++ % 10) == 0;
}
try {
if (flushCommitLogTimed) {
Thread.sleep(interval);
} else {
this.waitForRunning(interval);
}
if (printFlushProgress) {
this.printFlushProgress();
}
long begin = System.currentTimeMillis();
CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
long past = System.currentTimeMillis() - begin;
CommitLog.this.getMessageStore().getPerfCounter().flowOnce("FLUSH_DATA_TIME_MS", (int) past);
if (past > 500) {
log.info("Flush data to disk costs {} ms", past);
}
} catch (Throwable e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
this.printFlushProgress();
}
}
// Normal shutdown, to ensure that all the flush before exit
boolean result = false;
for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
result = CommitLog.this.mappedFileQueue.flush(0);
CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
}
this.printFlushProgress();
CommitLog.log.info(this.getServiceName() + " service end");
}
@Override
public String getServiceName() {
if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) {
return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + FlushRealTimeService.class.getSimpleName();
}
return FlushRealTimeService.class.getSimpleName();
}
private void printFlushProgress() {
// CommitLog.log.info("how much disk fall behind memory, "
// + CommitLog.this.mappedFileQueue.howMuchFallBehind());
}
@Override
public long getJoinTime() {
return 1000 * 60 * 5;
}
}
FlushRealTimeService
类是RocketMQ中负责实时刷盘的服务,它继承自 FlushCommitLogService
。这个服务的主要任务是在指定的时间间隔内将内存中的消息持久化到磁盘,并监控和报告刷盘进度。
-
成员变量:
lastFlushTimestamp
:记录上次执行完整刷盘操作的时间戳。printTimes
:用于控制打印刷盘进度的频率。
-
run() 方法:
服务的主运行循环,在未停止的情况下定期进行以下操作:- 根据配置决定是否定时刷盘(
flushCommitLogTimed
)以及设置刷盘间隔时间(interval
)、至少需要刷盘的物理队列页数(flushPhysicQueueLeastPages
)和彻底刷盘间隔(flushPhysicQueueThoroughInterval
)。 - 控制何时打印刷盘进度信息。
- 执行实际的刷盘操作,调用
mappedFileQueue.flush()
方法,并记录当前存储时间和更新检查点。 - 计算并统计刷盘耗时,并在耗时超过500ms时输出日志。
- 当服务正常关闭时,确保所有数据都已刷盘后退出。
- 根据配置决定是否定时刷盘(
-
getServiceName() 方法:
返回服务名称,根据Broker配置决定是否包含Broker标识符。 -
printFlushProgress() 方法:
虽然在代码中注释掉了具体的打印逻辑,但理论上该方法应该用于输出当前刷盘的进度信息。 -
getJoinTime() 方法:
返回服务启动后多久可以接受新的刷盘请求,默认为5分钟。
总之,FlushRealTimeService
类在RocketMQ中扮演着关键角色,通过定期或按需触发刷盘操作,保证了消息存储系统的可靠性与稳定性。
GroupCommitService
GroupCommitService
在RocketMQ中是一个专门针对批量提交消息到磁盘的服务。当Broker接收到生产者发送的消息后,这些消息首先会被写入内存中的CommitLog(主日志文件)。为了提高刷盘效率和降低磁盘I/O压力,RocketMQ引入了批量刷盘机制。
GroupCommitService
的工作原理如下:
-
收集请求:每当有新的消息需要刷盘时,系统会将这个刷盘请求添加到一个队列(如LinkedList)中,而不是立即执行刷盘操作。
-
批量处理:服务定期或者在达到一定数量的刷盘请求后,一次性将队列中的所有刷盘请求进行批量处理。
-
刷盘逻辑:在批量处理阶段,服务会检查每个待刷盘的消息是否满足刷盘条件(例如已成功写入内存,并且当前刷盘位置已达到该消息在CommitLog中的偏移量),如果不满足则继续尝试刷盘直到满足条件或超时。
-
优化性能:通过这种批量处理的方式,可以减少磁盘IO次数,从而提升整体系统的吞吐量和响应速度。
-
同步与异步:同时,根据配置和实际场景需求,RocketMQ支持同步和异步两种刷盘模式,以平衡数据持久性和性能之间的关系。
class GroupCommitService extends FlushCommitLogService {
private volatile LinkedList<GroupCommitRequest> requestsWrite = new LinkedList<>();
private volatile LinkedList<GroupCommitRequest> requestsRead = new LinkedList<>();
private final PutMessageSpinLock lock = new PutMessageSpinLock();
public void putRequest(final GroupCommitRequest request) {
lock.lock();
try {
this.requestsWrite.add(request);
} finally {
lock.unlock();
}
this.wakeup();
}
private void swapRequests() {
lock.lock();
try {
LinkedList<GroupCommitRequest> tmp = this.requestsWrite;
this.requestsWrite = this.requestsRead;
this.requestsRead = tmp;
} finally {
lock.unlock();
}
}
private void doCommit() {
if (!this.requestsRead.isEmpty()) {
for (GroupCommitRequest req : this.requestsRead) {
boolean flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
for (int i = 0; i < 1000 && !flushOK; i++) {
CommitLog.this.mappedFileQueue.flush(0);
flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
if (flushOK) {
break;
} else {
// When transientStorePoolEnable is true, the messages in writeBuffer may not be committed
// to pageCache very quickly, and flushOk here may almost be false, so we can sleep 1ms to
// wait for the messages to be committed to pageCache.
try {
Thread.sleep(1);
} catch (InterruptedException ignored) {
}
}
}
req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
this.requestsRead = new LinkedList<>();
} else {
// Because of individual messages is set to not sync flush, it
// will come to this process
CommitLog.this.mappedFileQueue.flush(0);
}
}
@Override
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
this.waitForRunning(10);
this.doCommit();
} catch (Exception e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
}
}
// Under normal circumstances shutdown, wait for the arrival of the
// request, and then flush
try {
Thread.sleep(10);
} catch (InterruptedException e) {
CommitLog.log.warn("GroupCommitService Exception, ", e);
}
this.swapRequests();
this.doCommit();
CommitLog.log.info(this.getServiceName() + " service end");
}
@Override
protected void onWaitEnd() {
this.swapRequests();
}
@Override
public String getServiceName() {
if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) {
return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + GroupCommitService.class.getSimpleName();
}
return GroupCommitService.class.getSimpleName();
}
@Override
public long getJoinTime() {
return 1000 * 60 * 5;
}
}
GroupCommitService
是RocketMQ中负责批量处理消息刷盘的服务,其目的是为了提高磁盘I/O效率。下面是对这个类的详细解释:
-
成员变量:
requestsWrite
: 用于存放待写入磁盘的消息请求的链表。requestsRead
: 用于存放当前批次正在读取和处理的消息请求的链表。lock
: 使用自旋锁来保证在多线程环境下添加、交换和处理消息请求时的线程安全。
-
putRequest(GroupCommitRequest request) 方法:
将新的消息刷盘请求添加到requestsWrite
链表,并唤醒等待队列中的工作线程开始处理。 -
swapRequests() 方法:
在需要处理下一批次请求时,将requestsWrite
和requestsRead
链表进行交换,使得工作线程从requestsRead
中获取并处理上一轮放入的刷盘请求。 -
doCommit() 方法:
核心方法,负责执行实际的消息刷盘操作。遍历requestsRead
中的所有刷盘请求,逐个检查并尝试刷盘,如果刷盘成功,则更新相应的状态并唤醒客户端;若刷盘失败则重试直到达到最大次数(这里设定为1000次),期间还会短暂休眠以等待消息被提交到pageCache。当所有请求处理完毕后,清空requestsRead
并记录最新的存储时间戳。 -
run() 方法:
服务的主要运行循环,在未停止的情况下定期检查是否有待处理的刷盘请求,然后调用doCommit()
进行批量刷盘。在正常关闭服务前,会等待一段时间以处理剩余的请求。 -
继承的方法:
onWaitEnd()
:在等待结束时调用,这里调用swapRequests()
准备处理下一组刷盘请求。getServiceName()
:返回服务名称,根据配置决定是否包含Broker标识符信息。getJoinTime()
:返回服务启动后多久可以接受新的刷盘请求,默认为5分钟。
GroupCheckService
class GroupCheckService extends FlushCommitLogService {
private volatile List<GroupCommitRequest> requestsWrite = new ArrayList<>();
private volatile List<GroupCommitRequest> requestsRead = new ArrayList<>();
public boolean isAsyncRequestsFull() {
return requestsWrite.size() > CommitLog.this.defaultMessageStore.getMessageStoreConfig().getMaxAsyncPutMessageRequests() * 2;
}
public synchronized boolean putRequest(final GroupCommitRequest request) {
synchronized (this.requestsWrite) {
this.requestsWrite.add(request);
}
if (hasNotified.compareAndSet(false, true)) {
waitPoint.countDown(); // notify
}
boolean flag = this.requestsWrite.size() >
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getMaxAsyncPutMessageRequests();
if (flag) {
log.info("Async requests {} exceeded the threshold {}", requestsWrite.size(),
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getMaxAsyncPutMessageRequests());
}
return flag;
}
private void swapRequests() {
List<GroupCommitRequest> tmp = this.requestsWrite;
this.requestsWrite = this.requestsRead;
this.requestsRead = tmp;
}
private void doCommit() {
synchronized (this.requestsRead) {
if (!this.requestsRead.isEmpty()) {
for (GroupCommitRequest req : this.requestsRead) {
// There may be a message in the next file, so a maximum of
// two times the flush
boolean flushOK = false;
for (int i = 0; i < 1000; i++) {
flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
if (flushOK) {
break;
} else {
try {
Thread.sleep(1);
} catch (Throwable ignored) {
}
}
}
req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
}
this.requestsRead.clear();
}
}
}
public void run() {
CommitLog.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
this.waitForRunning(1);
this.doCommit();
} catch (Exception e) {
CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
}
}
// Under normal circumstances shutdown, wait for the arrival of the
// request, and then flush
try {
Thread.sleep(10);
} catch (InterruptedException e) {
CommitLog.log.warn("GroupCommitService Exception, ", e);
}
synchronized (this) {
this.swapRequests();
}
this.doCommit();
CommitLog.log.info(this.getServiceName() + " service end");
}
@Override
protected void onWaitEnd() {
this.swapRequests();
}
@Override
public String getServiceName() {
if (CommitLog.this.defaultMessageStore.getBrokerConfig().isInBrokerContainer()) {
return CommitLog.this.defaultMessageStore.getBrokerConfig().getIdentifier() + GroupCheckService.class.getSimpleName();
}
return GroupCheckService.class.getSimpleName();
}
@Override
public long getJoinTime() {
return 1000 * 60 * 5;
}
}
GroupCheckService
类似于前面讨论的 GroupCommitService
,但其具体功能可能与检查消费组的状态有关。根据代码逻辑,这个服务同样基于 FlushCommitLogService
扩展,但它不是处理消息刷盘请求,而是处理与消费组相关的请求(例如确认消费位点、监控消费进度等)。
-
请求队列管理:类中定义了两个列表
requestsWrite
和requestsRead
,分别用于存放待处理和正在处理的消费组检查请求。 -
异步请求满载判断:
isAsyncRequestsFull()
方法用于检测当前待处理的请求是否超过了预设的最大值。 -
添加请求:
putRequest(GroupCommitRequest request)
方法将新的消费组检查请求添加到requestsWrite
列表,并在超过阈值时记录日志并返回 true。 -
交换请求队列:
swapRequests()
方法会在适当的时候将待处理的请求 (requestsWrite
) 与正在处理的请求 (requestsRead
) 进行交换。 -
执行检查操作:
doCommit()
方法负责遍历requestsRead
中的请求进行实际的检查工作,比如确认消费位点是否已持久化至磁盘,并在完成后唤醒等待结果的客户端。 -
运行逻辑:
run()
方法是服务的主要执行循环,在服务未停止的情况下,定期检查并处理消费组检查请求,最后在服务正常关闭时等待一段时间后完成剩余请求的处理。 -
继承方法实现:覆盖了
onWaitEnd()
、getServiceName()
和getJoinTime()
方法,分别对应于父类中的钩子函数、获取服务名称以及服务启动后的加入时间间隔。
同步落盘OR异步落盘
public DefaultFlushManager() {
if (FlushDiskType.SYNC_FLUSH == CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
this.flushCommitLogService = new CommitLog.GroupCommitService();
} else {
this.flushCommitLogService = new CommitLog.FlushRealTimeService();
}
this.commitRealTimeService = new CommitLog.CommitRealTimeService();
}
DefaultFlushManager
类的构造方法根据 RocketMQ 配置决定使用哪种刷盘服务策略。
-
判断
CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()
的值,如果为FlushDiskType.SYNC_FLUSH
,则说明配置要求采用同步刷盘策略。在这种情况下,创建并初始化一个CommitLog.GroupCommitService
实例作为刷盘服务,这意味着消息在内存写入后将立即执行磁盘同步操作,确保数据持久化。 -
如果不是同步刷盘策略,则创建并初始化一个
CommitLog.FlushRealTimeService
实例作为刷盘服务,这通常意味着采用异步或者实时(基于时间间隔或消息数量)刷盘策略,这种策略可以提高系统的吞吐量,但可能会牺牲一定的数据持久性保证。
此外,无论何种刷盘策略,都会实例化一个 CommitLog.CommitRealTimeService
对象作为实时提交服务,该服务可能与消息的实时处理和提交有关,确保消息尽快被处理和存储到合适的位置。