Redis高级实践之————Redis短连接性能优化

摘要: 对于Redis服务,通常我们推荐用户使用长连接来访问Redis,但是由于某些用户在连接池失效的时候还是会建立大量的短连接或者用户由于客户端限制还是只能使用短连接来访问Redis,而原生的Redis在频繁建立短连接的时候有一定性能损耗,本文从源码角度对Redis短连接的性能进行了优化。

1. 问题

通过历史监控我们可以发现用户在频繁使用短连接的时候Redis的cpu使用率有显著的上升

2. 排查

通过扁鹊查看但是Redis的cpu运行情况如下

从扁鹊我们可以看到Redis在freeClient的时候会频繁调用listSearchKey,并且该函数占用了百分30左右的调用量,如果我们可以优化降低该调用,短连接性能将得到具体提升。

3. 优化

通过以上分析我们可以知道Redis在释放链接的时候频繁调用了listSearchKey,通过查看Redis关闭客户端源码如下:

void freeClient(redisClient *c) {
    listNode *ln;

    /* If this is marked as current client unset it */
    if (server.current_client == c) server.current_client = NULL;

    /* If it is our master that's beging disconnected we should make sure
     * to cache the state to try a partial resynchronization later.
     *
     * Note that before doing this we make sure that the client is not in
     * some unexpected state, by checking its flags. */
    if (server.master && c->flags & REDIS_MASTER) {
        redisLog(REDIS_WARNING,"Connection with master lost.");
        if (!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
                          REDIS_CLOSE_ASAP|
                          REDIS_BLOCKED| REDIS_UNBLOCKED)))
        {
            replicationCacheMaster(c);
            return;
        }
    }

    /* Log link disconnection with slave */
    if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR)) {
        redisLog(REDIS_WARNING,"Connection with slave %s lost.",
            replicationGetSlaveName(c));
    }

    /* Free the query buffer */
    sdsfree(c->querybuf);
    c->querybuf = NULL;

    /* Deallocate structures used to block on blocking ops. */
    if (c->flags & REDIS_BLOCKED)
        unblockClientWaitingData(c);
    dictRelease(c->bpop.keys);

    freeClientArgv(c);

    /* Remove from the list of clients */
    if (c->fd != -1) {
        ln = listSearchKey(server.clients,c);
        redisAssert(ln != NULL);
        listDelNode(server.clients,ln);
    }

    /* When client was just unblocked because of a blocking operation,
     * remove it from the list of unblocked clients. */
    if (c->flags & REDIS_UNBLOCKED) {
        ln = listSearchKey(server.unblocked_clients,c);
        redisAssert(ln != NULL);
        listDelNode(server.unblocked_clients,ln);
    }
    ...
    ...
    ...
    /* Release other dynamically allocated client structure fields,
     * and finally release the client structure itself. */
    if (c->name) decrRefCount(c->name);
    zfree(c->argv);
    freeClientMultiState(c);
    sdsfree(c->peerid);
    if (c->pause_event > 0) aeDeleteTimeEvent(server.el, c->pause_event);
    zfree(c);
}

从源码我们可以看到Redis在释放链接的时候遍历server.clients查找到对应的redisClient对象然后调用listDelNode把该redisClient对象从server.clients删除,代码如下:

/* Remove from the list of clients */
    if (c->fd != -1) {
        ln = listSearchKey(server.clients,c);
        redisAssert(ln != NULL);
        listDelNode(server.clients,ln);
    }

查看server.clients为List结构,而redis定义的List为双端链表,我们可以在createClient的时候将redisClient的指针地址保留再freeClient的时候直接删除对应的listNode即可,无需再次遍历server.clients,代码优化如下:

3.1 createClient修改

redisClient *createClient(int fd) {
    redisClient *c = zmalloc(sizeof(redisClient));

    /* passing -1 as fd it is possible to create a non connected client.
     * This is useful since all the Redis commands needs to be executed
     * in the context of a client. When commands are executed in other
     * contexts (for instance a Lua script) we need a non connected client. */
    if (fd != -1) {
        anetNonBlock(NULL,fd);
        anetEnableTcpNoDelay(NULL,fd);
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    }
     ...
    if (fd != -1) {
        c->client_list_node = listAddNodeTailReturnNode(server.clients,c);
    }
    return c;
}

3.2 freeClient修改

freeClient修改如下:

 /* Remove from the list of clients */
    if (c->fd != -1) {
        if (c->client_list_node != NULL) listDelNode(server.clients,c->client_list_node);
    }

3.3 优化结果

在同一台物理机上启动优化前后的Redis,分别进行压测,压测命令如下:

redis-benchmark -h host -p port -k 0 -t get -n 100000  -c 8000

其中-k 代表使用短连接进行测试

原生Redis-server结果:

99.74% <= 963 milliseconds
99.78% <= 964 milliseconds
99.84% <= 965 milliseconds
99.90% <= 966 milliseconds
99.92% <= 967 milliseconds
99.94% <= 968 milliseconds
99.99% <= 969 milliseconds
100.00% <= 969 milliseconds
10065.42 requests per second

优化后Redis-server结果

99.69% <= 422 milliseconds
99.72% <= 423 milliseconds
99.80% <= 424 milliseconds
99.82% <= 425 milliseconds
99.86% <= 426 milliseconds
99.89% <= 427 milliseconds
99.94% <= 428 milliseconds
99.96% <= 429 milliseconds
99.97% <= 430 milliseconds
100.00% <= 431 milliseconds
13823.61 requests per second

我们可以看到优化之后的Redis-server性能在短连接多的场景下提升了百分30%以上。

 

时间: 2025-01-28 08:04:35

Redis高级实践之————Redis短连接性能优化的相关文章

Redis短连接性能优化

对于Redis服务,通常我们推荐用户使用长连接来访问Redis,由于短连接每次需要新建链接所以短连接在tcp协议层面性能就比长连接低效,但是由于某些用户在连接池失效的时候还是会建立大量的短连接或者用户由于客户端限制还是只能使用短连接来访问Redis,而原生的Redis在频繁建立短连接的时候有一定性能缺陷,我们在云上就碰到用户短连接的性能问题. 1. 问题 通过历史监控我们可以发现用户在频繁使用短连接的时候Redis的cpu使用率有显著的上升 2. 排查 通过扁鹊查看但是Redis的cpu运行情况

提高短连接性能方法的测试

创建测试脚本 通过在三个会话中同时执行test_login.sh脚本,模拟当数据库多个短连接情况性能 [oracle@xifenfei tmp]$ more test_login.sh #!/bin/bash echo "start login database `date`*********" >>/tmp/test_1.log e=2000 for((i=1;i<=$e;i=i+1)) do /tmp/login_oracle.sh done echo "

《Oracle数据库性能优化方法论和最佳实践》——1.2 性能优化目标的确定和衡量

1.2 性能优化目标的确定和衡量 性能优化,顾名思义就是一个改善的过程,通过这个过程实现从当前A到B的目标,其中A和B必须可以被描述和衡量.其中,A为当前状态的描述和测量.B为需要达到目标状态的描述和测量.很遗憾的是,在现实的Oracle性能优化实践中,很多情况下A和B不可描述或未被准确描述,只知道我要变快,而且是尽可能的快,最终使性能优化变成一锅乱炖.大家都知道,性能优化的常规目标是使响应速度变快,但快和慢是相对而言的,如果两者没有一致的描述基准,那么就会产生问题.比如,如果觉得现在的系统速度

JavaScript 字符串连接性能优化_javascript技巧

复制代码 代码如下: var str = "hello"; str += " world"; 后台所做工作: 1)创建存储 "hello" 的字符串,且使 str 指向它. 2)创建存储 "world" 的字符串. 3)创建存储结果的字符串. 4)将 str 中的当前内容复制到结果字符串中. 5)把 world 复制到结果字符串中. 6)更新 str ,使 str 指向结果字符串. 每拼接一次字符串就循环重复2)~6),如果重

MongoDB · 最佳实践 · 短连接Auth性能优化

问题 通常我们使用MongoDB的时候,客户端(driver)和MongoDB之间都是使用长连接,但是在某些场景下.某些driver仍然只能使用短连接进行连接,比如PHP.就在我们阿里云数据库MongoDB版商业化后没多久,我们就遇到了一个用户短连接过多导致的性能问题. 这个问题的症状是MongoD的CPU使用率居高不下,16个核都跑满了,影响到了用户的正常使用. 排查 首先想到的当然是看看有没有很多慢查询,针对存在的慢查询都建议用户建了索引后,情况还是没有好转.这时我们观察到用户的driver

redis-关于Redis数据库的java代码多线程读写性能问题,希望可以帮忙看看、、、

问题描述 关于Redis数据库的java代码多线程读写性能问题,希望可以帮忙看看... 渣渣一枚..由于项目关系,需要用到内存数据库来存key-value所以就开始研究redis这个数据库.现在已经把redis放在了CentOS的测试服务器上,然后通过网上的资料写了链接代码! 测试用了100个线程,每个线程插入10000条数据,结果竟然花了**200s**, 同事用memcached测试同样的数据只要了**20s** 而在linux中用redis自带的redis-benchmark查询性能 结果

Redis · 最佳实践 · 阿里云Redis助力双11业务

双11如火如荼的结束了,阿里云Redis(ApsaraDB for Redis原KVStore)也圆满完成了双11Redis的保障工作.目前阿里云Redis提供了单机版本和集群版本的Redis. 单机版本Redis具有很高的兼容性,并且支持Lua脚本及地理位置计算. 集群版本具有大容量.高性能的特性,能够突破Redis单线程的单机性能极限. 阿里云Redis默认双机热备并提供了备份恢复支持,同时阿里云Redis源码团队持续对Redis进行优化升级,提供了强大的安全防护能力.本文将选取双11的一些

redis jedis jedispool 获取不到连接

问题描述 redis jedis jedispool 获取不到连接 每次都在第8次获取 jedis的时候 获取不到 package com.ryx.sync.util; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedi

MongoDB短连接Auth性能优化

通常我们使用MongoDB的时候,客户端(driver)和MongoDB之间都是使用长连接,但是在某些场景下.某些driver仍然只能使用短连接进行连接,比如PHP.就在我们阿里云数据库MongoDB版商业化后没多久,我们就遇到了一个用户短连接过多导致的性能问题. 问题 这个问题的症状是MongoD的CPU使用率居高不下,16个核都跑满了,影响到了用户的正常使用. 排查 首先想到的当然是看看有没有很多慢查询,针对存在的慢查询都建议用户建了索引后,情况还是没有好转.这时我们观察到用户的driver