1-9章
作者:互联网
2、简单动态字符串
-
redis中没有使用c语言的字符串,而是用到了简单动态字符串(SDS),set name "hello"表示的意思是:键值对的键是一个字符串对象,底层实现是一个保存字符串name的SDS。
-
SDS的作用:处理用来保存字符串值之外,还用作缓冲区(AOF模块中的AOF缓冲区、客户端状态中的输入缓冲区)
-
SDS定义
-
buf中最后一个'\0'并不计算在len中
-
-
为什么使用SDS形式的字符串而不是直接用C字符串?
-
提高了性能:C字符串获取长度的时间复杂度为O(N),而SDS是O(1)
-
杜绝缓冲区溢出:因为C字符串并不会计算自身长度,假如拼接字符串的时候就有可能造成溢出,但是SDS在执行拼接操作的API时会先检查空间是否足够,不够的话先扩容然后拼接
-
减少修改字符串时带来的内存重分配次数:C字符串增加或者减少字符串操作时都需要进行内存重分配,但是SDS通过未使用空间实现了空间预分配和惰性空间释放两种优化策略
-
空间预分配优化字符串增长操作:不仅会分配必要的空间还会分配额外的空间
-
惰性空间优化字符串缩短操作:空间不会被回收,而是加入free中
-
-
二进制安全:SDS API会以处理二进制的方式来处理SDS中存放的buf中的数据,redis不仅可以存放文本,还可以存放任意二进制的数据
-
兼容部分C字符串函数:SDS会遵循C字符串以空字符结尾的惯例,这样可以使用C函数库中的函数
-
3、链表
-
用到链表的地方:列表键、发布与订阅、慢查询、监视器
4、字典
-
redis的数据库的底层就是使用字典实现的,对数据库的增删改查也是建立在操作字典之上。字典还是哈希键的底层实现之一
-
字典使用哈希表作为底层实现
-
数据结构表示:
-
typedef struct dictht{
dictEntry ** table; //哈希表数组
undigned long size; //哈希表大小
unsigned long sizemask; //哈希表大小掩码,用于计算索引值,size-1
unsigned long used; //已有的结点的数量
}dictht;
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
struct dictEntry *next;
}dictEntry; -
redis中的字典如下表示:
-
typedef struct dict{
dictType *type; //类型特定函数
void *privdata; //私有数据
dictht ht[2]; //哈希表
int rehashidx //rehash索引,记录了rehash目前的进度
}dict;-
一般情况下字典只使用ht[0]作为哈希表,ht[1]只会对ht[0]进行rehash时使用
-
-
当要插入键值对的时候计算出哈希值和索引值,redis使用Murmurhash2算法计算键的哈希值
-
redis使用链地址法解决哈希冲突。为了速度考虑,冲突的结点插入到链表的表头位置
-
为了让哈希表的负载因子维持在合理的范围之内,程序需要对哈希表进行rehash操作,过程如图所示:
-
渐进式rehash:rehash操作并不是一次、集中式完成的,而是分多次渐进式完成的。因为当ht[0]数据很多的时候,一次性的rehash会造成程序的卡顿。过程为:当对ht[0]中的键值对进行添加、删除、查找、更新操作的时候,不仅完成此操作还要把值移动到ht[1],但是添加操作直接把值加到ht[1]
5、跳跃表
-
使用场景:redis中的有序集合键底层、集群结点中用作内部数据结构
-
跳跃表中的概念:层、后退指针、跨度、前进指针、分值、成员对象
-
跨度:实际用来计算排位的,在查找某个节点的过程中,把沿途的所有访问的层累积起来就是排位
-
成员对象:实际是一个值,它指向字符串对象,底层实现是SDS
-
-
每个跳跃表的层高都是1-32之间的随机数
6、整数集合
-
当一个集合中只包含整数元素,并且元素的个数不多时,redis会用整数集合作为集合键的底层实现,保证集合中的元素不重复
7、压缩列表
-
redis中的列表键和哈希键的底层实现用压缩列表之一,当列表项是小的整数值或者短的字符串时
-
压缩列表是由一系列特殊编码的连续的内存块组成的顺序型数据结构
-
其中压缩列表节点entry包含
-
previous_entry_length:前一个节点的长度,可以从表尾向表头遍历
-
encoding:记录了节点的content属性所保存数据的类型以及长度
-
content:负责保存节点的值,可以是一个字节数组或者整数
-
-
连锁更新情况
-
空间扩展:比如e1 e2 e3中的长度位于250-253字节之间,所有previous_entry_length长度为1字节即可,但在e1前面加入一个255字节的数据,那么e1的previous_entry_length就要变成5字节,这时e1就会大于254,所以e2中的previous_entry_length也要变化,所以引发了一系列的改变
-
同样删除节点也会引发连锁更新
-
9、数据库
-
typedef struct redisDb{
dict *dict; //键空间
dict *expires; //过期键
}
-
键空间
-
键空间的键也就是数据库的键,每个键都是一个字符串对象。数据库的键空间是一个字典
-
键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种Redis对象。
-
-
读写键空间时的维护操作
-
在读取一个键之后(读操作和写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中( hit)次数或键空间不命中( miss)次数
-
在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间,使用OBJECT idletime <key>命令可以查看键key的闲置时间。
-
如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键
-
服务器每次修改一个键之后,都会对脏( dirty)键计数器的值增1,这个计数器会触发服务器的持久化以及复制操作
-
如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知
-
-
设置键的生存时间和过期时间
-
PERSIST:可以在过期字典中查找过期的键,并解除键和过期时间的关联
-
过期键的删除策略
-
定时删除
-
优点:到期就删除过期键,释放了内存
-
缺点:如果过期键的数量挺多,那么删除的时候就会占用CPU较长时间,会对服务器的响应时间和吞吐量造成影响
-
-
惰性删除:只有在取出键时才对过期与否进行检查
-
缺点:对内存不友好
-
-
定期删除
-
定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。通过定期删除过期键,定期删除策略有效地减少了因为过期键而带来的内存浪费。
-
定期删除策略的难点是确定删除操作执行的时长和频率
-
-
-
Redis的过期键删除策略
-
使用的是惰性删除和过期删除
-
-
RDB对过期键的处理
-
执行SAVE命令或者BGSAVE命令时会生成RDB文件,会把不过期的键复制过去
-
如果服务器以主服务器模式运行:只载入不过期键
-
如果以服务器模式运行:会载入所有的键
-
-
AOF对过期键的处理
-
当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF 文件不会因为这个过期键而产生任何影响。
-
当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加( append)一条DEL命令,来显式地记录该键已被删除。
-
和生成RDB文件时类似,在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中。
-
-
复制模式对过期键的处理
-
从服务器的过期键删除动作由主服务器控制
-
从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键
-
通过主服务器控制从服务器删除过期键,保证了主从服务器数据的一致性
-
-
当Redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知。
标签:,删除,SDS,过期,哈希,字符串,服务器 来源: https://www.cnblogs.com/zyj23/p/16124883.html