Redis源码分析(二十一)——慢查日志slow_log

slow_log是Redis提供的用于观察系统性能的简单功能。慢查日志对应服务器中的慢查日志链表,将中执行时间超过设定最大时间slog_log_slower_then的命令放到该链表中。 

考虑到效率问题,慢查日志是放在内存而非日志文件中的;另一方面,为了节约内存空间,慢查日志记录的命令数量内增加到设定值后,每加入一条新的命令记录,链表中最旧的命令记录将被删除,同时在记录命令的参数时对那些参数过多或者参数过长的命令参数也作了相应的截断处理。


服务器结构中的慢查日志状态量:

struct redisServer {
 //**********************
  list *slowlog;                  /* 记录日志的链表 SLOWLOG list of commands */
  long long slowlog_entry_id;/* 命令记录节点ID,同时也用于命令记录数量计数SLOWLOG current entry ID */
  long long slowlog_log_slower_than; /* 设定的命令执行时间值SLOWLOG time limit (to get logged) */
  unsigned long slowlog_max_len;     /* 设定的日志命令记录数量SLOWLOG max number of items logged */
 //*********************
	}

命令记录结构:

/* This structure defines an entry inside the slow log list */
typedef struct slowlogEntry {
    robj **argv;   //命令参数
    int argc;     //命令参数数量
    long long id;   命令ID    /* Unique entry identifier. */
    long long duration; //命令执行时间/* Time spent by the query, in nanoseconds. */
    time_t time;       //命令开始时间 /* Unix time at which the query was executed. */
} slowlogEntry;

创建一条命令记录: 注意对参数过多或参数过长的处理,以节约内存空间。

slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
    slowlogEntry *se = zmalloc(sizeof(*se));
    int j, slargc = argc;

    // 如果参数过多,那么只记录服务器允许的最大参数数量
    if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;

    // 记录参数数量
    se->argc = slargc;

    // 遍历并记录命令的参数
    se->argv = zmalloc(sizeof(robj*)*slargc);
    for (j = 0; j < slargc; j++) {
        /* Logging too many arguments is a useless memory waste, so we stop
         * at SLOWLOG_ENTRY_MAX_ARGC, but use the last argument to specify
         * how many remaining arguments there were in the original command. */
        // 当参数的数量超过服务器允许的最大参数数量时,
        // 用最后一个参数记录省略提示
        if (slargc != argc && j == slargc-1) {
            se->argv[j] = createObject(REDIS_STRING,
                sdscatprintf(sdsempty(),"... (%d more arguments)",
                argc-slargc+1));
        } else {
            /* Trim too long strings as well... */
            // 如果参数太长,那么进行截断
            if (argv[j]->type == REDIS_STRING &&
                sdsEncodedObject(argv[j]) &&
                sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
            {
                sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);

                s = sdscatprintf(s,"... (%lu more bytes)",
                    (unsigned long)
                    sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);

                se->argv[j] = createObject(REDIS_STRING,s);
            } else {
                se->argv[j] = argv[j];
                incrRefCount(argv[j]);
            }
        }
    }

    // 命令的执行时间
    se->time = time(NULL);

    // 执行命令耗费的时间
    se->duration = duration;

    // 设置慢查询 id
    se->id = server.slowlog_entry_id++;

    return se;
}


慢查日志的记录:

每次命令执行前,Redis有一个参数记录了命令开始执行的时间,当命令执行完毕时,计算当前时间,减去开始时间得到命令的执行时间duration,将duration传入函数slowlogpushEntryIfNeed函数,判断,如果执行时间大等于设定的slog_log_slower_then值,则创建命令节点,追加到慢查日志链表的头部,如果链表长度达到设定值,则从链表尾部(最旧)删除最旧命令记录。

/* Push a new entry into the slow log.
 *
 * 如果参数 duration 超过服务器设置的上限时间,
 * 那么将一个新条目以 FIFO 顺序推入到慢查询日志中。
 *
 * This function will make sure to trim the slow log accordingly to the
 * configured max length. 
 *
 * 根据服务器设置的最大日志长度,可能会对日志进行截断(trim)
 */
void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) {

    // 慢查询功能未开启,直接返回
    if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */

    // 如果执行时间超过服务器设置的上限,那么将命令添加到慢查询日志
    if (duration >= server.slowlog_log_slower_than)
        // 新日志添加到链表表头
        listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration));

    /* Remove old entries if needed. */
    // 如果日志数量过多,那么进行删除
    while (listLength(server.slowlog) > server.slowlog_max_len)
        listDelNode(server.slowlog,listLast(server.slowlog));
}



SLOWLOG 命令的实现: 支持 GET / RESET 和 LEN 参数

void slowlogCommand(redisClient *c) {

    // 重置
    if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {
        slowlogReset();
        addReply(c,shared.ok);

    // 返回长度
    } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {
        addReplyLongLong(c,listLength(server.slowlog));

    // 获取某条或者全部日志
    } else if ((c->argc == 2 || c->argc == 3) &&
               !strcasecmp(c->argv[1]->ptr,"get"))
    {
        long count = 10, sent = 0;
        listIter li;
        void *totentries;
        listNode *ln;
        slowlogEntry *se;

        if (c->argc == 3 &&
            getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != REDIS_OK)
            return;

        // 遍历日志,取出指定数量的日志
        listRewind(server.slowlog,&li);
        totentries = addDeferredMultiBulkLength(c);
        while(count-- && (ln = listNext(&li))) {
            int j;

            se = ln->value;
            addReplyMultiBulkLen(c,4);
            addReplyLongLong(c,se->id);
            addReplyLongLong(c,se->time);
            addReplyLongLong(c,se->duration);
            addReplyMultiBulkLen(c,se->argc);
            for (j = 0; j < se->argc; j++)
                addReplyBulk(c,se->argv[j]);
            sent++;
        }
        setDeferredMultiBulkLength(c,totentries,sent);
    } else {
        addReplyError(c,
            "Unknown SLOWLOG subcommand or wrong # of args. Try GET, RESET, LEN.");
    }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值