redis基础

1:Redis数据结构与对象

简单动态字符串

Redis 没有直接使用 语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串 (simple dynamic string, SDS) 的抽象类型,并将 SDS 用作 Redis 的默认字符串表示。

除了用来保存数据库中的字符串值之外, SDS 还被用作缓冲区 (buffer)

SDS 遵循 字符串以空字符结尾的惯例,保存空字符的 字节空间不计算在 SDS的len 属性里面

SDS与C字符串的区别

常数复杂度获取字符串长度

SDS有一个len属性

杜绝缓冲区溢出

与C字符串不同, SDS 的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当 SDS-API 需要对 SDS 进行修改时, API 会先检查 SDS 的空间是否满足修改所需的要求,如果不满足的话, API 会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改
操作,所以使用 SOS 既不需要手动修改 SOS 的空间大小,也不会出现前面所说的缓冲区溢出问题。

减少修改字符串时带来的内存重分配次数

为了避免C字符串的频繁修改内存缺陷, SDS 通过未使用空间解除了字符串长度和底层数组长度之间的关联:在 SDS 中, buf 数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由 SDS free 属性记录。
通过未使用空间, SDS 实现了空间预分配惰性空间释放两种优化策略。

空间预分配用于优化 SDS 的字符串增长操作:当 SDS的API 对一个 SDS 进行修改,并且需要对 SDS 进行空间扩展的时候,程序不仅会为 SDS 分配修改所必须要的空间,还会为SDS分配额外的未使用空间。

惰性空间释放用于优化 SDS 的字符串缩短操作:当 SDS的API 需要缩短 SDS 保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用 free 属性将这些字节的数量记录起来,并等待将来使用。

二进制安全

C字符串中的字符必须符合某种编码(比如 ASCil), 并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读人的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。

兼容部分C字符串函数

SDS-API

链表

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。

链表和链表节点的实现

链表和链表节点的 API

重点回顾

字典

字典,又称为符号表 (symbol table) 、关联数组 (associative array) 或映射 ,一种用于保存键值对 (key-value pair) 的抽象数据结构
在字典中,一个键 (key) 可以和一个值 (value) 进行关联(或者说将键映射为值),这些关联的键和值就称为键值对
字典中的每个键都是独一无二的,程序可以在字典中根据键查找与之关联的值,或者通过键来更新值,又或者根据键来删除整个键值对

Redis 的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

Redis 的哈希表使用链地址法 (separate chaining) 来解决键冲突

随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子 (load factor) 维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。
扩展和收缩哈希表的工作可以通过执行 rehash (重新散列)操作来完成

跳跃表

跳跃表 (skiplist) 是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均 O(logN) 、最坏 O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媳美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。

Redis 使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员 (member) 是比较长的字符串时, Redis 就会使用跳跃表来作为有序集合键的底层实现。

跳跃表的实现

跳跃表节点

整数集合

压缩列表

​ 压缩列表 (ziplist) 是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么 Redis会使用压缩列表来做列表键的底层实现。

对象

​ 在前面的数个章节里,我们陆续介绍了 Redis 用到的所有主要数据结构,比如简单动态字符串 (SDS) 、双端链表、字典、压缩列表、整数集合等等。
​ Redis 并没有直接使用这些数据结构来实现键值对数据库,而是基千这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们前面所介绍的数据结构。
​ 通过这五种不同类型的对象, Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。
​ 除此之外, Redis 的对象系统还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外, Redis 还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一
个对象来节约内存。
​ 最后, Redis 的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了 maxmemory 功能的情况下,空转时长较大的那些键可能会优先被服务器删除。

对象的类型和编码

​ Redis 使用对象来表示数据库中的键和值,每次当我们在 Redis 的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。

对象的 ptr 指针指向对象的底层实现数据结构,而这些数据结构由对象的 encoding属性决定。
encoding 属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,这个属性的值可以是表 8-3 列出的常量的其中一个。

使用 OBJECT ENCODING 命令可以查看一个数据库键的值对象的编码:

2:Redis 5种基本数据类型

字符串对象(String)

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于 32 字节,那么字符串对象将使用一个简单动态字符串 (SDS) 来保存这个字符串值,并将对象的编码设置为raw

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于 32 字节,那么字符串对象将使用 embstr 编码的方式来保存这个字符串值。

列表对象(List)

列表对象的编码可以是 ziplist 或者 linkedlist

哈希对象(Hash)

哈希对象的编码可以是 ziplist 或者 hashtable

集合对象(Set)

集合对象的编码可以是 intset 或者 hashtable

有序集合对象(Zset)

有序集合的编码可以是 ziplist 或者 skiplist。

ziplist 编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员 (member), 而第二个元素则保存元素的分值 (score) 。压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。

zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的 object 属性保存了元素的成员,而跳跃表节点的score 属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围型操作。比如 ZRANK ZRANGE 等命令就是基于跳跃表 API 来实现的。

除此之外, zset 结构中的 diet 字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以用 0(1) 复杂度查找给定成员的分值, ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性。

因为C语言并不具备自动内存回收功能,所以 Redis 在自己的对象系统中构建了一个引用计数 (reference counting) 技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。

重点回顾:

3:Redis数据库

本章将对 Redis 服务器的数据库实现进行详细介绍,说明服务器保存数据库的方法,客户端切换数据库的方法,数据库保存键值对的方法,以及针对数据库的添加、删除、查看、更新操作的实现方法等。除此之外,本章还会说明服务器保存键的过期时间的方法,以及服
务器自动删除过期键的方法

服务器中的数据库

切换数据库

每个 Redis 客户端都有自己的目标数据库,每当客户端执行数据库写命令或者数据库读命令的时候,目标数据库就会成为这些命令的操作对象。默认情况下, Redis 客户端的目标数据库为 0号数据库,但客户端可以通过执行SELECT 命令来切换目标数据库。

在服务器内部,客户端状态 redisClie吐结构的 db 属性记录了客户端当前的目标数据库,这个属性是一个指向 redisDb 结构的指针

数据库键空间

设置键的生存时间或过期时间

1:redis为什么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单

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

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

3:持久化方式

RDB 持久化
将某个时间点的所有数据都存放到硬盘上。
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长。
AOF 持久化
将写命令添加到 AOF 文件(Append Only File)的末尾。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:
选项同步频率always每个写命令都同步everysec每秒同步一次no让操作系统来决定何时同步
always 选项会严重减低服务器的性能;
everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。

4:缓存过期淘汰策略

volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru从所有数据集中挑选最近最少使用的数据淘汰
allkeys-random从所有数据集中任意选择数据进行淘汰
noeviction禁止驱逐数据

5:与mysql数据一致性

img

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致。

  1. 先更新数据库,再更新缓存
  2. 先删除缓存,再更新数据库
  3. 先更新数据库,再删除缓存

6:zset底层实现

跳跃表:

与红黑树等平衡树相比,跳跃表具有以下优点:

  • 插入速度非常快速,因为不需要进行旋转等操作来维护平衡性;
  • 更容易实现;
  • 支持无锁操作。

7:缓存穿透、缓存击穿、缓存雪崩

缓存穿透:就是客户持续向服务器发起对不存在服务器中数据的请求。客户先在Redis中查询,查询不到后去数据库中查询。

缓存击穿:就是一个很热门的数据,突然失效,大量请求到服务器数据库中

缓存雪崩:就是大量数据同一时间失效。

打个比方,你是个很有钱的人,开满了百度云,腾讯视频各种杂七杂八的会员,但是你就是没有netflix的会员,然后你把这些账号和密码发布到一个你自己做的网站上,然后你有一个朋友每过十秒钟就查询你的网站,发现你的网站没有Netflix的会员后打电话向你要。你就相当于是个数据库,网站就是Redis。这就是缓存穿透。
大家都喜欢看腾讯视频上的《水果传》,但是你的会员突然到期了,大家在你的网站上看不到腾讯视频的账号,纷纷打电话向你询问,这就是缓存击穿
你的各种会员突然同一时间都失效了,那这就是缓存雪崩了。

放心,肯定有办法解决的。
缓存穿透:
1.接口层增加校验,对传参进行个校验,比如说我们的id是从1开始的,那么id<=0的直接拦截;
2.缓存中取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,这样可以防止攻击用 户反复用同一个id暴力攻击

​ 3布隆过滤器

缓存击穿
1:最好的办法就是设置热点数据永不过期,拿到刚才的比方里,那就是你买腾讯一个永久会员

2:分布式锁

在DB往数据库写入数据时,加锁,防止并发

缓存雪崩:

1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注