从零搭建一个下载站,我的服务器选择和踩坑记录
服务器选型:从阿里云轻量到腾讯云CVM的折腾之路
最开始做亿企财税下载站的时候,我图省事直接买了阿里云的轻量应用服务器,2核4G配置,一年600多。心想一个小下载站能有多大压力?结果上线第一天就给了我一个下马威。晚上流量稍微上来点,服务器CPU直接飙到90%,打开后台都卡得想摔键盘。后来发现是轻量服务器的IOPS限制太死,用户并发下载时读写磁盘一频繁,整台机器就跟死机似的。更坑的是带宽才3Mbps,有人下载一个百兆的安装包,其他人再访问页面直接超时,这种体验谁受得了?
后来我换了腾讯云的CVM标准型S5,同样是2核4G但贵了一倍多,良心云的好处是带宽可以升到5Mbps,而且这块机器的磁盘随机读写性能确实比轻量强很多。操作上就是先在控制台搞个CentOS 7.9镜像,系统盘直接用40G高效云盘——千万别贪便宜买共享云盘,那玩意的IOPS高峰期连MySQL查询都慢。装好系统后第一件事就是关掉防火墙的iptables默认规则,要不然配死你,然后装Nginx和PHP环境。记得选nginx1.18而不是最新的1.20,因为很多老版本的下载站程序对1.20的支持有坑,我后来被坑过。
至于预算,如果你只是做个小站,我个人推荐腾讯云轻量服务器里那款2核2G的就行,带宽选4Mbps,一年大概400出头,日常够用。但要是预计有上千同时在线,最少得上CVM的4核8G,带宽开到10Mbps以上,不然流量高峰期服务器迟早给你颜色看。
系统环境搭建:宝塔面板的便捷和手动编译的取舍
环境配置我试过两种路子。一种是纯手动编译Nginx和PHP,另一种是用宝塔面板。手动编译最大的优点是灵活,比如我可以给Nginx单独开gzip压缩、调整worker_processes到和CPU核心一致,还能把PHP的pm.max_children设成更适合下载站的数值。但缺点是太费时间了,光编译Nginx就得等十几分钟,中间如果漏了某个依赖库还得回去折腾。
最终我还是选了宝塔面板,主要是省事。安装命令就一行,装好后直接在后台选LNMP一键部署。这里有个细节:安装时记得选PHP7.4,别选8.0或更高,因为很多老下载站的后台程序对PHP8支持不好,会出现后台菜单点击没反应的怪毛病。Nginx版本我选了1.22,宝塔自带的配置默认够用。面板里有个“网站”功能,添加站点时绑定域名、设置伪静态规则,这一步推荐用thinkphp的伪静态规则,因为很多下载站程序基于ThinkPHP框架开发的,不然访问页面404。
另外特别提醒——宝塔面板默认的PHP上传限制是2M,下载站肯定不够。你必须去PHP设置里把upload_max_filesize和post_max_size都改大,比如改成200M或更大,不然用户点下载按钮后进度条走一半就报错。这坑我当初找了两天才发现。
下载站程序选择:从开源CMS到定制化开发的博弈
程序这块我一开始图省事,找了个开源的CMS,比如帝国CMS加个下载插件。说实话,部署速度快,后台模板也现成的。但问题是这种通用CMS对下载站的功能支持太薄弱了——比如多文件分卷下载、断点续传、防盗链,这些核心功能要么没自带,要么得自己折腾二次开发。我当年为了搞分卷下载,愣是花了三天看帝国CMS的模板标签文档,最后搞出来效果还不好,用户反馈说点本页下载按钮后进度条卡住不动,原来是服务器端没处理好Range请求头。
后来我干脆找人定制了一套简单的下载管理系统,用PHP自己写逻辑。核心代码大概300行,主要就是处理文件索引数据库、生成真实的下载直链、配合Nginx的X-Accel-Redirect来把文件分发权限交给Nginx。这样好处是极大降低了PHP的内存占用,而且能灵活配置防盗链——比如只允许特定Referer或IP来访问下载链接。不过代价是开发费花了小两千,而且还得自己维护,不像CMS那样有官方更新。
如果你预算有限又想省心,我推荐用WordPress加Download Monitor插件。这个组合在功能上相对平衡,免费插件支持文件版本控制、下载次数统计、甚至简单的防盗链。部署时只要装好WP后,从后台插件市场搜索Download Monitor安装就行。注意安装后去设置里把下载文件的存储方式改成“重定向”,不然用户点本页下载按钮后走的还是PHP脚本,流量大时秒变拖垮服务器。
文件存储与CDN加速:用对象存储减轻服务器硬盘压力
我的下载站早期所有安装包都扔在服务器本地硬盘上。刚开始还好,后来文件多了,一个软件的不同版本加起来上百个,每个几百兆,40G的系统盘很快就满了。而且用户下载时全从服务器带宽走,5Mbps的带宽根本扛不住。后来才学会把文件迁移到阿里云OSS或腾讯云COS这种对象存储服务上。
操作步骤不复杂:先去云厂商的控制台建一个存储桶,区域选离你目标用户近的,比如国内用户就选华东或华南。权限一定要设成私有读写,所有文件通过服务器生成临时授权URL来提供给用户——如果设成公共读,一台服务器被刷流量就哭死了。然后在你下载站的后台文件管理功能里,把上传地址改成对象存储的API地址。代码里只需要调一次SDK,把文件上传到Bucket就行。注意上传时一定要把文件名保持原样,否则用户下载下来文件名乱码。
真正的好处是CDN加速。把OSS或COS和CDN绑定后,用户点本页下载按钮,实际是从CDN节点拉文件。比如我用的阿里云CDN,只需在OSS控制台开启CDN加速,然后在CDN控制台设置回源鉴权和防盗链。我自己只加了一条Referer白名单,只允许自己域名下的页面请求,不然有人把下载链接直接发出去被别人刷流量,一个月的CDN费用就直奔上千去了。
CDN的缓存策略也有讲究。对于安装包这种静态文件,缓存时间至少设一个月,过期后让CDN从源站拉新版本。注意如果更新了文件版本,一定要改文件名或者强制刷新CDN缓存,不然用户还是下载到旧版本。
功能实现:多版本下载和断点续传的配置细节
下载站的核心功能就是让用户能选版本、顺利下载。多版本下载我采用的方式是在数据库里建一个file_list表,字段包括文件名、版本号、大小、上传时间、对应的对象存储路径。后台加一个版本管理的表单,上传新版本时自动赋值版本号,旧版本保留在库里但显示为“历史版本”。用户在前台看到的就是一个下拉菜单,选一个版本后点本页下载按钮,PHP逻辑会根据版本号去数据库查对应的存储路径,生成临时签名URL。
断点续传这功能折腾了我好久。一开始用户反馈下载到一半断了,重来又得从头,100M的文件重新下气得摔手机。实际上要让前端支持断点续传很简单,重点是Nginx或CDN要在响应头里支持Range。你可以在Nginx的站点配置里加一句add_header Accept-Ranges bytes;,同时在PHP里把文件输出方式改成fread读流的方式。更省事的是直接把CDN或OSS的下载链接暴露给用户,这两个服务天生就支持Range,用户点本页下载按钮后直接连到CDN就好。
但有个常见问题:部分用户用的下载工具不支持断点续传(比如某些老旧浏览器),这时候点本页下载按钮会导致下载失败。解决办法是不要只提供一个下载方式,可以在页面加一个“使用浏览器的直接另存为”和“使用下载工具”两个按钮,前者走IDM那种直接下载,后者走普通的GET请求。小细节看是麻烦,但真能解决用户骂娘的问题。
防刷与安全:反盗链和限速策略的实战
下载站最怕被人恶意刷流量。我遭遇过一次,某天突然发现CDN流量比平时翻了十倍,查日志才发现有个IP短时间内请求同一个安装包上百次。问题是我当时啥防护都没做,直接几百块就没了。
后来我加了防盗链,最简单的就是在PHP代码里检查请求的Referer头。如果为空或者不是自己域名,直接返回403。但这种方法不够完善,因为有些下载工具的Referer是可以伪装的。更靠谱的是在Nginx层配合ngx_http_referer_module做白名单,只允许自己域名和某些搜索引擎的Referer来访问下载目录。具体配置把valid_referers写上你的域名,加上blocked参数,然后设if ($invalid_referer) { return 403; }。有的CDN也有类似功能,像阿里云CDN可以在高级设置里开启Referer防盗链,还能设成白名单模式。
限速也是防刷的重要手段。宝塔面板的Nginx配置里可以加limit_rate,比如在每个location块里加一句limit_rate 1000k,这样每个用户的下载速度就被限制在1MB/s左右。这样即使有人恶意下载,也不会吃满带宽。但注意设得太低会影响正常用户体验,1MB/s对百兆安装包来说大概一百秒,勉强能接受。
还有个重要经验:定期分析服务器日志。用命令行awk加sort就能统计出下载次数最多的IP,如果发现某个IP下载量异常大,就去云厂商的安全组里封掉它。别怕影响正常用户,真的正常用户不会一天下一个200M的软件二十次。
日常维护与备份:从手动到脚本化的转变
刚上线那阵子,我每天晚上手工备份数据库和文件。一个月后我就受不了了——累死,而且会忘。后来写了个Shell脚本,放在cron里每天凌晨三点自动执行。脚本内容很简单:先把MySQL数据库导出成SQL文件,然后用tar把整个网站目录和这个SQL包打成一个tarball,再用ossutil工具(阿里云的对象存储命令行工具)把包上传到备份Bucket。保留最近7天的备份,旧的脚本自动删。
这里有个坑:备份的压缩包一定要测试能恢复。我某次碰到服务器硬盘坏了,从备份恢复时发现SQL文件是空的,因为mysqldump命令没有加--lock-tables参数,导致备份过程中数据库写入新数据导致导出失败。后来我改了脚本,加上--single-transaction参数,这样能保证导出时数据一致性。
对于下载站来说,文件备份也值得重视。虽然用了对象存储,但万一不小心删了Bucket里的文件就麻烦了。我额外做了一个每日增量备份方案:用rsync把服务器上的下载文件同步到另一个云厂商的存储桶,虽然慢点但等于多了一道保险。注意rsync时别设--delete参数,不然误删本地文件后远程也跟着删就完蛋了。
最后说个教训:更新下载站程序前一定打快照。有一次我打补丁时忘了备份,结果导致后台管理页面出现500错误,用户无法上架新版本安装包,折腾了一下午才恢复。从那以后我养成了习惯,每次要动核心代码之前先在云控制台打个系统盘快照,花几毛钱买个安心。