Redis重要特性

(一)简介

Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。Redis具有内置复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel提供高可用性并使用Redis Cluster自动分区。

Redis使用标准C编写实现,而且将所有数据加载到内存中,所以速度非常快。官方提供的数据表明,在一个普通的Linux机器上,Redis读写速度分别达到81000/s和110000/s,是已知性能最好的Key-Value DB。

(二)事务

Redis 提供的事务机制与传统的数据库事务有些不同,传统数据库事务必须维护以下特性:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability),简称ACID。在Redis中有些特性无法得到满足。

1. 原子性

Redis本身提供的所有API都是原子操作。但Redis在事务执行过程的错误情况做出了权衡取舍,那就是放弃了回滚。Redis官方文档对此给出的解释是:

  1. Redis 操作失败的原因只可能是语法错误或者错误的数据库类型操作,这些都是在开发层面能发现的问题不会进入到生产环境,因此不需要回滚。
  2. Redis 内部设计推崇简单和高性能,因此不需要回滚能力。

因此我们可以说,Redis没有像传统关系型数据库那样的原子性,但他具有单个操作的原子性。

2. 一致性

一致性意味着事务结束后系统的数据依然保证一致。Redis舍弃了回滚的设计,基本上也就舍弃对数据一致性的有效保证,因此Redis不具有一致性。

3. 隔离性

隔离性保证了在事务完成之前,该事务外部不能看到事务里的数据改变。由于Redis采用单线程设计,隔离性得到保证。

4. 持久性

Redis一般情况下都只进行内存计算和操作,持久性无法保证。但Redis也提供了2种数据持久化模式,RDB和AOF,RDB的持久化操作与命令操作是不同步的,无法保证事务的持久性。而 AOF模式意味着每条命令的执行都需要进行系统调用操作磁盘写入文件,可以保证持久性,但会大大降低Redis的访问性能。

(三)数据持久化

Redis提供了两种不同的持久化选项:

  • RDB持久化以指定的时间间隔执行数据集的时间点快照。
  • AOF持久化会记录服务器接收到的每个写入操作,这些操作将在服务器启动时再次执行,重建原始数据集。AOF使用与Redis协议本身相同的格式以追加方式记录命令。

下面我们来看一下这两种持久化方式的特点和相应的优缺点:

1. RDB(快照)

RDB的逻辑十分简单,首先Redis调用fork(),产生一个子进程。然后子进程把Redis中的数据写到一个临时的RDB文件中。当子进程写完新的RDB文件后,用它把旧的RDB文件替换掉。RDB的启动可以在配置文件中配置或者使用bgsave命令,bgsave的流程如下:

redis 三大特点 redis的特点_数据库

RDB的优点:

  • RDB是一个紧凑压缩的二进制文件,存储效率较高。
  • RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景。
  • RDB恢复数据的速度要比AOF快很多。

RDB的缺点:

  • RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据。假设每5分钟保存一次快照,如果Redis因为某些原因终止,那么从上次产生快照到Redis出现问题这段时间的数据就会丢失。
  • bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能。
  • Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象。

2. AOF(日志)

AOF的整个流程大体来看可以分为两步,第一步是命令的写入,第二步是对AOF文件的重写。对于增量追加到文件这一步主要的流程是:命令写入->追加到aof_buf ->同步到AOF磁盘。那么这里为什么要先写入aof_buf再同步到磁盘呢?如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能。AOF重写是为了减少AOF文件的大小,可以手动或者自动触发。fork的操作也是发生在重写这一步,也是这里会对主进程产生阻塞。

redis 三大特点 redis的特点_缓存_02


对于上图有四个关键点补充一下:

  • 在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性,因此它依然会写入旧的AOF file中,如果重写失败,能够保证数据不丢失。
  • 为了把重写期间响应的写入信息也写入到新的文件中,因此也会为子进程保留一个buf,防止新写的file丢失数据。
  • 重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。
  • AOF文件直接采用的文本协议,主要是兼容性好、追加方便、可读性高可以人为修改修复。

AOF的优点:

  • 比RDB可靠,在配置文件中可以选择不同的fsync策略:不进行fsync、每秒fsync一次和每次查询进行fsync。默认是每秒fsync一次,这意味着最多丢失一秒钟的数据。
  • AOF日志文件是一个纯追加的文件,即使服务器突然宕机,也不会出现日志的定位或者损坏问题。甚至如果因为某些原因(例如磁盘满了)命令只写了一半到日志文件里,也可以用redis-check-aof这个工具进行修复。
  • 当AOF文件太大时,Redis会自动在后台进行重写。重写很安全,因为重写是在一个新的文件上进行,同时Redis会继续往旧的文件追加数据。新文件上会写入能重建当前数据集的最小操作命令的集合。当新文件重写完,Redis会把新旧文件进行切换,然后开始把数据写到新文件上。
  • AOF把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易用于恢复数据。

AOF的缺点:

  • 在相同的数据集下,AOF文件的大小一般会比RDB文件大,恢复数据也会更慢。
  • 在某些fsync策略下,AOF的速度会比RDB慢。通常fsync设置为每秒一次就能获得比较高的性能,而在禁止fsync的情况下速度可以达到RDB的水平。
  • 在过去曾经发现一些很罕见的BUG导致使用AOF重建的数据跟原数据不一致的问题。

(四)删除策略

Redis所有的键都可以设置过期时间属性,内部保存在过期表中,超过过期时间的key就可以被认为是垃圾。Redis对于过期的键有三种清除策略:

  • 立即删除
  • 惰性删除
  • 定时删除

1. 立即删除

对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU资源去处理过期的数据,会影响Redis的吞吐量和响应时间。

2. 被动删除(惰性删除)

被动删除指只有key被操作时(如GET等操作),Redis才会被动检查该key是否过期,如果过期则删除。这种删除策略对CPU是友好的,删除操作只有在不得不的情况下才会进行,不会浪费CPU时间。但是这种策略对内存不友好,一个key已经过期,但是在它被操作之前不会被删除,仍然占据内存空间。如果有大量的过期键存在但是又很少被访问到,那会造成大量的内存空间浪费。

但仅仅用被动删除在一些情况下是不够的,因为可能存在一些key永远不会被再次访问到,这些设置了过期时间的key也是需要在过期后被删除的,我们甚至可以将这种情况看作是一种内存泄露,因为服务器不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息。

3. 定时删除

Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。典型的方式为,Redis每秒做10次如下的步骤:

  1. 随机测试100个设置了过期时间的key。
  2. 删除所有发现的已过期的key。
  3. 若删除的key超过25个则重复步骤1。

这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。这也意味着在任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4。

该策略是前两者的一个折中方案,还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU和内存资源达到最优的平衡效果。

(五)数据淘汰策略

我们知道Redis是基于内存的key-value数据库,因为系统的内存大小有限,所以我们在使用Redis的时候可以配置Redis能使用的最大的内存大小。既然可以设置Redis最大占用内存大小,那么配置的内存就有用完的时候。那在内存用完的时候,还继续往Redis里面添加数据不就没内存可用了吗?

因此,Redis设计了多种淘汰机制,用来解决内存不足的问题:

  • noeviction(默认策略):内存不足时对于写请求不再提供服务,直接报错。
  • allkeys-lru:在所有key中使用LRU算法进行淘汰。
  • volatile-lru:在设置了过期时间的key中使用LRU算法进行淘汰。
  • allkeys-random:在所有key中随机淘汰数据。
  • volatile-random:在设置了过期时间的key中随机淘汰。
  • volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰。
  • volatile-lfu:在设置了过期时间的key中使用LFU算法淘汰。
  • allkeys-lfu:在所有的key中使用LFU算法淘汰数据。

这些淘汰策略不难理解,我们需要理解的是两种淘汰算法:LRU和LFU算法。

1. LRU算法

LRU(Least Recently Used),即最近最少使用,是一种缓存置换算法。在使用内存作为缓存的时候,缓存的大小一般是固定的。当缓存被占满,这个时候继续往缓存里面添加数据,就需要淘汰一部分老的数据,释放内存空间用来存储新的数据。这个时候就可以使用LRU算法了。其核心思想是:如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉。

Redis使用的是近似LRU算法,它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据,每次随机出5(默认)个key,从里面淘汰掉最近最少使用的key。可以通过maxmemory-samples参数修改采样数量: 例:maxmemory-samples 10。 maxmenory-samples配置的越大,淘汰的结果越接近于严格的LRU算法。Redis为了实现近似LRU算法,给每个key增加了一个额外增加了一个24bit的字段,用来存储该key最后一次被访问的时间。

Redis3.0对近似LRU算法进行了一些优化。新算法会维护一个候选池(大小为16),池中的数据根据访问时间戳进行排序,第一次随机选取的key都会放入池中,随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中,直到候选池被放满。当放满后,如果有新的key需要放入,则将池中最后访问时间戳最大(最近被访问)的移除。当需要淘汰的时候,则直接从池中选取最近访问时间戳最小(最久没被访问)的key淘汰掉就行。

2. LFU算法

LFU算法是Redis4.0里面新加的一种淘汰策略,全称是Least Frequently Used,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。

LFU算法能更好的表示一个key被访问的热度。假如你使用的是LRU算法,一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况,因为使用一次并不会使一个key成为热点数据。

2020年9月11日