《Redis设计与实现》--对象

目录

对象的类型与编码

字符串对象

列表对象

哈希对象

集合对象

有序集合

类型检查与命令多态

内存回收/对象共享/对象空转时长


对象的类型与编码

Redis 并没有直接使用数据结构来实现键值对数据库, 而是基于这些数据结构创建了一个对象系统, 这个系统包含以下五种:

  • 字符串对象
  • 列表对象
  • 哈希对象
  • 集合对象
  • 有序集合对象

这五种类型的对象, 每种对象都用到了至少一种前面所介绍的数据结构。而使用对象的好处可以总结为以下两点:

  • 可以在执行命令之前, 根据对象的类型来判断一个对象是否可以执行给定的命令。
  • 可以针对不同的使用场景, 为对象设置多种不同的数据结构实现, 从而优化对象在不同场景下的使用效率。

 

每次创建一个键值对时,至少会创建两个对象:键对象和值对象。其中每个对象都由以下结构体来表示。

typedef struct redisObject {
    //类型
    unsigned type:4;

    //编码
    unsigned encoding:4;

    //用于计算空转时长
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    //引用计数
    int refcount;

    //指针,指向数据结构
    void *ptr;
} robj;

其中对象的类型(type)可以是以下几个之一:

(对于 Redis 数据库保存的键值对来说, 键总是一个字符串对象, 而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种)

 

对象的编码(encoding)可以是以下几种之一:

通过 encoding 属性来设定对象所使用的编码, 而不是为特定类型的对象关联一种固定的编码, 极大地提升了 Redis 的灵活性和效率, 因为 Redis 可以根据不同的使用场景来为一个对象设置不同的编码, 从而优化对象在某一场景下的效率。

 

字符串对象

根据场景的不同,字符串对象的编码可以是以下三种:

  • int
  • embstr
  • raw

如果一个字符串对象保存的是整数值, 并且这个整数值可以用 long 类型来表示, 那么字符串对象会将整数值保存在字符串对象结构的 ptr属性里面(将 void* 转换成 long ), 并将字符串对象的编码设置为 int 。

int类型的字符串对象图示:

 

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

embstr类型的字符串对象图示:

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

raw类型的字符串对象图示:

字符串对象的3种编码转换条件和过程:

 

字符串对象命令:

字符串对象命令
命令功能
SET保存值
GET返回值
APPEND先变为raw编码,后在末尾增加值
INCRBY对int编码进行加法
INCRBYFLOAT浮点数加法
DECRBY整数值减法
STRLEN字符串长度(int类型拷贝后处理,不改变编码)
SETRANGE按索引改变值(会变为raw)
GETRANGE返回索引上的值(int类型拷贝后处理,不改变编码)

 

 

 

 

 

 

 

 

 

 

 

列表对象

列表对象的编码可以是 以下两种:

  • ziplist
  • linkedlist

ziplist 编码的列表对象使用压缩列表作为底层实现, 每个压缩列表节点(entry)保存了一个列表元素。

ziplist编码
ziplist编码

 

linkedlist 编码的列表对象使用双端链表作为底层实现, 每个双端链表节点(node)都保存了一个字符串对象, 而每个字符串对象都保存了一个列表元素

linkedlist编码

注意, linkedlist 编码的列表对象在底层的双端链表结构中包含了多个字符串对象, 这种嵌套字符串对象的行为在后面的的哈希对象、集合对象和有序集合对象中都会出现, 字符串对象是 Redis 五种类型的对象中唯一一种会被其他四种类型对象嵌套的对象。

编码转换条件:

当列表对象可以同时满足以下两个条件时, 列表对象使用 ziplist 编码,不能满足这两个条件的列表对象需要使用 linkedlist 编码。

  1. 列表对象保存的所有字符串元素的长度都小于 64 字节;
  2. 列表对象保存的元素数量小于 512 个;

 

哈希对象

哈希对象的编码可以是:以下两种

  • ziplist
  • hashtable 

ziplist 编码的哈希对象使用压缩列表作为底层实现, 每当有新的键值对要加入到哈希对象时, 程序会先将保存了键的压缩列表节点推入到压缩列表表尾, 然后再将保存了值的压缩列表节点推入到压缩列表表尾。

ziplist编码的哈希对象图示:

该压缩列表的细节如下:

 

 

hashtable 编码的哈希对象使用字典作为底层实现, 哈希对象中的每个键值对都使用一个字典键值对来保存。

其中键和值都是嵌入的字符串对象,如图:

编码转换:

当哈希对象可以同时满足以下两个条件时, 哈希对象使用 ziplist 编码,不能满足这两个条件的哈希对象需要使用 hashtable 编码。

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节;
  • 哈希对象保存的键值对数量小于 512 个;

 

集合对象

集合对象的编码可以是 以下两种:

  • intset 
  • hashtable 。

intset 编码的集合对象使用整数集合作为底层实现, 集合对象包含的所有元素都被保存在整数集合里面。

 

hashtable 编码的集合对象使用字典作为底层实现, 字典的每个键都是一个字符串对象, 每个字符串对象包含了一个集合元素, 而字典的值则全部被设置为 NULL

当集合对象可以同时满足以下两个条件时, 对象使用 intset 编码,不能满足这两个条件的集合对象需要使用 hashtable 编码。

  • 集合对象保存的所有元素都是整数值;
  • 集合对象保存的元素数量不超过 512 个;

有序集合

有序集合的编码可以是以下这两种:

  • ziplist
  • skiplist 。

ziplist 编码情况下,集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。

压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。

其中压缩列表存储的细节如图:

 

而有序集合中的skiplist编码的情况下,有序集合对象使用 zset 结构作为底层实现, 一个 zset 结构同时包含一个字典和一个跳跃表。

typedef struct zset {

    zskiplist *zsl;

    dict *dict;

} zset;

图示:

字典和跳跃表:

命令中只有zadd和zscore会涉及使用字典,其余全用跳跃表。

当有序集合对象可以同时满足以下两个条件时, 对象使用 ziplist 编码,不能满足以上两个条件的有序集合对象将使用 skiplist 编码。

  • 有序集合保存的元素数量小于 128 个;
  • 有序集合保存的所有元素成员的长度都小于 64 字节;

注意:在实际中, 字典和跳跃表会共享元素的成员和分值, 所以并不会造成任何数据重复, 也不会因此而浪费任何内存。

 

类型检查与命令多态

Redis 中用于操作键的命令基本上可以分为两种类型。

其中一种命令可以对任何类型键执行, 比如说 DEL 命令、 EXPIRE 命令、 RENAME 命令、 TYPE 命令、 OBJECT 命令, 等等

而另一种命令只能对特定类型的键执行, 比如说:

  • SET 、 GET 、 APPEND 、 STRLEN 等命令只能对字符串键执行;
  • HDEL 、 HSET 、 HGET 、 HLEN 等命令只能对哈希键执行;
  • RPUSH 、 LPOP 、 LINSERT 、 LLEN 等命令只能对列表键执行;
  • SADD 、 SPOP 、 SINTER 、 SCARD 等命令只能对集合键执行;
  • ZADD 、 ZCARD 、 ZRANK 、 ZSCORE 等命令只能对有序集合键执行;

类型检查与命令多态可以用下面这张图描述:

基于类型的多态:如 DEL 、 EXPIRE 、 TYPE,对任何类型的键都可以执行

基于编码的多态:如SPOP、ZRANK

 

内存回收/对象共享/对象空转时长

先说Redis中的对象共享机制,从redisObject 中的 int refcount 参数来说:

举个例子, 假设键 A 创建了一个包含整数值 100 的字符串对象作为值对象,如果这时键 B 也要创建一个同样保存了整数值 100 的字符串对象作为值对象, 那么服务器将让键 A 和键 B 共享同一个字符串对象,即执行以下两个步骤:

  1. 将数据库键的值指针指向一个现有的值对象;
  2. 将被共享的值对象的引用计数( refcount )增一。

 

Redis 系统中也基于引用计数(reference counting)构建了一个内存回收机制。

对象的引用计数信息会随着对象的使用状态而不断变化:

  • 在创建一个新对象时,refcount = 0;
  • 当对象被一个新程序使用时, refcount++;
  • 当对象不再被一个程序使用时, refcount--;
  • 当对象的refcount == 0 时, 对象所占用的内存会被释放。
127.0.0.1:6379> set int1 100
OK
127.0.0.1:6379> object refcount int1
(integer) 2 //一个系统引用,一个用户引用
127.0.0.1:6379> hset ht1 a 100
(integer) 1
127.0.0.1:6379> object encoding ht1
"ziplist"
127.0.0.1:6379> object refcount int1
(integer) 2 //ziplist编码情况下,int1引用没增加,因为压缩列表中没有嵌套object
127.0.0.1:6379> hset ht1 b 2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
(integer) 1
127.0.0.1:6379> object encoding ht1
"hashtable"//转hashtable
127.0.0.1:6379> object refcount int1
(integer) 3 //这时int1引用已经增加,因为在转换过程中会重新编码
127.0.0.1:6379> hset ht1 c 100
(integer) 1
127.0.0.1:6379> object refcount int1
(integer) 4 //再增加1

继续说 redisObject 的 unsigned lru:

LRU(least recently used)是一种缓存置换算法。即在缓存有限的情况下,如果有新的数据需要加入,则要替换掉最不可能被访问的数据。

redisObject中的lru实际上是一个秒级时间戳。OBJECT IDLETIME 命令可以打印出给定键的空转时长, 是通过将当前时间减去键的值对象的 lru 时间计算得出的;

如果服务器打开了 maxmemory 选项, 并且服务器用于回收内存的算法为 volatile-lru 或者 allkeys-lru , 那么当服务器占用的内存数超过了 maxmemory 时, 空转时长较高的那部分键会优先被服务器释放, 从而回收内存。

Redis初始实现LRU的算法很简单,即随机从dict中取出5个key,淘汰一个LRU值最小的。

Redis3.0以后,算法改为了随机选出的key都会放进一个pool(size:16)中,pool中的key按照LRU的大小排列。当pool满了的时候,放入新的就需要将pool中LRU最大的替换掉。淘汰的时候,直接将pool中LRU最小的淘汰即可。

作者对算法和samples大小的实验结果:

 

-----整理自《redis设计与实现(第二版)》

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值