上个月接手了一个基础设施类项目,需求单上写得很简单:把视觉质检场景下的数据上报和结果查询服务从零新建出来,老板只给了一个方向——“并发要扛得住,吞吐要上得去”,于是项目代号就干脆叫了“新建并发吞吐”。真正动手前以为难点在于排队拍照推送、图片压缩、模型推理,做起来才发现,最难的部分全部集中在“新建系统时如何把并发模型和吞吐量预算一次想清楚”。
这篇文章打算记录这段时间从环境初始化到压测调优的完整过程,包括新建Linux用户、模块划分、线程池与锁的处理、Kafka削峰、并发压测方法,以及最后一批线上才暴露出来的坑。适合正在从零搭建高并发服务、或者系统压测一直卡在某个QPS上不去的同学看,不需要你有多深的框架经验,只要关心“为什么这么做”就能跟得上。
1. “新建并发吞吐”到底要解决什么问题
1.1 这个项目为什么叫这个名
业务上,我们需要在工厂车间里部署几十路工业相机,每个相机在检测到异常后会把缺陷图片和结构化结果回传到中心服务。现场不是只有一台相机在跑,而是多台同时触发、同时上传,到了换班或设备复检的时间段还会出现明显的高峰。过去的老系统基本是单机写文件加直连数据库,图片一多就开始不干活了,所以这次的核心诉求就一句话:从零新建一套能同时支撑“多路并发上报、高峰期数据入库、后续结果查询”的服务,而且吞吐量不能随着并发数上升而线性垮掉。
这类项目最怕的是把“并发”和“吞吐”当成两个模糊的口号。并发数、QPS、响应时间、吞吐量这几个名词大家都认识,但需求方和开发方脑子里理解的往往是不同模型。如果不把目标量化成具体数字,后面压测和调优就没有校验标准,排障时也会各说各话。
我们当时的做法是先拉了一次历史日志,统计出单日最高上报量、最集中的小时段、单路相机的平均触发间隔。经过一周统计,最终定出来的目标并不浮夸:系统要能支撑50路并发上报,接口峰值需要达到每分钟8000次写入,单张图片上传与结果写入的整体链路P99响应时间不超过500毫秒。这个数字后来成为所有架构和代码决策的衡量标尺。
1.2 并发数、QPS、RT、吞吐量:先把账算明白
因为是被标题“新建并发吞吐”招进来的项目,很多人误以为只要把并发数调大,吞吐量就自然上去了。但这里有个最容易混淆的关系:并发数只是“同时挂在线上的请求数量”,吞吐量是“单位时间内处理完的请求数量”,响应时间则是“单个请求从进到出花的时间”。它们之间有公式关联,但方向不完全一致。
系统里常用的一个简化换算公式是:
- 并发数 = QPS * 平均响应时间
- 吞吐量(每秒处理数) = 并发数 / 平均响应时间
所以我经常看到新同学在压测时拼命加大并发数,结果QPS不但没上去,响应时间直线飙升。原因很简单:当并发超过系统真实处理能力,请求全部挤在队列里等待,吞吐量被资源瓶颈卡住,RT被排队时间拉长,整个系统的体验反而更差。
对我们要承担的场景来说,需要的不是“极端情况下能同时挂几万个连接”,而是“每分钟到达的8000次写入能不能稳定消费完”。所以从第一天起,我就把项目真正关心的指标定义成了三条:入站QPS曲线、消息积压深度、全链路P99响应时间。所有架构选型和代码设计都围绕这三条来展开,后面陆续新建的功能模块也不会跑偏。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从新建一台机器到一个可扩展的服务:环境的几个硬约束
2.1 别用root,新建普通用户并把系统资源梳理清楚
新建项目的第一步通常是买机器、装环境,绝大多数人习惯拿root直接把环境一把梭装好。但在一个要长期承载高并发的服务上,我建议一开始就规规矩矩地新建独立运行用户。
操作上需要做的不多,但每一步都有它的作用:
bash复制useradd -m app
passwd app
mkdir -p /data/app /data/logs
chown -R app:app /data
看起来只是权限隔离,实际至少在三个地方为“并发高吞吐”保驾护航。第一,如果服务以普通用户运行,即使某个接口因为代码漏洞被外部命令注入,攻击者拿到的也只是一个受限账号,而不是整个服务器的root权限,这对暴露在公网的上报接口来说不是小事。第二,多个服务共用一个机器时,单独用户能把日志和临时文件隔离清楚,排查问题方便太多。第三,高并发服务常见的文件句柄上限是按用户维度设置的,用独立用户才能单独调优,不至于影响宿主机上的其他进程。
新用户建好之后,还要立刻看一遍系统层面的软硬限制。高并发服务通常涉及的资源无外乎文件句柄、线程数、进程数这几类,Linux默认值往往保守得可怕。以文件描述符为例,默认的1024对于单机几十路并发摄像头上传图片来说可能一会儿就耗光了。建议至少加到65535甚至更高。我一般会在 /etc/security/limits.conf 里针对运行用户配置:
code复制app soft nofile 1048576
app hard nofile 1048576
app soft nproc 65536
app hard nproc 65536
改完之后不是重启机器就算完,还要用 ulimit -n 确认当前会话是否加载新值。曾经遇到过配置写完没生效、压测一上连接数稍微涨起来就抛 Too many open files 的情况,排查半天发现是改错了用户或者没有重新登录会话。这类系统初始化问题窗口期极短,等压测才发现就已经耽误了一整天。
2.2 工程模块怎么拆,决定了后面能不能并发扩容
很多人新建工程喜欢按代码层来拆,比如建一个 common、一个 api、一个 dao、一个 job。这么拆对小项目没问题,但对以“并发和吞吐”为核心目标的项目,我建议按业务边界拆,让每个模块都具备独立部署和横向扩容的可能性。
我们的系统最后拆成了这么几个模块:
- 设备接入模块:接收各个相机的上报请求,负责图片临时落盘和原始消息入队;
- 质检任务模块:从消息队列中拉取图片,调用后端检测服务完成缺陷识别;
- 结果存储模块:负责检测结果、告警事件和图片索引的落库,同时提供查询接口;
- 基础支撑模块:包括Redis缓存、消息队列、对象存储和数据库连接池的管理。
拆分的理由很直接:上报接口是“写入密集型”,承担的压力主要来自网络IO和磁盘缓存;结果检索则偏“读密集型”,压力来自数据库查询和缓存命中率。二者经常发生资源争抢。如果模块之间没有隔离,一旦摄像头集中上传,把CPU榨干,查询接口也会跟着超时;分拆之后,至少可以给不同模块设置不同的最大线程数,也可以单独扩容。
以我们用的技术栈为例,Java这边最自然的选择是Spring Boot写各个worker服务,Go写边缘节点轻量上报网关,中间通过HTTP/gRPC隔离。新建工程时不需要一上来就追求特别花哨的微服务框架,先做到代码模块与部署单元对应,后面在压测时哪块成为瓶颈就单独加资源就好。
2.3 新建表、连接池、缓存与消息队列时就要留好并发位
还有一个特别容易被忽略的点:数据库表设计时就要为并发写入留好位。我们第一次设计上报结果表时,字段包含了设备ID、检测时间、图片路径、缺陷类型、原始回传数据。粗看没什么问题,但一旦多路相机在同一秒上报,设备ID+检测时间本来想当唯一键,实际因为系统时钟和图像延迟,往往会撞车。
后来我们把上报流水号作为分表和业务唯一键。每张表的初始结构看起来平平无奇,但索引和主键都尽量选无意义的雪花ID,避免并发插入时在高位叶子节点上互相竞争。MySQL的B+树主键如果使用递增型,写入基本是顺序的,如果是随机字符串,每次插入都可能引发页分裂,写吞吐会明显下降。
缓存方面,Redis实例在项目启动阶段就要决定是单机还是集群,不能等并发起来再迁移。因为上报的图片和检测结果存在短时间的高频访问,如果事先没有分片,单机。Redis很快会遇到网络带宽瓶颈。我们一开始用的是三主三从集群加Slot分片,虽然运维复杂度上去了,但后续压测到高位时缓存没有再成为瓶颈。
消息队列的选型几乎是这类项目的必经之路。我们在选型时对比过RabbitMQ和Kafka,最后选了Kafka,原因后面专门说。但新建集群时最需要注意的是Topic分区数一定要预留扩容空间。分区数不能拍脑袋,它和消费者并发数是半线性关系。如果一开始只建了3个分区,后面消费者线程想加到10也没用,吞吐上限已经被分区数锁死了。
3. 多路并发进来以后,系统如何“接住”请求
3.1 线程池大小为什么不能拍脑袋
请求打到服务端后,所有并发其实都会落到线程池或连接池上。很多新手给我看代码,线程池配置是从网上抄的,核心线程数写50,最大线程数写200,队列容量写1000。问他为什么这么配,答案基本是“感觉这样比较稳”。但是线程池的每个参数都在控制系统行为,配错不等于能用,而是等于把系统变成一颗定时炸弹。
在设计上报接口的线程池时,可以用一个经验公式作为起点来处理IO密集型任务:
线程数 = CPU核心数 * 2 / (1 - 阻塞系数)
阻塞系数可以粗略取0.5到0.8之间。如果单个请求内包含网络调用、数据库写入或Redis操作,CPU大部分时间在等IO,为了让CPU不被浪费,可以适当多开线程;如果任务是图片压缩这种CPU密集操作,阻塞比例很低,线程数设成小于CPU核心数即可,多了反而增加上下文切换开销。
实际代码里我不会直接new一个裸线程池,而是用Spring的ThreadPoolTaskExecutor,配好拒绝策略。这里有一个很多人没想明白的问题:当任务队列堆满时应该怎么办?
我见过最容易出事故的方案是 CallerRunsPolicy,看起来好像挺平滑,实际上是让Tomcat的请求处理线程去执行积压任务。一旦并发真的冲上来,请求会全部卡在业务线程里,容器线程被填满后其他接口也一起超时,从“一个接口变慢”直接发展成“整个服务不可用”。
我们所以选择的是有界队列加抛异常策略,再把异常统一转成HTTP 503。让请求失败不是坏事,至少客户端能感知压力然后做退避重试,而不是服务端默默堆积请求把自己压死。对高吞吐系统而言,最快失败有时比排队成功更有价值。
3.2 数据库并发锁:乐观、悲观与分布式之间的取舍
“新建并发吞吐”这个项目里,我们也有幸踩了数据库并发锁的坑。表象是查看设备汇总状态时,同一设备在某一秒内多次上报,更新数据时丢失了部分结果。这种问题本质是多个请求同时读出一条旧数据,修改后再写回,后来者把前者的更新覆盖了。
解决并发覆盖最简单的方案是乐观锁:在表中加一个 version 字段,更新时带上版本号,用 update ... set status=?, version=version+1 where id=? and version=? 判断影响行数。影响行数为0就说明版本冲突,请求方重新获取数据并合并更新。我们的业务里,同一设备的多次上报结果往往是可以做合并的,所以乐观锁足够,不需要把所有并发都串行化。
但也有真正需要悲观锁的场景,比如对同一个设备做发号、或者扣减某个并发预算。必须使用 select ... for update 时,有两条规则极其重要:第一,锁一定要落在索引上,否则InnoDB会全表扫描,然后对主键范围加锁,整个表的写入吞吐都崩了;第二,锁的粒度和业务生命周期要短,不要在持有数据库锁的情况下去调用远程接口或者做图片上传等待。
还有一个更隐蔽的坑是分布式锁。很多人直接拿Redis的 SETNX 当分布式锁用,但如果不设置过期时间,业务机器宕机后锁永远不会释放。正确姿势是用一条原子命令:SET lock_key unique_value NX PX 30000,释放时还要用Lua脚本校验value是不是自己写入的,避免误删别人刚拿到的锁。高并发下每个步骤都要考虑“进程是否在任意时刻崩溃”这个前提。
3.3 前端并发请求限制:客户端也要学会自我保护
这部分来自一个和前端同学协作的插曲。网页端批量上传缺陷图片时,前端一次性把几十张图片全部并发发出,服务端网关瞬间被打到满负荷。有些接口框架天然支持并发下载,所以上传也一样,浏览器不会因为你只点了“上传”按钮就自动控制并发度。大量并发请求同时撞上来,即使服务端能扛住,也会挤占其它正常业务请求的带宽和线程。
因此在上传端必须加“前端并发数控制”的逻辑。业界最常见的实现是维护一个任务池,每次最多同时执行N个上传请求,完成一个再推下一个。N的取值要看单张图片大小和服务端处理时间,我们当时的图片大多几百KB到几MB,经过服务端压缩后传输链路并不长,所以把并发数压到5~8路就能把网络带宽打满,再往上只会增加失败率。
这个被热搜词反复挂在嘴边的“JS并发请求限制 请求个数”问题,本质上是资源预算管理。不仅服务端要限制,客户端也要限制自己的瞬时并发。后来我把上传模块的并发限制和失败重试逻辑做了统一,请求失败后按指数退避重试,而不是立即重发,服务端的瞬时压力立刻小了很多。
4. 吞吐量真正的瓶颈往往在同步与串行,削峰靠消息队列
4.1 一次同步调用就吃掉你80%的请求预算
系统真正开始压测之后,我们发现了一个反直觉的现象:单台上报请求本身的处理时间只有5毫秒,但全链路P99却到了600毫秒以上。追查后发现,问题出在写入流程上:上报接口收到图片后,先同步把图片写到对象存储,再同步插入数据库,最后同步推送一条Webhook告警。对象存储虽然号称低延迟,但大图片上传一次就是几十到上百毫秒;数据库写入又受刷盘策略影响;Webhook如果对方服务变慢,还会继续拖累主流程。
从并发模型来看,每一笔同步调用都意味着一个线程被长时间占住,CPU和线程资源被白白消耗在等待IO上。如果链路里有三个串行的外部依赖,理论上相同时间内系统能支撑的并发数就压缩为原来的三分之一不到,吞吐量也就随之下降。
所以这类以“写”为主的业务,最重要的一步是流程异步化。我们把上报接口的主链路改成:接收图片二进制数据后,立即用CRC校验完整度并快速落一个临时文件,然后直接返回200。拿到文件路径和元数据后,将“图片处理任务”作为一个消息发到Kafka,由后台worker负责后续的压缩、推理、入库和告警。这样上报接口从“需要等待完整结果”变成了“只做接收与入队”,RT从几百毫秒降到20毫秒以内,吞吐量瞬间上了一个台阶。
4.2 用Kafka处理高并发消息,分区和消费者要匹配
为什么选Kafka处理高并发消息而不选别的消息中间件?原因在于Kafka的顺序写盘和分区并行模型天然适合“高峰削峰、低峰回补”。消息生产端把请求快速写入分区,消费者端根据能力拉取,瞬间的峰值不会直接压到数据库上,而是变成了一个消息积压缓冲区。生产者侧不需要等待消费者实时处理,这和我们的质检上报场景非常契合。
真正容易踩的是消费者侧的并发模型。Kafka里一个分区的消息在同一消费者组里只会被一个消费者线程处理,所以如果你想让消费者吞吐提高,单纯增加消费者进程数不一定有用,必须先确认Topic的分区数是否够多。
我新建Topic时一般按“目标吞吐核算分区数”:假设单消费者线程每秒能处理500条消息,业务目标需要每秒5000条,那么至少要10个分区。消费者的线程池大小则与分区数保持1:1或1:N的关系,但不要少于最小消费者拉取速度要求。我们后来给缺陷上报Topic建了24个分区,消费者侧用线程池并发处理24个分区的消息,单机处理吞吐达到每秒3000条,再往上加机器反而需要先用压测验证消费者线程会不会因为等待数据库连接而阻塞。
4.3 消息重复与积压:吞吐量上去了稳定性垮了同样白搭
Kafka的高吞吐是拿“至少一次”的投递语义换来的,这意味着消费者必须自己处理重复消息。如果消息重复来了,而你的数据库写入没有做幂等,那么同一张缺陷图片会被重复入库,形成大量脏数据。刚开始我们没太在意,直到压测后发现结果表膨胀速度是上报量的两倍,一查才发现消费者在处理消息时偶尔发生了rebalance,一批消息被重复拉取并再次提交。
解决方式不复杂,但不做就会持续踩坑:
- 在消息体中携带全局唯一的
batchId或requestId; - 数据库表对
requestId建唯一索引; - 消费者在写入时先执行
insert ignore,或者根据唯一键先查再写。
保证幂等之后,数据重复问题就基本消失了。
消息积压的处理则又是一个容易失控的场景。这里最忌讳的是一发现积压就武断地把消费者线程调大,或者把消息批量大小调大。真正的做法是先看积压发生在哪个环节:如果消息已经进入Kafka,但消费者处理速度上不去,要先查数据库瓶颈、图片处理耗时;如果是消费者处理速度总体可以但偶尔卡顿,则可能是某条大图消息或者某次GC停顿导致分区拉取下游线程被拖住。否则盲目调参,会把短时波动人为放大,积压没解决,还带来一批超时和超卖问题。
5. 压测:确认系统吞吐量不是靠猜的
5.1 压力工具的结果怎么看,别只看“每秒多少个请求”
很多人会把压测等同于“用工具发请求然后看QPS”,但实际上压测工具对结果的干扰远超想象,尤其是你自己拿笔记本电脑跑JMeter去压服务器的时候。工具所在机器的CPU、网络、连接数限制都会成为瓶颈,最后压出来的数据其实是客户端的结果。
我们做压测时把工具和服务分开放,压测机配置不能太低。工具选择上,简单场景用wrk,因为它足够轻量,脚本能模拟多线程和多连接;端到端业务流程复杂或者需要做上传操作,用JMeter更灵活。
压测过程中我不建议只盯着工具输出的“Requests per second”。最靠谱的组合是同时看三个指标:
- 平均响应时间与P99响应时间,观察长尾是否严重;
- 错误率,出现5xx或超时说明系统已到瓶颈边缘;
- 施压端的最大并发连接数和实际完成数,确认并发生成了有效压力。
真实系统压测时,QPS曲线开始上升但P99同时快速上涨,说明服务正进入过载状态。这个拐点就是系统的“红色水位线”。如果压测工具只报了一个高QPS,却不看P99和错误率,很容易把系统真实的稳定吞吐量高估一倍以上。
5.2 从压测数据定位瓶颈的一次完整过程
记录一次我们调吞吐的完整过程,可以当作案例参考。
第一轮压测,场景是单台上报接口直接写MySQL。压到每秒500次请求时,CPU和内存都不高,但平均RT到了1.2秒,且每秒完成的请求数始终突破不上去。从压测机侧看网络带宽没满,服务端线程池也没满。进入容器用 top 看到磁盘IO等待率到了90%,问题指向数据库刷盘机制。
MySQL InnoDB默认每次提交都要把redo log刷到磁盘。在机械盘或低IOPS的云盘下,这个 fsync 就是吞吐瓶颈。于是我们调整了innodb_flush_log_at_trx_commit参数,从每个事务都刷改成每秒刷一次,同时打开了 sync_binlog=0。这么调整确实让单写吞吐翻倍,但代价是断电时最多丢1秒数据。对这种图片检测结果数据来说可以接受,如果换成订单支付场景绝对不能这么做。
第二次压测,单写是上去了,但消息消费者吞吐卡在每秒600条。查MySQL活动会话发现大量线程卡在同一个update语句上,定位到这是一把业务主键上的行锁冲突。同一台设备在高峰期频繁上报,每笔消息都去更新它的汇总计数,导致行锁互等。后来我们引入Redis做计数累加,再定时批量回写MySQL,把行锁竞争的概率大幅降低,消费者吞吐从600涨到了3000。
每一次排查我都有个习惯:先看慢的地方到底在哪一层,再改参数和代码,不要一上来就加机器。很多时候瓶颈不是机器不够,而是单点资源访问逻辑设计不合理。压测的价值就在这里,它能把系统里每一处串行或者锁冲突暴露出来。
5.3 一些容易被忽略的内核参数与连接项
这类高并发项目还有一类问题不在代码,而在操作系统连接处理参数上。最典型的是TCP连接状态堆积。压测中如果看到大量的 TIME_WAIT 或 CLOSE_WAIT,先不要急着以为是代码问题。
TIME_WAIT 多通常是因为短连接太多,主动断开的一方把连接进入TIME_WAIT状态,默认等待时间长达60秒。如果压测报文很短又高频率,TIME_WAIT端口会被占满,新连接因为端口不足而失败。此时可以在服务端改造为长连接,或在压测场景允许情况下调整内核参数:
bash复制net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535
但这里要特别说明:tcp_tw_reuse 只对主动发起新连接的一端有效,如果服务端是纯accept的一方,TIME_WAIT主要由客户端连接退出产生,服务端能做的是减小连接空闲超时,让过期连接更快释放。
CLOSE_WAIT 多则基本是应用程序没有及时关闭socket造成的。服务端收到对端FIN后进入了CLOSE_WAIT,如果业务代码没有调用close方法,连接就会一直挂着。有一个排查方法很实用:用 ss -ant 统计CLOSE_WAIT数量,如果大量存在,去抓线程栈找哪些线程持有连接不释放,基本都能在代码的异常处理分支里找到漏洞。
另外还有TCP backlog的限制。Linux默认全连接队列长度是128,一般设置成2048甚至更高:
bash复制net.core.somaxconn = 2048
net.ipv4.tcp_max_syn_backlog = 2048
如果你的服务前面有Nginx或SLB,这个参数主要在监听端口的accept队列上起作用。压测时如果请求方大量出现连接超时,但服务端CPU闲置,先看一眼队列溢出计数,很可能就是backlog太小导致内核直接丢弃新连接了。
6. 这批坑其实是常态,提前规避比事后再补更划算
6.1 连接池利用率高,但不代表连接池够
在压测调优聊得差不多时,我们曾把数据库连接池调得非常大,一开始觉得并发多当然需要更多连接。结果压测过程中发现,MySQL在连接数到达一定层级后,反而出现大量“too many connections”错误。原因是MySQL每建一个连接都要占内存,还要起一个线程来处理SQL,同时连接多起来之后,InnoDB内部锁竞争也会上升。
后来我们把连接池参数从50、100一路做对比压测,发现当前4核8G的单实例上,连接池设置在20到30之间时TPS最高。继续增加连接数,TPS不仅没有提升,还频繁出现获取连接慢和锁等待。我们才意识到:数据库连接池不是让所有请求都同时拿着一个线程去访问数据库,而是在等待数据库空闲连接时就开始排队。高并发下的常见反模式就是“每个请求都占用一个DB连接等待IO”,正确方式是让请求快速拿到连接、快速执行、快速释放,连接池里同时活跃的连接数保持在一个合理范围。
连接池监控一定不能省。至少要监控当前活跃连接数、等待获取连接方线程数、空闲连接数。等待获取连接的线程数一旦持续大于0,说明连接池太小或SQL执行时间太长,而不是简单调大连接池就能解决的。这组数据也是后面决定加不加读写分离的重要依据。
6.2 高并发场景下的日志与GC是隐形吞吐杀手
日志是高并发系统里最容易被忽视的吞吐杀手。最典型的是把图片上传过程中每一帧的信息都在业务日志里以debug级别输出,日志框架在磁盘IO接近饱和时会把业务线程阻塞在线程同步写盘上。我们在压测时发现吞吐一直上不去,换了好几次参数都没用,后来 top 看到日志进程CPU很高,再检查日志文件大小已经膨胀到几十GB。
处理方式不算复杂但很有效:
- 生产环境日志级别统一调到INFO以上,避免Debug高频输出;
- 高吞吐接口的关键埋点用指标系统采集,而不是逐条打日志;
- 日志框架必须启用异步appender,并且设置独立的缓冲区大小,防止日志阻塞业务线程。
Java服务还要特别关注GC。堆内存过大导致的Full GC停顿可能让吞吐短时间降到0,然后又瞬间积压大量请求。一个可行的方法是给关键服务设置固定的低延迟垃圾回收器,并监控GC停顿和吞吐数据。我个人的经验是不要无脑加大堆内存,内存大意味着GC扫描耗时也长,反而给长尾RT增加不稳定因素。
6.3 容量评估要留“惨旺季”余量
最后说一个看起来和代码无关但实际上非常要命的经验:容量评估的时候,不能只按平均值设计,也不能只按理论峰值设计。大多数工厂的缺陷上报并不是均匀分布的,我们观察到的真实曲线是:上午8点开工后的半小时内请求量是平时的4到5倍,下午换班后又有一次小高峰。如果按平均QPS去设计Kafka分区数和消费者线程数,高峰时段一定会积压。
所以在估算时,我把当前观测到的最差情况再放大两倍作为设计容量。比如观测到的瞬时峰值是每分钟8000次,我就会按每分钟16000次来处理。因为业务量是增长的,设备点位也会持续增加,如果上线三个月后就因为新增两台相机把系统打穿,那前期的所有并发设计都是白费的。团队内部约定了一个“惨旺季”场景,每次迭代发布前都要做一次模拟最差高峰的压测,确认吞吐余量还在。
最后分享一点实际体会
这套系统从新建环境到稳定压测大概用了三周多,过程中很多问题回头来看并不是高深莫测的分布式难题,而是最基础的线程池参数、数据库连接数、消息队列分区数和Linux内核参数没有在设计阶段被认真对待。我个人这几年做高并发项目最大的一个感触是:高并发吞吐不是靠某一次调优调出来的,而是靠“新建系统时把每一层值得怀疑的地方都设计到位”才能撑起来。
如果你现在也要做一个从零开始的高并发项目,我的建议是先别急着写业务逻辑,先把“目标峰值、允许的响应时间、可接受的失败策略”写出来。再按照从客户端到服务端的顺序过一遍,把同步阻塞逐步异步化,把资源瓶颈点先识别出来。项目做完之后你会发现,最值钱的部分不是代码本身,而是那套可见、可量化、可复现的容量测算与压测方法。后面再扩容,无非就是照着这套方法再演算一轮,踏实得多。
