CentOS上解决 ThinkPHP 并发问题的落地方案
一 架构与基础设施优化
- 开启 OPcache 并合理分配内存,例如:opcache.enable=1、opcache.memory_consumption=256、realpath_cache_size=4096K,减少 PHP 编译与文件查找开销。
- 调整 Nginx:worker_processes auto;worker_connections 10240;keepalive_timeout 30;开启 gzip,提升静态资源与接口吞吐。
- 关闭调试:将 APP_DEBUG = false;执行路由缓存:php think optimize:route,降低框架路由开销。
- 会话与缓存统一到 Redis:配置 ‘session’ => [‘type’=>‘redis’, …];Cache 默认使用 Redis,便于多实例共享与横向扩展。
- 数据库瓶颈突破:开启 读写分离(如 ‘rw_separate’ => true)、合理设置主从;对热点数据使用 缓存预热(定时任务提前加载到 Redis)。
- 水平扩展:前置 Nginx 负载均衡,后端多台应用实例共享 Redis 与 数据库集群,避免单点。
以上措施可稳定提升基础吞吐,从百级到千级 QPS 的跃迁通常首先依赖这些“基础设施层”的优化。
二 数据一致性与并发控制
- 原子计数与库存扣减:优先使用数据库的原子操作,如 ThinkPHP 的 setInc/setDec,避免“先读后写”的竞态条件。示例:扣减库存仅在库存充足时生效(where(‘stock’,‘>’,0))。
- 悲观锁:对冲突概率高且要求强一致的关键行,使用 lockForUpdate() 行级排他锁,事务内完成检查与更新,防止并发脏写。
- 乐观锁:为表增加 version 字段,更新时 where 带上 version 并递增;依据受影响行数判断冲突并重试或提示。适合读多写少、冲突中等的场景。
- 唯一约束兜底:对 手机号、邮箱、订单号 等设置数据库唯一索引,应用捕获 23000 唯一冲突异常并返回友好提示,防止业务层重复提交。
- 防重复提交令牌:对“注册/下单”等接口,使用 Redis 集合/令牌桶 记录已处理的手机号或请求令牌,快速拒绝重复请求,再进入业务处理流程。
以上策略覆盖“计数/库存”“强一致写”“冲突重试”“唯一性兜底”“重复提交”的典型并发问题,可按场景单独或组合使用。
三 异步化与削峰填谷
- 将非实时逻辑(如发短信/邮件、积分入账、写动态、邀请奖励)放入 消息队列,由 Worker 异步消费,显著降低接口 RT 与数据库瞬时压力。
- 在 ThinkPHP 中集成 think-queue(支持 Redis 等驱动),控制器入队,任务处理器消费;结合 失败重试、幂等处理 保证可靠性。
- 秒杀/抢购等高并发入口:用 Redis 分布式锁 控制对共享资源(如活动资格)的瞬时争用,成功者入队下单,失败者快速返回,避免雪崩。
异步化能把突发流量转化为可平稳处理的“后台作业”,是支撑从千级到更高并发的关键手段。
四 落地配置与代码示例
- 环境配置要点(示例)
- php.ini:opcache.enable=1;opcache.memory_consumption=256;realpath_cache_size=4096K
- .env:APP_DEBUG=false
- Nginx:worker_processes auto;worker_connections 10240;keepalive_timeout 30;gzip on
- Redis:作为 Cache 与 Session 的后端存储
- 数据库:开启 读写分离 与必要索引;热点数据 缓存预热
- 原子库存扣减(TP6)
- 代码示例:
- $affected = ProductModel::where(‘id’, $pid)->where(‘stock’, ‘>’, 0)->setDec(‘stock’, $num);
- if ($affected) { /* 扣减成功,继续后续写库与入队 / } else { / 库存不足 */ }
- 秒杀入口与队列(TP6 + Redis 锁 + think-queue)
- 控制器:
- $lockKey = ‘seckill_lock_’ . $productId;
- $redis = \think\facade\Cache::store(‘redis’)->handler();
- if (!$redis->setnx($lockKey, 1)) { return json([‘status’=>‘failed’,‘msg’=>‘系统繁忙’]); }
- try {
- $jobId = Queue::push(‘app\job\SeckillJob’, [‘user_id’=>$uid,‘product_id’=>$pid]);
- return json([‘status’=>‘success’,‘job_id’=>$jobId]);
} finally { $redis->del($lockKey); }
- 任务:消费时校验库存→创建订单→失败回滚→写入结果
- 防重复提交(注册场景)
- 令牌/集合校验:以手机号为 key,使用 Redis 集合或一次性令牌,判定存在则直接拒绝,否则进入注册流程并在失败或超时后清理。
以上配置与示例覆盖了“基础设施—数据一致性—异步化”的完整闭环,可直接用于生产环境的落地与压测调优。