news 2026/9/15 9:53:29

服务器多IP配置实战:从端口限制到高并发编排

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
服务器多IP配置实战:从端口限制到高并发编排

做 YouTube 数据采集和视频自动化处理的人,迟早会遇到同一个困惑:代码里多线程、异步、协程都上了,请求一多还是 timeout、断连,甚至被对方限住。我整理“YouTube 海量视频并发”这个系列的时候,发现很多问题不是出在语言或框架,而是服务器多 IP 配置这一步没打牢。单台服务器往往默认只有一个 IP,出站连接的端口数、TIME_WAIT、服务端限流都卡在单点上。这篇把服务器多 IP 配置从网卡绑定、源地址选择、策略路由到并发编排拆开讲清楚,适合在做采集服务、批量视频信息处理,或者单纯想把单机并发能力用好读明白的同学。

1. 单 IP 并发先撞墙:到底哪里堵

1.1 一次出站请求所涉及的链路

很多刚接触海量抓取的人,会把并发数当成唯一的指标,觉得“线程拉到 1000 请求就会更快”。实际情况是,每次从你的服务器发到目标服务器的请求,都要占用一对 TCP 四元组:源 IP、源端口、目标 IP、目标端口。目标 IP 和目标端口基本是固定的,比如某个视频站的数据接口都是同一个域名、同一个 443 端口,等于四个元素里能变的只剩“源 IP”和“源端口”。

单 IP 的情况下,源 IP 也固定了,能够变化的空间就只有源端口。这是最容易被忽略的第一堵墙。

1.2 源端口不是取之不尽的

Linux 默认的本地端口范围一般是这样:

sysctl net.ipv4.ip_local_port_range # 输出类似:32768 60999

这个范围里大概有 2.8 万个端口可用。对一个固定的目标 IP 和端口,理论上同时能建立的连接数上限就是 2.8 万左右。听起来很多?问题在于短连接断开后的 TIME_WAIT 状态。

HTTP 请求如果走的短连接,每次连接关闭后,这个四元组会进入 TIME_WAIT 状态,默认等 60 秒甚至更久才能安全复用。假设每秒建立 1000 个新连接,那 60 秒里就有 6 万个端口被占用,单 IP 的端口池根本扛不住。实际表现就是并发一高,日志里开始刷Cannot assign requested address

这不是代码 bug,是单 IP 的物理限制。我之前在一台机器上用单 IP 跑短连接请求,QPS 刚过 800 就开始间歇性报错,把日志拉出来看,全卡在“分配不到本地端口”。换成每 IP 单独带连接池之后,同样代码跑到了 3000 QPS 以上,依旧很顺畅。

1.3 服务端限流才是最先感受到的墙

端口只是客户端侧的限制,更直接的是目标服务器对单 IP 的访问频率限制。视频站对同一个 IP 的每秒请求数、连接数都有自己的窗口判断,短时间请求太密集,响应码直接就变了,要么是 429,要么弹校验页面。这时候你去调代码、改并发模型,基本没有效果,因为问题出在“这个 IP 已经不受欢迎了”。

所以多 IP 不是为了显得厉害,而是让出站流量在地理上和逻辑上被分散开,每个 IP 都处在一个相对正常的请求节奏里。这也是为什么并发越高,越早规划多 IP 越划算。

2. 多 IP 落地:从网卡绑定到连通性自检

2.1 IP 资源从哪来

在动手之前,先盘点清楚你手上的 IP 是怎么分配的。

物理服务器或者独服,服务商通常会给一组 IP,一般同网段或者同一个 VLAN。这种环境下,把多个 IP 绑到网卡上是纯系统操作,自由度极高。

云服务器情况复杂一点。很多云主机的“多 IP”其实分两件事:私网辅助 IP 和公网 EIP。如果你只在系统层面加了一个私网 IP,但云控制台上没有把对应的公网映射绑定好,出网依然可能走默认的主 IP。这个必须先去控制台把辅助私网 IP 和对应的公网 IP 绑定好,再回系统里配置。否则后面验到一半会发现源 IP 根本没换,会非常绕。

2.2 把多个 IP 绑到网卡上的操作

Linux 下临时加 IP 非常简单,一条命令:

ip addr add 203.0.113.5/32 dev eth0 ip addr add 203.0.113.6/32 dev eth0

这里用/32是因为单个辅助 IP 并不需要跟网卡所属网段保持一致,只要本机知道这个地址属于自己就行。查看效果:

ip addr show eth0

可以看到 eth0 下面挂着多个inet地址。如果想让配置重启后还在,Debian/Ubuntu 可以在/etc/network/interfaces里加静态地址块,CentOS/RHEL 系则建议用 NetworkManager 的方式管理。不管哪种方式,最终目的都是让操作系统承认“我有这么多本地地址”。

2.3 配完先自检一遍

IP 加上去之后,先别直接跑业务,验证一下每个地址是否能正常出网:

curl --interface 203.0.113.5 https://api.ipify.org curl --interface 203.0.113.6 https://api.ipify.org

--interface会强制 curl 使用对应 IP 作为源地址。如果两个接口返回不同的出口 IP,说明基本网络层面已经通了。如果返回相同或者超时,就需要检查路由表和云控制台绑定关系,别等任务跑起来才发现。

3. 出站源 IP 分配:让流量真正按设计分散

3.1 默认路由总会优先走主 IP

多 IP 配好了,很多人会直接开始跑并发,然后发现:目标服务器看到的来源 IP 还是原来那一个。这是整个多 IP 配置里最容易踩的坑。

Linux 选择出站源地址时,默认查主路由表,如果没有特别指定,往往优先使用主 IP,或者说第一个配上去的地址。辅助 IP 只是“存在”,不代表出站流量会自动用它。想让流量真正分散,必须主动干预源地址的选择,常见就两条路:应用层绑定,或者策略路由。

3.2 应用层绑定源地址

应用层绑定是最直接的做法,代码里指定这次请求用哪个本地 IP。Python 的requests底层用的是 urllib3,可以通过自定义 HTTPAdapter 来实现。

import requests from urllib3.poolmanager import PoolManager from requests.adapters import HTTPAdapter class SourceAddrAdapter(HTTPAdapter): def __init__(self, source_ip, *args, **kwargs): self.source_ip = source_ip super().__init__(*args, **kwargs) def init_poolmanager(self, connections, maxsize, block=False, **kwargs): self.poolmanager = PoolManager( num_pools=connections, maxsize=maxsize, block=block, source_address=(self.source_ip, 0), **kwargs, )

用的时候一个 IP 建一个 Session:

session_5 = requests.Session() session_5.mount("https://", SourceAddrAdapter("203.0.113.5")) session_6 = requests.Session() session_6.mount("https://", SourceAddrAdapter("203.0.113.6"))

之后所有走session_5的请求,源地址都会是 203.0.113.5;走session_6的请求,源地址就是 203.0.113.6。这样做的好处是逻辑清楚,跟并发框架解耦,适合大多数脚本任务。

3.3 策略路由:把不同源地址引向不同路由表

有些场景不适合改代码,比如业务是 Java 起的,或者中间还隔了一层分发服务。这时候用策略路由更合适,核心思路是给每个源 IP 建一条独立的路由规则。

假设网关是 192.168.1.1,网卡是 eth0:

echo "100 ip01" >> /etc/iproute2/rt_tables echo "101 ip02" >> /etc/iproute2/rt_tables # ip01 用的路由表 ip route add 192.168.1.0/24 dev eth0 src 203.0.113.5 table 100 ip route add default via 192.168.1.1 dev eth0 table 100 ip rule add from 203.0.113.5 table 100 # ip02 用的路由表 ip route add 192.168.1.0/24 dev eth0 src 203.0.113.6 table 101 ip route add default via 192.168.1.1 dev eth0 table 101 ip rule add from 203.0.113.6 table 101

这样任何源地址是 203.0.113.5 的流量,都会去查 100 号路由表;源地址是 203.0.113.6 的流量,查 101 号路由表。即使业务代码里没有任何绑定逻辑,也能达到分流效果。

如果要按进程或者用户分,还可以配合 iptables 打标记:

iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 100 ip rule add fwmark 100 table 100

把特定业务账号的流量全部引到对应路由表,运维层面会省很多事。

3.4 两种方式怎么选

方式侵入性适合场景主要注意点
应用层绑定需要改代码Python、脚本、独立任务每个 session 只能固定一个源地址
策略路由不需要改业务代码Java、多进程、不便改代码的服务要维护路由表,出问题排查成本较高
iptables 标记需要配置规则按用户、按进程隔离规则多了容易混乱

我自己在线上环境里用最多的是“应用层绑定 + per-IP 连接池”,因为任务层本来就是我控制的,代码里一个地方封装好,后续加 IP 只需要改配置项。策略路由则用在一些不改代码的历史服务上,效果也不错。

4. 并发任务层与多 IP 的编排细节

4.1 从“IP 池”到“每 IP 连接池”的抽象

多 IP 配好之后,真正的难点不是网络配置,而是任务层怎么把 IP 用起来。最笨的办法是给每个请求随机分配一个 IP,但这会导致同一个 IP 同时被几十个任务打,照样触发限流。

更好的做法是做一层“IP 配额”抽象:每个 IP 维护一个独立连接池,每个连接池同时允许的活跃请求数是固定的。任务队列拿到一条待抓取记录后,先看哪个 IP 的当前活跃请求没到上限,再从对应的连接池发请求。

这个思路跟数据库连接池很像,每个 IP 就是一个资源单位,池子是并发窗口。流量不会集中在某个 IP 上,也不会出现某个 IP 被闲置。

4.2 代码里绑定源地址的两种写法

用异步框架时,aiohttp原生支持local_addr参数,写起来很直接:

import aiohttp IP_POOL = ["203.0.113.5", "203.0.113.6", "203.0.113.7"] connectors = { ip: aiohttp.TCPConnector(local_addr=(ip, 0), limit=15) for ip in IP_POOL } sessions = { ip: aiohttp.ClientSession(connector=conn) for ip, conn in connectors.items() }

这样每个 IP 对应一个独立的aiohttp.ClientSession,每个连接池最多 15 个并发。取任务时按 IP 的剩余容量分配:

async def fetch_one(ip, url): async with sessions[ip].get(url) as resp: return await resp.text()

如果你用的是同步requests,就按前文那个自定义 Adapter 的方式,先按 IP 建好多个 Session,再配合线程池分发。Java 系也类似,核心逻辑都是“把源 IP 当作连接池的一个维度”。

4.3 连接复用对源 IP 的影响

HTTP keep-alive 能让同一个连接反复使用,减少握手开销,但对多 IP 策略有一个副作用:连接一旦建立,源地址就固定了,后续所有走这条连接的请求都会带着同一个 IP。

这本身不是问题,问题在于很多任务层会误以为“发到同一台目标服务器的请求会自动轮换 IP”。如果你用了一个公共连接池,连接池里只有一两条长连接,那所有请求实际都是在重复消费同一个 IP。

解决的办法前面已经提到了:一定要按 IP 拆连接池,让同一时间不同 IP 的连接池各有各的长连接。重试的时候也要注意,某个 IP 触发限流后,优先换一个空闲 IP 再试,而不是同一个 session 里拼命重试。

5. 2C4G 小机器到底能扛多大的并发

5.1 瓶颈三维度:带宽、CPU、内存

很多人问过类似“2C4G 的实例能撑多少并发”的问题。说实话,抛给任何技术博主都没法给一个固定数字,因为瓶颈取决于任务类型。

优先级最高的是带宽。视频站的海量抓取有两种典型场景:一种是抓视频信息、评论、字幕等小体积元数据,单响应可能只有几十 KB;另一种是直接下载视频文件,动辄几十 MB 到几个 GB。后者根本不是“并发”问题,一个线程就能把带宽打满。前者才有讨论并发数的意义。

第二是 CPU。TLS 握手非常消耗 CPU,每建一个新连接都要完成一次完整的握手过程。2C 的机器每秒能完成的握手次数大概是几百到一两千,具体看密码套件和 CPU 型号。如果你的代码里还做了大量 JSON 解析,CPU 压力会进一步放大。

第三才是内存。同步线程模型下,每个线程默认栈空间可能占 8MB,但实际上 Python 线程还要叠加解释器和业务对象的内存,4G 内存跑 500 个线程会有点紧张。异步协程模型能省很多内存,但依然会被 CPU 卡住。

一个粗略的参考表:

任务类型单次响应大小2C4G 合理并发参考主要瓶颈
元数据接口5-50KB200-600 并发请求CPU / 带宽
视频文件50MB-2GB8-20 个下载任务带宽 / 磁盘 IO
接口压测1-10KB1000-3000 QPS 量级CPU / 源端口

5.2 值得调的内核参数

几个参数在多 IP 并发里常见,调一下效果立竿见影:

sysctl -w net.ipv4.ip_local_port_range="1024 65535" sysctl -w net.ipv4.tcp_fin_timeout=15 sysctl -w net.ipv4.tcp_tw_reuse=1

ip_local_port_range把能用的源端口扩大,配合多 IP 之后,端口空间翻倍的效果会被放大。tcp_fin_timeout缩短的是 FIN_WAIT 状态的时间,让空闲连接更快释放。tcp_tw_reuse在客户端出站方向一般可以开,但如果你这台机器还承担 NAT 转换,稳妥起见先别开。

5.3 怎么验证源 IP 真的分布开了

配置完多 IP 之后,不要只看业务日志,要用网络层的数据说话。

服务器上抓包:

tcpdump -nn -i eth0 host 目标服务器地址 and tcp port 443

观察源地址分布是否均匀。或者用ss统计本地连接的源地址:

ss -tn | awk '{print $4}' | cut -d: -f1 | sort | uniq -c

如果看到某个 IP 的连接数明显偏高,说明任务层的负载均衡逻辑没有生效。我一般会把这个统计做成一个监控项,跑上一段时间之后基本能看出整个 IP 池的使用情况。

6. 这轮配置中踩过的坑

6.1 加了辅助 IP 反而连不出去:rp_filter 的锅

有一段时间我在服务器上加了两个 IP,其中一个怎么都 curl 不通,查了半天发现是反向路径过滤。Linux 默认开启rp_filter,会检查回包的源地址是否可达,如果入站数据包的源 IP 在路由表里没有合理的返回路径,直接丢包。

查看:

sysctl net.ipv4.conf.eth0.rp_filter

如果要临时关掉:

sysctl -w net.ipv4.conf.eth0.rp_filter=0

不过在云环境里,更推荐去云控制台确认辅助 IP 是否已经正确关联到实例。很多辅助 IP 配不出去的根因,是控制台绑定步骤没走完,系统层面的修复治标不治本。

6.2 出口 NAT 把应用绑定悄悄覆盖了

云主机如果通过 NAT 网关出网,应用绑定的源地址可能会在网关那一层被改写,最终目标服务器看到的还是主 IP。这不是你代码写错了,而是网络架构决定的。

遇到这种情况,需要去云控制台把多个公网 IP 映射到对应的辅助私网 IP 上,或者使用服务商提供的多 IP 网关能力。简单说,多 IP 要打通“系统 IP、私网 IP、公网 IP”三层映射,少了任何一环都不算真正生效。

6.3 TIME_WAIT 把端口吞光

短连接高并发跑一段时间,很容易碰到Cannot assign requested address。一开始我以为是并发数太高,后来看ss -s才发现 TIME_WAIT 堆了上万个连接。即使有多个源 IP,如果任务层复用了同一个连接池、疯狂新建短连接,TIME_WAIT 依然会堆积。

后面引入了两个手段:一是按 IP 拆连接池,尽量用长连接;二是调整内核参数并开启tcp_tw_reuse。两者配合之后,这个报错基本消失了。

6.4 IPv6 优先级导致 IPv4 地址池闲置

有次压测发现响应挺快,但回去看源 IP 分布,发现 IPv4 地址池根本没动静。原因是域名解析到了 AAAA 记录,流量全走 IPv6 出去了。如果你的服务器配了 IPv6,而目标站也支持,DNS 解析时可能会优先用 IPv6。多 IP 规划如果只配了 IPv4,就需要在应用层强制只解析 A 记录,或者单独规划 IPv6 地址池,别让两套机制互相打架。

6.5 多 IP 不是无限提速券

最后提醒一句,多 IP 解决的是“请求来源分散”的问题,不是“吞掉所有并发”的万能药。如果单个 IP 的请求节奏已经过于激进,加再多 IP 也只是把风险摊开,并没有从根上降低目标服务器的压力。我实际跑下来,最好的效果是把每个 IP 的请求速率控制在不到限制线的七八成,整体吞吐反而比全部打满更高,因为重试少了,成功率高了。

服务器多 IP 配置这件事,说到底不是网络命令合集,而是和并发任务层协同的一环。IP 配好了,任务层不懂得按 IP 做连接池隔离,效果照样拉胯;任务层设计得再漂亮,IP 源地址没有真正分散开,也会被单点限制卡死。把这两层联动起来,才是这个系列真正想聊清楚的东西。下一篇文章,我会接着写任务队列和失败重试的设计,把这次的 IP 池跟并发编排完整串起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 9:52:07

COMSOL复现液氮致岩石热损伤:三场耦合建模与调试全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 9:49:26

dsh-web:从聊天界面到插件化开发工作台的架构实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 9:48:50

ajs17网站建设避坑指南:改需求不拖一周的5个注意事项

ajs17网站建设避坑指南:改需求不拖一周的5个注意事项 改个按钮颜色建站公司拖一周?别骂了,去查查合同里的验收标准。 很多福建老板做ajs17网站建设,最怕的就是这种“软拖延”。你以为只是改个色值,对方却说要走流程、要测试、要评估风险。这背后其实是 需求边界模糊 和 技术架构僵化 在作祟。…

作者头像 李华
网站建设 2026/9/15 9:47:36

TMS320F2812直流电机闭环控制硬约束解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 9:45:00

彻底清理Windows软件卸载残留:AppData与注册表三步搞定

很多朋友找我帮忙看电脑,十次里有五六次都是同一个问题:某个软件卸完,C 盘空间没回来多少,重新装旧版本又总报错,说什么“已安装”“配置不完整”“找不到指定组件”。我以前也以为卸载软件就是把安装目录拖进回收站&a…

作者头像 李华
网站建设 2026/9/15 9:44:26

C++ Builder 256色BMP图像处理实战指南

简介:这是一套基于C Builder开发的256色BMP图像处理完整工程源码,面向图形学初学者与C桌面应用开发者,聚焦位图文件解析、调色板管理及基础图像变换等核心能力训练。资源包含88个文件,主体为39个头文件(h)与…

作者头像 李华