一、概述
1.1 什么是Redis?
- Redis 是一个使用 C 语言写成的,开源的高性能key-value非关系缓存数据库。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。Redis的数据都基于缓存的,所以很快,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。Redis也可以实现数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。
1.2 Redis是单线程的还是多线程的?
当我们在学习和使用redis的时候,大家口耳相传的,说的都是redis是单线程的。其实这种说法并不严谨,Redis的版本有非常多,3.x、4.x、6.x,版本不同架构也是不同的,不限定版本问是否单线程不太严谨。
1)版本3.x,最早版本,也就是大家口口相传的redis是单线程。
2)版本4.x,严格意义上来说也不是单线程,而是负责客户端处理请求的线程是单线程,但是开始加了点多线程的东西(异步删除)
3)最新版本的6.0.x后,告别了刻板印象中的单线程,而采用了一种全新的多线程来解决问题。
所以我们可以得出,Redis 6.0版本以后,对于整个redis来说,是多线程的。
Redis是单线程到多线程的演变:
在以前,Redis的网络IO和键值对的读写都是由一个线程来完成的,Redis的请求时包括获取(Socket读),解析,执行,内容返回(socket写)等都是由一个顺序串行的主线程处理,这就是所谓的单线程。
但此时,Redis的瓶颈就出现了:
I/O的读写本身是阻塞的,比如当socket中有数据的时候,Redis会通过调用先将数据从内核态空间拷贝到用户态空间,再交给Redis调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。
在Redis6.0中新增加了多线程的功能来提高I/O读写性能,他的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使多个socket的读写并行化了,采用I/O多路复用技术可以让单个线程高效处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的socket读取,请求解析,写入单独外包出去,剩下的命令执行任然是由主线程串行执行和内存的数据交互。
结合上图可知,网络IO操作就变成多线程化了,其他核心部分仍然是线程安全的,是个不错的折中办法。
流程图:
流程简述:
1)主线程负责接收建立链接请求,获取socket放入全局等待读取队列
2)主线程处理完读事件后,将这些连接分配给这些IO线程
3)主线程阻塞等待IO线程读取socket完毕
4)IO线程组收集各个socket输入进来的命令,收集完毕
5)主线解除阻塞,程执行命令,执行完毕后输出结果数据
6)主线程阻塞等待 IO 线程将数据回写 socket 完毕
7)解除绑定,清空等待队列
1.3 Lua 脚本的理解
- Lua 是一种轻量级的、可嵌入的脚本语言,常用于扩展应用程序的功能或嵌入到其他软件中。Redis 提供了对 Lua 脚本的支持,可以在 Redis 中执行 Lua 脚本。
- Lua 脚本可以被视为一系列 Redis 命令的集合,你可以编写一段 Lua 代码,包含一系列 Redis 命令,然后将这段代码传递给 Redis 执行。执行 Lua 脚本的好处是,Redis 将整个脚本作为一个原子操作执行,确保在执行期间不会被其他命令中断,保证了脚本的原子性。
- 在执行 Lua 脚本时,你可以使用 Redis 提供的特殊命令
EVAL
或EVALSHA
。EVAL
命令接受脚本代码作为参数,并在执行期间将其编译和执行。EVALSHA
命令与EVAL
类似,但是它接受一个 SHA1 哈希值作为参数,用于在 Redis 服务器中缓存和复用脚本。 - 在 Lua 脚本中,你可以使用 Redis 提供的特殊全局变量
redis
来执行 Redis 命令。例如,redis.call('GET', 'mykey')
将执行 Redis 的GET
命令来获取名为 ‘mykey’ 的键的值。 - 通过使用 Lua 脚本,你可以在 Redis 中实现复杂的原子操作,利用 Lua 语言的强大功能,以及 Redis 提供的丰富命令集。
1.4 如果 Redis 在执行异步删除操作期间发生故障或意外挂掉,你该怎么办?
-
恢复 Redis:首先,需要将 Redis 恢复到正常工作状态。这可能涉及重新启动 Redis 服务器或者进行其他相应的故障恢复措施。
-
检查异步删除状态:一旦 Redis 已经恢复,你可以检查异步删除操作的状态。Redis 提供了一些命令和指标来帮助你了解异步删除的进度。
- 使用
INFO
命令获取 Redis 的信息,并查看与内存相关的指标。你可以关注used_memory
(已使用内存)和used_memory_rss
(Redis 进程占用的物理内存)指标,以了解删除操作是否已释放相应的内存空间。 - 使用
MEMORY STATS
命令查看 Redis 的内存统计信息。其中的allocator_resident
字段表示 Redis 所占用的实际物理内存大小,可以用来评估删除操作的进度。
- 使用
-
手动触发异步删除:如果检查发现异步删除操作尚未完成,你可以手动触发 Redis 的异步删除机制,以继续释放未删除的数据占用的内存。可以使用
MEMORY PURGE
命令来立即启动异步删除线程,Redis 将开始异步地释放未删除的数据。MEMORY PURGE
这样,Redis 将开始尽快地释放未删除数据的内存,以完成之前的异步删除操作。
值得一提的是异步删除是基于 Redis 的内存管理策略和后台线程机制,它无法保证所有删除操作的完全执行。在出现 Redis 挂掉或故障的情况下,可能会有部分删除操作的进度丢失。因此,在对数据的持久性和可靠性要求较高的场景中,可能需要考虑其他备份、故障转移或数据恢复的机制来确保数据的完整性。
1.5 Redis有哪些优缺点?
- 优点
- 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
- 缺点
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
1.6 使用redis有哪些好处?
- (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都很低
- (2)支持丰富数据类型,支持string,list,set,sorted set,hash
- (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
1.7 为什么要用 Redis / 为什么要用缓存
主要从“高性能”和“高并发”这两点来看待这个问题。
- 高性能:
- 假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
- 高并发:
- 直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
1.8 为什么要用 Redis 而不用 map/guava 做缓存?
- 缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
- 使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
1.9 Redis为什么这么快
- 1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
- 2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
- 3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 4、使用多路 I/O 复用模型,非阻塞 IO;
- 5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
1.10 Redis的瓶颈在哪里?
说了这么多redis的优点,redis的性能瓶颈到底在哪里?
从cpu上看:
1)redis是基于内存的,因此减少了cpu将数据从磁盘复制到内存的时间
2)redis是单线程的,因此减少了多线程切换和恢复上下文的时间
3)redis是单线程的,因此多核cpu和单核cpu对于redis来说没有太大影响,单个线程的执行使用一个cpu即可
综上所述,redis并没有太多受到cpu的限制,所以cpu大概率不会成为redis的瓶颈。
而内存大小和网络IO才有可能是redis的瓶颈。
所以Redis使用了I/O多路复用模型,来优化redis。
1.11 Redis有哪些数据类型
- Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求
数据类型 | 可以存储的值 | 操作 | 应用场景 |
---|---|---|---|
String | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作 对整数和浮点数执行自增或者自减操作 | 做简单的键值对缓存 |
List | 列表 | 从两端压入或者弹出元素 对单个或者多个元素进行修剪, 只保留一个范围内的元素 | 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据 |
Set | 无序集合 | 添加、获取、移除单个元素 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素 | 交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集 |
Hash | 包含键值对的无序散列表 | 添加、获取、移除单个键值对 获取所有键值对 检查某个键是否存在 | 结构化的数据,比如一个对象 |
ZSet | 有序集合 | 添加、获取、删除元素 根据分值范围或者成员来获取元素 计算一个键的排名 | 去重但可以排序,如获取排名前几名的用户 |
1.12 Redis的应用场景
-
计数器
可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。
-
缓存
将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
-
会话缓存
可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。
-
全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
-
查找表
例如 DNS 记录就很适合使用 Redis 进行存储。查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。
-
消息队列(发布/订阅功能)
List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息。不过最好使用 Kafka、RabbitMQ 等消息中间件。
-
分布式锁实现
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
-
其它
Set 可以实现交集、并集等操作,从而实现共同好友等功能。ZSet 可以实现有序性操作,从而实现排行榜等功能。
1.13 持久化
- 什么是Redis持久化? 持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis 的持久化机制是什么?各自的优缺点?
- Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:
RDB:是Redis DataBase缩写快照
- RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
-
优点:
1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
-
缺点:
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
AOF:持久化:
- AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
- 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复