Redis—day01|青训营笔记

125 阅读11分钟

Redis可以用来做什么

  1. 用来作缓存,Redis是分布式缓存的首选中间件。
  2. 用来做数据库,用来实现对于点赞、排行等对性能要求极高的需求。
  3. 用于当做计算工具,在较小的开销下统计PV/UV(INCR命令时间复杂度是O1,并且其是线程安全的,可以实现高并发场景下的原子计数)。
  4. 可以做分布式锁。
  5. 可以做消息队列。

Redis和传统的数据库有什么区别

Redis是一种基于键值对的NoSQL数据库,并且其数据存储在内存中,读写性能惊人,可以达到10万条每秒,其性能远超关系型数据库。

关系型数据库一般采用二维表的结构存储数据,它的数据格式更加严谨,并且数据存储在磁盘上,更适合存放海量数据,但是性能远远不如Redis。

Redis有哪些数据类型?

  1. 五种核心数据类型:字符串、哈希、列表、集合(set)、有序集合(zset)。
  2. 还提供Bitmap(位图)、hyperLogLog(可以在较小的开销下实现对于集合中不重复元素个数的统计)、Geo(地理信息)等数据类型,这些数据类型都是基于五种核心数据类型实现的。
  3. Redis5.0以后推出了Streams数据类型,他是一个可以多播、可以持久化的功能强大的消息队列。

Redis是单线程的为什么还这么快?

  1. 对于服务端程序来说,线程切换和锁往往是性能的杀手,而Redis采用干了单线程的架构,不存在线程切换和锁竞争,避免了因线程切换和锁竞争带来的资源消耗。
  2. Redis大部分操作是基于内存完成的,这也是它速度快的一个重要原因。
  3. Redis采用了IO多路复用机制,使其能在网络IO操作中并发处理大量的客户端请求,实现高吞吐量。
  4. Redis的单线程并不是真正意义上的单线程,我们常说的额Redis单线程一般是指Redis的网络IO和键值对读写是由一个线程串行执行的,而在进行持久化、集群数据复制、异步删除这种比较容易引起线程阻塞的操作时,会fork出一个子进程,来做相应的处理。

set和zset有什么区别

set、zset中存储的元素都是不可重复的。 但是zset中存储的每一个元素都有对应的分值,以此作为排序的依据(不同元素的分数可以相同)。而set中的元素是无序的。

说一下Redis中改的watch命令

很多时候,要确保事务中的数据没有被及其他客户端修改,才能执行该事务,Redis采用watch来解决这类问题,watch是一种乐观锁的机制,可以使用watch监视一个或多个key,当事务监视的key被其他客户端修改时,则该客户端的事务将被服务端拒绝执行,并返回一个该客户端一个空值。

说一说Redis的List结构的相关操作

  1. lpush/rpush
  2. lpop/rpop
  3. blpop/brpop 从列表的左侧或者右侧弹出一个数据,如果列表没有数据则会blocking(阻塞)
  4. lrange 返回指定索引范围处的数据
  5. lindex 返回指定索引处的数据

Redis的过期时间应该如何设置?

  1. 热点数据不设置过期时间,防止缓存击穿。
  2. 设置不同的缓存过期时间,在设置缓存过期时间的时候,在过期时间上加一个随机数,防止大量缓存同时过期,导致缓存雪崩。

Redis的setnx的返回值是什么,如何利用setnx实现分布式锁?

setnx返回整数值,0或1,返回1代表设置成功,返回0代表设置失败。

一般我们不直接使用setnx来实现分布式锁。因为如果直接利用setnx实现分布式锁,实现思路如下:

#加锁
setnx key value
#解锁
del key

但是由于可能会忘记解锁,或者解锁失败。这就容易导致发生死锁。

expire key seconds

为了防止死锁,往往通常需要设置一个过期时间,如果过期时间设定失败,还是容易出现死锁。

所以,我们采用

set key value nx ex seconds 

将枷锁和过期命令合并成一个原子操作,就可以很大程度上防止死锁的发生。

看上去好像已经完美了,但是存在一个问题。在手动解锁前,锁已经过期,发生了自动释放锁的情况,在后续手动解锁时,有可能解除掉的是其他锁。

举例:假如A客户端先获取到锁,然后A客户端过期,自动释放锁。B客户端获取锁,如果A客户端在此时手动解锁,则会释放掉B客户端的锁,这是不符合逻辑的。

如何解决:因此在释放锁的时候,我们需要进行判断操作,判断当前该key的value是否为A客户端线程存入的value,如果是,则该锁可以释放。如果不是,则该锁不能释放。判断-释放应该合并为一个原子操作,不可以被其他操作打断,因此可以判断-释放的逻辑写入一个Lua脚本,然后执行这个Lua脚本来实现。(注:Lua脚本的原子操作仅限于不可被打断,这里的原子性不具备回滚功能)。

image.png

说一说Redis的持久化策略

支持的持久化方式类型

Redis支持RDB持久化、AOF持久化、RDB-AOF混合持久化。

RDB持久化

RDB持久化原理

RDB持久化是Redis默认的持久化策略,它以二进制快照的形式将数据持久化到磁盘中。RDB持久化方式会创建一个.rdb结尾的二进制压缩文件,用于存储各个数据库的键值对等信息。

现在的Redis都是使用BGSAVE来实现持久化,Redis执行BGSAVE后,会通知父进程fork出一个子进程,用于RDB持久化,子进程共享父进程的内存空间。然后由子进程对内存空间中的数据打快照,并创建一个.rdb结尾的文件,将快照以二进制文件的形式压缩存储至.rdb结尾的文件中。重启时,加载.rdb文件即可快速恢复数据。

RDB持久化触发方式

手动触发:通过SAVE或者BGSAVE命令触发RDB持久化操作,创建".rdb"文件。 自动触发:通过配置选项,让服务器在满足指定条件时,自动执行BGSAVE指令。

SAVE和BGSAVE的区别

其中SAVE命令执行期间,Redis服务器会被阻塞,直到.rdb文件创建完为止。而BGSAVE则是异步版本的SAVE命令,它会fork出一个子进程,使用该子进程创建.rdb文件。在创建子进程时候会出现短暂的阻塞,但是之后服务器便可以继续执行其他任务,总之BGSAVE是针对SAVE命令的阻塞问题做出的优化,现在Redis内部所有的RDB操作使用的都是BGSAVE操作,SAVE指令已经被废弃了。

在RBD持久化操作执行BGSAVE指令时,如果发生了写操作,时如何处理的?

如果发生了写操作,采用的是COW的机制(Copy ON Write写时复制)进行处理。需要修改的原数据页不做修改,仍然用于BGSAVE。对需要进行写操作的数据页进行复制,得到副本,然后对该副本进行修改。

AOF持久化

AOF持久化原理

AOF以独立日志的形式,记录了每次写入Redis的命令,重启时重新执行AOF文件中的命令即可恢复数据。

AOF持久化的过程是这样的:首先将命令写入AOF缓冲区(位于内存中),将AOF缓冲区的内容定时同步至AOF文件中,以保证数据的持久性和一致性。由于AOF文件中的指令过于冗余,需要定时进行AOF文件重写,因为AOF文件重写比较耗时,Redis还会定时fork出一个子进程用于AOF文件的重写,用于AOF文件中冗余指令的合并、删减,以减小AOF的文件大小以及AOF文件中的指令个数。在进行AOF文件重写的时候,Redis客户端仍然可以接受新的指令,新的指令将会被存储在一个新的AOF文件中。这就避免了在重写过程中丢失客户端的命令。

image.png 注意:AOF持久化的过程中AOF文件同步的过程是主进程来完成的。而AOF文件重写的过程,是由子进程来完成的。

AOF的配置项

AOF持久化不是默认开启的,需要修改相关的配置项来启用它。 Redis为用户提供了appendsync选项,来选择AOF文件同步的策略,有三种策略可以选择:

  1. always:每次写入都会进行文件同步,调用linux的fsync函数将指定文件从缓冲区刷到硬盘,也就是将AOF缓冲区的指令持久化到AOF文件中。在服务器停机时会丢失一条指令。
  2. everysecond:每隔一秒钟进行一次文件同步,将指令持久化到AOF文件中。在服务器停机时,这样只会丢失1s内的指令。
  3. no:不主动进行文件同步。何时进行文件同步由操作系统决定。

注:由于文件同步这件事不是由子进程来完成的,而是由父进程来完成的。因此,频繁地文件同步将会对主进程起到一定的阻塞作用,降低Redis的性能。一般采用everysecond的策略。

AOF持久化和RDB持久化的优缺点对比

每次进行RDB持久化都会fork出一个子进程,这是一个比较重量级的操作。因此不应该频繁地进行RDB持久化,这就会导致RDB持久化不具备实时性,会丢失大量的数据。AOF持久化在进行AOF文件同步的时候,是采用主进程进行的,不需要在每次文件同步时都fork出一个子进程,因此可以较为频繁地进行AOF文件同步,保证了数据的实时性。

RDB持久化保存的是数据压缩后的二进制快照,体积较小。而AOF文件保存的是每一条修改数据的指令,文件较大。在进行数据恢复的时候若采用RDB持久化,则加载这个体积较小的.rdb文件就可以实现数据的恢复。而若采用AOF,则需要重新执行AOF文件中的每一条指令,开销较大且较为缓慢。并且AOF在进行文件重写时,若AOF文件较大,进行AOF重写的进程将会占据较大的资源,甚至阻塞主进程。

RDB-AOF混合持久化

在AOF进行文件重写时,将内存中的数据以二进制压缩文件的形式保存到AOF文件当中。对于重写之后的Redis命令,则追加到刚刚记录的RDB数据后面。在进行数据恢复的时候,会首先加载AOF文件中的RDB数据,这个过程非常迅速,然后再逐条执行AOF文件中RDB数据后的AOF指令。这种方式既保证了实时性,又可以快速实现数据的恢复。

在进行AOF重写时,如果Redis客户端传来新的修改数据的指令,这些指令将会被依次记录在一个新的AOF文件中,以保证数据的实时性。

Redis为什么存的快,内存断电以后数据如何恢复?

Redis存的快是因为其将数据存在内存里,在进行数据读写的时候大大减少了磁盘IO的操作。为了保证数据的持久性,Redis提供三种持久化方案:RDB、AOF、RDB-AOF混合持久化。若断电我们可以利用持久化文件进行恢复数据,理论上来说RDB—AOF持久化机制可以将数据恢复的时间控制在1s以内。