Redis短连接性能优化

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云数据库 Tair(兼容Redis),内存型 2GB
简介: 对于Redis服务,通常我们推荐用户使用长连接来访问Redis,但是由于某些用户在连接池失效的时候还是会建立大量的短连接或者用户由于客户端限制还是只能使用短连接来访问Redis,而原生的Redis在频繁建立短连接的时候有一定性能损耗,本文从源码角度对Redis短连接的性能进行了优化。

对于Redis服务,通常我们推荐用户使用长连接来访问Redis,由于短连接每次需要新建链接所以短连接在tcp协议层面性能就比长连接低效,但是由于某些用户在连接池失效的时候还是会建立大量的短连接或者用户由于客户端限制还是只能使用短连接来访问Redis,而原生的Redis在频繁建立短连接的时候有一定性能缺陷,我们在云上就碰到用户短连接的性能问题。

1. 问题

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

2. 排查

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

bianque

从扁鹊我们可以看到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%以上。

4. 结束

云数据库Redis版(ApsaraDB for Redis)是一种稳定可靠、性能卓越、可弹性伸缩的数据库服务。基于飞天分布式系统和全SSD盘高性能存储,支持主备版和集群版两套高可用架构。提供了全套的容灾切换、故障迁移、在线扩容、性能优化的数据库解决方案。欢迎各位购买使用:云数据库 Redis 版

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3月前
|
存储 监控 NoSQL
揭秘Redis慢查询:这个工具将彻底改变你的性能优化策略!
【8月更文挑战第8天】在互联网应用中,数据库性能常成瓶颈。Redis作为高速内存数据库亦可能遭遇慢查询问题。本文探讨慢查询成因与解决方法。首先定义慢查询及其影响因素,随后介绍Redis内置的慢查询日志功能,通过配置`slowlog-log-slower-than`与`slowlog-max-len`来监控执行时间过长的命令。利用`SLOWLOG get`命令分析日志,定位性能瓶颈所在。以`LRANGE`命令为例,提出数据结构调整、使用流水线、限制返回元素数量、异步执行及优化内存使用等策略。最终实现Redis性能提升,确保应用流畅运行。性能优化需持续监控、分析与调整。
101 1
|
4月前
|
监控 NoSQL Redis
Redis性能优化问题之lazyfree_pending_objects 这个指标有什么作用
Redis性能优化问题之lazyfree_pending_objects 这个指标有什么作用
|
4月前
|
监控 NoSQL Redis
Redis性能优化问题之什么是 Swap,为什么使用 Swap 会导致 Redis 性能下降,如何解决
Redis性能优化问题之什么是 Swap,为什么使用 Swap 会导致 Redis 性能下降,如何解决
|
4月前
|
NoSQL Redis 开发工具
Redis性能优化问题之检查 Redis 实例是否启用了透明大页机制,如何解决
Redis性能优化问题之检查 Redis 实例是否启用了透明大页机制,如何解决
|
4月前
|
NoSQL Linux Redis
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
|
4月前
|
NoSQL Redis
Redis性能优化问题之根据 Redis 的 AOF 配置级别优化性能,如何解决
Redis性能优化问题之根据 Redis 的 AOF 配置级别优化性能,如何解决
|
4月前
|
存储 缓存 NoSQL
Redis性能优化问题之优化 Redis fork 耗时严重的问题,如何解决
Redis性能优化问题之优化 Redis fork 耗时严重的问题,如何解决
|
4月前
|
NoSQL Redis
Redis性能优化问题之为什么配置为 appendfsync everysec 的 AOF 也可能导致 Redis 延迟变大
Redis性能优化问题之为什么配置为 appendfsync everysec 的 AOF 也可能导致 Redis 延迟变大
|
4月前
|
NoSQL 网络协议 Redis
Redis性能优化问题之如何判断 Redis 性能下降是否由复杂度过高的命令引起
Redis性能优化问题之如何判断 Redis 性能下降是否由复杂度过高的命令引起
|
4月前
|
NoSQL 固态存储 Redis
Redis性能优化问题之如果磁盘资源被其他应用程序占用导致 Redis 性能下降,应该如何解决
Redis性能优化问题之如果磁盘资源被其他应用程序占用导致 Redis 性能下降,应该如何解决

相关产品

  • 云数据库 Tair(兼容 Redis)