💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## **Redis 是什么** Redis是一个使用 C编写的开源、支持网络、基于内存、分布式、可选持久性的键值对存储数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis 可以将数据写入磁盘中,保证了数据的安全不丢失,而且 Redis 的操作是原子性的。 ## **Redis 的优点** 1. **基于内存操作**,内存读写速度快。 2. Redis 是**单线程**的,避免线程切换开销及多线程的竞争问题。单线程是指网络请求使用一个线程来处理,即一个线程处理所有网络请求,Redis 运行时不止有一个线程,比如数据持久化的过程会另起线程。 3. **支持多种数据类型**,包括 String、Hash、List、Set、ZSet 等 4. **支持持久化**。Redis 支持 `RDB` 和 `AOF` 两种持久化机制,持久化功能可以有效地避免数据丢失问题。 5. **支持事务**。Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作合并后的原子性执行。 6. **支持主从复制**。主节点会自动将数据同步到从节点,可以进行读写分离。 ## **Redis 为什么这么快** * **基于内存**:Redis 是使用内存存储,没有磁盘IO上的开销。数据在内存中,读写速度快。 * **单线程实现(Redis 6.0 以前)**:Redis 使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。 * **IO多路复用模型**:Redis 采用I/O多路复用技术,使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O 上浪费过多时间。 * **高效的数据结构**:数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的; ## **Redis 为何选择单线程** * **避免过多的上下文切换开销**。程序始终运行在进程的单个线程中,没有多线程切换的场景 * **避免同步机制的开销**:如果 Redis 选择多线程模型,需要考虑数据同步的问题,则必然会引入某些同步机制,会导致在操作数据过程中带来更多的开销,增加程序复杂度的同时还会降低性能。 * ****实现简单,方便维护****:如果 Redis 使用多线程模式,那么所有的底层数据结构的设计都必须考虑线程安全问题,那么 Redis 的实现将会变得更加复杂。 > 单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。 ## **Memcached 和 Redis 的区别** 1. Redis 只使用**单核**,而 Memcached 可以使用多核。 2. MemCached 数据结构单一,仅用来缓存数据,而 **Redis 支持多种数据类型** 3. MemCached 不支持数据持久化,重启后数据会消失。**Redis 支持数据持久化** 4. **Redis 提供主从同步机制和 cluster 集群部署能力**,能够提供高可用服务。Memcached 没有提供原生的集群模式,需要依靠客户端实现往集群中分片写入数据。 5. **Redis 使用单线程的多路 IO 复用模型**,Memcached 使用多线程的非阻塞 IO 模型 ## **Redis 数据类型** ### 基本数据类型 1. **Sting**:可以是字符串、数字或者二进制,但值最大不能超过 512MB。 2. ** Hash** :一个键值对集合。 3. **Set**:无序去重的集合。Set 提供了交集、并集等方法,对于实现共同好友、共同关注等功能特别方便。 4. **List**:有序可重复的集合,底层是依赖双向链表实现的 5. **Sorted Set(ZSet)**:有序 Set。内部维护了一个 `score` 的参数来实现。适用于排行榜和带权重的消息队列等场景。 ### 特殊的数据类型 1. **Bitmap**:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存 0 或者 1,数组的下标在 Bitmap 中叫做偏移量。Bitmap 的长度与集合中元素个数无关,而是与基数的上限有关。 2. **Hyperloglog**:用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客 3. **Geospatial **:主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。 ## **Redis 事务** 事务的原理是将一个事务范围内的若干命令发送给 Redis,然后再让 Redis 依次执行这些命令。 一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程的中,会按 照顺序执行! 一次性、顺序性、排他性!执行一些列的命令! 事务的生命周期: 1. 使用`MULTI`开启一个事务 2. 开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真正执行 3. `EXEC`命令进行提交事务 一个事务范围内某个命令出错不会影响其他命令的执行,不保证原子性 ~~~ 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set a 1 QUEUED 127.0.0.1:6379(TX)> set b 1 2 QUEUED 127.0.0.1:6379(TX)> set c 3 QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) (error) ERR syntax error 3) OK 127.0.0.1:6379> keys * 1) "c" 2) "a" ~~~ ### WATCH 命令 `WATCH`命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行(类似于乐观锁)。执行`EXEC`命令之后,就会自动取消监控。 ~~~ 127.0.0.1:6379> watch name OK 127.0.0.1:6379> set name zhaosi OK 127.0.0.1:6379> get name "zhaosi" 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set name liuneng QUEUED 127.0.0.1:6379(TX)> exec (nil) 127.0.0.1:6379> get name "zhaosi" ~~~ ## **redis 内存淘汰机制** * **被动删除(惰性)**:在访问 key 时,如果发现 key 已经过期,那么会将 key 删除。 * **主动删除(定期)**:定时清理 key,每次清理会依次遍历所有 DB,从 db 随机取出 20 个 key,如果过期就删除,如果其中有 5 个 key 过期,那么就继续对这个 db 进行清理,否则开始清理下一个 db * **内存不够时清理**:Redis 有最大内存的限制,通过 maxmemory 参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存。 ## 内存淘汰策略 ~~~ # The default is: # # maxmemory-policy noeviction ~~~ 当 Redis 的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略,删除一些不常用的数据,以保证Redis 服务器正常运行。 Redis v4.0 之前的 6 中数据淘汰策略: * `volatile-lru`:LRU (Least Recently Used)最近使用。利用 LRU 算法移除设置了过期时间的 key。 * `allkeys-lru`:当内存不足以容纳新写入数据时,从数据集中移除最少使用的 key * `volatile-ttl`:从已设置过期时间的数据集中挑选将要过期的数据淘汰 *` volatile-random`:从已设置过期时间的数据集中任意选择数据淘汰 * `allkeys-random`:从数据集中任意选择数据淘汰 * `no-eviction`:禁止删除数据,当内训不足以容纳新写入数据时,新写入操作会报错 Redis v4.0之后增加: * `volatile-lfu`:LFU(Least Frequently Used)最少使用。从已设置过期时间的数据集中挑选最不经常使用的数据淘汰 * `allkeys-lfu`:从数据集中移除最不经常使用的 key ## **Redis 的持久化** * **RDB** 做全量持久化(Redis 默认的持久化方式)。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。对应产生的数据文件为 dump.rdb,通过配置文件中的 save 参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。) * **AOP** 增量持久化:Redis 会将每一个收到的写命令都通过 Write 函数追加到文件最后,类似于 MySQL 的 `binlog`。必要时,可以通过重新执行文件中保存的写命令来在内存中重建Redis 数据库。 Redis 本身的机制是:当 AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件。当 AOF 持久化关闭或不存在 AOF 文件时,加载 RDB 文件。加载 AOF/RDB 文件成功之后,Redis 启动成功。如果 AOF/RDB 文件存在错误,则 Redis 启动失败并打印错误信息 ## **简单介绍 Pipeline** 可以将多条命令一起打包发送至 redis 处理,处理完成后,将处理结果按照顺序打包,一起返回,好处是可以减少 I/O 次数,提升 redis 吞吐量。 **注意**:一个 pipeline 中的命令互相之间不能有因果关系。另外,一个 pipeline 中的命令不宜过多,不然数据量过大,增加客户端的等待时间,还可能造成网络阻塞。可以将大量命令的拆分多个小的 pipeline 命令完成。 ## **使用过 redis 做异步队列实现方式** 一般使用 list 结构做队列, `lpush` 生产消息, `rpop` 消费消息,当 `rpop` 没有消息的时候,适当的 `sleep` 一会儿再重试 ### 不用 sleep 使用 list 结构中的阻塞指令,`brpop`、`blpop`。在列表没有元素时,会阻塞列表直接等待超时或发现可弹出元素为止 ### 如果我想生产一次,消费多次呢? 可以使用 pub/sub 发布订阅模式 ,实现 1:N 的消息队列 ## **pub /sub 模式缺点** 在消费者下线的情况下,生产的消息会丢失。可以使用专业的消息队列保证消息不丢失,如 rocketMQ、rabbitMQ 等 ## **Redis 如何实现延时队列** 使用 `sortedset`,用时间戳作为 `score`,消息内容作为 key,生产者调用 zadd 生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的消息,轮询进行处理 ## **如果事务中有某条 / 某些命令执行失败了会怎么样呢?** 事务中的某条命令执行失败了,事务中的其他指令依然会继续执行。另外,Redis 事务不支持回滚 ## **假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如何将它们全部找出来?** 使用 keys 指令可以扫出指定模式的 key 列表。 ### 如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题 这时回答一个 Redis 关键性特性:redis 是**单线程**的。roolback keys 指令会导致线程阻塞一段时间,线上服务会停顿,知道指令执行完成,服务才能恢复。这时候可以使用 `scan` 命令,`scan` 命令可以无阻塞的提取出指定模式的 key列表,但是 key 可能会有一点重复,在客户端做一次去重就可以了。 scan 整体所花费的时间会比用 keys 指令长。 ## **MySQL 里有 2000w 数据,redis 中只能存 20w 的数据,如何保证 redis 中的数据都是热点数据?** 当客户端往 Redis 添加了新的数据后。Redis 会检查内存使用情况,如果大于 maxmemory 的限制,则根据设定好的**淘汰策略**对旧数据进行回收。 ## **怎么保证集群的高可用** Redis Sentinal(哨兵)在 master 宕机时会自动将 slave 提升为 master,继续提供服务。 Redis 集群没有使用一致性 hash, 而是引入了 哈希槽的概念。Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。 ## 单线程可以处理高并发请求吗? 可以。 并发并不是并行。并发性I/O流,意味着能够让一个计算单元来处理来自多个客户端的流请求。并行性是服务器能够同时执行几个事情,具有多个计算单元。 Redis 是用“单线程-多路复用I/O模型”来实现高性能的内存数据服务的,这种机制避免了使用锁,但同时这种机制在进行耗时命令时会使 redis 并发下降,因为是单一线程,所以同一时刻只能有一个操作进行,耗时的命令会导致并发下降,不只是读并发,写并发也会下降。 单一线程也只能用到一个 CPU 核心,所以可以在同一个多核服务器中,启动多个实例,组成`master-master`或者 `master-slave` 的形式,耗时的读命令可以完全在slave进行。 *