Redis学习笔记(第三章——Redis数据类型)
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Redis学习笔记(第三章——Redis数据类型),小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含8448字,纯文字阅读大概需要13分钟。
内容图文
![Redis学习笔记(第三章——Redis数据类型)](/upload/InfoBanner/zyjiaocheng/914/da13195eb8764961b18b14d7b97d59e6.jpg)
摘录自《Redis设计与实现 第一版》黄健宏
原书用c语言进行结构编写,我按着自己的理解用java语言翻译了一下,可能会有些出入
Redis的键可以保存不同类型的值,为了让类型的操作更加方便,Redis创建了自己的类型系统
对象处理机制
简述
Redis命令中,对键(key)的处理占了一大补分,而根据键的类型,它所能执行的命令各有不同,不同类型的键,实现命令时,存在不同的处理方式(如删除列表键和删除字符串键就不太一样)
redis类型系统
- RedisObject对象
- 基于RedisObject对象的类型检查
- 基于RedisObject对象的显式多态函数(?)
- 对RedisObject进行分配、共享、销毁
class RedisObject{
Type type; 对象类型(重要)
Object notused; 对齐位
Object encoding; 底层实现方式(重要)
Object LRU; LRU时间
int refcount; 引用计数
Object obj; 对象本身(重要)
enum Type{
REDIS_STRING(0), 字符串
REDIS_LIST(1), 列表
REDIS_SET(2), 集合
REDIS_ZSET(3), 有序集
REDIS_HASH(4); 哈希表
...
}
enum encoding{
REDIS_ENCODING_RAW(0), 字符串
REDIS_ENCODING_INT(1), 整数
REDIS_ENCODING_HT(2), 字典
REDIS_ENCODING_ZIPMAP(3), zipmap(Redis2.6以后,不再是任何数据类型的底层结构)
REDIS_ENCODING_LINKEDLIST(4), 双端链表
REDIS_ENCODING_ZIPLIST(5), 压缩列表(内存映射数据结构)
REDIS_ENCODING_INTSET(6), 整数集合(内存映射数据结构)
REDIS_ENCODING_SKIPLIST(7); 跳跃表
...
}
}
命令的类型检查和多态
- 根据给定的key,找到对应的RedisObject实例,未找到就返回null
- 检查该实例的type域,看看域值是否和执行命令所需的类型相符,如果不符,返回类型错误
- 根据该实例的encoding域的值,选择适合的操作方法处理底层的数据结构
- 返回数据结构的操作结果作为指令的返回值
对象共享
- 某些对象在Redis中非常常见,Redis在内部使用了一个Flyweight模式,预分配了一些常见的值对象,并在多个数据结构间共享这些对象,避免了重复分配
- 预分配的值对象包括
- 各种命令的返回值,包括(OK,ERROR,WRONGTYPE等),这些回复值因为直接返回给客户端,所以无需共享
- 包括0在内,小于REDIS_SHARED_INTEGERS的默认整数(REDIS_SHARED_INTEGERS默认值为10000,即0~10000),当某个命令的输入值是小于REDIS_SHARED_INTEGERS的整数对象,那么,当该对象被保存进数据库时,Redis会释放原对象,而将值得指针指向该值对应的预分配对象
- 注意:对象对象只能被带指针的数据结构使用(用Java理解就是,共享指针只能被值域类型是Object对象而非基本数据类型的的数据结构使用),像整数集和压缩列表这种内存映射数据结构,保存的是字符串、证书等字面值(Java中的基本数据类型,整数是int而非包装类Integer,字符串是char[]而非Character[]),是无法共享对象的
引用计数及对象销毁
因为redis是用C语言编写,本身不会自动释放内存,需要程序员主动释放(Java所使用的虚拟机HotSpot有内存回收机制GC),RedisObject系统使用引用计数来维持和销毁对象(int refcount):
- 每个对象的int refcount域,指示对象被引用了多少次
- 初始化值为1
- 当该对象被引用时,int refcount +1
- 取消该对象引用时,int refcount -1
- 当int refcount变成0时,该对象及对象中Object obj引用指向的对象,会被释放
小结
- Redis使用自己的RedisObject系统来实现类型判断、命令多态和基于引用计数的垃圾回收
- 一种Key(Redis键)可以有多种底层实现
- Redis会预分配一些常用的数据对象,并通过对象共享来减少内存的占用,避免频繁地为小对象分配内存
字符串
REDIS_STRING是Redis使用得最为广泛的数据类型
- SET、GET命令的操作对象
- 数据库中所有键的数据类型
- 执行命令时提供给Redis的参数
字符串编码
- REDIS_ENCODING_INT:使用long类型保存long类型值
- REDIS_ENCODING_RAW:使用sdfhdr结构保存sds
- 即只有能表示为long类型(基本数据类型)的值,才会以整数(基本数据类型)的形式保存,其他类型的整数、小数和字符串,都是用sdshdr结构进行保存
编码的选择
- 新创建的字符串默认使用REDIS_ENCODING_RAW编码,在将字符串作为键或者值保存进数据库时,程序会尝试将字符串转为REDIS_ENCODING_INT编码
字符串命令的实现
- 基本上是通过包装sds数据结构的操作函数来实现的
哈希表
- REDIS_HASH是HSET和HLEN等命令的操作对象
- 编码一:压缩列表REDIS_ENCODING_ZIPLIST(默认)
- 编码二:字典REDIS_ENCODING_HT
压缩列表编码的哈希表(默认)
- 键和值一同推入压缩列表,形成以下键值对结构
- 新添加的key-value会被添加到压缩列表的表尾
- 进行查找/删除/更新时,首先定位key的位置,然后+1定位value的位置
字典编码的哈希表
- 将哈希表的键(key,字符串对象)作为字典的键,将哈希表的值保存为字典的值(value,字符串对象)
编码的选择
- 默认使用“压缩列表”编码,当满足任意以下一个条件时,切换为“字典”
- 哈希表中某个键或某个值长度大于server.hash_max_ziplist_value(默认64)
- 压缩列表节点数大于server.hash_max_ziplist_entries(默认512)
哈希命令的实现
全部是对字典和压缩列表操作函数的包装,以及几个在两种编码间进行转换的函数
列表
- REDIS_LIST时LPUSH、LRANGE等命令的操作对象
- 编码一:压缩列表REDIS_ENCODING_ZIPLIST(默认)
- 编码二:双端链表REDIS_ENCODING_LINKEDLIST
编码的选择
- 默认使用“压缩列表”编码,当满足任意以下一个条件时,切换为“双端链表”
- 试图往列表中添加一个字符串,且长度超过server.list_max_ziplist_value(默认值64)
- 压缩列表节点数大于server.list_max_ziplist_entries(默认512)
列表命令的实现
两种底层实现的抽象方式和列表的抽象方式很接近,所以列表命令几乎就是通过一对一地映射到底层数据结构地操作来实现的
阻塞的条件
- BLPOP、BRPOP、BRPOPLPUSH三个命令都可能造成客户端阻塞,称为列表的阻塞原语
- 阻塞源于不一定会造成客户端阻塞:
- 当这些命令被用于空列表时,阻塞客户端
- 如果被处理的列表不为空,它们执行无阻塞版本的LPOP、RPOP、RPOPLPUSH
阻塞
- 阻塞一个客户端的步骤
- 将客户端状态设为“正在阻塞”,并记录阻塞该客户端的各个键,以及阻塞的最长时间(timeout)等数据
- 将客户端信息记录到server.db[i]->blocking_keys中(i为客户端所使用的数据库号码)
- 继续维持客户端和服务器之间的网络连接,但不再向客户端传送任何信息,造成客户端阻塞
- 步骤2用java理解就是,每个数据库都自己维护了一个blocking_keys域,其类型是字典,键就是哪些造成客户端阻塞的键,而值是一个链表,保存了所有因为这个键被阻塞的客户端的标志信息(被同一个键所阻塞的客户端可能不止一个)
脱离阻塞
- 被动脱离:其他客户端为造成阻塞的键推入了新元素
- 主动脱离:到达执行阻塞原语时设定的最大阻塞时间
- 强制脱离:客户端强制终止和服务器的连接,或者服务器停机
被动脱离(因LPUSH、RPUSH、LINSERT等添加命令而取消阻塞)
- pushGenericCommand函数:
- 判断key是否为空
- 如果不为空,进入步骤2
- 如果为空,检查key是否存在于数据库的阻塞域blocking_keys中,如果存在,进入步骤3
- 判断key类型是否为列表
- 否,返回类型错误,结束
- 是,直接将值推入列表,结束
- 创建ReadyList对象,RedisDb类型引用指向该键所在数据库对象,RedisObject类型引用指向该键对象,并将新状态的ReadyList对象并添加到server.ready_keys链表中,将给定的值添加到给定的列表键key中
class ReadyList{
RedisDb db; 该键所在的数据库
RedisObject key;造成阻塞的键
}
- handleClientsBlockedOnLists函数
- 如果server.ready_keys链表不为空,那么弹出该链表的表头元素ReadyList对象
- 判断ReadyList对象的域对象db找到对应数据库,搜索其blocking_keys域,找到所有因为该ReadyList对象的key域而阻塞的客户端
- 并将该key所对应的value作为返回值,作为阻塞原语的返回值
- 删除该数据库对象blocking_keys域中该key中的弹出客户端标志位信息,取消客户端阻塞状态
主动脱离
每次Redis调用常规操作函数执行时,程序都会检查所有连接到服务器的客户端,查看那些处于“正在阻塞”状态的客户端的最大阻塞时限是否已经过期,如果是,返回空白回复,撤销对客户端的阻塞
集合
- SADD、SRANDMEMBER等命令的操作对象
- 编码一:整数集REDIS_ENCODING_INTSET
- 编码二:字典REDIS_ENCODING_HT
编码的选择
- 第一个添加到集合的元素,决定了创建集合时使用的底层数据结构
- 如果第一个元素是可以表示为long类型的基本数据类型,那么初始化编码为“整数集”
- 否则,初始化编码为“字典”
- 当一个集合使用“整数集”编码,满足以下任何一个条件时,会被转换成“字典”
- intset保存的整数个数超过server.set_max_intset_entries(默认512)
- 试图往集合里添加一个新元素,并且该元素不能用基本数据类型long表示
字典编码的集合
当用“字典”进行编码时,集合将元素保存到字典的键里,而值统一设置为NULL
有序集
- ZADD、ZCOUNT等命令的操作对象
- 编码一:压缩列表REDIS_ENCODING_ZIPLIST
- 编码二:跳跃表REDIS_ENCODING_SKIPLIST
编码的选择
- 第一个添加到集合的元素,决定了创建集合时使用的底层数据结构,如果第一个元素满足以下任一条件,那么初始化编码为“压缩列表”,否则为“跳跃表”
- server.zset_max_ziplist_entries的值大于0(默认128)
- 元素的member长度小于server.zset_max_ziplist_value(默认64)
- 当满足以下条件之一时,“压缩列表”转为“跳跃表”
- ziplist保存的元素数量超过server.zset_max_ziplist_entries的值(默认128)
- ziplist新添加元素的member长度大于server.zset_max_ziplist_value(默认64)
压缩列表编码的有序集
- member和score一通压入压缩列表,形成以下结构
- 多个元素间按socre值从小到大排序,如果两个元素score相同,按字典序对member进行比对
- 虽然socre按值排序,但是压缩列表的指针节点只能线性移动,所以查找的复杂度为O(N)
- 增加、删除、修改的复杂度都不低于O(N)
跳跃表编码的有序集
- 有序集元素用以下结构进行保存
class ZSet{
Dict dict;
ZSkipList zsl;
}
- 同时使用字典和跳跃表保存有序集,两个数据结构通过引用指向这两个值来节约空间(不需要创建同样的两份对象)
- member用RedisObject对象保存
- score用基本数据类型double保存
- 通过字典结构,将member作为键,score作为值,可以在复杂度O(1)内:
- 检查member是否存在于有序集内
- 取出member对应的score值(ZSCORE)
- 通过使用跳跃表
- 在O(log2N)期望时间、O(N)最坏时间内根据score对member进行定位
- 范围性查找和处理操作,这是高效实现ZRANGE、ZRANK、ZINTERSTORE等命令的关键
- 两种结构的同时存在,有序集可以高效地实现按成员查找和按顺序查找两种操作
内容总结
以上是互联网集市为您收集整理的Redis学习笔记(第三章——Redis数据类型)全部内容,希望文章能够帮你解决Redis学习笔记(第三章——Redis数据类型)所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。