IM
IM
简单介绍一下你的这个 DDD 六边形和微服务架构的 IM 项目
这个项目是一个基于 Go 和 DDD 领域驱动设计的分布式即时通讯系统,主要是面向在保证消息可靠性的前提下的高并发实时通信场景
架构方面我根据领域边界将系统拆分为了一个网关和六个微服务,分别是用户、会话、消息、投递、在线状态和文件,服务间通过 gRPC 同步通信,对外暴露 HTTP 接口,使用 Kafka 统一处理消息队列和吞吐管理,每个服务采用的是六边形架构,端口层定义入站出站接口,适配器层实现具体的技术细节,领域层划分核心业务规则,应用层编排具体的业务流程,这样就实现了业务逻辑和技术细节的解耦,便于测试、技术栈替换和不同团队协作
消息分发策略采用的主要是读写扩散的混合模型,在单聊和 500 人以下的小群主要使用写扩散,在写入时将消息分发到每个成员的 Inbox 收件箱,读取时就可以 O(1) 直接获取,大群则使用读扩散,只写一份 Timeline 时间线,读取时再根据游标计算未读消息和最新消息
可靠性方面主要是使用先落库后投递的策略,通过 Outbox 发件箱模式实现最终一致性,消费端通过指数退避、幂等重试和死信队列兜底,保证至少一次可达,客户端则通过 ACK 机制确认消息收到,未确认的消息会在 30 秒后重传
技术选型上由于 Kafka 的分区机制可以保证同一会话的消息有序,吞吐量也更高,就没有使用 RabbitMQ,而是通过 Conversation ID 作为分区键,将同一会话的消息路由到同一分区,这样可以天然保证顺序;另外在生成全局唯一递增 ID 时需要高并发,于是采用了 Lua 脚本在 Redis 单线程模型下天然保证了原子性递增,不需要分布式锁,获得更好的性能
通过这个项目我主要是深入理解了分布式系统的一致性、可用性和分区容错这三个核心问题之间的取舍,最后也是选择了最终一致性作为实现目标,在保证消息不丢失的前提下,再去追求更高的可用性和性能
什么是 DDD?为什么你的项目要用 DDD?
DDD 是 Domain Driven Design 的缩写,也就是领域驱动设计,它的核心思想是将业务逻辑与技术实现彻底解耦,它不像传统的 MVC 那样自顶向下的 Web 层 Controller —— 业务层 Service —— 数据访问层 DAO 或 Model —— 数据库的依赖方向,而是强调由外向内,最外层的适配器层分为入站和出站两类,入站是驱动应用的外部输入,出站则是被应用驱动的外部依赖,会涉及底层的技术细节,端口层则往往是边界,入站和出站端口分别定义了应用能做什么和应用需要什么外部能力,领域层就是最内层最纯粹的业务逻辑
在我的项目中除了适配器、端口和领域以外,还有一层应用层,负责编排业务逻辑,这样领域层就只需要划分实体、值对象和领域服务,我可以在还没有写好外部依赖的情况下就通过 mock interface 直接对核心业务逻辑进行单元测试,
可以讲一下你在这个项目里是怎么用到 DDD 架构的吗,以及微服务架构
我先把这个项目拆分成了七个微服务和一个网关,分别是 identity、conversation、message、delivery、presence、media-signal 和 file,然后每个微服务进行六边形分层,分别是领域层(domain)、应用层(application)、端口层(port)和适配器层(adapter)
其中端口层和适配器层分为入站和出站,对内就是 http、WebSocket 和 gRPC,对外就是各种中间件什么的(MySQL、Kafka、redis),领域层则主要分为实体、值对象和领域服务,适配器层主要实现中间件相关接口,应用层则是用例的编排以及调用领域服务
每个服务拥有自己的数据和模型:
-
identity:用户/联系人表,令牌签发与黑白名单
-
conversation:会话与成员表
-
message:消息表、inbox、outbox;对外暴露“写入/历史/已读”
-
delivery:不持久化消息本体,消费 Kafka,按在线/离线分别推送/更新进度
-
presence:用 Redis 维护 user_id -> node_id + last_seen 的在线路由
-
media-signal:呼叫/接受/拒绝/挂断,做 SDP/ICE 交换
-
file:预签名直传/回调,只存对象键与元数据
-
对外只有 api-gateway:握手鉴权、心跳、限流、WS 收发与转发
服务间读小写少走 gRPC(网关→各服务,delivery→网关),Kafka 的 key 取 conversation_id,保证同会话同分区;clientMsgId + 唯一约束去重;投递按 user_id, seq 记已送进度,写 DB 成功即返回;投递失败由重试/死信兜底,恢复后补送
介绍一下你从 MVC 转型到 DDD 的构思
MVC 里一个接口经常串很多表和外部动作(写消息→推送→更新已读),业务规则散在 Controller/Service/DAO 各处,重复且难以复用;IM 强依赖会话内有序 + 幂等 + 最少一次,MVC 里常见“写库成功后再调用推送”,中间失败就乱序或丢投递,单体里写入与投递绑在一起,高峰时无法只扩“投递”,长连接治理也分散到各处。
Conversation(含 Participants):成员不能重复;拉人/踢人按角色校验;单聊通过业务键保证唯一会话
Message:会话内 seq 自增且唯一;写入必须幂等
Inbox:每用户每会话一条进度,delivered/read 只单调前进
Outbox:与消息同事务写入,作为对外投递的唯一事实来源
入站端口:gRPC/WS Handler
出站端口:仓储(MySQL/GORM)、队列发布(Kafka)、缓存(Redis)、对象存储
适配器可换,领域层不依赖具体技术
领域边界 = 微服务边界:identity、conversation、message、delivery、presence、media-signal、file;外部统一走网关
MVC 老路:Controller 校验 → Service 写库 → 调 WebSocket 推送 → 出错回滚/重试点很多,顺序与幂等靠代码约定
DDD/六边形新路:
应用层 SendMessage 调领域服务生成 seq;
同事务写 messages + outbox,返回 MessageItem(seq, id);
Outbox-relay 按 conversation_id 作为 key 写 Kafka;
delivery 消费后依据 presence 找节点推送,失败重试/死信;离线只推进 inbox.delivered,上线按 seq 回放 结果:顺序有了抓手(seq + 分区),可靠有了抓手(Outbox + 重放),扩展有了抓手(写入/投递各自扩容)
你在做架构转型时遇到的最麻烦的一个点是什么,有没有遇到比较典型的一些困难
你觉得使用微服务这种方式来部署的优点是什么,当时为什么要用这种方式来部署你的服务
微服务你分了哪几块呢?
你在构建文件处理服务时有没有做一些优化呢?比方说对存储的一些交互
你提到的这个缓存我比较好奇,比如用户上传一个文件,在数据库里有这样的一个文件的信息记录,文件被上传或被下载实际对应的请求是到后端的存储对吧,这个存储你用的是什么类型的?比如说你去申请云厂商,会让你选存储的类型,有没有关注选的是哪一种类型的存储呢?
那你用的是什么协议呢,和文件上传下载相关的一些协议,或者你是用的哪个 sdk 去和文件系统做的交互?(答了 aws,面试官提示说是 s3)
能讲一下音视频服务用到的协议的一些概念吗?就 WebRTC 这一块的概念,以及你在做这个项目里面会遇到一些什么问题
你说有一个专门用作用户管理的微服务,就是用户信息和鉴权,那首先一点,你用的鉴权方式是什么呢?然后和前端那块是怎么交互的?
你刚刚提到了用 jwt 的 token 去做整个系统的鉴权,一个是 auth token,一个是 refresh token,那你是如何去验证这个 token 的有效性的?第二个点是,如果前端发送的请求带的是一个有效的 refresh token,那你是怎样继续去后面的逻辑,让这个请求可以完整的去返回给前端呢?
网关这一块你主要是涉及到哪一些开发?
网关的逻辑是你自己写的吗,还是说延续了之前的
对于 HTTP 请求,这个网关是怎样的逻辑去转发给后端不同的服务的?比方说用户验证,或者说刚刚提到的音视频请求,你是怎样分发给这些服务的,你微服务之间是怎样去做协调的?
它的网络连接是怎么走的?比如现在有一个请求进来,到网关之后被认为请求是有效的,你会将相应的一个 message 发到消息队列中对吧,所有的微服务都在监听着这一个事件中心,或者说是 eventbus,比方说我是属于一个音频服务,我现在需要返回我的服务给前端,它后续的逻辑是怎么走的?完整的请求链是怎么实现的?
-
你在这个项目中是把网关做成了适配器层吗,还是转发层
-
你这个网关是业务网关还是流量网关
-
那假如现在让你把这个项目做成一个高性能的服务,部署在各个国家各个地区的节点,那你会怎么设计这个系统
-
你的这个项目有什么亮点吗
-
那你是如何界定多个模块之间的领域边界的呢
-
项目做了哪些功能?
-
挑一个你开发的功能模块介绍
-
为什么要做这个项目?项目规模多大?做之前有调研过其他同类产品吗?
-
你觉得你的项目相比别人有什么优点?
-
如果要优化你的项目,可能会有哪些可以优化的点?
-
回顾整个项目,你最自豪的技术实现点是什么?
-
如果给你一次重构机会,你最想改进或重写哪部分?为什么?
-
分析一下你之前项目当中,印象比较深刻的部分(难点,亮点)
-
项目的高并发是怎么做的?是自己假想了高并发的场景?
-
项目的同步机制和低延迟通信是怎么做的?
-
项目里有没有使用过多线程,线程安全问题
-
为什么项目要用微服务
-
是如何具体用JWT实现身份认证的?
-
(如果简历里有写压测)10w+ 并发用户是怎么测的,用什么工具?
-
(如果简历里有写部署)项目服务器是怎么部署的?用到了几台服务器?
-
项目为什么要用 JWT
-
项目的代码量大概是多少?是你自己一个人在写吗,项目是怎么来的
-
介绍一下你简历上的几个项目
-
我刚刚听你有提到你对项目进行了 DDD 的重构,能解释一下 DDD 的概念吗
-
那假如现在我们有两个不同的领域,但是它们需要共用用户的数据,那你这时候该如何把用户的数据按照领域去拆分呢,你有遇到过这种情况吗
面试时可拓展:
- CAS/乐观锁与可能存在的ABA问题、Redis Lua 脚本
- 分布式多节点
- 多端同步
- 强一致性保证
- 已读/未读
- 撤回、重新编辑