2020 01 17 Redis系列(十)Redis对象系统

前言

其实关于本文,我犹豫再三。面试

  1. 对象系统值得写一篇文章吗?从技术上来说,固然是值。可是对于咱们大部分人来讲,它都是隐身的。
  2. 写的话,顺序放在哪里?在 Redis 系列(九)底层数据结构之五种基础数据类型的实现中其实就提到了,那么应该在此以前先介绍它吗?

结论:想那么多屁事,写就完事了。redis

介绍

正如上一篇文章提到的,Redis 不是生硬的使用前面介绍过的数据结构,来实现了字符串,列表,字典等等数据结构,而是精心打造了一个对象系统。算法

对于 Redis 来讲,全部的所谓的数据类型,本质上都是一个对象,并且同一个类型的对象,底层实现编码不同。数据库

Redis 对象的定义为:后端

// 类型
typedef type:4;
// 编码
unsigned encoding:4;
// 指向底层数据结构的指针
void *ptr;
...
复制代码

类型

对象的 type 属性,记录了对象的类型,这个类型就是咱们所熟知的 Reids 的数据类型了,好比字符串,列表,集合,有序集合,散列等。缓存

对于 Redis 数据库中的键值对来说,键值永远是一个字符串对象,值能够是不少种。微信

编码和底层数据结构

对象的 ptr 指针,指向对象的底层数据结构,而这个数据结构是什么,则由 encoding 来决定。它能够是如下任意一种:markdown

  • REDIS_ENCODING_INT long 类型的整数
  • REDIS_ENCODING_ENBSTR embstr 编码的简单动态字符串(不知道的能够去看上一篇文章)
  • REDIS_ENCODING_RAW 简单动态字符串
  • REDIS_ENCODING_HT 字典
  • REDIS_ENCODING_LINKEDLIST 双端链表
  • REDIS_ENCODING_ZIPLIST 压缩列表
  • REDIS_ENCODING_INTSET 整数集合
  • REDIS_ENCODING_SKIPLIST 跳跃表和字典

每种类型的对象都至少可使用两种不一样的编码。以下表:数据结构

2020-01-16-20-48-29

五种常见的对象类型

对于咱们而言,工做中最经常使用以及面试中最常被问到的五种数据类型,他们的底层分别使用了什么编码及数据结构,多种编码之间的切换条件是怎样的?oop

这些问题你均可以在上一篇文章中找到答案。敬请查看 Redis 系列(九)底层数据结构之五种基础数据类型的实现

类型检查与命令多态

若是读者熟悉 Redis 的命令的话(不熟不要紧,看下一篇文章), 就会发现,Redis 的命令设计维度不是单一的。

好比有一类命令只能对指定的 数据类型执行。好比 ZADD 及各类 ADD.

而有一些命令是能够对全部类型操做的,好比 TYPE DEL 等等。

为了确保命令能够被正确的执行,Redis 须要进行命令的检查,由于相信用户不会乱用是十分愚蠢的。

在全部命令被执行以前,Redis 会首先检查输入的键的类型是否与命令匹配,这个检查就是应用 redisObject 中的 type字段进行的。

若是匹配,则继续执行命令,若是不匹配则返回特定的错误信息。

除了进行类型检查以外,Redis 还应用对象的类型进行命令的多态。

设想一下,列表对象能够 使用LLEN命令来求出当前元素的个数,而在之前,列表对象的实现多是压缩列表,也能够是双端链表,那么对于他们而言,求出长度的方法固然是不同的。

Redis 会首先进行类型检查,以后根据当前对象的编码来决定当前命令应该调用哪一个数据结构的 API. 以此来实现命令的多态。

内存回收

学习 Java 的同志们看到这里是否是倍感亲切,仿佛看到了家人。

众所周知,c 语言是没有自动化的内存管理的,可是 Redis 这么大的系统又不可能彻底手动的控制内存使用,所以须要一套自动化的内存回收机制。

Redis 在本身的对象系统中,基于引用计数实现了内存回收。

在 redisObject 对象中,还有一个额外的书序 refcount.

  • 建立对象时,引用计数为 1.
  • 当对象被一个新程序使用时,引用计数+1.
  • 当对象被一个程序抛弃的时候,引用计数-1;
  • 当对象的引用计数为 0, 对象会被回收,它所占用的内存被释放掉。

对于这一块的具体实现我也没看,可是引用计数的原理想必各位都很清楚了,若是不清楚的话随便 google 一下JVM 内存回收基本上都会顺手讲到引用计数的。

对象共享

除了用于使用基于引用计数的内存回收以外,对象的引用计数属性,还被用来作一些对象共享的工做。

设想一下,首先你建立了一个 kye=a, value=100的对象,过一会你又建立了一个key=b, value=100的对象,如此循环往复。内存会无线增大,可是其实保存的是同一个信息。

这些对象理论上来说是彻底能够进行共享的,即,首先我建立一个value=100的对象放在这里,每当你新建立一个上面那样的对象时,我就把指针指过来就行了。

Redis 有选择性的这样子作了,当它共享以前,会先给对应的对象的引用计数+1, 以后把指针指过来。

为何说是有选择性的呢?由于 Redis 只会缓存0-9999的数字字符串,若是你建立的键值对的值是这个,Redis 就会直接使用共享对象了。

为何很少缓存一点呢?最好是把系统中全部相同的值全缓存起来,这样子最省内存了。Redis 不是最缺内存了吗?

是的,这样子固然是省内存,可是** Redis 是一个高性能的内存数据库**.

性能这一块,Redis 卡的死死的。

想要判断两个对象的值是否相同,若是都是整数,只须要 O(1). 若是都是字符串,那么须要 O(N). 若是都是复杂对象(好比 hash), 那么可能须要 O(N2). Redis 为了更好的性能,放弃了缓存更加复杂的对象。

对象淘汰:空转时长

RedisObject 还有一个属性,unsigned lru:32;.

从名字咱们就能够看出来它是作什么的了,它记录了当前对象最后一次被访问的时间。

这个时间会在 Redis 的内存使用满了以后,Redis 会进行对象的淘汰,其中有一种算法是LRU. 会用到对象上一次被访问的时间。

同时,咱们也能够手动的查看某一个对象的空转时长。空转时长=当前时间-最后一次访问时间.

2020-01-16-21-19-56

总结

这篇文章大概了讲了一下 Redis 中的对象系统设计,及对象系统能够用来作什么。

  • 能够为数据类型的多种实现方式提供土壤
  • 类型检查与命令多态
  • 基于引用计数的内存回收
  • 对象共享,节省内存
  • 记录对象的空转时长,用于 LRU 等。

参考文章

《Redis 的设计与实现(第二版)》


完。

联系我

最后,欢迎关注个人我的公众号【 呼延十 】,会不按期更新不少后端工程师的学习笔记。 也欢迎直接公众号私信或者邮箱联系我,必定知无不言,言无不尽。


以上皆为我的所思所得,若有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文连接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见我的博客或关注微信公众号 < 呼延十 >------>呼延十

相关文章
相关标签/搜索