# 同步引擎的工作原理

v1.10.0 新增

本页描述的是 **WCPOS v1.10.0** 中引入的同步引擎。更早的版本使用另一种按屏幕划分的复制模型——请参见本页末尾的 [v1.10.0 有哪些变化](#what-changed-in-v1100)。

WCPOS 是本地优先的：每个屏幕都读写设备上的一个数据库，而 **同步引擎** 在后台让该数据库与您的 WooCommerce 商店保持收敛。本页解释引擎如何决定获取什么、何时获取，以及您的销售数据如何回到服务器——这一层次的细节对开发者、集成商以及希望了解 POS 对其主机做了什么的店主很有用。

## 每个商店和收银员各一个引擎[​](#one-engine-per-scope "直接链接到 每个商店和收银员各一个引擎")

在每台设备上，POS 为 **每个站点 + 商店 + 收银员的组合运行一个同步引擎**。该引擎拥有自己的本地数据库，因此切换商店或收银员切换的是整个数据平面，而不是在同一堆共享记录上做筛选。按收银员隔离是有意为之：同一台设备上的两名收银员绝不会共享本地数据。

升级时，本地数据库绝不会就地迁移——应用会启动一个全新的数据库并从服务器重新下载，服务器始终持有权威副本（参见 [从 v1.9 升级](#upgrading-from-v19)）。

在 Pro 多商店安装中，每个同步请求都会标明自己所属的商店，因此在收银台修改的价格更新的是该商店的价格，而不是网店的价格。

## 后台运行的内容[​](#sync-lanes "直接链接到 后台运行的内容")

引擎按计划执行的每一项工作都是一个 **通道**——一个有名称、有边界的后台工作单元。通道分为三组：

| 分组     | 通道                                     | 默认节奏                                                                            |
| -------- | ---------------------------------------- | ----------------------------------------------------------------------------------- |
| **拉取** | 变更检查（变更信号）                     | 10 秒 – 5 分钟，由您的 [同步预设](/zh-CN/support/store-health.md#sync-presets) 决定 |
|          | 最近订单、产品目录预填充、参考数据预填充 | 约 5 分钟                                                                           |
|          | 客户涓流下载（仅在空闲时）               | 约 5 分钟                                                                           |
| **推送** | 写入排空——发送已排队的本地更改           | 约 10 秒                                                                            |
| **维护** | 完整性与删除审计、服务器总数刷新         | 每隔几分钟到约 17 分钟                                                              |

每个通道都满足两条特性：

* **每个通道都声明单次运行的请求上限。** 任何通道都不得在一次运行中发出数量不受限的请求；较大的任务使用带可恢复游标的有界批次。这是一项核心设计不变式，[同步性能](/zh-CN/reference/sync-performance.md) 中有深入介绍。
* **维护工作让位于收银员。** 审计和后台预热在让收银台可以开始销售的交互性工作 *之后* 运行，绝不会抢在前面；而且一旦您的服务器出现压力迹象，它们是最先被暂停的。

上述节奏都是默认值。检查间隔和每次请求的记录数可由商家在 POS 的 **商店健康 → 性能** 中按设备调整——请参见 [商店健康](/zh-CN/support/store-health.md)。

## POS 如何得知发生了变更[​](#change-signal "直接链接到 POS 如何得知发生了变更")

引擎不会为了弄清数据是否变化而重新下载数据。服务器维护一份变更日志，POS 轮询一个轻量的 **变更检查**，它只回答一个问题：自我上次所处的位置以来，有任何东西变动过吗？

* **一次检查覆盖八个集合**——产品、变体、税率、客户、优惠券、分类、品牌和标签。订单被有意排除在变更检查之外；订单的时效性由它自己的最近订单通道负责，这也是产品更新和订单更新可能以不同节奏到达的原因。
* **空闲的收银台几乎不产生开销。** 引擎发送条件请求：当没有任何变化时，服务器只回应一个无正文的 `304 Not Modified`。安静的商店会稳定在每次检查一个极小响应的水平。
* **检查带有 ±20% 的抖动**，这样同一站点上的多台收银机会彼此错开，而不是同步地对服务器发起突发请求。
* **空闲衰减：** 在 10 分钟无交互之后，检查会被拉长（下限为 60 秒）。任何真实活动——触摸、按键、一次被接受的条形码扫描——都会立即恢复到完整节奏，并触发一次即时的追赶检查。衰减只会拉长间隔，绝不会比所配置的设置轮询得更快。
* **停用数天的收银台会重新建立基线，而不是回放历史。** 如果变更日志相对收银台上次所处的位置已经推进太远，逐行回放会耗费数百个请求。引擎会改为把游标跳到最新位置，并重新检查设备已持有内容在服务器上的当前状态——因此开销取决于本地副本的规模，而不是收银台离开了多久。

## 屏幕如何获得数据[​](#declared-demand "直接链接到 屏幕如何获得数据")

在 v1.10.0 中，屏幕不再运行自己的同步。**屏幕只声明它正在显示什么——搜索词、筛选条件、排序、页码——由引擎决定这是否需要发起请求。** 每次声明会以三种方式之一得到解决：

* **已获取**——引擎在网络上做了工作来满足它。
* **本地满足**——设备已经拥有答案，或者一次相同的近期获取已经覆盖了它。
* **被取代**——屏幕已经变化（您滚动了页面、更换了筛选条件），一个更新的声明取代了它。这是常规现象，不是错误。

只能在本地回答的筛选条件永远不会发送到服务器。而且，一次成功的声明并不意味着某个集合已完整下载——完整性是单独跟踪的（见下一节）。

并非所有内容都会被急切地下载，启动时设备上有什么内容也因集合而异：

1. **预填充**——产品通过有界的目录预填充逐步填满；税率在启动时拉取（没有税率，POS 无法完成购物车计算）。
2. **按需下载，外加空闲涓流**——客户没有急切的预填充。客户涓流下载在每个空闲间隔下载一小批，只要收银员处于活动状态就完全跳过；新增或变更的客户则通过变更检查到达。
3. **首次打开时获取**——分类、标签、品牌和优惠券在收银员首次打开它们时拉取。没有人打开的集合永远不会产生任何请求。

此外，变体选择器每次打开时会刷新一次价格和库存，因此已在本地的变体可以立即渲染，但绝不会显示数天前的过期库存。

## “是否全部下载完了？”——诚实的覆盖率[​](#coverage "直接链接到 “是否全部下载完了？”——诚实的覆盖率")

像 *“5,000 件产品中的 1,240 件”* 这样的读数，其分母需要一个 **服务器端** 总数。引擎为每个集合维护一个这样的总数（大约每 15 分钟刷新一次，通常没有额外开销——真实的同步响应本身就带有总数，因此很少需要专门发一个请求），并遵守严格的诚实约定：

* 过期或缺失的服务器总数会显示为 *正在检查…*——绝不会悄悄用本地计数代替。用本地数字作分母永远会显示 100%，恰好掩盖了这个数字本该揭示的差距。
* 当引擎无法为完整性背书时，结论就是 *未知*，并且界面会把该数字标注为本地计数。
* 订单覆盖率条是相对于 **整个** 服务器订单历史来衡量的，而收银台有意只保留未结和最近的订单——所以健康的收银台在那里显示为部分覆盖，是设计使然。

这些数字会呈现在 **商店健康 → 数据库** 中，同时还有一个 *可开始销售* 里程碑：只要设备上有了第一件产品，它就会翻转为已达成——离线并不会阻止它，因为离线销售正是重点所在。请参见 [商店健康](/zh-CN/support/store-health.md)。

## 更改如何回到您的商店[​](#write-path "直接链接到 更改如何回到您的商店")

每一次本地写入——一笔销售、一次产品编辑、一次客户更新——都会先落入设备上的一个 **持久化出站队列**，然后由排空通道每隔几秒把队列推送到 WooCommerce。这正是离线销售安全的原因：在无连接状态下开出的销售会留在队列中，并在连接恢复时排空发出。

值得关注的细节：

* **购物车写入按订单串行化。** 扫描枪连续快速扫描时会逐条应用，重复添加会合并到它所重复的那一行，而不是排队生成第二行。
* **订单确认会采纳服务器的副本。** WooCommerce 在创建时会为订单行项目分配 ID；引擎会采纳已确认的订单，使后续更新对应到这些行，而不是追加重复项。采纳是保守的——它绝不会覆盖服务器尚未见过的本地编辑，并且只适用于订单。
* **服务器永久拒绝的写入绝不会被无声重试。** 它会连同服务器给出的原因一起被搁置，并呈现在 **商店健康 → 数据库** 的 *“从未送达服务器的更改”* 中，附带两个明确的操作：**重新发送**（根据记录当前的状态重新构建请求，因此之后所做的修正会生效）和 **放弃**。这里没有自动重试循环——恢复始终是一次可见且刻意的操作。面向商家的操作说明请参见 [商店健康](/zh-CN/support/store-health.md#database)。

### 多个浏览器标签页[​](#multi-tab "直接链接到 多个浏览器标签页")

支持在同一商店的多个标签页中运行 Web 版 POS。每个标签页都可以开单——写入会追加到共享队列——但对于每个商店 + 收银员作用域，**由一个被选举出来的标签页负责发送**。如果该标签页关闭，浏览器会自动把下一个提升上来。以不同收银员身份登录的两个标签页属于不同作用域，各自管理自己的队列。

## 本地存储[​](#local-storage "直接链接到 本地存储")

Web 版和桌面版应用通过 **OPFS（源私有文件系统）工作线程** 存储数据；iOS 和 Android 应用通过文件系统引擎使用相同的磁盘格式。四个平台共用一种存储格式和同一套损坏恢复工具。（更早的版本在 Web 上使用 IndexedDB，在原生端使用 SQLite。）

查询在数据库层内部执行——选择器、排序和分页——因此只有可见的那一页数据会跨入应用。在一个包含 10,000 笔订单的合成测试数据上，这种下推把实时订阅下的单次写入更新开销从约 27 毫秒降到了约 0.06 毫秒。

## 从 v1.9 升级[​](#upgrading-from-v19 "直接链接到 从 v1.9 升级")

v1.10.0 不迁移本地数据库——它执行一次 **冷重新同步**：

1. 首次启动时，应用会打开一个全新的本地数据库并从您的商店重新下载。每台设备都会经历一次性的完整重新下载。
2. 不会丢失任何东西：您的 WooCommerce 服务器是所有已同步数据的权威副本。待发送的未提交更改会在旧数据被清理 **之前** 排空或呈现出来——升级不可能毁掉一笔未发送的销售。
3. **应用与插件同步发布。** v1.10.0 客户端使用插件的 v2 同步 API。如果只升级了其中一侧，请求会以 `rest_no_route` 错误失败——这个特征意味着“请更新另一半”，而不是安装损坏。

## v1.10.0 有哪些变化[​](#what-changed-in-v1100 "直接链接到 v1.10.0 有哪些变化")

|              | v1.9.x                           | v1.10.0                                     |
| ------------ | -------------------------------- | ------------------------------------------- |
| 同步单位     | 每个挂载的屏幕查询一条复制流     | 每个站点 + 商店 + 收银员一个引擎            |
| 什么驱动获取 | 屏幕挂载                         | 屏幕声明它需要什么                          |
| 变更检测     | 定期轮询 + 每小时全量审计        | 变更日志游标配合条件式 `304` 响应           |
| 服务器总数   | 不跟踪                           | 各集合的总数，并带有诚实的 *正在检查…* 状态 |
| 失败的写入   | 不透明地重试                     | 持久化队列、可见的恢复面板、无无声重试      |
| 多标签页 Web | 不协调                           | 每个作用域一个被选举的发送方                |
| 本地搜索     | 按词首匹配                       | 子串匹配（最少 3 个字符）                   |
| 存储         | IndexedDB（Web）、SQLite（原生） | 所有平台均采用 OPFS 格式存储                |
| 同步调优     | 固定                             | 商店健康中按设备提供的预设与调节项          |

有关引擎背后的性能理念——请求上限、服务器压力下的退避以及实测数据——请参见 [同步性能](/zh-CN/reference/sync-performance.md)。
