失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > php代码并发控制 php并发控制

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

时间:2023-05-05 21:40:37

相关推荐

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

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

在发短信的时候 一般会限制一个手机号发送一次的时间间隔在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);

}

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

当我们果断时间再次请求 我们收到日志1-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

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

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

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

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

-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

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

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

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

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

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

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

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

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

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

1. mysql 唯一索引

2. mysql 加锁

MyISAM 表级锁

InnoDB 行级锁

Write

Read

3. redis 加锁 或者文件加锁

4. 队列

如果觉得《php代码并发控制 php并发控制》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。