PHP 处理商城抢购高并发一般涉及到的技术栈

1. 负载均衡(Load Balancing)

  • Nginx/HAProxy:通过负载均衡分发流量,防止单个服务器过载。
  • CDN:将静态资源(图片、CSS、JS 等)缓存到 CDN 节点,减轻源站服务器压力。

2. 缓存(Cache)

  • Redis/Memcached:利用缓存层加速频繁访问的数据,如商品信息、库存数量等。通过预热缓存,避免大量请求直接访问数据库。
  • 页面静态化:对抢购页面进行 HTML 静态化,减少 PHP 后端的请求压力。可以通过定时更新 HTML 缓存的方式保持内容实时性。

3. 数据库优化(Database Optimization)

  • 数据库读写分离:通过主从复制,将读操作分担到从库,减轻主库压力。
  • 分表分库:根据用户 ID、订单 ID 等对表进行分片,避免单张表数据过多导致查询效率下降。
  • 索引优化:为高频查询字段添加索引,提高查询速度。
  • 行锁和事务:抢购时需要锁定库存和订单相关表,通过行锁避免超卖,确保数据一致性。

4. 消息队列(Message Queue)

  • Kafka/RabbitMQ/RocketMQ:通过消息队列异步处理耗时的任务,例如订单生成、库存更新等。消费者处理队列中的订单,确保操作顺序执行。
  • 削峰填谷:在抢购时,消息队列可帮助缓冲瞬间涌入的大量请求,从而平稳地处理订单。

5. 限流与排队(Rate Limiting & Queuing)

  • 令牌桶算法或漏桶算法:使用限流算法,防止流量暴增导致系统崩溃。Nginx 或 PHP 可以结合 Redis 实现限流。
  • Redis 排队:在 Redis 中通过 LPUSHRPOP 实现排队,将请求按顺序处理,防止并发操作导致超卖。
  • 限流中间件:例如 Guava 的 RateLimiter,或使用 Redis 实现简易的限流策略。

6. 分布式锁(Distributed Lock)

  • Redis 分布式锁:使用 SETNX 和 EXPIRE 实现锁机制,控制多个请求对同一资源的并发访问。
  • ZooKeeper 分布式锁:ZooKeeper 适用于需要严格锁机制的场景,可以确保锁的高可靠性。

7. 库存扣减策略

  • Redis 扣库存:抢购场景下可以先将库存同步到 Redis,抢购请求直接操作 Redis 的库存值,通过 INCRBY 和 DECRBY 操作实现库存的原子性扣减。
  • 数据库最终一致性:将 Redis 扣减后的数据异步写回数据库,保证数据的最终一致性。

8. 应用层优化

  • 异步化和队列化:尽可能将非核心业务流程(如发送短信、生成报表)异步处理。
  • PHP-FPM 配置优化:调整 PHP-FPM 的 max_childrenmax_requests 等参数,提高 PHP 的并发处理能力。

9. 性能监控与告警

  • APM(应用性能监控):使用工具如 New Relic、SkyWalking、Prometheus 等,实时监控 PHP 应用的性能,快速发现和定位瓶颈。
  • 日志监控:通过 ELK(Elasticsearch、Logstash、Kibana)对日志进行集中存储和分析,便于跟踪高并发情况下的错误和异常。

示例流程

一个典型的抢购流程可能会涉及以下步骤:

  1. 用户请求进入负载均衡,通过 Redis 限流。
  2. 通过 Redis 分布式锁或排队机制控制抢购顺序,避免超卖。
  3. Redis 扣减库存,并将扣减信息异步写入消息队列。
  4. 消息队列消费订单消息,生成订单并更新数据库库存。
  5. 在缓存和数据库同步成功后,订单完成并通知用户。
<?php

// 引入 Kafka 生产者库和 Redis 扩展
use RdKafka\Producer;
use RdKafka\Conf;

class FlashSaleHandler
{
    private $redis;
    private $kafkaProducer;
    private $topic;

    public function __construct()
    {
        // 初始化 Redis 连接
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);

        // 初始化 Kafka 生产者
        $conf = new Conf();
        $conf->set('metadata.broker.list', 'localhost:9092');
        $this->kafkaProducer = new Producer($conf);
        $this->topic = $this->kafkaProducer->newTopic("order_topic");
    }

    /**
     * 处理抢购请求
     */
    public function handleRequest($userId, $productId)
    {
        $lockKey = "flash_sale_lock:" . $productId;

        // 使用 Redis 分布式锁,过期时间设置为 1 秒,避免死锁
        if ($this->redis->set($lockKey, 1, ['nx', 'ex' => 1])) {
            try {
                // 检查库存
                $stockKey = "product_stock:" . $productId;
                $stock = $this->redis->get($stockKey);

                if ($stock > 0) {
                    // 扣减库存
                    $this->redis->decr($stockKey);

                    // 推送订单生成消息到 Kafka
                    $orderData = [
                        'user_id' => $userId,
                        'product_id' => $productId,
                        'timestamp' => time()
                    ];
                    $this->topic->produce(RD_KAFKA_PARTITION_UA, 0, json_encode($orderData));
                    $this->kafkaProducer->poll(0);

                    echo "抢购成功,订单正在生成中...";
                } else {
                    echo "库存不足,抢购失败。";
                }
            } finally {
                // 释放锁
                $this->redis->del($lockKey);
            }
        } else {
            echo "系统繁忙,请稍后重试。";
        }

        // 确保队列中的消息被发送
        $this->kafkaProducer->flush(1000);
    }
}

// 测试抢购请求
$flashSaleHandler = new FlashSaleHandler();
$userId = $_GET['user_id'] ?? 1;
$productId = $_GET['product_id'] ?? 101;

$flashSaleHandler->handleRequest($userId, $productId);