作者: Unmesh Joshi
译者: java达人
来源: https://martinfowler.com/articles/patterns-of-distributed-systems/
预写日志中的索引,表示可以丢弃日志的哪一部分。
问题
预写日志维护对持久性存储的每次更新。随着时间的流逝,它会无限增长。Segmented Log允许一次处理较小的文件,但是如果不检查,总磁盘存储量将无限制增长。
解决方案
有一种机制可以告诉日志记录机器可以安全地丢弃日志的哪一部分。该机制提供了最低的偏移量或low water mark,在此之前的日志可以丢弃。让任务在后台的单独线程中运行,该任务连续检查可以丢弃日志的哪一部分并删除磁盘上的文件。
this.logCleaner = newLogCleaner(config);
this.logCleaner.startup();
日志清理器可以实现为定时任务
public void startup() {
scheduleLogCleaning();
}
private void scheduleLogCleaning() {
singleThreadedExecutor.schedule(() -> {
cleanLogs();
}, config.getCleanTaskIntervalMs(), TimeUnit.MILLISECONDS);
}
基于快照的Low-Water Mark
大多数共识实现(例如Zookeeper或etcd(在RAFT中定义))都实现了快照机制。在此实现,存储引擎需要定期快照。除快照外,它还存储成功应用的日志索引。参考“Write-Ahead Log”模式中的简单键值存储实现,可以采取以下快照:
public SnapShot takeSnapshot() {
Long snapShotTakenAtLogIndex = wal.getLastLogEntryId();
return new SnapShot(serializeState(kv), snapShotTakenAtLogIndex);
}
一旦快照成功保存在磁盘上,日志管理器将获得Low-Water Mark,以丢弃较旧的日志。
List<WALSegment> getSegmentsBefore(Long snapshotIndex) {
List<WALSegment> markedForDeletion = new ArrayList<>();
List<WALSegment> sortedSavedSegments = wal.sortedSavedSegments;
for (WALSegment sortedSavedSegment : sortedSavedSegments) {
if (sortedSavedSegment.getLastLogEntryId() < snapshotIndex) {
markedForDeletion.add(sortedSavedSegment);
}
}
return markedForDeletion;
}
基于时间的Low-Water Mark
在某些系统中,不一定要使用日志来更新系统状态,可以在给定的时间窗口后丢弃日志,而不必等待任何其他子系统共享可以删除的最低日志索引。例如,在像Kafka这样的系统中,日志将保留7周;消息时间超过7周的所有日志段都将被丢弃。对于此实现,每个日志条目还包括创建时的时间戳。然后,日志清理器可以检查每个日志段的最后一个条目,并丢弃早于配置的时间窗口的日志段。
private List<WALSegment> getSegmentsPast(Long logMaxDurationMs) {
long now = System.currentTimeMillis();
List<WALSegment> markedForDeletion = new ArrayList<>();
List<WALSegment> sortedSavedSegments = wal.sortedSavedSegments;
for (WALSegment sortedSavedSegment : sortedSavedSegments) {
if (timeElaspedSince(now, sortedSavedSegment.getLastLogEntryTimestamp()) > logMaxDurationMs) {
markedForDeletion.add(sortedSavedSegment);
}
}
return markedForDeletion;
}
private long timeElaspedSince(long now, long lastLogEntryTimestamp) {
return now - lastLogEntryTimestamp;
}
例子
•所有共识算法(例如Zookeeper和RAFT)中的日志实现均实现基于快照的日志清理•Kafka中的存储实现遵循基于时间的日志清理
java达人
ID:drjava
(长按或扫码识别)