首页 >web前端 >js教程 >复制品大图

复制品大图

Patricia Arquette
Patricia Arquette原创
2025-01-02 20:23:39909浏览

复制缓存

这是一个帮助实现本地优先软件的框架。 Git 同样可以通过推和拉来帮助组织同步任务。

Replicache 在后台异步同步服务器数据,消除服务器往返并实现即时 UI 更改。

复制品零件

Replicache 由多个元素组成。

复制缓存

Replicache 可以被视为浏览器内的键值存储,其中包含内部类似 git 的操作。先写入内存,后同步。

您的申请

这是我们创建的应用程序,就像网络应用程序一样。它是 Replicache 中存储状态的实体。变异器和订阅的实现是为了改变和响应状态

你的服务器

它的存在是为了存储最可靠的数据。连接到服务器的数据库中存储的状态优先于应用程序中的状态。

服务器必须实现push(上游)和pull(下游)才能与客户端的Replicache通信。

  • push(上游):Replicache 将更改发送到推送端点。变异器在服务器和应用程序上实现,并且此推送端点执行此变异器来更改数据库的状态。

  • pull(下游):当定期或显式请求时,Replicache 会向服务器发送拉取请求。服务器返回客户端所需的更改,以使其与服务器的状态相同。

  • poke:虽然客户端会周期性的发送一个pull request,但是为了更实时的展示,当服务端有变化时,就是服务端给客户端一个提示的信号提出拉取请求。它不携带任何数据。

同步

应用程序和服务器已同步到最新状态。下图清楚地展示了这个过程。它展示了定期从服务器拉取状态更改并更新 UI 的过程,以及客户端上的状态更改如何首先更新 UI,然后推送到服务器

Replicache Big Picture
来源

客户端、客户端组、缓存

内存中的副本称为客户端。

import {Replicache} from "replicache";

const rep = new Replicache({
  name: userID,
  ...
});

console.log(rep.clientID);

每次点击通常有一个客户端。客户端是不稳定的并且遵循选项卡的生命周期。拥有唯一的 clientID。

客户端组是一组共享本地数据的客户端。即使离线时,该客户端组内的客户端也会共享状态。

Client Group 使用磁盘上的持久缓存,该缓存通过 Replicache 构造函数的 name 参数进行区分。属于具有相同名称的客户端组的所有客户端共享相同的缓存。

客户视角

客户端在持久化缓存中有一个键值对的有序映射,称为客户端视图。客户端视图是应用程序数据,与服务器数据同步。之所以称为客户端视图,是因为不同的客户端可能在不同的客户端视图中拥有服务器数据。这意味着每个客户端看到的服务器状态都不同。

访问客户端视图非常快。读取延迟小于 1 毫秒,大多数设备的吞吐量为 500MB/s。

建议您直接从 Client View 读取并使用它,而不是使用 useState 从 React 等地方单独复制 Client View 并将其上传到内存。当 mutator 更改 Client View 时,会触发订阅,从而更新 UI

订阅

Subscribe 函数接收 ReadTransaction 参数并实现从 Replicache 读取。每当此订阅由于副本数据更改而过期时,就会再次执行订阅功能。如果此结果发生变化,则值会更新,UI 也会更新。

如果您通过订阅配置 UI,则可以始终保持最新状态。

import {Replicache} from "replicache";

const rep = new Replicache({
  name: userID,
  ...
});

console.log(rep.clientID);

突变

Mutation 是指改变 Replicache 数据的任务。接收突变并实际更改数据的主体称为突变者。

启动时,Replicache 中会注册几个 Mutators,但它们实际上只是命名函数。下面的createTodo和markTodoComplete都是通过WriteTransaction改变Replicache数据的变异器。

const todos = useSubscribe(rep, async tx => {
  return await tx.scan({prefix: 'todo/'}).toArray();
});
return (
  <ul>
    {todos.map(todo => (
      <li key={todo.id}>{todo.text}</li>
    ))}
  </ul>
);

Mutator 的工作原理如下。当mutator运行时,数据发生变化,与之相关的订阅被触发,UI也发生变化。

const rep = new Replicache({
  ...
  mutators: {
    createTodo,
    markTodoComplete,
  },
});

async function createTodo(tx: WriteTransaction, todo: Todo) {
  await tx.set(`/todo/${todo.id}`, todo);
}

async function markTodoComplete(tx: WriteTransaction,
    {id, complete}: {id: string, complete: boolean}) {
  const key = `/todo/${id}`;
  const todo = await tx.get(key);
  if (!todo) {
    return;
  }
  todo.complete = complete;
  await tx.set(key, todo);
}

在内部,Mutator 创建了一种称为突变的东西。它就像一个执行记录,但 Replicache 创建了以下突变。

await rep.mutate.createTodo({id: nanoid(), text: "take out the trash"});

这些突变被标记为待处理,直到它们被推送到服务器并完全同步。

同步详情

现在,这里详细介绍Sync,它可以说是Replicache的核心。同步已在服务器上完成。

副本同步模型

(从现在开始,“状态”一词是指由多个键值对组成的数据(键值空间)的状态。)

Replicache 试图解决的同步问题是在多个客户端同时更改相同状态并且存在以下情况时发生的。

  1. 服务器的状态是事实的来源。它被表示为规范。
  2. 客户端本地状态的变化会立即反映出来。这就是所谓的投机。
  3. 服务器必须仅应用一次更改,并且结果必须是可预测的。应用于服务器的更改必须能够合理地与客户端上的本地更改合并。

其中的最后一项是将服务器更改与本地状态“合理合并”,这是一个有趣的主题。为了‘合理合并’,必须考虑以下情况。

  • 如果本地更改尚未应用到服务器。在这种情况下,即使从服务器检索到新状态,您也需要确保本地更改不会从应用程序的 UI 中消失。从服务器接收到新状态后,任何现有的本地更改都必须在服务器状态之上重新执行。

  • 当客户端上所做的本地更改已发送到服务器并反映在服务器状态中时。在这种情况下,请注意不要两次应用本地更改。不应重新应用本地更改

  • 如果有其他客户端已将服务器状态更改为相同状态。在这种情况下,与第一种情况一样,必须根据从服务器接收到的状态重做本地更改。但是,由于同一资源可能会发生冲突,因此必须仔细规划合并逻辑。将此逻辑写入 Mutator 中。

我们来看看Mutator的操作流程。

本地执行

变异器在本地运行,副本的值根据变异器逻辑发生变化。同时,该客户端创建一个mutation,其mutationId依次增加。突变会作为待处理的突变进行排队

待处理的突变被发送到服务器上实现的推送端点(replicache-push)。

变异通过执行服务器上实现的变异器来改变规范状态。在应用突变时,该客户端的最后一个突变 ID 会被更新,并成为一个值,让您知道当该客户端进行下一次拉取时要重新应用哪个突变

本地应用的待定突变会生成推测结果,而应用于服务器的突变会生成规范结果。应用于服务器的变异已被确认,不会再次在本地执行。即使相同的突变返回不同的结果,服务器的规范结果也会优先,因此客户端的结果会发生变化

Replicache 定期向拉取端点 (replicache-pull) 发送请求,以检索最新状态并更新 UI。

拉取请求包含cookie和clientGroupId,并返回新的cookie、补丁和lastMutationIDChanges。

Cookie用于区分客户端持有的服务器状态。任何可以跟踪服务器和客户端状态差异程度的值就足够了。您可以将其视为一个全局“版本”,只要数据库状态发生变化,该版本就会发生变化。或者,您可以使用 cookie 策略来跟踪更具体的数据范围。

lastMutationIdChanges 是一个值,表示服务器最后为每个客户端应用的突变 ID。 mutationID 小于该值的所有突变不应再被视为待处理而是已确认。

变基

当客户端收到拉取时,补丁必须应用于本地状态。然而,由于待处理的突变会影响当前的本地状态,因此补丁不能直接应用于本地状态。相反,恢复本地挂起的突变,首先应用作为拉取接收的补丁,然后再次应用本地挂起的突变。

为了实现这种撤消和重新应用,Replicache 的设计与 Git 类似。您可以将服务器的状态视为主分支,将由本地挂起的突变更改的状态视为开发分支,接收从服务器到主分支的拉取,并将开发变基到主分支。

rebase过程中可能出现的冲突将在下面单独讨论。

Poke,如上所述,是服务器告诉客户端拉取的提示消息。

冲突解决

在 Replicache 这样的分布式系统中,合并冲突是不可避免的。拉推过程中需要合并。合并的方式应该使合并结果可预测并符合应用程序的目的。

如果是会议室预订应用,发生冲突时只能批准一个请求。因此,你必须采用合并方式,只批准先预约的客户。

另一方面,如果它是一个Todo应用程序,Todo列表的目的是即使添加同时发生,两个更改也会被批准。

以下两种情况会发生合并冲突。

  1. 本地更改何时应用到服务器。这是因为本地应用时的状态和服务器上应用时的状态可能不同。

  2. 变基时。这是因为申请时的状态可能会有所不同。

Replicache 认识到合并方法必须根据应用程序的目的以不同的方式实现,因此它允许开发人员实现它。开发者可以通过 Mutator 来实现这个逻辑。

以上是复制品大图的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn