按 Enter 键跳转到正文

PHP 实现 Redis 高并发解决方案

PHP 实现 Redis 高并发解决方案

使用 PHP 实现 Redis 在缓存加速、分布式锁和队列场景中的应用。

首先确保已安装 PHP Redis 扩展

一、缓存加速实现

1. 基本缓存操作

 1<?php
 2$redis = new Redis();
 3$redis->connect('127.0.0.1', 6379);
 4
 5// 设置缓存
 6function setCache($key, $value, $expire = 3600) {
 7    global $redis;
 8    $serialized = serialize($value);
 9    return $redis->setex($key, $expire, $serialized);
10}
11
12// 获取缓存
13function getCache($key) {
14    global $redis;
15    $serialized = $redis->get($key);
16    return $serialized ? unserialize($serialized) : false;
17}
18
19// 删除缓存
20function deleteCache($key) {
21    global $redis;
22    return $redis->del($key);
23}
24
25// 示例:用户数据缓存
26function getUser($userId) {
27    $cacheKey = "user:{$userId}";
28    $user = getCache($cacheKey);
29    
30    if ($user === false) {
31        // 模拟数据库查询
32        $user = [
33            'id' => $userId,
34            'name' => 'User ' . $userId,
35            'email' => "user{$userId}@example.com"
36        ];
37        // 写入缓存,有效期1小时
38        setCache($cacheKey, $user, 3600);
39    }
40    
41    return $user;
42}
43
44// 使用示例
45$user = getUser(123);
46print_r($user);

2. 防止缓存穿透

 1function getProduct($productId) {
 2    global $redis;
 3    $cacheKey = "product:{$productId}";
 4    $product = getCache($cacheKey);
 5    
 6    if ($product === false) {
 7        // 使用互斥锁防止缓存击穿
 8        $lockKey = "lock:product:{$productId}";
 9        $locked = $redis->set($lockKey, 1, ['nx', 'ex' => 10]);
10        
11        if ($locked) {
12            try {
13                // 模拟数据库查询
14                $product = [
15                    'id' => $productId,
16                    'name' => 'Product ' . $productId,
17                    'price' => rand(100, 1000)
18                ];
19                
20                if (empty($product)) {
21                    // 缓存空对象防止穿透,有效期5分钟
22                    setCache($cacheKey, [], 300);
23                } else {
24                    setCache($cacheKey, $product, 3600);
25                }
26            } finally {
27                $redis->del($lockKey);
28            }
29        } else {
30            // 等待其他进程完成缓存设置
31            usleep(500000); // 等待500ms
32            return getProduct($productId); // 重试
33        }
34    }
35    
36    return $product ?: null;
37}

二、 分布式锁实现

1. 基本分布式锁

 1class RedisLock {
 2    private $redis;
 3    private $lockKey;
 4    private $identifier;
 5    private $lockTimeout;
 6    
 7    public function __construct($redis, $lockKey, $lockTimeout = 10) {
 8        $this->redis = $redis;
 9        $this->lockKey = $lockKey;
10        $this->lockTimeout = $lockTimeout;
11        $this->identifier = uniqid();
12    }
13    
14    public function acquire($waitTimeout = 5) {
15        $end = microtime(true) + $waitTimeout;
16        
17        while (microtime(true) < $end) {
18            if ($this->redis->set(
19                $this->lockKey, 
20                $this->identifier, 
21                ['nx', 'ex' => $this->lockTimeout]
22            )) {
23                return true;
24            }
25            
26            usleep(10000); // 等待10ms
27        }
28        
29        return false;
30    }
31    
32    public function release() {
33        $script = '
34            if redis.call("get", KEYS[1]) == ARGV[1] then
35                return redis.call("del", KEYS[1])
36            else
37                return 0
38            end
39        ';
40        
41        return $this->redis->eval($script, [$this->lockKey, $this->identifier], 1);
42    }
43    
44    public function __destruct() {
45        $this->release();
46    }
47}
48
49// 使用示例
50$lock = new RedisLock($redis, 'lock:order:123');
51if ($lock->acquire()) {
52    try {
53        // 执行需要加锁的操作
54        echo "Lock acquired, doing critical section...\n";
55        sleep(2);
56    } finally {
57        $lock->release();
58    }
59} else {
60    echo "Failed to acquire lock\n";
61}

2. 可重入锁实现

 1class ReentrantRedisLock extends RedisLock {
 2    private $heldCount = 0;
 3    
 4    public function acquire($waitTimeout = 5) {
 5        // 检查是否已经持有锁
 6        if ($this->heldCount > 0) {
 7            $this->heldCount++;
 8            return true;
 9        }
10        
11        $acquired = parent::acquire($waitTimeout);
12        if ($acquired) {
13            $this->heldCount = 1;
14        }
15        return $acquired;
16    }
17    
18    public function release() {
19        if ($this->heldCount > 1) {
20            $this->heldCount--;
21            return true;
22        }
23        
24        $released = parent::release();
25        if ($released) {
26            $this->heldCount = 0;
27        }
28        return $released;
29    }
30}

三、队列实现

1. 简单队列

 1// 生产者
 2function enqueue($queueName, $data) {
 3    global $redis;
 4    return $redis->lPush($queueName, json_encode($data));
 5}
 6
 7// 消费者
 8function dequeue($queueName, $timeout = 30) {
 9    global $redis;
10    $result = $redis->brPop($queueName, $timeout);
11    return $result ? json_decode($result[1], true) : null;
12}
13
14// 示例
15enqueue('email_queue', [
16    'to' => 'user@example.com',
17    'subject' => 'Welcome',
18    'body' => 'Thank you for registering'
19]);
20
21$task = dequeue('email_queue');
22if ($task) {
23    // 处理任务
24    echo "Sending email to: {$task['to']}\n";
25}

2. 延迟队列

 1function enqueueDelayed($queueName, $data, $delaySeconds) {
 2    global $redis;
 3    $score = time() + $delaySeconds;
 4    return $redis->zAdd($queueName, $score, json_encode($data));
 5}
 6
 7function processDelayedQueue($queueName) {
 8    global $redis;
 9    $now = time();
10    $items = $redis->zRangeByScore($queueName, 0, $now);
11    
12    if (!empty($items)) {
13        // 使用事务移除已处理项
14        $redis->multi();
15        foreach ($items as $item) {
16            $redis->zRem($queueName, $item);
17        }
18        $redis->exec();
19        
20        return array_map(function($item) {
21            return json_decode($item, true);
22        }, $items);
23    }
24    
25    return [];
26}
27
28// 示例
29enqueueDelayed('reminder_queue', ['user_id' => 123, 'message' => 'Pay your bill'], 60);
30
31$tasks = processDelayedQueue('reminder_queue');
32foreach ($tasks as $task) {
33    echo "Sending reminder to user {$task['user_id']}: {$task['message']}\n";
34}

3. Pub/Sub 模式

 1// 发布者
 2function publishMessage($channel, $message) {
 3    global $redis;
 4    return $redis->publish($channel, json_encode($message));
 5}
 6
 7// 订阅者
 8function subscribe($channels, $callback) {
 9    global $redis;
10    $pubsub = $redis->pSubscribe($channels);
11    
12    try {
13        foreach ($pubsub as $message) {
14            if ($message->kind === 'message') {
15                $data = json_decode($message->payload, true);
16                call_user_func($callback, $message->channel, $data);
17            }
18        }
19    } catch (Exception $e) {
20        $redis->pUnsubscribe();
21        throw $e;
22    }
23}
24
25// 示例使用
26// 在一个进程中
27publishMessage('notifications', ['user_id' => 123, 'text' => 'New message']);
28
29// 在另一个进程中
30subscribe(['notifications'], function($channel, $message) {
31    echo "Received on {$channel}: ";
32    print_r($message);
33});

四、综合应用示例 - 秒杀系统

 1class SeckillService {
 2    private $redis;
 3    private $productKey;
 4    
 5    public function __construct($redis, $productId) {
 6        $this->redis = $redis;
 7        $this->productKey = "seckill:product:{$productId}";
 8    }
 9    
10    public function initInventory($inventory) {
11        // 设置商品库存
12        $this->redis->set($this->productKey, $inventory);
13    }
14    
15    public function seckill($userId) {
16        // 使用分布式锁
17        $lock = new RedisLock($this->redis, "lock:{$this->productKey}");
18        
19        if (!$lock->acquire(1)) {
20            return ['success' => false, 'message' => '系统繁忙'];
21        }
22        
23        try {
24            // 检查库存
25            $inventory = $this->redis->get($this->productKey);
26            if ($inventory <= 0) {
27                return ['success' => false, 'message' => '已售罄'];
28            }
29            
30            // 扣减库存
31            $this->redis->decr($this->productKey);
32            
33            // 记录订单 (实际应用中应该入队列异步处理)
34            $orderId = uniqid();
35            $this->redis->hSet("seckill:orders", $orderId, json_encode([
36                'user_id' => $userId,
37                'product_key' => $this->productKey,
38                'created_at' => time()
39            ]));
40            
41            return ['success' => true, 'order_id' => $orderId];
42        } finally {
43            $lock->release();
44        }
45    }
46}
47
48// 使用示例
49$redis = new Redis();
50$redis->connect('127.0.0.1', 6379);
51
52$seckill = new SeckillService($redis, 1001);
53$seckill->initInventory(100); // 初始化100件库存
54
55// 模拟并发请求
56for ($i = 0; $i < 5; $i++) {
57    $result = $seckill->seckill(rand(1000, 9999));
58    print_r($result);
59}

五、最佳实践建议

1. 连接管理:使用连接池或持久连接

1$redis = new Redis();
2$redis->pconnect('127.0.0.1', 6379);

2. 错误处理:添加重试机制

 1function redisRetry($callback, $maxRetries = 3) {
 2    $retries = 0;
 3    while ($retries < $maxRetries) {
 4        try {
 5            return $callback();
 6        } catch (RedisException $e) {
 7            $retries++;
 8            if ($retries >= $maxRetries) {
 9                throw $e;
10            }
11            usleep(100000 * $retries); // 指数退避
12        }
13    }
14}

3. 性能优化:使用管道(pipeline)

1$pipe = $redis->pipeline();
2$pipe->set('key1', 'value1');
3$pipe->set('key2', 'value2');
4$pipe->incr('counter');
5$pipe->expire('key1', 60);
6$results = $pipe->exec();

4. Lua脚本:保证原子性

1$script = '
2    local current = redis.call("GET", KEYS[1])
3    if current == ARGV[1] then
4        return redis.call("INCRBY", KEYS[1], ARGV[2])
5    else
6        return nil
7    end
8';
9$result = $redis->eval($script, ['counter', 'expected_value', 'increment'], 1);

发表评论