宕机后的架构思考
背景
上个月公司服务器频繁宕机,第一次是因为日志撑爆了服务器,已经在[服务器日志空间解决方案-外挂磁盘]中解决了;
后面又有两次宕机,都是数据库 RDS 使用率太高导致的;
由于阿里云监控 RDS 报警不及时,所以比较难及时进行处理;
后来我使用了 processlist 查看:
1 2 3 4 |
|
可以实时的观测到是哪些 sql 现在执行慢,当然这些 sql 可能本身不慢,而是因为被慢 sql 阻塞了,导致执行时间太久;
我们可以通过 kill 命令将进程号杀掉(需要用有权限的账号登录);
这些慢 sql 是 java 的新项目部署上去没有增加有效索引导致的,而新项目 和 已经稳定并且拥有大部分用户的 PHP 项目放在同一个 ECS,数据库也是同一台 RDS; 一个库占用了大部分 CPU 牵连另一个库执行 sql,造成部分 PHP 项目的用户开始投诉;
不过该问题已经得到解决,目前已经为不同项目拆分不同 ECS 和 不同的 RDS;
当前的 PHP 项目一些表的数据也都到达 2,3k万,现在 java 的项目有数据仓,有中台; 数据增长速度会比 PHP 项目快;
虽然已经稳定运行,但是这件事让我思考一个问题,如果以后用户级上去了,该怎么办?
每秒请求数 qps
前阵子看过一篇关于架构设计的文章,这里借用他的方法来设计为当前项目的架构做分析;
架构设计一般通过估量 qps,然后来设计架构;
首先我去查看项目的 qps,因为目前项目已经在线上,可以通过动态截取每秒日志请求数来估算 QPS:
1
|
|
得出结论,大概 qps 为 100 左右;
目前 100 个请求每秒对于 8 核的 ECS 已经足够;
如果按照每个请求一秒要处理 3 个 sql 请求,那么对于 RDS 就是 300 请求每秒,也是足够的;
所以,就目前位置,服务器 ECS 和 数据库 RDS 都能够承载当前用户量访问。
集群化部署
系统集群化部署
那么以后销售人员推广力度变大,qps 将逐渐变大,那么一台 ECS 可能承载不了;
这时候我们可以加多一台 ECS 来拆分 qps;
假设 qps 为 500,我们使用“负载均衡”策略,将 qps 平分到两台 ECS 服务器上:
架构
|
500/s
|
↓
{ nginx 负载均衡 }
| |
250/s 250/s
| |
↓ ↓
{ ECS1 } { ECS2 }
| |
750/s 750/s
| |
+————————+
| |
↓ ↓
{ 数据库 RDS }
简单实现:
Nginx 通过 upstream 来给两台 ECS 分配权重, 以下是 tomcat + nginx 配置,PHP + nginx 也类似,只是换一下 tomcat_server 和 端口 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
数据库分库分表 + 读写分离
但是现在有一个问题,就是数据量庞大,例如开单表已经到达了 26,934,626 条,虽然都有效的利用了索引,但是明显开单已经变慢;
再加上每天店铺小妹都在同一个高峰时段不停开单,若有卡顿,则可能会拖累其他 sql 执行,并且造成 CPU 使用率上升,从而宕机;
所以面临的问题主要是两个:
1:数据量太大,导致查询缓慢:
2:面对较大的 qps,例如前面 500 qps 已经分成两个请求 750/s 到 RDS 上;
当 qps 不断增加,ECS 都可以用负载均衡增加机器解决,但是系统层面上解决了,数据库层面上并发量到达 3000/s 就有问题了;
解决方案:
通过将经常查询并且数据量大的表进行 “水平分割”;
分库分表 + 读写分离; 也就是把一个库拆分为多个库,部署在多个数据库服务上,这是作为主库承载写入请求的;然后每个主库都挂载至少一个从库,由从库来承载读请求。 此时假设对数据库层面的读写并发是 3000/s,其中写并发占到了 1000/s,读并发占到了 2000/s。 那么一旦分库分表之后,采用两台数据库服务器上部署主库来支撑写请求,每台服务器承载的写并发就是 500/s。每台主库挂载一个服务器部署从库,那么 2 个从库每个从库支撑的读并发就是 1000/s。
架构
|
高峰 1000/s
↓
{ nginx 负载均衡 }
| | |
300/s 300/s 300/s
↓ ↓ ↓
+———→ { ECS1 } { ECS2 } { ECS3 } ←————+
| | | |
| 500/s 500/s |
1000/s ↓ ↓ 1000/s
| { 主库1 } { 主库2 } |
| ↓ ↓ |
+———— { 从库1 } { 从库2 } ——————+
简单实现
主从数据库:
如果数据库是 ECS 自己创建的,需要通过开启 my.cnf 的 Binary log 功能实现 ;
如果是直接购买 RDS,可以购买两台,一台做主库,一台做从库,通过阿里云自带服务 DTS 同步数据;
分库分表:
最好的做法,是服务在 搭建之初 就设计为分库分表的存储模式,从根本上杜绝中后期的风险。 不过,会牺牲一些便利性,例如列表式的查询,同时,也增加了维护的复杂度。
如何分表: 像 Laravel,我们可以事先写一个 model,对应这个 model 的所有分表,然后为这个 model 添加一个方法;
这个方法 通过传入查询条件,对每个表进行查询,然后 union 起来返回;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
不过,最好还是使用 数据库中间件,数据量再大的时候,放到 Elasticsearch 里面,自带分片处理,交给他底层实现。
缓存
当 qps 再次上升,系统层面可以加机器,但是数据库其实本身不是用来承载高并发请求的,
所以通常来说,数据库单机每秒承载的并发就在几千的数量级,而且数据库使用的机器都是比较高配置,比较昂贵的机器,成本很高。;
或者原先没有分库分表,想分库加机器又觉得贵;
对于这两个问题,可以通过引入缓存解决;
根据 “二八定律”,80%的请求只关注在20%的热点数据上。因此,我们应该建立 服务器 和 数据库 之间的缓存机制。
这种机制,可以用磁盘作为缓存,也可以用内存缓存的方式。通过它们,将大部分的 热点数据查询,阻挡在数据库之前;
可以根据系统的业务特性,对那种写少读多的请求,引入缓存集群。
写数据库的时候同时写一份数据到缓存集群里,然后用缓存集群来承载大部分的读请求,这里可以使用 Memcached 或是 Redis 实现;
像前面架构图,读请求目前是每秒 2000/s,两个从库各自抗了 1000/s 读请求,但是其中可能每秒 1800 次的读请求都是可以直接读缓存里的不怎么变化的数据的,
剩下落到数据库的只有 200 次;
架构
|
高峰 1000/s
|
↓
{ nginx 负载均衡 }
| | |
300/s 300/s 300/s
↓ ↓ ↓
+——————+——→ { ECS1 } { ECS2 } { ECS3 } ←————+———————+
| | | | | |
| | 500/s 500/s | |
| 100/s ↓ ↓ 100/s |
| | { 主库1 } { 主库2 } | |
| | | | | |
| | 同步 同步 | |
900/s | ↓ ↓ | 900/s
| +—— { 从库1 } { 从库2 } ——————+ |
| |
+——————————————————— { 缓存集群 } ———————————————————+
参考:http://www.php.cn/linux-417207.html
消息中间件集群
上面对读的数据做了缓存,那么如果 写操作 比较多,那该怎么办,不可能一直加机器吧?
这里我们要引入 消息中间件集群,例如 RabbitMQ,他是非常好的做写请求异步化处理,实现 削峰填谷 的效果。
架构
|
高峰 1000/s
|
↓
{ nginx 负载均衡 }
| | |
300/s 300/s 300/s
| | | +————————————————————————————+
↓ ↓ ↓ | |
+——————+——→ { ECS1 } { ECS2 } { ECS3 } ←————+———————+ |
| | | | | | { 消息中间件 MQ }
| | 500/s 500/s | | |
| | | | | | 削峰填谷 100/s 请求慢慢写
| 100/s ↓ ↓ 100/s | |
| | { 主库1 } { 主库2 } ←————+——————+———————————+
| | | | | |
900/s | ↓ ↓ | 900/s
| +—— { 从库1 } { 从库2 } ——————+ |
| |
+——————————————————— { 缓存集群 } ———————————————————+