微服务杂念

微服务杂念

微服务已经是一个不再新鲜的概念,但我却依然模糊不清。

微服务,抛开 微 不看,首先是一个面向服务的架构,即 SOA。系统模块,业务逻辑之间的界限是服务,服务为 服务名+方法名,与 CRUD/REST 不同。服务重在交互,CRUD/REST 重在数据。

SOA 曾经有 SOAP、 Java RMI,现在主流的 gRPC 和常见的 JsonRPC。这些定义的都是服务交互协议,而微服务更多的关注服务的组合、分布、编排、容错、发现、通信、跟踪等。因此会想到这样的一些主题:

  • 服务通信
    • 点对点 - 简单好理解,想要稳定使用则会导致实现复杂
    • 广播总线 - 实现简单但依赖额外服务
    • 网关 - 实现简单,易于理解,额外的一次跳转
  • 服务序列化
    • 一个发展了很久,认为已经被解决的问题
    • 曾经序列化主要以性能作为考虑,现今计算冗余,前期选择最简单的 JSON 已经足够
  • 服务发现
  • 服务容错

服务通信

服务之间的通信可类比为网络通信,主要分为点对点,广播和网关:

  • 点对点场景
    • 前后端通信
    • 服务端调用
  • 广播场景
    • 服务发现
    • 服务总线
  • 网关场景
    • 前后端通信
    • 服务路由
    • 协议适配
    • 鉴权

从服务提供方来说服务的通信也可以区分为主动和被动:

  • 主动
    • 监听过滤特定服务进行处理
      • 例如 监听 Topic,维护链接稳定性,过滤只处理本地提供的服务
    • 长链接注册服务
      • 链接断开则服务停止
  • 被动
    • 监听固定接口
      • 例如 监听本地 8080,提供 JsonRPC,处理主动请求过来的服务调用
    • 启动时注册服务
      • 由注册中心服务定期检测服务存活

点对点通信

HTTP

目前 HTTP 在快速发展,新的协议提高的主要是性能和对长链接流的支持,如果选择简单实现服务调用,HTTP 请求足以。

使用 HTTP POST 实现 单次请求响应,使用 WebSocket 实现流式请求。使用 HTTP 的好处是能利用现有的基础设施,HTTP 是目前支持最为广泛的协议,也是最受关注的协议。

但选择 HTTP 时,如果自行实现,则最好选择 HTTP 1,避免使用 HTTP 2 等新增特性,HTTP 2+ 能传递 HTTP 1 的所有语义,且性能各方面能得到很好的提升,这样足以。如果要独立使用 HTTP 协议新特性,例如 Trailer,则实现会变得过于复杂。

非 HTTP 语义

当使用非 HTTP 语义语义时,可能是基于 TCP,也可能是对 Payload 进行定义,抽象传输层,但如今想要跨语言,使用非 HTTP 协议阻碍会非常大,因此宁愿选择对 Payload 做自定义也不会选择不使用 HTTP。

点对点网关

通常点对点是需要直接通信,但现今有提供 Mesh 网络的网关组件,使得原本不互通的网络服务能够互相访问。同时也能实现对应的服务之间的权限管理和监控。

使用 Mesh 网关则可以让客户端的网络层实现更为简单,由网关去维护服务网络的稳定性,对服务网络的变化也能更快的反应。但成本是维护额外的基础设施组件,且注册中心与网关有强关联和交互,每个节点运行一个网关也是额外的资源消耗。

广播通信

服务总线本来是一种设计模式,比较常见的是基于事件驱动的设计,但在服务场景会有所不同。事件驱动一般有固定的 主题/Topic ,在定订阅初已经确定了部分语义。而服务总线则可以理解为只有一个 Topic,通过消息的内容来进行过滤谁处理谁不需要处理。

总线的通信模式解决了服务之间网络隔离的阻碍,不再需要 A服务 与 B服务 网络互通,网络的变化也不会阻碍服务的通信。但如果总线本身如果不支持一些 meta 信息的模式匹配,则会导致流量过大,处理能力受限。

选择广播通信,则必然会选择某种形式的 消息队列/MQ 实现,因此会牵扯到其他的基础设施服务,从部署跟踪上来说,这是扣分的。

网关通信

网关通信与 Mesh 网关不同,Mesh 的目的在于打通节点,网关的目的在于磨平/适配不同的环境服务,提供相同的能力。

所有的请求通过网关,由网关进行路由,这是典型的 Nginx 反向代理场景。因为所有请求都加了中间层,因此很多事情都可以在中间层完成:

  • 路由
    • 服务 A -> 节点 B
  • 监控
    • 采集服务请求指标
  • 鉴权
    • 用户是否允许访问服务
    • 服务之间是否允许通信
  • 适配
    • 前端 JsonRPC -> 后端服务 gRPC
  • 跟踪
    • 添加 RequestID
    • 记录请求到 Elastic
  • 集成服务
    • 服务 A -> 外部服务 B
  • 服务错误熔断
  • 负载均衡
  • TLS

该模式与实际最底层的服务通信有有一点区别,主要作为适配和耦合存在,如果网关功能越多做的事情越多也越容易造成单点瓶颈。

通信与其他组件的关联

通信是服务的根本,对于微服务更是如此。通信的模式间接或直接的影响其他组件的选择和设计。但不同的通信模式并不是互斥的,在对于的场景选择对于的方式才是最好的选择。

  • 点对点
    • 优势
      • 易于跟踪
      • 易于理解
      • 可利用现有基础设施
    • 注册时候需要注册本地 IP、端口
      • 如果是被动,则还需要 IP 和 端口能够被注册中心访问以确保服务健康
      • 如果是主动,则可能注册中心会成为瓶颈
    • 受网络影响较大
      • 部署节点变更可能需要一定时间才能更正为新的地址
      • 影响因素主要有:
        1. 检测到服务下线的时间
        • 对于被动场景则是 心跳间隔失败次数 - 一般为 `15s 3 = 45s` - 不进行主动干预的情况
        1. 客户端检测到新服务信息的时间
      • 为了减少网络影响,可能抽象服务为 VIP
        • 例如 K8S 的服务定义,节点注册的地址作为 endpoing,实际请求 service
        • 引入新的基础设施服务
    • 重客户端
      • 想要实现稳定的服务请求,客户端设计实现会变得复杂,主要是服务发现和元数据变更相关。
    • 可用于 Web 端 - 但通常还是会使用 API 网关进行暴露
  • 广播/总线通信
    • 优势
      • 架构简单
      • 部署时对环境要求更小
    • 注册时候只需要注册基本信息即可,消费端和提供端基本可以实现 0 耦合
      • 点对点需要知道提供端的地址信息
    • 强依赖额外的消息队列服务
      • 实时型消息队列
      • 使用消息队列带来额外延时
    • 提供服务的一端必须是长链接监听消息
      • 因此 PHP 这类的 CGI 脚本语言比较难以使用
  • 网关
    • 优势
      • 整合
    • 不一定与实际服务调用相关
    • 与注册中心耦合 - 受注册中心控制
    • 不管选用何种通信,一般都会存在一个网关

服务发现

服务发现通常只是一种行为,但实际使用的应该是服务注册中心,主要能力包括:

  • 记录服务节点信息 - 面向服务提供者
    • 提供 A 服务节点的 IP 是多少,端口是多少
  • 提供服务节点对应关系 - 面向服务消费者 - 发现
    • 谁提供 A 服务
  • 服务元数据记录 - 服务管理
    • A 服务有那些版本,有那些方法,参数类型是什么样的
    • A 服务是用来做什么的服务
  • 服务健康状态 - 服务维护
    • 主动检查
    • 被动上报
  • 服务安全 - 通常需要配合网关
    • A 服务是否允许调用 B 服务

服务注册中心通常不是,而是一个集群,其中的服务元信息也可被同步到 RDBMS 便于开发人员查看和使用,场景例如:

  • 搜索接口
  • 查找接口文档
  • 查看服务状态
  • 生成客户端代码
  • 测试调用服务

服务容错

服务容错包含服务异常、节点异常、网络异常,不同的异常可能采取不同的应对方式,我理解的常见功能包括

  • 限流
  • 重试
  • 负载

容错的实现通常配合监控,网关,注册中心。算是优化服务体验的一部分,可以作为延伸支持考虑。

微服务

微服务,微的是除了服务之外的部分,也就是上述的所有内容。微服务的期望是让开发只关心业务逻辑的编写,开发好的业务逻辑能够快速稳定的上线被使用。

如果不在一套完善的体系下谈论开发微服务都是没有意义且难以实现的,而这套体系的搭建不背靠现在的 K8S 之类的体系也是难以成形的。

总结

将最近混乱思绪进行梳理,不一定都是正确,仅作为参考便于理解,引导之后的选择和决定。

参考