【Redis】一文快速入门Redis(基础数据结构、相关常用命令、持久化及淘汰策略详解)

目录

一、Redis 基础数据结构

1.1 string (字符串)

1.2 list (列表)

1.3 hash (字典)

1.4 set (集合)

1.5 zset (有序集合)

1.6 其他高级命令

二、核心原理

2.1 Redis的单线程和高性能

2.1.1 Redis 单线程为什么还能这么快?

2.1.2 Redis 单线程如何处理那么多的并发客户端连接?

三、持久化

3.1 RDB快照(snapshot)

他什么时候fork子线程,或者什么时候触发rdb持久化机制

3.2 AOF(append-only file)

3.3 RDB 和 AOF ,我应该用哪一个?

3.4 Redis 4.0 混合持久化

混合持久化aof文件结构

aof重写机制

3.5 小总结:

1.redis提供了rdb持久化方案,为什么还要aof?

2.如果aof和rdb同时存在,听谁的?

3.rdb和aof优势劣势

3.6 性能建议(这里只针对单机版redis持久化做性能建议):

四、缓存淘汰策略


一、Redis 基础数据结构

Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。

1.1 string (字符串)

字符串 string 是 Redis 最简单的数据结构。Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体使用 JSON 序列化成字符串,然后将序

列化后的字符串塞进 Redis 来缓存。同样,取用户信息会经过一次反序列化的过程。

 

键值对

 

批量键值对:可以批量对多个字符串进行读写,节省网络耗时开销这是一个原子命令

 

过期和 set 命令扩展:可以对 key 设置过期时间,到点自动删除,这个功能常用来控制缓存的失效时间

setnx如果redis数据库中没有name这个key的话,就会执行这个set命令,并且返回1表示执行成功。如果数据库中已经存在了这个key的话就不会执行set命令,并且返回0。这个命令可以用来实现分布式锁

 

原子计数:如果 value 值是一个整数,还可以对它进行自增操作。自增是有范围的,它的范围是 signed long 的最大最小值,超过了这个值,Redis 会报错

自减命令是:decr age    decrby age 5

他们之所以能实现原子操作,是因为redis是单线程的,保证每一个客户端操作的时候只有一个用户对数据操作。这个原子性的原理和JUC中的原子性操作原理不同。redis因为是单线程的,每一个客户端操作redis的时候是串行一个一个执行的,都在一个队列中依次操作。JUC的原子性操作原理是通过CAS来实现的。

 

1.2 list (列表)

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n),这点让人非常意外。 当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

 

右边进左边出:队列

 

右边进右边出:栈

 

 

1.3 hash (字典)

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。

hash 结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash 可以对用户结构中的每个字段单独存储。这样当我们需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。 hash 也有缺点,hash 结构的存储消耗要高于单个字符串,到底该使用 hash 还是字符串,需要根据实际情况再三权衡。

 

 

1.4 set (集合)

Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL。 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

 

 

1.5 zset (有序集合)

zset 似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重

zset 可以用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进行排序。

zset 还可以用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。我们可以对成绩按分数进行排序就可以得到他的名次。

 

1.6 其他高级命令

keys:全量遍历键用来列出所有满足特定正则字符串规则的keyredis数据量比较大时,性能比较差,要避免使用。

 

scan:渐进式遍历键,类似于分页查找。scan 参数提供了三个参数,第一个是 cursor 整数值(查找的第一个数据的游标指针),第二个是 key 的正则模式,第三个是遍历的 limit hint(指这一轮查找要查找多少个数据)。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历到返回的 cursor 值为 0 时结束。当数据量比较大的时候,可以使用scan来遍历key,这样效率比较高。

第一条命令的含义就是从哈希游标为0的这个数据开始找起,从这个位置开始向下查找1000个数据,然后找出这个哈希表的1000个数据中满足正则表达式的数据。

  1. 第一行返回的是本次查找的最后一个位置是哈希游标为13976的数据。
  2. 后面那些是查找到的符合正则表达式的数据。

第二条命令就是说接着上一条命令开始找起,从13976这个位置开始向下再遍历1000条数据,查找出符合正则表达式的数据。注意这里返回的第一行本次查找的最后一个位置的哈希游标变成了1996,之所以比起始的游标还要小,是因为在哈希表中,虽然数据是顺序存放的,但是他们的哈希值并不是顺序排列的。

一直执行这样的操作,直到返回得游标变成最开始遍历的那个游标数后(上面的这个例子就是又回到了0),那么就证明我们已经遍历完了整个redis的Hash表,把所有数据都遍历查找完一遍了。

Redis存储键值对实际使用的是hashtable的数据结构

 

Info查看redis服务运行信息,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:

Server 服务器运行的环境参数

Clients 客户端相关信息

Memory 服务器运行内存统计数据

Persistence 持久化信息

Stats 通用统计数据

Replication 主从复制相关信息

CPU CPU 使用情况

Cluster 集群信息

KeySpace 键值对统计数量信息

flushall:清空redis的所有库的数据(默认redis是16个库)

flushdb清空当前库的数据

DBSIZE当前数据库有多少个数据 

 

二、核心原理

2.1 Redis的单线程和高性能

2.1.1 Redis 单线程为什么还能这么快?

因为它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,因为redis单线程同一时间只能执行一个命令,keys命令非常耗时,所以在很长一段时间就不能去执行其他命令,可能会导致 Redis 卡顿。

 

2.1.2 Redis 单线程如何处理那么多的并发客户端连接?

Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

Nginx也是采用IO多路复用原理解决C10K问题

 

 

三、持久化

说白了,就是在指定的时间间隔内,将内存当中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存。

什么意思呢?我们都知道,内存当中的数据,如果我们一断电,那么数据必然会丢失,但是玩过redis的同学应该都知道,我们一关机之后再启动的时候数据是还在的,所以它必然是在redis启动的时候重新去加载了持久化的文件。

redis提供两种方式进行持久化,一种是RDB持久化默认,另外一种是AOF(append only file)持久化。

3.1 RDB快照(snapshot

原理是redis会单独创建(fork)一个与当前线程一模一样的子进程来进行持久化,这个子进程的所有数据(变量。环境变量,程序程序计数器等)都和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的io操作,这就确保了极高的性能。

在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。

 

他什么时候fork子线程,或者什么时候触发rdb持久化机制

  1. shutdown时,如果没有开启aof,会触发配置文件中默认的快照配置
  2. save:是只管保存,其他不管,全部阻塞
  3. bgsave: redis会在后台异步进行快照操作,同时可以响应客户端的请求
  4. 执行flushall命令,但是数据库是空的,无意义

你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。

比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集(这里是备份整个内存的数据快照):

# save 60 1000

 

3.2 AOFappend-only file

快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化,将修改的每一条指令记录进文件。原理是将Reids的操作日志以追加的方式写入文件,读操作是不记录的。

你可以通过修改配置文件来打开 AOF 功能:

# appendonly yes

从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾(这里是只备份新追加的数据)。

这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。

你可以配置 Redis 多久才将数据 fsync 到磁盘一次。

有三个选项:

  • appendfsync always:同步持久化,每次有新命令追加到 AOF 文件时就执行一次 fsync,非常慢,也非常安全。
  • appendfsync everysec:每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
  • appendfsync no:从不 fsync,将数据交给操作系统来处理,等操作系统进行数据缓存同步到磁盘。更快,也更不安全的选择。

推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。

 

3.3 RDB AOF ,我应该用哪一个?

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,因为RDB将多条命令聚合到了一起,但是AOF都是分条的命令。

比如AOF就只会记录下面的每一条命令

但是RDB只记录一条

set count 5

 RDB在占用空间和恢复速度上都高于AOF,但是AOF比RDB更能保证数据安全,最大限度保证数据不丢失。

 

3.4 Redis 4.0 混合持久化

重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化

AOF在重写(aof文件里可能有太多没用指令,所以aof会定期根据内存的最新数据生成aof文件)时将重写这一刻之前的内存rdb快照文件的内容和增量的 AOF修改内存数据的命令日志文件存在一起,都写入新的aof文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换;也就是说重写其实就是给AOF“瘦身”。

可以看到在AOF文件中上半部分是一些乱码,就是二进制的RDB快照,后半部分就是一些字符串的命令,就是AOF持久化文件,这就是混合持久化的文件。

 

混合持久化aof文件结构

AOF根据配置规则在后台自动重写,也可以人为执行命令bgrewriteaof重写AOF。 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

set count 5

lpush list a1 a2 a3

 

aof重写机制

  • 当AOF文件增长到一定大小的时候Redis能够调用 bgrewriteaof对日志文件进行重写 。当AOF文件大小的增长率大于该配置项时自动开启重写(这里指超过原大小的100%)。
  • auto-aof-rewrite-percentage 100
  • 当AOF文件增长到一定大小的时候Redis能够调用 bgrewriteaof对日志文件进行重写 。当AOF文件大小大于该配置项时自动开启重写
  • auto-aof-rewrite-min-size 64mb
  • 手动执行重写命令bgrewriteaof,这个命令是异步命令

 

4.0版本的混合持久化默认关闭的,通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用,5.0之后默认开启。

开启混合持久化:

# aof-use-rdb-preamble yes   

 

 

混合持久化是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入新的aof文件(4),然后再将重写缓冲区的增量命令以AOF方式写入到文件(5-2),写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件(5-3)。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据。

优点:混合持久化结合了RDB持久化 和 AOF 持久化的优点, 由于绝大部分都是RDB格式,加载速度快,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。

缺点:兼容性差,一旦开启了混合持久化,在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,阅读性较差

 

3.5 小总结:

1.redis提供了rdb持久化方案,为什么还要aof

优化数据丢失问题,rdb会丢失最后一次快照后的数据,aof丢失不会超过2秒的数据

2.如果aofrdb同时存在,听谁的?

aof

3.rdbaof优势劣势

rdb 适合大规模的数据恢复,对数据完整性和一致性不高 , 在一定间隔时间做一次备份,如果redis意外down机的话,就会丢失最后一次快照后的所有操作。aof 根据配置项而定。官方建议 两种持久化机制同时开启,如果两个同时开启 优先使用aof持久化机制

 

3.6 性能建议(这里只针对单机版redis持久化做性能建议):

因为RDB文件只用作后备用途,只要15分钟备份一次就够了,只保留save 900 1这条规则。

如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。 代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。 只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。 默认超过原大小100%大小时重写可以改到适当的数值。

 

四、缓存淘汰策略

当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。

在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。

当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。

 

noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。

volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。

volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。

volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。

allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。

allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。

 

volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰。如果你只是拿 Redis 做缓存,那应该使用 allkeys-xxx,客户端写缓存时不必携带过期时间。如果你还想同时使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,这样可以保留没有设置过期时间的 key,它们是永久的 key 不会被 LRU 算法淘汰。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值