PHP 并发场景的几种解决方案

2023-11-14

PHP 并发场景的几种解决方案

在秒杀,抢购等并发场景下,可能会出现超卖的现象,在 PHP 语言中并没有原生提供并发的解决方案,因此就需要借助其他方式来实现并发控制。

列出常见的解决方案有:

使用队列,额外起一个进程处理队列,并发请求都放到队列中,由额外进程串行处理,并发问题就不存在了,但是要额外进程支持以及处理延迟严重,本文不先不讨论这种方法。
利用数据库事务特征,做原子更新,此方法需要依赖数据库的事务特性。
借助文件排他锁,在处理下单请求的时候,用 flock 锁定一个文件,成功拿到锁的才能处理订单。
一、利用 Redis 事务特征
redis 事务是原子操作,可以保证订单处理的过程中数据没有被其它并发的进程修改。

示例代码:

<?php
$http = new swoole_http_server("0.0.0.0", 9509);   // 监听 9509

$http->set(array(
    'reactor_num' => 2,  //reactor thread num
    'worker_num' => 4    //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
    $uniqid = uniqid('uid-', TRUE);    // 模拟唯一用户ID
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);    // 连接 redis

    $redis->watch('rest_count');  // 监测 rest_count 是否被其它的进程更改

    $rest_count = intval($redis->get("rest_count"));  // 模拟唯一订单ID
    if ($rest_count > 0){
        $value = "{$rest_count}-{$uniqid}";  // 表示当前订单,被当前用户抢到了

        // do something ... 主要是模拟用户抢到单后可能要进行的一些密集运算
        $rand  = rand(100, 1000000);
        $sum = 0;
        for ($i = 0; $i < $rand; $i++) {$sum += $i;}

      // redis 事务
        $redis->multi();
        $redis->lPush('uniqids', $value);
        $redis->decr('rest_count');
        $replies = $redis->exec();  // 执行以上 redis 事务

      // 如果 rest_count 的值被其它的并发进程更改了,以上事务将回滚
        if (!$replies) {
            echo "订单 {$value} 回滚" . PHP_EOL;
        }
    }
    $redis->unwatch();
});

$http->start();在这里插入代码片
使用 ab 测试

$ ab -t 20 -c 10 http://192.168.1.104:9509/

二、利用文件排他锁 (阻塞模式)
阻塞模式下,如果进程在获取文件排他锁时,其它进程正在占用锁的话,此进程会挂起等待其它进程释放锁后,并自己获取到锁后,再往下执行。

示例代码:

<?php
$http = new swoole_http_server("0.0.0.0", 9510);

$http->set(array(
    'reactor_num' => 2,  //reactor thread num
    'worker_num' => 4    //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {

    $uniqid = uniqid('uid-', TRUE);
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);

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

    // 阻塞(等待)模式, 要取得独占锁定(写入的程序)
    if (flock($fp,LOCK_EX)) {  //锁定当前指针

      // 成功取得锁后,放心处理订单
        $rest_count = intval($redis->get("rest_count"));
        $value = "{$rest_count}-{$uniqid}";
        if ($rest_count > 0) {
            // do something ...
            $rand = rand(100, 1000000);
            $sum = 0;
            for ($i = 0; $i < $rand; $i++) {$sum += $i;}

            $redis->lPush('uniqids', $value);
            $redis->decr('rest_count');
        }

      // 订单处理完成后,再释放锁
        flock($fp, LOCK_UN);
    }
    fclose($fp);

});

$http->start();

使用 ab 测试

$ ab -t 20 -c 10 http://192.168.1.104:9510/

三、利用文件排他锁 (非阻塞模式)
非阻塞模式下,如果进程在获取文件排他锁时,其它进程正在占用锁的话,此进程会马上判断获取锁失败,并且继续往下执行。\

示例代码:

<?php
$http = new swoole_http_server("0.0.0.0", 9511);

$http->set(array(
    'reactor_num' => 2,  //reactor thread num
    'worker_num' => 4    //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {

    $uniqid = uniqid('uid-', TRUE);
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);

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

    // 非阻塞模式, 如果不希望 flock() 在锁定时堵塞,则给 lock 加上 LOCK_NB
    if(flock($fp,LOCK_EX | LOCK_NB))   //锁定当前指针
    {
      // 成功取得锁后,放心处理订单
        $rest_count = intval($redis->get("rest_count"));
        $value = "{$rest_count}-{$uniqid}";
        if($rest_count > 0){
            // do something ...
            $rand  = rand(100, 1000000);
            $sum=0;
            for ($i=0;$i<$rand;$i++){ $sum+=$i; }

            $redis->lPush('uniqids', $value);
            $redis->decr('rest_count');
        }

      // 订单处理完成后,再释放锁
        flock($fp,LOCK_UN);
    } else {
      // 如果获取锁失败,马上进入这里执行
        echo "{$uniqid} - 系统繁忙,请稍后再试".PHP_EOL;
    }
    fclose($fp);

});

$http->start();

使用 ab 测试

$ ab -t 20 -c 10 http://192.168.1.104:9511/

最后给出三种处理方式的测试结果比较
redis 事务方式:


Concurrency Level: 10
Time taken for tests: 20.005 seconds
Complete requests: 17537
Failed requests: 0
Total transferred: 2578380 bytes
HTML transferred: 0 bytes
Requests per second: 876.62 [#/sec] (mean)
Time per request: 11.407 [ms] (mean)
Time per request: 1.141 [ms] (mean, across all concurrent requests)
Transfer rate: 125.86 [Kbytes/sec] received

文件排他锁(阻塞模式):


Concurrency Level: 10
Time taken for tests: 20.003 seconds
Complete requests: 8205
Failed requests: 0
Total transferred: 1206282 bytes
HTML transferred: 0 bytes
Requests per second: 410.19 [#/sec] (mean)
Time per request: 24.379 [ms] (mean)
Time per request: 2.438 [ms] (mean, across all concurrent requests)
Transfer rate: 58.89 [Kbytes/sec] received

文件排他锁(非阻塞模式):


Concurrency Level: 10
Time taken for tests: 20.002 seconds
Complete requests: 8616
Failed requests: 0
Total transferred: 1266846 bytes
HTML transferred: 0 bytes
Requests per second: 430.77 [#/sec] (mean)
Time per request: 23.214 [ms] (mean)
Time per request: 2.321 [ms] (mean, across all concurrent requests)
Transfer rate: 61.85 [Kbytes/sec] received

经测试结果对比,redis 事务方式优于文件排他锁方式,而文件排他锁方式中,非阻塞模式优于阻塞模式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PHP 并发场景的几种解决方案 的相关文章

  • Express 和 Redis 会话的过期时间

    我正在使用express和redis来使会话在我的系统上保持活动状态 我在设置 sessionCookie 上的 maxAge 时遇到一些问题 默认情况下 我读到的时间是 24 小时 但这对于保持其存活来说是很长的时间 我想设置大约 30
  • $redis 全局变量与 ruby​​ on Rails

    我使用 redis 作为读取缓存 我创建了一个初始化程序 配置 初始化程序 redis rb redis Redis new host gt ENV REDIS HOST port gt ENV REDIS PORT 每当创建新工作人员时
  • Node.js 公牛队列中的作业陷入“等待”状态

    我有一堆工作在公牛队列中 其中一个被卡住了 1 个多小时 通常需要大约 2 分钟才能运行 但没有失败 我无法使用我使用的 bull arena UI 将作业从活动状态中删除 因此我删除了 Redis 中活动作业的密钥 这消除了卡住的活动作业
  • 以原子方式从 Redis 数据结构中弹出多个值?

    是否有一个 Redis 数据结构 允许弹出 获取 删除 其中包含的多个元素的原子操作 有众所周知的 SPOP 或 RPOP 但它们总是返回单个值 因此 当我需要 set list 中的前 N 个值时 我需要调用该命令 N 次 这是昂贵的 假
  • RQ - 清空和删除队列

    我在用着RQ http python rq org 我有一个failed排队有数千件物品 还有另一个test我不久前创建的用于测试的队列现在是空的且未使用 我想知道如何删除所有作业failed排队 然后删除test完全排队吗 对于基本问题表
  • 如何隔离Spring Boot应用程序Redis和Spring Boot会话全局Redis

    据我所知 spring boot和spring session为我们提供了一站式自动配置 但是当我的应用程序使用会话redis和应用程序缓存redis时 不是同一个redis服务器 我该如何配置呢 非常感谢您的回复 事实上 默认情况下 sp
  • django:redis:CommandError: 您尚未设置运行服务器所需的 ASGI_APPLICATION

    我正在尝试在 django 中创建套接字 我按照这个安装了asgi redislink https realpython com getting started with django channels 当我运行命令 python mana
  • StackExchange.Redis 和 StackExchange.Redis.StrongName 之间有什么区别?

    当我关注Azure时文档 http azure microsoft com en us documentation articles cache dotnet how to use azure redis cache 关于如何在Azure
  • 找不到模块“socket.io/node_modules/redis”

    当尝试做的时候 var redis require socket io node modules redis 我收到错误 找不到模块 socket io node modules redis 我不明白为什么 我正在运行 Windows 并运
  • 使用 EVAL、SCAN 和 DEL 的 Redis 通配符删除脚本返回“非确定性命令后不允许写入命令”

    因此 我正在寻求构建一个 lua 脚本 该脚本使用 SCAN 根据模式查找键并删除它们 原子地 我首先准备了以下脚本 local keys local done false local cursor 0 repeat local resul
  • 使用brew在MacOSx上安装Redis JSON

    如何使用brew 在 macOSx 上安装 RedisJSON 如何在不编译redis的情况下启用redis上的模块 我不想使用 docker 客户端 Redis Stack 可能是最简单的方法 它不仅仅是 RedisJSON 还包括 Re
  • Laravel - 缓存 Eloquent 并频繁更新

    是否可以对经常修改的对象使用缓存 例如 假设我们有一个 BlogPost 对象 并且有一个经常更改的 num of views 列 以及其他列 是否可以更新缓存和数据库中的 num of views 字段 而不破坏缓存对象并重新创建它 我可
  • 保护节点 Redis

    我正在尝试保护 Node Redis IPC 服务器以使用私钥 公钥 我已经关注了本教程 http bencane com 2014 02 18 sending redis traffic through an ssl tunnel wit
  • 我的 Redis 自动生成的密钥

    我不知道我的 Redis 版本 4 0 9 到底发生了什么 我正在运行一个应用程序并使用 Redis 来存储我的数据库 但是 然后 Redis 自动创建 3 个新键 Backup1 Backup2 Backup3 并删除我的所有数据 这是我
  • PooledRedisClientManager 未释放连接

    我将 json 数据列表存储在 redis 中并使用 ServiceStack c 客户端访问它 我本质上是在管理自己的外键 我在其中存储zrangeid 我使用应用程序内部的接口从zrange然后从 Redis 获取底层 json 对象并
  • 如何统计 Redis 流中未读或已确认的消息?

    使用 Redis 5 0 3 假设我们创建一个名为streamy和一个消费群体consumers XGROUP CREATE streamy consumers MKSTREAM 然后向其中添加一些消息 XADD streamy messa
  • Redis INCRBY 有限制

    我想知道是否有一种方法可以通过我的应用程序的单次往返在 Redis 中执行此操作 对于给定的键K 其可能值V是范围内的任意整数 A B 基本上 它有上限和下限 When an INCRBY or DECRBY发出命令 例如INCRBY ke
  • 在 Kubernetes/Openshift 中将客户端-服务器流量保持在同一区域的最佳方法?

    我们运行兼容 Kubernetes OKD 3 11 的本地 私有云集群 其中后端应用程序与用作缓存和 K V 存储的低延迟 Redis 数据库进行通信 新的架构设计将在两个地理上分布的数据中心 区域 之间平均划分工作节点 我们可以假设节点
  • 如何设置和获取Redis中存储的对象?

    我试图在 redis 中存储一个对象 当我获取该对象时 它似乎不起作用 I tried u User new u name blankman redis set test u x redis get test x name error 我想
  • Redis发布/订阅:查看当前订阅了哪些频道

    我目前有兴趣查看我拥有的 Redis 发布 订阅应用程序中订阅了哪些频道 当客户端连接到我们的服务器时 我们将它们注册到如下所示的通道 user user id 这样做的原因是我希望能够看到谁 在线 目前 我在不知道客户端是否在线的情况下盲

随机推荐

  • mysql存储引擎层核心服务层_MySQL(逻辑分层,存储引擎,sql优化,索引优化以及底层实现(B+Tree))...

    一 逻辑分层 连接层 连接与线程处理 这一层并不是MySQL独有 一般的基于C S架构的都有类似组件 比如连接处理 授权认证 安全等 服务层 包括缓存查询 解析器 优化器 这一部分是MySQL核心功能 包括解析 优化SQL语句 查询缓存目录
  • 无痕渗透“INSERT INTO”型SQL注入

    原文链接 http www mathyvanhoef com 2011 10 exploiting insert into sql injections html 在某个寂静的深夜 你徘徊在一个网站中 其中包含一个可提交form 需要你输入
  • 通过C#学习redis(集合)

    static void Main string args RedisClient cli new RedisClient 127 0 0 1 6379 password defaultDatabase 0 region 集合操作 Redis
  • Latex 作者上角标,通讯作者的小信封标记

    一 作者上角标 论文中作者的上角标一般用于标记一作二作的单位 添加方式如下 author Lily textsuperscript 1 and Alexw textsuperscript 2 结果如图所示 二 通讯作者的小信封标识 用来表示
  • Java时间日期格式转换

    Java时间格式转换大全 import java text import java util Calendar public class VeDate 获取现在时间 return 返回时间类型 yyyy MM dd HH mm ss pub
  • Dockerfile——ENTRYPOINT详解

    文章目录 前言 一 ENTRYPOINT 命令格式介绍 二 示例 总结 前言 Entrypoint的作用是 把整个container变成了一个可执行的文件 这样不能够通过替换CMD的方法来改变创建container的方式 但是可以通过参数传
  • XYZZY 【POJ - 1932】【SPFA】

    题目链接 有N个点 然后输入1 N个点 输入从它到其他点的血量变化 然后有几个点能到达 最后是这几个点 我们起点为1 终点为N 然后求的是我们是不是有可能或者达到终点 gt 0 直接SPFA跑最长路 感觉是在造样例 6 0 1 2 1000
  • 文件通配符

    一 文件通配符 通配符主要用通过设定一定的条件来查找匹配到的字符 匹配任意个字符包括0个 匹配任意单个字符 username 匹配username的家目录 cp root file1 tom 把file1文件复制到tom用户的家目录中 匹配
  • C语言算法复杂度大O表示法

    算法 程序运行的次数 O 1 常数复杂度 printf hello world O log n 对数复杂度 for int i 1 i lt n i i 2 printf hello world n O n 线性时间复杂度 for int
  • linux消息分发机制,linux下使用hiredis异步API实现sub/pub消息订阅和发布的功能

    最近使用redis的c接口 hiredis 使客户端与redis服务器通信 实现消息订阅和发布 PUB SUB 的功能 我把遇到的一些问题和解决方法列出来供大家学习 废话不多说 先贴代码 redis publisher h gt File
  • ElasticSearch快速入门笔记,ElasticSearch基本操作以及爬虫(Java-ES仿京东实战)(狂神说)

    文章目录 ElasticSearch 库 表 记录 笔记 ElasticSearch概述 ELasticSearch VS Solr总结 ElasticSearch安装 ELK的下载地址 核心概念 IK分词器插件 RESTful风格说明 关
  • MySQL 日期时间加减

    now 当前具体的日期和时间 curdate 当前日期 curtime 当前时间 1 MySQL加减某个时间间隔 设置当前日期变量 set dt now 设置当前日期 select dt 查询变量值 加减某个时间间隔函数date add 与
  • 微信小程序源码-图书馆预约系统的计算机毕业设计(附源码+论文)

    大家好 我是职场程序猿 感谢您阅读本文 欢迎一键三连哦 当前专栏 微信小程序毕业设计 精彩专栏推荐 安卓app毕业设计 Java毕业设计 基于微信小程序的图书馆预约系统 java 演示 源码下载地址 https download csdn
  • 简单实现数据库DAO

    一 什么是DAO DAO Data Access Object 是一个数据访问接口 数据访问 顾名思义就是与数据库打交道 夹在业务逻辑与数据库资源中间 DAO是把对数据库的操作全部封装在里面 DAO把底层的数据访问逻辑和高层的业务逻辑分开
  • 原神服务端搭建架设教程win系统(附客户端+服务端+环境配置)

    原神服务端搭建架设教程win系统 附客户端 服务端 环境配置 大家好 我是艾西原神一款开放世界冒险3D游戏以七种元素 分别为风 雷 岩 火 水 草 冰 交汇的幻想世界 提瓦特 创造的游戏世界 以角色扮演的RPG游戏还是有非常多的玩家热爱 以
  • ERROR conf.Configuration: error parsing conf mapred-site.xml

    Hadoop进行namenode格式化时报错 ERROR conf Configuration error parsing conf mapred site xml com ctc wstx exc WstxParsingException
  • MTCNN

    Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks 使用多任务级联卷积网络的联合人脸检测和对齐 摘要 由于各种姿势 光照和遮挡
  • Java基础 类访问权限

    转载自https www cnblogs com jinggod p 8425423 html java基础 七 java四种访问权限 引言 Java中的访问权限理解起来不难 但完全掌握却不容易 特别是4种访问权限并不是任何时候都可以使用
  • Visual Studio 远程调试正在运行的进程

    使用场景 当项目在测试环境上有bug 需要运行代码调试一下 这时就需要在测试环境上安装一个调试工具 然后在本地运行代码 远程链接到测试环境服务器来调试代码 假期鸽了这末长的时间 方式一 工具下载 https visualstudio mic
  • PHP 并发场景的几种解决方案

    PHP 并发场景的几种解决方案 在秒杀 抢购等并发场景下 可能会出现超卖的现象 在 PHP 语言中并没有原生提供并发的解决方案 因此就需要借助其他方式来实现并发控制 列出常见的解决方案有 使用队列 额外起一个进程处理队列 并发请求都放到队列