这里写目录标题
序
现在网上关于秒杀,抢票,超卖等并发场景的文章已经烂大街了。之前看过很多,但从来没自己测试过。今天心血来潮,想落地一下。
虽然解决的方法很多,可不一定都适合各种具体场景,所以过一遍流程,也能更好的把握哪些场景更适合怎样的方法,此篇文章的目的就是如此。
再啰嗦一句:并发和大流量是两码事,小流量也可以有并发。
业务逻辑
老板发福利,400个奖,不能发重,不能发超,大家快来抢啊!
准备工作
环境
脚本:PHP,框架:Laravel,web服务器:Nginx,数据库:MySQL,NoSQL:Redis,并发压测工具:Go-stress-testing-linux,系统:CentOS7。
具体的脚本不重要,这里用的是自己比较熟悉的。
数据库表结构
code
字段 | 类型 | 说明 |
---|---|---|
id | int11 unsigned not null | 自增主键 |
code | char14 not null | 14位Char unique |
status | bit1 not null | 0未发放 1已发放 |
update_time | datetime | 发放时间 未发放为null |
code_out
字段 | 类型 | 说明 |
---|---|---|
id | int11 unsigned not null | 自增主键 |
code_id | nt11 unsigned not null | code表主键 |
create_time | datetime not null | 发放时间 默认CURRENT_TIMESTAMP |
code_out表主要用来表现并发问题。
正常情况下,code_out表数据量和code表status=1的数据量必须一样,且code_out表一定没有code_id相同的记录,否则同一code肯定被发给了多个用户。
这里补充下,时间为什么没有用timestamp。
其实以前我也喜欢用timestamp类型的,可自从有一次遇到有记录的实际创建时间是18xx年,导致客户劈头盖脸来骂了一顿这种情况之后,就改掉了这个习惯。当然我也不是说timestamp不好,而是人总是有惯性思维。
再补充一下,为什么很多字段要可以不允许为null。
字段为null是很危险的,它可能导致查询的数据和实际逻辑要求的不一致,并且null比空字符串会占用更多的空间。所以,除非业务要求区分"0"和"没有",都建议字段不允许null,怎么算都不划算对吧。
数据填充
use Illuminate\Support\Str;
// 原谅我放纵不羁爱自由,懒得建模型了,直接用DB类走起
for ($i = 0; $i < 100; $i++) {
\DB::table('code')
->insert([
'code' => Str::random(14),
]);
}
安装go-stress-testing-linux
go-stress-testing-linux是Go写的压测工具。
git上有打成二进制的可执行文件,下载即可(github搜索link1st/go-stress-testing)。
下载后记得赋予文件可执行权限哦。想偷懒的话,就直接拷贝到/usr/bin下吧。如果使用二进制文件的话,不需要装go环境。
为什么选择go-stress-testing-linux?
它的运行原理是利用Go的携程发起并发,是真正意义上的多线程并发。
安装Redis
不再赘述,网上教程很多。
安装php redis扩展
这一步可选,php有很多种方式可以和redis互通,个人更喜欢这种原始的方法。
让游戏开始吧
压测参数
go-stress-testing-linux -c 1500 -n 2 -u {url}
模拟1500个用户,每个用户请求2次。看上去数字并不大对吧?
压测过程
没有任何保护措施
开抢咯
$remain = \DB::table('code')
->where('status', 0)
->select('id', 'code')
->first();
if (null == $remain) {
return [
'code' => 500,
'msg' => 'no code available',
'data' => null
];
}
\DB::table('code')
->where('id', $remain->id)
->update([
'status' => 1,
'out_time' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME'])
]);
\DB::table('code_out')
->insert([
'code_id' => $remain->id
]);
return [
'code' => 200,
'msg' => 'congratulations',
'data' => $remain->code
];
结果
┬────┬──────┬──────┬──────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬
│ 耗时│ 并发数│ 成功数│ 失败数│ qps │ 最长耗时 │ 最短耗时 │ 平均耗时│ 下载字节│ 字节每秒 │ 错误码 │
┼────┼──────┼──────┼──────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼
│ 1s│ 81│ 81│ 0│ 2080.32│ 1000.70│ 389.09│ 721.04│ │ │ 200:81│
│ 2s│ 310│ 310│ 0│ 1173.30│ 1971.56│ 389.09│ 1278.44│ │ │ 200:310│
│ 3s│ 545│ 545│ 0│ 835.09│ 2949.67│ 389.09│ 1796.22│ │ │ 200:545│
│ 4s│ 778│ 778│ 0│ 657.16│ 3924.38│ 389.09│ 2282.54│ │ │ 200:778│
│ 5s│ 1005│ 1005│ 0│ 545.64│ 4908.34│ 389.09│ 2749.07│ │ │200:1005│
│ 6s│ 1233│ 1233│ 0│ 464.19│ 5949.70│ 389.09│ 3231.45│ │ │200:1233│
│ 7s│ 1451│ 1453│ 0│ 404.71│ 6909.48│ 389.09│ 3706.35│ │ │200:1453│
│ 8s│ 1500│ 1680│ 0│ 365.77│ 7277.43│ 389.09│ 4100.99│ │ │200:1680│
│ 9s│ 1500│ 1902│ 0│ 341.60│ 7277.43│ 389.09│ 4391.14│ │ │200