一、从常用数据结构说起 上文说到Redis提供了丰富的数据结构,包括STRING(字符串)、LIST(列表)、SET(集合)、HASH(散列)和ZSET(有序集合)基本数据类型。
先来一波操作感受一下:
文章图片
基本的数据类型的大概使用就到这里,接下来就分析一下它的内部结构是怎么实现的。
二、底层实现 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到此我们大概看出来一些端倪,Redis的数据类型背后根据存储的数据不同使用的不同的编码存储。为什么要这样做呢?
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”
节约存储空间!
其实远远不止这些,我们后面会详细说到。接下来就看每种数据结构有哪些编码类型。
三、数据结构详解 1、String 上文我们已经分析出了,String底层有三种。
int:存储8个字节的长整形接下来问题来了,什么是SDS呢?SDS全称是Simple Dynamic String,即简单动态字符串。
embstr:代表embsds格式的SDS,存储小于44字节的字符串
raw:存储大于44字节的SDS
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、必须分配足够的空间,否则可能会溢出好了,既然有了这些问题,SDS是如何解决的呢?
2、如果要获取长度,需要遍历数组,时间复杂度是O(n)
3、如果长度变更,需要重新内存分配
4、字符串规则是遇到第一个’/0’即为结束,因此不能存放二进制的内容
1、SDS实现了动态扩容,无需担心溢出的问题哇,到这里是不是感觉到Redis的编码格式设计的很巧妙。别着急,慢慢来,还有。
2、定义了len属性,获取长度时间复杂度是O(1)
3、通过空间预分配和惰性空间释放,放置了重新分配内存
4、判断结束标志示len属性,避免了二进制不安全
embstr和raw,为什么要设计两个编码格式呢?就是为了长度不同吗?SDS也已经满足了呀?
实际原因是embstr的使用,只分配了一次内存,redisObject和SDS是一起分配的,二raw是分配了两次内存
文章图片
那这三种类型之间是怎么转换的呢?
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有什么区别呢?如下图所示:
文章图片
从上图可以明显看出:
1、把所有相关的值聚集到一个key中,节省内存空间那么它的底层编码是如何实现的呢?是不是使用dicEntry实现的呢?我们先来操作一波:
2、只使用一个key,可以有效减少key冲突
3、当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU
127.0.0.1:6379> hset user1 name aaaaaaaaaaaaaaaaaaa可以看到,哈希类型的内部编码有两种:ziplist(压缩列表),hashtable(哈希表)。又有两个陌生的结构,不要慌张,我们下文会逐个分析。
(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”
推荐阅读
- 蓝桥杯|第十届蓝桥杯大赛软件类省赛 Java 大学 B 组试题A:组队——解题步骤
- 环境配置|Cent OS7安装JDK8,超详细
- 程序员|大厂福利内卷,35岁不再是条红线(DBA攻坚指南竟成最佳破冰手段)
- 为什么 Nacos 会在单个集群中同时运行 CP 协议以及 AP 协议呢()
- CGB2202|CGB2202面向对象第6天
- 安卓开源框架学习|OKHttp原理讲解之基本概念
- Java学习|SpringBoot---杂七杂八---终篇
- 面渣逆袭|Spring Bean生命周期,好像人的一生。。
- 阿里一面(ReadWriteLock 读写之间互斥吗(我竟然答不上来。。))