由于诸多原因,现在已经很少能长篇大论介绍Redis的相关技术,但日常工作以及脑子中经常想整理和总结一些Redis的点点滴滴,既能帮助自己记录问题,又能帮助他人减少碰到类似问题,于是有个想法,准备写一个小的系列:Redis小功能大用处。

本文将介绍Redis 3后一个新的统计项total_net_output_bytes。

一、问题

之前有朋友问我,Redis统计的网络输出流量总值total_net_output_bytes和瞬时值instantaneous_output_kbps比实际机器统计的要高。(Redis版本3.2.3)

Redis小功能大用处-total_net_output_bytes_java

经试验证明,确实如此:一个机器的输出网络流量远远小于几个Redis实例的。

1.单机Redis实例网络输出流量统计:

Redis小功能大用处-total_net_output_bytes_java_02

2.单机整机网络输出流量统计:

Redis小功能大用处-total_net_output_bytes_java_03

一般碰到这类问题,习惯性的在源码里找下,看看到底是如何计算的。

二、网络输出流量计算

代码里搜server.stat_net_output_bytes,找到如下这个代码

1. networking.c的writeToClient函数:

将输出缓冲区数据通过socket发送给客户端,源码比较长:

/* Write data in output buffers to client
int writeToClient(int fd, client *c, int handler_installed) {
    ssize_t nwritten = 0, totwritten = 0;
    size_t objlen;
    size_t objmem;
    robj *o;

    while(clientHasPendingReplies(c)) {
        if (c->bufpos > 0) {
            nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
            if (nwritten <= 0) break;
            c->sentlen += nwritten;
            totwritten += nwritten;

            if ((int)c->sentlen == c->bufpos) {
                c->bufpos = 0;
                c->sentlen = 0;
            }
        } else {
            o = listNodeValue(listFirst(c->reply));
            objlen = sdslen(o->ptr);
            objmem = getStringObjectSdsUsedMemory(o);

            if (objlen == 0) {
                listDelNode(c->reply,listFirst(c->reply));
                c->reply_bytes -= objmem;
                continue;
            }

            nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
            if (nwritten <= 0) break;
            c->sentlen += nwritten;
            totwritten += nwritten;

            if (c->sentlen == objlen) {
                listDelNode(c->reply,listFirst(c->reply));
                c->sentlen = 0;
                c->reply_bytes -= objmem;
            }
        }
        server.stat_net_output_bytes += totwritten;
        if (totwritten > NET_MAX_WRITES_PER_EVENT &&
            (server.maxmemory == 0 ||
             zmalloc_used_memory() < server.maxmemory)) break;
    }
    .............忽略忽略忽略..............

2. 基础知识:

Redis为每个客户端配置了输入缓冲区和输出缓冲区,用户暂存发送的命令和返回的结果,其中输出缓冲区分为:普通客户端缓冲区、复制客户端缓冲区、pubsub客户端缓冲区,并且输出缓冲区内部分为bufpos用于缓存一些小结果集,另外设置一个队列缓存大结果:

(1) 小结果集:

typedef struct redisClient {

 // 缓存小结果集
 char buf[REDIS_REPLY_CHUNK_BYTES]
 int bufpos

}

Redis小功能大用处-total_net_output_bytes_java_04

(2) 小结果集:

typedef struct redisClient {

 // 缓存队列
 list *reply

}

Redis小功能大用处-total_net_output_bytes_java_05

三、Redis作者的低级失误:

server.stat_net_output_bytes记录从输出缓冲区累加的字节数,但3.2.3之前似乎放错了位置,导致过大。

可以想到这个bug肯定已经被提过并修复了(https://github.com/redis/redis/commit/d70ac1d1)

Redis小功能大用处-total_net_output_bytes_java_06

四、其他

1.instantaneous_output_kbps:total_net_output_bytes是一个累计值,为了方便看到瞬时值提供了instantaneous_output_kbps,它在内部用采样的方法计算的,如果你比较懒,不乐意自己按一定时间计算差值,可以使用instantaneous_output_kbps。

instantaneous_output_kbps = (float)getInstantaneousMetric(STATS_METRIC_NET_OUTPUT)/1024,

/* Return the mean of all the samples. */
long long getInstantaneousMetric(int metric) {
    int j;
    long long sum = 0;

    for (j = 0; j < STATS_METRIC_SAMPLES; j++)
        sum += server.inst_metric[metric].samples[j];
    return sum / STATS_METRIC_SAMPLES;
}

2.作用

该统计指标非常重要:通过对其监控,规划和防止打爆机器网卡(千兆、万兆)、找到bigkey(例如执行大的mget、hgetall、lrange等等)。

五、最后

希望自己有足够的时间学习和撰写文章,希望2021会更好。