前不久,一个朋友网站出了问题,让我帮忙看看,是一个商城,每天早上9点有一个秒杀活动,每天到点准时卡顿,希望我给点建议。

简单介绍一下优化的过程,和各位同行一起分享。

1. 项目server配置情况

一台mysql server,独立服务器,内存128G,i7 64核,配置比较高

web server 配置一般,10台,跑php-fpm

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_数据

基础架构图

php-fpm根据配置不同,起的php-fpm进程也不同。

运行情况,平时运行稳定,没什么压力,但每天抢单时,页面就i开始卡顿很长时间,有时还会出错;

web server和db服务器cpu使用率都非常高,达到95%以上,很明显是没有并发能力啦!

php-fpm优化能做的都做了,效果不是很明显,估计应该是mysql遇到瓶颈:

mysql> show status like 'Table%';
+----------------------------+----------+
| Variable_name | Value |
+----------------------------+----------+
| Table_locks_immediate | 10043 |
| Table_locks_waited | 0 |
+----------------------------+----------+

Table_locks_immediate 指的是能够立即获得表级锁的次数

Table_locks_waited 指的是不能立即获取表级锁而需要等待的次数

show OPEN TABLES where In_use > 0;

show variables like '%max_connections%'; 查看最大连接数

最大连接数已经达到了4000+,设置的max_connections=5000,感觉已经快达到极限了。

2. 优化第一步,肯定是sql优化了

配置my.cnf

slow_query_log=1
slow_query_log_file=slow.log
long_query_time=2

开启后,第二天一看,果然有大量的慢查询,最慢的竟然长达40s之多,在高并发下这种延时累积,系统不崩才怪呢!

日志下载下来,开始分析吧

基本3种情况:

(1) 没有索引,查询条件没有索引进行全表扫描,数据量大了很慢,这种加索引就行了。

(2) sql太复杂,很多类似下面

select * from table where userid in (select user_id from table2 where ...)

这种就尽量拆开,分成简单的sql,在php代码中处理。

(3) 数据量大,有些log数据,有几千万条,这些数据参与了逻辑判断,如判断登录次数,第几次登录等这种场景,可以分成冷热数据,就拆成了两个表了。

经过优化,读的问题有很大的改善。

3.增加从库,读写分离

为了减轻MySQL的压力,增加了两台从库,配置和主库一样,都是高配。

代码配置

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_redis_02

主从配置图

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_redis_03

主从架构图

增加了两台从库后,主库的压力依然很高,从库压力很小了,cpu只有20%多。

通过增加从库,我们达到了两个目的:

a. 提供了db读能力

b. 降低了写锁冲突

问题

1.为了避免实时读数据延迟,有些读操作仍然走的master主库,这也是主库压力大的一个因素;

2.数据唯一性问题 ,主从数据有差异,有些大表的记录数有很小的出入,当更新、删除从库不存在的记录,会导致同步失败。每次重新同步就要晚上停服,同步,再启动,让人很不爽!

4. 引入redis

虽然系统性能有了很大的改善,但高峰期依然压力大,还没有解决根本问题。于是决定引入redis,redis有两个作用。

(1) cache,能进缓存就放缓存

系统配置,这些也是在mysql ,虽然数据不多,但几乎没什么变化,不需要每次读取;

统计信息,这些不需要实时,而且查询性能较低,可以放缓存。

加索引效果不好的,如有的条件是男女,状态是1、2,这些加了索引收效甚微,也可以考虑结果放缓存。

(2)系统核心是抢单,也是写并发所在,把抢单操作放到redis,理论上并发量可以提高两个数量级。

因为要判断库存、写数据,而redis没有事务控制,如何才能保证不写脏数据呢,

看下面的伪代码

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_mysql并发抢单_04

redis抢单

经测试,随着并发量增加,redis事务被打断次数也随着上升,即事务执行失败。所以用redis的事务是不可靠的。

最后,引入框架提供的redis锁 yii\redis\Mutex,具体加锁解锁可以查看源码

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_mysql_05

redis锁

最终的架构图如下:

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_mysql_06

加了redis的架构图

最后:分库分表

表的水平切分,如图,把一个表的数据按照算法分到两个表里

服务器 抢单 部署python 程序 应该买什么服务器 抢单服务器怎么配置_mysql_07

分库

这样可以:

a. 降低每个表的容量

b. 提高每个表的读写能力

缺点:增加了系统复杂性,如果读写分离需要增加更多的机器,扩展时要考虑数据迁移等问题;

时间紧迫的情况下,不能快速实现。所以没有实施这项方案。

总结:

1.系统在满足需求情况下,越简单越好。开发维护成本越低,在项目初期一般以业务为主。

2.系统优化优先最简单、最直接的方法,有时简单的方法也很有效。复杂的方案实施起来成本也很高。

你在实际项目中都落地了哪些优化方案呢,欢迎留言一起探讨。