1.什么是Redis?
Redis是基于内存的数据库,它采用的是key-value这种存储形式,value提供了许多的数据结构(数据对象)例如string、list、set、zset、hash、bitmap、GEO、HyperLogLog等等,常用于缓存、消息队列、发布订阅、分布式锁等等场景。
2.Redis的特点有哪些?
数据结构多样
Redis采用的是key-value的存储模式,value中又分为许多的数据结构可以使用,例如
String
List
Set
Bset
Hash
高性能
因为数据存储在内存中,所以它的IO速度很快。
Redis采用单线程、异步IO,避免了多线程的同步问题。
每秒可以达到11万次的读,和8.1万次的写操作
高可用
主从结构,一般为一主多从,主从之间的一致性主要依靠全量同步、TCP连接后的同步、增量同步
支持持久化
Redis中提供多两种持久化方式
RDB快照
快照方式是将Redis内存存储的数据以二进制格式写入磁盘中
AOF追加日志(Append-only File)
AOF则通过追加记录Redis的操作名来来实现持久化
支持事务
Redis虽然支持事务但是不支持回滚。
执行multi会开启一个事务,执行exec会提交这个事务。
Redis中保持事务原子性是采用的WATCH机制
WATCH机制提供了一种乐观锁的机制,它可以去监视一个或者多个键。如果在事务执行过程中,其他事务修改了我所监视键的值,那么exec会失败,事务的命令不会执行。
但是Redis没有提供回滚机制,请看他下面的例子
MULTI
SET key1 "value1" # 1号操作
INCR key2 # 2号操作
SET key3 "value3" # 3号操作
EXEC
# 假设现在1号2号操作执行成功,但是三号操作由于自身的原因执行失败,并不是因为执行过程中被其他事务修改了,那么1号2号操作不会回滚
Redis事务的原子性解释
在 Redis 中,事务的原子性体现在以下两个方面:
- 命令的顺序执行:在
EXEC
命令执行时,事务中的所有命令会按照顺序依次执行,且不会被其他客户端的命令打断。 - 事务的整体执行:如果使用了
WATCH
命令监视某些键,并且在EXEC
之前这些键被其他客户端修改了,那么整个事务会被取消,所有命令都不会执行。
3.Redis和Memcached有什么区别?
共同点
两者都是基于内存的key-value形式的存储系统
不同点
value的数据结构上
Redis支持的数据结构更加丰富(string、list、set、bset、hash、bitmap、GEO等等)
而Memcached只支持最简单的key-value形式
持久化
Redis通过快照或者AFO的方式支持持久化(断电后内存消失,然后可以恢复)
而Memcached不支持持久化
集群模式
Redis原生支持集群模式
而Memcached没有原生的集群模式,需要依赖客户端来实现往集群中分片和写入数据
Redis支持发布订阅功能、Lua脚本、事务等等
4.为什么用Redis作为MySQL的缓存?
因为Redis具备高性能和高并发的特性
高性能
Redis是基于内存的存储系统,对内存的IO速度肯定比对磁盘的要快。我们把数据库中的一些数据放在Redis缓存中,那么下次再访问这些数据的时候,就不需要去磁盘中寻找了,提高了IO的速度
当MySQL中的数据改变的时候,需要同步到Redis中,这里会产生双鞋数据不一致的问题
高并发
单线程(多路IO复用)、非阻塞(IO
一个设备中Redis的一秒钟能处理的请求次数是10W以上,而MySQL只有1W,性能相差10倍。
所有Redis能承受的请求数量的远远大于MySQL的。
Redis采用单线程异步IO这句话包含了两个重要概念:单线程和异步IO。让我们逐一解析:
- 单线程:
- Redis的核心操作(如处理客户端请求、执行命令等)都在一个主线程中完成。
- 这意味着Redis不会为每个客户端连接创建一个新的线程。
- 单线程模型避免了多线程环境下的竞争条件和锁,简化了开发和调试。
- 异步IO:
- 尽管Redis是单线程的,但它使用异步IO模型来处理网络请求。
- 异步IO允许Redis在等待IO操作完成时继续处理其他请求,而不是阻塞等待。
- Redis使用事件循环(event loop)来管理多个连接,能够高效地处理并发请求。
结合起来的含义:
- Redis使用一个主线程来处理所有客户端请求。
- 这个主线程使用异步IO和事件循环来管理多个客户端连接。
- 当遇到IO操作时(如读写socket),Redis不会阻塞,而是继续处理其他请求。
- IO操作完成后,会触发相应的事件,Redis再在事件循环中处理这些完成的IO操作。
优势:
- 简单性:单线程模型易于理解和维护。
- 避免锁:不需要处理复杂的多线程同步问题。
- 高性能:异步IO允许Redis高效处理大量并发连接。
- 减少上下文切换:单线程减少了CPU在不同线程间切换的开销。
需要注意的是,虽然Redis的核心是单线程的,但在某些情况下(如持久化、集群通信等),Redis也会使用额外的线程来处理这些任务,以避免影响主线程的性能。
总的来说,"单线程异步IO"模型是Redis高性能的关键因素之一,使其能够在单个线程中高效处理大量并发请求。
5.Redis中数据类型以及使用场景是什么?
数据类型
String(字符串)、List(列表)、Set(集合)、Zset(有序集合)、Hash(哈希)、BitMap(位图)、GEO(经纬坐标)
String
a. 短链接服务
b. setnx分布式锁
c. 存储session,解决session不一致问题
d. incr操作+1 + 1 做流量控制
List
a. 消息队列
Set
a. 共同关注
b. 公众号推送
Zset
a. 排行榜
Hash
a. 存储用户信息
b. 存储配置信息
BitMap
a. 连续签到(一个月申请4B空间就可以)
GEO
a. 存储地理位置信息的场景,比如滴滴出行
6.String
两种底层数据结构
String类的底层数据结构有俩种
int
当存储的时long类型可以存下的数字的时候,String的底层数据结构就会选用int
SDS
但存储的时字符串的时候,或者时存储二进制数据(音视频等),String的底层会选用SDS
SDS(Simple Dynamics String)是简单动态字符串,由三部分组成
struct SDS{
long len; // 已使用的字节数量
long free; // 未使用的字节数量
char buf[]; // 字节数组
}
在存储字符串的时候,buf和C语言存储类似,最后又’\0’结尾,这是为了适配C语言函数
SDS和C语言的字符串相比又哪些优势?
a. 可以存储二进制数据
b. SDS获取长度的时间复杂度是O(1)
c. Redis的SDS的API(函数)是安全的,不会造成拼接字符串的时候缓冲区的溢出
三种编码方式
a. int 编码
当String存储的是数字的时候,会采用int编码,int作为底层数据结构,ptr指针的void*类型改为long类型
b. emstr编码
当String存储的是小字符串的时候,会采用emstr编码,SDS作为底层数据结构
emstr编码值需要调用一次内存分配函数(redis对象和SDS在一块连续的内存中),释放的时候也值需要调用一次内存释放函数
c. raw编码
当String存储的是大字符串的是,会采用raw编码,SDS作为底层数据结构
raw需要调用俩次内存分配函数(redis对象分配内存调用一次,SDS调用一次),释放的时候也是需要释放俩次
embstr编码的缺点
emstr虽然只需要调用一次内存分配函数,但是它存储的字符串实际上是只读的,对emstr编码的字符串做任何修改,都会由emstr类型转为raw类型
常见指令
应用场景
a. 存储session信息,解决session不一致问题
为什么产生session不一致问题?
首先session是用来保存客户端用户的会话(登录)状态的,但是如果采用的是分布式服务器或者集群服务器的话,就会产生session不一致的问题,我们把多个服务器的登录信息都存储到session中,就可以解决了
b. 存储数字的时候,可以做计数器或者流量控制,利用incr或者incrby
c. setnx不存在就插入返回1,存在就返回0,根据1和0实现分布式锁,解决接口的幂等性问题,但是需要设置过期时间
如果不设置过期时间的话,假设一方挂掉之前一直没释放锁,那么另外一方就会一直等待
d. 短链接服务
set www.dwz.cn www.baidu.com
get www.dwz.cn
> www.baidu.com
7.List
底层数据结构
压缩列表
当列表中元素个数小于512个,每个元素的大小小于64B的时候,Redis会使用压缩链表作为底层数据结构
压缩列表–> 存储元素时一段连续的内存空间,并且他存储每个结点的内容是上一个结点的长度和内容,不用存储指针,节约内存空间。
缺点:不能保存过多的元素,它和数组不一样,每个元素所占空间一样,直接随机访问,它是顺序去查找的,所以也不能保存过大的元素
双向链表
如果列表中的元素不满足上面的条件,则使用双向链表
在Redis3.2版本之后,List的底层数据结构只有quicklist了,替代了双向链表和压缩列表
quicklist的每个结点时一个压缩列表
常见指令
应用场景
a. 消息队列
rpush key value
lbpop key 阻塞式弹出元素
List作为消息队列有什么缺陷?
List 不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了无法被其它消费者再次消费。
要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,使得多个消费者可以消费同一条消息,但是 List 类型并不支持消费组的实现。
这就要说起 Redis 从 5.0 版本开始提供的 Stream 数据类型了,Stream 同样能够满足消息队列的三大需求,而且它还支持「消费组」形式的消息读取
8.Set
Set会对存储的元素进行去重操作,并且集合中的元素是无序的
底层数据结构
整数集合
如果元素都是整数且元素中的个数都小于512,Set会使用整数集合作为底层数据结构
哈希表
不满足上述条件,使用哈希表作为底层数据结构
常见指令
应用场景
set的特性有无序性、去重性、可交、可并
a. 点赞功能(去重性)
key 公众号
value 用户
b. 共同关注(交集)
c. 抽奖活动(去重性)
9.Zset
Zset数据类型是有序集合,会读集合中的元素按照权重进行排序,所以它是key-score-value的形式
底层数据结构
压缩列表
跳表
Redis7.0中,压缩列表数据结构已经废弃了,用listpack数据结构来实现
常见指令
应用场景
排行榜
10.BitMap
位图,是一串连续的二进制数据,里面元素要么是0要么是1
常见的指令
应用场景
a. 连续签到
假设一个游戏要连续签到一个月给奖励,那么我们就可以用一个4B(32位)的位图来统计是否连续签到
b. 判断用户登录状态
5000 万用户只需要 6 MB 的空间。
c. 千万级用户连续签到3天怎么设计
用天数作为key,用户的id作为value
千万级别用户是1e7b->1e6B–>1MB–>3MB也不太大
11.HyperLogLog
HyperLogLog数据类型可以提供不准确的去重计数
和Set的其中计数比起来HyperLogLog的优点在哪?
HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。
个不同元素的基在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。
缺点
HyperLogLog进行去重计数的时候会有误差,大概0.81%
常用指令
网页UV计数是什么意思?
Unique Visitor
独立访客的数量
网页UV计数中的“UV”代表“Unique Visitor”,即“独立访客”。网页UV计数指的是在一定时间段内访问该网页的独立访客的数量。每个访客在该时间段内只计入一次,不论他们访问了该网页多少次。
举个例子,如果一个网站在一天内有100个不同的用户访问,那么该网页的UV计数就是100。即使这些用户中的某些人访问了多次,也只会计入一次UV。
UV计数是衡量网站流量和用户覆盖范围的一个重要指标。它帮助网站管理员了解有多少不同的用户访问了网站,而不仅仅是总的访问次数(这通常用PV,即Page View表示)。
使用场景
百万级别网页UV计数
12.GEO
应用场景
嘀嘀打车
存储经纬坐标
常见指令
13.Redis是单线程吗?
是的
Redis的单线程指的是 服务器接收客户端请求–>解析请求–>进行数据的读写操作–>发送数据给客户端都是一个线程来完成的
14.Redis为什么单线程还可以实现10W/s的吞吐量?
Redis是基于内存的存储系统
单线程避免了线程之间的互相竞争
Redis采用了多路IO复用机制来处理大量的客户端socket请求
15.如何避免缓存雪崩?
什么是缓存雪崩?
由于Redis缓存中的大量数据在同一时间过期或者Redis宕机,导致大量的请求直接访问MySQL数据库,导致数据库的压力骤增,延展可能会造成数据库宕机,这就是缓存雪崩
如何避免?
将缓存的过期时间打散
做流量控制,熔断机制(比如用string的incr做流量控制)
提高Redis和MySQL的容灾(做集群或者分布式)
缓存预热
16.如何避免缓存击穿?
缓存击穿通常是要访问缓存中的热点数据,但是这个热点数据过期了,导致大量的请求落在数据库上,数据库压力剧增,可能会宕机
你可以认为缓存击穿是缓存雪崩的子情况
不设置过期时间
互斥锁方案,利用redis中的setnx也就是分布式锁
假设现在又多个线程同时向访问缓存中的热点数据,但是该过期了,所以得去数据库中查找,但是如果加了分布式锁的话,同一时刻是能有一个线程去访问数据库,找到热点数据之后,立马更新到缓存中,那么其他的线程就不用去访问数据库了,因为缓存有已经有我想要的数据了
但是它也有缺点,就是会导致redis的性能变差
17.如何避免缓存穿透?
穿透和击穿的区别在哪?
击穿指的是热点数据过期了,但是数据中的热点数据还在
但是穿透,一般是由于恶意第三方,访问根本不存在的数据,这个数据缓存中没有,数据库中也没有
穿透的话可以有挽救措施,就是对缓存中的数据进行恢复,但是击穿的话就不能了因为数据库中压根就没有这个数据
避免
限制非法的请求
对于这些非法的请求我们它访问缓存之前就应该有判断,它是一个非法的请求,比如你去判断它要访问的字段是否存在,它的IP是否是国外的IP等等
设置空值或者默认值
当发生穿透现象之后,我们可以立马给缓存中它要访问的非法数据设置一个空值或者默认值,放置它下面的请求去访问数据库
通过布隆过滤器判断数据是否存在,不要到数据库那里才知道该数据根本不存在是非法数据
博隆过滤器由位图和一组哈希函数组成
18.AOF持久化是怎么实现的?
它与MySQL的binlog文件很像,都是会保存写操作命令道文件中
具体来说,当Redis执行了一条些操作命令到时候,首先会执行写命令把数据放到内存中,然后会把这条写操作命令放到AOF日志的内核缓冲区中,等到合适的时机把AOF日志缓冲区的写命令写回到磁盘中。这个合适的时机,就是对应的三种写回策略
然后Redis有对应的重写机制,当重启Redis的时候会通过磁盘中的AOF文件把数据恢复到内存中,这个过程很复杂,我没有具体的了解
19.RDB快照持久化是怎么实现的?
AOF文件的内容是写操作命令
RDB快照文件的内容是二进制数据
所有RDB重启Redis时候恢复数据要比AOF文件恢复数据要快
Redis提供了俩个命令来生成RDB文件,分别是save和bgsave
save命令,会在主线程中生成RDB文件,如果生成RDB文件的时间太长的话,可能会阻塞主线程,那么无法执行其他命令
gbsave命令,会创建一个子线程,在子线程中生成RDB文件,不会阻塞主线程
可以说一下全量同步的时候通过bgsave生成RDB文件时候写操作命令还可以执行产生的主从不一致问题
执行快照的时候,数据能被修改吗?
直接说结论吧,执行 bqsave 过程中,Redis 依然可以继续处理操作命令的,也就是数据是能被修改的。关键的技术就在于写时复制技术(Copy-On-Write,coW),具体不了解
20.数据库和缓存如何保证一致性?
先更新数据库再更新缓存为什么不行?
场景:对同一个字段进行了并发操作,举例比如用户修改了俩次姓名,第一次要修改成A,第二次要修改成B,并发操作的时候就会造成问题
先更新缓存再更新数据库为什么不行?
场景:对同一个字段进行了并发操作,举例比如用户修改了俩次姓名,第一次要修改成A,第二次要修改成B,并发操作的时候就会造成问题
先删除缓存再更新数据库为什么不行?
一般采用先更新数据库后删除缓存
虽然这种操作也会有数据不一致的问题,然后产生的概率很低
因为缓存的写回速度肯定比数据库更新速度要快
21.Redis的主从复制
为什么要有主从复制?
如果只有一个Redis服务器,那么如果这个Redis宕机了,那么再它重启到数据恢复的这个阶段,都无法访问缓存。
或者Redis由于磁盘的损坏无法进行持久化
这俩个问题,都可以通过主从复制来解决
什么是主从复制?
主从复制采用的是读写分离的形式,主Redis专门用于写数据,从Redis用来读取数据,一般会设置一主多从的形式,因为读操作通常比写操作要多。只有主Redis用来接收写操作,那么主从之间就需要有同步机制。下面来说下主从的同步机制
第一次同步
主从Redis第一次同步的过程可以分为三个阶段
a. 确定主从关系,建立主从Redis之间的连接,协商同步模式
最开始的时候就是一个Redis的集群,里面谁是主谁是从都不确定,选择一个Redis执行replicaof指令那么这个Redis就是从Redis,
replicaof 后面跟的是主Redis的IP和端口号。
当执行replicaof指令之后,从Redis后发送一个psync请求数据同步的指令,里面有两个参数,runid = ?,offset = -1.runid是用来唯一表示一个Redis服务器的,offset是用来描述复制的进度的。
主Redis接收到psync指令之后,会发送一个全量同步的指令(FULLRESYNC),并携带自己的runid和offset
从Redis保存号runid 和 offset
b. 主Redis传递同步数据给从Reids
主Redis会执行bgsave指令,然后回生成一个RDB文件,生成该文件会fork一个子进程,不会阻塞主线程,也就是说生成RDB文件的时候,可能会有新的写操作指令,这些写操作指令会被存储到replication buf 缓冲区中。
生成号RDB文件之后,主Redis会把该文件传给从Redis。
从Redis接收RDB文件,会首先做一个数据的清楚,然后执行RDB文件,进行数据同步。RDB文件是一个二进制数据文件。
c. 主Redis将新的写操作命令传给从Redis
主Redis把repliaction buf 缓存区中存储的写操作命令发送给从Redis。
从Redis执行缓冲区里的这些命令,此时主从Redis数据就一致了
命令传播
主从Redis再进行完第一次全量同步之后,双方会维护一个TCP连接
后续,主Redis就是通过这个连接将写操作命令传递给从Redis,实现数据的一致性的。
这个TCP连接时长连接的,目的时避免频繁的TCP断开和连接带来性能的开销
分摊主服务器的压力
增量同步
在进行第一次全量同步之后,会进行TCP的连接,后续的数据的一致性由TCP的连接做写操作命令的发送。但是网络总时不稳定的,当发生网络问题到网络恢复正常这个过程很有可能从Redis的数据已经成了旧数据,那么在网络恢复之后,就需要进行一次增量同步
增量同步的步骤时这样的
从Redis在恢复网络之后,会发送已给psync命令给主Reids,此时psync里面的offset参数不是-1了
主Redis在接收到该命令后,然后用continue命令告诉从Redis接下来采用增量复制的方式同步数据
然后主Redis将从Redis断线期间,所执行的写操作命令全部发送给从Redis,然后从执行这些命令
22.千万级用户连续签到7天的总人数如何设计?
1e7bit–>1e6–>B KB MB–> 1MB
7个位图,KEY时天数,value是千万级用户