面试官问我 Redis 数据类型,我回答了 8 种.( 二 )


127.0.0.1:6379> set number 100OK127.0.0.1:6379> object encoding number"int"127.0.0.1:6379> set number "100"OK127.0.0.1:6379> object encoding number"int"127.0.0.1:6379> set number abcOK127.0.0.1:6379> object encoding number"embstr"127.0.0.1:6379> set number aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaOK127.0.0.1:6379> object encoding number"raw"127.0.0.1:6379> set number 9999999999999999999999999OK127.0.0.1:6379> object encoding number"embstr"127.0.0.1:6379> set number 99999999999999999999999999999999999999999999999999999999999999OK127.0.0.1:6379> object encoding number"raw"我们用我们最常使用的字符串做了测试,观察到其编码类型随着我设置的 value 值不同而改变,我整理了如下表格来表示上面的测试结果

面试官问我 Redis 数据类型,我回答了 8 种.

文章插图
 
当然,我是因为知道字符串的编码类型的条件,踩专门选取了这些有代表性的值进行测试,我们可以总结一个规律
  • 不论是 100 还是 "100",编码类型都是 int,说明 redis 在判断是否可以用整数这个编码类型表示对象的时候,就只是看这个值是否能转换成一个整数
  • 比较短的字符串 abc 被编码为 embstr,比较长的字符串 aaaaaaa..a 被编码为 raw,说明长短字符串的编码类型不一样,由此可以猜测 redis 可能是对短的字符串进行了存储上的优化策略(当然目前只是合理猜测,还有可能是对长字符串进行了某种优化)
  • 整数 999...9 和更长的整数 9999999...9 也都被转换成了相应的表示字符串的类型,说明可以用整数编码类型表示的值,是有一定大小限制的
redis 对字符串编码类型的优化浅析上面的实验我们了解到,字符串对象的编码类型确实有三种:int,raw,embstr 。
int 类型分析起来没什么意思,想想就知道肯定是能用整型存储的,尽量用整型存储,一定比字符串方式更节省空间嘛 。下面我们分析一下,长字符串和短字符串的编码类型做了区分,这是为什么呢?
不只是字符串类型,包括哈希、列表这些对象类型,都是用一个统一的结构体 redisObject 来表示的 。他的结构如下:
redis.h
typedef struct redisObject {unsigned type:4; // 对象类型unsigned encoding:4; // 编码类型void *ptr; // 值的指针...(省略一些不重要的字段)} robj;占了 4 位的 type 表示 对象类型(5 种那个),同样占了 4 位的 encoding 字段表示 编码类型(8 种那个),指针字段 ptr 表示实际值的 内存地址 。
如果该对象的编码类型为整数(encoding=REDIS_ENCODING_INT),那么这个 ptr 指向的将会是一个 long 类型的变量 。
util.c
if (!string2ll(s,slen,&llval))return 0;...*lval = (long)llval;return 1;object.c
...o->ptr = (void*) value;如果该对象的编码类型为 raw 或者 embstr,那么这个 ptr 指向的将会是一个 sdshdr 结构的变量
sds.h
struct sdshdr {unsigned int len; // 字符串长度unsigned int free; // buf空闲数char buf[]; // 字符数组};既然都是指向同一个结构,那是怎么优化的呢?那就得进入如下两个方法具体看看了
object.c
robj *createStringObject(char *ptr, size_t len) {if (len <= 39)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);}你看,这段代码非常清晰,字符串长度 <=39 时,就创建 embstr 类型的字符串对象,否则创建 raw 类型的字符串对象 。那么这两个创建方式的区别,一定就隐藏在这两个方法里,我们点进去!
embstr 类型
robj *createEmbeddedStringObject(char *ptr, size_t len) {robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);struct sdshdr *sh = (void*)(o+1);o->type = REDIS_STRING;o->encoding = REDIS_ENCODING_EMBSTR;o->ptr = sh+1;... (一些赋值操作)return o;}raw 类型
robj *createRawStringObject(char *ptr, size_t len) {return createObject(REDIS_STRING,sdsnewlen(ptr,len));}sds sdsnewlen(const void *init, size_t initlen) {...struct sdshdr *sh = zmalloc(sizeof(struct sdshdr)+initlen+1);...}robj *createObject(int type, void *ptr) {robj *o = zmalloc(sizeof(*o));o->type = type;o->encoding = REDIS_ENCODING_RAW;o->ptr = ptr;...(一些赋值操作)return o;}对于阅读源码比较多的同学,可能立刻就察觉到了他们的区别 。其实很简单,就是 raw 类型这种方式,为 redisObject 和 sdshdr 结构分别申请了内存空间,而 embstr 只申请了一次内存空间,然后将这两个结构紧挨着放 。除此之外没有其他任何区别了 。直观图如下(图画反了,不想改了哈哈哈):


推荐阅读