【自研项目之分布式IM】03. 微服务篇

187 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

按照面试流程,上一节先画出整体架构后就应该到业务服务介绍了。本节将介绍2个核心的微服务。

微服务主要划分出了2个主要的服务,一个logic-service业务服务,另一个push-service推送服务。实际情况会存在更多的服务,总体还是依赖于logic-service去分发消息,如下图: logic服务.png

1. 详解 logic-service

logic-service服务是消息的统一入口,客户端发送的消息都需要从它开始处理。

为什么要把它作为统一入口?

首先我们知道用户发送消息主要有2种方式:

  1. Http方式:使用场景有IM聊天、管理后台操作、程序业务推送等
  2. Tcp/WebSocket长连接方式。

无论使用哪种方式发送消息,都需要对消息的做校验和业务处理。比如:

  • 对发送或接收用户做合法性校验:用户是否存在、是否被封禁等等
  • 消息内容做敏感词检测、消息的落库
  • 消息流量统计:比如消息上行统计等等
  • 自身业务处理等等

复杂的业务逻辑不可能放到Socket服务去做,所以就需要一个统一的入口服务来做这些事(logic-service)。经logic-service处理的消息会转发到队列提供给push-service推送服务或其他下游服务消费。这里队列的主要作用是削峰和解耦。

可能有人会有疑问为什么发送IM消息使用Http的方式?
其实Http方式也可以,虽然实时性不高但特别适合某些场景:服务发送推送、管理后台操作等等

Http方式的优缺点

  • 客户端实现方便
  • 遇到突增流量只需要对logic-service扩容
  • 资源占用比较大实时性较差,若是强实时性的推荐tcp方式。

logic-service总结

  • 作为消息统一入口,处理复杂业务、耗时操作等。
  • 使用MQ解耦,合法的消息会放到MQ队列提供下游消费
  • 高并发、弹性拓展,突增流量只需要拓展该服务,对其他服务无影响

2. 详解 push-service

下面重点关注下push-service。我们知道客户端是和Socket服务建立长连接,而消息又转发到微服务层。那么问题来了,微服务层和Socket层怎么通讯?。答案就在push-service中。

2.1 先简单介绍下客户端和Socket服务建立的长连接分布情况

假设目前有3台Socket服务,用户A/B/C分别和其中一台建立了连接,并且用户ABC有一个群。按照此例子场景说说单聊和群聊的通讯流程。

  1. 单聊:用户A对用户B说:垃圾
  2. 群聊:用户A在群里说:都是垃圾
    下面是消息在系统的流转图:

push与socket通讯.png 从上图得知,问题就在于解决第4步。

思考下如果Socket服务有成百上千台怎么优化?

2.2 push-service服务怎么和Socket服务通讯?

服务之间的通讯实现方式很多,下面介绍下常见的几种方式:

  1. Http
    • 性能较差,不适合高并发。
  2. 基于Redis的发布订阅
    • Socket服务较少的情况下适合
    • 存在丢消息情况
    • 每个订阅者都全量消费消息,对于单聊不友好。
  3. 使用MQ队列
    • 同上类似但不丢数据
  4. gRpc
    • 性能强悍,跨语言。
    • 配置灵活,假设Socket服务高达几百上千时,可以设计分组Group广播消息,后续会讲解这一部分。
    • 支持同步和异步回调:如果使用Redis的订阅发布可能存在丢消息的情况,gRpc提供的异步回调可以避免这种情况)
    • 缺点是没有提供服务发现、负载均衡功能。这里后续会使用Nacos来实现。
  5. 基于Netty自行封装,笔者的公司就是基于Netty自研的通讯组件。
  6. 其他中间件(Dubbo)就不介绍了,欢迎补充~

面试中可能会问gRpc和Dubbo或Feign的区别

本项目选用的是gRpc方式,如果对gRpc不熟悉的朋友可以先了解下,或者替换成Dubbo这种中间件来理解也是可以的。
到此上面2.1中第4步的问题就解决了微服务和Socket服务的通讯了。

解决了通讯方式似乎还不够,push-service怎么知道某台Socket服务的地址?这就需要一个调用服务的路由组件,如果不好理解这个路由是什么,下面看一个例子:

举个栗子

假设你(push-service)是一个海王,你和10个妹子/帅哥(Socket服务)有联系。你现在想给妹子A发信息,只需要在这10个妹子的微信号(ip路由)中找到妹子A的微信发送就行。

如果使用的是Redis的发布订阅,那完蛋了,10个妹子都收到了。

从例子中得知,路由组件的作用就是让push-service找到目标用户的Socket服务(说白了就是妹子A连接的Socket服务的ip+port地址),知道了ip和端口,就可以使用gRpc进行通讯了。

上述例子只是粗略介绍路由,其中还有很多关键信息还没披露,比如push-service怎么知道Socket服务的ip+port、客户端重连TCP后不在同一个Socket服务怎么办...这些后续会一一详解。

push-service总结

  1. 老规矩,高并发、弹性拓展必须得有
    • 消费logic-service的MQ消息
    • 当MQ发生堆积可以弹性拓展push-service增加消费者(需结合gRpc的消费能力)
  2. 负责与Socket服务通讯,推送消息到Socket服务

思考题

下面是针对push-service的几个思考题:

  1. 过期消息怎么处理。
  2. 怎么优化消息的推送速度。(这里先不谈中间件优化)
  3. 怎么保证消息一定能推送成功。

以上是微服务的介绍,下一篇将详细介绍push-service的路由组件实现。文章若有错误或建议,欢迎留言~