Redis源码学习之对象系统

在前面的文章中,我介绍了Redis的底层数据结构,但Redis对外提供的命令并没有直接使用它们,而是基于它们构建更高级的数据对象,总共包括5中对象类型,分别为【字符串对象】、【列表对象】、【哈希对象】、【集合对象】和【有序集合对象】,Redis实现对象系统的思想是:每一种对象都使用多种底层数据结构进行实现,具体使用哪种数据结构基于使用场景进行选择,并且可以在触发条件时进行转换。

一.Redis对象结构体

1.type字段

表示该对象的类型具体枚举值如下图所示:

Redis对象类型枚举

Redis提供了【type】命令来获取某个对象具体的类型,例如:

127.0.0.1:6379> set a 1

OK

127.0.0.1:6379> type a

string

2.encoding字段

表示该对象是哪种编码,也就是底层使用哪种数据结构进行实现,具体枚举值如下图所示:

Redis对象编码枚举

Redis提供了【object encoding】命令来获取某个对象的编码,例如:

127.0.0.1:6379> object encoding a

"int"

3.lru字段

保存了该对象最后一次被访问的时间戳,主要用于计算这个对象的idle时长,以便根据idle时长选择是否对其内存进行回收。

Redis提供了【object idletime】命令来获取某个对象的idle时长,例如:

127.0.0.1:6379> object idletime a

(integer) 252

4.refcount字段

表示该对象被引用次数,当某个对象不再被引用时(即refcount=0),它占用的内存将被释放;此外,Redis还会在服务器初始化时创建一系列共享对象,例如值在[0,10000]区间内的整数,会被创建为类型为字符串、编码为整数的共享对象,其refcount字段永远为1,因而常驻在内存中,来避免创建相同对象,浪费内存。

Redis提供了【object refcount】命令来获取某个对象的引用次数,例如:

127.0.0.1:6379> object refcount a

(integer) 4

Redis共享对象(常用整数)

Redis在三种情况下会修改refcount的值

第一种:对象初始化时设置为1

第二种:对象被某个程序引用时,refcount加1

第三种:对象被某个程序弃用时,refcount减1,且如果减到0则释放其占用的内存

5.ptr字段

指向底层数据结构的指针,保存了数据的真实值。

二.Redis对象的操作

1.创建对象

其中,对于lru时钟的获取,Redis在3.0版本做了优化。

首先,Redis会有一个专门的线程作为定时事件,不停的刷新系统当前时间,而server.hz就是serverCron运行的频率。所以,在获取lru时钟的时候,会优先根据hz和时钟精度判断是否可以获取系统缓存的时钟值,如果不可以再进行系统调用获取时钟。

此外,Redis还封装了一系列的创建具体类型对象的方法。但实际上,除了嵌入式SDS之外,其他都是调用createObject方法。方法如下:

创建字符串对象

createStringObject(const char *ptr, size_t len)

创建SDS编码的字符串对象

createRawStringObject(const char *ptr, size_t len)

创建嵌入式SDS编码的字符串对象

createEmbeddedStringObject(const char *ptr, size_t len)

根据传入的longlong整型值,创建一个字符串对象

createStringObjectFromLongLong(long long value)

根据传入的long double类型值,创建一个字符串对象

createStringObjectFromLongDouble(long double value, inthumanfriendly)

创建压缩列表编码的列表对象

createZiplistObject(void);

创建集合对象

*createSetObject(void)

创建整型集合编码的集合对象

createIntsetObject(void)

创建哈希对象

createHashObject(void)

创建有序集合对象

createZsetObject(void)

创建压缩列表编码的有序集合对象

createZsetZiplistObject(void)

在后面分析每种对象的文章中我会逐一介绍。

2.释放对象

正如前文所说,Redis根据对象的refcount字段判断是否需要释放当前对象。

当某个程序弃用该对象时,Redis会对该对象的refcount值进行判断:

如果该值为非正数,则是一次异常调用

如果该值为1,则根据对象类型选择对应方法释放内存

如果该值大于1,则refcount--

而每种类型的释放方法中,又会根据编码去掉用底层数据结构的释放内存方法,以列表对象为例,如果该对象是双端列表实现,则调用双端列表的释放内存方法,否则释放压缩列表的释放内存方法:

3.计算对象空转时长

Redis基于近似LRU算法,计算某个对象的空转时长。在当前时钟大于等于对象的lru时钟时,说明该对象已经空转一段时间,返回他们的差值即可;而当前时钟小于lru时钟时,则需要将它们的差值(负数)与LRU时钟最大值进行相加再返回。Redis会在需要使用LRU策略清理内存时,从所有key中进行采样,从中淘汰空转时长最长的key。

4.对象交互命令

前文中提到,Redis针对对象实现了3个命令,分别为获取引用次数命令【object refcount】、获取编码命令【object encoding】和获取空转时长命令【object idletime】。

三.Redis对象思维导图

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网杂技

JS模块与命名空间的介绍

起因 将代码组织到类中的一个重要原因是让代码更加“模块化”,可以在很多不同的场景中实现代码的重用。但类不是唯一的模块化代码的方式。 一般来讲,模块是一个独立的J...

39760
来自专栏电光石火

null或空值的判断处理

1,错误用法一: if (name == "") {      //do something } 2,错误用法二: if (name.equal...

22790
来自专栏Java职业技术分享

Java技术——你真的了解String类的intern()方法吗

是不是感觉莫名其妙,新定义的str2好像和str1没有半毛钱的关系,怎么会影响到有关str1的输出结果呢?其实这都是intern()方法搞的鬼!看完这篇文章,你...

17300
来自专栏SDNLAB

Open vSwitch系列之数据结构解析深入分析Hmap

作为Open vSwitch系列的第一篇文章,选择分析哪个数据结构我思考很久,最后还是选择比较常见而且很基础的结构hmap。 在Open vSwitch世界中很...

37540
来自专栏liulun

Nim教程【十二】

排除指定符号 一般情况下使用import语句,会把一个模块内的符号都导入进来 如果你像排除特定的符号(不想让某些符号被导入进来) 可以使用except子句 就像...

232100
来自专栏Java面试通关手册

可能是把Java内存区域讲的最清楚的一篇文章

哈哈 皮一下!我自己开源的一个Java学习指南文档。一份涵盖大部分Java程序员所需要掌握的核心知识,正在一步一步慢慢完善,期待您的参与。Github地址:ht...

10620
来自专栏Java 源码分析

Java 虚拟机运行时数据区

运行时数据区: Java 虚拟机的运行时数据区按照大的可以分为线程独立使用的数据区,和所有线程共享的数据区。 一.线程独立使用数据区 1.程序计数器 程序计数器...

34840
来自专栏日常学python

详解Python中的__init__和__new__的区别

使用Python写过面向对象的代码的同学,可能对 __init__ 方法已经非常熟悉了,__init__ 方法通常用在初始化一个类实例的时候。例如:

6810
来自专栏全沾开发(huā)

拿Proxy可以做哪些有意思的事儿

26580
来自专栏余林丰

int类型和byte类型的强制类型转换

今天在读《Java网络编程》这本书的第二章 流 时,看到书中有一个地方关于int强制转换为byte类型时应注意的地方。这个地方有点细节,不过就应该把这种细节把握...

22850

扫码关注云+社区

领取腾讯云代金券