php代码并发控制,php并发控制

博客探讨了在并发环境下,如抢票和短信发送场景中可能出现的超卖和重复发送问题。通过举例说明了在PHP中,由于并发请求导致的逻辑错误,并解释了这个问题源于进程并发执行。提出了使用文件锁作为解决方案,以确保在同一时刻只有一个请求能够执行关键操作,避免了并发导致的错误。此外,还提到了数据库的唯一索引和不同级别的锁作为其他防止数据冲突的方法。
摘要由CSDN通过智能技术生成

这两天遇到一个常见的并发控制的问题,类似抢票问题,当剩下一张票的时候 两个人同时抢这张票 可能会出现多卖的情况

在发短信的时候 一般会限制一个手机号发送一次的时间间隔在60s

我们的代码大概会这么写1

2

3

4

5

6

7

8

9

10$time = getLastSendTime();

if (time() - $time > 60) {

sendSms();

# 更新最后发送时间

updateLastSendTime(time());

}

else {

不能发送

}

看似严谨的逻辑 但是在并发的情况下 同一时间 两个请求发送过来的话 情况就不一样了

可能第一个请求还没有执行到 updateLastSendTime 第二个请求就已经执行到判断语句了

这个时候 程序判断会允许这个请求发送短信的,当请求是成千上万的并发 短信就会灾难性的被刷掉了

同样在上面买票的场景,并发过来的时候,会出现超卖的情况

看下面代码,重现下这个问题 (这里为了简化问题 使用文件代替redis 等持久化存储)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$interval = 60;

if (time() - getLastSendTime() > $interval) {

$msg = "send sms";

echo $msg;

_log($msg);

updateLastSendTime();

}

else {

$msg = "please request after $interval s";

echo $msg;

_log($msg);

}

/* 更新最后发送时间 */

function ()

{

return file_put_contents('time', time());

}

/* 获取最后发送时间 */

function getLastSendTime()

{

if (!file_exists('time')) {

updateLastSendTime();

return time();

}

return file_get_contents('time');

}

/* 日志记录 */

function _log($content)

{

file_put_contents('sendsms.log', date('Y-m-d H:i:s') . " : " . $content . "n", FILE_APPEND);

}

当一个请求触发脚本 我们收到日志12017-04-06 10:57:16 : send sms

当我们果断时间再次请求 我们收到日志12017-04-06 10:57:36 : please request after 60 s

这个是我们一般情况下的状态,下面我们使用 ab(apache beanch) 工具来测试下并发的情况

我们发送5个用户 的并发1ab -c 5 -n 5 http://localhost/lock.php

这个时候我们得到的日志是这样的1

2

3

4

52017-04-06 11:04:22 : send sms

2017-04-06 11:04:22 : send sms

2017-04-06 11:04:22 : send sms

2017-04-06 11:04:22 : please request after 60 s

2017-04-06 11:04:22 : please request after 60 s

同一时刻发送的五个请求 前三个都成功了 你的逻辑被羞辱了 ==!

注意 这里产生的原因是进程问题导致

如果你使用php自带的servphp -S 127.0.0.1:8081/lock.php 就不会出现这个问题

那是因为他是单进程/单线程运行的 所有的请求是进行排队的

但是如果你使用的nginx 那个这个问题会很明显,因为nginx有强大的吞吐量(基于epoll模型)

你开的进程越多 问题越明显,我这里开发环境开的是三个nginx进程 正对应了上面的日志

当然线上服务为了更好服务更好的请求 会开更多的进程。

那个如何解决这样的问题呢?

这两天实验了两种方法队列

文件锁

队列一个目的是为了 代码解耦,这里要把整个逻辑代码搬到队列 不太合适

文件锁可以在控制器层来做,且按需求来做,但是高并发用户量的时候这么做可能会让其他请求用户一直等待

可能等到http请求60秒超时…

这里使用文件锁的方法先解决这个问题1

2

3

4

5

6

7

8#给文件上锁

# source 是文件资源句柄 fopen() 的返回

# LOCK有以下几个取值

# LOCK_EX 排他锁 被锁定的文件 在被其他进程上锁的时候 需要阻塞等待 文件被解锁

# LOCK_SH 共享锁

# LOCK_UN 释放锁

# LOCK_NB 非阻塞 仅适用于 linux

bool flock(source, LOCK)

下面加入锁的功能1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25lock(function (){

if (time() - getLastSendTime() > 10) {

$msg = "send sms";

echo $msg;

_log($msg);

updateLastSendTime();

}

else {

$msg = "please request after 60 s";

echo $msg;

_log($msg);

}

});

function lock($callback)

{

$fp = fopen('.lock', 'w+');

if (flock($fp, LOCK_EX)) {

call_user_func($callback);

}

fclose($fp);

}

......

这个时候再次1ab -c 5 -n 5 http://localhost/lock.php

得到日志:1

2

3

4

52017-04-06 11:21:32 : send sms

2017-04-06 11:21:32 : please request after 60 s

2017-04-06 11:21:32 : please request after 60 s

2017-04-06 11:21:32 : please request after 60 s

2017-04-06 11:21:32 : please request after 60 s

只有第一次执行成功,后面的都失败 判断成功了。

加锁的目的就是让第一个请求先执行完(记录最后一次发送的时间),在执行第二个请求

这样判断才能在并发的场景生效

并发请求 数据库重复插入数据 解决办法

1. mysql 唯一索引

2. mysql 加锁

MyISAM 表级锁

InnoDB 行级锁

Write

Read

3. redis 加锁 或者文件加锁

4. 队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值