Redis源码学习

学习http://blog.csdn.net/men_wen/article/details/75668345

简单动态字符串:string

http://blog.csdn.net/men_wen/article/details/69396550

sds结构:

struct sdshdr {
    int len;        //buf中已占用空间的长度
    int free;       //buf中剩余可用空间的长度
    char buf[];     //初始化sds分配的数据空间,而且是柔性数组(Flexible array member)
};

优点:

1、二进制安全:能够存储含'\0'(C 内置字符串类型结束符)的字符串,比如二进制图片、视频等

2、

链表结构:list 双向链表

http://blog.csdn.net/men_wen/article/details/69215222

  • 链表节点
typedef struct listNode {
    struct listNode *prev; //前驱节点,如果是list的头结点,则prev指向NULL
    struct listNode *next;//后继节点,如果是list尾部结点,则next指向NULL
    void *value;            //万能指针,能够存放任何信息
} 
  • 表头
typedef struct list {
    listNode *head;     //链表头结点指针
    listNode *tail;     //链表尾结点指针

    //下面的三个函数指针就像类中的成员函数一样
    void *(*dup)(void *ptr);    //复制链表节点保存的值
    void (*free)(void *ptr);    //释放链表节点保存的值
    int (*match)(void *ptr, void *key); //比较链表节点所保存的节点值和另一个输入的值是否相等
    unsigned long len;      //链表长度计数器
} list;

利用 list 表头管理链表信息:

1、head和tail指针:对于链表的头结点和尾结点操作的复杂度为O(1)。

2、len 链表长度计数器:获取链表中节点数量的复杂度为O(1)。

3、dup、free和match指针:实现多态,链表节点listNode使用万能指针void *保存节点的值,而表头list使用dup、free和match指针来针对链表中存放的不同对象从而实现不同的方法。

  • 链表迭代器

Redis 字典结构:hash、关联数组、map

哈希表结构

typedef struct dictht { //哈希表
    dictEntry **table;      //存放一个数组的地址,数组存放着哈希表节点dictEntry的地址。
    unsigned long size;     //哈希表table的大小,初始化大小为4
    unsigned long sizemask; //用于将哈希值映射到table的位置索引。它的值总是等于(size-1)。
    unsigned long used;     //记录哈希表已有的节点(键值对)数量。
} dictht;

哈希表节点结构

typedef struct dictEntry {
    void *key;                  //key
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;                        //value
    struct dictEntry *next;     //指向下一个hash节点,用来解决hash键冲突(collision)
} dictEntry;

字典结构

typedef struct dict {
    dictType *type;     //指向dictType结构,dictType结构中包含自定义的函数,这些函数使得key和value能够存储任何类型的数据。
    void *privdata;     //私有数据,保存着dictType结构中函数的参数。
    dictht ht[2];       //两张哈希表。
    long rehashidx;     //rehash的标记,rehashidx==-1,表示没在进行rehash
    int iterators;      //正在迭代的迭代器数量
} dict;

dictType类型保存着 操作字典不同类型key和value的方法 的指针。

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);      //计算hash值的函数
    void *(*keyDup)(void *privdata, const void *key);   //复制key的函数
    void *(*valDup)(void *privdata, const void *obj);   //复制value的函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);  //比较key的函数
    void (*keyDestructor)(void *privdata, void *key);   //销毁key的析构函数
    void (*valDestructor)(void *privdata, void *obj);   //销毁val的析构函数
} dictType;

哈希算法

int整型哈希值

unsigned int dictIntHashFunction(unsigned int key)      //用于计算int整型哈希值的哈希函数
{
    key += ~(key << 15);       //~按位取反 ,^按位异或
    key ^=  (key >> 10);
    key +=  (key << 3);
    key ^=  (key >> 6);
    key += ~(key << 11);
    key ^=  (key >> 16);
    return key;
}

字典用作数据库底层实现或哈希键时,redis使用 MurmurHash2算法,能产生32-bit或64-bit 哈希值

冲突&rehash

Redis 跳跃表(skiplist):有序集合

跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,大部分情况下,跳跃表的效率可以和平衡树相媲美。

Redis 整数集合(intset)

整数集合(intset)是集合键底层实现之一。集合键另一实现是值为空的散列表(hash table),虽然使用散列表对集合的加入删除元素,判断元素是否存在等等操作时间复杂度为O(1),但是当存储的元素是整型且元素数目较少时,如果使用散列表存储,就会比较浪费内存,因此整数集合(intset)类型因为节约内存就存在。

Redis 压缩列表(ziplist)

压缩列表(ziplist)是哈希键的底层实现之一。它是经过特殊编码的双向链表,和整数集合(intset)一样,是为了提高内存的存储效率而设计的。当保存的对象是小整数值,或者是长度较短的字符串,那么redis就会使用压缩列表来作为哈希键的实现。

redis 3.2以后,quicklist作为列表键的实现底层实现之一,代替了压缩列表。

Redis 快速列表(quicklist)

127.0.0.1:6379> RPUSH list 1 2 5 1000
"redis" "quicklist"(integer) 
127.0.0.1:6379> OBJECT ENCODING list
"quicklist"

Redis 对象系统

对象结构robj功能:

  • 为5种不同的对象类型提供同一的表示形式。
  • 为不同的对象适用于不同的场景,支持同一种对象类型采用多种的数据结构方式。
  • 支持引用计数,实现对象共享机制。
  • 记录对象的访问时间,便于删除对象。
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

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的占5种类型:
/* Object types */
#define OBJ_STRING 0    //字符串对象
#define OBJ_LIST 1      //列表对象
#define OBJ_SET 2       //集合对象
#define OBJ_ZSET 3      //有序集合对象
#define OBJ_HASH 4      //哈希对象

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// encoding 的10种类型
#define OBJ_ENCODING_RAW 0     /* Raw representation */     //原始表示方式,字符串对象是简单动态字符串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */         //long类型的整数
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */      //字典
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */          //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */  //双端链表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */         //压缩列表
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */          //整数集合
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */      //跳跃表和字典
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */   //embstr编码的简单动态字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */   //由压缩列表组成的双向列表-->快速列表

Redis 字符串键的实现(t_string)

Redis 列表类型命令实现(t_list)

1 BLPOP key1 [key2 ] timeout:移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
2 BRPOP key1 [key2 ] timeout:移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

Redis 数据库

Redis是一个key-value数据库服务器,它将所有的键值对都保存在 redisDb 结构中的 dict 字典成员

  • 键值对字典的键,就是数据库的key,每一个key都是字符串的对象。

  • 键值对字典的值,就是数据库的value,每一个value可以是字符串的对象,列表对象,哈希表对象,集合对象和有序集合对象中的任意一种。

typedef struct redisDb {
    // 键值对字典,保存数据库中所有的键值对
    dict *dict;                 /* The keyspace for this DB */
    // 过期字典,保存着设置过期的键和键的过期时间
    dict *expires;              /* Timeout of keys with a timeout set */
    // 保存着 所有造成客户端阻塞的键和被阻塞的客户端
    dict *blocking_keys;        /*Keys with clients waiting for data (BLPOP) */
    // 保存着 处于阻塞状态的键,value为NULL
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    // 事物模块,用于保存被WATCH命令所监控的键
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    // 当内存不足时,Redis会根据LRU算法回收一部分键所占的空间,而该eviction_pool是一个长为16数组,保存可能被回收的键
    // eviction_pool中所有键按照idle空转时间,从小到大排序,每次回收空转时间最长的键
    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
    // 数据库ID
    int id;                     /* Database ID */
    // 键的平均过期时间
    long long avg_ttl;          /* Average TTL, just for stats */
} redisDb;

typedef struct client {
    redisDb *db;            /* Pointer to currently SELECTed DB. */
} client;

struct redisServer {
    redisDb *db;
    int dbnum;                      /* Total number of configured DBs */
};

数据库每次根据键名找到值对象时,是分为以读操作 lookupKeyRead() 或写操作 lookupKeyWrite() 的方式取出的,而这两种有一定的区别。

Redis 通知功能实现与实战

客户端可以通过 订阅与发布功能(pub/sub)功能,来接收那些以某种方式改动了Redis数据集的事件。

Redis的订阅与发布功能采用的是发送即忘(fire and forget)的策略,当订阅事件的客户端断线时,它会丢失所有在断线期间分发给它的事件

通知功能类型:

  • 键空间通知(key-space notification)
  • 键事件通知(key-event notification)

Redis输入输出的抽象(rio)

rio是Redis对IO操作的一个抽象,可以面向不同的输入输出设备,例如一个缓冲区IO、文件IO和socket IO。

一个rio对象提供一下四个方法:

  • read:读操作
  • write:写操作
  • tell:读写的偏移量
  • flush:冲洗缓冲区操作

对rio抽象的结构体中,使用了一个共用体(union),它可能是三种不同的对象,分别是:

  • 缓冲区 IO(Buffer I/O)
  • 标准输入输出 IO(Stdio file pointer)
  • 文件描述符集合(File descriptors set)

标准IO和文件IO的区别:

    标准IO:文件流fp,有缓存,库函数

    文件IO:文件描述符fd(小的,非负的整型数),无缓存,系统调用

    标准IO是依赖于文件IO的

b1: 标准IO:

     stdin   键盘/stdout   屏幕/stderr   屏幕

     fgetc   每次读入一个字符          fputc   每次写一个字符

     fgets   每次读入一行              fputs   每次写一行

     getchar

    fprintf   格式化输出到文件流

     sprintf   格式化输出到缓存

     fopen   打开文件,建立一个文件流,与文件关联

     fclose   关闭文件流

     fread   对文件流直接进行读操作

     fwrite   对文件流直接进行写操作

     errno   输出错误的原因(结果是个数)

     strerror   把结果数转换为字符串,然后输出错误的原因   

     perror   打印出错的原因(常用)

     fflush   强制刷新缓存

     stdout   行缓冲,只对/n进行刷新,对/r不进行刷新

     stderr   无缓冲

     fseek   移动当前的读写位置

     ftell   查看当前的读写位置

     freopen   重定向文件流

b2: 文件IO:

     文件描述符分配原则:当前未被使用的数值最小的int数,0(标准输入)stdin,1(标准输出)stdout,2(标准错误)stderr

     open   打开文件,将文件与文件描述符关联

     close   关闭文件

     read   读取文件,以字节为单位

     write   写入文件

     lseek   定位读写位置

b3: 文件描述符数值:有多个描述符指向file结构体

    file结构体里面的成员变量指向文件的inode节点(结构体)

    file结构体有一成员变量“引用计数”refc记录指向file结构体的文件描述符数量

b4: dup 文件描述符复制

    duplicate a file descriptor

    有多个文件描述符指向file结构体

D: 文件类型(7种):一切皆文件

     ls -l:查看文件类型

     bcd-lsp(b:块文件   c:字符文件   d:目录   -:常规文件   l:链接文件   s:套接字   p:管道文件)

Redis RDB持久化机制

因为Redis是内存数据库,因此将数据存储在内存中,如果一旦服务器进程退出,服务器中的数据库状态就会消失不见,为了解决这个问题,Redis提供了两种持久化的机制:RDBAOF。本篇主要剖析RDB持久化的过程。

RDB持久化是把当前进程数据生成时间点快照(point-in-time snapshot)保存到硬盘的过程,避免数据意外丢失。

AOF持久化:以独立日志的方式记录每次写命令,重启时在重新执行AOF文件中的命令达到恢复数据的目的。

由于Redis是单线程响应命令,所以每次写AOF文件都直接追加到硬盘中,那么写入的性能完全取决于硬盘的负载,所以Redis会将命令写入到缓冲区中,然后执行文件同步操作,再将缓冲区内容同步到磁盘中,这样就很好的保持了高性能。

Redis 事件处理实现

Redis包装了常见的select epoll evport kqueue,他们在编译阶段,根据不同的系统选择性能最高的一个多路复用库作为Redis的多路复用程序的实现,而且所有库实现的接口名称都是相同的,因此Redis多路复用程序底层实现是可以互换的。

INFO server 

Redis 复制(replicate)实现

1. 复制的介绍

Redis为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,实现Redis高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性

2.复制的实现

高可用性

Redis Sentinel实现

Raft 算法:

http://thesecretlivesofdata.com/raft/

Redis Cluster 集群伸缩原理

Redis 故障转移流程和原理

Redis 优化方法

Redis 利用Hash存储节约内存

1、使用Redis的String结构来做一个key-value存储,

SET media:1155315 939   
GET media:1155315   
> 939

将数据按上面的方法存储,1,000,000数据会用掉70MB内存,300,000,000张照片就会用掉21GB的内存。

将key值存成纯数字,经过实验,内存占用会降到50MB,总的内存占用是15GB,

2、使用Hash结构。具体的做法就是将数据分段,每一段使用一个Hash结构存储,由于Hash结构会在单个Hash元素在不足一定数量时进行压缩存储,所以可以大量节约内存,所以可以大量节约内存。这一点在上面的String结构里是不存在的。而这个一定数量是由配置文件中的hash-zipmap-max-entries参数来控制的。经过开发者们的实验,将hash-zipmap-max-entries设置为1000时,性能比较好,超过1000后HSET命令就会导致CPU消耗变得非常大。

#include <stdio.h>
struct str{
    int len;
    char s[0];
};
 
struct foo {
    struct str *a;
};
 
int main(int argc, char** argv) {
    struct foo f={0};
    if (f.a->s) {
        printf( f.a->s);
    }
    return 0;
}

猜你喜欢

转载自my.oschina.net/u/347414/blog/1627818
今日推荐