前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式系统模式2-Write-Ahead Log

分布式系统模式2-Write-Ahead Log

作者头像
java达人
发布2020-12-03 10:40:00
5390
发布2020-12-03 10:40:00
举报
文章被收录于专栏:java达人java达人

作者: Unmesh Joshi

译者: java达人

来源: https://martinfowler.com/articles/patterns-of-distributed-systems/

通过将每个状态更改作为命令添加到append only 日志中,从而提供持久性保证,而无需将数据结构刷新到磁盘。

问题

即使在服务器存储数据失败的情况下,也需要强大的持久性保证。服务器确认执行某个操作后,即使它故障并失去所有的内存状态,也应该执行该操作。

方案

将每个状态更改作为命令存储在硬盘上的文件中。为每个服务器进程维护一个日志,该日志被顺序附加。单个日志按顺序附加,简化了重新启动时的日志处理和后续联机操作(当日志附加新命令时)。每个日志条目都有一个唯一的标识符。唯一的日志标识符有助于对日志执行某些其他操作,例如Segmented Log 或使用Low-Water Mark清除日志等。可以使用Singular Update Queue来实现日志更新。

典型的日志条目结构如下所示:

代码语言:javascript
复制
class WALEntry…

  private final Long entryId;
  private final byte[] data;
  private final EntryType entryType;
  private long timeStamp;

可以在每次重新启动时读取文件,并且可以通过重放所有日志条目来恢复状态。考虑一个简单的内存键值存储:

代码语言:javascript
复制
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操作表示Command,在更新内存哈希之前将其序列化并存储在日志中。

代码语言:javascript
复制
class SetValueCommand…

  final String key;
  final String value;

  public SetValueCommand(String key, String value) {
      this.key = key;
      this.value = value;
  }

  @Override
  public byte[] serialize() {
      try {
          var baos = new ByteArrayOutputStream();
          var dataInputStream = new DataOutputStream(baos);
          dataInputStream.writeInt(Command.SetValueType);
          dataInputStream.writeUTF(key);
          dataInputStream.writeUTF(value);
          return baos.toByteArray();

      } catch (IOException e) {
          throw new RuntimeException(e);
      }
  }

  public static SetValueCommand deserialize(InputStream is) {
      try {
          DataInputStream dataInputStream = new DataInputStream(is);
          return new SetValueCommand(dataInputStream.readUTF(), dataInputStream.readUTF());
      } catch (IOException e) {
          throw new RuntimeException(e);
      }
  }

这样可以确保一旦put方法成功返回后,即使保存KVStore的进程崩溃了,也可以通过在启动时读取日志文件来恢复其状态。

代码语言:javascript
复制
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());
  }

实现注意

实现Log时,有一些重要的注意事项。重要的是要确保写入日志文件的条目保留在物理介质上。所有编程语言中提供的文件处理库都提供了一种机制,可以强制操作系统将文件更改“flush”到物理介质。使用flush机制时有一点需要权衡考虑。

flush每个写入磁盘的日志可提供强大的持久性保证(这是将日志放在首位的主要目的),但这会严重限制性能,并很快成为瓶颈。如果flush延迟处理或异步完成,则可以提高性能,但是如果在flush条目之前服务器崩溃,则可能会丢失日志中的条目。大多数实现使用诸如批处理之类的技术来限制flush操作的影响。

另一个注意事项是确保在读取日志时检测到损坏的日志文件。为了解决这个问题,通常在日志条目中写入CRC记录,然后在读取文件时可以对其进行验证。

单个日志文件可能变得难以管理,并且可能很快消耗所有存储空间。为了解决此问题,使用了Segmented Log和Low-Water Mark之类的技术。

预写日志是append-only的。因此,在客户端通信失败和重试的情况下,日志可能包含重复的条目。应用日志条目时,需要确保忽略重复项。如果最终状态是类似HashMap的状态,其中对同一key的更新是幂等的,则不需要特殊的机制。如果不是,则需要实现某种机制,用唯一标识符标记每个请求并检测重复项。

示例:

•所有共识算法(例如Zookeeper和RAFT)中的日志实现类似于预写日志

•Kafka中的存储实现遵循与数据库中的提交日志类似的结构

•所有数据库,包括像Cassandra这样的nosql数据库,都使用预写日志技术来保证持久性

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
  • 方案
  • 实现注意
  • 示例:
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档