首页 云计算 服务器 大数据 存储 IT 安全 物联网 软件 案例库

存储

存储频道旗下栏目:

Redis你可能不了解的那些事

来源:今日头条   发布时间:2019-04-03
摘要:引子 Redis 是一个高性能分布式的key-value数据库。它支持多种数据结构,并可应用于缓存、队列等多种场景下。使用过Redis的小伙伴们可能对这些已经非常熟知了,下面我想来谈谈Redis也许并不被每个人了解的那点事。 Redis持久化机制 刚看到标题你可能会说,

引子

Redis 是一个高性能分布式的key-value数据库。它支持多种数据结构,并可应用于缓存、队列等多种场景下。使用过Redis的小伙伴们可能对这些已经非常熟知了,下面我想来谈谈Redis也许并不被每个人了解的那点事。

Redis持久化机制

刚看到标题你可能会说,我知道,不就是RDB和AOF嘛。这些已经是老生常谈了。那么我们今天就深入谈谈这两种持久化方式的逻辑和原理。

RDB的原理

在Redis中RDB持久化的触发分为两种:自己手动触发与Redis定时触发。

针对RDB方式的持久化,手动触发可以使用:

(1)save:会阻塞当前Redis服务器,直到持久化完成,线上应该禁止使用。

(2)bgsave:该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。

而自动触发的场景如下:

  • 根据我们的 save m n 配置规则自动触发;
  • 从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave;
  • 执行 debug reload 时处罚;
  • 执行 shutdown时,如果没有开启aof,也会触发。

由于 save 基本不会被使用到,我们来看看 bgsave 这个命令是如何完成RDB的持久化的。

RDB文件保存过程

(1)redis调用fork,现在有了子进程和父进程。

(2)父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照。

(3)当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

PS:fork 操作会阻塞,导致Redis读写性能下降。我们可以控制单个Redis实例的最大内存,来尽可能降低Redis在fork时的时间消耗;或者控制自动触发的频率减少fork次数。

AOF的原理

AOF的整个流程大体来看可以分为两步,一步是命令的实时写入(如果是 appendfsync everysec 配置,会有1s损耗),第二步是对aof文件的重写。

对于增量追加到文件这一步主要的流程是:

(1)命令写入

(2)追加到aof_buf

(3)同步到aof磁盘

那么这里为什么要先写入buf再同步到磁盘呢?如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能。

AOF重写

你可以会想,每一条写命令都生成一条日志,那么AOF文件是不是会很大?答案是肯定的,AOF文件会越来越大,所以Redis又提供了一个功能,叫做AOF rewrite。其功能就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作。

手动触发: bgrewriteaof

自动触发就是根据配置规则来触发,当然自动触发的整体时间还跟Redis的定时任务频率有关系。

下面来看看重写的流程图:


(1)redis调用fork ,现在有父子两个进程

(2)子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令

(3)父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。

(4)当子进程把快照内容写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。

(5)现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

PS:需要注意到是重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

Redis为什么这么快?

Redis采用的是基于内存的单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!原因如下:

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速;

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁的操作,也不可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

5、使用的底层模型不同,底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接构建了自己的VM机制。

多路I/O复用模型

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗)。

Redis事务

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。

举个例子,使用redis-cli连接redis,然后在命令行工具中输入如下命令:


从输出中可以看到,当输入MULTI命令后,服务器返回OK表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不会马上执行,而是返回”QUEUED”,这表示命令已经被服务器接受并且暂时保存起来,最后输入EXEC命令后,本次事务中的所有命令才会被依次执行,可以看到最后服务器一次性返回了三个OK,这里返回的结果与发送的命令是按顺序,这说明这次事务中的命令全都执行成功了。

再举个例子,在命令行工具中输入如下命令:


最火资讯