您的位置:首页 > 数据库 > Redis

Redis数据类型

2017-08-09 11:43 141 查看
Redis数据类型
字符串
哈希表
列表
集合
有序集
 
对象处理机制
对键能执行的命令各部相同,但有些又是通用的。
 
Redis 必须让每个键都带有类型信息,使得程序可以检查键的类型,并为它选择合适的处理方式
因为各类型的底层实现(Redis 称为编码,encoding),各不相同,因此程序必须根据键所采取的编码进行不同的操作。
 
比如说集合类型就可以由字典和整数集合两种不同的数据结构实现。
当用户执行ZADD命令时,应该不必关心结合使用的是什么编码,只要能将新元素添加到集合就可以了。
因此,操作数据类型的命令除了要对键的类型进行检查之外,还需要根据数据类型的不同编码进行多态处理
 
Redis构建了自己的类型系统
redisObject 对象
基于 redisObject 对象的类型检查
基于 redisObject 对象的显式多态函数
对 redisObject 进行分配、共享和销毁的机制
 
redisObject 数据结构
redisObject 是 Redis 类型系统的核心,数据库中的每个键、值,以及 Redis 本身处理的参数,都表示为这种数据类型。
 

redisObject 的定义位于 redis.h :

/*
* Redis 对象
*/
typedef struct redisObject {
// 类型
unsigned type:4;

// 对齐位
unsigned notused:2;

// 编码方式
unsigned encoding:4;

// LRU 时间(相对于 server.lruclock)
unsigned lru:22;

// 引用计数
int refcount;

// 指向对象的值
void *ptr;
} robj;


 

type 、 encoding 和 ptr 是最重要的三个属性
 
type(类型)
REDIS_STRING 0 // 字符串
REDIS_LIST 1      // 列表
REDIS_SET 2       // 集合
REDIS_ZSET 3     // 有序集
REDIS_HASH 4    // 哈希表

encoding(编码)
REDIS_ENCODING_RAW 0             // 编码为字符串
REDIS_ENCODING_INT 1               // 编码为整数
REDIS_ENCODING_HT 2                // 编码为字典
REDIS_ENCODING_ZIPMAP 3        // 编码为 zipmap
REDIS_ENCODING_LINKEDLIST 4   // 编码为双端链表
REDIS_ENCODING_ZIPLIST 5          // 编码为压缩列表
REDIS_ENCODING_INTSET 6          // 编码为整数集合
REDIS_ENCODING_SKIPLIST 7        // 编码为跳跃表

ptr
指向实际保存值得数据结构
这个数据结构由type属性 和encoding 属性决定

例子
redisObject 的 type属性为REDIS_LIST,encoding属性为REDIS_ENCODING_LINKEDLIST
那么这个对象就是一个Redis列表,它的值保存在一个双端链表内,而ptr指针就指向这个双端链表

 

 

 

命令的类型检查和多态
当执行一个处理数据类型的命令时,Redis执行以下步骤:
根据给定 key ,在数据库字典中查找和它像对应的 redisObject ,如果没找到,就返回NULL 。
检查 redisObject 的 type 属性和执行命令所需的类型是否相符,如果不相符,返回类型错误。
根据 redisObject 的 encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构。
返回数据结构的操作结果作为命令的
bad3
返回值。
 
对键key执行LPOP命令的完整过程:


 

 
对象共享
Redis 在内部使用了一个 Flyweight 模式 :通过预分配一些常见的值对象,
并在多个数据结构之间共享这些对象,程序避免了重复分配的麻烦,也节约了一些CPU时间。
 
Redis 预分配的值对象有如下这些:
各种命令的返回值,比如执行成功时返回的 OK ,执行错误时返回的 ERROR ,类型错误时返回的 WRONGTYPE ,命令入队事务时返回的 QUEUED ,等等。

包括0在内,小于redis.h/REDIS_SHARED_INTEGERS的所有整数

(REDIS_SHARED_INTEGERS的默认值为10000)

共享对象只能被字典和双端链表这类能带有指针的数据结构使用。
像整数集合和压缩列表这些只能保存字符串、整数等字面值的内存数据结构,就不能使用共享对象。
 
引用计数以及对象的销毁
C语言本身没有自动释放内存的相关机制
以及对象被引用了多少次?
 
Redis使用引用计数来负责维持和销毁对象
每个redisObject 结构都带有一个 refcount 属性,指示这个对象被引用了多少次。
当新创建一个对象时,它的 refcount 属性被设置为 1 。
当对一个对象进行共享时,Redis 将这个对象的 refcount 增一。
当使用完一个对象之后,或者取消对共享对象的引用之后,程序将对象的 refcount 减一。
当对象的 refcount 降至 0 时,这个 redisObject 结构,以及它所引用的数据结构的内存,都会被释放。
 
小结
Redis 使用自己实现的对象机制来实现类型判断、命令多态和基于引用计数的垃圾回收。
一种 Redis 类型的键可以有多种底层实现。
Redis 会预分配一些常用的数据对象,并通过共享这些对象来减少内存占用,和避免频繁地为小对象分配内存。
 
字符串
REDIS_STRING (字符串)是 Redis 使用得最为广泛的数据类型,它除了是 SET 、 GET 等命令的操作对象之外,
数据库中的所有键,以及执行命令时提供给Redis的参数,都是用这种类型保存的。
 
字符串类型分别使用REDIS_ENCODING_INT和REDIS_ENCODING_RAW两种编码:
• REDIS_ENCODING_INT 使用 long 类型来保存 long 类型值。

• REDIS_ENCODING_RAW 则使用 sdshdr 结构来保存 sds (也即是 char* )、 long long 、
double 和 long double 类型值。
 
在Redis中,只有能表示为long 类型的值,才会以整数的形式保存其他类型的整数,小数,字符串都是用sdshdr结构来保存的


 

 
默认编码REDIS_ENCODING_RAW即使用sdshdr保存数据
 
哈希表


 

当哈希表使用字典编码时,程序将哈希表的键( key)保存为字典的键,将哈希表的值( value)保存为字典的值。
哈希表所使用的字典的键和值都是字符串对象。
包含三个键值对的哈希表:

 

 

默认使用ziplist,压缩列表作为哈希表的编码
 
列表
REDIS_LIST(列 表)是LPUSH ,LRANGE 等命令的操作对象


 

阻塞的条件
BLPOP,BRPOP,BRPOPLPUSH 
三个命令都可能造成客户端被阻塞,将这些命令统称为列表的阻塞原语
 
阻塞原语并不是一定造成客户端阻塞:
只有当这些命令被用于空列表时,它们才会阻塞客户端
如果被处理的列表不为空的话,它们就执行无阻塞版本的 LPOP 、 RPOP 或 RPOPLPUSH 命令。
 
BLPOP决定是否对客户端进行阻塞过程:


 

 
阻塞
当一个阻塞原语的处理目标为空键时,执行该阻塞原语的客户端就会被阻塞
阻塞一个客户端需要执行以下步骤:
1. 将客户端的状态设为“正在阻塞” ,并记录阻塞这个客户端的各个键,以及阻塞的最长时限( timeout)等数据。
2. 将客户端的信息记录到 server.db[i]->blocking_keys 中(其中 i 为客户端所使用的数据库号码)。
3. 继续维持客户端和服务器之间的网络连接,但不再向客户端传送任何信息,造成客户端阻塞。
 
解除阻塞
server.db[i]->blocking_keys 是一个字典,字典的键是那些造成客户端阻塞的键,
而字典的值是一个链表,链表里保存了所有因为这个键而被阻塞的客户端(被同一个键所阻塞的客户端可能不止一个)

[b] 

[/b]
 

当客户端被阻塞之后,脱离阻塞状态有以下三种方法:
1. 被动脱离:有其他客户端为造成阻塞的键推入了新元素。

2. 主动脱离:到达执行阻塞原语时设定的最大阻塞时间。

3. 强制脱离:客户端强制终止和服务器的连接,或者服务器停机
 
lpush rpush linsert 
推入新元素
内部均由pushGenericCommand 去做
pushGenericCommand 函数执行以下两件事:
检查这个键是否存在于前面提到的 server.db[i]->blocking_keys 字典里,
如果是的话,那么说明有至少一个客户端因为这个 key 而被阻塞,
程序会为这个键创建一个redis.h/readyList 结构,并将它添加到 server.ready_keys链表中。
即将readylist 添加到服务器

将给定的值添加到列表键中。
 
虽然key3已经不再是空键,但到目前为止,被key3阻塞的客户端还没有任何一个被解除阻塞状态。
调用handleClientsBlockedOnLists,执行:

def handleClientsBlockedOnLists():

# 执行直到 ready_keys 为空
while server.ready_keys != NULL:

# 弹出链表中的第一个 readyList
rl = server.ready_keys.pop_first_node()

# 遍历所有因为这个键而被阻塞的客户端
for client in all_client_blocking_by_key(rl.key, rl.db):

# 只要还有客户端被这个键阻塞,就一直从键中弹出元素
# 如果被阻塞客户端执行的是 BLPOP ,那么对键执行 LPOP
# 如果执行的是 BRPOP ,那么对键执行 RPOP
element = rl.key.pop_element()

if element == NULL:
# 键为空,跳出 for 循环
# 余下的未解除阻塞的客户端只能等待下次新元素的进入了
break
else:
# 清除客户端的阻塞信息
server.blocking_keys.remove_blocking_info(client)
# 将元素返回给客户端,脱离阻塞状态
client.reply_list_item(element)


 

先阻塞先服务FBFS策略,这点从上面伪代码也是可以看出的,根据从前开始取列表
 
阻塞因超时而取消
每次Redis服务器常规操作函数( server cron job)执行时,程序都会检查所有连接到服务器
的客户端查看那些处于"正在阻塞"状态的客户端的最大阻塞时限是否已经过期,
 
集合

 
REDIS_SET 集合是SADD,SRANDMEMBER等命令的操作对象
 


 

 
第一个添加到集合的元素,决定了创建集合时所使用的编码
如果第一个元素可以表示为 long long 类型值(也即是,它是一个整数),那么集合的初始编码为 REDIS_ENCODING_INTSET
否则,集合的初始编码为 REDIS_ENCODING_HT
 
 
sinter,sinterstore,求并交集
sdiff,sdiffstore ,求集合差算法
 
有序集

REDIS_ZSET (有 序 集)是 ZADD ,ZCOUNT 等命令的操作对象

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: