1.3 机器准备:轻量应用服务器怎么切
场景:一台机器跑十个站,还是十台机器各跑一个
定了混合分层的架构后,机器怎么买成了现实问题。最初我们抠门,所有角色挤在一台 2 核 4G 的轻量应用服务器上:Nginx、PHP-FPM、MySQL、Redis 全部本地,十个客户一起跑。结果某个客户做活动流量一冲,CPU 满载,所有租户的站点一起 502,客服电话被打爆。那天我们意识到,多租户最怕的不是某一个租户出问题,而是一个租户把所有人都拖下水,定制时代一只手只抖自己的杯子,SaaS 时代一只手抖的是全桌的杯子。
后来财务一句算不上温柔的提醒也加速了我们重新规划:十几个客户摊下来,机器成本其实已经超过了带宽和域名,而且每加一个新客户就得加内存或换机器,这笔账永远在涨。只看单价不看的总账的习惯,差点把利润吃光。
重新规划时我们立了一条原则:无状态的计算节点尽量集中,有状态的数据尽量往外搬。Web 与后台是计算,搬上轻量应用服务器集群;MySQL 搬到云数据库 MySQL,Redis 搬到云数据库 Redis,文件搬到对象存储 COS,日志丢进日志服务 CLS。机器上只留代码和 Nginx,哪台挂了直接换镜像,数据都在云上面。轻量应用服务器的文档入口是 cloud.tencent.com/document/product/1207,新用户可以先在 轻量应用服务器特惠 领个低价配置试起来。
这条原则定下来之后,还带来一个隐形好处:机房再也不用备空间硬盘和备用机了,本地数据全部消失的那天,我们终于可以睡整觉。在此之前每次机房维护公告都能让运维同事半夜爬起来看磁盘,就怕哪块硬盘挺不过去。
另一个关键决策是站点与后台分开。后台是超级管理员在用,操作频率低但权限极大,和租户流量混在一起既不安全也容易被大流量影响。分开后我们只开放后台机器的白名单访问,前台机器的 SSH 权限也收到最小范围,运维同事只需要维护两条明确的边界,而不是一张乱糟糟的权限表。
方案:角色分层与机型规划
我们按角色把机器切成四类,各司其职,互不连坐。
| 角色 | 部署位置 | 机型建议 | 扩容方式 |
|---|---|---|---|
| 前台站点(租户访问) | 轻量应用服务器 | 2 核 4G 起步 | 镜像复制加负载均衡 |
| 后台(运营与超级管理) | 轻量应用服务器 | 4 核 8G | 逻辑上单台够用 |
| MySQL | 云数据库 MySQL | 按租户数选规格 | 主从升级不中断 |
| Redis | 云数据库 Redis | 2G 起步 | 集群版平滑扩容 |
机型上一个小建议:前台机器买两台中等配置,好过一台高配。两台之间能互相兜底,一台挂了用另一台顶一会儿,租户最多觉得慢一点;单台高配一旦挂了就是全站连环崩。成本几乎一样,稳定性差一个数量级,这笔账怎么算都划算。
这样切的好处每个都踩过坑才体会到:数据库上云后,独立库的新租户开通只是云 API 建个实例五分钟的事,不用再下单装机;文件进 COS 后,机房带宽再也不会被下载洪峰打满;日志进 CLS 后,查某租户的报错不再需要登服务器翻文件,还能直接做告警。
配置:一台机器挂多个租户站点
单机上多租户站点用 Nginx 的 server_name 区分,每个租户一个 server 块,root 指向同一份代码,靠环境变量注入当前租户。部署脚本批量更新配置后 reload,全程不用登机器改文件:
# /etc/nginx/conf.d/tenant-acme.conf
server {
listen 80;
server_name acme.oursaas.com;
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param TENANT_ID acme; # 注入租户号
fastcgi_param APP_ENV production;
}
}
PHP 侧在入口文件最前面读取 TENANT_ID,完成租户解析、连接切换、缓存前缀设置,之后整个请求都在正确的上下文里,业务代码完全不需要知道自己在哪台机器上。新增租户时开通脚本生成这样一个 conf 文件,跑 reload 即可生效,比手工编辑省事也不容易漏。上线初期十几个租户两台机器就够了,流量涨了就再做镜像加机器,伸缩的过程对租户完全透明。
机器一多,怎么知道每台还够用就成了日常问题。我们给每台机器定了三条红线,监控越过任意一条就自动通知,不再靠人肉巡检:
| 指标 | 警戒线 | 触发后的动作 |
|---|---|---|
| CPU 五分钟均值 | 大于百分之八十 | 通知并记录,连续三次就扩容 |
| PHP-FPM 活跃 worker | 大于总量的八成 | 告警,防止请求堆积 |
| 磁盘剩余 | 少于百分之二十 | 告警并清理日志 |
这三条红线定下来之后,两年里只被动扩过三次容,其余时候都是看到趋势提前动手。多租户业务有个特点是流量不均匀,某个租户搞促销就能撑爆一台机器,所以后来我们又加了按租户维度的机器负载前十看板,哪户在吃机器一目了然,拿着这个数据去跟客户沟通也比拿空口数字强。
顺带说一句镜像管理的细节:我们维护一个不含任何租户信息的黄金镜像,所有机器从它克隆,内存分配器、PHP 扩展、Nginx 配置模板全部预装好。租户相关的 conf 文件由开通脚本单独下发,机器本身永远是干净的。这个习惯在我们换 Ubuntu 大版本时救了命,几十台机器重做只花了一个下午。
踩坑:机器层面的四个教训
第一个坑是自动伸缩依赖本地状态。最初 session 和队列状态都在机器本地上,某次宕机切换到备用机,所有用户被踢下线。后来把会话整体迁到云数据库 Redis,机器真正变成无状态,做镜像替换才没有心理负担。会话与会话隔离的具体做法在 缓存隔离:Redis 键前缀与防串号 一节展开。
第二个坑是机器监控只看总量的假象。CPU 平均 40% 看着很健康,其实是两个租户跑满、其他租户在排队。监控必须按租户维度拆开,我们最后在应用层加了按租户号的上报,把每户的请求数和耗时推到日志服务,再在看板里分组聚合,哪个租户在拖后腿一秒可见。监控体系可以参考 轻量建站实战 的告警配置思路,小团队也做得起。
第三个坑是忽略了后台的安全暴露面。有次巡检发现后台机器的 9000 端口被扫描了上千次。赶紧收敛端口、加访问白名单,并把后台登录改成二次验证。多租户系统后台是一个入口管所有租户,一旦被打穿就是灾难级事故,怎么强调都不过分,这是我们认为机器层最值得投入时间的一处加固。
第四个坑是扩容时的配置漂移。手工加机器时漏装了一个 PHP 扩展,新机器跑了一周才暴露,某些页面偶尔白屏,排查了两个通宵才定位到是镜像差异。之后我们坚持所有机器从同一个镜像生成,绝不允许登机手工装东西,谁要改镜像必须走流程重新打包。
成本:二十个租户时的机器账单
分好层之后,机器费用变得可算可预测。这是我们二十个租户、其中四个独立库户时的月度账单构成,供同样在路上的团队参考:
| 项目 | 月费区间 | 说明 |
|---|---|---|
| 前台机器两台 | 一百六十元左右 | 2 核 4G 按量包年 |
| 后台机器一台 | 一百元左右 | 4 核 8G |
| 云数据库 MySQL | 六百元左右 | 共享库加四个独立库最低配 |
| 云数据库 Redis | 一百五十元左右 | 主从版 |
| 对象存储 COS | 按量 | 约五十元,随用量浮动 |
| 日志服务 CLS | 按量 | 约三十元,按索引流量计费 |
合计每月约一千一,摊到二十个租户头上每户五十五元,而我们的入门套餐年费远高于这个数。这就是分层带来的安全感:你知道每加一个租户大概多花多少,而不是月底看到账单才倒吸一口凉气。详细的全量成本复盘我们放在 5.4 那一节,那里还会算上一百个租户一年的总账。
小结
机器规划的核心是让状态离开机器:计算集中在轻量应用服务器,数据交给云数据库 MySQL 与云数据库 Redis,文件交给对象存储 COS,日志交给日志服务 CLS。机器越轻,扩容与容灾越简单,坏了一台直接换,数据一秒都不丢。记住两条就够用:角色分层互不连坐,所有机器从同一镜像出生。下一节我们把这套机器布局翻译成完整的技术栈地图,看看 PHP 框架和这些云产品怎么对接。
