`
maosheng
  • 浏览: 566223 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Redis 设计解析

阅读更多
一、客户端与服务器连接

在使用Redis的时候,通常是多个客户端连接Redis服务器,然后各自发送命令请求(例如Get、Set)到Redis服务器,最后Redis处理这些请求返回结果。





那Redis服务端是使用单进程单线程来处理客户端请求。

有10000个客户端需要连上一个服务器并保持TCP连接,客户端会不定时的发送请求给服务器,服务器收到请求后需及时处理并返回结果。我们应该怎么解决?

方案一:我们使用一个线程来监听,当一个新的客户端发起连接时,建立连接并new一个线程来处理这个新连接。





缺点:当客户端数量很多时,服务端线程数过多,即便不压垮服务器,由于CPU有限其性能也极其不理想。因此此方案不可用。


方案二:我们使用一个线程监听,当一个新的客户端发起连接时,建立连接并使用线程池处理该连接。





优点:客户端连接数量不会压垮服务端。

缺点:服务端处理能力受限于线程池的线程数,而且如果客户端连接中大部分处于空闲状态的话服务端的线程资源被浪费。

因此,一个线程仅仅处理一个客户端连接无论如何都是不可接受的。那能不能一个线程处理多个连接呢?该线程轮询每个连接,如果某个连接有请求则处理请求,没有请求则处理下一个连接,这样可以实现吗?

答案是肯定的,而且不必轮询。我们可以通过I/O多路复用技术来解决这个问题。


Redis 数据库里面的每个键值对(key-value pair)都是由对象(object)组成的,其中
1.数据库键总是一个字符串对象(string object)
2.数据库键的值则可以是字符串对象(string object)、列表对象(list object)、哈希对象(hash object),集合对象(set object)、有序集合对象(sorted set object)这五种对象中的其中一种

二、Redis 数据结构

1. SDS(Simple Dynamic String) 简单动态字符串

当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis就会使用SDS来表示字符串值,比如在Redis的数据库里面,包含字符串值得键值对在底层都是由SDS实现的。

SDS(Simple Dynamic String) 简单动态字符串类型的结构:
struct sdshdr {

   //记录buf数组中已使用字节的数量
   //等于SDS所保存字符串的长度
   long len;

   //记录buf数组中未使用字节的数量
   long free;

   //字节数组,用于保存字符串
   char buf[];

};

len 是buf 数组的长度。
free 是数组中剩余可用字节数,由此可以理解为什么string 类型是二进制安全的了,因为它
本质上就是个byte 数组,当然可以包含任何数据了。
buf 是个char 数组用于存贮实际的字符串内容。

通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略
1.空间预分配
  1)如果对SDS进行修改之后,SDS的长度(也就是len属性的值)将小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDS len属性的值将和free属性的值相同。
  2)如果对SDS进行修改之后,SDS的长度(也就是len属性的值)将大于等于1MB,那么程序会分配1MB的未使用空间。
  通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数。

2.惰性空间释放
  惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录下来,并等待将来使用。


2. 链表

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
链表在Redis中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。

链表节点和链表的结构

typedef struct listNode{

   //前置节点
   struct listNode *prev;

   //后置节点
   struct listNode *next;

   //节点的值
   void *value;

}listNode;

typedef struct list{

   //表头节点
   listNode *head;

   //表尾节点
   listNode * tail;

   //链表所包含的节点数量
   unsigned long len;

   //节点值复制函数
   void *(*dup)(void *ptr);

   //节点值释放函数
   void (*free)(void *ptr);

   //节点值对比函数
   void (*match)(void *ptr,void *key);

}list;

list 结构为链表提供了表头指针head、表尾指针tail,以及链表长度计数器len,而dup、free和match成员则是用于实现多态链表所需的类型特定函数
dup函数用于复制链表节点所保存的值
free函数用于释放链表节点所保存的值
match函数则用于对比链表节点所保存的值和另一个输入值是否相等

链表实现的特性如下:

双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)

无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点

带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)

多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值


3. 字典

字典是一种用于保存键值对(key-value pair)的抽象数据结构

在字典中,一个键(key)可以和一个值(value)进行关联,这些关联的键和值就成为键值对

字典中的每个键都是独一无二的,程序可以在字典中根据键查找与之关联的值,或者通过键来更新值,又或者根据键来删除整个键值对等等

Redis的数据库就是使用字典来作为底层实现的。对数据库的增、删、查、改操作也是构建在对字典的操作之上的。

字典的实现:
    Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

哈希表:

typedef struct dictht{

   //哈希表数组
   dictEntry **table;
  
   //哈希表大小
   unsigned long size;
  
   //哈希表大小掩码,用于计算索引值
   //总是等于size - 1
   unsigned long sizemask;
  
   //该哈希表已有节点的数量
   unsigned long used;

}dictht;

table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个价值对。


哈希表节点:

每个dictEntry结构都保存着一个键值对

typedef struct dictEntry{

   //键
   void *key;

   //值
   union{
       void *val;
   uint64_tu64;
   int64_ts64;
   }v;
  
   //指向下个哈希表节点,形成链表
   struct dictEntry *next;

}dictEntry;


字典结构:

typedef struct dict{

   //类型特定函数
   dictType *type;
  
   //私有数据
   void *privdata;
  
   //哈希表
   dictht ht[2];
  
   //rehash 索引
   //当rehash不在进行时,值为 -1
   int trehashidx;
  
}dict;

4. 跳跃表(skiplist)

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
Redis使用跳跃表作为有序结合键的底层实现之一,如果一个有序集合包含的元素数量表较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。

Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。

跳跃表节点结构:

typedef struct zskiplistNode{

   //层
   struct zskiplistLevel{
  
      //前进指针
  struct zskiplistNode *forward;
 
  //跨度
  unsigned int span;
  
   }level[];
  
   //后退指针
   struct zskiplistNode *backward;

   //分值
   double score;
  
   //成员对象
   robj *obj;
 
}zskiplistNode;


跳跃表结构:

typedef struct zskiplist{

   //表头节点和表尾节点
   struct zskiplistNode *header,*tail;

   //表中节点的数量
   unsigned long length;
  
   //表中层数最大的节点的层数
   int level;
 
}zskiplist;


header和tail 指针分别指向跳跃表的表头和表尾节点,通过这两个指针,程序定位表头节点和表尾节点的复杂度为O(1)。
通过使用length熟悉来记录节点的数量,程序可以在O(1)复杂度内返回跳跃表的长度。
level熟悉则用于在O(1)复杂度内获取跳跃表中层高最大的那个节点的曾数量(表头节点的层高并不计算在内)。


整数集合(intset)

整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现

整数集合(intset)是Redis用于保存整数值得集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素

typedef struct intset{

   //编码方式
   uint32_t encoding;
  
   //集合包含的元素数量
   uint32_t length;
  
   //保存元素的数组
   int8_t contents[];
  
}intset;

contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组的一个数据项(item),各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。

整数集合的底层实现为数组,这个数组以有序、无重复的方式保存集合元素,在有需要的时候,程序会根据新添加元素的类型,改变这个数组的类型。

整数集合只支持升级操作,不支持降级操作。


5. 压缩列表(ziplist)

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表要么就是小整数值,要么就是长度比较短的字符串,那么Reids就会使用压缩列表来做列表键的底层实现。

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。


三、Redis 对象

Redis 基于以上这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们前面介绍的数据及结构。

通过这五种不同类型的对象,Redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用频率。

Redis 的对象系统还实现了基于引用计数计数的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。

Redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能会优先被服务器删除。

Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。

Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性

typedef struct redisObject{

   //类型
   unsigned type:4;
  
   //编码
   unsigned encoding:4;
  
   //指向底层实现数据结构的指针
   void *ptr;
  
   //.......
  
}redisObject;


对象的type 属性记录了对象的类型,这个属性的值可以是下表列出的其中一个:





对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以使字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种。因此,当我们对一个数据库键执行type命令时,命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型,键对象的类型总是字符串对象类型。如下表:





对象的ptr指针指向对象的底层实现数据结构,而这些数据机构由对象的encoding属性决定。encoding属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,encoding属性值如下表:





每种类型的对象都至少使用了两种不同的编码,如下表:





通过encoding熟悉来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大地提升了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率。








  • 大小: 31.3 KB
  • 大小: 30.6 KB
  • 大小: 16 KB
  • 大小: 61.8 KB
  • 大小: 78.4 KB
  • 大小: 113.1 KB
  • 大小: 278.8 KB
分享到:
评论

相关推荐

    redis3源码及解析

    Redis源码解析是一项深入学习数据库系统、内存管理以及并发控制等核心计算机科学概念的宝贵资源。 首先,我们来看Redis中的核心数据结构。Redis支持多种数据类型,包括字符串(String)、哈希表(Hash)、列表...

    Redis源码解析涵盖数据结构及特性实现

    内容概要:本篇文章深入研究了Redis源码的细节。...使用说明:阅读此内容是为了获得对Redis设计及其实现的理解,从而提高解决实际工程问题的能力。建议对照相关代码和官方资料进行研究以验证所学知识。

    Redis 设计与实现

    《Redis设计与实现》这本书是黄健宏先生的著作,主要针对Redis这一流行的数据存储系统进行深入剖析。Redis,全称Remote Dictionary Server,是一个开源的、高性能的键值存储系统,广泛应用于缓存、数据库、消息...

    阿里云Redis云服务解析.docx

    阿里云Redis云服务是针对企业级应用而设计的高性能、高可用的数据存储解决方案。作为一款基于ApsaraDB平台的服务,阿里云Redis提供了多种产品形态,包括主从双副本、主从单副本以及集群双副本,以满足不同业务场景的...

    Redis命令参考手册 + Redis设计与实现.rar

    《Redis设计与实现+第1版(黄健宏).pdf》由Redis的主要贡献者黄健宏编写,深入解析了Redis的内部设计和实现细节。这本书涵盖了Redis的架构、数据结构、网络模型、主从复制、持久化策略(如RDB和AOF)、内存管理、命令...

    Redis学习笔记,基于《Redis设计与实现》的内容,对Redis的基础知识进行总结.zip

    【Redis学习笔记】基于《Redis设计与实现》的深度解析 Redis是一款高性能的键值存储系统,被广泛应用于缓存、消息队列、分布式锁等场景。本笔记将结合《Redis设计与实现》这本书的内容,深入探讨Redis的基础知识,...

    redis设计学习指南

    Redis 设计学习指南深入解析 Redis 是一款高性能的键值存储系统,广泛应用于缓存、数据库等领域。本文将围绕 Redis 的核心数据结构展开,包括简单动态字符串 (SDS)、链表、字典等,以及它们在 Redis 内部如何协同...

    Redis开发规范解析-键名设计指南.docx

    在Redis开发中,键名设计是一项至关重要的任务,因为它直接影响到数据存储的效率、可维护性和内存使用。本文主要探讨了Redis键名设计的规范和一个实际案例,涉及SDS(Simple Dynamic String)内存优化的问题。 首先...

    c++ 操作redis数据库

    在`rediscommand.cpp`中,可能会定义一系列函数,每个函数对应一个Redis命令,通过发送协议报文到Redis服务器并解析响应,完成对数据的操作。 其次,`redistest.cpp`可能是测试代码,用于验证`rediscommand.cpp`中...

    Redis读书笔记(设计实现PDF)

    本文将基于“Redis读书笔记(设计实现PDF)”的标题和描述,结合黄建宏的《Redis设计与实现原理第二版》及老钱在掘金小册中的《Redis深度探险》资源,为你解析Redis的核心概念、设计思想和实际应用。 1. **Redis的...

    WPF操作Redis简单实例

    **标题解析:** "WPF操作Redis简单实例" 这个标题表明了本文将要讨论的是如何在Windows Presentation Foundation (WPF)应用中与Redis数据库进行交互。Redis是一种开源、高性能的键值对数据存储系统,常用于缓存、...

    redis设计与实现原理及运作机制

    ### Redis设计与实现原理及运作机制 #### 一、内部数据结构 Redis 是一款高性能的键值存储系统,其高效性很大程度上得益于内部使用的多种高效数据结构。这些数据结构不仅支持Redis提供的各种复杂数据类型,同时也...

    C++ 操作Redis数据库VS2013测试Demo及redis sdk

    hiredis是专门为Redis设计的C语言库,提供了简单的API,用于高效地读取Redis服务器的回复。它支持同步和异步两种模式,同步模式适用于简单场景,异步模式则适合处理高并发请求。 2. **VS2013环境配置**: 在...

    RedisStudio--redis界面查看工具

    Redis Studio 是一款专为Redis设计的图形用户界面工具,旨在帮助开发者和管理员更直观地管理和操作Redis数据库。通过这款工具,用户可以轻松地查看和修改Redis中的键值对,进行数据的增删改查操作,提高工作效率。...

    基于Java的Redis数据同步与命令解析设计源码

    该项目是一款基于Java实现的Redis数据同步与命令解析工具源码,共计535个文件,涵盖460个Java源文件、43个RDB文件、8个AOF文件、7个Markdown文件、5个YAML文件、2个XML文件、2个PEM文件、1个Git忽略文件和1个反996...

    基于C语言的redis0.1版本设计源码解析与解读

    本项目深入解析了redis0.1版本的C语言源码,包含187个文件,涵盖63个HTML文件、21个Ruby文件、13个C文件、13个头文件、6个文本文件、5个Shell脚本、5...该解析旨在帮助开发者更好地理解redis0.1的核心架构和工作原理。

    springMVC注解+ security + redis 实例

    Spring MVC通过DispatcherServlet作为入口,负责请求分发,ModelAndView对象处理数据展示,以及视图解析。注解在Spring MVC中扮演了重要角色,如@Controller、@RequestMapping等,使得开发者可以减少XML配置,实现...

    SpingMVC+MongoDB+Redis 初步架构设计

    在构建一个基于SpringMVC、MongoDB和Redis的初步架构设计时,我们需要深入了解这三大技术以及它们如何协同工作。SpringMVC是Spring框架的一部分,主要用于处理Web应用的模型-视图-控制器(MVC)结构。MongoDB是一个...

Global site tag (gtag.js) - Google Analytics