分布式技术 之 Write-Ahead Log 预写式日志
Posted BBinChina
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式技术 之 Write-Ahead Log 预写式日志相关的知识,希望对你有一定的参考价值。
分布式开发模式中,我们经常会讲到预写式日志(Write-ahead logging,缩写 WAL),其主要方式是将所有修改在提交之前都先写入log日志中。
举个例子说明:当我们的设备接收到请求进行操作时,突然断电了。在重启恢复时,我们需要确认操作是成功了,还是部分成功,甚至失败了。我们可以通过检测log,判断计划执行操作的内容与实际上执行操作的内容进行比较,通过比较程序来觉得是否应用,还是回滚等补救措施。
WAL 是关系数据库系统中用于提供原子性和持久性(ACID属性中的两个)的一系列技术,比如PostgreSQL也是用WAL来提供point-in-time恢复和数据库复制特性。
martin fowler 说过:
Provide durability guarantee without the storage data structures to be flushed to disk, by persisting every state change as a command to the append only log.
相比于持久化数据结构到磁盘,我们可以通过将每个状态的改变作为指令追加到日志中来提供持久性的保证。
WAL的解决方案:
每台服务器独立维护单个日志,将使状态更改的操作指令顺序地存储在硬盘上的文件中。按顺序追加的单个日志可以简化重启时的日志处理和后续在线操作(当日志被追加新的命令时)。每个日志条目都有一个唯一的标识符。唯一的日志标识符有助于在日志上执行某些其他操作,如分段日志或清除带有低水位标记的日志等。日志更新可以通过更新队列实现。
关于segment log 段日志、低水位日志check point low-water mark、以及更新日志会在后续文章内容讲到。
定义我们的日志类:
class WALEntry…
private final Long entryId;
private final byte[] data;
private final EntryType entryType;
private long timeStamp;
ky存储结构:
class KVStore…
private Map<String, String> kv = new HashMap<>();
public String get(String key) {
return kv.get(key);
}
public void put(String key, String value) {
appendLog(key, value);
kv.put(key, value);
}
private Long appendLog(String key, String value) {
return wal.writeEntry(new SetValueCommand(key, value).serialize());
}
put将对我们的状态进行修改,那么可以将此操作指令序列化存储到log中
class SetValueCommand…
final String key;
final String value;
final String attachLease;
public SetValueCommand(String key, String value) {
this(key, value, "");
}
public SetValueCommand(String key, String value, String attachLease) {
this.key = key;
this.value = value;
this.attachLease = attachLease;
}
@Override
public void serialize(DataOutputStream os) throws IOException {
os.writeInt(Command.SetValueType);
os.writeUTF(key);
os.writeUTF(value);
os.writeUTF(attachLease);
}
public static SetValueCommand deserialize(InputStream is) {
try {
DataInputStream dataInputStream = new DataInputStream(is);
return new SetValueCommand(dataInputStream.readUTF(), dataInputStream.readUTF(), dataInputStream.readUTF());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
将指令写入log放在状态更新之前,意味着当我们put操作返回成功后,当断电重启后,我们可以根据log恢复put操作。
class KVStore…
public KVStore(Config config) {
this.config = config;
this.wal = WriteAheadLog.openWAL(config);
this.applyLog();
}
public void applyLog() {
List<WALEntry> walEntries = wal.readAll();
applyEntries(walEntries);
}
private void applyEntries(List<WALEntry> walEntries) {
for (WALEntry walEntry : walEntries) {
Command command = deserialize(walEntry);
if (command instanceof SetValueCommand) {
SetValueCommand setValueCommand = (SetValueCommand)command;
kv.put(setValueCommand.key, setValueCommand.value);
}
}
}
public void initialiseFromSnapshot(SnapShot snapShot) {
kv.putAll(snapShot.deserializeState());
}
常用的Zookeeper、RAFT、kafka 都采用了类似WAL的方式 来保证可靠性
以上是关于分布式技术 之 Write-Ahead Log 预写式日志的主要内容,如果未能解决你的问题,请参考以下文章