专栏名称: 阿里开发者
阿里巴巴官方技术号,关于阿里的技术创新均将呈现于此
目录
相关文章推荐
游戏茶馆  ·  严格执行!越南新规:上架App ... ·  10 小时前  
游戏茶馆  ·  苹果App ... ·  10 小时前  
法治进行时  ·  过年了打麻将,发的红包算赌博吗? ·  14 小时前  
51好读  ›  专栏  ›  阿里开发者

诡异!Redis Proxy RT上升后连接倾斜

阿里开发者  · 公众号  ·  · 2024-07-01 08:30

正文

阿里妹导读


本文细致地描述了关于Redis Proxy RT上升后连接倾斜问题的排查过程和根本原因,最后给出了优化方案。

问题背景

 

Redis 代理集群版流量模型如上图,客户端通过域名访问到 AliLB,这是一个 4 层的负载均衡,会把连接均匀地分发到后端的 proxy 上,理论上每个 proxy 上处理的客户端连接数应该相近。

如果 proxy 上出现负载不均,就可能出现一个 proxy 的 cpu 已经接近满的状态,但其他 proxy 还很空闲,用户的实际吞吐远低于集群的能力上限,但访问到高负载 proxy 的请求 RT 开始升高,导致业务受损。

导致 proxy 负载不均的原因通常有 2 类。

连接不均衡

  • 早期 AliLB 调度算法采用了 WRR,这是一种带权重的调度算法,当后端 proxy 在增加或减少时,由于算法本身的问题会出现连接调度不均,目前换为 RR 调度算法后,该问题不再出现。

  • 部分 proxy 重启。 RR 算法下 AliLB 是轮训调度,不会考虑后端 proxy 上的连接数,所以当部分 proxy 重启后,重启的 proxy 连接数会变 0,后续新建连接数和其他 proxy 相同,总连接数会低于其他 proxy。 但非主动重启的 proxy 占比较小,实际情况下还没有因为这种情况导致问题。

负载不均衡

  • 应用可能使用 pipeline 或异步的方式在一个连接上发送大量请求,这导致处理该连接的 proxy 负载很高。 该问题只能修改业务的访问代码来优化,将请求通过更多的连接分发来达到均衡。

 除了上述已知原因导致的不均衡外,还有一个困扰了 1-2 年的连接不均衡问题。

该问题现象如下,在某一时刻,因为一台机器故障导致该机器上部署的 proxy RT 变高,或者因为瞬时的流量峰值导致其中一个 proxy RT 变高,从故障时间点开始该 proxy 上的连接数就逐步上升,负载越来越高,导致 RT 也变得更高,呈现一个雪崩的状态。

问题分析

怀疑 AliLB 连接分配不均

proxy 是被动接受新连接,连接数多于其他 proxy 肯定是分配过来的新连接更多。所以该问题首先猜测是 AliLB 调度不均匀。

但根据原理判断问题 proxy 更有可能出现到 AliLB 的健康保活失败,理论上应该调度过来的新连接更少才对,这和现象相反, 拉 ALB 相关同学分析后台日志并没有连接调度不均的情况。当时 proxy 的监控信息没有建连总数,问题排查阻塞了,只能增加日志继续观察。

怀疑客户端连接泄露

后来问题第二次出现了,这次从 proxy 日志看到问题时间段每个 proxy 上新建连接数确实是相近的,那么连接数不均衡只能是一个原因,就是问题 proxy 上断连的数量变少了。

但问题时间段 proxy 没有主动断连,所有的断连请求都是客户端发起,这就非常奇怪,客户端所有的连接的目的端地址都是指向 AliLB 的域名,对于客户端而言每个连接没什么区别,它怎么会保留问题 proxy 的连接而断开其他的。

思来想去得出一个结论,可能是客户端访问 RT 高的 proxy 时出现了超时异常,代码没有处理好异常导致连接泄露,于是问题 proxy 上的连接就越来越多。

该逻辑能够解释通,后续和业务方一起通过压测尝试复现过该问题,通过统计日志能够看到具体的客户端 ip 和 qps,但实际场景非常复杂,业务方有多个应用使用不同的模型访问 Redis,统计日志中没有找到明显的连接数、流量上升的客户端 ip,也没有找到客户端上连接泄露的具体代码。

所以很长时间的结论是,AliLB 调度新建连接是均匀的,proxy 是被动地建立和释放连接,客户端对所有连接是一视同仁的,可能是业务代码哪里超时后没有释放连接。

问题复现

排查另一个问题时发现 Jedis、lettuce 等客户端连接池默认管理策略是 LIFO,之前一直认为 Jedis 连接池是轮训调度,在内部讨论以及和业务方交流时从来没人质疑过这一点。LIFO 的调度策略本身是不均匀的,基于该策略考虑构造一个场景来复现该问题。


测试环境

服务端为 4 个 proxy 的 Redis 集群版,其中一个 proxy 增加了 200ms 延时。

客户端使用 Jedis 连接池来访问 Redis。

流量模型为每个客户端进程每秒 100 get 请求。

每 10 秒一次流量峰值,每秒 150 get 请求。

同时启动 4 个客户端进程。


测试代码

<dependency>    <groupId>redis.clientsgroupId>    <artifactId>jedisartifactId>    <version>3.6.3version>dependency>
JedisPoolConfig config = new JedisPoolConfig();config.setMaxIdle(200);config.setMaxTotal(200);config.setMinEvictableIdleTimeMillis(5000);config.setTimeBetweenEvictionRunsMillis(1000);config.setTestOnBorrow(false);config.setTestOnReturn(false);config.setTestWhileIdle(false);config.setTestOnCreate(false);
JedisPool pool = new JedisPool(config, host, port, 10000, password);Semaphore sem = new Semaphore(0);for (int i = 0; i < 200; i++) { new Thread(new Runnable() { @Override public void run() { while (true) { Jedis jedis = null; try { sem.acquire(1); jedis = pool.getResource(); jedis.get("key"); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } } } }).start();}long last_peak_time = System.currentTimeMillis();while (true) { try { long cur = System.currentTimeMillis(); if (cur - last_peak_time > 10000) { last_peak_time = cur; sem.release(150); } else { sem.release(100); } Thread.sleep(1000); } catch (Exception e) { }}

测试结果

问题 proxy 的连接数和流量逐步上升。



正常 proxy 的连接数和流量在下降。

 

现象分析

因为 Jedis 连接池默认参数设置了 LIFO 为 True,该模式下后归还的连接会放在队列头,后续被更高频的使用。

当流量峰值时,会扩充连接池的大小,这些连接会随机建立到 4 个 proxy 上,但因为问题 proxy 的 RT 高,连接到问题 proxy 的连接会更晚归还到连接池中,导致后续请求会优先访问到问题 proxy。而那些 RT 更低的 proxy 的连接因为更早归还到连接池中,被放到了队列尾部,在低峰期不会被使用,因此连接空闲过段时间就被自动释放了。







请到「今天看啥」查看全文