简历里写着“熟悉TCP/IP”,可线上环境一条错误的路由规则就让整个微服务链路超时——这是很多后端人踩过的坑。
今天我们从IP协议、路由表、最长前缀匹配一路聊到Nginx反向代理和Docker网络模式,把计网八股变成真正能帮你排查问题的工程思维。
写在前面
在开发智荟知识库系统和知识汇教育平台的过程中,我踩过不少“网络层”的坑:
用SpringCloud开发微服务时,网关路由配置错误导致服务之间调不通;
用Nginx做反向代理,因为不熟悉
proxy_pass末尾的斜杠规则,静态资源404;用Docker部署Python的FastAPI和Java的SpringBoot,容器间通信全靠
--link和自定义网络。
回头再看408里那些枯燥的IP、路由表、ARP,忽然觉得它们全都“活”了。
所以这篇博客不背概念,而是从一个后端开发者的视角,重新理解IP协议、路由与寻址,并且告诉你:它们在Nginx、Docker、微服务网关里到底是怎么体现的。
一、IP协议:不只是“门牌号”
1.1 为什么我们离不开IP?
IP协议的核心职责就两件事:编址+封装。
编址:给每个联网设备一个唯一的IP地址(比如你的服务器内网IP
172.17.0.2)。封装:把上层数据(TCP/UDP报文)塞进IP数据报,再扔给链路层。
面试时常问:IP和MAC地址都唯一,为什么不能只用一个?
我的理解:MAC地址像身份证号,一辈子不变,但无法“寻路”;IP地址像你现在的住址,可以随网络位置变化,并且路由器根据IP前缀就能快速决定往哪个方向扔。快递员不会拿着你的身份证号送货,他看的是地址。
1.2 IPv4 / IPv6 与子网掩码
在实际开发中,你配过最多的就是192.168.x.x和10.x.x.x这类私有地址。
子网掩码决定了一个IP的“网络部分”和“主机部分”:
敲黑板:同一个子网内的设备可以不经过路由器直接通信(通过ARP协议找MAC地址)。不同子网之间通信,必须经过网关(通常是路由器)。
工程关联:
Docker容器默认的桥接网络模式,会给每个容器分配一个
172.17.0.0/16网段的IP,容器之间可以直接通信,但要从宿主机访问容器端口,需要做-p 8080:8080端口映射——这就是一种网络地址转换。你在
application.yml里配置的server.address如果不小心填错了网段,可能导致服务只在某个虚拟网卡上监听,外部死活连不上。
1.3 IP数据报里藏着什么?
一个IP包长这样(头部20~60字节,后面是数据):
| 版本 | 首部长度 | 服务类型 | 总长度 | | 标识 | 标志 | 片偏移 | | TTL | 协议 | 首部校验和 | | 源IP地址 | | 目的IP地址 | | 选项(如果有) | | 数据(TCP/UDP报文等) |对开发有用的两个字段:
TTL(生存时间):每经过一个路由器减1,变成0就丢弃。
ping命令返回的ttl=64说明目标大概率是Linux系统(默认初始TTL 64),如果ttl=128则是Windows。协议字段:值为6表示上层是TCP,17表示UDP。抓包时靠这个区分。
一个小实验:在Linux上traceroute baidu.com能看到每一跳的路由器IP,原理就是利用TTL递增来触发ICMP超时报文。
二、路由与寻址:路由器是怎么“问路”的?
2.1 路由表:每台设备的决策依据
不只是路由器有路由表,你的电脑、你的云服务器也都有。
查看本机路由表:
# Linux / macOS ip route show # 示例输出 default via 192.168.1.1 dev eth0 proto static metric 100 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.105 metric 100 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1解释一下:
default via 192.168.1.1:默认路由,所有不在直连网段的目标IP都交给192.168.1.1(你的家用路由器/网关)。192.168.1.0/24:直连路由,发往这个网段的包直接通过eth0发出,不需要网关。172.17.0.0/16:Docker网桥,宿主机和容器通信走这条路。
开发中的实际场景:
你在云服务器上配了VPN,或者用Docker启动了多个容器网络,路由表会动态变化。有一次我的SpringBoot应用无法访问外部的Redis,排查半天发现是容器路由被覆盖,所有流量被指向了错误的网关。
2.2 最长前缀匹配:路由器怎么选“最精确”的路?
假设路由表里有这两条:
192.168.1.0/24 via 10.0.0.1 192.168.1.128/25 via 10.0.0.2现在要转发目标IP为192.168.1.130的包。
两条规则都匹配,但第二条的掩码是/25(255.255.255.128)比第一条/24更精确,所以选择10.0.0.2。
工程隐喻:这就像你在微服务网关里配置路由——/order/**和/order/query/**同时存在时,更具体的路径会优先匹配。
2.3 路由是如何一步步完成的?(简化版)
主机A发出一个IP包,目标IP为
8.8.8.8。主机A查自己的路由表:发现不在任何直连网段,于是交给默认网关
192.168.1.1。网关路由器收到包,查自己的路由表(可能包含去往
8.0.0.0/8的边界路由),转发给下一跳。每一跳重复这个过程,直到到达目标主机所在的子网。
最后一跳路由器通过ARP找到目标主机的MAC地址,把IP包封装成帧发送过去。
这就是“路由”的本质:逐跳转发,每个路由器只知道下一跳,不需要知道整条路径。
三、这些计网知识在Java后端开发中到底有什么用?
3.1 Nginx 路由与反向代理:你每天都在配置的路由表
Nginx 的location指令本质上就是一个基于URI的路由表,匹配规则和IP路由的最长前缀匹配如出一辙:
server { listen 80; # 精确匹配 location = /health { return 200 "OK"; } # 前缀匹配(类似 /24) location /api/ { proxy_pass http://backend_java_app:8080/; } # 正则匹配(更具体) location ~* ^/api/v2/order { proxy_pass http://order_service:8081/; } }反向代理的“反向”是什么意思?
传统正向代理是代理客户端(比如你翻墙),反向代理是代理服务器——客户端不知道真正提供服务的是哪台机器。Nginx收到请求后,根据location规则(类似于路由表),把请求转发到后端的Java应用、Python服务或者静态文件目录。
坑点提醒:proxy_pass末尾的斜杠很重要。
proxy_pass http://backend/:会把/api/user代理到backend/api/userproxy_pass http://backend(无斜杠):会把/api/user代理到backend//api/user(多一个斜杠,可能导致路径错误)
这不就是字符串拼接版的“路由前缀匹配”吗?
3.2 Docker 网络模式:IP和路由的容器化演绎
Docker默认的bridge网络模式会在宿主机创建一个docker0虚拟网桥,每个容器分配一个172.17.x.x的IP。
容器间通信靠的是宿主机的路由转发和iptables规则。
你可以通过docker network create --subnet=10.0.0.0/24 mynet自定义一个网段,然后让容器加入这个网络——这就相当于在宿主机内部建了一个小的“局域网”。
实战经验:
在知识汇教育平台项目中,我用Docker Compose同时启动了SpringCloud微服务、Elasticsearch、MinIO和RabbitMQ。如果不在同一个自定义网络里,服务之间只能通过暴露端口+宿主机IP访问,非常别扭。把它们都加入同一个backend_net(比如172.20.0.0/16),就可以直接用容器名作为主机名互相访问——背后的原理就是Docker内置的DNS解析和路由规则。
3.3 微服务网关:企业级路由和寻址
在SpringCloud Gateway中,路由配置更加声明式:
spring: cloud: gateway: routes: - id: order_route uri: lb://order-service predicates: - Path=/order/** - id: user_route uri: lb://user-service predicates: - Path=/user/**这里的Path匹配规则类似于IP路由中的前缀匹配,而lb://(负载均衡)则是在多个服务实例之间做“下一跳选择”——这不就是一个七层的路由器吗?
3.4 一个真实的排错故事
之前做企业级AI知识库系统,Java后端(SpringBoot)调用Python的FastAPI嵌入服务。一开始直接用的http://localhost:8000,一切正常。后来我把两个服务都放进Docker容器,Java容器里访问localhost:8000就变成了访问Java容器自己,显然不对。
排查后发现:容器之间的通信必须通过容器IP或容器名,并且要在同一个自定义网络里。我执行docker network inspect看了路由表和IP分配,最终用docker-compose的links和自定义网络解决。
那一刻我突然明白了:计网书上讲的“不同子网之间通信需要路由器”,在容器世界里就是“不同网络之间的容器无法直接通信,需要加入同一个网络或者做端口映射”。
四、总结:把八股变成肌肉记忆
下次你再配置Nginx反向代理、调试Docker容器网络、或者写微服务网关路由的时候,不妨想一想:“我正在写的就是一张七层的路由表,而IP层的那张表,正在我的Linux内核里默默地为我工作。”
📌 互动思考题
你在 Linux 服务器上用 Docker 部署了两个容器:
容器 A(Java SpringBoot 应用,端口 8080)
容器 B(Redis,端口 6379)
两个容器都在默认的bridge网络中,IP 分别为172.17.0.2和172.17.0.3。
现在容器 A 需要通过redis://172.17.0.3:6379访问容器 B,一切正常。
问题:如果你在宿主机上执行ip route show,会看到一条关于172.17.0.0/16的路由。请问这条路由的作用是什么?如果手动删除这条路由,容器 A 还能 ping 通容器 B 吗?为什么?
欢迎在评论区留下你的答案和排查思路 —— 下一篇我会专门讲讲 Docker 网络模式与 Linux 路由表的关系。