jeu*_*x20 1 eventual-consistency microservices asp.net-core api-gateway
举个例子,假设我正在构建一个简单的社交网络。我目前有两项服务:
Identity,管理用户、他们的个人数据(电子邮件、密码哈希等)及其公共配置文件(用户名)和身份验证Social,管理用户的帖子、朋友和动态该Identity服务可以使用其 API 提供用户的公共资料/api/users/{id}:
// GET /api/users/1 HTTP/1.1
// Host: my-identity-service
{
"id": 1,
"username": "cat_sun_dog"
}
Run Code Online (Sandbox Code Playgroud)
该Social服务可以在以下位置发布其 API 的帖子/api/posts/{id}:
// GET /api/posts/5 HTTP/1.1
// Host: my-social-service
{
"id": 5,
"content": "Cats are great, dogs are too. But, to be fair, the sun is much better.",
"authorId": 1
}
Run Code Online (Sandbox Code Playgroud)
这很好,但我的客户端(一个 Web 应用程序)希望显示带有作者姓名的帖子,并且它最好在一个 REST 请求中接收以下 JSON 数据。
{
"id": 5,
"content": "Cats are great, dogs are too. But, to be fair, the sun is much better.",
"author": {
"id": 1,
"username": "cat_sun_dog"
}
}
Run Code Online (Sandbox Code Playgroud)
我发现了两种主要方法来解决这个问题。
正如Microsoft 的数据指南和Microsoft 的微服务间通信指南中所述,微服务可以通过设置事件总线(例如 RabbitMQ)并使用来自其他服务的事件来复制其所需的数据:
最后(这是构建微服务时出现大多数问题的地方),如果您的初始微服务需要最初由其他微服务拥有的数据,请不要依赖于对该数据发出同步请求。相反,通过使用最终一致性(通常通过使用集成事件,如后续部分中所述)将该数据(仅需要的属性)复制或传播到初始服务的数据库中。
因此,Social服务可以使用该服务生成的事件Identity,例如UserCreatedEvent和UserUpdatedEvent。然后,该Social服务可以在其自己的数据库中拥有所有用户的副本,但只有所需的数据(他们的Id和Username,仅此而已)。
通过这种最终一致的方法,Social服务现在可以在一个请求中获得 UI 所需的所有数据!
// GET /api/posts/5 HTTP/1.1
// Host: my-social-service
{
"id": 5,
"content": "Cats are great, dogs are too. But, to be fair, the sun is much better.",
"author": {
"id": 1,
"username": "cat_sun_dog"
}
}
Run Code Online (Sandbox Code Playgroud)
好处:
Social服务完全独立于Identity服务;没有它它也可以正常工作缺点和问题:
ProfilePicture?正如Microsoft 数据指南中所述,可以创建一个 API 网关来聚合来自两个请求的数据:一个请求到Social服务,另一个请求到Identity服务。
/api/posts/{id}因此,我们可以在 ASP.NET Core 的伪代码中实现这样的API 网关操作 ( ):
[HttpGet("/api/posts/{id}")]
public async Task<IActionResult> GetPost(int id)
{
var post = await _postService.GetPostById(id);
if (post is null)
{
return NotFound();
}
var author = await _userService.GetUserById(post.AuthorId);
return Ok(new
{
Id = post.Id,
Content = post.Content,
Author = new
{
Id = author.Id,
Username = author.Username
}
});
}
Run Code Online (Sandbox Code Playgroud)
然后,客户端只需使用 API 网关并在一次查询中获取所有数据,无需任何客户端开销:
// GET /api/posts/5 HTTP/1.1
// Host: my-api-gateway
{
"id": 5,
"content": "Cats are great, dogs are too. But, to be fair, the sun is much better.",
"author": {
"id": 1,
"username": "cat_sun_dog"
}
}
Run Code Online (Sandbox Code Playgroud)
好处:
缺点和问题:
Identity如果服务关闭,操作就会中断,尽管可以使用断路器模式来缓解这种情况,但客户端无论如何都看不到作者的名字有这两种选择:API 网关上的聚合和使用事件在各个微服务上进行数据复制,哪种情况使用哪一种,以及如何正确实现它们?
一般来说,我强烈支持通过持久日志结构存储中的事件进行状态复制,而不是进行同步(在逻辑意义上,即使以非阻塞方式执行)查询的服务。
请注意,所有系统在足够高的级别上最终都是一致的:因为我们不会阻止世界允许服务更新发生,所以从更新到其他地方的可见性(包括在用户的脑海中)总是存在延迟。
一般来说,如果您丢失了数据存储,一切都会毁掉。但是,不可变事件的日志几乎免费为您提供主动-被动复制(您拥有该日志的使用者,可以将事件复制到另一个数据中心):在灾难中,您可以使被动方处于主动状态。
如果您需要的事件多于已发布的事件,只需添加日志即可。您可以使用日志存在之前状态的合成事件的回填转储来为日志播种(例如转储所有当前的ProfilePictures)。
当您将事件总线视为复制日志时(例如,通过使用 Kafka 实现它),事件的消费并不会阻止任意许多其他消费者稍后出现(它只是增加您在日志中的读取位置)。这样,其他消费者就可以使用日志来进行自己的混音。其中一个消费者可能只是将日志复制到另一个数据中心(启用主动-被动)。
请注意,一旦您允许服务维护自己对来自其他服务的重要数据位的视图,您实际上就是在进行命令查询职责分离(CQRS);因此,熟悉 CQRS 模式是个好主意。
| 归档时间: |
|
| 查看次数: |
1487 次 |
| 最近记录: |