Redis源码解析(1) 动态字符串与链表
Redis源码解析(2) 字典与迭代器
Redis源码解析(3) 跳跃表
Redis源码解析(4) 整数集合
Redis源码解析(5) 压缩列表
Redis源码解析(6) 键的过期处理策略
Redis源码解析(7) 发布订阅机制
Redis源码解析(8) AOF持久化
Redis源码解析(9) RDB持久化
Redis源码解析(10) 网络框架
Redis源码解析(11) 内存淘汰策略
Redis源码解析(12) 命令执行过程
Redis源码解析(13) 主从复制
Redis源码解析(14) 哨兵机制[1] 结构与初始化
Redis源码解析(15) 哨兵机制[2] 信息同步与TILT模式
Redis源码解析(16) 哨兵机制[3] 判断下线
Redis源码解析(17) 哨兵机制[4] 故障转移
Redis源码解析(18) 集群[1]初始化,握手与心跳检测
Redis源码解析(19) 集群[2] 主从复制,故障检测与故障转移
Redis源码解析(20) 集群[3] 键的存储,重新分片与重定向
Redis源码解析(21) 集群[4] 故障转移failover与slave迁移
Redis源码解析(22) 事务
Redis源码解析(23) SCAN命令实现

引言

其实这里的副标题"信息同步"是有点混淆视听,这篇文章并不是将"一致性"这样一个问题.在我们使用redis提供的哨兵时,我们只需要在每一个哨兵的配置文件中写上它所要监控的主服务器地址即可,我们根本不必去关心其他sentinel节点和从服务器,它们会在sentinel集群的交互过程中自动交换信息,这篇文章要将的就是这样一个区间[初始化完成后,主服务器主观下线前],在这个区间中所有有关系的sentinel节点,主服务器,从服务器将连成一张网.那么是如何做到信息同步的呢?我们就来看看吧!

这篇文章的思路如下

  1. 首先用文字简要的阐述如何可以做到信息同步
  2. 对于每一步进行源码分析

如何?

首先我们在每个sentinel节点启动的时候它们都只会加载配置文件中指定的所有主服务器,然后建立两个连接,一个是命令连接,负责发送命令.一个是订阅连接,负责接收发到此主服务器上的发布消息.它们分别负责获取从服务器节点信息和其他sentinel节点信息.sentinel节点一般会每十秒向主服务器发送INFO信息获取从服务器信息,当然还有其他主服务器的状态,当解析出从服务器信息的时候对从服务器进行连接,然后创建一个实例,上一篇文章说过,就是加到sentinel的主字典中,然后在对应的主服务器中建立一个slave链表,方便调用.这样我们就获取了主服务器的信息.

sentinel节点的信息通过订阅连接来获取.每个sentinel节点在监视了主服务器节点和从服务器节点后会已命令的形式以默认两秒一次的频率发送PUBLISH信息,这样其他与这个服务器建立订阅连接的sentinel节点就可以获取到这条信息,从而知道这个sentinel节点的存在,然后与之建立一个连接,即命令连接,负责之间的PING与SENTINEL命令的传递,它们之间不需要订阅连接.当然它本身也会接到这条消息,只需要判断其中的消息是不是自己发的很容易的排除.当然还会在知道服务器建立一个sentinel链表,存着所有监视这个服务器节点的sentinel信息.

sentinel发送的信息格式如下
PUBLISH __sentinel__:hello "<s_ip>, <s_port>, <s_runid>, <s_epoch>,<m_name>,<m_ip>, <m_port>,<m_epoch>"

参数 意义
s_ip sentinel本身的IP
s_port sentinel本身的port IP和port使得其他sentinel可以发起连接
s_runid sentinel运行时ID
s_epoch sentinel的纪元 用于故障转移 默认两秒更新一次
m_name 主服务器名称由配置文件加载 从服务自动生成 格式为<IP:port>
m_ip 服务器IP
m_port 服务器port
m_epoch 服务器当前纪元

可以看到根据上述信息我们可以很好的得到发送这个信息的sentinel的重要信息,然后其他接收到这条消息的sentinel就可以与之发起命令连接了,并更新其信息.当然这个过程是相互的,第一次是为了初始化信息,两秒一次则是为了更新信息.

到这这个系统就稳定下来了,这也是这篇文章的要将的内容,接下来我们在源码中看看这一切是如何运作的.

源码解析

我们从哨兵机制的主函数sentinelTimer入手

void sentinelTimer(void) {// 记录本次 sentinel 调用的事件,// 并判断是否需要进入 TITL 模式sentinelCheckTiltCondition();// 执行定期操作// 比如 PING 实例、分析主服务器和从服务器的 INFO 命令// 向其他监视相同主服务器的 sentinel 发送问候信息// 并接收其他 sentinel 发来的问候信息// 执行故障转移操作,等等sentinelHandleDictOfRedisInstances(sentinel.masters);//脚本相关操作..................../* We continuously change the frequency of the Redis "timer interrupt"* in order to desynchronize every Sentinel from every other.* This non-determinism avoids that Sentinels started at the same time* exactly continue to stay synchronized asking to be voted at the* same time again and again (resulting in nobody likely winning the* election because of split brain voting). *///用一个巧妙的操作降低选举sentinel时重新选举的概率server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}

我们可以看到第一个函数是判断是否进入TILT模式,那么TILT是什么呢?通过查阅文档我们可以得到如下信息

Redis Sentinel is heavily dependent on the computer time: for instance in order to understand if an instance is available it remembers the time of the latest successful reply to the PING command, and compares it with the current time to understand how old it is.
However if the computer time changes in an unexpected way, or if the computer is very busy, or the process blocked for some reason, Sentinel may start to behave in an unexpected way.
The TILT mode is a special “protection” mode that a Sentinel can enter when something odd is detected that can lower the reliability of the system. The Sentinel timer interrupt is normally called 10 times per second, so we expect that more or less 100 milliseconds will elapse between two calls to the timer interrupt.
What a Sentinel does is to register the previous time the timer interrupt was called, and compare it with the current call: if the time difference is negative or unexpectedly big (2 seconds or more) the TILT mode is entered (or if it was already entered the exit from the TILT mode postponed).
When in TILT mode the Sentinel will continue to monitor everything, but:

  • It stops acting at all.
  • It starts to reply negatively to SENTINEL is-master-down-by-addr requests as the ability to detect a failure is no longer trusted.

If everything appears to be normal for 30 second, the TILT mode is exited.
Note that in some way TILT mode could be replaced using the monotonic clock API that many kernels offer. However it is not still clear if this is a good solution since the current system avoids issues in case the process is just suspended or not executed by the scheduler for a long time.

Redis哨兵是及其依赖于系统时间的,例如为了了解某个实例是否是可用的,其会记住最后一次成功回复PING命令的时间,然后与现在时间进行比较判断生存时间.
但是如果计算机时间发生了意外的变化,或者计算机现在非常忙碌,或者进程因为某些原因阻塞,哨兵可能会有意料之外的行为
TILT是一个特殊的保护模式,当检测到一些异常情况时会进入,这会降低系统的可靠性(笔者注:因为此时故障转移无法进行).sentinel计时器中断一般一秒十次.我们希望两次定时器中断的时间内控制在大约100毫秒.
Sentinel的作用是在上次调用计时器中断时进行注册,并将其与当前调用进行比较,如果时差为负或者出乎意料的大(2秒或更长时间),则进入TILT模式(如果已经进入了TILT模式则延迟退出)
当进入TILT模式时哨兵将会继续监视所有内容,但是

  • 停止所有的动作
  • 会开始否定SENTINEL is-master-down-by-addr命令因为其不信任故障检测的能力

如果30秒以内一切正常则退出TILT模式.
注意可以使用很多内核提供的单调时钟API已替换TILT模式.但是尚不清楚这是否是一个好的解决方案,因为当前系统避免了如果进程只是被挂起或长时间未由调度程序执行这种问题.

所以我们可以看到其只是一种保护模式,在计算机时间发生严重变化的时候进入TILT模式,并禁止除了监控以外的操作,因为此时sentinel依赖于时间,判断某个节点下线是根据PING的间隔来实现的,如果进入TILT模式证明时钟出现问题,此时这个sentinel已经不能被相信了.

void sentinelCheckTiltCondition(void) {// 计算当前时间mstime_t now = mstime();// 计算上次运行 sentinel 和当前时间的差mstime_t delta = now - sentinel.previous_time;// 如果差为负数,或者大于 2 秒钟,那么进入 TILT 模式if (delta < 0 || delta > SENTINEL_TILT_TRIGGER) {// 打开标记sentinel.tilt = 1;// 记录进入 TILT 模式的开始时间sentinel.tilt_start_time = mstime();// 打印事件sentinelEvent(REDIS_WARNING,"+tilt",NULL,"#tilt mode entered");}// 更新最后一次 sentinel 运行时间sentinel.previous_time = mstime();
}

我们可以看到函数的实现其实很简单,判断的两个条件文档中已经说了.差为负的话说明时钟出现问题.大于二的话则证明当前进程可能被阻塞或十分繁忙.

接下来就是重点中的重点,sentinelHandleDictOfRedisInstances,它遍历所有在此sentinel中存储的节点,并执行sentinelHandleRedisInstance

void sentinelHandleDictOfRedisInstances(dict *instances) {dictIterator *di;dictEntry *de;sentinelRedisInstance *switch_to_promoted = NULL;/* There are a number of things we need to perform against every master. */// 遍历多个实例,这些实例可以是多个主服务器、多个从服务器或者多个 sentineldi = dictGetIterator(instances);while((de = dictNext(di)) != NULL) {// 取出实例对应的实例结构sentinelRedisInstance *ri = dictGetVal(de);// 执行调度操作sentinelHandleRedisInstance(ri);// 如果被遍历的是主服务器,那么递归地遍历该主服务器的所有从服务器// 以及所有 sentinelif (ri->flags & SRI_MASTER) {// 所有从服务器sentinelHandleDictOfRedisInstances(ri->slaves);// 所有 sentinelsentinelHandleDictOfRedisInstances(ri->sentinels);// 对已下线主服务器(ri)的故障迁移已经完成// ri 的所有从服务器都已经同步到新主服务器if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) {// 已选出新的主服务器switch_to_promoted = ri;}}}// 将原主服务器(已下线)从主服务器表格中移除,并使用新主服务器代替它if (switch_to_promoted)sentinelFailoverSwitchToPromotedSlave(switch_to_promoted);dictReleaseIterator(di); //释放迭代器
}
// 对给定的实例执行定期操作
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {/* ========== MONITORING HALF ============ *//* ==========     监控操作    =========*//* Every kind of instance *//* 对所有类型实例进行处理 */// 如果有需要的话,创建连向实例的网络连接// 这也会在哨兵刚刚启动的时候连接所有的主服务器sentinelReconnectInstance(ri);// 根据情况,向实例发送 PING、 INFO 或者 PUBLISH 命令sentinelSendPeriodicCommands(ri);/* ============== ACTING HALF ============= *//* ==============  故障检测   ============= */........................
}

注释掉的地方是故障检测与故障转移操作,我们在下一篇文章中分析.

void sentinelReconnectInstance(sentinelRedisInstance *ri) {// 示例未断线(已连接)// 因为这个函数在每次定时器中断被调用,所以需要排除很多已连接的if (!(ri->flags & SRI_DISCONNECTED)) return;/* Commands connection. */// 对所有实例创建一个用于发送 Redis 命令的连接if (ri->cc == NULL) {// 连接实例ri->cc = redisAsyncConnect(ri->addr->ip,ri->addr->port);// 连接出错if (ri->cc->err) {sentinelEvent(REDIS_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",ri->cc->errstr);sentinelKillLink(ri,ri->cc);// 连接成功} else {// 设置连接属性ri->cc_conn_time = mstime();ri->cc->data = ri;redisAeAttach(server.el,ri->cc);// 设置连线 callbackredisAsyncSetConnectCallback(ri->cc,sentinelLinkEstablishedCallback);// 设置断线 callbackredisAsyncSetDisconnectCallback(ri->cc,sentinelDisconnectCallback);// 发送 AUTH 命令,验证身份sentinelSendAuthIfNeeded(ri,ri->cc);sentinelSetClientName(ri,ri->cc,"cmd");// 发送一个PINGsentinelSendPing(ri);}}/* Pub / Sub */// 对主服务器和从服务器,创建一个用于订阅频道的连接 sentinel不需要 if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && ri->pc == NULL) {// 连接实例ri->pc = redisAsyncConnect(ri->addr->ip,ri->addr->port);// 连接出错if (ri->pc->err) {sentinelEvent(REDIS_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",ri->pc->errstr);sentinelKillLink(ri,ri->pc);// 连接成功} else {int retval;// 设置连接属性ri->pc_conn_time = mstime();ri->pc->data = ri;redisAeAttach(server.el,ri->pc);// 设置连接 callbackredisAsyncSetConnectCallback(ri->pc,sentinelLinkEstablishedCallback);// 设置断线 callbackredisAsyncSetDisconnectCallback(ri->pc,sentinelDisconnectCallback);// 发送 AUTH 命令,验证身份sentinelSendAuthIfNeeded(ri,ri->pc);// 为客户端设置名字 "pubsub"sentinelSetClientName(ri,ri->pc,"pubsub");/* Now we subscribe to the Sentinels "Hello" channel. */// 发送 SUBSCRIBE __sentinel__:hello 命令,订阅频道retval = redisAsyncCommand(ri->pc,sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s",SENTINEL_HELLO_CHANNEL);// 订阅出错,断开连接if (retval != REDIS_OK) {/* If we can't subscribe, the Pub/Sub connection is useless* and we can simply disconnect it and try again. */sentinelKillLink(ri,ri->pc);return;}}}/* Clear the DISCONNECTED flags only if we have both the connections* (or just the commands connection if this is a sentinel instance). */// 如果实例是主服务器或者从服务器,那么当 cc 和 pc 两个连接都创建成功时,关闭 DISCONNECTED 标识// 如果实例是 Sentinel ,那么当 cc 连接创建成功时,关闭 DISCONNECTED 标识 即此时已连接成功if (ri->cc && (ri->flags & SRI_SENTINEL || ri->pc))ri->flags &= ~SRI_DISCONNECTED;
}

在这个函数过后,如果是第一次调用,所有被创建实例的主服务器会被发起异步连接,如果不是第一次,其他被新加入的从服务器和sentinel节点会发起异步连接.

// INFO用于获取从服务器信息 PUBLISH用来获取其他哨兵的信息 PING用来检测现在的连接状况
void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {mstime_t now = mstime();mstime_t info_period, ping_period;int retval;/* Return ASAP if we have already a PING or INFO already pending, or* in the case the instance is not properly connected. */// 函数不能在网络连接未创建时执行if (ri->flags & SRI_DISCONNECTED) return;/* For INFO, PING, PUBLISH that are not critical commands to send we* also have a limit of SENTINEL_MAX_PENDING_COMMANDS. We don't* want to use a lot of memory just because a link is not working* properly (note that anyway there is a redundant protection about this,* that is, the link will be disconnected and reconnected if a long* timeout condition is detected. */// 为了避免 sentinel 在实例处于不正常状态时,发送过多命令// sentinel 只在待发送命令的数量未超过 SENTINEL_MAX_PENDING_COMMANDS 常量时// 才进行命令发送 在每次发送命令还未回复时会使pending_commands加一if (ri->pending_commands >= SENTINEL_MAX_PENDING_COMMANDS) return;/* If this is a slave of a master in O_DOWN condition we start sending* it INFO every second, instead of the usual SENTINEL_INFO_PERIOD* period. In this state we want to closely monitor slaves in case they* are turned into masters by another Sentinel, or by the sysadmin. */// 对于从服务器来说, sentinel 默认每 SENTINEL_INFO_PERIOD 秒向它发送一次 INFO 命令// 但是,当从服务器的主服务器处于 SDOWN 状态,或者正在执行故障转移时// 为了更快速地捕捉从服务器的变动, sentinel 会将发送 INFO 命令的频率该为每秒一次// 需要更快的发现一个从服务器升级为主服务器 从而进行故障转移if ((ri->flags & SRI_SLAVE) && //减少INFO间隔是为了更快的获取其身份信息(ri->master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS))) {info_period = 1000; //减小INFO命令时间间隔} else {info_period = SENTINEL_INFO_PERIOD;}/* We ping instances every time the last received pong is older than* the configured 'down-after-milliseconds' time, but every second* anyway if 'down-after-milliseconds' is greater than 1 second. */// 这里就是把配置文件中的down_after_period的时间与正常PING的间隔作比较,最小为1秒ping_period = ri->down_after_period;if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;// 实例不是 Sentinel (而是主服务器或者从服务器)// 并且以下条件的其中一个成立:// 1)SENTINEL 未收到过这个服务器的 INFO 命令回复// 2)距离上一次该实例回复 INFO 命令已经超过 info_period 间隔// 那么向实例发送 INFO 命令// sentinel会向其连接的哨兵发送PING,但不会发送PUBLISHif ((ri->flags & SRI_SENTINEL) == 0 &&(ri->info_refresh == 0 ||(now - ri->info_refresh) > info_period)) //间隔一般为十秒 特殊为1秒{/* Send INFO to masters and slaves, not sentinels. */retval = redisAsyncCommand(ri->cc, //命令均为异步执行sentinelInfoReplyCallback, NULL, "INFO"); //回调为对INFO命令的回复if (retval == REDIS_OK) ri->pending_commands++; //已发送但尚未回复的命令} else if ((now - ri->last_pong_time) > ping_period) { //间隔一般为一秒 取决于down_after_period/* Send PING to all the three kinds of instances. */sentinelSendPing(ri);} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) { //间隔两秒/* PUBLISH hello messages to all the three kinds of instances. */sentinelSendHello(ri);}
}

sentinelInfoReplyCallback这个函数就是一个对INFO命令回复的解析过程,我们会在需要时分析一部分,比如原来是从服务器,现在是主服务器,证明升级成功,看看会发生什么事情.这里我们看看当接收到一个新的服务器连接时会发生什么

...............
// 如果发现有新的从服务器出现,那么为它添加实例
if (sentinelRedisInstanceLookupSlave(ri,ip,atoi(port)) == NULL) {if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,ip,atoi(port), ri->quorum, ri)) != NULL){sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");}
}
.................

我们可以看到解析出来信息以后会先查看主服务器是否存在这个slave,不存在的话创建一个实例.这样我们就可以得到其他从服务器了.

我们再来看看处理PUBLISH的返回处理回调,其中会把主服务器中不存在的slave加入主服务器

void sentinelProcessHelloMessage(char *hello, int hello_len) {/* Format is composed of 8 tokens:* 0=ip,1=port,2=runid,3=current_epoch,4=master_name,* 5=master_ip,6=master_port,7=master_config_epoch. */int numtokens, port, removed, master_port;uint64_t current_epoch, master_config_epoch;char **token = sdssplitlen(hello, hello_len, ",", 1, &numtokens);sentinelRedisInstance *si, *master;if (numtokens == 8) {/* Obtain a reference to the master this hello message is about */// 获取主服务器的名字,并丢弃和未知主服务器相关的消息。master = sentinelGetMasterByName(token[4]);if (!master) goto cleanup; /* Unknown master, skip the message. *//* First, try to see if we already have this sentinel. */// 看这个 Sentinel 是否已经认识发送消息的 Sentinelport = atoi(token[1]);master_port = atoi(token[6]);// 会遍历所有的输入实例 看是否存在 不存在返回NULLsi = getSentinelRedisInstanceByAddrAndRunID(master->sentinels,token[0],port,token[2]);current_epoch = strtoull(token[3],NULL,10);master_config_epoch = strtoull(token[7],NULL,10);if (!si) {// 这个 Sentinel 不认识发送消息的 Sentinel // 将对方加入到 Sentinel 列表中/* If not, remove all the sentinels that have the same runid* OR the same ip/port, because it's either a restart or a* network topology change. *//*删除所有具有相同runid或相同ip/port的实例,因为这是重新启动或网络拓扑更改。*/removed = removeMatchingSentinelsFromMaster(master,token[0],port,token[2]);if (removed) {sentinelEvent(REDIS_NOTICE,"-dup-sentinel",master,"%@ #duplicate of %s:%d or %s",token[0],port,token[2]);}/* Add the new sentinel. */ //创建一个新实例 这里更新了sentinelsi = createSentinelRedisInstance(NULL,SRI_SENTINEL,token[0],port,master->quorum,master);if (si) {sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@");/* The runid is NULL after a new instance creation and* for Sentinels we don't have a later chance to fill it,* so do it now. */si->runid = sdsnew(token[2]);sentinelFlushConfig();}}/* Update local current_epoch if received current_epoch is greater.*/// 如果消息中记录的纪元比 Sentinel 当前的纪元要高,那么更新纪元// 这里的sentinel是此sentinel的主结构体 即 struct sentinelStateif (current_epoch > sentinel.current_epoch) {sentinel.current_epoch = current_epoch;sentinelFlushConfig();sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",(unsigned long long) sentinel.current_epoch);}/* Update master info if received configuration is newer. */// 如果消息中记录的配置信息更新,那么对主服务器的信息进行更新// 这里也是主服务器信息改变的地方 if (master->config_epoch < master_config_epoch) { //纪元增大 下一轮投票master->config_epoch = master_config_epoch;if (master_port != master->addr->port ||  //端口变了strcmp(master->addr->ip, token[5])){sentinelAddr *old_addr;sentinelEvent(REDIS_WARNING,"+config-update-from",si,"%@");sentinelEvent(REDIS_WARNING,"+switch-master",master,"%s %s %d %s %d",master->name,master->addr->ip, master->addr->port,token[5], master_port);old_addr = dupSentinelAddr(master->addr); //复制并返回给定地址的一个副本//端口改变当然要改变信息啦sentinelResetMasterAndChangeAddress(master, token[5], master_port);sentinelCallClientReconfScript(master,SENTINEL_OBSERVER,"start",old_addr,master->addr);releaseSentinelAddr(old_addr);}}/* Update the state of the Sentinel. */// 更新我方 Sentinel 记录的对方 Sentinel 的信息。if (si) si->last_hello_time = mstime();}cleanup:sdsfreesplitres(token,numtokens);
}

我们使用INFO信息的返回这添加从节点,订阅机制获取其他sentinel并更新服务器,并使用PING命令不停的更新与其他主服务器,从服务器,sentinel节点最后一次通信的时间,用于判断下线,此时这个系统就构成了一张网,下一篇我们来看看如何判断下线和在sentinel集群中找到leader,使其进行开始故障转移.

Redis源码解析(15) 哨兵机制[2] 信息同步与TILT模式相关推荐

  1. Redis源码解析——内存管理

    在<Redis源码解析--源码工程结构>一文中,我们介绍了Redis可能会根据环境或用户指定选择不同的内存管理库.在linux系统中,Redis默认使用jemalloc库.当然用户可以指定 ...

  2. react源码解析15.schedulerLane

    react源码解析15.scheduler&Lane 视频讲解(高效学习):进入学习 往期文章: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 ...

  3. Redis源码解析——双向链表

    相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的.教科书般简单的库.但是作为Redis源码的一部分,我决定还是要讲一讲的.(转载请指明出于breaksoftware的csdn ...

  4. Redis源码解析——字典基本操作

    有了<Redis源码解析--字典结构>的基础,我们便可以对dict的实现进行展开分析.(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的, ...

  5. Redis源码解析——前言

    今天开启Redis源码的阅读之旅.对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事.但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它.除了一些高大上 ...

  6. Redis源码解析——有序整数集

    有序整数集是Redis源码中一个以大尾(big endian)形式存储,由小到大排列且无重复的整型集合.它存储的类型包括16位.32位和64位的整型数.在介绍这个库的实现前,我们还需要先熟悉下大小尾内 ...

  7. Redis源码解析——Zipmap

    本文介绍的是Redis中Zipmap的原理和实现.(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结 ...

  8. Redis源码解析——字典结构

    C++语言中有标准的字典库,我们可以通过pair(key,value)的形式存储数据.但是C语言中没有这种的库,于是就需要自己实现.本文讲解的就是Redis源码中的字典库的实现方法.(转载请指明出于b ...

  9. Redis源码解析——字典遍历

    之前两篇博文讲解了字典库的基础,本文将讲解其遍历操作.之所以将遍历操作独立成一文来讲,是因为其中的内容和之前的基本操作还是有区别的.特别是高级遍历一节介绍的内容,充满了精妙设计的算法智慧.(转载请指明 ...

最新文章

  1. 驾校计算机岗位管理制度,驾校计算机的规章制度.doc
  2. MFC消息响应函数OnPaint
  3. xamarin android pdf,Xamarin.Android - 下载pdf和视频到应用空间并打开
  4. 2013 第4届 蓝桥杯 黄金连分数【详解】
  5. Java Arraylist 如何使用 Comparator排序
  6. 鹤峰:美丽的茶乡—— 舞狮篇
  7. Sorting It All Out (易错题+拓扑排序+有向图(判环+判有序)优先级)
  8. python扫雷 广度优先_广度优先搜索(BFS)解题总结
  9. maven配置本地jar包
  10. LSET与LREM结合删除list中特定索引的值
  11. Unity3D研究院之mac上从.ipa中提取unity3D游戏资源
  12. 虚拟仪器是在计算机基础上通过增加相关硬件和软件构建而成的仪器,无损检测考试...
  13. BLP防数据泄露安全操作系统:道里云公司参展英特尔北京IDF峰会产品介绍(二)
  14. LAZADA四大行业最新趋势选品指南!菲律宾Bday大促活动报名
  15. Android 音视频开发相关知识
  16. object.key
  17. python snownlp情感分析和词云分析
  18. PostgreSQL使用PostGIS插件,存储GIS数据
  19. 小队pkc++_骑士小队2人金属第一印象
  20. 网页调用腾讯qq在线客服

热门文章

  1. 工作中可能用到的——集中式版本控制系统SVN
  2. matlab unifit,【matlab】matlab在概率统计中的应用(二)
  3. HTML基础知识整理
  4. MMD以及核公式推导
  5. 1367 二叉树中的列表(递归)
  6. 喂养三种宠物:猫、狗和鸟
  7. python 来实现文件复制操作
  8. Python 文件打开读取写入方法
  9. 记某环境SqlServer异地备份的坑(Server权限管控贼拉严格)
  10. 六年级计算机学情分析报告,六年级学生学情分析报告.doc