redis技术内幕

周立功CAN盒二次开发实录:用QT5.1.0解析汽车CAN报文(附数据可视化技巧) 本文详细介绍了基于QT5.1.0和周立功USBCAN-E-U设备进行汽车CAN总线二次开发的实战过程。核心内容包括利用CANdb++数据库解析原始报文并转换为物理值,以及通过QCustomPlot库实现高性能实时数据可视化,为车载诊断与车联网开发提供了一套完整的工程实践方案。 阅读详情

redis基本数据结构及实现

redis基本数据结构

  • string
  • list
  • set
  • zset
  • hash

redis数据结构的实现

SDS(simple dynamic string,SDS简单动态字符串)

SDS是redis基于C字符串自己构建的一种数据结构,在redis中有着广泛的应用,下面揭开SDS的神秘面纱

  • SDS结构定义
// sds.h/sdshdr
struct sdshdr {
    int len;     // SDS保存字符串的长度
    int free;    // buf数组中未使用的字节的数量
    char buf[];  // 字节数组,用于保存字符串
}
  • SDS逻辑图,如图1
    • free(buf数组中未使用的字节的数量,图1中free=5,表示buf空出了5个字节)
    • len(buf数组中未使用的字节的数量,图1中len=5)
    • buf(字节数组,用于保存字符串,length(buf)=len+free+1(多出来的1字节保存’\0’))

Alt

图1 SDS逻辑图
  • SDS的特性

    • 空间预分配
      空间预分配是指redis的SDS是可修改的,并且在对字符串操作之前进行长度校验,如果长度不足,则会申请一定的空间进行存储。如果source=sdshdr("Redis"), sdscat(source, "spencer")执行sdscat之前会检查append的字符串长度大于free,那么buf需扩容

    • 扩容规则

    1. 修改之后len属性小于1MB,那么free=len(修改后的)。
    2. 修改后的len属性大于1MB,那么free=1MB。
    • 空间释放
      空间释放是说对redis字符串进行截断操作,则SDS的len属性将减少,即使空出一部分,free属性将增加,但redis不回收这部分空间。

    • 二进制安全
      二进制安全是说redis字符串可以保存图片、音频等二进制数据,C字符串不能保存二进制数据是因为C字符串以’\0’结尾,但是这些二进制数据本身可能包含’\0’,而redis的SDS具有len属性,知道buf何处结束,因此能够保存。

    • 兼容C的API
      虽然redis的SDS保存有len属性,但是为了兼容C字符串的API,还是保存了字符串末尾’\0’的规范,这也就是为什么length(buf)=len+free+1

SDS问题

redis是二进制安全的,也就是说它能够保存二进制数据,这些二进制数据文本中可能带有’\0’,而redis的SDS又兼容C字符串的API,那么在对二进制数据执行C字符串的API时难道没有问题吗?二进制数据不会被截断吗?

链表

redis中的链表为带有头尾节点的双向链表,结构比较简单,这里只给出链表的结构定义及逻辑图

  • 链表的结构定义
// adlist.h/listNode
typedef struct listNode {
    struct listNode *prev; // 前置节点
    struct listNode *next; // 后置节点
    void *value;           // 节点的值 
}

// adlist.h/list
typedef struct list {
    listNode *head;                      // 链表的头结点
    listNode *tail;                      // 链表的尾结点
    unsigned long len;                   // 链表中包含的节点数量
    void *(*dup)(void *ptr);             // 节点值复制函数
    void (*free)(void *ptr);             // 节点值释放函数
    int (*match)(void *ptr, void *key);  // 节点值比较函数
}
  • 链表的逻辑图
    Alt
图2 redis使用的链表逻辑图

字典

  • 字典的定义
// dict.h/dict
typedef struct dict {
    dictType *type; // 特定于类型的处理函数
    void *privdata; // 类型处理函数的私有数据
    dictht ht[2];  // 哈希表(2个)
    int rehashidx; // 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行
    int iterators; // 当前正在运作的安全迭代器数量
} dict;

// dict.h/dictht 
typedef struct dictht {
    dictEntry **table; // 哈希表节点指针数组(俗称桶,bucket)
    unsigned long size; // 指针数组的大小
    unsigned long sizemask; // 指针数组的长度掩码,用于计算索引值
    unsigned long used; // 哈希表现有的节点数量
} dictht;
  • 字典的逻辑图
    Alt
图3 redis字典的逻辑图
  • hash算法及拉链法

    • redis计算哈希值和索引值方法
    hash = dict->type->hashFunction(key);
    index = hash & dict->ht[x].sizemask; // x根据不同情况可取0,1
    
    • 拉链法解决冲突
      当出现key冲突时,即两个不同的key使用redis计算出来的索引值相同,redis会采用拉链法解决冲突,即每一个dictEntry都有一个next指针指向下一个dictEntry

    • 扩容和缩容(表格中n为需要变化的最小值,取整;bgsave和bgrewriteaof分别为持久化的命令)

      load_factor = ht[0].used / ht[0].size
      
    名称时机机制
    扩容(load_factor >=1 && (!bgsave && !bgrewriteaof)) ||
    ( load_factor >= 5 && ( bgsave || bgrewriteaof ) )
    2^n >= ht[0].used*2
    缩容load_factor < 0.12^n>=ht[0].used
    • 渐进式rehash
      • 步骤
    1. 给ht[1]分配适当的空间;
    2. rehashidx=0,表示rehash工作开始;
    3. 在rehash期间,每次对字典进行操作时,除了必要的操作另外,还会将ht[0]中rehashidx位置上所有键值对rehash到ht[1]上,该rehashidx所有键值对rehash完毕,rehashidx++;
    4. 随着时间进行,ht[0]上所有的键值对都rehash到ht[1]上,此时rehashidx=-1表示当前没有在进行rehash
    • 好处
      渐进式rehash将重新hash的操作分摊到每个key上,避免了集中式rehash带来的庞大计算量

    • 查询键及更新键
      执行delete、find、update等操作时可能服务器正在进行rehash操作,因此需要计算该key在ht[0]和ht[1]两个index然后查找,如果都找不到,则返回不存在。
      新增键一律添加到ht[1]上,这样就保证随着时间的推进ht[0]上所有的键都被rehash到ht[1]上

跳跃表

  • 跳跃表的结构定义
// redis.h/zskiplist
typedef struct zskiplist {
    struct zskiplistNode *header, *tail; // 头节点,尾节点
    unsigned long length;                // 节点数量
    int level;                           // 目前表内节点的最大层数
} zskiplist;

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

  • 跳跃表的逻辑图
    Alt
图4 跳跃表的结构
  • 结构详解
    将跳跃表的结构和跳跃表的逻辑图结合起来看更方便理解。zskiplist中有指向跳跃表节点zskiplistNode的头尾指针,每个zskiplistNode都有指向下一个节点的指针(forward)和前一个节点的指针(backward),这样设计不管从前往后或者从后往前遍历都很方便。除此之外,每一个zskiplistNode还有“层”的概念,越往上一层,数据越少。redis使用一种算法,当插入某一个节点时(首先插入到level[0]中),会有1/2概率插入到level[1],会有1/4的概率插入到level[2]…

  • 性能表现
    跳跃表是redis中zset的底层实现之一,支持平均O(logN)、最坏O(N)复杂度的节点查找.正是因为跳跃表中含有backward和forward指针,因此可以在O(N)时间内通过score正序/倒序排列出zset所有元素

整数集合

  • 整数集合的结构定义
// intset.h/intset
typedef struct intset {
    uint32_t encoding; // 保存元素所使用的类型的长度
    uint32_t length;   // 元素个数
    int8_t contents[]; // 保存元素的数组
} intset;
  • 整数集合的结构图

    • encoding(取值有int16,int32,int64)
    • length(长度)
    • contents(整数集合中的元素)
      Alt
    图5 整数集合的结构
  • 升级
    整数集合中如果遇到操作数比当前元素的范围大,则会先进行类型升级(int16–>int32–>int64),但不会降级

压缩列表

  • 压缩列表的结构定义
    Alt
图6 压缩列表结构定义
  • 压缩列表字段说明
属性类型长度用途
zlbytesuint32_t4 字节记录整个压缩列表占用的内存字节数在对压缩列表进行内存重分配,或者计算zlend 的位置时使用
zltailuint32_t4 字节记录压缩列表表尾节点距离压缩列表的起始地址有多少字节 通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址
zllenuint16_t2 字节记录了压缩列表包含的节点数量当这个属性的值小于 UINT16_MAX(65535)时,这个属性的值就是压缩列表包含节点的数量当这个值等于 UINT16_MAX时,节点的真实数量需要遍历整个压缩列表才能计算得出
entryX列表节点不定压缩列表包含的各个节点节点的长度由节点保存的内容决定
zlenduint8_t1 字节特殊值 0xFF (十进制 255 ),用于标记压缩列表的末端
  • 压缩列表的结构图
    Alt
图7 压缩列表结构图
  • 压缩列表节点构成
    • 字段说明

      属性名长度详细说明
      previous_entry_length1/4字节如果前一节点的长度小于254字节,那么长度为1字节,前一节点的长度就保存在这一个字节里面;如果前一节点的长度大于等于254字节,那么长度为5字节;其中属性的第一字节会被设置为0xFE(十进制值 254)而之后的四个字节则用于保存前一节点的长度
      encoding1字节标识content保存的值
      content变长保存数据
    • 示例
      Alt

图8 压缩列表节点实例
  • 连锁更新
    在一个压缩列表中,有多个连续的、长度介于250字节到253字节之间的节点e1至eN,这时, 如果我们将一个长度大于等于254字节的新节点new设置为压缩列表的表头节点, 那么 new将成为e1的前置节点,e1的previous_entry_length将扩展为5字节,引起e2更新…从而导致连锁更新.删除一个节点时也会导致连锁更新
  • 遍历与更新
    • 遍历
      在从后往前遍历时,首先拿到起始指针p,根据zltail偏移量计算跳到最后一个entry上,然后在最后一个节点根据previous_entry_length跳到倒数第二个节点…以此类推完成从后往前遍历;在从前往后遍历到某个节点X时,已经知道该节点的起始指针p,再根据encoding中本节点的长度知道偏移多少字节可以到达下一个节点起始地址p+length完成遍历。
    • 更新
      在尾结点后插入元素时,先生成相应的节点,然后更新zltail。在头结点前插入元素时,需要先将所有元素后移,再插入到头结点。插入删除节点都可能导致压缩列表连锁更新。

redis数据结构总结

Alt

图9 redis基本数据结构与其底层结构实现

redis过期策略与持久化

redis数据库

  • 客户端服务器结构定义
// 服务端 redis.h/redisDb 
struct redisServer {
    // ...
    int dbnum;   // 服务器中数据库数量
    redisDb *db; // 一个数组,保存着redis中所有的数据库
    // ...
}

// 客户端 
struct redisClient {
    // ...
    redisDb *db; // 一个指针,指向当前连接的服务端的数据库
    // ...
}

typedef struct redisDb {
    // ...
    dict *dict; // 数据库键空间,保存着数据库中所有的键值对
    // ...
} redisDb;
  • 客户端服务器逻辑结构图
    Alt
图10 redis客户端服务端与数据库连接

从图10中可以看到,RedisServer中有一个变量dbnum保存当前redis服务器中数据库的数量,默认是16,配置文件中可以修改。db则是一个大小为dbnum的redisDb的数组。redisClient中有一个指向redisDb数组中某一个元素的指针,使用select可以切换客户端连接的数据库,也就是修改db指针的指向,但是到目前为止,redis没有一个命令返回当前所处的数据库编号,在使用时要格外小心。
Alt

图11 redisDb数据结构示意

redisDb是一个结构体,其中dict存储了所有的键值对,expires保存了所有带有过期键的信息,键的过期时间保存在longlong类型的变量expireTimestamp中,该变量是一个时间戳,在计算某一个键的过期时间时使用当前时间戳减去expireTimestamp即可。

  • 更新及查询操作
    所有的操作均作用在redisDb.dict上,有了图11的逻辑架构,对于redis的更新及查询操作就很容易理解了

  • 过期键删除设置

    • 方法
      redis通过expire key 5设置过期时间,通过ttl key获取过期时间
    • 删除策略
    策略名称详细描述优点缺点
    定时删除每当有一个key设置了过期时间则开启一个定时器,定时器到期则删除key对内存友好,所有的key在过期的第一时间会被清除内存对CPU不友好,若有大量的key在同一时间过期,redis服务器会在该时间附近处理大量删除key操作,可能造成服务器短暂不可用
    惰性删除每当有key设置了过期时间则记录该时间,下次访问时,先检查该key是否过期,如果过期,则删除对CPU友好,删除key的操作只发生在访问该key时,服务器响应快对内存不友好,如果设置了过期时间但是却不再次访问它,那么该key一直存在于内存中不被清理,某种情况下可以认为是内存泄漏
    定期删除服务器每隔一定的时间(默认100ms)扫描一遍,查看数据库中的key是否过期,如果过期则删除,限制删除操作的时长和频率是上述两种方法的折中难以确定删除执行的时长和频率,如果执行时间太长,或者太频繁,则退化为定时删除;如果执行时间太短,或者执行太少,则退化为惰性删除
  • redis过期键删除策略
    redis采用惰性删除+定期删除的策略.一方面当对某个key进行操作前走惰性删除;另一方面,redis在redis.c/serverCron函数执行时,采取定期删除.

  • redis持久化对过期键的策略

  1. RDB持久化

    • 生成RDB文件
      执行save或bgsave时将未过期的键保存到RDB文件中

    • 载入RDB文件

      1. 服务器以主服务器模式运行
        过期的键被忽略
      2. 服务器以从服务器模式运行
        不管键是否过期,都载入,主从服务器同步时,从服务器的数据库被清空
  2. AOF持久化
    AOF文件写入时,对于已过期的键,会单独生成一条del key的命令;AOF重写时,程序会对数据库中的键进行检查,已过期的键不会保存到重写后的AOF文件中。

  3. 复制
    主服务器删除一个过期键后,会显示发送一条del命令给从服务器;从服务器为保证跟主服务器数据一致性,不主动删除,只有收到主服务器的del命令才删除key

redis持久化

RDB持久化

  • 创建
创建方法服务器情况
save阻塞,无法处理请求
bgsave非阻塞,派生出子进程创建,父进程继续处理请求
  • 自动间隔保存
    在这里插入图片描述
图12 RDB持久化示意图

图12展示了RDB文件持久化的逻辑,当客户端发送save、bgsave命令时,redis服务器会生成一个RDB文件;当redis服务器因某种原因宕机后重启时,redis会加载本地的dump.rdb文件还原。save和bgsave的区别是save会阻塞redis服务器,直到RDB文件生成完毕,而bgsave会fock出一个子进程进行操作,父进程继续处理客户端请求。由于生成RDB文件是一个比较耗时的操作,因此一般不建议直接执行save命令。
redis支持通过配置的方式以一定的策略进行bgsave的操作,如图12所示,在redis.conf配置文件中写入

save 900 1
save 300 10
save 60 10000

表明上一次生成RDB文件后,900s中有1次更新或者300s中有10次更新或者60s中有10000次更新时需要执行bgsave操作。那么对于RedisServer是怎么做到这一点的呢,首先redis服务器加载redis.conf将其解析成saveparams数组,数组中的每一个元素包含seconds和changes两个参数,在redisServer结构中保存了lastsave和dirty两个参数,它们的含义分别是上一次生成RDB文件的时间戳以及距离上一次生成RDB文件后执行了多少次更新键的操作,伪代码如下:

def serverCron():
  for param in saveparams:
    if (now - lastsave >= seconds) && dirty >= changes:
      bgsave
      lastsave = time_of_now
      dirty = 0
      break
  time.sleep(100ms)
  • RDB文件结构

在这里插入图片描述

图13 RDB文件结构

图13展示了RDB文件的结构,结构中最重要的就是databases数组,数组中的每一个元素都是database结构体

typedef struct database{
	const string SELECT_DB, // 一个常量,表示接下来是数据库标号
	int db_number, // 当前所在的数据库标号
	struct []key_value_pairs, // 键值对数组
}

typedef struct key_value_pair{
	const string EXPIRETIME_MS, // 一个常量,表示接下来是过期时间
	long ms, // 过期时间
	enum type, // value的类型,取值有STRING、LIST、ZSET等类型
	string key,
	object value, // 具体的value对象
}

AOF持久化

在这里插入图片描述

图14 AOF持久化过程
  • 持久化策略
    图14展示了AOF持久化的逻辑,服务器会将每一次更新键的指令写入到aof缓冲区(图中的aof_buf)中,AOF持久化有三种策略:always表示每次更新aof缓冲区即将其命令append到aof文件(图14中旧的aof文件);everysec表示每隔1s将aof缓冲区的命令append到aof文件中;no表示服务器进程不主动将aof缓冲区的命令append到aof文件中,何时append由操作系统底层决定。对比下这三者策略,always几乎不会丢数据,即使丢失也是只丢失最近的一条指令,但是磁盘IO太过频繁,对CPU极不友好;everysec和no性能相似,而且everysec策略极端情况下只会丢失1s内的操作数据,而no策略会丢失多少数据完全未知,因此一般都采用everysec策略。
  • aof重写
    随着命令执行越来越多,aof文件会越来越大,如果不进行重写,aof会占用极大的空间,服务器进程加载aof会消耗很长的时间,为了解决这个问题,redis服务器会对aof文件进行重写,重写不会对旧的aof文件有任何影响。服务器进程决定重写时,会fork一个子进程,子进程扫描父进程下所有的键值对,并将该键值对写入新的aof临时文件中,由于这个写入需要一个过程,父进程在子进程写入临时文件过程中仍需要继续处理客户端请求,因此,当fork出子进程后,父进程会将客户端命令同时写入aof_buf和aof_rewrite_buf两个缓冲区中,当子进程重写完成后,再执行aof_rewrite_buf中的指令,最后,覆盖掉旧的aof文件。

redis客户端与服务器

客户端与服务器结构

typedef redisServer struct {
	redisClient*[]clients, // 与服务端连接的多个客户端
}

typedef redisClient struct {
	int fd, // 当前客户端描述符,-1为伪客户端(redis中有两个地方需要使用伪客户端,一处是执行lua脚本时,一处是加载aof文件时),>-1为普通客户端
	robj * name, // 客户端名称
	int flags, // 标识客户端当前状态,redis_lua_client|redis_blocked表明客户端是lua客户端且客户端被blpush命令阻塞
	SDS querybuf, // 输入缓冲区,指定了客户端发出的请求,eg:set key v1
	robj ** argv, // 解析querybuf参数,将其分为set key v1三部分
	int argc, // 输入缓冲区参数数量
	redisCommand *cmd, // querybuf第一个参数对应的处理函数,eg:setCommand(因为querybuf中命令是set key v1),如果为空表明命令有误
	char buf[REDIS_REPLY_CHECK_BYTES],// 输出缓冲区,保存服务端响应的数据
	int bufpos, // 输出缓冲区已使用字节数
	list * reply, // 输出缓冲区不足,使用该结构
	int authenticated, // 用于标识客户端是否鉴权
	time_c lastinteraction, // 标识上一次和服务器交互的时间
}

redis集群

Redis哨兵模式

在这里插入图片描述

图15 redis哨兵模式

哨兵模式角色介绍

redis哨兵模式是redis提供的一种高可用方案,它的主要目标是高可用,当主服务器宕机时哨兵能够及时发现并从主服务器的从服务器中选出一个作为主服务器,哨兵模式主要有以下角色:

  • 哨兵集群
    1. 哨兵a 50.100:26379
    2. 哨兵b 50.101:26379
    3. 哨兵c 50.102:26379
  • 主节点(50.100:6379)
  • 从节点
    1. 从节点a 50.102:6379
    2. 从节点b 50.103:6379

其中,主节点会异步保持与两个从节点间的数据同步,哨兵集群中的所有哨兵会互相监控互通消息,哨兵a在启动时会监听一个主节点,每隔10s向主节点发送信息,主节点回应自己节点的信息、从节点信息以及所有监听自己的哨兵信息,哨兵a得到主节点回复的消息后会更新自己的结构,同时将主节点返回的信息同步发送给其他监听该主节点的哨兵。另外,哨兵a还会每隔10s向从节点发送消息以确认从节点是否在线。

故障迁移

在这里插入图片描述

图16 sentinelState结构信息

每一个哨兵都维持sentinelState的结构信息,图16给出了结构体字段的具体含义。current_epoch是一个int整数,含义为配置纪元,一会介绍哨兵故障迁移时会详细介绍它的作用。masters保存了所有被哨兵监听的主服务器信息,它是一个dict,key为主服务器名,value为sentinelRedisInstance结构体,该结构体中保存了从节点信息、监听自己的哨兵信息,以及自己的数据元信息。
哨兵模式存在的意义就是解决故障迁移问题。当主节点宕机时,假设此时哨兵a先发现无法正常与主节点通信,它会每隔1s给主节点发送消息,如果主节点没有在规定的时间(规定的时间就是sentinelRedisInstance结构体中的down_after_period字段)回应,哨兵a会将该主节点标记为主观下线;随后,哨兵a询问其他同样监听该主服务器的哨兵b和哨兵c是否也认为主服务器已下线,这里需要注意,每个哨兵都有sentinelState这个结构体,它们的down_after_period很可能不同,也就是说哨兵b和哨兵c不一定同意哨兵a的观点。如果哨兵集群中认为主节点主观下线的哨兵数量达到客观下线投票数(sentinelRedisInstance结构体中的quorum),那么主节点会被认为客观下线,此时,故障迁移开始了。
故障迁移开始时,所有的哨兵行动起来,互相通信选出一个领头sentinel,哪个哨兵先累计到quorum数,那么该哨兵就向其他哨兵发出选举申请,选举信息中包含自己的配置纪元,自己的节点runid,其他哨兵在收到选举申请后,判断选举申请中的配置纪元是否和自己当前配置纪元一致,如果小于自己的配置纪元,直接否决;否则,同意哨兵a的选举申请并向其发送确认消息。哨兵a在收到回应后,检查同意自己申请的哨兵是否超过哨兵集群半数以上,是则成为领头sentinel。选举之后,不论选举是否成功,所有哨兵的配置纪元都自增1。
领头sentinel选举成功后(假设哨兵a当选),哨兵a会在主节点中所有从节点选出一个节点作为新的主节点,选举的策略是当前在线的且数据和主节点最接近的从节点当选,一旦选出,哨兵a会给其他从节点发送消息,让其他从节点跟随新的主节点,并更新自己结构信息。

redis集群的数据复制与同步

Alt

图17 集群模式下数据的复制与同步

图17给出了数据复制的过程,首先从服务器向主服务器发送sync命令,告知主服务器需要进行数据同步,主服务器在收到命令后执行bgsave生成rdb持久化文件,并将其传送给从服务器,同时,主服务器会将此时客户端的写命令记录到buffer中。从服务器收到主服务器发来的rdb文件后进行加载,加载完成后向主服务器发送加载完成指令,主服务器将buffer中的命令传给从服务器,从服务器继续执行,直到主从数据达到一致的状态。
上面的复制过程看起来没什么问题,考虑这种情况,如果从服务器正常加载rdb文件后,在主服务器向从服务器传播buffer中的命令时,从服务器挂了,过了一段时间,从服务器恢复过来,但是并不知道该从什么地方继续同步,只好重新向主服务器发送sync命令。这就是旧版本的复制过程,而bgsave是比较消耗CPU资源的,redis从2.8版本后引入了新的复制机制。
在这里插入图片描述

图18 新版本的集群复制机制

如果一个从服务器是重新启动,或者之前的主服务器和当前主服务器不一致时,复制过程和图17一致;如果在复制过程中出现了宕机,如图18所示,情况又有什么不同呢。
首先,主服务器拥有一个编号runid标识自己的身份,并维护一个offset偏移量表明当前buffer命令的进度,在4个从节点中,也维持runid和offset两个变量,表明自己身份和已经自己已经复制的进度。假设主服务器当前offset=165,从服务器00b和00c跟主服务器一直保持同步,offset都是165;而从服务器00d在offset=98时便与主服务器断开连接,从服务器00a是在offset=160断开连接的,当主服务器offset=165时从服务器00a和00d连接恢复,此时,它们会首先询问主服务器当前的偏移量,得到回答是offset=165,随后,它们会去复制积压缓冲区中寻找自己丢失的那部分偏移量是否存在,00a需要161-165偏移量的数据,发现在缓冲区中,则00a直接从缓冲区中取出落后的偏移量并执行;00d需要99-165偏移量的数据,但是缓冲区中不够,于是00d只能告诉主服务器执行sync同步指令。如果此时主服务器收到客户端的更新请求,处理完成后主服务器更新偏移量,假设offset=165+33,主服务器会将新增的33偏移量数据发送给所有的从服务器,并且向复制积压缓冲区中也传播一份,缓冲区默认是1MB大小,如果满了,会清除旧的偏移量信息。

redis集群-槽指派和扩展

在这里插入图片描述

图19 redis集群模式下槽指派和扩展流程

redis的cluster集群主要目标是可扩展性,这点与哨兵集群模式不同。图19给出了cluster集群模式下是如何进行的。在一个cluster集群中会有16384个槽,cluster中每个主节点负责其中的若干个槽,图19中,0~3276槽归Node-1负责,依次类推。当需要对某个key进行操作时,首先计算slotID=CRC16(key)&16384=3281,那么集群通过slotID找到负责的节点即Node-2,再由Node-2执行相应的命令。如果此时进行扩展,即Node-5加入集群,那么集群会进行槽重新分配。

独立功能

I/O多路复用

在这里插入图片描述

图20 redis的I/O多路复用策略

redis是一个单线程的服务器,那它是怎么处理多个客户端连接并正确响应的呢。原因就在于redis使用了I/O多路复用机制。简单来说,I/O多路复用机制就是服务器同时监听多个文件描述符,当任意一个文件描述符就绪(有数据到达时),多路复用机制都能通知到redis服务器。
I/O多路复用机制将原本是多线程并发请求的场景进行请求排队转为单线程的问题,不仅提升了服务器的性能而且还避免了多线程环境下临界资源锁竞争的问题。能够做到这一点,是因为redis服务器是I/O密集型的服务器,网络I/O的时间要远远大于CPU处理时间。

内存淘汰策略

redis是一个内存数据库,当里面的数据越来越多时,内存占用会越来越大,如果不作处理,假以时日内存会到达上限,此时就会触发内存淘汰。redis支持多种内存淘汰策略,具体介绍如下

  • redis内存淘汰策略对比
淘汰策略说明
noeviction不淘汰,内存满后,新增失败
volatile-lru根据lru算法淘汰redis服务器中带有过期时间的key
volatile-ttl淘汰服务器中过期时间最短的key
volatile-random随机淘汰服务器中带有过期时间的key
allkeys-lru根据lru算法淘汰redis服务器中的key
allkeys-random随机淘汰服务器中的key

serverCron

serverCron是redis服务器下的一个非常重要的定时任务,默认100ms执行一次,主要执行以下功能

  • 更新服务器的各类统计信息
    • 时间(由于serverCron是100ms执行一次,如果对时间要求特别精确就不要使用这里的时间)
    • 内存占用
    • 数据库占用
  • 过期键的清理
  • 关闭和清理失效的客户端
  • 尝试AOF和RDB持久化
  • 主从定期同步
  • 集群模式下连接测试

其他独立功能

  • lua
    redis服务器内嵌了lua的环境,当需要在服务器端进行逻辑运算时,就可以使用lua脚本,服务器在执行lua脚本时,会分配一个伪客户端进行交互,使用lua脚本的好处是可以减少网络I/O
  • 延时队列
    延时队列是一种常用的功能,比如淘宝的订单付款模块,在用户下单后,往往需要延迟一段时间进行付款,此时就需要用到延时队列。redis的zset可以支持这项功能,将事件作为zset的value,延时的时间time作为zset的二级key,然后主线程每隔一定时间检查一次zset,查看zset中最小的时间time是否到期,如果到期,则取出并触发value事件,否则睡眠一段时间继续监测。使用redis的zset实现延时队列优势是方便,用户不需要关心太多具体细节,劣势是当延时队列中数据非常多时,会带来性能问题,而且大部分时间都花在I/O上。延时队列有其他更好的实现方式,比如时间轮。
  • HyperLogLog
    HyperLogLog是一种统计算法,在redis中可以用来统计大数据下某一个key出现的次数,它有两个api:pfadd和pfcount,一个是添加,一个是计数,以下是使用案例
    pfadd name_list user1
    pfcount name_list  // 返回1
    pfadd name_list user1 // 重复添加只计算一次
    pfadd name_list user2
    pfadd name_list user3
    pfadd name_list user4
    pfcount name_list  // 返回4
    
    实际上,HyperLogLog实现了set的添加和求长度功能,只是HyperLogLog适用于大数据场景下,而且pfcount会有一定的误差,标准误差是0.81%,它的这项功能比set节约大量的内存。
  • 布隆过滤器
    布隆过滤器也是在set基础上,提供set集合的sadd和sismenber两个操作,和HyperLogLog类似,布隆过滤器比set节约大量的内存,布隆过滤器也有一定的误判率,合理设置参数可以有效降低误判率。以下是布隆过滤器的使用案例
    bf.add name_list user1
    bf.add name_list user1 // 重复添加只计算一次
    bf.add name_list user2
    bf.add name_list user3
    bf.add name_list user4
    
    bf.exists name_list user1 // 返回true
    bf.exists name_list userX // 有可能返回true
    
    也就是说,布隆过滤器返回false,则说明集合中一定不存在某项元素;返回true,说明集合中可能存在,也可能不存在(有一定的误判)。

总结

本文从redis最基本的数据结构出发,剖析基本数据结构的内部实现,希望能够帮助读者更清晰认识和使用redis的结构;第二部分介绍了redis的过期策略和持久化,详细阐述了redis是如何进行过期清理以及两种非常重要的持久化机制aof和rdb,这两种持久化方式类似wal和快照,帮助redis服务器宕机重启后能恢复到正常状态,第三部分介绍了redis客户端和服务器结构;第四部分介绍了redis的两种集群模式,哨兵模式和cluster,它们分别着眼于高可用性和可扩展性;最后介绍了redis的一些独立功能。
当然,redis的功能远不止这些,还有很多有趣且使用的功能本文并未完全列举出来,redis也在不断完善和修复中,欢迎大家一起交流讨论。

机器学习(ML)在材料领域应用 机器学习作为一种兼顾开发效率以及开发成本的方法,已经逐渐应用于材料发现、结构分析、性质预测、反向设计等多个领域,并且在材料学研究中展现出惊人的潜力。传统的发现新材料的方法,如经验试错法和基于密度泛函理论(DFT)的方法,往往需要较长的研发周期,成本高、效率低,已经不能很好的适应如今需求量激增的材料学领域 ... 阅读详情

相关推荐

从MATLAB代码入手:手把手教你复现OTFS调制解调核心模块(附完整函数解析)

本文详细解析了OTFS调制解调技术在MATLAB中的实现方法,包括核心模块如ISFFT变换、海森堡变换及MP检测算法的代码实现。通过完整的函数解析和性能验证框架,帮助通信工程师快速掌握这一前沿技术,提升在高速移动场景下的通信系统设计能力。

weixin_42568801的博客 318

SSD算法详解

SSD github : https://github.com/weiliu89/caffe/tree/ssd SSD paper :https://arxiv.org/abs/1512.02325 SSD 动态PPT:https://docs.google.com/presentation/d/1rtfeV_VmdGdZD5ObVVpPDPIODSDxKnFSU0bsN_rgZXc/pu...

ytusdc的博客 5万+

faster lio 回环 加入GTSAM优化的记录

对Faster lio 加入回环、gtsam优化,在室外建图遇到的问题和过程

Nico_jion的博客 2599

【面试资料】Redis篇 之 缓存穿透 / 击穿 / 雪崩

Redis缓存穿透、击穿、雪崩问题。

weixin_45306667的博客 1004

水星路由器+群晖NAS+外网访问

水星路由器+群晖NAS+外网访问 有了公网IP,家里是水星路由器的 怎么设置外网访问 1.先电脑登录群晖的http://【你的群晖名】.quickconnect.to/ 2.【控制面板】进入设置 3.选择synology依次设置 ,【主机名称】就是你的名称。注意:左下角第7步,一定勾选。 4.下面进入192.168.1.1水星路由器设置。【IP与MAC绑定】点击NAS对应的【绑定】注意,点这里之前Nas一定设置了固定内网ip(后面补充 )。 5.【高级用户】-虚【拟服务器】。常用服务器不用选,其他的对应

wjxue201的博客 2597

关于Arduino中的常用函数

一、I/O函数 二、时间函数 三、数学函数 abs 返回其参数的无符号值 【abs(12)返回12,abs(-12)也返回12】 constrain 限制一个数字以阻止它超出范围。第一个参数是被限制的数字,第二个参数是范围区间的开始,第三个参数是允许的数字范围区间的结尾。 【constrain(8,1,10)返回8,constrain(11,1,10)返回10,constrain(0,1,10)返回1】 map 将一个范围内的数字映射到另一个范围。第一个参数是要映射到数字,第二个和第三个参数是from范围(

TREE的博客 7322

redis学习路线

一. 数据结构与对象 list hash set sorted set string 二. 单机数据库的实现 Redis数据库的实现方法 Redis的事件 Redis客户端和服务端的运行机制 三. 多机数据库的实现 Rentinel 复制(Replication) 集群(Cluster) 四. 独立功能的实现 发布订阅 事务 Lua脚本 排序 二进制位数组 ...

heihei 1306

SpyGlass CDC实战:如何用sgdc约束文件解决跨时钟域报错(附常见错误排查)

本文详细介绍了如何使用SpyGlass CDC工具编写sgdc约束文件解决跨时钟域(CDC)报错问题。从基础语法到实战案例,涵盖时钟声明、复位约束、路径豁免等关键技巧,特别针对SPI断续时钟等特殊场景提供解决方案,并附常见错误排查指南,帮助工程师高效完成CDC验证。

weixin_28744423的博客 625

深入理解 Redis:高性能缓存数据库的技术内幕

Redis 是一种开源的、基于内存的缓存数据库,它以其出色的性能和丰富的功能而闻名。本文将深入探讨 Redis 的核心技术,包括数据结构、内存管理、持久化等方面,帮助读者更好地理解 Redis 的工作原理。通过深入理解 Redis 的核心技术,我们可以更好地利用 Redis 的优势,为我们的应用提供更好的性能和可靠性。Redis 支持多种数据结构,包括字符串、列表、集合、哈希、有序集合等。虽然 Redis 是一种基于内存的数据库,但它也提供了持久化功能,以保证数据的可靠性。

momo_128的博客 892

【知识分享】高速印刷电路板(PCB)寄生参数计算指南

对于高速PCB设计而言,寄生参数一直是令人讨厌的问题,它们会悄悄潜入PCB并在电路中造成严重的破坏,具体来说,它们是渗透到高速电路中的隐藏杂散电容器和电感器,包括由封装引线和长走线形成的电感器、PCB焊盘对金属平面形成的电容器、走线之间形成的电容器等等。

一只豌豆象的博客 1780

基于Keil软件实现对射式红外传感器计次 (江协科技HAL库)

用HAL库复现江协科技,让嵌入式学习之路更顺畅。

2401_82728381的博客 798

Redis 技术内幕——Redis Cluster

Redis 在 3.0 版本中提供了 Redis Cluster (集群) 来满足分布式的需求。Redis Cluster 采用无中心结构,每个节点保存数据和整个集群的状态,每个节点都和其他所有节点连接,节点之间使用流言协议 (Gossip Protocols) 去传播信息以及发现新的节点。

一叶知秋的博客 1497

Kafka 性能优化与常见问题排查

Kafka 的高性能依赖于合理的集群设计、参数优化与高效的监控体系。生产环境中,应重点关注I/O 瓶颈、分区规划、Producer/Consumer 参数调优,并通过监控与排查手段快速定位问题,从而保障 Kafka 在实时场景下的稳定运行。

X2829352498的博客 235

Redis 技术内幕——Redis Sentinel

Redis Sentinel(哨兵)是 Redis 官方提供的集群管理工具,是 Redis 高可用的解决方案,本身是一个独立运行的进程,它可以监视多个 Master-Slave 集群,发现 Master 宕机之后,能进行自动切换,将该 Master 下的某个 Slave 晋升为 Master,继续处理请求。

一叶知秋的博客 2130

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Redis 技术内幕——主从同步原理

单机 Redis 可能存在机器故障、容量瓶颈和 QPS 瓶颈。主从复制为一个数据提供了多个副本,扩展了读性能。一个 Master 可以有多个 Slave,一个 Slave 只能有一个 Master,Redis 数据流向是单向的,Master 到 Slave。

一叶知秋的博客 734

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Redis 技术内幕——布隆过滤器

Redis Cluster(集群)是官方提供的集群功能。Redis 在 3.0 版本中提供了 Redis Cluster 来满足分布式的需求。为什么需要集群?并发量,据 Redis 官方测试,有 50 个并发程序来执行 10 万次请求,Redis 读的速度达到了 11 万次/秒,写的速度达到了 8.1 万次/秒。那如果业务需要 100 万次/秒呢? 数据量,一台生产机器内存一般在 16G~256G,如果业务需要 500G 数据呢?

一叶知秋的博客 2444

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

【Docker技术内幕】(十)使用docker安装Redis

它为基于虚拟机管理程序的虚拟机提供了可行、经济、高效的替代方案,因此您可以利用更多的计算能力来实现业务目标。Docker 是基于容器的平台,允许高度可移植的工作负载。Docker 容器可以在开发人员的本机上,数据中心的物理或虚拟机上,云服务上或混合环境中运行。Docker 的可移植性和轻量级的特性,还可以使您轻松地完成动态管理的工作负担,并根据业务需求指示,实时扩展或拆除应用程序和服务。Docker 允许开发人员使用您提供的应用程序或服务的本地容器在标准化环境中工作,从而简化了开发的生命周期。

330

阿里大佬花费24小时整理的Spring源码/技术内幕+Nginx+Redis+面试

今天小编整理关于Nginx+Spring+SpringBoot+Redis+SpingCloud的干货,以及相对应的面试专题及答案,下面小编给你们一一分享一下: Nginx实战 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表...

如果频繁就私信一下呢 552

通病:工作中用不到,就不需要学习吗?滴滴T9技术大牛写出这份深入了解 Redis技术内幕必读之作

本书全面而完整地讲解了Redis的内部机制与实现方式,对Redis的大多数单机功能以及所有多机功能的实现原理进行了介绍,展示了这些功能的核心数据结构以及关键的算法思想,图示丰富,描述清晰,并给出大量参考信息。通过阅读本书,读者可以快速、有效地了解Redis的内部构造以及运作机制,更好、更高效地使用Redis。 本书主要分为四大部分 第1章引言 第一部分“数据结构与对象 第一部分“数据结构与对象”介绍了Redis 中的各种对象及其数据结构,并说明这些数据结构如何影响对象的功能和性..

+W❤:bjmsb2019 588

细节爆炸!阿里内部疯传的“千万级”Redis技术内幕这么牛?

零基础学redis: Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。 Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类keyvalue存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。 受众人群:从事编...

Java555222的博客 235

redis lua 二进制_滴滴T9技术大牛写出这份深入了解 Redis技术内幕必读之作

本书全面而完整地讲解了Redis的内部机制与实现方式,对Redis的大多数单机功能以及所有多机功能的实现原理进行了介绍,展示了这些功能的核心数据结构以及关键的算法思想,图示丰富,描述清晰,并给出大量参考信息。通过阅读本书,读者可以快速、有效地了解Redis的内部构造以及运作机制,更好、更高效地使用Redis。本书主要分为四大部分第1章引言第一部分“数据结构与对象第一部分“数据结构与对象”介绍了...

weixin_35841706的博客 168

Redis内幕揭秘:探索Redis基础知识及应用场景,挖掘出高效的缓存技术

Redis 是一个开源的内存数据结构存储系统,它可以用作数据库、缓存和消息中间件。随着时间的推移,Redis 已经成为了一个非常流行的开源项目,并被广泛应用于互联网和企业级应用程序中。

Hanko的专栏 326

【大厂技术内幕】字节跳动原来是这么做数据迁移的!,吊打面试官 redis

{item}笔者已经把面试题和答案整理成了面试专题文档网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!ccess);自媒体//文章审核成功。

2401_84438239的博客 1040

BAT架构书籍:Spring+Sping源码+SpringBoot+Redis+Nginx

Spring技术内幕:深入浅出 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。 Spring框架,由Ro...

芒果不是程序猿v: 的博客 780

小编精心整理,架构师推荐Java学习者必看书籍,无偿分享

小编话就不多说啦直接分享,架构师推荐Java学习者必看书籍 Nginx实战 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。 由于细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!获取方式:转发+...

芒果不是程序猿v: 的博客 246

2024年作品汇总

详见b站。

lbqjtyykx的博客 574
上一篇: leetcode 114. Flatten Binary Tree to Linked List
下一篇: libevent深入浅出
spencersong
博客等级 码龄9年 11粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值