序幕 无忧无虑
公司的一次中型线上活动,一个书法的微信投票活动。大约400+的投票项。
由于之前这个投票系统做过另一个投票(类似优秀小区评选),当时约有1900+的投票项,安稳的度过那次活动后,并不担心这次线上投票。
开始 循序渐进
第一天
由于之前流量不大,活动一开始1核1G也勉勉强强能扛住。
大约11点,流量开始涌入活动页面出现卡顿。于是开始把服务器升级到2核4G,解决问题。
一个小时之后,CPU又跑满,于是升级到4核8G,解决问题。
第二天
流量开始达到高峰 每小时将近4000IP,4-6W次PV访问。升级到8核16G,解决问题。
第三天
流量维持4000-5000IP/每小时,访问量开始上升。最大的时候每秒1.5K次请求。
CPU开始跑满,开始尝试优化代码
有java的同事推荐使用redis做队列,所有写入操作压入一个队列里,开启一个定时任务每秒执行N条操作即可。
理论上这个可以解决写入压力问题,但是其中也有一些关键因素导致无法使用redis
1.代码不成熟,写框架时并没有考虑到redis缓存,缓存类写的是file缓存。如果临时写一个redis类直接部署,怕影响线上环境
2.用户群体特殊,如果用户投票后发现票数并没有实时增加,就会产生一些臆想(主办方内部操作投票之类的)或者连续点击投票加重服务器负担。
3.压力所迫,如果部署上线没有解决写入问题却因为代码原因丢失写入的sql,整个影响是灾难性的。
所以当时思考再三并没有启用redis,而是采用了file缓存(这个缓存类经过多个项目的验证,证明是可信任、安全的代码)。
如何优化查询?
首先查看cnzz的访问记录,发现首页和介绍页的访问占比达到了49%,也就是说如果能把这2个地方优化好,至少能减少25-48%的压力。
首页
因为首页需要微信授权等一些操作,所以完全的静态页面是做不到的。
很感激自己当时写的时候,前台的投票项目列表用的是ajax请求接口,于是把一些接口做了file缓存 N秒刷新一次
首页一些 分享标题,内容,封面等可以减少查询的地方先直接在代码中写死,减少单个页面的请求
首页的bannar图也不会频繁变更 于是也先写死
介绍页
介绍页无非就是 bannar复用 + 介绍 不会变动,于是可以做成静态页面,减少请求。
做完这些,服务器的压力也小了一些。
之后对评论之类的接口都加了缓存,整体压力下来了
第四、五天 访问激增
这两天的访问量突然激增,同时也有部分刷票团队进入,本就高负载的服务器碰上高并发的刷票机器,更是不堪重负。
服务器频繁502 400错误,靠着重启mysqld 拉大缓存时间艰难的度过这2天
第六天 迁移新居
连着两天的高CPU和高频次访问,服务器不堪重负。于是准备从apache换成nginx。。毕竟不是说nginx高并发的负载更好么,而且还有waf防火墙可以设置,拦截一些IP高频次的请求。
这里踩到了一个坑,以往阿里云dns解析ip 大约2分钟就可以解析到新服务器,于是新购入了一台同等配置的机子(一周),先装好了环境拿测试域名测试通过之后,把ip解析到新服务器上。
谁知道,微信里的dns缓存炒鸡久。。。 大约1个多小时 dns才解析到新的IP上,于是就出现了IOS能访问新服务器,Android用户依旧访问老服务器的BUG。。。
切换到nginx之后,稳定下来了。 整个下午就出现了一次CPU 100%,且只持续了短短几分钟。
以为问题解决了,终于舒了口气好好睡一觉。
第七天 不得安生
一大早接到电话,服务器又炸了。
大早上起来看代码,没有问题啊。该优化的优化了,该缓存的缓存了,怎么又CPU 100%了? 登录阿里云APP查看云监控
TCP每分钟 2K请求,mysqld跑满了CPU。。
有php的同事看了之后,说压力是数据库的问题? (我当然知道是数据库= =!!)
说换上阿里云RDS 用云数据库就好了。(自掏腰包买了一个RDS 1核1G 支持2000并发)
这里要吐槽阿里云RDS一句 什么玩意?配置好加好白名单,内网连不进 外网巨慢无比,一点点查询CPU跑满?这效果我买你RDS还不如自建数据库呢?
于是折腾了几个小时,RDS配好了,数据同步完成。(下次准备出一个数据同步的教程,用着还是挺方便的)
挑了一个访问少的时间段,连上阿里云的RDS。
然后,因为访问RDS超时,导致php-fpm挂了,整个页面直接502。。。。
没办法,切回本地数据库 继续讨论
后来讨论时说到数据库进程表,试试查看进程来找到哪些sql执行慢。
觉得有戏,正好CPU 100% 于是输入了命令搜索(命令写在文末总结第11条了,记得看)
查到2条记录
1 2 3 4 5
| select count(id) from 数据表1 where ip = 'xxx.xxx.xxx.xxx' and posttime >= xxxxxxxxxx
select count(id) from 数据表2 where unionid = 'xxxx' and canceltime = 0
|
嗯哼,数据表1因为记录了大量数据,所以表行数将近110W条了,每次查询巨废时间。数据表2量不是太大,但是因为unionid和canceltime中没有设置索引,所以执行了全表查询。
ok,知道了问题 就好解决了
数据表1备份之后,清空了部分失效的无用记录(剩余将近8W条,查询压力一下子就小了)
数据表2在清理重复值后,把unionid设为 唯一 索引,查询速度也提升了
至此,CPU负载降下来了,困扰了一周的问题终于解决了。
出门,好好吃顿饭。
优化总结
1.查询语句优化,能合并查询减少请求的就合并掉
2.页面能静态化就做静态化,减轻服务器压力
3.页面的一些样式表 图片可以存放到cdn服务器(七牛 又拍 腾讯 阿里oss等等),能降低页面请求的数量
4.能用nginx就用nginx,高并发还真比apache强一点
5.接口做上缓存,实时性要求高就30-60秒缓存,要求低可以设置几分钟(能用redis就用redis,稳不住就用file缓存,不丢人),能避免短时间内大量重复查询
6.数据库设计尽量合理 有些功能如果不好设计宁可砍掉不要强加上去,高并发的时候有苦头吃
7.逻辑正确不代表高并发下逻辑也会正确,如果CPU跑满很有可能一开始的数据库返回的结果就有问题。需要多次执行sql的地方应该做好每一步数据校验
8.如果不是长期高并发,可以仅在活动期间购买一台按周计费的服务器跑程序,省钱。
9.换服务器换IP导致的DNS解析变更操作,尽量在人少 或者 半夜完成,谁也不知道微信里刷新dns要多久
10.RDS这东西,用着麻烦。临时抱佛脚不适用
11.show processlist是好东西 能在高峰期帮你找到sql慢的原因