Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。
1、String 字符串
字符串 string 是 Redis 最简单的数据结构。Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体使用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。同样,取用户信息会经过一次反序列化的过程。
键值对命令:
批量键值对命令:可以批量对多个字符串进行读写,节省网络耗时开销
过期和 set 命令扩展:可以对 key 设置过期时间,到点自动删除,这个功能常用来控制缓存的失效时间
原子计数:如果 value 值是一个整数,还可以对它进行自增操作。自增是有范围的,它的范围是 signed long 的最大最小值,超过了这个值,Redis 会报错
2、list(列表)
Redis的列表相当于java中的LinkedList,是链表而不是数组,这意味着list的插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n),当列表弹出了最后一个元素以后,该数据结构自动被删除,内存被回收;
Redis的列表结构常用来做异步队列使用,将需要延后处理的任务结构体序列化为字符串塞进Redis的列表,另一个线程从这个列表中轮询数据进行处理(netty做网络通信时,就可以使用,先把发送的消息存到redis列表中,然后另一个线程在轮询发送数据);
右边进左边出:队列
右边进右边出:栈
当然,也可以通过ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
redis 127.0.0.1:6379> LTRIM KEY_NAME START STOP;
* 注:start 和stop 是指list中元素位置下标,都是从0开始,下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
3、hash(字典)
Redis的字典相当于Java中的HashMap,是无序字典,内部实现结构上同Java的HashMap也是一致的,是数组+链表二维结构的,第一维hash的数组位置碰撞时,就会将碰撞的元素使用链表串接起来;
hash结构也可以用来存储用户信息,不同于字符串String需要一次性全部序列化整个对象,hash结构可以对用户结构中的每个字段单独存储,这样当我们需要获取用户信息时,可以进行部分获取,而以整个字符串的形式去保存用户信息的话,就只能一次性全部读取,这样就会比较浪费网络流量;
hash也有缺点,就是hash结构的存储消耗要高于单个字符串。
4、Set(集合)
实现一个无序不重复的列表,Redis的集合相当于Java中的HashSet,内部的键值对是无序的唯一的(无序是指不是按照插入数据顺序排序,而是按照字典序排序),内部实现相当于一个特殊的字典(hash),字典中所有的value都是一个值NULL,当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。
set还支持交集 并集 差集
sdiff key1 key2 返回直接定集合的差集
sinter key1 key2 返回直接定集合的交集
sunion key1 key2 返回直接定集合的并集
5、zset(有序集合)
实现一个有序不重复的列表,zset类似于java的SortedSet和HashMap的结合体,一方面它是一个set,保证了内存value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value排序权重。
zset可以用来存粉丝列表,value值是粉丝的用户ID,score的关注时间,可以可以对粉丝列表按关注时间进行排序;
zset还可以用来存储学生的成绩,value值是学生ID,score是他的成绩,我们可以对成绩按分数进行排序就可以得到他的名次;
6、高级命令
- keys:全量遍历键,用来列出所有满足正则字符串规则的key,当redis数据量比较大时,性能比较差,要避免使用
- scan:渐进式遍历键,scan参数提供了三个参数,第一个是cursor整数值,第二个是key的正则模式,第三个是遍历的limit hint;
第一次遍历时,cursor值为0,然后将返回结果中第一个整数值作为下一次遍历的cursor,一直遍历到返回的cursor值为0时结束
Redis存储键值对实际使用的是hashtable的数据结构
- info:查看redis服务运行信息,分为9大块,每个块都有非常多的参数,这9个块分别是:
1、Server 服务器运行的环境参数
2、Clients 客户端相关信息
3、Memory 服务器运行内存统计数据
4、Persistence 持久化信息
5、Stats 通用统计数据
6、Replication 主从复制相关信息
7、CPU CPU 使用情况
8、Cluster 集群信息
9、KeySpace 键值对统计数量信息
7、核心原理
1、Redis的单线程和高性能
Redis为什么这么快,尤其是其采用单线程
因为它的所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题;
而且正因为Redis是单线程,所以要小心使用Redis指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一步小心就可能会导致Redis卡顿;Redis单线程处理多个并发客户端连接:IO多路复用
Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
Nginx也是采用IO多路复用原理解决C10k问题
2、持久化
- RDB快照(snapshot)
在默认情况下,Redis将数据库快照保存在名字为dump.rdb的二进制文件中。
可以对Redis进行设置,让它在“N秒内数据集中至少有M个改的”这个条件被满足时,自动保存一次数据集。
//在满足“60s内至少有1000个键被改动”这一条件时,自动保存一次数据集
save 60 100
- AOF(append-only file)
快照功能并不是非常耐久(durable):如果Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的哪些数据。
从1.1版本开始,Redis增加了一种完全耐久的持久化方式,AOF持久化,将修改的每一条指令记录进文件,就是说,每当Redis执行一个改变数据集的命令时(比如SET),这个命令就会被追加到AOF文件的末尾,这样的话,当Redis重新启动时,程序就可以通过重新执行AOF文件中的命令来达到重建数据集的目的。
//通过修改配置文件来打开AOF功能
appendonly yes
可以配置 Redis 多久才将数据 fsync 到磁盘一次,有三个选项:
1、每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全。
2、每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
3、从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
RDB vs AOF
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。Redis 4.0 混合持久化
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。AOF在重写(aof文件里可能有太多没用指令,所以aof会定期根据内存的最新数据生成aof文件)时将 rdb文件的内容和增量的 AOF 日志文件存在一起,AOF根据配置规则在后台自动重写,也可以人为执行命令bgrewriteaof重写AOF。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
//开启混合持久化:
aof-use-rdb-preamble yes
混合持久化aof文件结构
3、缓存淘汰策略
当 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 算法淘汰
4、缓存失效策略
定时删除策略
在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对可以进行删除。
优点:保证内存尽快释放
缺点:若key过多,删除这些key会占用很多cpu时间,而且每个key创建一个定时器,性能影响严重惰性删除策略
key过期的时候不删除,每次从数据库redis获取key的时候去检查是否过期,若过期,则删除,同时返回null
优点:cpu时间占用比较少
缺点:若key很长时间没有被获取,将不会被删除,可能造成内存泄漏定期删除策略
每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key的操作
优点:可以通过控制删除操作的时长和频率,来减少cpu时间的占用,可以避免惰性删除时候内存泄漏的问题
缺点:对内存友好方面,不如定时删除策略
对CPU友好方面,不过惰性删除策略
Redis一般采用:惰性策略 + 定期策略两个相结合
Redis 的缓存淘汰策略用于处理内存不足时的需要申请额外空间的数据;
Redis 的过期策略用于处理过期的缓存数据