分布式通信
分布式系统的本质是多进程协作,共同完成任务。要协作,自然免不了通信,这也是分布式通信技术要解决的核心问题。
概述
按照"请求从外到内、从大到小"的层次,分布式通信涉及以下关键技术:
- 负载均衡:把外部请求合理分配到集群内部的多个服务器
- API 网关:作为系统入口聚合多个微服务调用
- 服务注册与发现:解决分布式系统内部服务如何感知彼此的问题
- 服务间的远程调用(RPC):让不同机器上的服务像本地调用一样通信
- 异步通信模式:当同步直接通信难以承载时,使用发布订阅或消息队列解耦
而从通信模式上看,主要分为三大类:
| 通信方式 | 思想 | 典型实现 |
|---|---|---|
| 远程调用(RPC) | 千里眼:直接调用远程方法 | Dubbo、gRPC、RMI、Netty |
| 发布订阅 | 送货上门:消息中心主动推送 | Kafka |
| 消息队列 | 货物自取:消费者按需拉取 | RocketMQ、RabbitMQ、Kafka |
负载均衡
什么是负载均衡
分布式系统通过"服务器集群"提升整体处理性能——把同一个服务扩展到不同的机器上,每次请求该服务时选其中一台服务器响应,无论在哪台服务器上执行,都能得到相同的响应。高性能集群的设计主要体现在请求分配上,这种将请求按一定规则分配到不同服务器执行的过程叫作负载均衡,完成负载均衡的组件叫作负载均衡器。
负载均衡并非"平均"分配,需要根据业务考虑策略(服务器负载、性能、业务类型),具体的分配规则就是负载均衡算法。
负载均衡的分类
以客户端请求应用服务器为例,典型的负载均衡分为三层:
- DNS 负载均衡:DNS 服务器根据用户所在网络区域,返回最近机房的 IP,实现区域级别的负载均衡。优点是成本低、用户就近访问;缺点是依赖 DNS 缓存,刷新不及时,且支持算法较少。
- 硬件负载均衡:如 F5 BIG-IP、NetScaler、Radware、Array。优点是支持算法多、并发高(百万级)、经过严格测试更稳定,还具备防火墙、防 DoS 等安全功能;缺点是贵,业务发展到一定规模才会用到。
- 软件负载均衡:如 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 网关的技术原理
- 协议转换:不同协议通过"通用协议"(如 JSON)中转,API 网关负责两端协议的翻译。
- 链式处理:采用责任链模式,消息流过一系列过滤器。Netflix Zuul 就是。
- 异步请求: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
- 每个主题分区中队列数量可由用户指定
- 队列是资源分配的基本单元
工作流程
- 启动 NameServer
- 启动 Broker,主动向 NameServer 注册并定期发送心跳(默认 1s 一次),可携带存储数据信息
- 创建主题,确定主题数据放入哪些 Broker
- Producer 发消息:先到 NameServer 查询主题对应的 Broker,再发送存储
- Consumer 消费消息:先到 NameServer 查询 Broker,再去拉取消息
适用场景
消息队列模式中,消费者按需去消息队列获取数据消费,消息队列中心无需提前知道消费者信息,因此适合消费者为临时用户的场景。
发布订阅 vs 消息队列
| 维度 | 发布订阅 | 消息队列 |
|---|---|---|
| 存储结构 | 消息中心(map/数组等) | 队列(FIFO) |
| 触发方式 | 消息中心主动推送给订阅者 | 消费者主动拉取 |
| 消费者要求 | 需提前订阅(适合长驻进程) | 无需提前订阅(适合临时用户) |
| 一条消息消费次数 | 可被多个订阅者消费 | 一般一个消费者消费一次 |
重复消费
这是幂等性问题。常见方案:
- 消息端控制:Kafka 用offset(消息序号)记录消费位置,Consumer 定期提交 offset 表示已消费;重启后从 offset 继续。
- 业务幂等:以订单号等唯一标识为去重键,数据库加唯一索引,或在内存中用 set 去重。
- Consumer 记录 offset:Broker 返回当前消费的偏移量,Consumer 记录,下次从该位置开始
重复消费本身不可怕,关键是要保证业务幂等性。
总结
场景对照
- 远程调用 RPC
- 发布订阅
- 消息队列
完整链路
- 外部请求进入:客户端 →DNS 负载均衡(区域级)→硬件负载均衡(F5,接入层)→软件负载均衡(Nginx,代理层)→ 应用服务器
- API 网关聚合:在客户端与微服务之间,API 网关做协议转换、链式处理、异步请求,聚合多个微服务调用
- 服务注册与发现:微服务间通过服务注册中心感知彼此,心跳保活,本地维护路由表
- 服务间通信:根据业务场景选型——同步直接通信用RPC(Netty),异步间接通信用发布订阅(Kafka)或消息队列(RocketMQ)
选型原则
- 同步 vs 异步:需直接结果(查询、下单)用同步 RPC;耗时任务(发邮件、推送)用异步消息中间件
- 临时 vs 长驻消费者:长驻进程适合发布订阅(消息中心推送);临时用户/任务型适合消息队列(拉取模式)
- 强解耦 vs 高一致性:发布订阅和消息队列都能解耦;要求强一致性的链路仍需 RPC 直接调用
- 容量与吞吐:百万级并发在代理层之上加 F5;RPC 通信采用多路复用 IO;消息中间件采用多分区+消费组提升吞吐