常见并发问题的解决方案

本文通过模拟并发场景,探讨并发问题及其解决方案,包括无保护措施导致的超发,版本控制的MVCC,使用Redis缓存以及并发锁。通过实验展示了不同策略在并发控制中的效果,强调了正确处理并发问题的重要性。
摘要由CSDN通过智能技术生成

现在网上关于秒杀,抢票,超卖等并发场景的文章已经烂大街了。之前看过很多,但从来没自己测试过。今天心血来潮,想落地一下。

虽然解决的方法很多,可不一定都适合各种具体场景,所以过一遍流程,也能更好的把握哪些场景更适合怎样的方法,此篇文章的目的就是如此。

再啰嗦一句:并发和大流量是两码事,小流量也可以有并发。

业务逻辑

老板发福利,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│    818102080.321000.70389.09721.04│        │        │  200:81│
│  2s│   31031001173.301971.56389.091278.44│        │        │ 200:310│
│  3s│   5455450835.092949.67389.091796.22│        │        │ 200:545│
│  4s│   7787780657.163924.38389.092282.54│        │        │ 200:778│
│  5s│  100510050545.644908.34389.092749.07│        │        │200:1005│
│  6s│  123312330464.195949.70389.093231.45│        │        │200:1233│
│  7s│  145114530404.716909.48389.093706.35│        │        │200:1453│
│  8s│  150016800365.777277.43389.094100.99│        │        │200:1680│
│  9s│  150019020341.607277.43389.094391.14│        │        │200
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值