核心路由原理:请求转发与负载均衡
路由转发,说白了就是API网关的“交通指挥”。客户端发来一个请求,网关得知道往哪送。这个“知道往哪送”的过程,就是我接下来要讲的核心。
我记得刚入行那会儿,觉得路由转发不就是查个表嘛。后来线上出了问题才发现——嗯,远没那么简单。请求怎么匹配、怎么分发、出错了怎么办,每一步都有坑。
请求转发的三个核心要素
要理解请求转发,得先搞清这三个东西:
- 路由表:存放着“什么请求该去哪”的规则集合。说白了就是一张映射表。
- 路由规则:定义如何匹配请求。比如根据路径、方法、Header甚至Cookie来匹配。
- 目标服务:后端真实提供业务的集群地址,一般用服务名或IP:Port表示。
我习惯把路由表看成“字典”。Key是请求特征,Value是目标集群地址。每次请求进来,就从这个字典里查一下。查不到?直接返回404。
核心流程:客户端请求 -> 解析请求特征(Path/Method/Header) -> 匹配路由规则 -> 找到目标服务 -> 转发请求
路由规则匹配:到底靠什么定位?
路由规则的匹配方式多种多样。不同业务场景,选择也不同。我给你列个对比表:
| 匹配方式 | 示例 | 适用场景 |
|---|---|---|
| 路径前缀匹配 | /api/order/* | RESTful API 分组 |
| 精确路径匹配 | /api/order/create | 单接口独立路由 |
| 方法+路径组合 | GET /api/order/:id | CRUD 统一资源 |
| Header条件匹配 | X-Version: v2 | 灰度/AB测试 |
| 多条件组合 | Path + Header + Cookie | 复杂分流策略 |
你想想看,如果只靠路径匹配,遇到灰度发版怎么办?我去年做个项目,线上流量要按1%比例导到新版本。这时候就得用Header条件匹配加权重控制。路径匹配?压根不够用。
个人习惯:路由规则尽量写在配置中心,不要在代码里硬编码。改了规则还要重启网关?那太原始了。
负载均衡:把流量均匀打散
请求找到了目标服务,但服务背后往往是一组机器。选谁?这就是负载均衡要干的活。
我遇到过最惨的一次事故:某次上新版本,两台机器配置不一样。结果网关用了轮询,压力全压到那台低配机器上。CPU飙到100%,响应超时,连锁故障。从那以后,我对负载均衡策略就特别敏感。
常见策略有这些:
- 轮询:挨个来。简单但容易“偏科”。
- 加权轮询:机器强就多分点流量,弱就少分。我比较推荐。
- 最少连接数:谁空闲找谁。适合长连接场景。
- IP Hash:根据客户端IP固定后端。用来保证会话一致性。
- 一致性Hash:比IP Hash更平滑,扩容缩容影响小。
来看个加权轮询的配置示例,我用的是类Nginx的语法:
upstream order_service {
server 192.168.1.10:8080 weight=5;
server 192.168.1.11:8080 weight=3;
server 192.168.1.12:8080 weight=2;
}
location /api/order/ {
proxy_pass http://order_service;
}
权重高的机器,被选中的概率就大。但要注意——概率,不是顺序。别指望它完全均匀。
注意:加权轮询不是万能的。如果你后端服务的CPU/内存差异很大,加权也未必精准。更精细的做法是配合动态权重,根据实时负载调整。
健康检查与熔断:别把请求发给“死人”
刚才提到的负载均衡,前提是所有后端机器都活着。但现实中机器会挂、网络会抖、服务会慢。怎么办?
我习惯把健康检查分成两种:
- 主动检查:网关定期向后端发心跳,不通就摘掉。
- 被动检查:记录请求失败次数,达到阈值自动熔断。
两者结合最稳。曾经有一次,某个服务JVM Full GC,心跳没问题(端口还活着),但请求响应已经超5秒了。这时候被动检查就发挥作用——连续超时3次后,直接熔断10秒。等服务恢复,再慢慢加回流量。
配置示例:
# 健康检查
health_check {
interval = 5s
timeout = 2s
unhealthy_threshold = 3
healthy_threshold = 2
}
# 熔断配置
circuit_breaker {
max_failures = 5
reset_timeout = 30s
half_open_max_requests = 3
}
请求转发的完整流程
把上面讲的串起来,就是下面这张图。你可以看到每一个环节:
从图上你能看到:请求先匹配路由规则,然后进入负载均衡选机器。选完机器还不算完——得确保那台机器是健康的。不行就熔断、降级。最后才真正转发。
我曾经踩过的坑:健康检查和负载均衡是两套独立逻辑。有些网关框架默认只做被动健康检查,没有主动心跳。长连接场景下,一个连接池满了,新请求全堆到那台机器上——你想想看,整个集群的吞吐量直接崩掉。
动态路由与配置热加载
线上环境,路由规则永远在变。新增接口、调整权重、上线灰度——总不能每次改个规则就重启网关吧?
我建议把路由配置放到外部存储里,比如Nacos、etcd或者Consul。网关监听配置变化,然后热加载到内存中。整个流程是:
- 配置中心推送新规则
- 网关监听器收到变更事件
- 解析并校验新规则(格式对不对?)
- 更新内存中的路由表
- 旧请求继续走旧表,新请求用新表(避免惊群)
代码上怎么实现?来看一个伪代码的思路:
// 配置监听器
@EventListener
public void onRouteConfigChange(RouteConfigEvent event) {
List<RouteRule> newRules = parseRules(event.getContent());
// 校验合法性
validate(newRules);
// 原子替换
this.routeTable.set(newRules);
log.info("路由表已更新,当前规则数: {}", newRules.size());
}
注意那个 原子替换。说白了就是用读写锁或者CAS,保证更新时不会出现“读到一半”的脏数据。
一次转发,多少毫秒?
路由转发本身性能开销其实很小。匹配路径、查表、选机器——大部分情况下可以控制在1ms以内。真正的开销是对后端建连和传输数据。所以:
- 路由表用Hash或者前缀树(Trie),别用遍历匹配
- 连接池要复用,别每次转发都建TCP连接
- 负载均衡选机器时别做太多计算,提前算好权重
我个人的经验:网关层的路由转发,延迟超过5ms就要警惕了。排查方向一般是两个:一是路由表太大,匹配慢;二是连接池用完了,在等空闲连接。