在大多数具有简单查询的单体系统中,所有必要的数据可以在单个数据库调用中检索。然而,当数据分散到由不同服务拥有的单独的数据库或模式中时,读操作的数据访问开始变得困难。
愿望单服务维护客户可能希望最终购买的项目列表,并包括客户ID、项目ID和项目添加日期在相应的愿望单表中。目录服务负责维护公司销售的所有项目,并包括项目ID、项目描述和静态产品维度信息,如重量、高度、长度等。
在这个示例中,当客户请求显示他们的愿望单时,项目ID和项目描述(item_desc)都会返回给客户。然而,愿望单服务的表中没有项目描述;该数据由目录服务拥有,在提供更改控制和数据所有权的紧密形成的有界上下文中。
服务间通信模式是分布式系统中最常见的用于访问数据的模式。如果一个服务(或系统)需要读取它无法直接访问的数据,它只需通过某种远程访问协议向拥有该数据的服务或系统请求数据。
请注意,对于每个获取客户愿望单的请求,愿望单服务必须进行远程调用以从目录服务获取项目描述。这种模式出现的第一个问题是由于网络延迟、安全延迟和数据延迟而导致的性能较慢。
这种模式的另一个重要缺点是服务耦合。因为愿望单必须依赖于目录服务是否可用,所以这两个服务在语义上和静态上都是耦合的,这意味着如果目录服务不可用,愿望单服务也不可用。此外,由于愿望单服务与目录服务之间的紧密静态耦合,愿望单服务随着需求量的增加而扩展,目录服务也必须扩展。
使用列模式复制模式,列被复制到表格中,从而复制数据并使其可用于其他有界上下文。
数据同步和数据一致性是与列模式复制数据访问模式相关的两个最大问题。每当创建产品、从目录中删除产品或更改产品描述时,目录服务必须以某种方式通知愿望单服务(和任何其他复制数据的服务)进行更改。通常,通过使用队列、主题或事件流进行异步通信来完成这一点。
这种模式的另一个挑战是很难管理数据所有权。因为数据复制在属于其他服务的表格中,这些服务可以更新数据,即使它们并没有正式拥有数据。这反过来又带来了更多的数据一致性问题。
尽管一般情况下我们警告不要在愿望单服务和目录服务示例等情况下使用这种数据访问模式,但在某些情况下可能需要考虑使用它,例如数据聚合、报告或其他数据访问模式不适合的情况,因为数据量大,响应要求高或容错要求高。
这种模式利用复制的内存缓存,使其他服务所需的数据可用于每个服务,而无需请求它们。复制缓存与其他缓存模型不同,因为数据保留在每个服务的内存中,并持续同步,以便所有服务在任何时候都具有相同的数据。
让我们与其他缓存模型进行比较,以更好地理解它。单个内存缓存模型是最简单的缓存形式,其中每个服务都有自己的内部内存缓存。使用这种缓存模型,内存数据在缓存之间不进行同步,这意味着每个服务都有其自己的特定于该服务的唯一数据。尽管这种缓存模型有助于提高每个服务内部的响应性和可伸缩性,但由于缺乏缓存之间的同步,对于共享数据而言并不有用。
在分布式架构中使用的另一种分布式缓存。正如此缓存模型所示,数据不存储。
在每个服务的内存中,而是存储在缓存服务器中。服务使用专有协议向缓存服务器发出请求以检索或更新共享数据。请注意,与单个内存缓存模型不同,数据可以在服务之间共享。
使用复制缓存,每个服务都有自己的内存数据,在服务之间保持同步,允许多个服务之间共享相同的数据。
那么复制缓存是如何工作的呢?为了解决分布式数据访问问题,让我们回到愿望单服务和目录服务的示例。目录服务拥有产品描述的内存缓存(这意味着它是唯一可以修改缓存的服务),愿望单服务包含相同缓存的只读内存副本。
有了这种模式,愿望单服务不再需要调用目录服务以检索产品描述 — 它们已经在愿望单服务的内存中。当目录服务对产品描述进行更新时,缓存产品将更新愿望单服务中的缓存以使数据一致。
复制缓存模式的明显优势在于响应性、容错性和可伸缩性。由于服务之间不需要明确的服务间通信,数据随时在内存中可用,提供了对不拥有数据的服务最快的数据访问。
这种模式的第一个权衡是与缓存数据和启动时间相关的服务依赖性。由于目录服务拥有缓存并负责填充缓存,所以在初始愿望单服务启动时,目录服务必须在运行时。如果目录服务不可用,初始愿望单服务必须进入等待状态,直到与目录服务建立连接。
这种模式的第二个权衡是数据量。如果数据量太大(如超过500 MB),则这种模式的可行性会迅速减小,特别是对于需要数据的多个服务的多个实例。
第三个权衡是,如果数据的更改率(更新率)太高,复制缓存模型通常不能在服务之间保持数据完全同步。
每种模式都有其适用的场景和权衡,选择合适的数据访问模式取决于具体的需求和约束。