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中有些特性无法得到满足。
Redis本身提供的所有API都是原子操作。但Redis在事务执行过程的错误情况做出了权衡取舍,那就是放弃了回滚。Redis官方文档对此给出的解释是:
- Redis 操作失败的原因只可能是语法错误或者错误的数据库类型操作,这些都是在开发层面能发现的问题不会进入到生产环境,因此不需要回滚。
- Redis 内部设计推崇简单和高性能,因此不需要回滚能力。
因此我们可以说,Redis没有像传统关系型数据库那样的原子性,但他具有单个操作的原子性。
一致性意味着事务结束后系统的数据依然保证一致。Redis舍弃了回滚的设计,基本上也就舍弃对数据一致性的有效保证,因此Redis不具有一致性。
隔离性保证了在事务完成之前,该事务外部不能看到事务里的数据改变。由于Redis采用单线程设计,隔离性得到保证。
Redis一般情况下都只进行内存计算和操作,持久性无法保证。但Redis也提供了2种数据持久化模式,RDB和AOF,RDB的持久化操作与命令操作是不同步的,无法保证事务的持久性。而 AOF模式意味着每条命令的执行都需要进行系统调用操作磁盘写入文件,可以保证持久性,但会大大降低Redis的访问性能。
Redis提供了两种不同的持久化选项:
下面我们来看一下这两种持久化方式的特点和相应的优缺点:
RDB的逻辑十分简单,首先Redis调用fork(),产生一个子进程。然后子进程把Redis中的数据写到一个临时的RDB文件中。当子进程写完新的RDB文件后,用它把旧的RDB文件替换掉。RDB的启动可以在配置文件中配置或者使用bgsave命令,bgsave的流程如下:
RDB的优点:
RDB的缺点:
AOF的整个流程大体来看可以分为两步,第一步是命令的写入,第二步是对AOF文件的重写。对于增量追加到文件这一步主要的流程是:命令写入->追加到aof_buf ->同步到AOF磁盘。那么这里为什么要先写入aof_buf再同步到磁盘呢?如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能。AOF重写是为了减少AOF文件的大小,可以手动或者自动触发。fork的操作也是发生在重写这一步,也是这里会对主进程产生阻塞。
AOF的优点:
AOF的缺点:
Redis所有的键都可以设置过期时间属性,内部保存在过期表中,超过过期时间的key就可以被认为是垃圾。Redis对于过期的键有三种清除策略:
对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU资源去处理过期的数据,会影响Redis的吞吐量和响应时间。
被动删除指只有key被操作时(如GET等操作),Redis才会被动检查该key是否过期,如果过期则删除。这种删除策略对CPU是友好的,删除操作只有在不得不的情况下才会进行,不会浪费CPU时间。但是这种策略对内存不友好,一个key已经过期,但是在它被操作之前不会被删除,仍然占据内存空间。如果有大量的过期键存在但是又很少被访问到,那会造成大量的内存空间浪费。
但仅仅用被动删除在一些情况下是不够的,因为可能存在一些key永远不会被再次访问到,这些设置了过期时间的key也是需要在过期后被删除的,我们甚至可以将这种情况看作是一种内存泄露,因为服务器不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息。
Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。典型的方式为,Redis每秒做10次如下的步骤:
这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。这也意味着在任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4。
该策略是前两者的一个折中方案,还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU和内存资源达到最优的平衡效果。
我们知道Redis是基于内存的key-value数据库,因为系统的内存大小有限,所以我们在使用Redis的时候可以配置Redis能使用的最大的内存大小。既然可以设置Redis最大占用内存大小,那么配置的内存就有用完的时候。那在内存用完的时候,还继续往Redis里面添加数据不就没内存可用了吗?
因此,Redis设计了多种淘汰机制,用来解决内存不足的问题:
这些淘汰策略不难理解,我们需要理解的是两种淘汰算法:LRU和LFU算法。
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淘汰掉就行。
LFU算法是Redis4.0里面新加的一种淘汰策略,全称是Least Frequently Used,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。
LFU算法能更好的表示一个key被访问的热度。假如你使用的是LRU算法,一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况,因为使用一次并不会使一个key成为热点数据。
2020年9月11日