Py学习  »  NoSql

强大的NoSQL数据库——Redis,原理和数据结构是怎样的?

陆海潘江小C • 2 年前 • 364 次点击  
阅读 48

强大的NoSQL数据库——Redis,原理和数据结构是怎样的?

目录

一、认识Redis

二、Redis 5大数据类型

三、Redis SDS底层数据结构解析

四、链表数据结构解析

五、列表数据结构

1、压缩列表

2、快速列表

六、Redis字典

七、Redis整数集合

八、Redis跳表

九、Redis对象

十、Redis使用场景

一、认识Redis

最近入门学习Redis数据库,从最简单的内容学习,然后总结记录于此,以备忘记,也与大家共享!

Windows安装如图:

下载地址:

Windows:github.com/microsoftar…

Linux:download.redis.io/releases/re…

Server端:

Client端:

作为一款高性能、开源的NoSQL数据库,Redis主要采用键值对形式存储数据,用C语言开发,采用BSD协议,功能强大、支持多种数据类型,属于非关系型数据库。

区别与Mysql、SQLServer、Oracle等关系型数据库。因此,Redis的特点让它具有许多优势:

  1. 支持多种编程语言,Such as Java、C、C++、Python、PHP、Go、Lua等;
  2. 包括丰富的数据类型,Such as String、List、Set、Hash、Sorted Set;
  3. 读写速度快,性能高。官方数据:读速度110000次/s,写速度81000次/s,意味着数据都存储在内存中进行读写;
  4. 持久化。Redis的重要特点就是实现了持久化,定时将内存中的数据保存到磁盘,重启服务器时候再次加载到内存中,持久化方式有AOF和RDB;
  5. 简单且功能强大。可以实现消息订阅发布、Lua脚本、数据库事务等。Redis是单线程工作,所有操作都是原子性的,使用简单。
  6. 实现高可用主从复制。
  7. 实现分布式集群和高可用,分别是Redis Cluster和Redis Sentinel。
  8. 支持多种数据结构。Such Hash、Set、位图等。

下面简单介绍Redis的 5大数据类型。

二、Redis 5大数据类型

Redis 5 大数据类型的操作命令具体可见:www.redis.cn/commands.ht…

1、字符串String

字符串是Redis中最基本的数据类型,是二进制安全的。String类型的键最大存储512M的数据。

支持的数据包括:二进制数据、序列化后数据、JSON化的对象等。

2、哈希Hash

Hash类型是一个String类型的域和值的映射表,常用来存储对象信息。每个哈希表可以存储2^32-1个键值对,相当于40亿个数据。

3**、列表List**

Redis的列表相当于简单字符串列表,按插入顺序排序,可以操作将一个元素插入表头或表尾。同样可以存储2^32-1个元素。

4、集合Set

Set数据类型是String类型的无序集合,每个元素都是唯一的。集合通过哈希表来实现,增删查效率高,复杂度O(1)。一个集合最大容量存储2^32-1个元素。

5、有序集合Sorted Sort

有序集合是String类型的集合,其中每个元素都对应一个double类型的值。Redis根据这个值大小对元素进行排序,同样通过哈希表来实现,增删查效率高,复杂度O(1)。一个集合最大容量存储2^32-1个元素。

三、Redis SDS底层数据结构解析

Redis由C语言编写,底层实现具有C语言的语法和特点。对于C中的字符串类型并没有直接使用,而是自己构建了SDS(简单动态字符串)类型,作为Redis默认字符串。

SDS部分源码:

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
复制代码

从底层代码可以看出,定义了多种结构体来适应不同的需求 。

C的字符串和SDS区别:

  1. C的字符串API是二进制不安全的,可能存在缓冲区溢出,只保存文本数据,可以使用string.h的所有库函数,修改一次长度就分配一次内存,获取字符串长度的时间复杂度O(n)。
  2. SDS的API是二进制安全的,不存在缓冲区溢出,可以保存文本/二进制数据,使用部分string.h的库函数,修改N次长度分配内存次数<=N,获取长度为O(1).

四、链表数据结构解析

/* Node, List, and Iterator are the only data structures used currently. */

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    PORT_ULONG len;
} list;
复制代码

C语言中没有内置链表结构,Redis自己构建了链表,采用双向链表,由多个离散分配的节点组成,之间通过指针相连,每个节点都有一个前驱节点和一个后继节点,除了头节点(无前驱)和尾节点(无后继)。

五、列表数据结构

1、压缩列表

#ifndef _ZIPLIST_H
#define _ZIPLIST_H

#define ZIPLIST_HEAD 0
#define ZIPLIST_TAIL 1

unsigned char *ziplistNew(void);
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
unsigned char *ziplistIndex(unsigned char *zl, int index);
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, PORT_LONGLONG *lval);
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
unsigned char *ziplistDeleteRange(unsigned char *zl, int index, unsigned int num);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
unsigned int ziplistLen(unsigned char *zl);
size_t ziplistBlobLen(unsigned char *zl);

#ifdef REDIS_TEST
int ziplistTest(int argc, char *argv[]);
#endif

#endif /* _ZIPLIST_H */
复制代码

Redis压缩列表由列表键和哈希键底层实现。

当列表中包含元素较少,且值较小,Redis采用压缩列表实现,它是一种顺序型数据结构。

2、快速列表

快速列表是有压缩列表组成的双向链表,链表的每个节点以压缩列表保存数据。

列表定义如下:

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    PORT_ULONG count;        /* total count of all entries in all ziplists */
    unsigned int 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 {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    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;
复制代码

六、Redis字典

Redis字典是一种用于保存Redis键值对的数据结构,也是由Redis自己构建,Redis数据库底层也是由字典实现,CURD操作建立在字典之上。

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;
    PORT_ULONG size;
    PORT_ULONG sizemask;
    PORT_ULONG used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    PORT_LONG rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;
复制代码

七、Redis整数集合

当一个集合只包含整数值元素,且元素数量不多时,Redis采用该数据结构实现集合键底层。

#ifndef __INTSET_H
#define __INTSET_H
#include <stdint.h>

typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

intset *intsetNew(void);
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, int *success);
uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(intset *is);
size_t intsetBlobLen(intset *is);

#ifdef REDIS_TEST
int intsetTest(int argc, char *argv[]);
#endif

#endif // __INTSET_H
复制代码

八、Redis跳表

跳表是一种有序的数据结构,支持快速节点查找,批量处理节点。如果有序集合的元素众多且元素值为较长字符串,Redis使用跳表作为该有序集合的底层实现。

九、Redis对象

Redis的 以上数据结构,实际上并没有直接使用,而是创建了一个个对象系统,包括字符串对象、列表对象等。Red is对象系统实现基于引用计数技术的内存回收机制,当某个对象不在使用时候,系统会自动回收该对象占用的内存空间。

robj *dupStringObject(robj *o) {
    robj *d;

    serverAssert(o->type == OBJ_STRING);

    switch(o->encoding) {
    case OBJ_ENCODING_RAW:
        return createRawStringObject(o->ptr,sdslen(o->ptr));
    case OBJ_ENCODING_EMBSTR:
        return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));
    case OBJ_ENCODING_INT:
        d = createObject(OBJ_STRING, NULL);
        d->encoding = OBJ_ENCODING_INT;
        d->ptr = o->ptr;
        return d;
    default:
        serverPanic("Wrong encoding.");
        break;
    }
}

robj *createQuicklistObject(void) {
    quicklist *l = quicklistCreate();
    robj *o = createObject(OBJ_LIST,l);
    o->encoding = OBJ_ENCODING_QUICKLIST;
    return o;
}

robj *createZiplistObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_LIST,zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

robj *createSetObject(void) {
    dict *d = dictCreate(&setDictType,NULL);
    robj *o = createObject(OBJ_SET,d);
    o->encoding = OBJ_ENCODING_HT;
    return o;
}

robj *createIntsetObject(void) {
    intset *is = intsetNew();
    robj *o = createObject(OBJ_SET,is);
    o->encoding = OBJ_ENCODING_INTSET;
    return o;
}

robj *createHashObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

robj *createZsetObject(void) {
    zset *zs = zmalloc(sizeof(*zs));
    robj *o;

    zs->dict = dictCreate(&zsetDictType,NULL);
    zs->zsl = zslCreate();
    o = createObject(OBJ_ZSET,zs);
    o->encoding = OBJ_ENCODING_SKIPLIST;
    return o;
}

robj *createZsetZiplistObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_ZSET,zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}
复制代码

十、Redis使用场景

讲了这么多的Redis理论知识点,优势何以见得呢?

在实际应用中,各大网站、系统都可引入Redis,而且应用广泛,解决了关系数据库无法解决的问题。

Redis使用场景:

  1. 做缓存。Redis最常见的使用场景。它不需要每次重新生成数据,且缓存速度和查询速度快。缓存新闻内容、商品信息或购物车等。
  2. 做计数器。Redis操作具有原子性。可以用来记录转发数、评论数。
  3. 实现消息队列系统。支持模式匹配,可实现消息订阅发布。阻塞队列命令可实现秒杀、抢购等活动。
  4. 实时系统、消息系统。Redis set功能可用来查看用户具体操作,实现实时系统。
  5. 亿万级别排行榜。这也是Redis的一个重要应用,利用有序集合,对亿万用户进行实时排名,这得益于Redis的读写速度。
  6. 大型社交网络。如QQ、微博等,用户的浏览和聊天都需要Redis的支持。

这一篇是小白最近入门学习Redis数据库,从最简单的内容学习记录,也与大家共享!Redis作为一款高性能、开源的NoSQL数据库,区别与Mysql、SQLServer、Oracle等关系型数据库。因此,Redis的特点让它具有许多优势。 还有许多重要的地方在这里没有记录,比如持久化具体实现原理AOF、RDB,Redis集群等,以后学习之后有空在记录!

如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!

本文首发我的CSDN博客:blog.csdn.net/Charzous/ar…

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/117623
 
364 次点击