(2)redis的数据结构

至于redis的下载和安装可以参考:https://redis.io/download

1.启动停止redis

安装完redis后的下一步就是怎么去启动和访问,我们首先先了解一下Redis包含哪些可执行文件

Redis-server Redis服务器
Redis-cli Redis命令行客户端
Redis-benchmark Redis性能测试工具
Redis-check-aof Aof文件修复工具
Redis-check-dump Rdb文件检查工具
Redis-sentinel Sentinel服务器(2.8以后)

我们常用的命令是redis-server和redis-cli

    1. 直接启动
      redis-server ../redis.conf
      服务器启动后默认使用的是6379的端口 ,通过--port可以自定义端口 ;
      Redis-server --port 6380
      以守护进程的方式启动,需要修改redis配置文件中daemonize为yes
    1. 停止redis
      redis-cli SHUTDOWN
      考虑到redis有可能正在将内存的数据同步到硬盘中,强行终止redis进程可能会导致数据丢失,正确停止redis的方式应该是向Redis发送SHUTDOW命令
      当redis收到SHUTDOWN命令后,会先断开所有客户端连接,然后根据配置执行持久化,最终完成退出

2.数据类型

2.1 字符串类型

字符串类型是redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。你可以用它存储用户的邮箱、json化的对象甚至是图片。一个字符类型键允许存储的最大容量是512M

  • 内部数据结构

可以参考文章: https://redisbook.readthedocs.io/en/latest/internal-datastruct/sds.html

在Redis内部,String类型通过int、SDS(simple dynamic string)作为结构存储,int用来存放整型数据sds存放字节/字符串和浮点型数据。在C的标准字符串结构下进行了封装,用来提升基本操作的性能,同时也充分利用已有的C的标准库,简化实现逻辑。我们可以在redis的源码中【sds.h】中看到sds的结构如下;

typedef char *sds;

redis3.2分支引入了五种sdshdr类型,目的是为了满足不同长度字符串可以使用不同大小的Header,从而节省内存,每次在创建一个sds时根据sds的实际长度判断应该选择什么类型的sdshdr,不同类型的sdshdr占用的内存空间不同。这样细分一下可以省去很多不必要的内存开销,下面是3.2的sdshdr定义

struct __attribute__ ((__packed__)) sdshdr8 { //8表示字符串最大长度是2^8-1 (长度为255)
    uint8_t len; //表示当前sds的长度(单位是字节)
    uint8_t alloc; //表示已为sds分配的内存大小(单位是字节)
    unsigned char flags;//用一个字节表示当前sdshdr的类型,因为有sdshdr有五种类型,所以至少需要3位来表示
                        //000:sdshdr5,001:sdshdr8,010:sdshdr16,011:sdshdr32,100:sdshdr64。高5位用不到所以都为0。
    char buf[];//sds实际存放的位置
};

sdshdr8的内存布局 :

(2)redis的数据结构_第1张图片
2.2 列表类型

列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素或者获得列表的某一个片段。

列表类型内部使用双向链表实现,所以向列表两端添加元素的时间复杂度为O(1), 获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是很快的

(2)redis的数据结构_第2张图片
  • 内部数据

redis3.2之前,List类型的value对象内部以linkedlist或者ziplist来实现, 当list的元素个数和单个元素的长度比较小的时候,Redis会采用ziplist(压缩列表)来实现来减少内存占用。否则就会采用linkedlist(双向链表)结构。redis3.2之后,采用的一种叫quicklist的数据结构来存储list,列表的底层都由quicklist实现

这两种存储方式都有优缺点,双向链表在链表两端进行push和pop操作,在插入节点上复杂度比较低,但是内存开销比较大;ziplist存储在一段连续的内存上,所以存储效率很高,但是插入和删除都需要频繁申请和释放内存quicklist仍然是一个双向链表,只是列表的每个节点都是一个ziplist,其实就是linkedlist和ziplist的结合,quick中每个节点ziplist都能够存储多个数据元素,在源码中的文件为【quicklist.h】,在源码第一行中有解释为:A doubly linked list of ziplists意思为一个由ziplist组成的双向链表;

先来看看quicklist的结构是怎样的

typedef struct quicklist {
    quicklistNode *head;     
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : 16;              /* fill factor for individual nodes */
    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;
typedef struct quicklistNode {//quicklistNode节点
    struct quicklistNode *prev;  /前驱节点    
    struct quicklistNode *next; //后继节点 
    unsigned char *zl;           //使用ziplist或者lzf编码的数据 8字节 32bit
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
typedef struct quicklistLZF {//当被压缩时,节点的成员zl指向quicklistLZF
    unsigned int sz; /* LZF size in bytes*/
    char compressed[];
} quicklistLZF;

关于压缩类表结构的内容可以参考:https://redisbook.readthedocs.io/en/latest/compress-datastruct/ziplist.html

quicklist的总体的结构如下:

(2)redis的数据结构_第3张图片
2.3hash类型
(2)redis的数据结构_第4张图片
image.png
  • 数据结构

更多关于hash类型的实现细节请先参考: https://redisbook.readthedocs.io/en/latest/internal-datastruct/dict.html

map提供两种结构来存储,一种是hashtable、另一种是前面讲的ziplist,数据量小的时候用ziplist. 在redis中,哈希表分为三层,分别是,源码地址【dict.h】

  • dict

dictht实际上就是hash表的核心,但是只有一个dictht还不够,比如rehash、遍历hash等操作,所以redis定义了一个叫dict的结构以支持字典的各种操作,当dictht需要扩容/缩容时,用来管理dictht的迁移,以下是它的数据结构,源码在

typedef struct dict {
    dictType *type;//dictType里存放的是一堆工具函数的函数指针,
    void *privdata;//保存type中的某些函数需要作为参数的数据
    dictht ht[2];//两个dictht,ht[0]平时用,ht[1] rehash时用
    long rehashidx; //当前rehash到buckets的哪个索引,-1时表示非rehash状态
    int iterators; //安全迭代器的计数。
} dict;
  • dictht
    字典所使用的哈希表实现由 dict.h/dictht 类型定义:
/*
 * 哈希表
 */
typedef struct dictht {
    // 哈希表节点指针数组(俗称桶,bucket)
    dictEntry **table;
    // 指针数组的大小
    unsigned long size;
    // 指针数组的长度掩码,用于计算索引值
    unsigned long sizemask;
    // 哈希表现有的节点数量
    unsigned long used;
} dictht;
  • dictEntry

管理一个key-value,同时保留同一个桶中相邻元素的指针,用来维护哈希桶的内部

typedef struct dictEntry {
  void *key;
  union { //因为value有多种类型,所以value用了union来存储
    void *val;
    uint64_t u64;
    int64_t s64;
    double d;
  } v;
struct dictEntry *next;//下一个节点的地址,用来处理碰撞,所有分配到同一索引的元素通过next指针
                       //链接起来形成链表key和v都可以保存多种类型的数据
} dictEntry;

比如我们要讲一个数据存储到hash表中,那么会先通过murmur计算key对应的hashcode,然后根据hashcode取模得到bucket的位置,再插入到链表中

hash的数据总体结构如下:


(2)redis的数据结构_第5张图片
2.3集合类型

集合类型中,每个元素都是不同的,也就是不能有重复数据,同时集合类型中的数据是无序的。一个集合类型键可以存储至多2的32次方-1个 。集合类型和列表类型的最大的区别是有序性和唯一性
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在。
由于集合类型在redis内部是使用的值为空的散列表(hash table),所以这些操作的时间复杂度都是O(1)

(2)redis的数据结构_第6张图片
  • 数据结构

Set在的底层数据结构以intset或者hashtable来存储。当set中只包含整数型的元素时,采用intset来存储,否则,采用hashtable存储,但是对于set来说,该hashtable的value值用于为NULL,通过key来存储元素
可以在【intset .h】找到intset 的结构,可以发现intset就是一个简单的int数组来存储。

typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;
2.4有序集合

有序集合类型,顾名思义,和前面讲的集合类型的区别就是多了有序的功能
在集合类型的基础上,有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是他们的分数却可以相同

(2)redis的数据结构_第7张图片

  • 数据结构

zset类型的数据结构就比较复杂一点,内部是以ziplist或者skiplist+hashtable来实现,这里面最核心的一个结构就是skiplist,也就是跳跃表

关于redis的跳跃表的内容可以参考文章: https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html

跳跃表由 redis.h/zskiplist 结构定义:

typedef struct zskiplist {
    // 头节点,尾节点
    struct zskiplistNode *header, *tail;
    // 节点数量
    unsigned long length;
    // 目前表内节点的最大层数
    int level;
} zskiplist;

跳跃表的节点由 redis.h/zskiplistNode 定义:

typedef struct zskiplistNode {
    // member 对象
    robj *obj;
    // 分值
    double score;
    // 后退指针
    struct zskiplistNode *backward;
    // 层
    struct zskiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 这个层跨越的节点数量
        unsigned int span;
    } level[];
} zskiplistNode;

举个例子, 以下代码创建了一个带有 3 个元素的有序集:

redis> ZADD s 6 x 10 y 15 z
(integer) 3

redis> ZRANGE s 0 -1 WITHSCORES
1) "x"
2) "6"
3) "y"
4) "10"
5) "z"
6) "15"

在底层实现中, Redis 为 x 、 y 和 z 三个 member 分别创建了三个字符串, 值分别为 double 类型的 6 、 10 和 15 , 然后用跳跃表将这些指针有序地保存起来, 形成这样一个跳跃表

(2)redis的数据结构_第8张图片

可以类比典型的跳跃表的结构:


(2)redis的数据结构_第9张图片

你可能感兴趣的:((2)redis的数据结构)