分布式通信

分布式通信

分布式系统的本质是多进程协作,共同完成任务。要协作,自然免不了通信,这也是分布式通信技术要解决的核心问题。

概述

按照"请求从外到内、从大到小"的层次,分布式通信涉及以下关键技术:

  • 负载均衡:把外部请求合理分配到集群内部的多个服务器
  • API 网关:作为系统入口聚合多个微服务调用
  • 服务注册与发现:解决分布式系统内部服务如何感知彼此的问题
  • 服务间的远程调用(RPC):让不同机器上的服务像本地调用一样通信
  • 异步通信模式:当同步直接通信难以承载时,使用发布订阅或消息队列解耦

而从通信模式上看,主要分为三大类:

通信方式思想典型实现
远程调用(RPC)千里眼:直接调用远程方法Dubbo、gRPC、RMI、Netty
发布订阅送货上门:消息中心主动推送Kafka
消息队列货物自取:消费者按需拉取RocketMQ、RabbitMQ、Kafka

负载均衡

什么是负载均衡

分布式系统通过"服务器集群"提升整体处理性能——把同一个服务扩展到不同的机器上,每次请求该服务时选其中一台服务器响应,无论在哪台服务器上执行,都能得到相同的响应。高性能集群的设计主要体现在请求分配上,这种将请求按一定规则分配到不同服务器执行的过程叫作负载均衡,完成负载均衡的组件叫作负载均衡器

负载均衡并非"平均"分配,需要根据业务考虑策略(服务器负载、性能、业务类型),具体的分配规则就是负载均衡算法

负载均衡的分类

以客户端请求应用服务器为例,典型的负载均衡分为三层:

  1. DNS 负载均衡:DNS 服务器根据用户所在网络区域,返回最近机房的 IP,实现区域级别的负载均衡。优点是成本低、用户就近访问;缺点是依赖 DNS 缓存,刷新不及时,且支持算法较少。
  2. 硬件负载均衡:如 F5 BIG-IP、NetScaler、Radware、Array。优点是支持算法多、并发高(百万级)、经过严格测试更稳定,还具备防火墙、防 DoS 等安全功能;缺点是贵,业务发展到一定规模才会用到。
  3. 软件负载均衡:如 LVS、HAProxy、Nginx。配置简单、使用灵活、成本低,可满足一般负载均衡需求。

硬件负载均衡器 F5 的三大类功能:

  • 多链路负载均衡:同时接入多家 ISP(网络服务供应商),通过 INBOUND 解析请求、OUTBOUND 配合 NAT 源地址转换,保证网络高可用。
  • 防火墙负载均衡:通过"交换机—防火墙—交换机"的"三明治"架构,在完成流量分发的同时维持用户会话完整性,避免多防火墙拆分会话。
  • 服务器负载均衡:对挂接的多个应用服务器做负载均衡,支持健康检查、虚拟 IP(VIP)、按服务类型/端口/内容分流等。

软件负载均衡

软件负载均衡所在的代理层"上连接入层(硬件负载均衡),下接应用服务器",可实现反向代理、负载均衡、动态缓存、过滤等功能。

反向代理与负载均衡:Nginx 等反向代理可对外隔离、对内路由,当某个服务(如订单服务)需要水平扩展时,通过负载均衡在多个实例间分配请求。

Nginx 的并发量计算示例:

worker_processes 2; # 双核 CPU worker_cpu_affinity 01 10; events { use epoll; worker_connections 15000; # 单进程最大连接数 }

最大并发量 ≈worker_processes × worker_connections = 2 × 15000 = 30000

百万级并发的方案:在代理层之上加入接入层(硬件 F5),由 F5 抗大流量,再转给 Nginx。

动态缓存与过滤:把变化不频繁的数据放到代理层作为缓存,降低回源频率。例如使用 Nginx + Lua 脚本从 Redis 校验一些信息。

负载均衡算法

算法说明适用场景
round-robin(轮询)默认算法,挨个请求上游服务器日常系统,服务器配置均匀
weight(权重)按权重分配,资源好的机器承担更多服务器配置不均(如秒杀核心服务)
IP-hash同一 IP 始终路由到同一上游需要 session 一致
hash key对 key 取模路由,服务器增减时 key 仍能找到原机器解决 IP-hash 在扩缩容时失准
least_conn把请求转发给连接数较少的服务器请求处理时长差异较大

round-robin 算法确实可以把请求均匀的分发给服务器,但特殊场景下有可能某服务器分配的全是执行耗时长、任务量大的请求,这时采用 least_conn 算法效果会更好。


API 网关

API 网关的定义

网关(Gateway)最早用于描述网络设备——两个相互独立的局域网通过路由器通信,中间的路由器就叫网关。在分布式系统中,网关存在于客户端与微服务系统之间

API 网关在三个层面解决了"门面"问题:

  • 业务层面:客户端完成某个业务需要调用多个微服务,网关把多个调用聚合成一个统一入口(类似门面模式 Facade)。
  • 系统层面:系统 A 和系统 B 之间的 API 互调,可由网关作为中介者。
  • 客户端层面:根据不同客户端(iOS、Android、PC、小程序)提供不同的 API 网关,屏蔽调用差异。

由于 API 网关处在客户端与微服务系统的交界,因此包括路由、负载均衡、限流、缓存、日志、发布等功能。

API 网关的服务定位

按服务对象可分为 5 类:

  • 面向WebAPP的 API 网关(H5/网站,前后端分离)
  • 面向MobileAPP的 API 网关(iOS/Android,需做移动设备管理 MDM)
  • 面向合作伙伴OpenAPI(考虑流量、安全、协议转换)
  • 企业内部可扩展 API(中台输出,需关注边界划分、认证授权)
  • (隐含)按客户端类型分层的多个 API 网关

API 网关的技术原理

  1. 协议转换:不同协议通过"通用协议"(如 JSON)中转,API 网关负责两端协议的翻译。
  2. 链式处理:采用责任链模式,消息流过一系列过滤器。Netflix Zuul 就是。
  3. 异步请求:Zuul 1.x 一个线程处理一个请求,IO 阻塞;Zuul 2 引入异步,把每个请求包装为事件,由 CPU 内核维护监听器轮询,通知时再处理,提升吞吐量。

API 网关和负载均衡器在"路由"层面是相通的,在功能上可以互换。


服务间的远程调用

本地调用指同一进程内的函数调用;远程调用进程间函数调用,是进程间通信(IPC)的一种方式。

按进程是否部署在同一台机器,可分两类:

  • 本地过程调用 LPC:同一台机器上的进程之间
  • 远程过程调用 RPC:不同机器上的进程之间

在分布式领域中,远程调用主要指RPC,关于 RPC 的内容,选择合适的 RPC 已经总结过了,包括 RPC 的组成(WSDL、UDDI、注册中心等)和历史,Java 生态里 Netty 是最流行的网络通信框架。

发布订阅模式

什么是发布订阅

发布订阅的思想随处可见:报纸订阅,作者将文章发布到报社(消息中心),用户向报社订阅自己感兴趣的报纸类型,报社根据订阅信息送报纸。

发布订阅三要素:

  • 生产者(发布者):负责产生数据
  • 消费者(订阅者):向消息中心订阅感兴趣的消息
  • 消息中心:存储发布数据并按订阅关系推送

这种"将数据送到消费者手里"的行为,就是"送货上门"。

发布订阅 vs 点对点

消息系统的两种典型模式:

模式一条消息能被多少消费者消费
点对点 P2P仅一个消费者消费,消费后消息中心不再存储
发布订阅 Pub/Sub可被多个订阅同一主题的消费者消费

Kafka

Kafka 是经典的发布订阅消息系统系统架构,包括Producer、Broker、Consumer三部分(新版本已无 Zookeeper):

  • Producer:发布消息到 Broker
  • Broker:消息中心,本质是 Kafka 集群,负责存储消息、管理订阅、推送数据
  • Consumer:订阅并消费消息

关键概念

  • 主题(Topic):逻辑概念,表示消息类型/数据类型
  • 分区(Partition):一个主题可划分成多个集合,分布在不同 Broker 上
  • 消费组(Consumer Group):多个消费者的集合,组内共同消费主题,每个消息仅由组内一个消费者消费

分区的两大好处:

  • 实现负载均衡:同一个主题的消息分布到多个分区/多个 Broker,避免单点压力过大
  • 实现消息备份:同一分区的内容可存储在多个 Broker,保证系统高可靠

消息过多时,单个 Consumer 消费能力有限,会导致 Broker 存储溢出而丢消息。Kafka 引入消费组解决这个问题——多个 Consumer 并行消费。

发布订阅的关键特征

  • 系统解耦,易于维护:生产者只管发,不知道订阅者数量订阅者只管订阅,不知道生产者何时发布。两者互相独立,单独维护。
  • 异步执行,避免高负载:消息超过消息中心容量时,中心丢弃多余消息,系统不会因消息过多而故障。

负载均衡消费

发布者消息量大、单个订阅者处理能力有限时,可通过多分区 + 多个消费者实现负载均衡。Kafka 中:

  • 分区数 ≥ 消费者数,组内消费者才能均衡消费
  • 一般让一个消费组的消费者数 ≤ 该主题的分区数(或与之成倍数),避免消费者空负载或超载
  • 消费者增减、分区增减时,会触发重平衡(rebalance),重新映射消费者与分区的对应关系,尽量不要人为误操作导致重平衡

消息队列模式

什么是消息队列

相比发布订阅的"送货上门",消息队列是"货物自取":消息或数据放到队列里,谁需要谁就去取。

以"用户注册"为例:

  • 写数据库 400ms + 发邮件 600ms =同步通信 1000ms
  • 引入消息队列后:写数据库 100ms 后即可返回响应(发邮件异步执行),响应时间缩短到 500ms,速度提升一倍

消息队列的定义:基于具有先进先出(FIFO)特点的数据结构,存储具有特定格式的消息数据(如包含消息类型、唯一 ID、消息内容的结构体)。消息以特定格式放入队尾后可直接返回,后续由消费者进程从队头依次取出处理。

好处:削峰填谷,异步通信、实现组件间解耦。

消息队列的工作原理

核心结构三部分:

  • 生产者:产生消息/数据,插入到消息队列(入队)
  • 消息队列:FIFO 数据结构,存储消息
  • 消费者:从队列取出消息处理(出队)

RocketMQ

架构

  • NameServer Cluster:服务器集群,管理 Broker 信息(地址、状态),供 Producer 和 Consumer 发现 Broker。
  • Producer Cluster:生产者集群,多 Producer 并发接收用户数据,提升处理效率;且单点故障不影响整体业务。
  • Broker Cluster:Broker 集群,负责存储消息,每个 Broker 采用主从设计:Master 可写可读,Slave 只读,Master 同步数据给 Slave 做备份,Master 故障切换到 Slave。
  • Consumer Cluster:消费者集群,提升消费能力,避免 Broker 存储溢出。

Broker 存储方式:Topic + Queue

  • 主题是逻辑概念,一个主题可分区,分布在不同 Broker
  • 每个主题分区中队列数量可由用户指定
  • 队列是资源分配的基本单元

工作流程

  1. 启动 NameServer
  2. 启动 Broker,主动向 NameServer 注册并定期发送心跳(默认 1s 一次),可携带存储数据信息
  3. 创建主题,确定主题数据放入哪些 Broker
  4. Producer 发消息:先到 NameServer 查询主题对应的 Broker,再发送存储
  5. Consumer 消费消息:先到 NameServer 查询 Broker,再去拉取消息

适用场景

消息队列模式中,消费者按需去消息队列获取数据消费,消息队列中心无需提前知道消费者信息,因此适合消费者为临时用户的场景。

发布订阅 vs 消息队列

维度发布订阅消息队列
存储结构消息中心(map/数组等)队列(FIFO)
触发方式消息中心主动推送给订阅者消费者主动拉取
消费者要求需提前订阅(适合长驻进程)无需提前订阅(适合临时用户)
一条消息消费次数可被多个订阅者消费一般一个消费者消费一次

重复消费

这是幂等性问题。常见方案:

  • 消息端控制:Kafka 用offset(消息序号)记录消费位置,Consumer 定期提交 offset 表示已消费;重启后从 offset 继续。
  • 业务幂等:以订单号等唯一标识为去重键,数据库加唯一索引,或在内存中用 set 去重。
  • Consumer 记录 offset:Broker 返回当前消费的偏移量,Consumer 记录,下次从该位置开始

重复消费本身不可怕,关键是要保证业务幂等性


总结

场景对照

  • 远程调用 RPC

  • 发布订阅

  • 消息队列

完整链路

  1. 外部请求进入:客户端 →DNS 负载均衡(区域级)→硬件负载均衡(F5,接入层)→软件负载均衡(Nginx,代理层)→ 应用服务器
  2. API 网关聚合:在客户端与微服务之间,API 网关做协议转换、链式处理、异步请求,聚合多个微服务调用
  3. 服务注册与发现:微服务间通过服务注册中心感知彼此,心跳保活,本地维护路由表
  4. 服务间通信:根据业务场景选型——同步直接通信用RPC(Netty),异步间接通信用发布订阅(Kafka)或消息队列(RocketMQ)

选型原则

  • 同步 vs 异步:需直接结果(查询、下单)用同步 RPC;耗时任务(发邮件、推送)用异步消息中间件
  • 临时 vs 长驻消费者:长驻进程适合发布订阅(消息中心推送);临时用户/任务型适合消息队列(拉取模式)
  • 强解耦 vs 高一致性:发布订阅和消息队列都能解耦;要求强一致性的链路仍需 RPC 直接调用
  • 容量与吞吐:百万级并发在代理层之上加 F5;RPC 通信采用多路复用 IO;消息中间件采用多分区+消费组提升吞吐