Post和 PostLike应该是独立的微服务吗?在跨服务场景下,如何验证实体是否存在?
现有设计
我有两个独立的微服务:
- Post Service — 负责管理帖子
- PostLike Service — 负责处理帖子的点赞
A PostLike 引用一个 PostId,但这两个服务完全解耦(分离的数据库、没有外键约束)。
问题
当用户点赞一个帖子时,我需要确保该帖子确实存在。由于服务是解耦的,这种验证不能在数据库层面强制。
这就出现了几种选项:
- 让PostLike Service向 Post Service发起同步API调用来验证帖子。
- 使用 异步验证(事件驱动 / 最终一致性)。
- 完全跳过验证,接受潜在的不一致性。
关注点
- 同步调用会增加耦合度和延迟。
- 异步验证可能在短时间内允许对不存在的帖子进行点赞(至少在短时间内如此)。
- 跳过验证会带来数据完整性风险。
备选考虑
我也在质疑Post和 PostLike是否应该作为独立的微服务存在。
- 把它们放在同一个服务边界内会不会更好?
- 还是把它们分开并以不同的方式处理一致性,是否合理?
问题
- 将Post和 PostLike分成不同微服务是一个明智的设计选择吗?
- 如果它们是独立的,推荐在允许点赞之前如何确保帖子存在?
- 在真实世界的系统中,这类验证通常是严格的,还是通常接受最终一致性?
我很想听听搭建过类似系统的人的见解。
解决方案
如果你能容忍不一致性,那么最终一致性模型要简单得多。现实上这意味着你需要某种方式来清理那些关联到不存在帖子的点赞;一项每日维护任务可能就够了。
如果你想要确保你的 “like” 服务永远不会附着到不存在的帖子上,那么你需要做类似这样的事情
- 告诉 “Post Service” 确保帖子存在
- 保存该点赞并提交到本地数据库
- 告诉 “Post Service” 释放对帖子的锁
现在你还需要容忍步骤1和3中的“锁”和“释放”可能被不同副本接收、Liked服务在步骤2崩溃而来不及释放锁,以及各种其他故障模式。
在这个具体领域,我也预计你会先获取帖子及其点赞,再去获取其点赞,但通常不会从一个具体的点赞出发再往上追溯到它的帖子。这样可以最小化没有父对象的影响。不同领域可能情况不同。
我可能会在HTTP接口层做一次性校验——如果你 POST /posts/1a2bc/like 且帖子似乎不存在,就返回HTTP 404——但不要进行比这更深层的校验。
你也在问它们是否应该是分开的服务。我会质疑它们是否应该根本就是独立的对象。
如果一个 “like” 只是帖子对象上的一个整数计数,那么它就应该是一个字段。
也可以考虑这样一种模型:你有帖子;帖子可以有评论,评论可以有回复;用户可以对帖子和评论进行点赞。这三种对象都属于一个 “用户内容” 域,并且有一个用户拥有者。它们中的两种有文本内容和子项;两种有父级。将这三种对象统一成一种“内容”对象也是完全合理的,并且看起来像(Python/Pydantic语法)
type UserContentId = str
type UserId = str
class UserContentType(StrEnum):
POST = "post"
COMMENT = "comment"
LIKE = "like"
class UserContent(BaseObject):
content_id: UserContentId
typ: UserContentType
parent: UserContentId | None
owner: UserId
content: str
def user_content_children(db: DatabaseConnection, content_id: UserContentId) -> list[UserContent]: ...
我可能仍会在HTTP API层对它们进行分离,但在单一服务内它们可以是同一种对象类型。这样可以避免跨服务调用,尤其是跨服务锁来做你上面描述的这类一致性检查的麻烦,也省去了维护另一项服务的开销。