大促前的压测档口,我负责的一个电商小程序后端需要扩容,当时手里有一台跑了三年多的老款Intel实例,压测到800 QPS的时候CPU就跑满了,接口响应直接从120ms飙升到2000ms。运维同学问我是不是该升级配置,我盯着三家云厂商的控制台看了两天,最后决定拿三家最新一代的实例做一轮实测,毕竟2024年的新机器和三年前的老架构,差距可能比我们想象的大。
我选的三款实例都是2023到2024年发布的最新款:阿里云g8i(基于Intel Sapphire Rapids,2023年12月上线)、腾讯云SA5(AMD EPYC Bergamo,2024年3月刚推出)、AWS EC2 M7g(Graviton3,2023年发布)。测试环境统一选2核4G配置,系统都是Ubuntu 22.04 LTS,测试场景模拟真实业务:用JMeter压测一个Spring Boot写的商品详情接口,这个接口会查MySQL(8.0.33版本)和Redis(7.0.12版本),逻辑和线上完全一致。
先跑CPU密集型场景,我把接口的缓存去掉,让每次请求都去查MySQL的商品表(表里有120万条数据)。阿里云g8i的2核跑满时QPS稳定在1100左右,平均响应145ms;腾讯云SA5更猛,同配置下QPS到了1350,响应只有128ms——后来查资料才知道Bergamo架构的AMD处理器单核性能比上一代提升了22%,多线程优化做得确实好;AWS M7g的ARM架构这次有点意外,QPS只有920,响应162ms,不过它的CPU占用率一直比前两家低5%左右,应该是Graviton3的能效比优势。
再测内存和带宽场景,我给接口加了个大对象缓存,每个请求会加载一个2MB的商品详情JSON到Redis。这时候三家的内存占用都差不多,但带宽表现差异明显:阿里云g8i的按量付费带宽峰值能到5Gbps,腾讯云SA5是4.5Gbps,AWS M7g只有3.8Gbps。不过价格上M7g最划算,2核4G按量付费每小时只要0.16美元,比阿里云g8i的0.22美元便宜27%,腾讯云SA5是0.19美元,性价比居中。
这里我踩过一个实际的坑:第一次测试的时候没关云服务器的超线程,结果腾讯云SA5的QPS比预期低了20%。后来查腾讯云的文档才发现,SA5实例默认开启超线程,但我们的业务是单线程为主的Java应用,超线程反而会导致上下文切换开销。关掉超线程之后重新跑,QPS直接回到了1350。我把当时关闭超线程的脚本贴在下面,这是我在生产环境验证过的:
最后我给那个电商项目选了腾讯云SA5,原因是我们的接口QPS峰值预计在1200左右,SA5刚好能扛住,而且价格比阿里云g8i便宜13%。如果当时选了老款实例,至少得升到4核8G才能满足需求,成本反而更高。这里也想提醒下,选实例不是越新越好,比如我们的另一个Python写的爬虫服务,用AWS M7g反而比Intel实例省了30%的成本,因为爬虫是IO密集型,ARM的能效比优势更明显。
我2022年接手过一个技术社区的重构项目,刚接手的时候日活只有8万,用的是单台阿里云老款ECS,2核4G的配置,MySQL和Redis都跑在同一台机器上。到2023年社区做了一波推广,日活涨到120万的时候,系统开始频繁出问题:晚上8点高峰的时候,首页加载要3秒多,评论提交经常超时,有次甚至因为MySQL磁盘满了导致整个站点宕机了20分钟。
第一次扩容我走了弯路,当时直接把单台实例升到了8核16G,以为配置越高越稳。结果升完之后,高峰时段CPU还是经常跑到90%,排查下来发现是MySQL的慢查询占了70%的资源——原来我把所有服务都堆在同一台机器上,数据库的IO和应用的CPU争抢资源,升配置根本解决不了根本问题。后来我重新做了架构拆分,这才有了一套能扛百万日活的架构。
当时的选型我纠结了很久,到底是继续用阿里云,还是换腾讯云或者AWS?我拉了三个场景做对比:首先是成本,我们当时每月的云资源预算是2万以内,阿里云g8i的8核16G实例每月要1800元,腾讯云SA5同配置只要1400元;其次是网络,我们的用户60%在南方,腾讯云的华南节点延迟比阿里云低15ms左右;最后是生态,我们的图片存储已经用了阿里云OSS,迁移成本太高。最后我选了混合方案:应用层用腾讯云SA5,存储层继续用阿里云OSS和RDS,中间用专线打通两个VPC。
拆分后的架构我分了四层:第一层是负载均衡,用了腾讯云的CLB,配了2台4核8G的SA5实例做应用服务器,开了弹性伸缩,CPU超过70%就自动加实例,最多扩到10台;第二层是缓存,用了3台2核4G的SA5部署Redis集群(7.0.12版本),热点帖子缓存时间设为10分钟,把数据库查询量降了60%;第三层是数据库,把原来的单台MySQL拆成了主从架构,主库用阿里云RDS的g8i实例(4核8G),从库用2台SA5自建,读写分离之后主库的CPU占用从90%降到了35%;第四层是静态资源,所有图片、CSS、JS都放OSS,前面套了阿里云CDN,带宽成本比直接走服务器省了40%。
这里有个实际的问题我印象特别深:架构上线第一周,评论接口偶尔会出现重复提交的问题,用户发一条评论会显示两条。我查了三天的日志,最后发现是弹性伸缩的实例启动太慢导致的——当时设置的触发条件是CPU超过70%就扩容,但新实例从启动到接入负载均衡需要120秒,这期间如果流量继续涨,现有实例还是扛不住,用户就会重复提交。后来我把触发条件改成了CPU超过60%就扩容,并且加了预热脚本,新实例启动后先跑30秒的预热请求再接入流量,问题就解决了。预热脚本的核心代码我贴在下面,这是当时线上跑了半年的版本:
现在这个社区日活稳定在150万左右,云资源每月成本控制在1.8万,高峰时段首页加载时间稳定在400ms以内。如果当初我没做架构拆分,一直堆单台高配实例,现在的成本至少是3万以上,而且很可能扛不住流量波动。这里的核心判断是:当业务日活超过50万的时候,单实例架构的边际成本会越来越高,拆分带来的成本优化和稳定性提升,远超过架构维护的精力投入。
上个月我帮一个做设计素材的朋友上线他的个人网站,从买服务器到能访问HTTPS的域名,整个过程我只花了3个小时,但中间有几个步骤如果没做对,后面改起来会特别麻烦。比如他一开始想把所有服务都放在同一个子网里,我拦住了他——后来他的网站被扫到Redis端口暴露,要是没有安全组限制,数据库直接就被拖走了。
第一步是VPC规划,我给他选了腾讯云,因为他的用户主要在华南,而且新用户有SA5实例的优惠券。VPC我选了10.0.0.0/16的网段,这个网段足够大,后面扩容到几百台实例都没问题。然后拆了两个子网:一个是10.0.1.0/24的公有子网,放负载均衡和应用服务器,绑定了弹性公网IP;另一个是10.0.2.0/24的私有子网,放Redis和MySQL,不分配公网IP。这样私有子网里的服务不能直接被外网访问,只能通过公有子网的应用层调用,安全性高很多。
安全组配置是最容易出问题的地方,我见过太多人把安全组的所有端口都开放,相当于把家门钥匙插在锁上。我给他配了两个安全组:公有子网的安全组只开放80、443、22端口,其中22端口只允许他自己的办公网IP访问;私有子网的安全组只允许公有子网的IP访问3306(MySQL)和6379(Redis)端口。这里要注意,安全组是有状态的,比如你开放了80端口的入站,出站会自动允许对应的响应,不用额外配置出站规则。
然后是服务器初始化,我选了1台2核4G的SA5实例,系统装Ubuntu 22.04 LTS,这里有个细节:我没有直接用root用户部署应用,而是新建了一个deploy用户,只给应用目录的读写权限。有一次我自己的服务器被入侵,就是因为在root用户下跑了有漏洞的Node.js应用,攻击者直接拿到了root权限删了所有日志。新建用户的命令和权限配置我贴在下面:
接下来是应用部署,他的网站是用Next.js写的,我用Docker部署,Docker版本是24.0.7。Dockerfile我用了多阶段构建,把构建环境和运行环境分开,最终镜像只有120MB,比单阶段构建小了60%。部署完之后,我用了Nginx做反向代理,配置文件里做了两个优化:一个是开了Gzip压缩,把JS和CSS的体积压小了70%;另一个是配置了静态资源缓存,图片缓存30天,CSS/JS缓存7天,首屏加载时间从1.2秒降到了380ms。
最后是HTTPS部署,我用了Let's Encrypt的免费证书,用certbot自动申请和续期。这里有个实际问题:第一次申请证书的时候,certbot一直提示域名解析不对,我查了半小时才发现,他的域名DNS解析里只配了A记录,没配www的子域名记录,而证书申请需要验证所有绑定的域名。后来加了www的A记录,重新申请就成功了。certbot的配置命令和自动续期的脚本我贴在下面,这个是经过生产验证的:
整个流程走完之后,我用SSL Labs测试了下HTTPS的配置,拿到了A+的评分。如果当时没做私有子网隔离,他的Redis端口暴露在外网,大概率会被勒索病毒攻击;如果没做安全组限制22端口的访问,服务器可能上线第一天就被爆破密码。这些步骤看起来多,其实是给网站加了几层保险,比后面出问题了再补救要省心得多。
去年双十一大促,我们那个订单系统突然告警,CPU 使用率从平时的 30% 直接飙到 98%,接口响应时间从 120ms 涨到 2.3 秒,用户下单失败率瞬间超过 15%。当时我正在家里吃火锅,看到监控群里疯狂弹窗,赶紧打开电脑连 VPN 排查。这种场景我经历过不下五次,每次的原因都不一样,但核心思路都是先定位瓶颈,再针对性解决。
先说说 CPU 飙升的排查。当时我们的服务部署在阿里云 g8i 实例(Intel Sapphire Rapids 架构,2023 年 12 月发布)上,配置是 8 核 16G,平时跑 2000 QPS 很稳。出问题后,我第一反应不是重启服务,而是先登录服务器看进程状态。用 top 命令发现有个 Java 进程占了 780% CPU(8 核满负荷),接着用 top -Hp 12345(12345 是进程 ID)看线程,发现有个线程 ID 是 67890 一直占着 90% 以上 CPU。
这时候需要把线程 ID 转成 16 进制,因为 Java 线程栈里显示的是 16 进制。执行 printf "%x\n" 67890,得到 10932。然后用 jstack 12345 | grep -A 20 10932 抓线程栈,发现这个线程一直在执行 com.example.OrderService.calculateDiscount() 方法里的循环逻辑。我翻代码才发现,大促时订单金额计算加了满减规则,有个嵌套循环没做边界判断:当订单里有 100 个商品时,内层循环会跑 100*100 次,而平时订单只有 3-5 个商品,根本触发不到这个问题。
修复很简单,加个商品数量上限判断,超过 20 个商品就走批量计算逻辑。上线后 CPU 立刻降到 45%,响应时间回到 150ms。这里有个细节:如果当时直接重启服务,问题会暂时消失,但半小时后流量再涨起来还会复现,因为根因没解决。
再说说网络丢包的问题。上个月我们用腾讯云 SA5 实例(AMD EPYC Bergamo 架构,2024 年 3 月发布)部署 API 网关,突然收到反馈,部分地区的客户端请求超时,丢包率最高到 8%。一开始我以为是带宽不够,看了云监控发现带宽只用到了 30%(我们买的是 5Mbps 固定带宽,实际峰值才 1.5Mbps)。后来用 mtr 工具做路由跟踪,发现丢包都发生在云服务器内部的内网网卡队列上。
查了腾讯云文档才知道,SA5 实例的内网包转发能力默认是 30 万 PPS,而我们的 API 网关当时因为有个配置错误,把大量小包(每个包只有 100 字节左右)的请求都转发到同一台实例,实际 PPS 到了 35 万,超过了上限。解决方法是调整安全组规则,把小包请求先过滤到负载均衡(CLB),由 CLB 做包合并后再转发到后端服务器。同时我们给实例开了“增强型网络”特性(腾讯云 2024 年新出的功能,支持 PPS 提升到 100 万),丢包率立刻降到 0.1% 以下。
这里给个实际排查时用的脚本,我平时用来监控 CPU 和线程栈的,直接保存成 check_cpu.sh 就能跑:
这个脚本我们放在所有生产服务器上,每分钟执行一次,出问题能立刻拿到线程栈,比手动登录查快至少 5 分钟。如果不做这个监控,等运维发现告警再登录,可能已经丢了几十万订单。
今年初我们做成本复盘,发现云服务器费用占了项目总支出的 35%,其中测试环境和部分非核心生产服务(比如图片处理、日志分析)的服务器,每天有 12 小时是闲置的(晚上没人用测试环境,图片处理只有白天有流量)。当时我们用的都是按量付费的固定实例,8 核 16G 的阿里云 g8i 实例,按量付费每小时 1.2 元,一个月下来一台就 864 元,10 台就是 8640 元。
我第一反应是能不能换成竞价实例。竞价实例是云厂商把闲置资源拿出来拍卖,价格通常是按量付费的 30%-50%,但有个风险:如果云厂商资源紧张,会主动回收实例(提前 2 分钟通知)。我们测试环境对稳定性要求不高,就算被回收,重新拉一台起来也就 3 分钟,完全能接受。生产环境的图片处理服务,我们做了多实例冗余,单台被回收也不影响业务。
先说竞价实例的选型。我们对比了阿里云、腾讯云、AWS 的竞价实例价格:同配置(8 核 16G,x86 架构)下,阿里云 g8i 竞价实例每小时 0.45 元,腾讯云 SA5 竞价实例每小时 0.38 元,AWS M7g(Graviton3 ARM 架构)竞价实例每小时 0.32 元。但考虑到我们代码是 Java 写的,ARM 架构需要重新编译适配,短期成本太高,最后选了腾讯云 SA5 竞价实例(2024 年 3 月发布的新实例,AMD EPYC Bergamo 架构,性价比比上一代高 20%)。
然后配弹性伸缩(Auto Scaling)。我们图片处理服务的流量规律是:早上 9 点到晚上 9 点,QPS 从 500 涨到 3000,晚上 9 点后降到 200 以下。之前固定开 5 台实例,每天有 12 小时只需要 1 台。用弹性伸缩后,我们设置了两个触发条件:CPU 使用率超过 60% 时加 1 台实例,低于 30% 时减 1 台实例,最小实例数 1,最大 10。同时把竞价实例的比例设为 80%,剩下 20% 用按量付费实例保底(防止竞价实例全部被回收)。
配置完第一个月,费用就从原来的 8640 元降到了 5184 元,降本 40%。这里有个细节:弹性伸缩的冷却时间要设合理,我们一开始设了 1 分钟,导致流量波动时实例频繁创建销毁,反而增加了启动成本(每台实例启动要 2 分钟,频繁启停等于白扔钱)。后来改成 5 分钟冷却,实例数量稳定多了。
给个我们实际用的弹性伸缩配置脚本(基于腾讯云 CLI,完整可运行):
这个脚本我们用来快速复制环境,现在测试环境、预发环境都用竞价实例+弹性伸缩,成本比之前降了 55%。有个坑要提醒:竞价实例被回收时,云监控会发通知,但你的服务如果没做优雅下线,正在处理的任务会失败。我们当时在图片处理服务里加了个监听 SIGTERM 信号的代码,收到回收通知后,先停止接收新任务,把正在处理的任务状态存到 Redis,等新实例起来再接着处理,任务失败率从 3% 降到了 0.1%。
另外,2024 年云厂商都在推 ARM 架构实例,比如 AWS Graviton3、阿里云鲲鹏,性价比比 x86 高 30% 左右。如果你们项目用的是 Go、Python 这类对架构无感的语言,完全可以迁移到 ARM 竞价实例,成本还能再降 20%。我们今年计划把日志分析服务(用 Go 写的)迁到 AWS M7g 竞价实例,预计每月能再省 2000 元。
去年帮一个做在线教育的小团队折腾过一次服务器迁移,他们的业务场景是晚上 8 点到 10 点直播课高峰,平时就零星回放请求。一开始他们听别人说 AMD 性价比高,直接上了 SA5 的 8 核 16G 实例,结果第一次直播高峰就翻车了——CPU 跑到 90% 不说,还偶尔丢包,老师讲课卡得学生全在弹幕刷“听不见”。
我当时第一反应是查监控,发现他们把直播推流服务和数据库都塞在同一台机器上,SA5 的单核性能其实够日常,但直播转码是吃单核主频的,高峰期一挤就爆。后来我给他们拆了架构:推流服务换 g8i 的 4 核实例,单核主频高,转码稳;数据库迁到单独的 M7g 实例,ARM 架构省成本还够用;平时非高峰时段用弹性伸缩缩到 2 台,高峰前自动扩容。折腾完第一次直播,CPU 最高才 55%,丢包也没了,月底账单还比之前省了 30% 多。
我自己这么多年用下来,觉得 g8i 适合吃单核性能的场景,比如直播、实时计算,别为了省那点钱硬上 AMD;SA5 适合跑批量任务、测试环境,平时低负载业务用着挺香,但别往核心服务上堆;M7g 适合当数据库、静态资源存储,ARM 架构的兼容性现在其实没啥大问题,除非你用的老框架不支持。
别盲目追新实例,也别只看价格选最便宜的。先拿测试环境跑一遍你的真实业务流量,比对着参数表拍脑袋靠谱多了。新手最容易犯的错就是“别人用啥我用啥”,真到线上出问题,还是得自己蹲监控查日志,那时候再后悔就晚了。