redis 数据结构 分析

Posted 坐看云起时

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis 数据结构 分析相关的知识,希望对你有一定的参考价值。

redis_server 结构体

/* 
redis服务器将所有数据库保存在服务器状态redis.h/redisserver结构的db数组中
*/
struct redisServer {//struct redisServer server; 

    /* General */

    // 配置文件的绝对路径
    char *configfile;           /* Absolute config file path, or NULL */

    // serverCron() 每秒调用的次数  默认为REDIS_DEFAULT_HZ  表示每秒运行serverCron多少次   可以由配置文件中的hz进行配置运行次数
    int hz;                     /* serverCron() calls frequency in hertz */

    // 数据库 selectDb赋值,每次和客户端读取交互的时候,都有对应的select x命令过来
    redisDb *db; //所有数据库都保存在该db数组中,每个redisDb代表一个数据库

    //命令表字典中的元素内容从redisCommandTable中获取,见populateCommandTable  实际客户端命令的查找在函数lookupCommandOrOriginal lookupCommand中
    // 命令表(受到 rename 配置选项的作用)  参考commandTableDictType可以看出该dict对应的key比较是不区分大小写的
    //如果是sentinel方式启动,则还会从sentinelcmds中获取,参考initSentinel
    dict *commands;             /* Command table */
    // 命令表(无 rename 配置选项的作用) 
    //redisServer->orig_commands redisServer->commands(见populateCommandTable)命令表字典中的元素内容从redisCommandTable中获取,见populateCommandTable
    dict *orig_commands;        /* Command table before command renaming. */

    // 事件状态
    aeEventLoop *el;

    // 最近一次使用时钟  getLRUClock();
    unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */

    // 关闭服务器的标识  每次serverCron函数运行时,程序都会对服务器状态的shutdown_asap属性进行检查,并根据属性的值决定是否关闭服务器:
    /* 
     关闭服务器的标识:值为1时,关闭服务器,值为0时,不做动作。
     */
    int shutdown_asap;          /* SHUTDOWN needed ASAP */

    // 在执行 serverCron() 时进行渐进式 rehash
    int activerehashing;        /* Incremental rehash in serverCron() */

    // 是否设置了密码 requirepass 配置认证密码
    char *requirepass;          /* Pass for AUTH command, or NULL */

    // PID 文件  进程号写入该文件中,见createPidFile
    char *pidfile;              /* PID file path */

    // 架构类型
    int arch_bits;              /* 32 or 64 depending on sizeof(long) */
 
    // serverCron() 函数的运行次数计数器  serverCron每运行一次就自增1
    int cronloops;              /* Number of times the cron function run */

    /*
    服务器运行ID: 除了复制偏移量和复制积压缓冲区之外,实现部分重同步还需要用到服务器运行ID
    1. 每个Redis服务器,不论主服务器还是从服务,都会有自己的运行ID。
    2. 运行rD在服务器启动时自动生成,由40个随机的十六进制字符组成,例如53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3。
    当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器,而从服务器则会将这个运行ID保存起来。
    当从服务器断线并重新连上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID:
    1. 如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,
    主服务器可以继续尝试执行部分重同步操作。
    2. 相反地,如果从服务器保存的运行ID和当前连接的主服务器的运行ID并不相同,那么说明从服务器断线之前复制的主服务器并不是
    当前连接的这个主服务器,主服务器将对从服务器执行完整重同步操作。

    举个例子,假设从服务器原本正在复制一个运行ID为53b9b28df8042fdc9ab5e3fcbbbabf fld5dce2b3的主服务器,那么在网络断开,从服务器
重新连接上主服务器之后,从服务器将向圭服务器发送这个运行ID,主服务器根据自己的运行ID是否53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3来
判断是执行部分重同步还是执行完整重同步。
     */
    // 本服务器的 RUN ID
    char runid[REDIS_RUN_ID_SIZE+1];  /* ID always different at every exec. */

    // 服务器是否运行在 SENTINEL 模式   见checkForSentinelMode
    int sentinel_mode;          /* True if this instance is a Sentinel. */


    /* Networking */

    //可以由启动redis的命令行赋值,默认REDIS_SERVERPORT   如果是Sentinel方式启动,则值为REDIS_SENTINEL_PORT
    // TCP 监听端口 port参数配置  表示作为服务器端,等待redis-cli客户端连接的时候,服务器端的监听端口,见initServer listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
    int port;                   /* TCP listening port */

    int tcp_backlog;            /* TCP listen() backlog */

    // 地址
    char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
    // 地址数量
    int bindaddr_count;         /* Number of addresses in server.bindaddr[] */

    // UNIX 套接字
    char *unixsocket;           /* UNIX socket path */
    mode_t unixsocketperm;      /* UNIX socket permission */

    // 描述符  对应的是redis服务器端listen的时候创建的套接字,服务器端可以bind多个端口和地址,有bind几个就有几个ipfd_count
    int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
    // 描述符数量 listenToPort(server.port,server.ipfd,&server.ipfd_count)中赋值
    int ipfd_count;             /* Used slots in ipfd[] */

    // UNIX 套接字文件描述符
    int sofd;                   /* Unix socket file descriptor */

    int cfd[REDIS_BINDADDR_MAX];/* Cluster bus listening socket */
    int cfd_count;              /* Used slots in cfd[] */

    // 一个链表,保存了所有客户端状态结构  createClient中把redisClient客户端添加到该服务端链表clients链表中  if (fd != -1) listAddNodeTail(server.clients,c);
    list *clients;               /* List of active clients */
    // 链表,保存了所有待关闭的客户端
    list *clients_to_close;     /* Clients to close asynchronously */

    // 链表,保存了所有从服务器,
    list *slaves,  //注意有可能从服务器下面还会挂接从服务器
    //保存了所有连接该服务器并且执行了monitor的的客户端
    //服务器在每次处理命令请求乏前,都会调用replicationFeedMonitors函数,由这个函数将被处理的命令请求的相关信息发送给各个监视器。
    *monitors;    /* List of slaves and MONITORs */

    // 服务器的当前客户端,仅用于崩溃报告
    redisClient *current_client; /* Current client, only used on crash report */

    int clients_paused;         /* True if clients are currently paused */
    //主接收到slave的CLUSTERMSG_TYPE_MFSTART,更新REDIS_CLUSTER_MF_TIMEOUT*2
    //当队该主的从进行强制故障转移cluster failover的时候,主暂时阻塞客户端请求这么多时间已进行强制故障转移,保证主的数据完全同步给从,见pauseClients
    mstime_t clients_pause_end_time; /* Time when we undo clients_paused */

    // 网络错误
    char neterr[ANET_ERR_LEN];   /* Error buffer for anet.c */

    // MIGRATE 缓存  存储的是KV,K为ip:port字符串,V为套接字信息该ip port对应的套接字信息server.migrateCachedSocket中
    dict *migrate_cached_sockets;/* MIGRATE cached sockets */ //该dict上最多只能有MIGRATE_SOCKET_CACHE_ITEMS个套接字信息,见migrateGetSocket

    /* RDB / AOF loading information */

    // 这个值为真时,表示服务器正在进行载入     如果该值为1,则会有打印addReply(c, shared.loadingerr);
    int loading;                /* We are loading data from disk if true */

    // 正在载入的数据的大小
    off_t loading_total_bytes;

    // 已载入数据的大小
    off_t loading_loaded_bytes;

    // 开始进行载入的时间
    time_t loading_start_time;
    off_t loading_process_events_interval_bytes;

    /* Fast pointers to often looked up command */
    // 常用命令的快捷连接
    struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
                        *rpopCommand;


    /* Fields used only for stats */

    // 服务器启动时间
    time_t stat_starttime;          /* Server start time */

    // 已处理命令的数量  当前总的指向命令总量  trackOperationsPerSecond   
    //call中自增
    long long stat_numcommands;     /* Number of processed commands */

    // 服务器接到的连接请求数量
    long long stat_numconnections;  /* Number of connections received */

    // 已过期的键数量
    long long stat_expiredkeys;     /* Number of expired keys */

    // 因为回收内存而被释放的过期键的数量
    long long stat_evictedkeys;     /* Number of evicted keys (maxmemory) */

    // 成功查找键的次数 //整个数据库的名字次数,可以通过info stats命令查看
    long long stat_keyspace_hits;   /* Number of successful lookups of keys */

    // 查找键失败的次数 //整个数据库的未名字次数,可以通过info stats命令查看
    long long stat_keyspace_misses; /* Number of failed lookups of keys */

    /*
    INFO memory命令的used memory_peak和used_memory_peak_human两个域分别以两种格式记录了服务器的内存峰值
     */
    // 已使用内存峰值  记录了服务器的内存峰值大小:
    size_t stat_peak_memory;        /* Max used memory record */

    // 最后一次执行 fork() 时消耗的时间
    long long stat_fork_time;       /* Time needed to perform latest fork() */

    // 服务器因为客户端数量过多而拒绝客户端连接的次数
    long long stat_rejected_conn;   /* Clients rejected because of maxclients */

    // 执行 full sync 的次数
    long long stat_sync_full;       /* Number of full resyncs with slaves. */

    // PSYNC 成功执行的次数
    long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */

    // PSYNC 执行失败的次数
    long long stat_sync_partial_err;/* Number of unaccepted PSYNC requests. */


    /* slowlog */

    // 保存了所有慢查询日志的链表  见slowlogPushEntryIfNeeded
    list *slowlog;                  /* SLOWLOG list of commands */ //链表节点类型slowlogEntry

    // 下一条慢查询日志的 ID  属性的初始值为O,每当创建一条新的慢查询日志时,这个属性的值就会用作新日志的id值,之后程序会对这个属性的值增一。
    long long slowlog_entry_id;     /* SLOWLOG current entry ID */

    /*
        slowlog-log-slower-than选项指定执行时间超过多少微秒(1秒等于1 000 0 0 0微秒)的命令请求会被记录到日志上。
    举个例子,如果这个选项的值为1 0 0,那么执行时间超过1 0 0微秒的命令就会被记录到慢查询日志
       */
    // 服务器配置 slowlog-log-slower-than 选项的值
    long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */

    /*
       lowlog-max-len选项指定服务器最多保存多少条慢查询日志。服务器使用先进先出的方式保存多条慢查询日志,当服务器存储的慢
       查询日志数量等于slowlog-max-len选项的值时,服务器在添加一条新的慢查询日志之前,会先将最旧的一条慢查询日志删除。
      */
    // 服务器配置 slowlog-max-len 选项的值  //Redis延迟监控框架详解http://ghoulich.xninja.org/2016/12/08/how-to-use-latency-monitor-in-redis/
    unsigned long slowlog_max_len;     /* SLOWLOG max number of items logged */
    size_t resident_set_size;       /* RSS sampled in serverCron(). */
    /* The following two are used to track instantaneous "load" in terms
     * of operations per second. */
    // 最后一次进行抽样的时间 
    long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
    // 最后一次抽样时,服务器已执行命令的数量 trackOperationsPerSecond
    long long ops_sec_last_sample_ops;  /* numcommands in last sample */
    // 抽样结果  大小(默认值为16)的环形数组,数组中的每个项都记录了一次抽样结果。
    long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
    // 数组索引,用于保存抽样结果,并在需要时回绕到 0    每次抽样后将值自增一,在值等于16时重置为口, 构成一个环形数组。
    int ops_sec_idx; //每次抽样加1,见trackOperationsPerSecond


    /* Configuration */

    // 日志可见性  日志登记配置 loglevel debug等
    int verbosity;                  /* Loglevel in redis.conf */

    // 客户端最大空转时间  默认REDIS_MAXIDLETIME不检查
    int maxidletime;                /* Client timeout in seconds */

    /*
    2、使用TCP的keepalive机制,UNIX网络编程不推荐使用SO_KEEPALIVE来做心跳检测(为什么??)。
    keepalive原理:TCP内嵌有心跳包,以服务端为例,当server检测到超过一定时间(/proc/sys/net/ipv4/tcp_keepalive_time 7200 即2小时)没有数据传输,那么会向client端发送一个keepalive packet,此时client端有三种反应:
    1、client端连接正常,返回一个ACK.server端收到ACK后重置计时器,在2小时后在发送探测.如果2小时内连接上有数据传输,那么在该时间的基础上向后推延2小时发送探测包;
    2、客户端异常关闭,或网络断开。client无响应,server收不到ACK,在一定时间(/proc/sys/net/ipv4/tcp_keepalive_intvl 75 即75秒)后重发keepalive packet, 并且重发一定次数(/proc/sys/net/ipv4/tcp_keepalive_probes 9 即9次);
    3、客户端曾经崩溃,但已经重启.server收到的探测响应是一个复位,server端终止连接。
    修改三个参数的系统默认值
    临时方法:向三个文件中直接写入参数,系统重启需要重新设置;
    临时方法:sysctl -w net.ipv4.tcp_keepalive_intvl=20
    全局设置:可更改/etc/sysctl.conf,加上:
    net.ipv4.tcp_keepalive_intvl = 20
    net.ipv4.tcp_keepalive_probes = 3
    net.ipv4.tcp_keepalive_time = 60
    */
    // 是否开启 SO_KEEPALIVE 选项  tcp-keepalive 设置,默认不开启
    int tcpkeepalive;               /* Set SO_KEEPALIVE if non-zero. */
    //默认初始化为1
    int active_expire_enabled;      /* Can be disabled for testing purposes. */
    size_t client_max_querybuf_len; /* Limit for client query buffer length */ //REDIS_MAX_QUERYBUF_LEN
    /*
    默认情况下,Redis客户端的目标数据库为O号数据库,但客户端可以通过执行SELECT命令来切换目标数据库。
     */
    //初始化服务器的时候,根据dbnum来决定创建多少个数据库  默认16  databases可以配置
    int dbnum;                      /* Total number of configured DBs */
    int daemonize;                  /* True if running as a daemon */
    // 客户端输出缓冲区大小限制
    // 数组的元素有 REDIS_CLIENT_LIMIT_NUM_CLASSES 个
    // 每个代表一类客户端:普通、从服务器、pubsub,诸如此类
    clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_LIMIT_NUM_CLASSES];


    /* AOF persistence */

    // AOF 状态(开启/关闭/可写) //appendonly  yes | no配置  配置为no则不会参数aof文件appendonly.aof
    int aof_state;                  /* REDIS_AOF_(ON|OFF|WAIT_REWRITE) */

    // 所使用的 fsync 策略(每个写入/每秒/从不)
    int aof_fsync;                  /* Kind of fsync() policy */
    char *aof_filename;             /* Name of the AOF file */
    int aof_no_fsync_on_rewrite;    /* Don‘t fsync if a rewrite is in prog. */
    //默认100,可以用auto-aof-rewrite-percentage进行配置 
    //auto-aof-rewrite-percentage(100):当AOF文件增长了这个比例(这里是增加了一倍),则后台rewrite自动运行
    int aof_rewrite_perc;           /* Rewrite AOF if % growth is > M and... */
    //auto-aof-rewrite-min-size(64mb):进行后面rewrite要求的最小AOF文件大小。这两个选项共同决定了后面rewrite进程是否到达运行的时机
    off_t aof_rewrite_min_size;     /* the AOF file is at least N bytes. */

    // 最后一次执行 BGREWRITEAOF 时, AOF 文件的大小
    off_t aof_rewrite_base_size;    /* AOF size on latest startup or rewrite. */

    // AOF 文件的当前字节大小  BGREWRITEAOF后有新的命令到来会追加到文件末尾,所以aof_current_size比aof_rewrite_base_size大,
    off_t aof_current_size;         /* AOF current size. */
    /*
在服务器执行BGSA VE’命令的期间,如果客户端向服务器发来BGREWRITEAOF命令,那么服务器会将BGREWRITEAOF命令的执行时间延迟到BGSAVE命令执行完毕之后。
服务器的aof rewrite scheduled标识记录了服务器是否延迟了BGREWRITEAOF命令
每次serverCron函数执行时,函数都会检查BGSAVE命令或者BGREWIUTEAOF命令是否正在执行,如果这两个命令都没在执行,并且aof rewrite scheduled属性的
值为1,那么服务器就会执行之前被推延的BGREWRITEAOF命令
     */
    int aof_rewrite_scheduled;      /* Rewrite once BGSAVE terminates. */

    /*
     服务器状态使用rdb_child_pid属性和aof_child_pid属性记录执行BGSAVE命令和BGREWRITEAOF命令的子进程的ID,这两个属性也可以用于检查
     BGSAVE命令或者BGREWRITEAOF命令是否正在执行
     */
    // 负责进行 AOF 重写的子进程 ID  记录执行BGREWRITEAOF命令的子进程的ID:如果服务嚣没有在执行BGREWRITEAOF,那么这个属性的值为-1。
    pid_t aof_child_pid;            /* PID if rewriting process */

    // AOF 重写缓存链表,链接着多个缓存块   
    //在进行AOF过程中,如果有产生新的写命令,则新的写命令会暂时保存到aof_rewrite_buf_blocks该链表中,然后在aofRewriteBufferWrite追加写入AOF文件末尾
    list *aof_rewrite_buf_blocks;   /* Hold changes during an AOF rewrite. */

    /* 当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式
将被执行的写命令追加到服务器状态的aof buf缓冲区的末尾: */
    // AOF 缓冲区
    sds aof_buf;      /* AOF buffer, written before entering the event loop */

    // AOF 文件的描述符
    int aof_fd;       /* File descriptor of currently selected AOF file */

    // AOF 的当前目标数据库
    int aof_selected_db; /* Currently selected DB in AOF */

    // 推迟 write 操作的时间
    time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */

    // 最后一直执行 fsync 的时间
    time_t aof_last_fsync;            /* UNIX time of last fsync() */
    time_t aof_rewrite_time_last;   /* Time used by last AOF rewrite run. */

    // AOF 重写的开始时间
    time_t aof_rewrite_time_start;  /* Current AOF rewrite start time. */

    // 最后一次执行 BGREWRITEAOF 的结果
    int aof_lastbgrewrite_status;   /* REDIS_OK or REDIS_ERR */

    // 记录 AOF 的 write 操作被推迟了多少次
    unsigned long aof_delayed_fsync;  /* delayed AOF fsync() counter */

    // 指示是否需要每写入一定量的数据,就主动执行一次 fsync()
    int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
    //只有在flushAppendOnlyFile失败的时候才会REDIS_ERR  一般都是内存不够或者磁盘空间不够的时候出现ERR
    int aof_last_write_status;      /* REDIS_OK or REDIS_ERR */
    int aof_last_write_errno;       /* Valid if aof_last_write_status is ERR */
    /* RDB persistence */

    /*
    服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增l,这个计数器会触发服务器的持久化以及复制操作
     */
    // 自从上次 SAVE 执行以来,数据库被修改的次数(包括写入、删除、更新等操作)。 执行save bgsave命令后会从新置为0
    //当bgsave执行完毕后,会在backgroundSaveDoneHandler更新该值
    long long dirty;                /* Changes to DB from the last save */

    // BGSAVE 执行前的数据库被修改次数  //当bgsave执行完毕后,会在backgroundSaveDoneHandler更新该值
    long long dirty_before_bgsave;  /* Used to restore dirty on failed BGSAVE */

    /*
     服务器状态使用rdb_child_pid属性和aof_child_pid属性记录执行BGSAVE命令和BGREWRITEAOF命令的子进程的ID,这两个属性也可以用于检查
     BGSAVE命令或者BGREWRITEAOF命令是否正在执行
     */
    // 负责执行 BGSAVE 的子进程的 ID
    // 没在执行 BGSAVE 时,设为 -1  记录执行BGSA VE命令的子进程的ID:如果服务器没有在执行BGSA VE,那么这个属性的值为-l。
    pid_t rdb_child_pid;            /* PID of RDB saving child */

/*
当Redis服务器启动时,用户可以通过指定配置文件或者传人启动参数的方式设置save选项,如果用户没有主动设置save选项,那幺服务器会为save选项设置默认条件:
    save 900 1
    save 300 10
    save 60 10000
saveparams属性是一个数组,数组中的每个元素都是一个saveparam结构,每个saveparam结构都保存了一个save选项设置的保存条件
*/  //saveparams属性生效地方见serverCron  
    struct saveparam *saveparams;   /* Save points array for RDB */
    int saveparamslen;              /* Number of saving points */
    char *rdb_filename;             /* Name of RDB file */ //dbfilename XXX配置  默认REDIS_DEFAULT_RDB_FILENAME
    int rdb_compression;            /* Use compression in RDB? */ //rdbcompression  yes | off
    //默认1,见REDIS_DEFAULT_RDB_CHECKSUM
    int rdb_checksum;               /* Use RDB checksum? */

    // 最后一次完成 SAVE 的时间 lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SA VE命令或者BGSAVE命令的时间。
    time_t lastsave;                /* Unix time of last successful save */ //当bgsave执行完毕后,会在backgroundSaveDoneHandler更新该值

    // 最后一次尝试执行 BGSAVE 的时间
    time_t lastbgsave_try;          /* Unix time of last attempted bgsave */

    // 最近一次 BGSAVE 执行耗费的时间
    time_t rdb_save_time_last;      /* Time used by last RDB save run. */

    // 数据库最近一次开始执行 BGSAVE 的时间
    time_t rdb_save_time_start;     /* Current RDB save start time. */

    // 最后一次执行 SAVE 的状态 //当bgsave执行完毕后,会在backgroundSaveDoneHandler更新该值
    //只有在进行bgsave的时候fork失败或者bgsave过程中被中断,写文件失败的时候才会为REDIS_ERR
    int lastbgsave_status;          /* REDIS_OK or REDIS_ERR */ 
    int stop_writes_on_bgsave_err;  /* Don‘t allow writes if can‘t BGSAVE */


    /* Propagation of commands in AOF / replication */
    redisOpArray also_propagate;    /* Additional command to propagate. */


    /* Logging */
    char *logfile;                  /* Path of log file */
    int syslog_enabled;             /* Is syslog enabled? */
    char *syslog_ident;             /* Syslog ident */
    int syslog_facility;            /* Syslog facility */


    /* Replication (master) */
    int slaveseldb;                 /* Last SELECTed DB in replication output */
    /*
    根据需要调整复制积压缓冲区的大小
        Redis为复制积压缓冲区设置的默认大小为l MB,如果主服务器需要执行大量写命令,又或者主从服务器断线后重连接所需的时间比较长,
    那么这个大小也许并不合适。
        如果复制积压缓冲区的大小设置得不恰当,那么PSYNC命令的复制重同步模式就不能正常发挥作用,因此,正确估算和设置复制积压缓冲区的大小非常重要。
        复制积压缓冲区的最小大小可以根据公式second * write_size_per_second来估算:
        其中second为从服务器断线后重新连接上主服务器所需的平均时阃(以秒计算):
        而write_size_per_second则是主服务器平均每秒产生的写命令数据量(协议格式的写命令的长度总和】。
        例如,如果主服务器平均每秒产生1MB的写数据,而从服务器断线之后平均要5秒才能重新连接上主服务器,那么复制积压缓冲区的大小就不能低于5 MB。
        为了安全起见,可以将复制积压缓冲区的大小设为2 * second * write_size_per_second.这样可以保证绝大部分断线情况都能用部分重同步来处理。



    复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小为lMB。
    固定长度先进先出队列的入队和出队规则跟普通的先进先出队列一样:新元素从一边进入队列,而旧元素从另一边弹出队列。
    和普通先进先出队列随着元素的增加和减少而动态调整长度不同,固定长度先进先出队列的长度是固定的,当入队元素的数量大于队列长度时,
    最先入队的元素会被弹出,而新元隶会被放入队列。

当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决
定对从服务器执行何种同步操作:
    如果offset偏移量之后的数据(也即是偏移量offset+l开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分萤同步操作。
    相反,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作。

    当从服务器A断线之后,它立即重新连接主服务器,并向主服务器发送PSYNC命令,报告自己的复制偏移量为10086。
    主服务器收到从服务器发来的PSYNC命令以及偏移量10086之后,主服务器将检查偏移量1 0 0 8 6之后的数据是否存在于复制积压缓冲区里面,
        结果发现这些数据仍然存在,于是主服务器向从服务器发送"+CONTINUE"回复,表示数据同步将以部分重同步模式来进行。
    接着主服务器会将复制积压缓冲区10086偏移量之后的所有数据(偏移量为10087至10119)都发送给从服务器。
*/
    
    // 全局复制偏移量(一个累计值)  是总长度,也就是写入积压缓冲区的时候就加,写多少加多少
    //info replication中的repl_backlog_first_byte_offset,表示积压缓存中有效数据的其实偏移量 
    //master_repl_offset表示积压缓冲区中的结束偏移量,结束偏移量和起始便宜量中的buf就是可用的数据
    //repl_backlog_histlen表示积压缓冲区中数据的大小 见feedReplicationBacklog
    long long master_repl_offset;   /* Global replication offset */
    // 主服务器发送 PING 的频率
    int repl_ping_slave_period;     /* Master pings the slave every N seconds */

     /*复制积压缓冲区见feedReplicationBacklog,这个主要用于网络闪断,然后通过client->reply链表存储发送这些KV,这些KV表面上
    发送出去了,实际上对方没有收到,下次改客户端连上后,通过replconf ack xxx来通告自己的offset,master收到后就可以判断对方是否有没收全的数据
    发送到client的实时KV积压buffer限制在checkClientOutputBufferLimits 
    
    这就是输出缓冲区:client->reply        checkClientOutputBufferLimits    主要应对客户端读取慢,同时又有大量KV进入本节点,造成积压
    复制积压缓冲区:server.repl_backlog    feedReplicationBacklog    主要应对网络闪断,进行部分重同步psyn,不必全量同步
    */

    //主备通过replicationFeedSlaves实现实时命令同步, 实时命令写入积压缓冲区在接口feedReplicationBacklog
    // backlog 本身 repl_backlog积压缓存区空间 repl_backlog_size积压缓冲区总大小 参考resizeReplicationBacklog 积压缓冲区空间分配在createReplicationBacklog
    char *repl_backlog;             /* Replication backlog for partial syncs */ //积压缓冲区在freeReplicationBacklog中释放
    //repl_backlog积压缓存区空间  repl_backlog_size积压缓冲区总大小  参考resizeReplicationBacklog
    // backlog 的长度 //repl-backlog-size进行设置  表示在从服务器断开连接过程中,如果有很大写命令,则用该大小的积压缓冲区来存储这些新增的,下次再次连接上直接把积压缓冲区的内容发送给从服务器即可
    long long repl_backlog_size;    /* Backlog circular buffer size */
    //info replication中的repl_backlog_first_byte_offset,表示积压缓存中有效数据的其实偏移量 
    //master_repl_offset表示积压缓冲区中的结束偏移量,结束偏移量和起始便宜量中的buf就是可用的数据
    //repl_backlog_histlen表示积压缓冲区中数据的大小 见feedReplicationBacklog
    // backlog 中数据的长度  repl_backlog_histlen 的最大值只能等于 repl_backlog_size 
    long long repl_backlog_histlen; /* Backlog actual data length */ //真正生效的地方见masterTryPartialResynchronization
    // backlog 的当前索引  也就是下次数据应该从环形缓冲区的那个地方开始写,见feedReplicationBacklog
    long long repl_backlog_idx;     /* Backlog circular buffer current offset */
    // backlog 中可以被还原的第一个字节的偏移量    见feedReplicationBacklog
    //info replication中的repl_backlog_first_byte_offset,表示积压缓存中有效数据的其实偏移量  master_repl_offset表示积压缓冲区中的结束偏移量,结束偏移量和起始便宜量中的buf就是可用的数据
    //真正生效的地方见masterTryPartialResynchronization
    long long repl_backlog_off;     /* Replication offset of first byte in the
                                       backlog buffer. */
    // backlog 的过期时间
    time_t repl_backlog_time_limit; /* Time without slaves after the backlog
                                       gets released. */

    // 距离上一次有从服务器的时间
    time_t repl_no_slaves_since;    /* We have no slaves since that time.
                                       Only valid if server.slaves len is 0. */

    /*
     辅助实现min-slaves配置选项
         Redis的min-slaves-to-write和min-slaves-max-lag两个选项可以防止主服务器在不安全的情况下执行写命令。举个例子,如果我们向主服务器提供以下设置:
       min-slaves-to-write 3
       min-slaves-max-lag 10
       那么在从服务器的数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10秒时,主服务器将拒绝执行写命令,这里的延迟值就是上面
       提到的INFO replication命令的lag值。
     */
    // 是否开启最小数量从服务器写入功能
    int repl_min_slaves_to_write;   /* Min number of slaves to write. */
    // 定义最小数量从服务器的最大延迟值
    int repl_min_slaves_max_lag;    /* Max lag of <count> slaves to write. */
    // 延迟良好的从服务器的数量  只是为了info的时候查看的
    int repl_good_slaves_count;     /* Number of slaves with lag <= max_lag. */


    /* Replication (slave) */
    // 主服务器的验证密码  masterauth配置
    char *masterauth;               /* AUTH with this password with master */
    //slaveof 10.2.2.2 1234 中的masterhost=10.2.2.2 masterport=1234  赋值见replicationSetMaster  建立连接在replicationCron
    // 主服务器的地址  如果这个部位空,则说明该参数指定的是该服务器对应的主服务器,也就是本服务器是从服务器

    //当从服务器执行slave no on,本服务器不在从属于某个主服务器了或者slave重新被选举为master的时候,replicationUnsetMaster中会把masterhost置为NULL
    char *masterhost;               /* Hostname of master */ //赋值见replicationSetMaster  建立连接在replicationCron
    // 主服务器的端口 //slaveof 10.2.2.2 1234 中的masterhost=10.2.2.2 masterport=1234
    int masterport;                 /* Port of master */ //赋值见replicationSetMaster  建立连接在replicationCron
    // 超时时间  从连接主的超时时间   主要用于判断主到从的PING保活和从到主的replconf ack保活
    int repl_timeout;               /* Timeout after N seconds of master idle */

    /*
    从服务器将向主服务器发送PSYNC命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态。
  值得一提的是,在同步操作执行之前,只有从服务器是主服务器的客户端,但是在执行同步操作之后,主服务器也会成为从服务器的客户端:
    1. 如果PSYNC命令执行的是完整重同步操作,那么主服务器需要成为从服务器的客户端,才能将保存在缓冲区里面的写命令发送给从服务器执行。
    2. 如果PSYNC命令执行的是部分重同步操作,那么主服务器需要成为从服务器的客户端,才能向从服务器发送保存在复制积压缓冲区里面的写命令。
    因此,在同步操作执行之后,主从服务器双方都是对方的客户端,它们可以互相向对方发送命令请求,或者互相向对方返回命令回复
    正因为主服务器成为了从服务器的客户端,所以主服务器才可以通过发送写命令来改变从服务器的数掘库状态,不仅同步操作需要用到这一点,
    这也是主服务器对从服务器执行命令传播操作的基础
     */

    /*
    主备同步会专门创建一个repl_transfer_s套接字(connectWithMaster)来进行主备同步,同步完成后在replicationAbortSyncTransfer中关闭该套接字
    主备同步完成后,主服务器要向本从服务器发送实时KV,则需要一个模拟的redisClient,因为redis都是通过redisClient中的fd来接收客户端发送的KV,
    主备同步完成后的时候KV和主备心跳保活都是通过该master(redisClient)的fd来和主服务器通信的
    */
    
    // 主服务器所对应的客户端,见readSyncBulkPayload    replicationSetMaster中是否该master资源  也就是本服务器的主服务器对象,见readSyncBulkPayload
    //使用这个的母的是让主服务器发送命令给从服务器,因为命令一般都是从客户端发送给服务器端的,

    //当本节点是从节点的时候,就创建一个redisClient节点用来专门同步主节点发往本节点的实时KV对给本从服务器  
    //例如从服务器连接到本主服务器,客户端更新了KV,这个更新操作就通过该redisClient来和主服务器通信
    //replicationHandleMasterDisconnection中置为NULL   如果主的在整体同步的时候,主老是不响应,则会触发undoConnectWithMaster进行重连
    redisClient *master;     /* Client that is master for this slave */ //如果备和主连接端口,则备会把该master记录到cached_master中,见replicationCacheMaster
    // 被缓存的主服务器,PSYNC 时使用  
//例如从服务器之前和主服务器连接上,并同步了数据,中途端了,则连接断了后会在replicationCacheMaster把server.cached_master = server.master;表示之前有连接到过服务器
//记录下这个主服务器机器相关状态的原因是,如果过一会儿又连接上了,则只需要部分从同步就可以了,而不用再做一次全量同步
    redisClient *cached_master; /* Cached master to be reused for PSYNC. */
    int repl_syncio_timeout; /* Timeout for synchronous I/O calls */
    // 复制的状态(服务器是从服务器时使用)  读取服务器端同步过来的rdb文件后,置为server.repl_state = REDIS_REPL_CONNECTED; 见readSyncBulkPayload
    int repl_state;          /* Replication status if the instance is a slave */
    // RDB 文件的总的大小
    off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
    // 已读 RDB 文件内容的字节数  repl_transfer_size - repl_transfer_read就是还未读取的rdb文件大小
    off_t repl_transfer_read;  /* Amount of RDB read from master during sync. */
    // 最近一次执行 fsync 时的偏移量
    // 用于 sync_file_range 函数  写入磁盘的文件大小,见readSyncBulkPayload
    off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */

    /*
    主备同步会专门创建一个repl_transfer_s套接字(connectWithMaster)来进行主备同步,同步完成后在replicationAbortSyncTransfer中关闭该套接字
    主备同步完成后,主服务器要向本从服务器发送实时KV,则需要一个模拟的redisClient,因为redis都是通过redisClient中的fd来接收客户端发送的KV,
    主备同步完成后的时候KV和主备心跳保活都是通过该master(redisClient)的fd来和主服务器通信的
    */
   
    // 主服务器的套接字  见connectWithMaster  用于主备同步  
    //主备同步会专门创建一个repl_transfer_s套接字(connectWithMaster)来进行主备同步,同步完成后在replicationAbortSyncTransfer中关闭该套接字
    int repl_transfer_s;     /* Slave -> Master SYNC socket */
    // 保存 RDB 文件的临时文件的描述符
    int repl_transfer_fd;    /* Slave -> Master SYNC temp file descriptor */
    // 保存 RDB 文件的临时文件名字
    char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
    // 最近一次读入 RDB 内容的时间  在接收RDB的时候,只要接收到一次数据就更新该值,见readSyncBulkPayload
    time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
    int repl_serve_stale_data; /* Serve stale data when link is down? */
    // 是否只读从服务器?  从服务器默认是只读,不能进行相关写操作
    int repl_slave_ro;          /* Slave is read only? */
    // 连接断开的时长
    time_t repl_down_since; /* Unix time at which link with master went down */
    // 是否要在 SYNC 之后关闭 NODELAY ?
    int repl_disable_tcp_nodelay;   /* Disable TCP_NODELAY after SYNC? */
    // 从服务器优先级
    int slave_priority;             /* Reported in INFO and used by Sentinel. */
    // 本服务器(从服务器)当前主服务器的 RUN ID
    char repl_master_runid[REDIS_RUN_ID_SIZE+1];  /* Master run id for PSYNC. */
    // 初始化偏移量
    long long repl_master_initial_offset;         /* Master PSYNC offset. */


    /* Replication script cache. */
    // 复制脚本缓存
    // 字典
    dict *repl_scriptcache_dict;        /* SHA1 all slaves are aware of. */
    // FIFO 队列
    list *repl_scriptcache_fifo;        /* First in, first out LRU eviction. */
    // 缓存的大小
    int repl_scriptcache_size;          /* Max number of elements. */

    /* Synchronous replication. */
    list *clients_waiting_acks;         /* Clients waiting in WAIT command. */
    int get_ack_from_slaves;            /* If true we send REPLCONF GETACK. */
    /* Limits */
    int maxclients;                 /* Max number of simultaneous clients */
    //生效比较见freeMemoryIfNeeded,实际内存是算出来的  maxmemory参数进行配置
    //如果内存超过该值,当继续set的时候会打印shared.oomerr
    unsigned long long maxmemory;   /* Max number of memory bytes to use */
    int maxmemory_policy;           /* Policy for key eviction */
    //maxmemory-samples中配置
    /*
    清理时会根据用户配置的maxmemory-policy来做适当的清理(一般是LRU或TTL),这里的LRU或TTL策略并不是针对redis的所有key,
    而是以配置文件中的maxmemory-samples个key作为样本池进行抽样清理。
    */
    int maxmemory_samples;          /* Pricision of random sampling */


    /* Blocked clients */
    unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */
    list *unblocked_clients; /* list of clients to unblock before next loop */
    list *ready_keys;        /* List of readyList structures for BLPOP & co */


    /* Sort parameters - qsort_r() is only available under BSD so we
     * have to take this state global, in order to pass it to sortCompare() */
    int sort_desc;
    int sort_alpha;
    int sort_bypattern;
    int sort_store;


    /* Zip structure config, see redis.conf for more information  */
    size_t hash_max_ziplist_entries;
    size_t hash_max_ziplist_value;
    size_t list_max_ziplist_entries;
    size_t list_max_ziplist_value; //默认=REDIS_LIST_MAX_ZIPLIST_VALUE,可以通过list-max-ziplist-value设置
    size_t set_max_intset_entries;
    size_t zset_max_ziplist_entries;
    size_t zset_max_ziplist_value;
    size_t hll_sparse_max_bytes;
    /*
     因为serverCron函数默认会以每100毫秒一次的频率更新unixtime属性和mstime属性,所以这两个属性记录的时间的精确度并不高:
     */
    //保存了秒级精度的系统当前UNIX时间戳  updateCachedTime中跟新
    time_t unixtime;        /* Unix time sampled every cron cycle. */
    //保存了毫秒级精度的系统当前UNIX时间戳 updateCachedTime中跟新
    long long mstime;       /* Like ‘unixtime‘ but with milliseconds resolution. */


     /*
    当一个客户端执行SUBSCRIBE命令订阅某个或某些频道的时候,这个客户端与被订阅频道之间就建立起了一种订阅关系。
        Redis将所有频道的订阅关系都保存在服务器状态的pubsub_channels字典里面,这个字典的键是某个被订阅的频道,而键的值则是一个链表,
    链表里面记录了所有订阅这个频道的客户端:
        subscribe频道订阅关系保存到pubsub_channels,psubscribe模式订阅关系保存到pubsub_patterns里面

        客户端1订阅:psubscribe  aaa[12]c, 则其他客户端2publish aaa1c xxx或者publish aaa2c xxx的时候,客户端1都会受到这个信息
         subscribe  ---  unsubscribe   频道订阅   
         psubscribe ---  punsubscribe  模式订阅
    */
    
    /* Pubsub */
    // 字典,键为频道,值为链表
    // 链表中保存了所有订阅某个频道的客户端
    // 新客户端总是被添加到链表的表尾
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */

    /*
     pubsub_patterns属性是一个链表,链表中的每个节点都包含着一个pubsub_pattern结构,这个结构的pattern属性记录了被订阅的模式,
     而client属性则记录了订阅模式的客户端
     */
    // 这个链表记录了客户端订阅的所有模式的名字  链表中的成员结构式pubsubPattern
    list *pubsub_patterns;  /* A list of pubsub_patterns */

    int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
                                   xor of REDIS_NOTIFY... flags. */


    /* Cluster */

    int cluster_enabled;      /* Is cluster enabled? */
    //默认REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT ms // 等待 PONG 回复的时长超过了限制值,将目标节点标记为 PFAIL (疑似下线)
    //起作用的地方见clusterCron
    mstime_t cluster_node_timeout; /* Cluster node timeout. */
    char *cluster_configfile; /* Cluster auto-generated config file name. */ //从nodes.conf中载入,见clusterLoadConfig
    //server.cluster = zmalloc(sizeof(clusterState));
    //集群相关配置加载在clusterLoadConfig,  server.cluster可能从nodes.conf配置文件中加载也可能如果没有nodes.conf配置文件或者配置文件空或者配置有误,则加载失败后
    //创建对应的cluster节点 clusterInit初始化创建空间
    struct clusterState *cluster;  /* State of the cluster */

    int cluster_migration_barrier; /* Cluster replicas migration barrier. */
    /* Scripting */

    // Lua 环境
    lua_State *lua; /* The Lua interpreter. We use just one for all clients */
    
    // 复制执行 Lua 脚本中的 Redis 命令的伪客户端
    //服务器会在初始化时创建负责执行Lua脚本中包含的Redis命令的伪客户端,并将这个伪客户端关联在服务器状态结构的lua_client属性中
    redisClient *lua_client;   /* The "fake client" to query Redis from Lua */

    // 当前正在执行 EVAL 命令的客户端,如果没有就是 NULL
    redisClient *lua_caller;   /* The client running EVAL right now, or NULL */

    // 一个字典,值为 Lua 脚本,键为脚本的 SHA1 校验和
    dict *lua_scripts;         /* A dictionary of SHA1 -> Lua scripts */
    // Lua 脚本的执行时限
    mstime_t lua_time_limit;  /* Script timeout in milliseconds */
    // 脚本开始执行的时间
    mstime_t lua_time_start;  /* Start time of script, milliseconds time */

    // 脚本是否执行过写命令
    int lua_write_dirty;  /* True if a write command was called during the
                             execution of the current script. */

    // 脚本是否执行过带有随机性质的命令
    int lua_random_dirty; /* True if a random command was called during the
                             execution of the current script. */

    // 脚本是否超时
    int lua_timedout;     /* True if we reached the time limit for script
                             execution. */

    // 是否要杀死脚本
    int lua_kill;         /* Kill the script if true. */


    /* Assert & bug reporting */

    char *assert_failed;
    char *assert_file;
    int assert_line;
    int bug_report_start; /* True if bug report header was already logged. */
    int watchdog_period;  /* Software watchdog period in ms. 0 = off */
};

 

以上是关于redis 数据结构 分析的主要内容,如果未能解决你的问题,请参考以下文章

如何利用redis来进行分布式集群系统的限流设计

Redis源代码分析(十七)--- multi事务操作

Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段

Redis源代码分析--- t_hash哈希转换

Redis 6.0 系列 | TLS源码分析

redis存储session配制方法