spring|Redis基础篇

一、从常用数据结构说起 上文说到Redis提供了丰富的数据结构,包括STRING(字符串)、LIST(列表)、SET(集合)、HASH(散列)和ZSET(有序集合)基本数据类型。
先来一波操作感受一下:
spring|Redis基础篇
文章图片

基本的数据类型的大概使用就到这里,接下来就分析一下它的内部结构是怎么实现的。
二、底层实现 Redis是KV类型的数据库,Key-Value我们一般会用什么数据结构存储?
哈希表!没错Redis的最外层确实也是通过hashtable实现的。在Redis里面每个键值对都是一个dictEntry,通过指针指向key的存储结构和value的存储结构,此外还有一个next存储里指向下一个键值对的指针。

typedef struct dictEntry { void *key; //key void*表示任意类型指针 union { void*val; //value定义 uint64_tu64; int64_ts64; doubled; } v; struct dictEntry *next; //next指针 } dictEntry;

看到这里大家会有疑惑,那过期时间放在哪里?
对,实际上在dicEntry的外面还有一层redisDB。
/* Redis数据库结构体 */ typedef struct redisDb { // 数据库键空间,存放着所有的键值对(键为key,值为相应的类型对象) dict *dict; // 键的过期时间 dict *expires; // 处于阻塞状态的键和相应的client(主要用于List类型的阻塞操作) dict *blocking_keys; // 准备好数据可以解除阻塞状态的键和相应的client dict *ready_keys; // 被watch命令监控的key和相应client dict *watched_keys; // 数据库ID标识int id; // 数据库内所有键的平均TTL(生存时间) long long avg_ttl; } redisDb;

外层说完了,接下来就以set hello world为例,逐步分析一下。
首先key是字符串,Redis自己实现了一个字符串类型叫SDS,后面我们会细说,所以hello指向一个SDS的存储结构。value是world,也是一个字符串,是不是也用SDS存储呢?
这里就要重点介绍以下了,由前文我们知道Redis的key都是字符串类型,value的类型有多种。那Redis如何是适配这多种类型呢?于是就封装了一层redisObject。而我们所说的Redis数据类型的任何一种,都是通过redisObject存储的。想学习交流HashMap,nginx、dubbo、Spring MVC,分布式、高性能高可用、MySQL,redis、jvm、多线程、netty、kafka、的加尉xin(同英):1253431195 扩列获取资料学习,无工作经验不要加哦!
我们来看一下redisObject的结构:
typedef struct redisObject { //对象的数据类型,占4bits,共5种类型 unsigned type:4; //对象的编码类型,占4bits,共10种类型 unsigned encoding:4; //least recently used //实用LRU算法计算相对server.lruclock的LRU时间 unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */ //引用计数 int refcount; //指向底层数据实现的指针 void *ptr; } robj;

到这里就能看出一些奇妙的地方了,type是数据类型,encoding是编码类型,且数量不等。有意思了,那我们接下来看看这个encoding究竟是些什么。
127.0.0.1:6379> set number 123
OK
127.0.0.1:6379> object encoding number
“int”
127.0.0.1:6379> set story “long long ago,and long long ago,other long long ago”
OK
127.0.0.1:6379> object encoding story
“raw”
127.0.0.1:6379> set msg “hello world”
OK
127.0.0.1:6379> object encoding msg
“embstr”
到此我们大概看出来一些端倪,Redis的数据类型背后根据存储的数据不同使用的不同的编码存储。为什么要这样做呢?
节约存储空间!
其实远远不止这些,我们后面会详细说到。接下来就看每种数据结构有哪些编码类型。
三、数据结构详解 1、String 上文我们已经分析出了,String底层有三种。
int:存储8个字节的长整形
embstr:代表embsds格式的SDS,存储小于44字节的字符串
raw:存储大于44字节的SDS
接下来问题来了,什么是SDS呢?SDS全称是Simple Dynamic String,即简单动态字符串。
struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* 长度 */ uint8_t alloc; /* 分配的内存大小 */ unsigned char flags; /* 属性,标志不同种类的sds */ char buf[]; /* 内容 */ };

本质上就是一个char数组!
那么既然是一个char数组,为什么要通过SDS实现呢?
SDS 很简单,因为C语言中没有字符串类型,只有char数组。但是使用char数组会有一些问题,什么问题呢,大家可以想一下。想学习交流HashMap,nginx、dubbo、Spring MVC,分布式、高性能高可用、MySQL,redis、jvm、多线程、netty、kafka、的加尉xin(同英):1253431195 扩列获取资料学习,无工作经验不要加哦!
1、必须分配足够的空间,否则可能会溢出
2、如果要获取长度,需要遍历数组,时间复杂度是O(n)
3、如果长度变更,需要重新内存分配
4、字符串规则是遇到第一个’/0’即为结束,因此不能存放二进制的内容
好了,既然有了这些问题,SDS是如何解决的呢?
1、SDS实现了动态扩容,无需担心溢出的问题
2、定义了len属性,获取长度时间复杂度是O(1)
3、通过空间预分配和惰性空间释放,放置了重新分配内存
4、判断结束标志示len属性,避免了二进制不安全
哇,到这里是不是感觉到Redis的编码格式设计的很巧妙。别着急,慢慢来,还有。
embstr和raw,为什么要设计两个编码格式呢?就是为了长度不同吗?SDS也已经满足了呀?
实际原因是embstr的使用,只分配了一次内存,redisObject和SDS是一起分配的,二raw是分配了两次内存
spring|Redis基础篇
文章图片

那这三种类型之间是怎么转换的呢?
1、int 不在时整形,转成raw
【spring|Redis基础篇】2、int大小超过long的范围,转成embstr
3、embstr超过44字节,转成raw
注意:不可回转!!!
到此,String的数据结构就介绍完毕了。最后大家都思考一下String的使用场景有哪些?
1、缓存:热点数据,提升检索效率;
2、数据共享:session共享多个服务器共用;
3、分布式锁:setNx方法,判断是否添加成功;
4、全局唯一ID:INT类型的incrby;
5、计数器:INT类型
6、限流:INT类型
哈哈,是不是感觉String好强大,不需要其他数据类型了?那么问题来了,如果我要存一个对象怎么办?举个例子,存一个学生信息,包括姓名、年龄、学号等信息。大家可能会说,String存储一个json就行了,没错可以实现。但是我如果只想获取学生的年龄呢?想学习交流HashMap,nginx、dubbo、Spring MVC,分布式、高性能高可用、MySQL,redis、jvm、多线程、netty、kafka、的加尉xin(同英):1253431195 扩列获取资料学习,无工作经验不要加哦!
接下来介绍的Hash类型,就是解决这个问题的。
2、Hash Hash类型是指Redis键值对中的值本身又是一个键值对结构,形如value=https://www.it610.com/article/[{field1,value1},…{fieldN,valueN}],hash的value只能是字符串,不能嵌套其他类型。同样是存储字符串,Hash和String有什么区别呢?如下图所示:
spring|Redis基础篇
文章图片

从上图可以明显看出:
1、把所有相关的值聚集到一个key中,节省内存空间
2、只使用一个key,可以有效减少key冲突
3、当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU
那么它的底层编码是如何实现的呢?是不是使用dicEntry实现的呢?我们先来操作一波:
127.0.0.1:6379> hset user1 name aaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> hset user2 name aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 1
127.0.0.1:6379> object encoding user1
“ziplist”
127.0.0.1:6379> object encoding user2
“hashtable”
可以看到,哈希类型的内部编码有两种:ziplist(压缩列表),hashtable(哈希表)。又有两个陌生的结构,不要慌张,我们下文会逐个分析。

    推荐阅读