概述

Redis 的 client-output-buffer-limit 可以用来强制断开无法足够快从 redis 服务器端读取数据的客户端。
保护机制规则如下:

  1. [hard limit] 大小限制,当某一客户端缓冲区超过设定值后,直接关闭连接。
  2. [soft limit] 持续时间限制,当某一客户端缓冲区持续一段时间占用过大空间时关闭连接。

该参数一般用在以下几类客户端中:

  • 普通 client,包括 monitor
  • 主从同步时的 slave client
  • Pub/Sub 模式中的 client

配置介绍与分析

该参数的配置语法:

client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>

配置实例:

# 普通client buffer限制
client-output-buffer-limit normal 0 0 0
# slave client buffer限制
client-output-buffer-limit slave 256mb 64mb 60
# pubsub client buffer限制
client-output-buffer-limit pubsub 32mb 8mb 60
  • client-output-buffer-limit normal 0 0 0

将 hard limit 和 soft limit 同时设置为 0,则表示关闭该限制。

  • client-output-buffer-limit slave 256mb 64mb 60

该配置表示,对于 slave 客户端来说,如果 output-buffer 占用内存达到 256M 或者超过 64M 的时间达到 60s,则关闭客户端连接。

  • client-output-buffer-limit pubsub 32mb 8mb 60

该配置表示,对于 Pub/Sub 客户端来说,若 output-buffer 占用内存达到 32M 或者超过 8M 的时间达到 60s,则关闭客户端连接。

概括说明:

一般情况下,对于普通客户端,client-output-buffer 是不设限制的,因为 server 只会在 client 请求数据的时候才会发送,不会产生积压。
而在 server 主动发送,client 来处理的场景下,这种一般都是异步处理的,会划出一个缓冲区来“暂存”未处理的数据,若 server 发送数据比 client 处理数据快时,就会发生缓冲区积压。对于用作 Pub/Sub 和 slave 的客户端,server 会主动把数据推送给他们,故需要设置 client-output-buffer 的限制。

示例分析

下面我们以主从同步时的 slave 客户端,来具体分析下。
在 redis 在主从同步时,master 会为 slave 创建一个输出缓冲区。在 master 保存 rdb,将 rdb 文件传输给 slave,slave 加载 rdb 完成之前,master 会将接收到的所有写命令,写入到内存中的这个输出缓冲区去。
若 rdb 的保存,传输,加载耗时过长,或者在此期间的写命令过多,则可能会造成超过缓冲区限制,造成 master 和 slave 的连接断开。此时则需要适当调整下 client-output-buffer-limit slave配置。

源码浅析-主从同步时 output buffer 使用

基于 redis5.0 版本源码

redis server 通过 addReply 将数据发送给客户端,以下源码见 https://github.com/redis/redis/blob/5.0/src/networking.c#L190-L211

/* Add the object 'obj' string representation to the client output buffer. */
void addReply(client *c, robj *obj) {
if (prepareClientToWrite(c) != C_OK) return; if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
_addReplyStringToList(c,obj->ptr,sdslen(obj->ptr));
} else if (obj->encoding == OBJ_ENCODING_INT) {
char buf[32];
size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
if (_addReplyToBuffer(c,buf,len) != C_OK)
_addReplyStringToList(c,buf,len);
} else {
serverPanic("Wrong obj->encoding in addReply()");
}
}

在函数的开头,会通过prepareClientToWrite(c)判断是否需要将数据写入客户端的 output buffer 中。我们看下什么条件下数据会被写入客户端的 output buffer 中,即返回 C_OK

/* This function is called every time we are going to transmit new data
* to the client. The behavior is the following:
*
* If the client should receive new data (normal clients will) the function
* returns C_OK, and make sure to install the write handler in our event
* loop so that when the socket is writable new data gets written.
*
* If the client should not receive new data, because it is a fake client
* (used to load AOF in memory), a master or because the setup of the write
* handler failed, the function returns C_ERR.
*
* The function may return C_OK without actually installing the write
* event handler in the following cases:
*
* 1) The event handler should already be installed since the output buffer
* already contains something.
* 2) The client is a slave but not yet online, so we want to just accumulate
* writes in the buffer but not actually sending them yet.
*
* Typically gets called every time a reply is built, before adding more
* data to the clients output buffers. If the function returns C_ERR no
* data should be appended to the output buffers. */
int prepareClientToWrite(client *c) {
/* If it's the Lua client we always return ok without installing any
* handler since there is no socket at all. */
if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK; /* CLIENT REPLY OFF / SKIP handling: don't send replies. */
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR; /* Masters don't receive replies, unless CLIENT_MASTER_FORCE_REPLY flag
* is set. */
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR; if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading. */ /* Schedule the client to write the output buffers to the socket, unless
* it should already be setup to do so (it has already pending data). */
if (!clientHasPendingReplies(c)) clientInstallWriteHandler(c); /* Authorize the caller to queue in the output buffer of this client. */
return C_OK;
} /* Return true if the specified client has pending reply buffers to write to
* the socket. */
int clientHasPendingReplies(client *c) {
return c->bufpos || listLength(c->reply);
} void clientInstallWriteHandler(client *c) {
/* Schedule the client to write the output buffers to the socket only
* if not already done and, for slaves, if the slave can actually receive
* writes at this stage. */
if (!(c->flags & CLIENT_PENDING_WRITE) &&
(c->replstate == REPL_STATE_NONE ||
(c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
{
c->flags |= CLIENT_PENDING_WRITE;
listAddNodeHead(server.clients_pending_write,c);
}
}

由于函数默认返回C_OK,我们只需要看哪几类情况返回的不是C_OK,即C_ERR,数据就不会被写入到客户端的 output buffer 中。
返回C_ERR的情况:

  • 客户端是个 fake client(用于加载 AOF 文件)
  • 客户端是一个 master
  • slave 的状态为 SLAVE_STATE_ONLINE 且其回调函数失败((c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)),或slave 的状态为 REPL_STATE_NONE

If the client should not receive new data, because it is a fake client (used to load AOF in memory), a master or because the setup of the write handler failed, the function returns C_ERR.

在 master 保存和发送 rdb 文件时,slave 的状态是以下几种,所以在这期间的写命令都会保存在 slave 的 output buffer。由于没有设置回调函数,数据并不会发送到 slave 上,仅存储在 master 为 slave 创建的 output buffer 内。

#define SLAVE_STATE_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
#define SLAVE_STATE_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to slave. */

那么何时才会从 output buffer 中“刷入”slave 呢?直到 master 将 rdb 文件完全发送给 slave 后,master 会在 sendBulkToSlave函数中进行相关操作。以下源码见:https://github.com/redis/redis/blob/5.0/src/replication.c#L876-L930

void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
// 此处省略部分源码 // rdb 文件已完全发送给 slave
if (slave->repldboff == slave->repldbsize) {
close(slave->repldbfd);
slave->repldbfd = -1;
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
putSlaveOnline(slave);
}
} void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0;
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
sendReplyToClient, slave) == AE_ERR) {
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
freeClient(slave);
return;
}
refreshGoodSlavesCount();
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
replicationGetSlaveName(slave));
}

此处会将 slave 状态改为 SLAVE_STATE_ONLINE,并将repl_put_online_on_ack置为0,(有没有很熟悉,对了,就是上面clientInstallWriteHandler中判断的内容)。同时也会设置回调函数sendReplyToClient,将此前 master 为 slave 创建的 output buffer 中的写操作全部发送到 slave 上。同时 slave 状态的变更,会使得后续 master 上的写操作可以正常的 push 到 slave 上了(直接,无需走 output buffer)。

总结

本次我们通过 client-output-buffer-limit参数,了解了其使用场景,并重点就主从同步时 output buffer 写入情况进行了源码的简单分析。今天的学习就到这里,我们改天接着肝。

参考内容

  1. https://www.cnblogs.com/wangcp-2014/p/15505180.html

认识 Redis client-output-buffer-limit 参数与源码分析的更多相关文章

  1. Caffe参数交换源码分析

    对境准备:对于多个GPU而言,一台机器2个GPU,参数交换的流程图: 参数交换从main()进入train()函数,在train函数中找到对应源码为: . . . . . ) { caffe::P2P ...

  2. 第10课:[实战] Redis 网络通信模块源码分析(3)

    redis-server 接收到客户端的第一条命令 redis-cli 给 redis-server 发送的第一条数据是 *1\r\n\$7\r\nCOMMAND\r\n .我们来看下对于这条数据如何 ...

  3. 【Redis】事件驱动框架源码分析

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

  4. 【Redis】事件驱动框架源码分析(单线程)

    aeEventLoop初始化 在server.c文件的initServer函数中,对aeEventLoop进行了初始化: 调用aeCreateEventLoop函数创建aeEventLoop结构体,对 ...

  5. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

  6. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

  7. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  8. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  9. redis源码分析之事务Transaction(下)

    接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...

随机推荐

  1. Redis 07 有序集合

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 Zset 就是 ...

  2. 用VS Code搞Qt6:至简窗口部件——QWidget

    在正题开始之前,老周照例扯点别的.嗯,咱们扯一下在 VS 2022 下结合 CMake 开发 Qt6 时的环境变量设置问题.在VS Code 中,通够通过 CMake Tools 扩展的配置来设置环境 ...

  3. DBPack 限流熔断功能发布说明

    上周我们发布了 v0.4.0 版本,增加了限流熔断功能,现对这两个功能做如下说明. 限流 DBPack 限流熔断功能通过 filter 实现.要设置限流规则,首先要定义 RateLimitFilter ...

  4. python爬虫之BeautifulSoup4使用

    钢铁知识库,一个学习python爬虫.数据分析的知识库.人生苦短,快用python. 上一章我们讲解针对结构化的html.xml数据,使用Xpath实现网页内容爬取.本章我们再来聊另一个高效的神器:B ...

  5. 刷题记录:Codeforces Round #725 (Div. 3)

    Codeforces Round #725 (Div. 3) 20210704.网址:https://codeforces.com/contest/1538. 感觉这个比上一个要难. A 有一个n个数 ...

  6. Mybatis-Plus高级之LambdaQueryWrapper,Wrappers.<实体类>lambdaQuery的使用

    一.前言 小编今天又来分享干货了,绝对的干净又卫生,大伙请放心食用哈!Mybatis-Plus我们经常使用,但是里面的很多功能,小编开始只是知道一点点,做个增删改查没问题.小编在新项目中发现,大神们不 ...

  7. Hnoi2014世界树

    题面 说明/提示 N<=300000, q<=300000,m[1]+m[2]+...+m[q]<=300000 题解 这道题一看 "m[1]+m[2]+...+m[q]& ...

  8. 面向对象06---static关键字

    public class Preson { { System.out.println("匿名代码块");// 2 } static { System.out.println(&qu ...

  9. 在Laravel框架blog中,终端的一些命令

    创建控制器php artisan make:controller TestController数据库迁移php artisan make:migration create_goods_table实行迁 ...

  10. hadoop 不在 sudoers 文件中,此事将被报告。

    问题来源: 使用sudo命令,让hadoop用户使用root身份执行命令时报错: [hadoop@mydocker ~]$ sudo date [sudo] password for hadoop: ...