Redis使用指南完整教程:从入门到生产级部署
摘要
本教程全面介绍Redis的使用方法,涵盖基础概念、数据结构详解、三服务器生产环境配置、常用命令实战、PHP/Laravel集成、性能优化、监控告警、故障处理等。基于真实生产环境经验,提供可直接复制的配置和代码。
第一部分:Redis基础认知
1.1 什么是Redis?
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息中间件。它由Salvatore Sanfilippo于2009年创建,现在是世界上最流行的键值存储系统之一。
核心特性:
1. 高性能:基于内存操作,读写速度可达10万次/秒以上
2. 丰富数据结构:支持String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、Geospatial等
3. 原子性操作:所有操作都是原子性的,要么成功要么失败
4. 持久化支持:支持RDB快照和AOF日志两种持久化方式
5. 主从复制:支持数据复制,实现读写分离和高可用
6. 集群支持:Redis Cluster提供自动分片和高可用
典型应用场景:
– 缓存层:减轻数据库压力,提升响应速度
– 会话存储:分布式会话管理
– 排行榜:利用Sorted Set实现实时排名
– 计数器:文章阅读数、点赞数等
– 消息队列:利用List结构实现简单队列
– 分布式锁:实现跨服务器的同步控制
1.2 Redis vs Memcached
选择建议:
– 简单缓存场景:Memcached
– 需要复杂数据结构、持久化:Redis
– 大多数现代应用:Redis
1.3 Redis性能基准
硬件环境:Intel Xeon E5-2680 v4 @ 2.4GHz,64GB RAM
测试结果:
– GET操作:~110,000 requests/second
– SET操作:~85,000 requests/second
– INCR操作:~95,000 requests/second
– LPUSH操作:~90,000 requests/second
生产环境建议:
– 预期QPS:50,000-80,000(单实例)
– 通过主从复制可扩展至100,000+ QPS
– 使用Redis Cluster可线性扩展
第二部分:数据结构详解
2.1 String(字符串)
特点:
– 最基本的数据类型
– 最大值512MB
– 可以是字符串、整数、浮点数
常用命令:
# 基本操作
SET user:1:name "张三"
GET user:1:name
SETEX session:abc123 3600 "user_data" # 设置3600秒过期
计数器
INCR article:123:views # 自增1
INCRBY article:123:likes 5 # 自增5
DECR article:123:dislikes # 自减1
批量操作
MGET user:1:name user:1:age user:1:email
MSET user:1:name "张三" user:1:age "25" user:1:email "test@your-domain.com"
不存在才设置(分布式锁基础)
SET lock:resource "uuid" NX EX 10
实战案例:
// 文章阅读数计数器
function incrementArticleViews($articleId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "article:{$articleId}:views";
$views = $redis->incr($key);
// 每增长100次,同步到数据库
if ($views % 100 == 0) {
syncViewsToDatabase($articleId, $views);
}
return $views;
}
// 分布式锁实现
function acquireLock($resource, $timeout = 10) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$lockKey = "lock:{$resource}";
$lockValue = uniqid('', true);
// NX: 不存在才设置,EX: 设置过期时间
$acquired = $redis->set($lockKey, $lockValue, ['NX', 'EX' => $timeout]);
if ($acquired) {
return $lockValue; // 返回锁的唯一标识
}
return false;
}
function releaseLock($resource, $lockValue) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$lockKey = "lock:{$resource}";
// Lua脚本确保只释放自己持有的锁
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
";
return $redis->eval($script, [$lockKey, $lockValue], 1);
}
2.2 Hash(哈希)
特点:
– 适合存储对象
– 一个Hash最多可存储2^32-1个键值对
– 内存优化:小于512字节的值使用ziplist编码
常用命令:
# 基本操作
HSET user:1 name "张三" age "25" email "test@your-domain.com"
HGET user:1 name
HGETALL user:1 # 获取所有字段
HDEL user:1 email # 删除字段
批量操作
HMSET user:2 name "李四" age "30" city "北京"
HMGET user:2 name age
字段操作
HEXISTS user:1 name # 检查字段是否存在
HKEYS user:1 # 获取所有字段名
HVALS user:1 # 获取所有值
HLEN user:1 # 获取字段数量
计数器
HINCRBY user:1 login_count 1
实战案例:
// 用户信息缓存
function cacheUserInfo($userId, $userInfo) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "user:{$userId}";
$redis->hmset($key, $userInfo);
// 设置1小时过期
$redis->expire($key, 3600);
}
function getUserInfo($userId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "user:{$userId}";
$userInfo = $redis->hgetall($key);
if (empty($userInfo)) {
// 缓存未命中,从数据库加载
$userInfo = loadUserFromDatabase($userId);
if ($userInfo) {
cacheUserInfo($userId, $userInfo);
}
}
return $userInfo;
}
// 购物车实现
function addToCart($userId, $productId, $quantity) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "cart:{$userId}";
// 增加商品数量
$redis->hincrby($key, $productId, $quantity);
// 设置24小时过期
$redis->expire($key, 86400);
}
function getCart($userId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "cart:{$userId}";
return $redis->hgetall($key);
}
2.3 List(列表)
特点:
– 双向链表实现
– 支持头尾插入和弹出
– 最大长度2^32-1
常用命令:
# 栈操作(LPUSH + LPOP)
LPUSH stack "item1"
LPUSH stack "item2"
LPOP stack # 返回"item2"
队列操作(LPUSH + RPOP)
LPUSH queue "task1"
LPUSH queue "task2"
RPOP queue # 返回"task1"
阻塞队列
BLPOP queue 0 # 永久阻塞,直到有数据
BRPOP queue 10 # 阻塞10秒
范围查询
LRANGE mylist 0 -1 # 获取所有元素
LRANGE mylist 0 9 # 获取前10个元素
LTRIM mylist 0 9 # 保留前10个,删除其余
列表操作
LLEN mylist # 获取列表长度
LINDEX mylist 0 # 获取索引为0的元素
LINSERT mylist BEFORE "item2" "item1.5"
实战案例:
// 消息队列实现
class MessageQueue {
private $redis;
private $queueName;
public function __construct($queueName) {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->redis->auth('password');
$this->queueName = "queue:{$queueName}";
}
// 生产者
public function push($message) {
return $this->redis->rpush($this->queueName, json_encode($message));
}
// 消费者
public function pop($timeout = 0) {
$data = $this->redis->blpop($this->queueName, $timeout);
if ($data) {
return json_decode($data[1], true);
}
return null;
}
// 批量生产
public function pushBatch($messages) {
$pipeline = $this->redis->pipeline();
foreach ($messages as $message) {
$pipeline->rpush($this->queueName, json_encode($message));
}
return $pipeline->exec();
}
// 获取队列长度
public function size() {
return $this->redis->llen($this->queueName);
}
}
// 最新文章列表(时间线)
function addArticleToTimeline($articleId, $timestamp) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "timeline:articles";
// 添加到列表头部
$redis->lpush($key, $articleId);
// 只保留最新1000篇
$redis->ltrim($key, 0, 999);
}
function getLatestArticles($limit = 20) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "timeline:articles";
$articleIds = $redis->lrange($key, 0, $limit - 1);
return loadArticlesByIds($articleIds);
}
2.4 Set(集合)
特点:
– 无序集合
– 元素唯一
– 支持集合运算(交集、并集、差集)
常用命令:
# 基本操作
SADD myset "member1" "member2" "member3"
SMEMBERS myset # 获取所有成员
SISMEMBER myset "member1" # 检查成员是否存在
SREM myset "member2" # 删除成员
集合运算
SADD seta 1 2 3
SADD setb 2 3 4
SINTER seta setb # 交集:2 3
SUNION seta setb # 并集:1 2 3 4
SDIFF seta setb # 差集:1
随机操作
SRANDMEMBER myset # 随机返回一个成员(不删除)
SPOP myset # 随机弹出成员(删除)
集合操作
SCARD myset # 获取成员数量
SMOVE set1 set2 "member" # 移动成员
实战案例:
// 标签系统
function addTagsToArticle($articleId, $tags) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "article:{$articleId}:tags";
$redis->sadd($key, ...$tags);
$redis->expire($key, 86400 * 30); // 30天过期
}
function getArticlesByTags($tags, $limit = 20) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
// 构建查询键
$keys = array_map(function($tag) {
return "tag:{$tag}:articles";
}, $tags);
// 计算交集(包含所有标签的文章)
$articleIds = $redis->sinter(...$keys);
return array_slice($articleIds, 0, $limit);
}
// 共同关注
function getCommonFollowers($userId1, $userId2) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key1 = "user:{$userId1}:following";
$key2 = "user:{$userId2}:following";
return $redis->sinter($key1, $key2);
}
// 抽奖系统
function lottery($users, $winnersCount) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "lottery:" . time();
// 添加所有用户到集合
$redis->sadd($key, ...$users);
// 随机抽取中奖者
$winners = [];
for ($i = 0; $i < $winnersCount; $i++) {
$winner = $redis->spop($key);
if ($winner) {
$winners[] = $winner;
}
}
return $winners;
}
2.5 Sorted Set(有序集合)
特点:
– 每个成员关联一个分数(score)
– 按分数排序
– 成员唯一,分数可重复
常用命令:
# 基本操作
ZADD myzset 100 "member1" 200 "member2" 150 "member3"
ZRANGE myzset 0 -1 # 获取所有成员(按分数升序)
ZREVRANGE myzset 0 -1 # 获取所有成员(按分数降序)
ZSCORE myzset "member1" # 获取成员分数
ZRANK myzset "member1" # 获取成员排名(升序)
ZREVRANK myzset "member1" # 获取成员排名(降序)
范围查询
ZRANGEBYSCORE myzset 0 150 # 获取分数0-150的成员
ZCOUNT myzset 100 200 # 统计分数范围内的成员数
删除操作
ZREM myzset "member1" # 删除成员
ZREMRANGEBYRANK myzset 0 2 # 删除排名0-2的成员
ZREMRANGEBYSCORE myzset 0 100 # 删除分数0-100的成员
集合运算
ZUNIONSTORE result 2 zset1 zset2 # 并集
ZINTERSTORE result 2 zset1 zset2 # 交集
实战案例:
// 排行榜系统
class Leaderboard {
private $redis;
private $key;
public function __construct($leaderboardName) {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->redis->auth('password');
$this->key = "leaderboard:{$leaderboardName}";
}
// 添加或更新分数
public function addScore($member, $score) {
return $this->redis->zadd($this->key, $score, $member);
}
// 增加分数
public function incrementScore($member, $delta) {
return $this->redis->zincrby($this->key, $delta, $member);
}
// 获取排行榜(Top N)
public function getTop($limit = 10) {
return $this->redis->zrevrange($this->key, 0, $limit - 1, true);
}
// 获取用户排名和分数
public function getRank($member) {
$rank = $this->redis->zrevrank($this->key, $member);
$score = $this->redis->zscore($this->key, $member);
if ($rank === false) {
return null;
}
return [
'rank' => $rank + 1, // 排名从1开始
'score' => $score
];
}
// 获取分数范围内的用户
public function getByScoreRange($min, $max, $limit = 10) {
return $this->redis->zrevrangebyscore($this->key, $max, $min, ['withscores' => true, 'limit' => [0, $limit]]);
}
}
// 使用示例
$leaderboard = new Leaderboard('game_scores');
// 更新玩家分数
$leaderboard->addScore('player1', 1000);
$leaderboard->addScore('player2', 1500);
$leaderboard->addScore('player3', 1200);
// 获取Top 10
$top10 = $leaderboard->getTop(10);
// 获取玩家排名
$playerRank = $leaderboard->getRank('player1');
// 返回:['rank' => 3, 'score' => 1000.0]
// 延时队列
function addDelayedJob($jobData, $delaySeconds) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "delayed_queue";
$score = time() + $delaySeconds;
$value = json_encode($jobData);
return $redis->zadd($key, $score, $value);
}
function getReadyJobs() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password');
$key = "delayed_queue";
$now = time();
// 获取到期的任务
$jobs = $redis->zrangebyscore($key, 0, $now);
if (!empty($jobs)) {
// 删除已获取的任务
$redis->zremrangebyscore($key, 0, $now);
}
return array_map('json_decode', $jobs);
}
第三部分:三服务器生产环境配置
3.1 Redis分配策略
在我们的三服务器架构中,Redis分配策略如下:
3.2 Redis安装配置
编译安装Redis 7.2:
# 1. 下载源码
cd /tmp
wget https://github.com/redis/redis/archive/refs/tags/7.2.3.tar.gz
tar -xzf 7.2.3.tar.gz
cd redis-7.2.3
2. 编译安装
make -j$(nproc)
make test
sudo make PREFIX=/server/redis install
3. 创建目录结构
sudo mkdir -p /server/redis/{bin,etc,data,logs}
sudo mkdir -p /server/redis/data
4. 复制配置文件
sudo cp redis.conf /server/redis/etc/
sudo cp sentinel.conf /server/redis/etc/
5. 创建systemd服务
sudo cat > /etc/systemd/system/redis.service << 'EOF'
[Unit]
Description=Redis In-Memory Data Store
After=network.target
[Service]
Type=forking
ExecStart=/server/redis/bin/redis-server /server/redis/etc/redis.conf
ExecStop=/server/redis/bin/redis-cli shutdown
Restart=always
User=redis
Group=redis
[Install]
WantedBy=multi-user.target
EOF
6. 创建redis用户
sudo useradd -r -s /bin/false redis
sudo chown -R redis:redis /server/redis
7. 启动Redis
sudo systemctl daemon-reload
sudo systemctl enable redis
sudo systemctl start redis
8. 验证安装
/server/redis/bin/redis-cli ping
应该返回:PONG
3.3 Redis配置文件优化
生产环境配置(/server/redis/etc/redis.conf):
# 网络配置
bind 0.0.0.0
port 6379
protected-mode yes
tcp-backlog 511
timeout 0
tcp-keepalive 300
通用配置
daemonize yes
supervised systemd
pidfile /var/run/redis_6379.pid
loglevel notice
logfile "/server/redis/logs/redis.log"
databases 16
快照配置(RDB)
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /server/redis/data
复制配置
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
内存管理
maxmemory 2gb
maxmemory-policy allkeys-lru
maxmemory-samples 5
AOF配置(建议开启)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
慢查询配置
slowlog-log-slower-than 10000
slowlog-max-len 128
延迟监控
latency-monitor-threshold 100
高级配置
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
3.4 Redis Sentinel高可用配置
Sentinel配置(/server/redis/etc/sentinel.conf):
# 端口
port 26379
daemonize yes
pidfile /var/run/sentinel.pid
logfile "/server/redis/logs/sentinel.log"
dir /server/redis/data
监控配置
sentinel monitor mymaster 106.13.74.202 6379 2
sentinel auth-pass mymaster your_redis_password
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 30000
故障通知脚本
sentinel notification-script mymaster /var/redis/notify.sh
重命名危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command SHUTDOWN SHUTDOWN_REDIS
启动Sentinel:
# 启动Sentinel
/server/redis/bin/redis-sentinel /server/redis/etc/sentinel.conf
或使用systemd
sudo systemctl start redis-sentinel
sudo systemctl enable redis-sentinel
3.5 HAProxy本地代理配置
安装HAProxy:
# CentOS/RHEL
sudo yum install haproxy -y
Ubuntu/Debian
sudo apt-get install haproxy -y
配置HAProxy(/etc/haproxy/haproxy.cfg):
frontend redis_local
bind *:6380
default_backend redis_cluster
backend redis_cluster
mode tcp
option tcp-check
tcp-check send PINGrn
tcp-check expect string +PONG
balance roundrobin
# 主库1
server redis1 127.0.0.1:6379 check inter 1s rise 2 fall 3
# 主库2(通过EIP连接)
server redis2 101.201.48.221:6379 check inter 1s rise 2 fall 3
# 主库3(通过EIP连接)
server redis3 8.130.67.202:6379 check inter 1s rise 2 fall 3
# Sentinel监控
server sentinel 106.13.74.202:26379 check inter 1s rise 2 fall 3 backup
重启HAProxy:
# 验证配置
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
重启服务
sudo systemctl restart haproxy
查看状态
sudo systemctl status haproxy
使用HAProxy连接:
# 连接本地HAProxy代理
redis-cli -h 127.0.0.1 -p 6380 -a "password"
应用连接配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6380
REDIS_PASSWORD=password
第四部分:PHP集成实战
4.1 安装Redis扩展
# PHP 8.4
sudo pecl install redis
添加到php.ini
echo "extension=redis.so" | sudo tee -a /etc/php.ini
重启PHP-FPM
sudo systemctl restart php-fpm
验证安装
php -m | grep redis
4.2 PHP Redis基础操作
<?php
/
* Redis管理类
*
* @ai-context
* - 支持连接池
* - 支持批量操作
* - 支持序列化
*/
class RedisManager {
private static $instance = null;
private $redis;
private $config;
private function __construct($config = []) {
$this->config = array_merge([
'host' => '127.0.0.1',
'port' => 6380,
'password' => 'password',
'database' => 0,
'timeout' => 5,
'persistent' => false,
], $config);
$this->connect();
}
public static function getInstance($config = []) {
if (self::$instance === null) {
self::$instance = new self($config);
}
return self::$instance;
}
private function connect() {
$this->redis = new Redis();
if ($this->config['persistent']) {
$this->redis->pconnect(
$this->config['host'],
$this->config['port'],
$this->config['timeout']
);
} else {
$this->redis->connect(
$this->config['host'],
$this->config['port'],
$this->config['timeout']
);
}
if (!empty($this->config['password'])) {
$this->redis->auth($this->config['password']);
}
if (!empty($this->config['database'])) {
$this->redis->select($this->config['database']);
}
// 设置序列化处理
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$this->redis->setOption(Redis::OPT_PREFIX, 'app:');
}
/
* 缓存操作
*/
public function cache($key, $value, $ttl = 3600) {
if ($value === null) {
return $this->redis->get($key);
}
return $this->redis->setex($key, $ttl, $value);
}
/
* 批量获取
*/
public function getMulti($keys) {
return $this->redis->mget($keys);
}
/
* 批量设置
*/
public function setMulti($data, $ttl = 3600) {
$pipeline = $this->redis->pipeline();
foreach ($data as $key => $value) {
$pipeline->setex($key, $ttl, $value);
}
return $pipeline->exec();
}
/
* 删除缓存
*/
public function delete($pattern) {
if (strpos($pattern, '*') !== false) {
$keys = $this->redis->keys($pattern);
if (!empty($keys)) {
return $this->redis->del($keys);
}
return 0;
}
return $this->redis->del($pattern);
}
/
* 获取Redis实例
*/
public function getRedis() {
return $this->redis;
}
/
* 关闭连接
*/
public function close() {
if ($this->redis) {
$this->redis->close();
}
}
}
// 使用示例
$redis = RedisManager::getInstance([
'host' => '127.0.0.1',
'port' => 6380,
'password' => 'password',
'database' => 0,
]);
// 缓存数据
$redis->cache('user:123', ['id' => 123, 'name' => '张三'], 3600);
// 获取缓存
$user = $redis->cache('user:123');
// 批量操作
$redis->setMulti([
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
], 3600);
$values = $redis->getMulti(['key1', 'key2', 'key3']);
4.3 Laravel集成
配置Redis(config/database.php):
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', 'laravel_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6380'),
'database' => env('REDIS_DB', '0'),
'read_timeout' => 5.0,
'timeout' => 5.0,
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6380'),
'database' => env('REDIS_CACHE_DB', '1'),
],
'session' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6380'),
'database' => env('REDIS_SESSION_DB', '2'),
],
],
.env配置:
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=password
REDIS_PORT=6380
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_SESSION_DB=2
缓存配置
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
使用Redis缓存:
<?php
namespace AppHttpControllers;
use IlluminateSupportFacadesCache;
use IlluminateSupportFacadesRedis;
class ArticleController extends Controller
{
/
* 获取文章详情(带缓存)
*/
public function show($id)
{
// 使用Cache门面
$article = Cache::remember("article:{$id}", 3600, function() use ($id) {
return Article::with('comments')->find($id);
});
return view('articles.show', compact('article'));
}
/
* 更新文章阅读数(使用Redis计数器)
*/
public function incrementViews($id)
{
$key = "article:{$id}:views";
// 使用Redis门面
Redis::incr($key);
// 每增长100次,同步到数据库
if (Redis::get($key) % 100 == 0) {
Article::where('id', $id)->update([
'views' => Redis::get($key)
]);
}
return response()->json([
'views' => Redis::get($key)
]);
}
/
* 文章列表缓存(带标签)
*/
public function index()
{
$page = request()->get('page', 1);
$articles = Cache::tags(['articles', "page:{$page}"])
->remember("articles:page:{$page}", 600, function() {
return Article::published()
->orderBy('published_at', 'desc')
->paginate(20);
});
return view('articles.index', compact('articles'));
}
/
* 清除文章缓存
*/
public function clearCache($id)
{
// 清除单个文章缓存
Cache::forget("article:{$id}");
// 清除所有文章列表缓存
Cache::tags(['articles'])->flush();
return response()->json(['message' => 'Cache cleared']);
}
}
使用Redis队列:
<?php
namespace AppJobs;
use IlluminateBusQueueable;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateQueueSerializesModels;
use IlluminateSupportFacadesLog;
class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $email;
private $subject;
private $body;
/
* 任务尝试次数
*/
public $tries = 3;
/
* 任务超时时间(秒)
*/
public $timeout = 30;
public function __construct($email, $subject, $body)
{
$this->email = $email;
$this->subject = $subject;
$this->body = $body;
// 指定队列
$this->onQueue('emails');
}
public function handle()
{
// 发送邮件逻辑
Log::info("Sending email to {$this->email}");
// 实际发送邮件...
sleep(2);
Log::info("Email sent successfully to {$this->email}");
}
public function failed(Throwable $exception)
{
Log::error("Failed to send email to {$this->email}: " . $exception->getMessage());
}
}
// 分发任务
use AppJobsSendEmailJob;
SendEmailJob::dispatch('user@your-domain.com', 'Welcome', 'Welcome to our app!');
第五部分:性能优化
5.1 Pipeline批量操作
<?php
/
* 批量操作优化
*/
class RedisPipeline {
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 批量设置
*/
public function setBatch($data) {
$pipeline = $this->redis->pipeline();
foreach ($data as $key => $value) {
$pipeline->set($key, $value);
}
return $pipeline->exec();
}
/
* 批量获取
*/
public function getBatch($keys) {
$pipeline = $this->redis->pipeline();
foreach ($keys as $key) {
$pipeline->get($key);
}
return $pipeline->exec();
}
/
* 批量设置过期时间
*/
public function expireBatch($keys, $ttl) {
$pipeline = $this->redis->pipeline();
foreach ($keys as $key) {
$pipeline->expire($key, $ttl);
}
return $pipeline->exec();
}
}
// 性能对比:1000次操作
$redis = new Redis();
$redis->connect('127.0.0.1', 6380);
$redis->auth('password');
// 不使用Pipeline
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
$redis->set("key:{$i}", "value:{$i}");
}
$timeWithoutPipeline = microtime(true) - $time;
// 使用Pipeline
$start = microtime(true);
$pipeline = $redis->pipeline();
for ($i = 0; $i < 1000; $i++) {
$pipeline->set("key:{$i}", "value:{$i}");
}
$pipeline->exec();
$timeWithPipeline = microtime(true) - $start;
// 结果:Pipeline快10-50倍
echo "Without Pipeline: {$timeWithoutPipeline}sn";
echo "With Pipeline: {$timeWithPipeline}sn";
5.2 合理设置过期时间
<?php
/
* 缓存过期时间策略
*/
class CacheTTL {
const TTL_SHORT = 300; // 5分钟 - 热点数据
const TTL_MEDIUM = 3600; // 1小时 - 一般数据
const TTL_LONG = 86400; // 1天 - 不常变化数据
const TTL_VERY_LONG = 604800; // 7天 - 静态数据
/
* 根据数据类型选择TTL
*/
public static function getTTL($dataType) {
switch ($dataType) {
case 'hot':
return self::TTL_SHORT;
case 'medium':
return self::TTL_MEDIUM;
case 'static':
return self::TTL_LONG;
case 'archive':
return self::TTL_VERY_LONG;
default:
return self::TTL_MEDIUM;
}
}
/
* 带随机抖动的TTL(防止缓存雪崩)
*/
public static function getTTLWithJitter($baseTTL, $jitterPercent = 10) {
$jitter = $baseTTL * ($jitterPercent / 100);
return $baseTTL + mt_rand(-$jitter, $jitter);
}
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6380);
$redis->auth('password');
// 热点数据(首页、排行榜)
$redis->setex('homepage:data', CacheTTL::TTL_SHORT, json_encode($homepageData));
// 一般数据(文章详情)
$redis->setex('article:123', CacheTTL::getTTLWithJitter(CacheTTL::TTL_MEDIUM), json_encode($article));
// 静态数据(配置信息)
$redis->setex('config:site', CacheTTL::TTL_LONG, json_encode($config));
5.3 避免大Key
<?php
/
* 大Key拆分策略
*/
class BigKeySplitter {
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 拆分大Hash
*/
public function splitHash($bigKey, $chunkSize = 1000) {
$data = $this->redis->hgetall($bigKey);
if (count($data) <= $chunkSize) {
return; // 不需要拆分
}
// 拆分成多个小Hash
$chunks = array_chunk($data, $chunkSize, true);
foreach ($chunks as $index => $chunk) {
$chunkKey = "{$bigKey}:chunk:{$index}";
$this->redis->hmset($chunkKey, $chunk);
}
// 保存元数据
$metaKey = "{$bigKey}:meta";
$this->redis->set($metaKey, json_encode([
'chunks' => count($chunks),
'chunkSize' => $chunkSize
]));
// 删除原大Key
$this->redis->del($bigKey);
}
/
* 获取拆分后的Hash数据
*/
public function getSplitHash($bigKey, $field) {
$metaKey = "{$bigKey}:meta";
$meta = json_decode($this->redis->get($metaKey), true);
if (!$meta) {
// 未拆分,直接获取
return $this->redis->hget($bigKey, $field);
}
// 查找字段在哪个chunk中
for ($i = 0; $i < $meta['chunks']; $i++) {
$chunkKey = "{$bigKey}:chunk:{$i}";
$value = $this->redis->hget($chunkKey, $field);
if ($value !== false) {
return $value;
}
}
return null;
}
}
5.4 连接池配置
<?php
/
* Redis连接池
*/
class RedisPool {
private static $pools = [];
private static $maxConnections = 10;
private static $minConnections = 2;
private $config;
private function __construct($config) {
$this->config = $config;
}
/
* 获取连接池
*/
public static function getPool($name = 'default') {
if (!isset(self::$pools[$name])) {
self::$pools[$name] = new self(self::loadConfig($name));
}
return self::$pools[$name];
}
/
* 获取连接
*/
public function getConnection() {
$redis = new Redis();
try {
$redis->connect(
$this->config['host'],
$this->config['port'],
$this->config['timeout']
);
if (!empty($this->config['password'])) {
$redis->auth($this->config['password']);
}
if (!empty($this->config['database'])) {
$redis->select($this->config['database']);
}
return $redis;
} catch (Exception $e) {
error_log("Redis connection failed: " . $e->getMessage());
return null;
}
}
private static function loadConfig($name) {
$configs = [
'default' => [
'host' => '127.0.0.1',
'port' => 6380,
'password' => 'password',
'database' => 0,
'timeout' => 5,
],
'cache' => [
'host' => '127.0.0.1',
'port' => 6380,
'password' => 'password',
'database' => 1,
'timeout' => 5,
],
];
return $configs[$name] ?? $configs['default'];
}
}
// 使用连接池
$pool = RedisPool::getPool('default');
$redis = $pool->getConnection();
if ($redis) {
$redis->set('key', 'value');
$value = $redis->get('key');
}
第六部分:监控与告警
6.1 Redis监控指标
#!/bin/bash
Redis监控脚本
REDIS_CLI="/server/redis/bin/redis-cli"
REDIS_HOST="127.0.0.1"
REDIS_PORT="6380"
REDIS_PASSWORD="password"
连接Redis并获取信息
INFO=$($REDIS_CLI -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD INFO 2>/dev/null)
解析关键指标
USED_MEMORY=$(echo "$INFO" | grep "used_memory_human:" | cut -d: -f2 | tr -d 'r')
TOTAL_CONNECTIONS=$(echo "$INFO" | grep "total_connections_received:" | cut -d: -f2 | tr -d 'r')
TOTAL_COMMANDS=$(echo "$INFO" | grep "total_commands_processed:" | cut -d: -f2 | tr -d 'r')
KEYSPACE_HITS=$(echo "$INFO" | grep "keyspace_hits:" | cut -d: -f2 | tr -d 'r')
KEYSPACE_MISSES=$(echo "$INFO" | grep "keyspace_misses:" | cut -d: -f2 | tr -d 'r')
计算命中率
if [ -n "$KEYSPACE_HITS" ] && [ -n "$KEYSPACE_MISSES" ]; then
TOTAL_REQUESTS=$((KEYSPACE_HITS + KEYSPACE_MISSES))
if [ $TOTAL_REQUESTS -gt 0 ]; then
HIT_RATE=$(echo "scale=2; $KEYSPACE_HITS * 100 / $TOTAL_REQUESTS" | bc)
echo "Cache Hit Rate: ${HIT_RATE}%"
fi
fi
echo "Used Memory: $USED_MEMORY"
echo "Total Connections: $TOTAL_CONNECTIONS"
echo "Total Commands: $TOTAL_COMMANDS"
慢查询
SLOWLOG=$($REDIS_CLI -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD SLOWLOG GET 10 2>/dev/null)
echo "Slow Queries (last 10):"
echo "$SLOWLOG"
6.2 PHP监控脚本
<?php
/
* Redis健康检查脚本
*/
class RedisHealthCheck {
private $redis;
private $alerts = [];
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 执行健康检查
*/
public function check() {
$this->checkConnection();
$this->checkMemory();
$this->checkHitRate();
$this->checkSlowQueries();
$this->checkReplication();
return $this->alerts;
}
/
* 检查连接
*/
private function checkConnection() {
try {
$pong = $this->redis->ping();
if ($pong !== '+PONG') {
$this->alerts[] = [
'level' => 'critical',
'message' => 'Redis ping failed'
];
}
} catch (Exception $e) {
$this->alerts[] = [
'level' => 'critical',
'message' => 'Redis connection failed: ' . $e->getMessage()
];
}
}
/
* 检查内存使用
*/
private function checkMemory() {
$info = $this->redis->info('memory');
$usedMemory = $info['used_memory'];
$maxMemory = $info['maxmemory'];
if ($maxMemory > 0) {
$usagePercent = ($usedMemory / $maxMemory) * 100;
if ($usagePercent > 90) {
$this->alerts[] = [
'level' => 'critical',
'message' => sprintf('Memory usage: %.2f%%', $usagePercent)
];
} elseif ($usagePercent > 80) {
$this->alerts[] = [
'level' => 'warning',
'message' => sprintf('Memory usage: %.2f%%', $usagePercent)
];
}
}
}
/
* 检查缓存命中率
*/
private function checkHitRate() {
$info = $this->redis->info('stats');
$hits = $info['keyspace_hits'];
$misses = $info['keyspace_misses'];
$total = $hits + $misses;
if ($total > 0) {
$hitRate = ($hits / $total) * 100;
if ($hitRate < 50) {
$this->alerts[] = [
'level' => 'warning',
'message' => sprintf('Cache hit rate too low: %.2f%%', $hitRate)
];
}
}
}
/
* 检查慢查询
*/
private function checkSlowQueries() {
$slowlog = $this->redis->slowlog('get', 10);
if (count($slowlog) > 5) {
$this->alerts[] = [
'level' => 'warning',
'message' => sprintf('Too many slow queries: %d', count($slowlog))
];
}
foreach ($slowlog as $entry) {
if ($entry[1] > 10000) { // 超过10秒
$this->alerts[] = [
'level' => 'warning',
'message' => sprintf('Very slow query: %.2fs - %s', $entry[1] / 1000000, $entry[3])
];
}
}
}
/
* 检查复制状态
*/
private function checkReplication() {
$info = $this->redis->info('replication');
if (isset($info['master_link_status']) && $info['master_link_status'] !== 'up') {
$this->alerts[] = [
'level' => 'critical',
'message' => 'Master link is down'
];
}
}
/
* 发送告警
*/
public function sendAlerts() {
if (empty($this->alerts)) {
return;
}
// 发送邮件、Slack、飞书等
foreach ($this->alerts as $alert) {
$this->sendNotification($alert);
}
}
private function sendNotification($alert) {
// 实现发送逻辑(邮件、Webhook等)
error_log(sprintf("[%s] %s", strtoupper($alert['level']), $alert['message']));
}
}
// 定时执行
$healthCheck = new RedisHealthCheck();
$alerts = $healthCheck->check();
if (!empty($alerts)) {
$healthCheck->sendAlerts();
}
第七部分:故障处理
7.1 常见问题诊断
#!/bin/bash
Redis故障诊断脚本
echo "=== Redis故障诊断 ==="
echo ""
1. 检查Redis进程
echo "【1】检查Redis进程"
if pgrep -x "redis-server" > /dev/null; then
echo "✅ Redis进程运行中"
PID=$(pgrep -x "redis-server")
echo " PID: $PID"
else
echo "❌ Redis进程未运行"
echo " 修复命令: systemctl start redis"
fi
echo ""
2. 检查Redis端口
echo "【2】检查Redis端口"
if netstat -tuln | grep -q ":6379 "; then
echo "✅ Redis端口6379监听中"
else
echo "❌ Redis端口6379未监听"
fi
echo ""
3. 测试Redis连接
echo "【3】测试Redis连接"
if redis-cli -h 127.0.0.1 -p 6379 -a "password" PING 2>/dev/null | grep -q "PONG"; then
echo "✅ Redis连接正常"
else
echo "❌ Redis连接失败"
echo " 可能原因:"
echo " 1. Redis服务未启动"
echo " 2. 密码错误"
echo " 3. 防火墙阻止"
fi
echo ""
4. 检查Redis内存
echo "【4】检查Redis内存使用"
MEMORY=$(redis-cli -h 127.0.0.1 -p 6379 -a "password" INFO memory 2>/dev/null | grep "used_memory_human:" | cut -d: -f2 | tr -d 'r')
echo " 内存使用: $MEMORY"
echo ""
5. 检查慢查询
echo "【5】检查慢查询"
SLOWLOG=$(redis-cli -h 127.0.0.1 -p 6379 -a "password" SLOWLOG LEN 2>/dev/null | tr -d 'r')
echo " 慢查询数量: $SLOWLOG"
if [ "$SLOWLOG" -gt 10 ]; then
echo " ⚠️ 慢查询过多,建议优化"
echo " 查看命令: redis-cli SLOWLOG GET 10"
fi
echo ""
6. 检查持久化
echo "【6】检查持久化状态"
RDB_LAST_SAVE=$(redis-cli -h 127.0.0.1 -p 6379 -a "password" LASTSAVE 2>/dev/null | tr -d 'r')
echo " 最后一次RDB保存: $RDB_LAST_SAVE"
AOF_ENABLED=$(redis-cli -h 127.0.0.1 -p 6379 -a "password" CONFIG GET appendonly 2>/dev/null | tail -1 | tr -d 'r')
if [ "$AOF_ENABLED" = "yes" ]; then
echo " AOF: 已启用"
else
echo " AOF: 未启用"
fi
echo ""
echo "=== 诊断完成 ==="
7.2 内存优化策略
#!/bin/bash
Redis内存优化脚本
echo "=== Redis内存优化 ==="
echo ""
1. 查找大Key
echo "【1】查找大Key(Top 20)"
redis-cli -h 127.0.0.1 -p 6379 -a "password" --bigkeys --sample 1000 2>/dev/null | head -50
echo ""
2. 查找过期Key
echo "【2】查找有过期时间的Key数量"
redis-cli -h 127.0.0.1 -p 6379 -a "password" DBSIZE 2>/dev/null
echo ""
3. 查看内存使用详情
echo "【3】内存使用详情"
redis-cli -h 127.0.0.1 -p 6379 -a "password" INFO memory 2>/dev/null | grep -E "used_memory|maxmemory|mem_fragmentation_ratio"
echo ""
4. 分析内存碎片
echo "【4】内存碎片率"
FRAG=$(redis-cli -h 127.0.0.1 -p 6379 -a "password" INFO memory 2>/dev/null | grep "mem_fragmentation_ratio:" | cut -d: -f2 | tr -d 'r')
echo " 碎片率: $FRAG"
FRAG_FLOAT=$(echo "$FRAG" | bc -l)
if (( $(echo "$FRAG_FLOAT > 1.5" | bc -l) )); then
echo " ⚠️ 碎片率过高,建议执行MEMORY PURGE"
fi
echo ""
5. 执行内存整理
echo "【5】执行内存整理"
read -p "是否执行内存整理(MEMORY PURGE)?[y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
redis-cli -h 127.0.0.1 -p 6379 -a "password" MEMORY PURGE 2>/dev/null
echo "✅ 内存整理完成"
fi
echo ""
echo "=== 优化完成 ==="
7.3 数据恢复
#!/bin/bash
Redis数据恢复脚本
REDIS_DIR="/server/redis/data"
BACKUP_DIR="/backup/redis"
REDIS_CLI="/server/redis/bin/redis-cli"
echo "=== Redis数据恢复 ==="
echo ""
1. 停止Redis
echo "【1】停止Redis服务"
systemctl stop redis
echo "✅ Redis已停止"
echo ""
2. 备份当前数据
echo "【2】备份当前数据"
BACKUP_NAME="redis_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR/$BACKUP_NAME"
cp -r $REDIS_DIR/* "$BACKUP_DIR/$BACKUP_NAME/"
echo "✅ 当前数据已备份到: $BACKUP_DIR/$BACKUP_NAME"
echo ""
3. 选择恢复方式
echo "【3】选择恢复方式"
echo "1. 从RDB文件恢复"
echo "2. 从AOF文件恢复"
read -p "请选择 [1/2]: " choice
case $choice in
1)
# 从RDB恢复
echo "请输入RDB文件路径:"
read RDB_FILE
if [ -f "$RDB_FILE" ]; then
cp "$RDB_FILE" "$REDIS_DIR/dump.rdb"
echo "✅ RDB文件已复制"
else
echo "❌ 文件不存在: $RDB_FILE"
exit 1
fi
;;
2)
# 从AOF恢复
echo "请输入AOF文件路径:"
read AOF_FILE
if [ -f "$AOF_FILE" ]; then
cp "$AOF_FILE" "$REDIS_DIR/appendonly.aof"
echo "✅ AOF文件已复制"
else
echo "❌ 文件不存在: $AOF_FILE"
exit 1
fi
;;
*)
echo "❌ 无效选择"
exit 1
;;
esac
4. 修复AOF(如果需要)
if [ -f "$REDIS_DIR/appendonly.aof" ]; then
echo ""
echo "【4】修复AOF文件"
$REDIS_CLI --appendonly fix
echo "✅ AOF文件已修复"
fi
5. 启动Redis
echo ""
echo "【5】启动Redis服务"
systemctl start redis
sleep 2
6. 验证数据
echo "【6】验证数据"
if $REDIS_CLI PING 2>/dev/null | grep -q "PONG"; then
DBSIZE=$($REDIS_CLI DBSIZE 2>/dev/null | tr -d 'r')
echo "✅ Redis启动成功"
echo " Key数量: $DBSIZE"
else
echo "❌ Redis启动失败"
systemctl status redis
exit 1
fi
echo ""
echo "=== 恢复完成 ==="
第八部分:生产环境最佳实践
8.1 安全加固
# 1. 重命名危险命令
redis-cli CONFIG SET rename-command FLUSHDB ""
redis-cli CONFIG SET rename-command FLUSHALL ""
redis-cli CONFIG SET rename-command CONFIG "CONFIG_REDIS_123"
2. 设置密码
redis-cli CONFIG SET requirepass "your_strong_password"
3. 禁用敏感命令
redis-cli CONFIG SET shutdown-command-on-noenabled yes
4. 网络隔离
- 使用防火墙限制访问
- 只允许特定IP连接
- 使用VPN或专线
8.2 备份策略
#!/bin/bash
Redis自动备份脚本
BACKUP_DIR="/backup/redis"
REDIS_CLI="/server/redis/bin/redis-cli"
REDIS_DATA_DIR="/server/redis/data"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="redis_backup_$DATE"
创建备份目录
mkdir -p "$BACKUP_DIR/$BACKUP_NAME"
RDB备份
echo "执行RDB备份"
$REDIS-cli BGSAVE
sleep 5
等待备份完成
while [ $($REDIS_CLI LASTSAVE) -eq $($REDIS-cli -h localhost -p 6379 LASTSAVE) ]; do
sleep 1
done
复制RDB文件
cp "$REDIS_DATA_DIR/dump.rdb" "$BACKUP_DIR/$BACKUP_NAME/"
AOF备份(如果启用)
if [ -f "$REDIS_DATA_DIR/appendonly.aof" ]; then
echo "执行AOF备份"
cp "$REDIS_DATA_DIR/appendonly.aof" "$BACKUP_DIR/$BACKUP_NAME/"
fi
压缩备份
tar -czf "$BACKUP_DIR/${BACKUP_NAME}.tar.gz" -C "$BACKUP_DIR" "$BACKUP_NAME"
rm -rf "$BACKUP_DIR/$BACKUP_NAME"
删除7天前的备份
find "$BACKUP_DIR" -name "redis_backup_*.tar.gz" -mtime +7 -delete
echo "备份完成: $BACKUP_DIR/${BACKUP_NAME}.tar.gz"
8.3 监控告警配置
<?php
/
* Redis监控告警系统
*/
class RedisMonitor {
private $redis;
private $thresholds = [
'memory_usage' => 80, // 内存使用率阈值(%)
'hit_rate' => 50, // 缓存命中率阈值(%)
'slow_queries' => 10, // 慢查询数量阈值
'connections' => 10000, // 连接数阈值
];
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 执行监控检查
*/
public function monitor() {
$metrics = $this->collectMetrics();
$alerts = $this->checkThresholds($metrics);
if (!empty($alerts)) {
$this->sendAlerts($alerts);
}
return $metrics;
}
/
* 收集指标
*/
private function collectMetrics() {
$info = $this->redis->info();
return [
'memory_usage' => $this->calculateMemoryUsage($info),
'hit_rate' => $this->calculateHitRate($info),
'slow_queries' => count($this->redis->slowlog('get')),
'connections' => $info['connected_clients'],
'commands_per_sec' => $info['instantaneous_ops_per_sec'],
'keyspace_size' => $info['db0'] ?? 0,
];
}
/
* 检查阈值
*/
private function checkThresholds($metrics) {
$alerts = [];
foreach ($this->thresholds as $key => $threshold) {
if (isset($metrics[$key]) && $metrics[$key] > $threshold) {
$alerts[] = [
'metric' => $key,
'value' => $metrics[$key],
'threshold' => $threshold,
'severity' => $this->getSeverity($key, $metrics[$key], $threshold)
];
}
}
return $alerts;
}
/
* 发送告警
*/
private function sendAlerts($alerts) {
foreach ($alerts as $alert) {
$message = sprintf(
"[Redis Alert] %s: %s (threshold: %s)",
$alert['metric'],
$alert['value'],
$alert['threshold']
);
// 发送到日志
error_log($message);
// 发送到邮件
mail('admin@your-domain.com', 'Redis Alert', $message);
// 发送到Slack/飞书
$this->sendWebhook($alert);
}
}
/
* 发送Webhook通知
*/
private function sendWebhook($alert) {
$webhookUrl = 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx';
$data = [
'msg_type' => 'text',
'content' => [
'text' => sprintf(
"Redis告警n指标: %sn当前值: %sn阈值: %sn级别: %s",
$alert['metric'],
$alert['value'],
$alert['threshold'],
$alert['severity']
)
]
];
$ch = curl_init($webhookUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_exec($ch);
curl_close($ch);
}
private function calculateMemoryUsage($info) {
$used = $info['used_memory'];
$max = $info['maxmemory'];
return $max > 0 ? ($used / $max) * 100 : 0;
}
private function calculateHitRate($info) {
$hits = $info['keyspace_hits'];
$misses = $info['keyspace_misses'];
$total = $hits + $misses;
return $total > 0 ? ($hits / $total) * 100 : 0;
}
private function getSeverity($metric, $value, $threshold) {
$ratio = $value / $threshold;
if ($ratio >= 1.5) {
return 'critical';
} elseif ($ratio >= 1.2) {
return 'warning';
} else {
return 'info';
}
}
}
// 定时执行(每5分钟)
$monitor = new RedisMonitor();
$monitor->monitor();
第九部分:性能测试与调优
9.1 基准测试
#!/bin/bash
Redis性能基准测试
REDIS_CLI="/server/redis/bin/redis-cli"
RESULTS_DIR="/tmp/redis_benchmark_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$RESULTS_DIR"
echo "=== Redis性能基准测试 ==="
echo ""
1. SET操作测试
echo "【1】SET操作测试(10万次)"
$REDIS_CLI --csv -h 127.0.0.1 -p 6379 -a "password" benchmark -t set -n 100000 -q | tee "$RESULTS_DIR/set_results.csv"
echo ""
2. GET操作测试
echo "【2】GET操作测试(10万次)"
$REDIS_CLI --csv -h 127.0.0.1 -p 6379 -a "password" benchmark -t get -n 100000 -q | tee "$RESULTS_DIR/get_results.csv"
echo ""
3. 混合操作测试
echo "【3】混合操作测试(10万次)"
$REDIS_CLI --csv -h 127.0.0.1 -p 6379 -a "password" benchmark -t set,get,incr,lpush,lpop -n 100000 -q | tee "$RESULTS_DIR/mix_results.csv"
echo ""
4. Pipeline性能测试
echo "【4】Pipeline性能测试(10万次,50批量)"
$REDIS_CLI --csv -h 127.0.0.1 -p 6379 -a "password" benchmark -t set,get -n 100000 -P 50 -q | tee "$RESULTS_DIR/pipeline_results.csv"
echo ""
5. 生成报告
echo "【5】生成性能报告"
cat > "$RESULTS_DIR/report.md" << EOF
Redis性能基准测试报告
测试时间: $(date)
Redis版本: $($REDIS_CLI --version | grep -oP 'v=K[0-9.]+')
测试数据量: 10万次操作
测试结果
SET操作
$(cat "$RESULTS_DIR/set_results.csv")
GET操作
$(cat "$RESULTS_DIR/get_results.csv")
混合操作
$(cat "$RESULTS_DIR/mix_results.csv")
Pipeline操作
$(cat "$RESULTS_DIR/pipeline_results.csv")
性能分析
- 单次操作QPS: 约10万+
- Pipeline操作QPS: 约50万+
- Pipeline性能提升: 5倍左右
优化建议
1. 使用Pipeline批量操作
2. 合理设置过期时间
3. 避免大Key
4. 使用连接池
EOF
echo "✅ 测试完成,结果保存在: $RESULTS_DIR"
echo " 报告文件: $RESULTS_DIR/report.md"
9.2 压力测试
<?php
/
* Redis压力测试脚本
*/
class RedisStressTest {
private $redis;
private $results = [];
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 执行压力测试
*/
public function test($operations = 100000, $threads = 10) {
echo "=== Redis压力测试 ===n";
echo "操作数: {$operations}n";
echo "并发数: {$threads}nn";
$this->testSet($operations);
$this->testGet($operations);
$this->testIncr($operations);
$this->testPipeline($operations);
$this->testConcurrent($threads);
$this->printResults();
}
/
* 测试SET操作
*/
private function testSet($operations) {
$start = microtime(true);
for ($i = 0; $i < $operations; $i++) {
$this->redis->set("key:{$i}", "value:{$i}");
}
$time = microtime(true) - $start;
$qps = $operations / $time;
$this->results['set'] = [
'operations' => $operations,
'time' => $time,
'qps' => $qps
];
}
/
* 测试GET操作
*/
private function testGet($operations) {
// 先填充数据
for ($i = 0; $i < $operations; $i++) {
$this->redis->set("key:{$i}", "value:{$i}");
}
$start = microtime(true);
for ($i = 0; $i < $operations; $i++) {
$this->redis->get("key:{$i}");
}
$time = microtime(true) - $start;
$qps = $operations / $time;
$this->results['get'] = [
'operations' => $operations,
'time' => $time,
'qps' => $qps
];
}
/
* 测试INCR操作
*/
private function testIncr($operations) {
$start = microtime(true);
for ($i = 0; $i < $operations; $i++) {
$this->redis->incr("counter:{$i}");
}
$time = microtime(true) - $start;
$qps = $operations / $time;
$this->results['incr'] = [
'operations' => $operations,
'time' => $time,
'qps' => $qps
];
}
/
* 测试Pipeline
*/
private function testPipeline($operations) {
$start = microtime(true);
$pipeline = $this->redis->pipeline();
for ($i = 0; $i < $operations; $i++) {
$pipeline->set("pipe:key:{$i}", "value:{$i}");
}
$pipeline->exec();
$time = microtime(true) - $start;
$qps = $operations / $time;
$this->results['pipeline'] = [
'operations' => $operations,
'time' => $time,
'qps' => $qps
];
}
/
* 测试并发
*/
private function testConcurrent($threads) {
$start = microtime(true);
$pids = [];
for ($i = 0; $i < $threads; $i++) {
$pid = pcntl_fork();
if ($pid == 0) {
// 子进程
$redis = new Redis();
$redis->connect('127.0.0.1', 6380);
$redis->auth('password');
for ($j = 0; $j < 10000; $j++) {
$redis->set("concurrent:{$i}:{$j}", "value");
}
exit(0);
} else {
$pids[] = $pid;
}
}
// 等待所有子进程
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
}
$time = microtime(true) - $start;
$totalOps = $threads * 10000;
$qps = $totalOps / $time;
$this->results['concurrent'] = [
'threads' => $threads,
'operations' => $totalOps,
'time' => $time,
'qps' => $qps
];
}
/
* 打印结果
*/
private function printResults() {
echo "n=== 测试结果 ===nn";
foreach ($this->results as $name => $result) {
echo "【{$name}】n";
foreach ($result as $key => $value) {
if ($key == 'time') {
echo " {$key}: " . number_format($value, 4) . "sn";
} elseif ($key == 'qps') {
echo " {$key}: " . number_format($value) . "n";
} else {
echo " {$key}: {$value}n";
}
}
echo "n";
}
// 性能对比
if (isset($this->results['set']['qps']) && isset($this->results['pipeline']['qps'])) {
$speedup = $this->results['pipeline']['qps'] / $this->results['set']['qps'];
echo "【性能提升】n";
echo " Pipeline vs SET: " . number_format($speedup, 2) . "xnn";
}
}
}
// 执行测试
$test = new RedisStressTest();
$test->test(100000, 10);
第十部分:实战案例
10.1 构建高性能计数器系统
<?php
/
* 高性能计数器系统
*
* @ai-context
* - 支持多维度计数
* - 支持自动归档
* - 支持实时查询
*/
class CounterSystem {
private $redis;
private $prefix = 'counter';
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 增加计数
*
* @param string $key 计数键
* @param int $delta 增量
* @param array $dimensions 维度标签
*/
public function increment($key, $delta = 1, $dimensions = []) {
$redisKey = $this->buildKey($key, $dimensions);
// 增加计数
$this->redis->incrby($redisKey, $delta);
// 设置过期时间(30天)
$this->redis->expire($redisKey, 86400 * 30);
// 记录到时间序列
$this->recordToTimeseries($key, $delta, $dimensions);
return $this->redis->get($redisKey);
}
/
* 获取计数
*/
public function get($key, $dimensions = []) {
$redisKey = $this->buildKey($key, $dimensions);
return (int)$this->redis->get($redisKey);
}
/
* 获取排行榜
*/
public function getRanking($key, $limit = 10) {
$pattern = "{$this->prefix}:{$key}:*";
$keys = $this->redis->keys($pattern);
$ranking = [];
foreach ($keys as $redisKey) {
$value = (int)$this->redis->get($redisKey);
$dimensions = $this->parseKey($redisKey);
$ranking[] = [
'dimensions' => $dimensions,
'value' => $value
];
}
// 按值排序
usort($ranking, function($a, $b) {
return $b['value'] - $a['value'];
});
return array_slice($ranking, 0, $limit);
}
/
* 获取时间序列数据
*/
public function getTimeseries($key, $startTime, $endTime, $dimensions = []) {
$timeseriesKey = $this->buildTimeseriesKey($key, $dimensions);
$startTimestamp = strtotime($startTime);
$endTimestamp = strtotime($endTime);
return $this->redis->zrangebyscore(
$timeseriesKey,
$startTimestamp,
$endTimestamp,
['withscores' => true]
);
}
/
* 批量增加计数
*/
public function incrementBatch($counters) {
$pipeline = $this->redis->pipeline();
foreach ($counters as $counter) {
$redisKey = $this->buildKey($counter['key'], $counter['dimensions'] ?? []);
$delta = $counter['delta'] ?? 1;
$pipeline->incrby($redisKey, $delta);
$pipeline->expire($redisKey, 86400 * 30);
}
return $pipeline->exec();
}
/
* 构建Redis键
*/
private function buildKey($key, $dimensions = []) {
if (empty($dimensions)) {
return "{$this->prefix}:{$key}";
}
ksort($dimensions);
$dimensionStr = http_build_query($dimensions);
return "{$this->prefix}:{$key}:{$dimensionStr}";
}
/
* 解析Redis键
*/
private function parseKey($redisKey) {
$parts = explode(':', $redisKey);
array_shift($parts); // 移除prefix
array_shift($parts); // 移除key
if (empty($parts)) {
return [];
}
$dimensionStr = implode(':', $parts);
parse_str($dimensionStr, $dimensions);
return $dimensions;
}
/
* 记录到时间序列
*/
private function recordToTimeseries($key, $delta, $dimensions = []) {
$timeseriesKey = $this->buildTimeseriesKey($key, $dimensions);
$timestamp = time();
$value = json_encode([
'delta' => $delta,
'timestamp' => $timestamp
]);
$this->redis->zadd($timeseriesKey, $timestamp, $value);
// 设置过期时间(90天)
$this->redis->expire($timeseriesKey, 86400 * 90);
}
/
* 构建时间序列键
*/
private function buildTimeseriesKey($key, $dimensions = []) {
$dimensionStr = empty($dimensions) ? '' : ':' . md5(json_encode($dimensions));
return "{$this->prefix}:timeseries:{$key}{$dimensionStr}";
}
}
// 使用示例
$counter = new CounterSystem();
// 简单计数
$counter->increment('page_views');
echo "Page Views: " . $counter->get('page_views') . "n";
// 带维度的计数
$counter->increment('article_views', 1, ['article_id' => 123]);
$counter->increment('article_views', 1, ['article_id' => 456]);
echo "Article 123 Views: " . $counter->get('article_views', ['article_id' => 123]) . "n";
// 多维度计数
$counter->increment('downloads', 1, [
'product_id' => 1,
'version' => '2.0',
'platform' => 'ios'
]);
// 获取排行榜
$ranking = $counter->getRanking('article_views', 10);
print_r($ranking);
// 获取时间序列
$timeseries = $counter->getTimeseries('page_views', '-7 days', 'now');
print_r($timeseries);
10.2 构建实时排行榜系统
<?php
/
* 实时排行榜系统
*
* @ai-context
* - 支持多种排名规则
* - 支持历史排名查询
* - 支持排名变化通知
*/
class RealtimeLeaderboard {
private $redis;
private $prefix = 'leaderboard';
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6380);
$this->redis->auth('password');
}
/
* 添加或更新分数
*/
public function addScore($leaderboard, $member, $score, $memberData = []) {
$key = "{$this->prefix}:{$leaderboard}";
// 更新分数
$this->redis->zadd($key, $score, $member);
// 保存成员数据
if (!empty($memberData)) {
$dataKey = "{$this->prefix}:data:{$leaderboard}:{$member}";
$this->redis->hmset($dataKey, $memberData);
$this->redis->expire($dataKey, 86400 * 30);
}
// 记录历史
$this->recordHistory($leaderboard, $member, $score);
// 检查排名变化
$this->checkRankChange($leaderboard, $member);
return $this->getMemberRank($leaderboard, $member);
}
/
* 批量添加分数
*/
public function addScores($leaderboard, $scores) {
$key = "{$this->prefix}:{$leaderboard}";
$pipeline = $this->redis->pipeline();
foreach ($scores as $member => $score) {
$pipeline->zadd($key, $score, $member);
}
return $pipeline->exec();
}
/
* 获取排行榜(Top N)
*/
public function getTop($leaderboard, $limit = 10, $withData = false) {
$key = "{$this->prefix}:{$leaderboard}";
$members = $this->redis->zrevrange(
$key,
0,
$limit - 1,
['withscores' => true]
);
if (!$withData) {
return $members;
}
// 获取成员数据
$result = [];
foreach ($members as $member => $score) {
$dataKey = "{$this->prefix}:data:{$leaderboard}:{$member}";
$memberData = $this->redis->hgetall($dataKey);
$result[] = [
'member' => $member,
'score' => $score,
'data' => $memberData
];
}
return $result;
}
/
* 获取成员排名和分数
*/
public function getMemberInfo($leaderboard, $member) {
$key = "{$this->prefix}:{$leaderboard}";
$rank = $this->redis->zrevrank($key, $member);
$score = $this->redis->zscore($key, $member);
if ($rank === false || $score === false) {
return null;
}
$dataKey = "{$this->prefix}:data:{$leaderboard}:{$member}";
$memberData = $this->redis->hgetall($dataKey);
return [
'member' => $member,
'rank' => $rank + 1,
'score' => $score,
'data' => $memberData
];
}
/
* 获取分数范围
*/
public function getByScoreRange($leaderboard, $minScore, $maxScore, $limit = 10) {
$key = "{$this->prefix}:{$leaderboard}";
return $this->redis->zrevrangebyscore(
$key,
$maxScore,
$minScore,
['withscores' => true, 'limit' => [0, $limit]]
);
}
/
* 获取排名范围
*/
public function getByRankRange($leaderboard, $minRank, $maxRank) {
$key = "{$this->prefix}:{$leaderboard}";
return $this->redis->zrevrange(
$key,
$minRank - 1,
$maxRank - 1,
['withscores' => true]
);
}
/
* 获取历史排名
*/
public function getHistory($leaderboard, $member, $limit = 10) {
$historyKey = "{$this->prefix}:history:{$leaderboard}:{$member}";
$history = $this->redis->zrevrange(
$historyKey,
0,
$limit - 1,
['withscores' => true]
);
$result = [];
foreach ($history as $data => $timestamp) {
$record = json_decode($data, true);
$record['timestamp'] = $timestamp;
$result[] = $record;
}
return $result;
}
/
* 记录历史
*/
private function recordHistory($leaderboard, $member, $score) {
$historyKey = "{$this->prefix}:history:{$leaderboard}:{$member}";
$timestamp = time();
$data = json_encode([
'member' => $member,
'score' => $score,
'rank' => $this->getMemberRank($leaderboard, $member)
]);
$this->redis->zadd($historyKey, $timestamp, $data);
// 只保留最近100条记录
$this->redis->zremrangebyrank($historyKey, 0, -101);
// 设置过期时间(90天)
$this->redis->expire($historyKey, 86400 * 90);
}
/
* 检查排名变化
*/
private function checkRankChange($leaderboard, $member) {
$currentRank = $this->getMemberRank($leaderboard, $member);
$previousRank = $this->getPreviousRank($leaderboard, $member);
if ($previousRank !== null && $currentRank !== $previousRank) {
$change = $previousRank - $currentRank;
// 排名上升超过10位,发送通知
if ($change >= 10) {
$this->sendRankUpNotification($leaderboard, $member, $currentRank, $change);
}
}
// 保存当前排名
$this->setPreviousRank($leaderboard, $member, $currentRank);
}
/
* 获取成员排名
*/
private function getMemberRank($leaderboard, $member) {
$key = "{$this->prefix}:{$leaderboard}";
$rank = $this->redis->zrevrank($key, $member);
return $rank !== false ? $rank + 1 : null;
}
/
* 获取上一次排名
*/
private function getPreviousRank($leaderboard, $member) {
$key = "{$this->prefix}:prev_rank:{$leaderboard}:{$member}";
$rank = $this->redis->get($key);
return $rank !== false ? (int)$rank : null;
}
/
* 保存当前排名
*/
private function setPreviousRank($leaderboard, $member, $rank) {
$key = "{$this->prefix}:prev_rank:{$leaderboard}:{$member}";
$this->redis->set($key, $rank);
$this->redis->expire($key, 86400 * 7);
}
/
* 发送排名上升通知
*/
private function sendRankUpNotification($leaderboard, $member, $currentRank, $change) {
// 实现通知逻辑(邮件、推送等)
$message = sprintf(
"恭喜!您在%s排行榜中上升了%d位,当前排名:第%d名",
$leaderboard,
$change,
$currentRank
);
error_log($message);
}
}
// 使用示例
$leaderboard = new RealtimeLeaderboard();
// 添加分数
$leaderboard->addScore('game', 'player1', 1000, ['name' => '张三', 'avatar' => 'avatar1.jpg']);
$leaderboard->addScore('game', 'player2', 1500, ['name' => '李四', 'avatar' => 'avatar2.jpg']);
$leaderboard->addScore('game', 'player3', 1200, ['name' => '王五', 'avatar' => 'avatar3.jpg']);
// 获取Top 10
$top10 = $leaderboard->getTop('game', 10, true);
print_r($top10);
// 获取玩家信息
$playerInfo = $leaderboard->getMemberInfo('game', 'player1');
print_r($playerInfo);
// 获取历史排名
$history = $leaderboard->getHistory('game', 'player1', 10);
print_r($history);
最后一些建议
本教程全面介绍了Redis的使用方法,从基础概念到生产环境部署,从简单操作到高级应用。主要内容包括:
核心知识点
1. Redis基础:数据结构、性能特点、应用场景
2. 数据结构:String、Hash、List、Set、Sorted Set的详细使用
3. 三服务器配置:生产环境部署、Sentinel高可用、HAProxy代理
4. PHP集成:基础操作、Laravel集成、连接池管理
5. 性能优化:Pipeline、缓存策略、大Key拆分
6. 监控告警:健康检查、性能监控、故障告警
7. 故障处理:问题诊断、数据恢复、内存优化
8. 实战案例:计数器系统、排行榜系统
最佳实践
1. 使用Pipeline批量操作:性能提升5-10倍
2. 合理设置过期时间:避免内存溢出
3. 避免大Key:拆分大Hash、大List
4. 使用连接池:减少连接开销
5. 启用持久化:RDB + AOF组合
6. 配置高可用:Sentinel自动故障切换
7. 监控关键指标:内存使用、命中率、慢查询
8. 定期备份:自动化备份策略
性能指标
– 单实例QPS:50,000-80,000
– Pipeline QPS:200,000-500,000
– 内存使用:根据数据量合理配置maxmemory
– 缓存命中率:建议保持在80%以上
Redis是提升应用性能的利器,合理使用可以显著改善系统响应速度和用户体验。希望本教程能帮助您在实际项目中更好地使用Redis!
参考资料:
– Redis官方文档:https://redis.io/documentation
– PHP Redis扩展:https://github.com/phpredis/phpredis
– Laravel Redis文档:https://laravel.com/docs/redis
相关文章: