redis读书笔记

发布时间:2018-11-19  栏目:NoSQL  评论:0 Comments

1、redis两种植存储机制(持久化)
Redis的囤机制分为:Snapshot和AOF 都先以内存存储在内存中。
(1)Snapshot当数码累计到自然的阈值,就会见触发dump将数据一次性写副到数据文件RDB文件。批量数据存储,写副频率低,效率为大。但是安全性稍微,redis宕机,没有写入的数额会造成丢失。
(2)AOF采用日志追加的方式来持久化数据,调用fsync来完成对此次写操作的日志记录。调用fsync追加日志文件之效率可以转移,always每次记录还加上进去,everysecond每秒添加同浅。rewrite这个日志的定义:根据客观的安排触发rewrite操作,将日志文件中具备数据还又描绘于新的公文中,对于跟个key的一再操作,保留最后的值得那么不行操作以日记文件被。缩小了日志文件大小。
2、redis的内存优化
(1)string和数字,redis能辨别出一个数字,并按数字存储,节省存储空间。redis内部发生一个数字池,默认10000,数字以是池塘中便不过需要一个简单易行的目引用进来就足以,不需要把再的数字分别存。
(2)复杂类型的囤优化,map,list,set这些大大小小不定点的集合类。redis会判断这里存储的entry数量,不多则利用紧凑格式来存储数据,这里不开解释。
3、讲讲redis中之hash数据类型
比较由String数据类型,这个更可储存对象,避免了序列化和反序列化

概括动态字符串

SDS(simple dynamic string)简单动态字符串,作为redis默认字符串表示

C字符串本身是一个char数组,获取字符串长度要遍历,复杂度O(N),对于N的字符的C字符串来说,数组长度是N+1,额外一个字符空间用于保存空字符串

SDS是一个C里面的一个struct数据结构,有特别的len字段表示总长度,free表示空字符个数,复杂度O(1)

SDS API会自动将SDS的空间拓展至执行所欲的深浅。

SDS空间预分配:

  • 空中进行上,SDS会预留额外的非运空间,即free。
  • 当SDS小于1MB,预留的free会和len相同。
  • 如果SDS大于1MB,则free会分配1MB空间。
  • 于进展时,会先行反省free是否足够,如果足够的话,就不见面实施重分配函数

惰性空间释放:SDS在缩短时,不会见即刻放飞惰性空间,SDS提供了另外的API用于释放惰性空间
SDS数据结构内的buf属性为字节数组
SDS的API兼容C字符串函数,比如strcat


图片 1

链表

除去链表键之外,发布与订阅、慢查询、监视器等作用吗以了链表。

节点数据结构

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);
    //节点值对比函数
    int (*match) (void *ptr,void *key);
} list;

链表特性:

  • 双向链表
  • 无环
  • 链表数据结构自带表头和表尾
  • 长计数器
  • 多态

 

字典

字典,又称作符号表、关联数组或映射,是相同种植用于保存键值对的悬空数据结构。相当给hashmap的数据结构。

字典的组织定义

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

ht属性其实是一个size为2之累累组,平时字典使用ht[0]哈希表,ht[1]哈希表只会当针对ht[0]哈希表进行rehash时使用。除了ht[1],在rehash时还会用到rehashidx,记录之目前rehash的进度,如果没有开展rehash,rehashidx=-1

哈希表结构定义

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

哈希表节点结构定义

typedef struct dictEntry {
    //键
    void *key;
    //值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    }
    //指向下个哈希表节点,形成链表
    struct dictEntry *next;
} dictEntry

redis使用MurmurHash2算法来计算键的哈希值

化解哈希冲突,哈希碰撞,redis使用链地址法(类似于JDK1.7事先的hashmap),为了速度考虑,程序总将新节点放在链表的表头位置。

当哈希表的载重因子小于0.1不时,程序自动开针对哈希表执行收缩操作。


hash是一个string 类型的field和value的映射表。
hash特别符合储存对象。相对于用目标的每个字段存成单个string
类型。一个靶存储于hash类型中会占用更少之内存,并且可以又有益的存取整个对象。
Redis的Hash数据类型的value内部是一个HashMap,如果该Map的积极分子比少,则会动一维数组的艺术来紧凑存储该MAP,省去了汪洋指南针的内存开销。
动key—field—value的主意。一个key可针对诺多只field,一个field对应一个value。这里以需要小心,Redis提供了接口(hgetall)可以一直获取到全体底属性数据,但是要是中Map的积极分子多,那么涉及到遍历整个中Map的操作,由于Redis单线程模型的原委,这个遍历操作可能会见于耗时,而使得其他客户端的请求了无应,这点要格外上心
提议采用对象类别和ID构成键名,使用字段表示对象属性,字段值存储属性值。
4、redis集群
如redis只是单例的言辞,万一挂了,数据丢失。而且当数据量很特别的当儿,集群可以管高可用。它好将多独redis实例整合在一起,形成一个集群,也就算是将数据分散到集群的多华机器上。但是该怎么分散为,一个Key只能被分配到同大机器及,集群是如何兑现将数据分配到不同的节点吧?我们于询问数据经常,数据在随心所欲一个节点受到,我们什么样管成功存取呢?

跳跃表

跳跃表(skiplist)是一种有序数据结构,它经过当每个节点受到维系多独针对任何节点的指针,从而达成快速访问节点的目的。
摸效率平均O(logN),最差状况下效率呢O(N)
绝大多数情景下,跳跃表的效率可以和平衡造就相媲美。
蹿表结构体

typedef struct zskiplist {
    //表头节点和表尾节点
    structz skiplistNode *header, *tail;
    //表中节点的数量
    unsigned long length;
    //表中层数最大的节点的层数
    int level;
} zskiplist;

跳表节点结构定义

typedef struct zskiplistNode {
    //后退指针
    struct zskiplistNode *backward;
    //分值
    double score;
    //成员对象
    robj *obj;
    //层
    struct zskiplistLevel {
        //前进指针
        struct zskiplistNode *forward;
        //跨度
        unsigned int span;
    } level[];
} zskiplistNode;

层数代表level数组含有的素个数,层数越多,访问其他节点的进度更快,类似于数据库索引底层的B-tree
跨度记录了向上指针所依靠为节点和脚下节点的偏离


图片 2

平头集合

恍如于hashset的数据结构
数据结构

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

依次项于屡组中仍照值的高低从小至大有序地排,并且数组中未含有其他重复项。

upgrade升级
当我们如果以一个初因素添加到整数集合里面,并且新元素的种类比较整数集合现有所有因素的类别且如抬高时,整数集合需要展开升级换代。
借设原集合里出3个要素,每个元素占16各类空间,现在以一个int32_t整数(占用32各空间)添加进去,在空中重新分配之后,底层数组大小也32*4=128位

平头集合不支持降级操作


设图是redis集群架构图,蓝色的凡redis服务器节点,绿色的是客户端。每个节点通过二进制协议进行通信,每个节点也保留着独具集群的消息。节点内互相ping对方,如果ping不通,说明对方节点挂了,由于每个节点会生一个称本slave,主从备份保证了后台的平稳。
客户端好与其它一个节点通信,对该存取和其余操作。由于数量被分配在不同之节点受到,集群是哪些找到数据在哪儿也?redis
集群有一个16384长短的插槽。slot。编号分别是0,1,2,……16383,16384.每个节点都见面担当一些插槽。集群维护这节点到插槽的投。是由于长也16384,类型为节点的数组实现之。槽编码就是多次组的下标。数组的情即是节点。所以这么迅速的能够了解如何槽是由于哪节点负责的。对于master节点来说,维护一个16384/8字节底号序列,比如对编号吧1的扇,Master只要判断序列的次各类(索引从0开始)是免是吗1即可。
redis的数额是key-value存储的。不同之key的多少如何对承诺交slot呢。有一个键空间分布之算法 HASH_SLOT=CRC16(key)mod16384
这样即使得算出当前key被存在谁slot里。
5、分片
分片不转移的是键对于槽的照耀,改变的凡槽对于节点的炫耀。

削减列表

减掉列表(ziplist)是排表键和哈希键的根实现有。当一个排列表键只包含少量排列表项,并且每个列表项或就是是聊整累值,要么就是长比少的字符串,那么redis就会见使用压缩列表来做列表键的平底实现。

减列表

  • zlbytes:4字节,记录整个压缩列表占用内存的字节数
  • zltail:4字节,记录压缩列表尾部节点距离起始地址之偏移量
  • zllen:2字节,记录压缩列表包含的节点数量
  • entry:不定,列表中的每个节点
  • zlend:1字节,特殊值0xFF,标记压缩列表的截止

抽列表节点构成

typedef struct zlentry {
    unsigned int prevrawlensize, prevrawlen;
    unsigned int lensize, len;
    unsigned int headersize;
    unsigned char encoding;
    unsigned char *p;
} zlentry;
  • prevrawlen:前置节点的尺寸
  • prevrawlensize:编码 prevrawlen 所急需的字节大小
  • len:当前节点的长
  • lensize:编码 len 所欲的字节大小
  • headersize:当前节点 header 的大大小小,等于 prevrawlensize + lensize
  • encoding:当前节点值所用的编码类型
  • p:指向当前节点的指针

快速列表quicklist

quicklist结构是在redis
3.2版本被新加底数据结构,用当列表的底色实现
quicklist结构以quicklist.c中之分解也A doubly linked list of
ziplists意思呢一个由于ziplist组成的双向链表。

  • quicklist宏观上是一个双向链表,因此,它抱有一个双向链表的粗,进行插队或去操作时杀便于,虽然复杂度为O(n),但是非待内存的复制,提高了频率,而且看两端元素复杂度为O(1)。
  • quicklist微观上是同等切开片entry节点,每一样切开entry节点内存连续且顺序存储,可以透过二细分查找以
    log2(n) 的复杂度进行一定。

数据结构

typedef struct quicklist {
    //指向头部(最左边)quicklist节点的指针
    quicklistNode *head;
    //指向尾部(最右边)quicklist节点的指针
    quicklistNode *tail;
    //ziplist中的entry节点计数器
    unsigned long count;    /* total count of all entries in all ziplists */
    //quicklist的quicklistNode节点计数器
    unsigned int len;    /* number of quicklistNodes */
    //保存ziplist的大小,配置文件设定,占16bits
    int fill : 16;    /* fill factor for individual nodes */
    //保存压缩程度值,配置文件设定,占16bits,0表示不压缩
    unsigned int compress : 16;    /* depth of end nodes not to compress;0=off */
} quicklist;

节点数据结构

typedef struct quicklistNode {
    struct quicklistNode *prev;    //前驱节点指针
    struct quicklistNode *next;    //后继节点指针
    //不设置压缩数据参数recompress时指向一个ziplist结构
    //设置压缩数据参数recompress指向quicklistLZF结构
    unsigned char *zl;
    //压缩列表ziplist的总长度
    unsigned int sz;    /* ziplist size in bytes */
    //ziplist中包的节点数,占16 bits长度
    unsigned int count : 16;    /* count of items in ziplist */
    //表示是否采用了LZF压缩算法压缩quicklist节点,1表示压缩过,2表示没压缩,占2 bits长度
    unsigned int encoding : 2;    /* RAW==1 or LZF==2 */
    //表示一个quicklistNode节点是否采用ziplist结构保存数据,2表示压缩了,1表示没压缩,默认是2,占2bits长度
    unsigned int container : 2;    /* NONE==1 or ZIPLIST==2 */
    //标记quicklist节点的ziplist之前是否被解压缩过,占1bit长度
    //如果recompress为1,则等待被再次压缩
    unsigned int recompress : 1;    /* was this node previous compressed? */
    //测试时使用
    unsigned int attempted_compress : 1;    /* node can't compress; too small */
    //额外扩展位,占10bits长度
    unsigned int extra : 10;    /* more bits to steal for future usage */
} quicklistNode;

quicklistLZF lzf压缩算法

typedef struct quicklistLZF {
    //表示被LZF算法压缩后的ziplist的大小
    unsigned int sz; /* LZF size in bytes*/
    //保存压缩后的ziplist的数组,柔性数组
    char compressed[];
} quicklistLZF;

entry

//管理quicklist中quicklistNode节点中ziplist信息的结构
typedef struct quicklistEntry {
    const quicklist *quicklist;    //指向所属的quicklist的指针
    quicklistNode *node;        //指向所属的quicklistNode节点的指针
    unsigned char *zi;            //指向当前ziplist结构的指针
    unsigned char *value;        //指向当前ziplist结构的字符串vlaue成员
    long long longval;            //指向当前ziplist结构的整数value成员
    unsigned int sz;                //保存当前ziplist结构的字节数大小
    int offset;                        //保存相对ziplist的偏移量
} quicklistEntry;

图片 3

quicklist


对象

redis的对象系统贯彻了基于引用计数的内存回收机制

数据结构

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

字符串对象的编码可以是

  • int 整数
  • raw 简单动态字符串SDS
  • embstr embstr编码的概括动态字符串SDS

如目标保存之凡字符串值,并且长度值仅次于等于39个字节,那么会采取embstr编码保存。
否则会利用raw格式(大于39只字节)

  • raw编码会调用两软外存分配函数来分别创建redisObject结构和sdshdr结构
  • embstr编码内存分配次数会从区区糟下降吗平赖,因为embstr编码的字符串对象具备数据还保留在相同片连续的内存里面

DEL、EXPIRE、TYPE等一声令下称之为多态命令,因为这些命令跟键格式无关

好像于Java常量池,redis初始化时会见创建0~9999所有整数值的字符串对象


数据库

redis初始化时默认会创建16个数据库,由服务器配置database选项dbnum决定
默认目标数据库也0号数据库
数据库中数据交互隔离
动SELECT命令在挨家挨户数据库里进行切换
应用flushdb这样危急的下令之前,最好先实施一下SELECT命令
redis是一个键值对数据库,redisdb数据结构里面的dict字典保存了数据库中拥有的键值对,我们以此字段称为键空间(key
space)

typedef struct redisDb {
    //...
    //数据库键空间,保存着数据库所有键值对
    dict *dict;
    //...
} redisDb;

键空间的key始终为字符串对象
键空间的value可以呢字符串对象、列表对象、哈希表对象、集合对象与稳步对象中的人身自由一栽

redis可以设置键过期时间,过期字典里保存了数据库键的逾期时
TTL命令以秒为单位返回键的剩下生存时间

过期键删除策略:

1、定时删除:创建定时器timer
2、惰性删除:当有收获该键的需要时,才去看清该键是否过
3、定时删除:每过一段时间,程序即使会见对数据库进行同样涂鸦检查,删除了期键

  • 定时删除CPU不团结
  • 惰性删除CPU友好,对内存不和谐
  • 定期去,时长过长出新CPU不协调,频率太没有产出内存不友善
逾期删除策略对RDB/AOF持久化的震慑

当过期键被惰性删除或限期去后,程序会向AOF文件增加(append)一修DEL命令,来展示地记下该键已为删。

replication

自从服务器只有接到主服务器发来的DEL命令后才会去了期键
而是,当客户端向从服务器发送GET请求,虽然于服务器键已经晚点,但是自从服务器并无会见删除该键,会拿结果返回给客户端

数据库通知(客户端通过订阅给一定频道channel或者模式pattern,来赢得知多少库键变化以及下令执行情况)
  • 关怀“某个键执行了哟令”的通告称为键空间通知
  • 关注“某个命令于什么键执行了”的通知称为键事件通报

RDB

咱将服务器受到之非空数据库和它们的键值对统称为数据库状态
SAVE命令会阻塞redis服务器进程
BGSAVE命令会派生出一个子历程
redis服务器如果开动时检测及RDB文件在,它便会自动载入RDB文件
BGSAVE命令执行时,客户端发送的SAVE命令会给服务器拒绝,BGREWRITEAOF会给延缓执行
BGREWRITEAOF执行时,客户端发送的BGSAVE命令会叫拒绝
咱得以经过配备实现数据库SAVE时间频率

save 900 1    //900一次修改
save 300 10    //300秒10次修改
save 60 10000    //60秒10000次修改

RDB文件保留的凡二进制数据

文本结构

REDIS db_version databases EOF check_sum
  • db_version长度为4字节,值也字符串表示的平头,记录RDB版本号
  • databases包含零个还是多单数据库
  • EOF长度也1只字节,标志RDB正文内容了
  • check_num为8字节无符号整数,保存个因前4只字段计算产生之校验和,以这来检查RDB是否发磨损

databases部分

SELECTDB db_number key_value_pairs
  • SELECTDB为常量,长度1字节,表示接下是db_number
  • db_number保存数据库号码,长度可以啊1字节、2字节要么5字节
  • key_value_pairs保存了数据库被具有键值对数据

key_value_pairs部分

TYPE key value
  • TYPE记录value类型
  • value字符串为REDIS_ENCODING_RWA时,大于20许节会被减去后保存

好以以下命令打开RDB文件

od -c dump.rdb

redis本身由带RDB检查器redis-check-dump


AOF

redis.conf配置appendfsync选项

appendfsync flushAppendOnlyFile函数的行为
always 将aof_buf缓冲区所有内存写入并同步到AOF文件
everysec 将aof_buf缓冲区所有内存写入并同步到AOF文件,每隔1秒钟对AOF文件进行同步
*no 将aof_buf缓冲区所有内存写入并同步到AOF文件,由操作系统决定何时AOF文件进行同步,默认是no

redis提供了AOF文件重写(rewrite)功能,新的AOF文件会举行指令压缩。

又写缓存区功能

在AOF定期写入和一块到AOF文件的而,服务器端执行之所有写命令会让记录到AOF重写缓冲区里面。
于AOF定期写入完成之后,后台hi启动一个历程将写命令追加到新的AOF文件末尾,然后据此新的AOF文件替换原有的AOF文件,完成AOF文件后台重写操作(不开缩减)


事件

redis事件分为

  • 文件事件
  • 事件风波
文件事件
  • 读事件(AE_READABLE)
  • 写事件(AE_WRITABLE)
    通过模拟接字与客户端进行连续
    文件处理器使用I/O多路复用程序来又监听多独拟接字,I/O多路复用会拿具有来事件之套接字都放一个排中
    多路复用的持有力量通过包select、epoll、evport和kqueue来实现
    先后会在编译时自动选择网遭到性能最高的I/O多路复用函数库当中redis底层实现
    只要I/O多路复用程序同意服务器又监听套接字的AE_READABLE事件和AE_WRITABLE事件,事件分配器会优先处理AE_READABLE事件
日事件
  • 定时事变
  • 周期性事件
    一个时间事件属于定时事件或周期性事件在时间事件处理器的返回值
    3.0版本redis只以周期性事件,而从不以定时事件
    日事件会给贮存于一个无序链表中,时间事件执行器运行时,它要尽历链表中之保有时间事件
事件调度

redis事件调度器会预先实行文件事件,再处理时事件
以处理事件过程被不见面出现抢占,所以实际上处理时事件之光阴比约定的时光缓慢(因为文件事件可能处理时比较长,延误了时事件的初始拍卖时)


客户端

  • 伪客户端(fake client)
    fd属性的价为-1
    伪客户端处理的一声令下请求来源于AOF文件或者Lua脚本
  • 一般客户端
    fd显示了客户端连接服务端所下的套接字描述吻合
    动用模拟接字来跟服务器进行通信

用CLIENT setname可以啊客户端设置一个名

客户端标志属性flags记录客户端角色和客户端所处状态
flags = <flag1> | <flag2> | …
REDIS_MULTI标志表示客户端在执行工作
REDIS_DIRTY_CAS表示事情使用WATCH命令监视数据库键已经让改动
REDIS_DIRTY_EXEC标志表示事情命令入队时起了不当
鲜单标志还表示事情之安全性就深受破坏

REDIS_FORCE_REPL强制主服务器将眼前执行之通令复制给有自服务器

PUBSUB的SCRIPT LOAD命令修改了服务器状态,所以该命令会写入AOF

输入缓冲区

服务端用于保存客户端发送的命令请求,最特别尺寸不超1GB
拿客户端发送的授命保存在客户端状态的argv和argc属性中
argv是一个字符串数组
argc负责记录argv数组长度

输出缓冲区

缓冲区分两种
1、固定大小缓冲区,用于保存长度比小的还原
2、可更换大小缓冲区,用于保存长度比生之复:由链表和一个或者多独字符串对象成

CLIENT list 命令

age域记录的客户端与服务端连接了略微秒
idle记录客户端空转时间
obuf_soft_limit_reached_time记录输出缓冲区首先潮到软性限制(soft
limit)的时

关门客户端

空转时间过timeout选项设置的价值经常,客户端将让关闭

  • 硬性限制:缓冲区大小超过硬性限制大小
  • 软性限制:缓冲区大小超过软性限制大小,但是未过硬性限制大小,服务器会记录obuf_soft_limit_reached_time
    假设直白过软性限制大小,持续时间超过服务器设置的时长,那么服务器将闭馆客户端。

lua_client伪客户端在服务器运行的尽生命周期中会一直留存,只有服务器被关闭时,这个客户端才见面倒闭


服务器

客户端通过将命转化为商的点子发送给服务器

SET KEY VALUE ======> *3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n

读取命令请求流程:
1、读取套接字中之吩咐请求,把其保存于客户端的输入缓冲区
2、从输入缓冲区提取命令请求中之指令参数,保存及客户端状态的argv和argc属性里面
3、调用命令执行器,执行客户端指定命令

一声令下执行器根据argv[0]参数,去命令表(command
table)查找参数所指定的一声令下
SET命令带有“wm”标识,代表履这个令前,服务器应该针对占内存状况进行反省

一声令下执行器后续操作:

  • 比方开了徐查询日志,则迟迟查询日志模块会检讨刚刚施行了的通令请求是否要补充加慢查询日志
  • calls计数器值加相同
  • AOF持久化模块写入AOF缓冲区
  • replication模块会管命传播给持有由服务器

LRU时钟
每个redis对象都产生一个lru属性,这个lru属性保存了靶最后一糟吃令访问的时光

INFO stats //可以看到服务器最近1秒钟内处理的命令请求数量

INFO memory //记录了服务器的内存峰值

serverCron函数运行时,程序都见面指向服务器状态的shutdown_asap属性进行自我批评,并依据性的值决定是否关闭服务器
当型号电脑接收至SIGTERM信号时,打开服务器状态的shutdown_asap标识

服务器状态的cronloops属性记录了serverCron函数执行之次数

服务器初始化

initServerConfig函数初始化server变量(服务器状态),并也组织中的依次属性设置默认值
包含:

  • server.clients链表,记录及服务器相连的客户端的状态结构
  • server.db数组,包含服务器里之具备数据库
  • 用来保存频道订阅信息之server.pubsub_channels字典,保存模式订阅信息的server.pubsub_patterns链表
  • 用来执行lua脚本的Lua环境server.lua
  • 用以保存慢查询日志的server.slowlog属性

initServer主要担负初始化数据结构

初始化的最后一步,执行服务器的波循环(即拍卖文件事件or时间事件)


复制

使用

SLAVEOF <master_ip> <master_port>

尽管可以吃眼前服务器成为目标IP服务器的打服务器,从服务器会复制主服务器
从今服务器的同步操作通过向主服务器发送SYNC命令来促成:
1、从服务器向主服务器发送SYNC命令
2、主服务器收到SYNC命令执行BGSAVE命令,生成一个RDB文件,并利用一个缓冲区记录由现行初始实行的有写命令
3、当主服务器BGSAVE命令就后,主服务器会将RDB文件发送给起服务器,从服务器收到并载入RDB文件
4、主服务器将记录在缓冲区里的备写命令发送给于服务器,从服务器执行这些命令

命令传播

于主导同步的步子4受到,有或以发送写命令时是主服务器被修改,这时主服务器需要对从服务器执行指令传播操作,从而达到基本一致

SYNC命令是一样种植好耗资源的操作

  • 主服务器执行BGSAVE消耗主服务器的CPU、内存和磁盘I/O资源
  • 预示服务器发送RDB会消耗大量网络资源
  • 由服务器收到载入RDB文件会死命令请求

Redis从2.8方始用PSYNC命令代替SYNC命令进行共同操作
PSYNC分为

  • 毕重复共:SYNC操作一致
  • 一些重新共:用于拍卖断线后重复制情况,主服务器可以用主导服务器连接断开期间履行的写命令发送给于服务器

着力服务器断开连接恢复后,从服务器会向主服务器发送PSYNC命令,主服务器会向从服务器返回COTINUE命令回复,然后往于服务器发送缺少的下令

一些重新共实现

  • 预示服务器的复制偏移量、从服务器复制偏移量
  • 主服务器的复制挤压缓冲区
  • 服务器的运行ID
复制偏移量

主干服务器分别会见保护一个复制偏移量

  • 预告服务器每次向于服务器传播N个字节的数经常,就拿团结的复制偏移量值长N
  • 由服务器每次收到从主服务器传播来之N个字节数据常常,就拿团结的复制偏移量的值长N

由此对比中心服务器的复制偏移量,程序就算会知道主从服务器是否处于相同状态

复制积压缓冲区

预告服务器维护,固定长度,先进先出,默认大小也1MB
复制挤压缓冲区会保留有以来传的抒写命令,并且记录每个字符的偏移量
当主从服务器恢复断开的连天后,从服务器会把PSYNC命令和融洽之复制偏移量offset发送给主服务器,主服务器判断offset是否在复制积压缓冲区
1、在复制积压缓冲区,执行PSYNC
2、不在复制积压缓冲区,执行SYNC完整的双重共

相似复制积压缓冲区大小相等2 * second * write_size_per_second
second为打服务器再连接上主服务器所急需的平均日
write_size_per_second是预示服务器平均每秒产生的刻画命令数据量

服务器运行ID

鉴于40个随机的十六迈入制字符组成
初次replication的早晚,主服务器会将ID发送给起服务器保存
断线重连下

  • ID与从服务器保存的同样,则止待PSYNC
  • ID与由服务器保存之差,则说明主服务器都再次选举,则replication执行总体的SYNC
PSYNC命令实现

PSYNC <runid> <offset>
runid也上次复制的主服务器运行ID
offset也于服务器时的偏移量

主服务器返回FULLRESYNC <runid>
<offset>,表示主服务器已转移,runid是最新的ID,offset是当下新主服务器的偏移量,需要实施总体的SYNC
预示服务器返回CONTINUE,则实行PSYNC
主服务器返回ERR,表示主服务器版本低于2.8,无法辨别PSYNC命令

复制的兑现原理

SLAVEOF <master_ip> <master_port> 保存数据结构

struct redisServer {
    //...
    //主服务器的地址
    char *masterhost;

    //主服务器的端口
    int masterport;
    //..
};

中心服务器通过模拟接字连接,从劳动器会为者宪章接字关联一个特别用来拍卖复制工作的文件事件处理器

当于服务器成为主服务器客户端后,做的首先宗工作是向主服务器发送PING请求
当主服务器返回超时或者返回一个误时,则从服务器断开并且再连接主服务器套接字

主服务器如果设置了requirepass,会针对从服务器进行身份验证,从服务器需要针对masterauth选项进行配备

主服务器会保护一布置从劳动器端口表

typedef struct redisClient {
    //...
    //从服务器的监听端口号
    int slave_listening_port;
    //...
} redisClient;

预告服务器执行INFO replication命令打印出从劳动器端口号

心跳监测

起服务器会坐各国秒一次于的平率,向主服务器发送REPLCONF ACK
<replication_offset>
replication_offset为偏移量
心跳监测的意吧:

  • 监测主从服务器网络状态
  • 助实现min-slaves选项:防止主服务器在无安全状况下实施写命令,如果从服务器遭受多数的推还不止等于N秒,则预示服务器拒绝执行命令
  • 监测命令丢失:通过偏移量,主服务器会对丢失数据开展补发操作

Sentinel

Sentinel(哨岗、哨兵)是Redis高可用解决方案
由一个要么基本上只Sentinel实例组成的Sentinel系统监视任意多独主服务器
于受监视的主服务器进入下线状态时,自动将下线主服务器属下的某部从服务器升级为新的主服务器

Sentinel本质上一味是一个运转于非常模式下之Redis服务器
Sentinel不利用数据库,数据保存在内存里

Sentinel状态被的masters字典记录了具备为Sentinel监视的主服务器的系消息

每个sentinelRedisInstance结构意味着一个呗Sentinel监视的Redis服务器实例,这个实例可以为主服务器、从服务器,或者另外一个Sentinel

typedef struct sentinelRedisInstance {
    //标识值,记录了实例的类型,以及该实例的当前状态
    int flags;

    //实例的名字
    //从服务器的名字由用于在配置文件中配置
    //从服务器以及Sentinel的名字由Sentinel自动设置
    //格式为ip:port,例如“127.0.0.1:26379”
    char *name;

    //实例的运行ID
    char *runid;

    //配置纪元,用于实现故障转移
    unit64_t config_epoch;

    //实例的地址
    sentinelAddr *addr;

    //SENTINEL down-after-milliseconds选项设定的值
    //实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
    mstime_t down_after_period

    //SENTINEL monitor <master-name> <IP> <port> <quorum>选项中的quorum参数
    //判断这个实例为客观下线(objectively down)所需的支持投票数量
    int quorum;

    //SENTINEL parallel-syncs <master-name> <number>选项的值
    //在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
    int parallel_syncs;

    //SENTINEL failover-timeout <master-name> <ms>选项的值
    //刷新故障迁移状态的最大时限
    mstime_t failover_timeout;
    //...
} sentinelRedisInstance;

Sentinel会创建两个连于主服务器的异步网络连接:
令连接,专用于向主服务器发送命令,并接受命令回复
订阅连接,专门用来订阅主服务器的sentinel:hello频道

2只连的必要性:目前pubsub功能中,被发送的音信不会见保留于redis服务器遭到,如果客户端离线或者断线,就会丢该信息,为了不丢掉sentinel:hello频道的别样音讯,Sentinel必须特别用一个订阅连接来经受该频道消息,另外Sentinel必须为主服务器发送命令,一糟糕来和主服务器通信,所以有命令连接。

Sentinel默认会以各国十秒一次等的效率,通过命令连接于吃监视的预兆服务器发送INFO命令,并经INFO命令的恢复来赢得主服务器的眼前消息。
相同Sentinel会以各级十秒一软的频率通过命令连接于于服务器发送INFO命令。

默认情况下,Sentinel会以各级半秒一赖的频率,通过命令连接于所有为监视的主服务器和由服务器发送以下格式的指令:

PUBLISH _sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
  • s_始于的参数记录的凡Sentinel本身的信息
  • m_开班的参数记录的凡预示服务器的音

Sentinel通过接受主服务器或者从服务器发来之频段信息来发现未知的新Sentinel

监测上下线

Sentinel会以各秒一差的效率为所有和它创建了命连接的实例(包含主服务器、从服务器、Sentinel)发送PING命令,通过实例返回的过来判断实例是否在线
Sentinel的安排文件被的down-after-milliseconds选项制定了判断实例进入下线所要的时日长短(这个专业不仅用于判断master,还见面用来master属下的所有自服务器)
设一个设备上下线状态,Sentinel会在他的组织里之flags属性里打开SRI_S_DOWN标识,以之来表示这实例已经进来主观下线状态

当Sentinel将一个主服务器判断也主观下线后,为了确认是主服务器是否真下线,它见面由平监视这主服务器的另外Sentinel进行询问,当它接受至足够数量(配置quorum参数)的曾下线判断之后,Sentinel会将从服务器判定为客体下线。

Sentinel选举算法

重要思路来源于raft算法
http://thesecretlivesofdata.com/raft/

  • 监视以及一个主服务器的具备在线的Sentinel都好叫增选为领头Sentinel
  • 老是进行选举后,无论选举成功吗,所有Sentinel的配备纪元会+1
  • 当一个配置纪元里面,Sentinel都产生同不成投票权,将某Sentinel选为一些领头,局部一旦设置不能够修改
  • 每个意识主服务器上客观下线的Sentinel都求另外Sentinel将好选出为片领头Sentinel
  • Sentinel选举局部领头的平整是优先到事先得
  • 推选局部头领的规则是1、配置纪元相同,2、运行ID一致
  • 每个Sentinel的选票只发同样布置
  • 止生给多数的Sentinel设置成一部分头领的Sentinel,才见面成真正的头儿
  • 每当一个布置纪元里面,只会现出一个领头Sentinel
  • 假设没选出来,或者有差不多个组成部分领头投票相同,则以展开相同次于加时赛
故障转移

1、从拥有自服务器遭受甄选有一个转账为主服务器
2、将其余从服务器移吗复制新服务器
3、将已经下线的主服务器改也于服务器,这个旧主服务器再上线后,会成新的主服务器的打服务器
透过发送SLAVEOF no one命令,将以此从服务器移吗兆服务器,通过SLAVEOF
<master_ip> <master_port>配置主从关系

主服务器选举规则

领衔的Sentinel根据从服务器优先级,对列表中多余的从服务器进行排序,选出优先级最高、偏移量最深、运行ID最小(优先级从赛交小)的起服务器


集群

Redis集群由多个节点组成
连接节点的办事经过CLUSTER
MEET命令就,收到命令的节点A会以及节点B进行握手(handshake),以这个来确认彼此的在,并也前之愈来愈通信打好基础,然后节点A会将节点B的信息通过gossip协议传播为集众多被之别节点,来深受另外节点认识节点B
一声令下格式如下:

CLUSTER MEET <ip> <port>

通过以下命令查询集群目前带有的节点情况

CLUSTER NODES
槽指派

Redis集群通过分片技术来保存数据库中之键值对
集群的全部数据库让分为16384个槽(slot)

clusterNode结构的slots和numslot记录了节点负责处理哪些槽

struct clusterNode {
    //...
    unsigned char slots[16384/8];
    int numslots;
    //...
};

slots属性是一个二进制的一再组,长度为16384/8=2048只字节,共含16384独二进制位

以集群执行命令

当客户端向节点发送数据库键相关的指令时,接收命令的节点会计算起命令要处理的数额库键属于哪个槽,并检查是槽是否派出给协调

  • 若果键刚好就是派出给当下节点,那么节点直接执行该令
  • 而键所于的槽并没有派出给当下节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向至是的节点,并还发送之前想只要尽之一声令下

redis通过槽分配算法来算得出槽号i,节点会检查数组中slot项之槽,然后判断键是否由于友好担负

  • 如果clusterState.slots[i] =
    clusterState.myself,说明槽i由目前节点负责
  • 如果clusterState.slots[i] !=
    clusterState.myself,节点会冲clusterState.slots[i]转车目标clusterNode结构记录的节点IP和端口号,并朝客户端返回MOVED错误

MOVED <slot> <ip>:<port>

节点转向实际即便变换一个模仿接字来发送命令

Redis重新分片操作是出于Redis集群管理软件redis-trib负责履行

ASK错误

在还分片期间,如果键被搬至另外节点,那么源节点将朝客户端返回一个ASK错误,指引客户端转向正在导入槽的对象节点(clusterState里的magrating_slots_to[i]),并再次发送之前想使履的吩咐。

typedef struct clusterState {
    //importing_slots_from数组记录了当前节点正在从其他节点导入的槽
    clusterNode *importing_slots_from[16384];
    //migrating_slots_to数组记录了当前节点正在迁移至其他节点的槽
    clusterNode *magrating_slots_to[16384];
} clusterState;

客户端转向到目标节点之后,会发送一个ASKING命令,之后再行还发送原本想要实践之命
ASKING命令需要开的是开辟该客户端的REDIS_ASKING标识
万一节点的clusterState.importing_slots_from[i]展示该节点正在导入槽i,客户端碰到REDIS_ASKING命令的前提下,节点会破例执行这关于槽i的下令一次
REDIS_ASKING标识是一次性标识,当节点执行了扳平浅带有REDIS_ASKING标识的客户端命令后,客户端的REDIS_ASKING标识就会见被移除。

MOVED错误以及ASK错误区别

MOVED错误标识槽的负责权预计从一个节点换到另外一个节点上,以后重新请求该槽,会靠于到对的节点
ASK是处理槽迁移过程遭到之同一种植临时措施,ASK错误不会见对后的下令请求产生其他影响

clusterState.myself.flags属性的值为REDIS_NODE_SLAVE代表节点是一个从节点,如果flags属性为REDIS_NODE_MASTER,表示节点为主节点

假若在一个集群中,半数以上负责处理槽的主节点都以某主节点x报告呢疑似下线,那么这主节点x将受记为已经下线(FAIL),并且以主节点x标记为早已下线的节点会向集群广播

消息

汇聚众多被相继节点通过发送和收信息来进展通信

  • MEET:发送者会为接受者发送MEET消息,请求接受者加入到发送者当前所处的集群内
  • PONG:一个节点吧堪通过奔集群广播自己之PONG消息来让集众多中之别节点及时刷新关于此节点的认,比如同糟故障转移成功实践下
  • FAIL:如果A节点判断节点B已经进FAIL状态时,节点A会于集群广播一长条有关节点B的FAIL的音信
MEET、PING、PONG音之落实

马上三种植信息还是由片个clusterMsgDataGossip结构重组

union clusterMsgData {
    //...
    //MEET、PING和PONG消息的正文
    struct {
        //每条MEET、PING、PONG消息都包含两个
        //clusterMsgDataGossip结构
        clusterMsgDataGossip gossip[1];
    } ping;
    //其他消息的正文...
};

节点通过消息头的type属性来判断一致长条消息是MEET/PING/PONG消息
历次发送MEET、PING、PONG消息,发送者都打自己之就掌握节点列表中所及选出两单节点(可以是主节点或者由节点),并将即时点儿个节点的信息分别保存至个别只clusterMsgDataGossip结构中

typedef struct {
    //节点的名字
    char nodename[REDIS_CLUSTER_NAMELEN];
    //最后一次向该节点发送PING消息的时间戳
    uint32_t ping_sent;
    //最后一次从该节点接收到PONG消息的时间戳
    uint32_t pong_received;
    //节点的IP地址
    char ip[16];
    //节点的端口号
    uint16_t port;
    //节点的标识值
    uint16_t flags;
} clusterMsgDataGossip;
PUBLISH信的落实

客户端向集群发送命令
PUBLISH <channel> <message>
接至PUBLISH命令的节点,它会朝着集群广播一修PUBLISH消息
改换句话说,这个令会促成集众多被之所有节点都向channel频道发送message消息

clusterMsgDataPublish
typedef struct {
    uint32_t channel_len;
    uint32_t message_len;
    //定义为8字节只为了对齐其他消息结构
    //实际的长度由保存的内容决定
    unsigned char bulk_data[8];
} clusterMsgDataPublish;

bulk_data属性是一个字节数组
字节数组保存了客户端通过PUBLISH命令发送给节点的channel参数和message参数
channel_len和message_len保存了即有限独参数的尺寸
bulk_data第0~channel_len-1字节保存channel参数,channel_len到channel_len+message_len-1字节封存message参数


公布与订阅

Redis发布以及订阅功能由PUBLISH、SUBSCRIBE、PSUBSCRIBE等一声令下成

通过执行SUBSCRIBE指令,客户端可订阅一个要么多单频道

PSUBSCRIBE一声令下订阅一个或者多独模式,从而成为一个模式的订阅者

当其他客户端向有频道发送信息,消息不但会发给订阅者,还会见发放具有与之频道相匹配的模式之订阅者

频道的订阅与退订

程序执行SUBSCRIBE命令订阅某个或某些频道时,服务器都见面拿客户端与为订阅的频道在pubsub_channels字典中开展关联

  • 倘已发出其他订阅者,则以客户端append到订阅者链表队尾
  • 若首坏订阅,程序首先在pubsub_channels字典中为频道创建一个键,并且安装空链表,然后用客户端append到链表,成为链表的率先独元素

退订频道

  • 根据频道称匹配链表,remove相关节点
  • 如果是最后一个节点,则remove节点同时,从pubsub_channels字典中删去频道对应键
模式之订阅与退订

跟频道订阅类似,模式订阅关系保留在pubsub_patterns里面,单一链表,不区分键

struct redisServer {
    //...
    //保存所有频道的订阅关系
    dict *pubsub_channels;
    //...
    //保存所有模式订阅关系
    list *pubsub_patterns;
};
发送信息
PUBLISH <channel> <message>

当redis客户端执行命令,将message发送给channel频道的时节,服务器执行以下简单独动作:
1、将信息message发送channel所有频道订阅者
2、如果出一个还是基本上个模式pattern与频道channel相配合,那么用message发送给pattern模式的订阅者

PUBSUB CHANNELS[pattern]
  • 如果不吃定pattern,那么命令归来服务器即为订阅的享有频道
  • 倘若让定pattern,那么命令归来服务器即及pattern模式相兼容的频段

PUBSUB NUMSUB [channel-1 channel-2 ... channel-n //该命令接收多个频道作为入参,并返回这些频道的订阅者数量

事务

Redis通过MULTI、EXEC、WATCH等一声令下来贯彻工作功能

事务首先为一个MULTI命令开始
下一场由EXEC命令将这业务提交(commit)给服务器执行

业务队列

每个Redis客户端都发出谈得来的作业状态,保存于mstate字段中

typedef struct redistClient {
    //...
    //事务状态
    multiState mstate; /* MULTI/EXEC state */
    //...
} redisClient;

业务状态包含一个事务队列,以及一个曾入队命令的计数器

typedef struct multiState {
    //事务队列,FIFO顺序
    multiCmd *commands;
    //已入队命令计数
    int count;
} multiState;

工作队列是一个multiCmd类型的数组

typedef struct multiCmd {
    //参数
    robj **argv;
    //参数数量
    int argc;
    //命令指针
    struct redisCommand *cmd;
} multiCmd;
WATCH命令

WATCH命令是一个乐观锁(optimistic
locking),它好于EXEC命令执行前,监视任意数量之数据库键,并在EXEC执行时,检查为监视键是否至少发生一个都被涂改了了,如果是的话,服务器拒接执行工作,会沾失败的东山再起。

typedef struct redisDb {
    //...
    //正在被WATCH命令监视的键
    dict *watched_keys;
    //...
} redisDb;

watched_keys是一个字典类型,服务器可以知道地懂得什么数据库键正在被监视
SET/LPUSH/SADD/ZREM/DEL/FLUSHDB等对数据库修改的一声令下,都见面触发multi.c/touchWatchKey函数对watched_keys字典进行检查

假若客户端事务安全性为破坏,那么touchWatchKey会看到REDIS_DIRTY_CAS标识为打开

事务的ACID性质

ACID,原子性Atomicity、一致性Consistency、隔离性Isolation、耐久性Durability

假设一个命令入队时错,会叫服务器拒接执行,事务中有着命令还未见面吃实践

redis>MULTI
OK

redis> SET msg "hello"
QUEUED

redis> GET
(error) ERR wrong number of arguments for 'get' command

redis> GET msg
QUEUED

redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

Redis不支持工作回滚机制
若事情队列在实行中出现了错,整个工作也会继续执行下去,直到将业务队列中之享有命令还尽了了

redis> SET msg "hello" # msg键是一个字符串
OK

redis> MULTI
OK

redis> SADD fruit "apple" "banana" "cherry"
QUEUED

redis> RPUSH msg "good bye" "bye bye" #错误地对字符串键msg执行列表键的命令
QUEUED

redis> SADD alphabet "a" "b" "c"
QUEUED

redis> EXEC
1) (integer) 3
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 3

若果在Redis事务执行中,服务器停机,则因持久化策略进行不同档次的恢复

回报了保证工作之耐久性,事务之末尾加上SAVE命令,但是这种做法频率太没有,不具实用性


Lua

EVAL

本条命令可以直接执行Lua脚本

EVALSHA

冲SHA1值来施行脚本function,要求校验和呼应之台本必须至少让EVAL命令执行过一样次于

Redis为了保证自由函数每次执行还发生同样之自由数排,所以本着math.random进行的改造
手拉手改造之还有SINTER/SUNION/SDIFF/SMEMBERS/HKEYS/HVALS/KEYS等一声令下

Redis并未禁止用户改都存在的全局变量,所以于执行Lua脚本的时节,必须特别小心,以免错误地改了曾在的全局变量

Redis使用串行化的法子来执行Redis命令,因此所有Redis服务器就待创造一个Lua环境即可

lua_scripts字典用于保存SCRIPT LOAD命令载入了的Lua脚本

struct redisServer {
    //...
    dict *lua_scripts;
    //...
};

服务器在执行Lua脚本之前,会呢脚本定义一个和该相对于之Lua函数,名字为f_面前缀+脚本的SHA1校验和当作后缀(40配符长度)

比如:

EVAL "return 'hello world'" 0

当服务器Lua环境被相当于

function f_5332031c6b470dc5a0dd9b4bf2030dea6d65de91 ()
    return 'hello world'
end

使用f_+SHA1定义函数有以下好处:

  • 实施下论步骤省略
  • 经函数的区域性,避免下全局变量
  • 要是脚本对应的函数在Lua环境让实施过一样潮,那么要记住是SHA1校验和,服务器可以于匪懂得脚本本身的状况下直通过调用Lua函数来实行脚本,这即是EVALSHA的兑现原理

Lua环境在实践脚本时,会装作在逾期处理钩子(hook),钩子可以在剧本运行超时时候,通过SCRIPT
KILL停止脚本,或者SHUTDOWN命令关闭服务器

剧本执行结果碰头保留在客户端状态的出口缓冲区

SCRIPT FLUSH

用于清理服务器被具备与Lua脚本有关的消息

SCRIPT EXISTS

冲输入的SHA1校验和,检查校验和对应的脚本是否有叫服务器中

SCRIPT LOAD + EVALSHA = EVAL
剧本复制

Redis通过命令传播的法子将实践之通令发送给从服务器

可当履EVALSHA的时候,会校验从服务器的lua_scripts是否留存对应的Lua脚本,如果未存在EVALSHA命令会吃转发为EVAL传播让由服务器

每当主服务器添加一个从服务器时,主服务器都见面清空自己的repl_scriptcache_dict字典,强制重新为有自服务器传播脚本,保证一致性


缓缓查询日志

slow-log-slower-than选项指定执行时越多少毫秒

slowlog-max-len指定服务器最多保留多少条慢查询日志,添加同长达新的缓查询日志之前,如果容量不够,会先将无限老的均等漫长慢查询日志删除

如若CONFIG
SET命令将slow-log-slower-than置为0毫秒,则其它命令还见面被记录及款查询日志

SLOWLOG GET用于查询时服务器所保存的暂缓查询日志

struct redisServer {
    //..
    //下一条慢查询日志的ID
    long long slowlog_entity_id
    //保存了所有慢查询日志的链表
    list *slowlog;
    //服务器配置slowlog-log-slower-than选项的值
    long long slowlog_log_slower_than;

    //服务器配置slowlog-max-len选项的值
    unsigned long slowlog_max_len;
    //...
};

在创建同条慢查询日志时,都见面针对slowlog_entity_id做+1操作
历次创建新日志时候,都见面补加至slowlog链表的表头,删除会由链表尾开始去

typedef struct slowlogEntry {
    //唯一标识符
    long long id;

    //命令执行时的世界,格式为UNIX时间戳
    time_t time;

    //执行命令消耗的时间,以微秒为单位
    long long duration;

    //命令与命令参数
    robj **argv;

    //命令与命令参数的数量
    int argc;
} slowlogEntry;

SLOWLOG LEN足查询日志数量

SLOWLOG RESET用以破除所有慢查询日志

留下评论

网站地图xml地图