Redis 合集
为什么使用Redis?(好处)
- 首先, 传统的关系型数据库 MySQL 现在已经不能很好的应对所有的业务场景了, 比如秒杀的库存减少、网站首页的访问流量高峰等等, 很容易让 MySQL 数据库崩掉, 索引引入了缓存中间件;
- 目前, 市面上流行的缓存中间件有 Redis 和 Memcached, 综合考虑他们的优缺点, 选择了 Redis;
Redis和Memcached有什么区别?
- 存储方式 Memecache 把数据全部存在内存之中, 断电后会挂掉, 数据不能超过内存大小, Redis 有部份存在硬盘上, 这样能保证数据的持久性;
- 数据支持类型 Memcache 对数据类型支持相对简单, Redis 有复杂的数据类型;
- 使用底层模型不同, 它们之间底层实现方式以及与客户端之间通信的应用协议不一样, Redis 直接自己构建了 VM 机制, 因为一般的系统调用系统函数的话, 会浪费一定的时间去移动和请求;
Redis相比Memcached有哪些优势?
- Memcached 所有的值均是简单的字符串, Redis 作为其替代者, 支持更为丰富的数据类型;
- Redis 的速度比 Memcached 快很多;
- Redis 可以持久化其数据;
Redis的数据类型?
String、Hash、List、Set、Zset
Redis的BloomFilter?
概念: 布隆过滤器是一个很长的二进制向量和一系列的随机映射函数, 主要用来检索一个元素是否存在一个集合里, 优点是: 空间效率和查询时间都远远超过一般的算法, 缺点: 有一定的错误识别率和删除困难
原理: 当添加元素时, 通过 K 个散列函数将这个元素映射到一个位数组的 K 个点, 并且把它们都置为 1; 在检索时, 只需要查看这些点是不是都 1 即可, 如果这些点中任意一个点为 0, 则被检查元素一定不在, 如果都为 1, 则很有可能在;
缓存穿透:
- 现象: 用 redis 做缓存, 数据库 id 一般都是从 1 开始自增添加, 如果说我知道了你当前的接口是通过 id 来进行查询, 那么我传入一个负数, 这个时候 Redis 缓存中没有这个数据, 那么就会去数据库进行查询, 如果这样的请求有成千成万个, 就会发生缓存穿透;
- 解决: 使用布隆过滤器就可以避免缓存穿透, 我们先把数据库中的数据加载到过滤器中, 比如, 数据库 id 有 1、2、3, 那么我们通过 K 次 hash 后把位数组中对应的 k 个点置为 1, 后续查询时, 我们把查询的 + 也进行 K 次 hash, 然后去对比, 如果完全一样, 那就存在, 如果不存在, 那么直接返回空即可;
缺点:
- 存在误判: 由于 hash 后的点都在同一个二进制向量中, 所以存在当前查询元素不存在集合中, 但是对应的 K 个点都为 1 这种情况出现, 可以通过设置白名单或者黑名单的方式存储有可能存在误判的元素
- 删除困难: 因为存入的元素都在同一个二进制向量中, 所以删除时不能简单的将对应的 K 个点置为 0, 可能会存在影响其他元素的判断, 可采用 Counting Bloom Filter;
实现:
- 在使用 bloom filter 时, 绕不过的两点是预估数据量 n 以及期望的误判率 fpp;
- 在实现 bloom filter 时, 绕不过的两点就是 hash 函数的选取以及 bit 数组的大小;
如果有大量的Key需要设置同一时间过期,需要注意什么?
- 如果有大量的 key 的过期时间过于集中, 那么在过期的时间点, Redis 会存在卡顿现象
- 如果严重的话, 可能会导致缓存雪崩, 我们一般需要给过期时间加一个随机值, 使过期时间分散一点
- 例: 电商首页经常会使用定时任务刷新缓存, 可能大量的数据失效时间都十分集中, 如果失效时间一样, 又刚好在失效的时间点大量用户涌入, 就有可能造成缓存雪崩
如果解决缓存雪崩?
- 避免对 key 设置同样的过期时间: 在设置 key 过期时间时, 加上随机时间, 使 key 的过期时间分散一点
- 接口限流: 对非核心数据进行一个接口限流, 比如原来可以 1 秒中接受 1000 个请求, 那么我们可以设置 1 秒钟只能接受 500 个请求, 降低缓存的压力
Redis的key扫描指令?
- Keys: 使用 Keys pattern 扫描指定模式的所有 key, 由于 redis 是单线程的, keys 指令会导致线程阻塞一段时间, 直到指令执行完毕, 那么就会存在线上服务卡顿的问题;
- Scan: 那么我们可以使用 Scan 指令无阻塞的提取出指定模式的 key 列表, 但是会一定的重复概率, 可以在客户端做一次去重[花费时间会比 keys 指令要长];
如何使用Redis做异步队列?
一般使用 list 结构做队列, rpush 生产消息, lpop 消费消息, 当 lpop 没有消息时, 要适当的 sleep 一会再重新尝试;
如果不想 sleep, 我们可以使用 blpop 指令, 它在没有消息时, 会进行阻塞知道有消息出现;
如果想实现生产一次, 消费对此, 可以使用 pub/sub 主题订阅模式, 实现 1:N 的消息队列;
当然使用 pub/sub 会存在一个生产者消息丢失问题, 当消费者下线时, 生产者的消息会丢失, 可以使用专业的消息队列如: RabbitMQ、RocketMQ 等;
如果使用Redis实现延迟队列?
可以使用 Zset, 那时间戳做 score, 消息内容作为 key 调用 zadd 生产消息, 消费者使用 zrangerbyscore 指令获取 n 秒之前的数据轮询进行处理
Redis如何持久化存储?
方式: RDB 存储、AOF 存储
RDB 存储方式:
- 工作原理: Redis 调用 fork() 函数, 产生一个子进程, 子进程把数据写入到一个临时的 rdb 文件, 当子进程写完新的 rdb 文件后, 再把旧的 rdb 文件替换掉
- 优缺点:
- 优点:
- rdb 是一个单文件, 保存的是某个时间点的 redis 数据, 适合用来备份数据, 可根据时间点进行 rdb 文件归档
- rdb 的性能很好, 持久化通过 fork 一个子进程来执行, 不影响自身的 I/O 操作
- 比起 AOF, 在数据量大的情况下, rdb 启动速度更快
- 缺点:
- 由于是间隔性进行数据持久化, 那么在 redis 宕机时, 未持久化的数据会丢失
- 如果数据量较大, 再用 fork 子进程进行持久化时, 会比较耗时, 造成 redis 服务停止服务几毫秒, 如果数据量过大且 CPU 性能不是很好的情况下, 服务停止可能能达到 1 秒
- 优点:
- 文件路径和名称: 通过 redis.conf 文件来配置
nginx# RDB文件名,默认为dump.rdb。 dbfilename dump.rdb # 文件存放的目录,AOF文件同样存放在此目录下。默认为当前工作目录。 dir ./AOF 存储方式:
- 工作原理: 每当 redis 接受数据修改命令操作时, 就会把命令追加到 aof 文件中, 当重启 redis 的时候, aof 里的命令会重新执行一遍, 重建数据
- 优缺点:
- 优点:
- 比 rdb 更加可靠, 可以指定不同的同步策略: 不进行 fsync、每秒进行 fsync、每次查询进行 fsync, 默认是每秒 fsync, 最多只丢失 1 秒数据
- aof 文件是一个纯追加的文件, 即使出现断电等意外情况也不会出现文件损坏问题
- 当文件过大时, 文件会进行重写, 过程很安全, 旧文件保留继续使用, 在新文件写入重建当前数据的最小操作命令的集合
- 数据可恢复, 因为存储的命令, 所以即使使用了 flushall 命令, 只需要清除 aof 文件的 flushall 命令, 然后重启 redis 数据就会恢复
- 缺点:
- 在相同数据集下, aof 文件一般会比 rdb 文件大
- 在某一些 fsync 策略下, aof 的速度会比 rdb 慢
- 优点:
- 文件路径和名称: 通过 redis.conf 文件来配置
nginx# 启动 aof appendonly yes # 文件存放目录,与RDB共用。默认为当前工作目录。 dir ./ # 默认文件名为appendonly.aof appendfilename "appendonly.aof"持久化数据加载流程:
1. redis 启动 -> 判断是否开启 AOF ? 如果没有, 就判断是否存在 rdb 文件?
2. 如果开启 -> 判断是否存在 AOF 文件? 如果没有, 就判断是否存在 rdb 文件?
3. 如果存在 -> 尝试加载 AOF 文件? 如果加载成功, 表示启动成功, 否则, 启动失败
4. 如果 rdb 文件存在 -> 尝试加载 rdb 文件? 加载成功, 启动, 否则, 失败
5. 如果 rdb 文件不存在 -> 启动成功管道请求模型(Pipeline)是什么?
- redis 的服务端和客户端交互:
- Redis 是基于一个 request(请求)和一个 response(响应)的同步方式, 正常情况下, 客户端发送一个命令, 等待 redis 服务器返回结果, redis 服务器接受到命令, 处理完成后响应给客户端;
- 无论网络如何延迟, 数据包总能从客户端发送到服务端, 并从服务端返回数据到客户端, 这个时间被称为 RTT(Round Trip Time 往返时间), 如果需要执行大量的命令, 则需要等待上一条命令执行完毕之后才能执行下一条命令, 那么这个之间它不仅会有 RTT 存在, 还会频繁调用系统 I/O, 发送网络请求, 并且还需要 redis 多次调用的 write() 和 read() 系统方法,
- 普通请求模型:
- 由于通信之间存在网络延迟, 假设客户端重服务端一次包传输时间为 0.125 ms, 那么我们执行 4 条命令就需要花费 1 秒才能处理完成;
- 那么如果我们的 redis 有每秒能处理 1000 条命令的能力, 那么我们的客户端 1 秒也只能发出 4 条命令, 很显然 redis 的性能并没有被我们充分利用;
- Pipeline请求模型:
- 那么管道就可以解决普通请求的问题, 管道可以一次发送多次命令给服务端, 服务端处理完毕之后, 通过一条响应一次性将所有的结果返回;
- 管道通过减少客户端和 redis 之间的通信次数来达到降低往返延迟时间的目的, 它的实现原理是队列, 我们知道队列它的特点就是先进先出, 因此它可以保证数据顺序一致;
- pipeline 的默认同步个数为 53 个, 也就是 arges 中数据累积到 53 条才会进行提交;
Pipeline有什么好处?为什么要使用Pipeline?
可以将多次 I/O 往返的时间压缩为一次, 前提是 pipeline 执行的指令之间没有关联性;
Redis的同步机制?
Redis 可以使用主从同步, 从从同步, 第一次做同步的时候, 主节点做一次 bgsave, 并同时将后续修改操作记录到内存 buffer, 待完成后将 RDB 全量同步到复制节点, 复制节点接受完成后将 RDB 镜像加载到内存, 加载完成后再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程, 后续的增量数据使用 AOF 日志进行同步即可;
Redis集群的高可用和原理?
Redis Sentinal 着眼于高可用, 在 master 宕机时会自动将 slave 提升为 master, 继续提供服务;
Redis Cluster 着眼于扩展性, 在单个 redis 内存不足时, 使用 Cluster 进行分片存储;