wqbala 2020-05-25
1.定义:Redis是一个开源的,基于内存的数据结构存储,可用作于数据库、缓存、消息中间件。
1.1.Redis是基于内存,常用作于缓存的一种技术,并且Redis存储的方式是以key-value
的形式。
PS:我们可以发现这不就是Java的Map容器所拥有的特性吗,那为什么还需要Redis呢?
Java实现的Map不是专业做缓存的,JVM内存太大容易挂掉的。一般用做于容器来存储临时数据,缓存的数据随着JVM销毁而结束。Map所存储的数据结构,缓存过期机制等等是需要程序员自己手写的。
目的:
1.提高性能 缓存查询速度比数据库查询速度快(内存VS硬盘);
2.提高并发能力 缓存分担部分请求,支持更高的并发.
本文不会讲述命令的使用方式,具体的如何使用可查询API。
Redis的存储是以key-value
的形式的。Redis中的key一定是字符串,value可以是string、list、hash、set、sortset这几种常用的。
但要值得注意的是:Redis并没有直接使用这些数据结构来实现key-value
数据库,而是基于这些数据结构创建了一个对象系统。
简单来说:Redis使用对象来表示数据库中的键和值。每次我们在Redis数据库中新创建一个键值对时,至少会创建出两个对象。一个是键对象,一个是值对象。
简单来说就是Redis对key-value
封装成对象,key是一个对象,value也是一个对象。每个对象都有type(类型)、encoding(编码)、ptr(指向底层数据结构的指针)来表示。
Redis使用sdshdr结构来表示一个SDS值
struct sdshdr{ // 字节数组,用于保存字符串 char buf[]; // 记录buf数组中已使用的字节数量,也是字符串的长度 int len; // 记录buf数组未使用的字节数量 int free; }
SDS与C的字符串表示比较
使用listNode结构来表示每个节点:
typedef strcut listNode{ //前置节点 strcut listNode *pre; //后置节点 strcut listNode *pre; //节点的值 void *value; }listNode
使用listNode是可以组成链表了,Redis中使用list结构来持有链表:
typedef struct list{ //表头结点 listNode *head; //表尾节点 listNode *tail; //链表长度 unsigned long len; //节点值复制函数 void *(*dup) (viod *ptr); //节点值释放函数 void (*free) (viod *ptr); //节点值对比函数 int (*match) (void *ptr,void *key); }list
示意图如下:
Redis的链表有以下特性:
void *
指针来保存节点值,可以保存各种不同类型的值在Redis里边,哈希表使用dictht结构来定义:
typedef struct dictht{ //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 //总是等于size-1 unsigned long sizemark; //哈希表已有节点数量 unsigned long used; }dictht
Redis为了更好的操作,对哈希表往上再封装了一层
typedef struct dict { //类型特定函数 dictType *type; //私有数据 void *privdata; //哈希表 dictht ht[2]; //rehash索引 //当rehash不进行时,值为-1 int rehashidx; }dict; //----------------------------------- typedef struct dictType{ //计算哈希值的函数 unsigned int (*hashFunction)(const void * key); //复制键的函数 void *(*keyDup)(void *private, const void *key); //复制值得函数 void *(*valDup)(void *private, const void *obj); //对比键的函数 int (*keyCompare)(void *privdata , const void *key1, const void *key2) //销毁键的函数 void (*keyDestructor)(void *private, void *key); //销毁值的函数 void (*valDestructor)(void *private, void *obj); }dictType
所以,最后我们可以发现,Redis所实现的哈希表最后的数据结构是这样子的:
从代码实现和示例图上我们可以发现,Redis中有两个哈希表:
key-vlaue
数据Redis中哈希算法和哈希冲突跟Java实现的差不多,它俩差异就是:
下面来具体讲讲Redis是怎么rehash的,因为我们从上面可以明显地看到,Redis是专门使用一个哈希表来做rehash的。这跟Java一次性直接rehash是有区别的。
在对哈希表进行扩展或者收缩操作时,reash过程并不是一次性地完成的,而是渐进式地完成的。
Redis在rehash时采取渐进式的原因:数据量如果过大的话,一次性rehash会有庞大的计算量,这很可能导致服务器一段时间内停止服务。
Redis具体是rehash时这么干的:
跳跃表(shiplist)是实现sortset(有序集合)的底层数据结构之一!
跳跃表可能对于大部分人来说不太常见,之前我在学习的时候发现了一篇不错的文章讲跳跃表的,建议大家先去看完下文再继续回来阅读:
Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成。其中zskiplist保存跳跃表的信息(表头,表尾节点,长度),zskiplistNode则表示跳跃表的节点。
按照惯例,我们来看看zskiplistNode跳跃表节点的结构是怎么样的:
typeof struct zskiplistNode { // 后退指针 struct zskiplistNode *backward; // 分值 double score; // 成员对象 robj *obj; // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *forward; // 跨度 unsigned int span; } level[]; } zskiplistNode;
zskiplistNode的对象示例图(带有不同层高的节点):
typeof struct zskiplist { // 表头节点,表尾节点 struct skiplistNode *header,*tail; // 表中节点数量 unsigned long length; // 表中最大层数 int level; } zskiplist;
整个跳跃表的示例图如下:
整数集合是set(集合)的底层数据结构之一。当一个set(集合)只包含整数值元素,并且元素的数量不多时,Redis就会采用整数集合(intset)作为set(集合)的底层实现。
整数集合(intset)保证了元素是不会出现重复的,并且是有序的(从小到大排序),intset的结构是这样子的:
typeof struct intset { // 编码方式 unit32_t encoding; // 集合包含的元素数量 unit32_t lenght; // 保存元素的数组 int8_t contents[]; } intset;
intset示例图:
说明:虽然intset结构将contents属性声明为int8_t类型的数组,但实际上contents数组并不保存任何int8_t类型的值,contents数组的真正类型取决于encoding属性的值:
从编码格式的名字我们就可以知道,16,32,64编码对应能存放的数字范围是不一样的。16明显最少,64明显最大。
如果本来是INTSET_ENC_INT16的编码,想要存放大于INTSET_ENC_INT16编码能存放的整数值,此时就得编码升级(从16升级成32或者64)。步骤如下:
另外一提:只支持升级操作,并不支持降级操作。
压缩列表(ziplist)是list和hash的底层实现之一。如果list的每个都是小整数值,或者是比较短的字符串,压缩列表(ziplist)作为list的底层实现。
压缩列表(ziplist)是Redis为了节约内存而开发的,是由一系列的特殊编码的连续内存块组成的顺序性数据结构。
压缩列表结构图例如下:
节点的结构图:
压缩列表从表尾节点倒序遍历,首先指针通过zltail偏移量指向表尾节点,然后通过指向节点记录的前一个节点的长度依次向前遍历访问整个压缩列表。
以上内容出自:https://segmentfault.com/a/1190000016837791 个人学习记录记载