Skip to the content.

Redis

常见问题

Redis 做异步队列

使用 list 来做异步队列, rpush生产消息, lpop消费消息, 缺点在于消费者挂掉时, 消息会丢失, 所以推荐使用 rabbitMQ 等专业队列. 当没有消息时, 需要sleep一段时间, 或者使用 blpop 在没有消息时, 会一直阻塞住. 还可以通过 sub/pub主题订阅模式, 达到一个消息多次消费的效果. 消费者下线后, 消息也会丢失.

Redis 查大量数据

假设Redis中有1亿数据, 其中有10w条数据key的前缀是相同的, 如何查阅这些数据? 由于redis是单线程的, 当有业务在运行时, 直接使用keys命令会导致一段时间的不可用, 所以推荐使用 scan 命令, 虽然会查出来一定的重复key, 但是可以在客户端去重即可, 这样对生产的影响会降低.

Redis 有哪些数据结构

String

字符串, 单个key最大储存 512M

List

列表

Hash

哈希

Set

无须集合

Sort Set

有序集合

Pub/Sub

订阅消费者模式

Geo

储存地理位置, 可以计算两个地理点的3D距离

HyperLogLog

基数统计算法, 是一种概率算法, 用来统计大量的数据的统计结果, 并不储存具体的键. 在 redis 中, 只需要 12k 内存就可以储存理论上接近 2^64 个不同元素的基数. 在储存的元素数量或者体积非常大时, 使用的空间总是固定的, 并且是很小的.

应用: 一般用于统计注册IP数, 每日页面访问数等

包含操作: PFADD, PFCOUNT, PFMERGE

Redis 数据淘汰策略

系统默认 no-eviction

  1. voltile-lru 在设置了过期时间的数据中, 淘汰最近最少使用的数据
  2. voltile-ttl 淘汰将设置了过期时间的数据, ttl大的优先淘汰 (即最接近过期的)
  3. voltile-random 随机淘汰设置了过期时间的数据
  4. allkeys-lru 淘汰最近最少使用的数据
  5. allkeys-random 任意选择淘汰
  6. no-eviction 禁止淘汰, 当内存不足时写入数据, 会返回错误

Redis 三种淘汰机制

  1. LRU (Least recently used 最近最少使用)
  2. TTL
  3. Random

Redis 订阅发布机制

两种订阅模式:

  1. channel 频道订阅模式, 例如订阅了 A 频道, 则 A 频道发布消息时, 订阅者都可以收到
  2. pattern glob-style 模式, 及匹配模式, 例如订阅了 *.news, China.news, America.news 发布消息时, 订阅了这个频道的人都会收到

Redis 性能优化

  1. master 最好不做持久化工作, 交给 slave 来做
  2. 为了主从复制的速度和连接的稳定性, master 和 slave 最好在同一个局域网内
  3. 尽量避免在压力大的主库上增加从库
  4. 主从复制尽量不采用网状结构, 而是线性结构, master->slave1->slave2->…

缓存与数据库数据不一致怎么办

假设使用的主存分离, 读写分离的数据库.

发生的可能性:

  1. 主库更新数据, 主库到从库的同步未完成, 从库读取数据, 未读到最新数据, 而更新了缓存

解决方案: 在从库接收到数据更新操作时, 淘汰掉这条数据的缓存

解决方案:

  1. 在数据性一致性要求不高时, 忽略掉这个数据的不一致
  2. 延时双删策略
    1. 先删除缓存
    2. 再写数据库
    3. 休眠500毫秒
    4. 再次删除缓存
    5. 设置缓存过期时间
  3. 异步更新缓存(基于订阅binlog的同步机制)
    1. Redis订阅 mysql binlog消息
    2. 依据消息来进行相关操作

Redis 缓存穿透

起因: 恶意请求故意大量查询不存在的key, 让请求到达MySQL, 对后端造成很大压力 解决方案: 对不存在的key也做有有效期的缓存; 对存在 key 做一个 布隆过滤, 不存在于过滤器中的数据直接返回空;

Redis 缓存雪崩

起因: 大量的缓存在同一时间段失效, 导致后端压力大. 解决方案: 对缓存的有效时间使用不同的过期时间; 做二级缓存;

缓存热点 key 重建

在缓存失效瞬间, 有大量的线程来重建缓存, 造成后端负载压力大, 甚至可能让应用崩溃

解决方案:

  1. 互斥锁

多进程获取到锁的可以更新缓存, 其它线程等待. 由于建立缓存是个幂等操作, 所以可以在一两次获取不到锁时, 可以直接绕过互斥锁

  1. 永远不过期

Redis 分布式锁

为什么要分布式锁

为了确保在多个线程中, 多服务器中执行任务时, 能够达到一致性

Redis 为什么可以做分布式锁

Redis 是单线程的(网络请求模块使用了一个线程, 所以不需要考虑并发性), 即一个线程处理所有网络请求, 其它模块仍用了多个线程.

如何用

使用 setnx key value即加锁, 其它线程再来设置会返回false del key 释放锁

解决死锁

  1. Redis控制: 使用 setnx key value 后, 立即使用 expire key timeout 设置有效期.
  2. 其它服务器控制: 通过 value 设置为失效时的时间戳(比如当前+1s), 其它服务器在获取锁时发现锁还在, 且超过了有效期, 就直接来释放锁, 在释放锁这个过程, 需要使用 GETSET key value 来操作, 直接对这个key进行getset, 看返回值如果是过期的, 说明拿到了锁, 反之拿失败了. 拿失败的情况下, 需要放弃后续的操作了

缺陷

当master上加了锁, 还未同步到 slave 时, master down了, 这个时候 slave 成为了 master, 其中并没有锁, 这个时候就会出现多个客户端同时拿到锁的问题.

锁延期机制 (watch dog)

当客户端超过了key的生存时间还在操作, 想要继续有这个锁, 那么可以使用看门狗程序, 在生存时间内会定时查这个锁是否还在, 还在的话就延期

Redis 做内存优化

  1. 尽可能使用散列表

Redis key 过期时间和永久有效设置

设置过期时间 EXPIRE key seconds 设置永久有效 PERSIST key

Redis 事务

Redis事务保证了命令的打包操作, 其中的一个命令失败不会回滚, 也不会影响下一个命令的执行, 只是打包操作了, 保证了在执行过程中, 不会有其它命令的插入

使用 MULTI 开始一个事务 使用 EXEC 执行事务

Redis 管道

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务器, 而不用等待回复, 最后在一个步骤中读取该答复.

Redis 为什么把数据都放在内存中

为了达到最快的读写速度, 并通过异步的方式将数据写入磁盘.

磁盘 I/O 会严重影响 redis 的性能

(2) Redis 的了解

Redis 是一款高性能的缓存储存系统, 支持多种数据格式, 能够持久化, 分布式

(4) Redis 持久化有哪几种方式? 怎么选?

Redis 内存淘汰策略, ttl 指令底层实现

Redis 主从同步是什么的过程? 新增加从库的步骤?

Redis 的 zset 怎么实现?

redis key 的过期策略

hashmap 是怎么实现的?

(2) Redis 哨兵和集群

(4) Redis 底层数据结构

(4) Redis 数据类型对应命令

Redis 如何实现高可用?

zset 延时队列怎么实现的

zset 天然有序, 使用 score 来储存时间戳, 设置定时器定时查询 zset (zrangebyscore), 获取到可以执行的元素, 然后执行

可以通过 zrem 命令来保证获取的原子性

zset做排行榜时, 如果要实现相同分数时按照时间顺序排序怎么实现?

在分数上加入时间戳, 计算公式为: 带时间戳的分数 = 实际分数 * 10000000000 + (9999999999 - timestamp)

这个思路是使用 分数位+ (9999999999 - 10位时间戳)

(3) redis单线程多线程? 原因? 如何实现高效?

多路复用

Linux下的select、poll和epoll就是干这个的。目前最先进的就是epoll。将用户socket对应的fd注册进epoll,然后epoll帮你监听那些socket上有消息到达,这样就避免了大量的无用操作。此时的socket采用非阻塞的模式。这样,这个过程只在调用epoll的时候才会阻塞,收发客户端消息是不会阻塞的,整个进程或线程就被充分利用起来,这也就是事件驱动。

redis能否当消息队列? 用过哪些中间件消息队列? 有什么不同?

Redis 消息推送 (基于 pub/sub)并不可靠, 断电就丢失

Redis list 有持久化, 但是功能太少, 也不完全可靠, 没有消息确认机制

RabbitMQ 有持久化, 有确认机制, 可以保证消息的生产和消费, 支持事务, 支持交换机, 路由键, 等功能, 有管理界面, 支持主从

Redis 作为消息队列的可靠性如何保证

增加 ack 机制, 消费端提供消费反馈

redis 订阅发布功能

Redis 通过 publish, subscribe 等命令实现订阅与发布模式, 分为两种通信机制

2核CPU4G内存使用Redis最大QPS是多少?

可以利用 Redis 单线程的特性, 启动两个 Redis 实例, 一般一个 Redis 实例的 QPS 在几万左右, 双实例大概可以翻倍.

Redis连接时的connect与pconnect的区别

connect 在脚本结束后就会释放连接

pconnect 在脚本结束后会被脚本的管理程序(php-fpm)储存起来, 再连接时会直接用, 可以减少连接次数

Redis key和value的大小限制

均为 512M

setnx 分布式锁实现

什么是分布式锁

分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁表现

分布式锁条件

分布式锁的主要实现

单机 Redis 分布式锁

Redis 各种类型的使用场景

缓存的热点 Key 怎么处理?

什么是热点 KEY

  1. 单一 key 在突发事件中访问量突增, 会对单一的 Server 造成很很大压力, 超过 Server 极限时, 就导致了热点 Key 问题的产生

怎么解决

  1. 服务端 缓存: 即将数据缓存至服务端的内存中
    • 如何保证 Redis 和 服务端热点 Key 的数据一致性: 使用 Redis 自带的消息通知机制
  2. 备份热点 Key: 即将热点 Key + 随机数, 随机分配至 Redis 其它节点中, 这样访问热点 key 的时候就会分配压力到其它节点l
    • Redis 集群中包含 16384 个哈希槽, 集群使用公式 CRC16(key) % 16384 来定位 key 属于哪个槽, 那么只需要在 key 后加随机数 来储存和访问即可平均分布到不同的机器上了
如何找到热点 Key

redis keys 命令有什么缺点?

有性能问题, Redis 单线程的, keys 指令会使服务阻塞一段时间. 可以使用 scan 命令来代替, 但是获取出来的值能可能有一定的重复, 客户端去重一次就可以了. scan 命令可以理解为分页式的向客户端返回数据, 因为在分页返回的过程中, 并不能保证数据不变, 在数据改变后, 返回的分页数据也就不能确保一致了

Bloom Filter 是什么

如果redis作为分布式锁的时候,主节点挂掉了,但是数据还没有同步到从节点,这种情况怎么办?

由于 Redis 的主从复制是异步进行的, 可能会造成多个客户端获取到了锁

可以使用 RedLock 算法来实现分布式锁服务, 主节点 cash 后, 从节点顶替主节点, 需要等待一个锁超时时间后在替代.

Redlock 算法

申请锁

起 5 个 master 节点 (奇数个), 分布在不同的机房尽量保证可用性. 为了获取锁, client 会进行如下操作:

  1. 得到当前的时间, 微秒单位
  2. 尝试在5个实例上申请锁, 当然是用的是相同的 key 和 random value, 这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小, 避免长时间和一个 fail 了的节点浪费时间
  3. 当 client 在大于 3 个 master 上成功申请到锁时, 且它会计算申请锁消耗了多少时间, 这个时间由 获得锁的时间减去第一步的时间 得到, 如果锁的持续时长(lock validity time) 比流逝的时间多的话, 那么锁就真正获取到了
  4. 如果锁申请到了, 那么锁真正的 lock validity time 应该是 origin (lock validity time) - 申请锁流逝的时间
  5. 如果锁申请失败了, 那么它就会在少部分申请成功锁的 master 节点上执行释放锁操作, 重置状态
失败重试

尽快释放掉以获取的 master 节点上的锁, 等待随机时间后再去重试获取锁

放锁

依次释放所有节点上的锁就可以了

性能, 崩溃恢复和 fsync
  1. 需要开启持久化, 当节点崩溃后需要恢复锁数据, 避免被当做新节点重新获取到锁
  2. fsnyc = 1秒时可能会发送 1 秒的数据丢失, 这种情况下可以在 master 恢复后, 等待锁的最大有效时间后再加入到集群中

加锁的时候什么时候选择本地锁,什么时候选择分布式锁?

Redis 4.0 有什么新特性

redis的lru策略

lru是如何移除和插入数据的?链表中存储的是什么数据,如果没有索引那还存储什么?

poll列表, 默认 16 个 key, 按照空闲时间排序, key 只有在 poll 不满, 或者空闲时间大于 poll 中最小的时才会进入 poll 中,然后从 poll 中选择空闲时间最大的 key 淘汰掉

Redis 出了问题解决步骤

rehash 过程?会主动 rehash 吗?

一致性哈希是什么?节点较少时数据分布不均匀怎么办?

为什么要有一致性哈希

单机储存容量有限, 到达上限后需要分多台服务器来储存, 在判断一个 key 应该储存到哪个服务器时, 就需要用 hash 的方式来判断了. 而普通的 hash 对于扩容性上让人不是很满意, 所以产生了一致性 hash.

为什么不能直接用 hash

当主机数量变化时, 所有缓存得重新 hash 存放, 或者当有一台主机出现故障, 这个时候需要故障转移, 在转移过程中服务需要中断一段时间.

什么是一致性哈希 (哈希环)

普通 hash 是对服务器数量进行取模

一致性 hash 是对 2^32 取模, 确认 key 的位置 然后对服务器(关键字, ip等) hash, 确定服务器的位置, key 在找服务器时, 会顺时针找下一个 node 的位置

当其中一个 node 失效, key 的储存和读取会被定位到下一个节点上 当增加一个 node 时, 只需要拆分下一个 node 的数据到这个node上, 只会影响到下一个节点

数据倾斜问题

当节点太少时, 会出现数据集中到某一个节点上的问题, 这个问题可以使用虚拟节点来增加总节点数, 使数据平均

lua 脚本的作用是什么?

嵌入到 redis 中执行, 可以高效的执行 check-set 这样的操作, 并且是原子性的操作. 一个脚本运行的时候, 中间不会有其它脚本或者 Redis 命令被执行.

hashtable 退化为 ziplist

hgetall 或者 hashtable 有很多 key 如何优化

跳表

网络模型

主从如何保持一致

为什么高性能

为什么单线程

因为 Redis 是基于内存的操作, CPU 不是 Redis 的瓶颈, Redis 的瓶颈最优可能的是内存的大小或者网络带宽. 而单线程容易实现, 并且资源消耗少, 所以使用单线程方案.