Redis

Redis

目录

持久化

持久化就是把内存的数据写到磁盘中,防止服务器宕机后丢失数据

RDB(默认)

按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件中,对应产生的数据文件为dump.rdb

优点:

加载备份文件很快

每个rdb文件都是完整的备份文件,占用空间小

Rdb恢复比aof快

弊端:

重启时可能会丢失最后一次备份后的所有修改

保存快照时如果数据过大,对系统性能有影响

AOF

Redis将接收到的每一个写命令通过Write函数追加到文件末,类似于Mysql的binlog。

当Redis重启时会通过重新执行文件中的写命令重建整个数据库的内容

优点:

  • 控制写入频率,例如每秒写入一次或每次修改都写入
  • 即使写入过程中系统奔溃,也不会像rbd有文件损坏的风险
  • 可以指定从aof哪个点开始恢复

弊端:

  • 相同数据变动情况下,aof文件比rdb大
  • Aof恢复速度比rdb慢

RDB与AOF的区别

  1. AOF文件比RDB更新频率高,优先使用AOF还原数据
  2. AOF比RDB更安全也更占用硬盘内存
  3. RDB的性能比AOF好
  4. 如果两种方式都配置则优先AOF

数据类型

String字符串

最基础的数据类型

使用场景:缓存、计数器、共享Session、限速

Hash哈希

键值对结构

使用场景:用户信息、订单信息

List列表

用来储存多个有序的字符串,列表中每个字符串称为元素。可以从队列的两端插入和取出,还可以获取指定范围的元素列表、获取执行索引的元素等

使用场景:push以及pop实现阻塞队列

Set集合

用来保存多个字符串的元素,与列表不同的是不允许存在重复的元素,并且元素是无序的。支持增删改查,还支持多个集合取交集、并集和差集。

使用场景:共同好友、共同爱好、相同标签

ZSET有序集合

保留集合中不允许重复元素的特性,但是元素是可以排序的,与列表使用索引下标为排序不同,可给每个元素设置分数作为排序依据

使用场景:排行榜

缓存击穿

缓存穿透是指查询不存在的数据导致请求直接落到数据库上,缓存无法命中,如果恶意攻击或者频繁访问不存在的缓存,将给数据库带来极大压力。

解决策略

空对象缓存:即使数据不存在,也将空结果缓存,并且设置较短的过期时间
布隆过滤器:在缓存层之前增加布隆过滤器,将所有可能存在的数据哈希到一个足够大的位数组中,查询前先判断布隆,如果不存在则直接返回

缓存雪崩

在某个时间点,缓存中大量的或者全部数据过期失效,导致所有请求都落到数据库上,造成数据库瞬时访问压力过大,甚至导致数据库崩溃

解决策略

  1. 设置不同的过期时间,避免大量数据同时过期
  2. 对于某些热点数据,可设置为永不过期,通过程序定期更新缓存

缓存击穿

对于某个热点key大量并发访问,当这个key在缓存中过期的瞬间,持续的大量请求落到数据库上,导致数据库的瞬时压力骤增

解决策略

  1. 设置热点缓存永不过期,通过程序定期更新
  2. 加锁或者队列:当缓存失效时,通过锁或者队列的方式,保证只有一个请求去数据库查询数据并回填缓存,其他请求则等待缓存回填后再访问

消息队列

List

通过Lpush/Rpush生产消息,通过BLPOP/BRPOP消费消息

发布Pub/订阅Sub

发布者向一个频道(channel)发布消息,所有订阅该频道的订阅者都将接受到消息

发布者示例:

publish channel “hello world”

订阅者示例:

Subscribe channel

流(Streams)

            // XADD key ID field value [field value ...]
            // '*' 表示让 Redis 自动生成唯一 ID (时间戳-序列号)
            $messageId = $this->redis->xAdd(
                $streamName, 
                '*', 
                $data
            );
//创建消费者组,同个组内的消费者进程只有其中一个能接收到消息
$this->redis->xGroup('CREATE', $this->streamName, $this->groupName, '$', true);
// 2. 阻塞读取消息
// XREADGROUP GROUP group consumer COUNT count BLOCK milliseconds STREAMS key id
// id 为 '>' 表示只读取新消息
$messages = $this->redis->xReadGroup(
    $this->groupName,
    $this->consumerName,
    [$this->streamName => '>'], // 读取新消息
    10, // 每次最多读取条数
    $this->blockTime // 阻塞时间 ms
);

// 3. 处理消息
// $messages 结构: ['streamName' => [['id' => '...', 'data' => [...]], ...]]
if (isset($messages[$this->streamName])) {
    foreach ($messages[$this->streamName] as $item) {
        $msgId = $item[0]; // 消息 ID
        $data = $item[1];  // 消息内容 (关联数组)
        //业务逻辑
        $success = handle($data);
        if ($success) {
            // 4. 消息确认 (ACK)
            $this->redis->xAck($this->streamName, $this->groupName, $msgId);
            $output->writeln("Message ACKed: {$msgId}");
        } else {
            // 业务处理失败策略:
            // 方案 A: 不 ACK,消息会留在 PEL (Pending Entries List),稍后可由 XCLAIM 重新分配
            // 方案 B: 记录日志并 ACK (丢弃消息,视业务重要性而定)
           
        }
    }
 }

Lua

使用示例

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 定义脚本
$script = <<<LUA
    //增加次数
    local current = redis.call('INCR', KEYS[1])
    if current == 1 then
        //设置过期时间
        redis.call('EXPIRE', KEYS[1], ARGV[1])
    end
    //判断次数是否超出
    if current > tonumber(ARGV[2]) then
        return 0
    else
        return 1
    end
LUA;

$key = 'limit:api:login';
$expire = 60; // 60秒窗口
$limit = 5;   // 最多5次

// 先加载脚本获取 SHA,再调用 EVALSHA (生产环境)
$sha = $redis->script('load', $script);
// 执行 (参数:sha, keys数组, args数组)
// phpredis 的 evalSha 签名: evalSha($sha, $args, $numKeys)
// 这里的 $args 需要把 KEYS 和 ARGV 合并,前 $numKeys 个是 KEYS
$args = [$key, $expire, $limit]; 
$numKeys = 1;

$result = $redis->evalSha($sha, $args, $numKeys);

if ($result === 1) {
    echo "请求允许";
} else {
    echo "请求被限流";
}
?>

有事务为什么使用Lua

MULTI 不支持回滚(中间错了继续执行),不支持逻辑判断;Lua 支持逻辑判断且真正原子。复杂逻辑首选 Lua。

posted @ 2026-06-07 17:14  Myifb  阅读(12)  评论(0)    收藏  举报