Redis是目前最流行的内存数据库,以高性能、高并发、丰富的数据结构著称。很多人只会用Redis做简单的缓存,但是Redis的功能远不止于此,它还可以做消息队列、发布订阅、分布式锁、计数器、排行榜等。今天就来分享Redis的高级用法。
1. Redis数据结构
Redis支持多种数据结构,不同的数据结构适用于不同的场景:
- String(字符串):最基本的数据结构,可以存储字符串、整数、浮点数。适用于缓存、计数器、分布式锁等。
- Hash(哈希):键值对集合,适合存储对象。适用于存储用户信息、文章信息等。
- List(列表):有序可重复的字符串列表,基于链表实现。适用于消息队列、最新列表等。
- Set(集合):无序不可重复的字符串集合。适用于标签、共同好友、抽奖等。
- Sorted Set(有序集合):有序不可重复的字符串集合,每个元素有一个分数。适用于排行榜、延时队列等。
- Bitmap(位图):基于String的位操作,适用于签到、用户在线状态等。
- HyperLogLog:基数统计,适用于UV统计等。
- Geo:地理位置,适用于附近的人、距离计算等。
2. 缓存实战
缓存是Redis最常用的场景,合理使用缓存能大大提升系统性能,减轻数据库压力。
基本缓存:
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 从缓存获取数据
$key = 'article:' . $id;
$data = $redis->get($key);
if ($data === false) {
// 缓存未命中,从数据库查询
$data = $db->query("SELECT * FROM articles WHERE id = $id")->fetch(PDO::FETCH_ASSOC);
// 写入缓存,设置过期时间1小时
$redis->setex($key, 3600, json_encode($data));
} else {
$data = json_decode($data, true);
}缓存穿透: 查询一个不存在的数据,缓存永远不命中,请求都打到数据库。 解决方案:
- 缓存空值:查询结果为空也缓存,设置较短的过期时间
- 布隆过滤器:在缓存前加一层布隆过滤器,不存在的直接返回
// 缓存空值
$data = $db->query("SELECT * FROM articles WHERE id = $id")->fetch(PDO::FETCH_ASSOC);
if ($data) {
$redis->setex($key, 3600, json_encode($data));
} else {
// 缓存空值,过期时间短一些
$redis->setex($key, 60, json_encode(null));
}缓存雪崩: 大量缓存同时过期,请求都打到数据库。 解决方案:
- 过期时间加随机值:避免大量key同时过期
- 多级缓存:本地缓存 + Redis缓存
- 熔断降级:数据库压力大时返回默认值
// 过期时间加随机值,避免雪崩
$ttl = 3600 + rand(0, 300);
$redis->setex($key, $ttl, json_encode($data));缓存击穿: 一个热点key过期,大量并发请求打到数据库。 解决方案:
- 互斥锁:缓存未命中时加锁,只让一个请求去查数据库
- 逻辑过期:不设置TTL,数据中包含过期时间,过期后异步更新
// 互斥锁方案
$data = $redis->get($key);
if ($data === false) {
// 加锁
$lockKey = 'lock:' . $key;
$locked = $redis->set($lockKey, 1, ['nx', 'ex' => 10]);
if ($locked) {
// 获得锁,查询数据库
$data = $db->query("SELECT * FROM articles WHERE id = $id")->fetch(PDO::FETCH_ASSOC);
$redis->setex($key, 3600, json_encode($data));
// 释放锁
$redis->del($lockKey);
} else {
// 未获得锁,等待后重试
usleep(100000);
$data = $redis->get($key);
if ($data) $data = json_decode($data, true);
}
} else {
$data = json_decode($data, true);
}3. 消息队列
Redis的List结构可以用来做简单的消息队列,适用于异步任务、削峰填谷等场景。
生产者:
// 生产者:将任务推入队列
$redis->lPush('task_queue', json_encode([
'task' => 'send_email',
'data' => ['to' => 'user@example.com', 'subject' => 'Hello']
]));消费者:
// 消费者:从队列取出任务处理
while (true) {
// 阻塞式弹出,没有任务时等待
$task = $redis->brPop('task_queue', 30);
if ($task) {
$data = json_decode($task[1], true);
// 处理任务
if ($data['task'] === 'send_email') {
sendEmail($data['data']);
}
}
}延时队列: 使用Sorted Set实现延时队列,score为执行时间戳。
// 添加延时任务,10分钟后执行
$redis->zAdd('delay_queue', time() + 600, json_encode([
'task' => 'order_timeout',
'order_id' => 12345
]));
// 消费者轮询
while (true) {
// 查询到期的任务
$tasks = $redis->zRangeByScore('delay_queue', 0, time(), ['limit' => [0, 10]]);
foreach ($tasks as $task) {
// 删除任务(防止重复处理)
$removed = $redis->zRem('delay_queue', $task);
if ($removed) {
$data = json_decode($task, true);
// 处理任务
processTask($data);
}
}
sleep(1);
}4. 发布订阅
Redis支持发布订阅模式,适用于消息广播、实时通知等场景。
发布者:
// 发布消息到频道
$redis->publish('news_channel', json_encode([
'title' => '重大新闻',
'content' => '今天发生了一件大事...'
]));订阅者:
// 订阅频道,接收消息
$redis->subscribe(['news_channel'], function($redis, $channel, $message) {
$data = json_decode($message, true);
echo "收到消息:{$data['title']}\n";
// 处理消息
processNews($data);
});模式匹配订阅: 订阅匹配特定模式的频道。
// 订阅所有以news_开头的频道
$redis->psubscribe(['news_*'], function($redis, $pattern, $channel, $message) {
echo "频道 $channel 收到消息:$message\n";
});5. 分布式锁
在分布式系统中,需要分布式锁来保证数据一致性。Redis可以用来实现分布式锁。
加锁:
function lock($key, $timeout = 10) {
global $redis;
$value = uniqid(); // 唯一值,用于释放锁时验证
// SET key value NX EX timeout
$result = $redis->set($key, $value, ['nx', 'ex' => $timeout]);
if ($result) {
return $value; // 返回锁的值,用于释放锁
}
return false;
}释放锁:
function unlock($key, $value) {
global $redis;
// 使用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, [$key, $value], 1);
}使用示例:
$lockKey = 'lock:order:' . $orderId;
$lockValue = lock($lockKey, 10);
if ($lockValue) {
try {
// 执行业务逻辑
processOrder($orderId);
} finally {
// 释放锁
unlock($lockKey, $lockValue);
}
} else {
echo "系统繁忙,请稍后重试\n";
}6. 排行榜
使用Sorted Set实现排行榜,适用于热门文章、用户积分排行等场景。
// 增加文章阅读量
$redis->zIncrBy('article_ranking', 1, $articleId);
// 获取排行榜前10名
$ranking = $redis->zRevRange('article_ranking', 0, 9, true);
foreach ($ranking as $articleId => $views) {
echo "文章 $articleId 阅读量 $views\n";
}
// 获取某篇文章的排名
$rank = $redis->zRevRank('article_ranking', $articleId);
echo "文章排名:" . ($rank + 1) . "\n";7. Redis持久化
Redis支持两种持久化方式:RDB和AOF。
RDB(快照): 定时将内存数据快照保存到磁盘。
- 优点:文件小,恢复快
- 缺点:可能丢失最后一次快照后的数据
# redis.conf
save 900 1 # 900秒内至少1个key修改则保存
save 300 10 # 300秒内至少10个key修改则保存
save 60 10000 # 60秒内至少10000个key修改则保存
dbfilename dump.rdb
dir /var/lib/redisAOF(追加文件): 将每条写命令追加到文件。
- 优点:数据安全性高,最多丢失1秒数据
- 缺点:文件大,恢复慢
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # 每秒同步一次
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb建议: 生产环境同时开启RDB和AOF,兼顾性能和数据安全。
总结
Redis是一个功能强大的内存数据库,除了简单的缓存,还可以做消息队列、发布订阅、分布式锁、排行榜等。掌握Redis的高级用法,能让我们设计出更高性能、更可靠的系统。
希望这篇文章能帮助大家更好地理解和使用Redis。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录