我的成长磨练,每天写博客,年轻人,卷起袖子,来把手弄脏吧! ------ 博客首页

Redis-4-问题解决

Redis-4-问题解决

1. redis连接数过多

1.0 现象

redis应用在高峰期大量连接失效,导致穿透至DB层,服务响应速度下滑。

1.1 分析

发现redis缓存层大量报出”ERR max number of clients reached“。

首先确定是连接层问题,查询监控redis连接数量突然大量增加,redis拒绝连接导致。

cluster现在是5主5从,每个节点的连接数已经达到4K,为什么连接不上去了,因为redis的单节点连接数量是可以破万的,是不是调整最大连接数就能先撑过去了?

查询redis的现有连接数

127.0.0.1:8381> info clients
# Clients
connected_clients:3808
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0

查询现在的相关配置

# 最大可连接的客户端数量
127.0.0.1:8381> config get maxclients
1) "maxclients"
2) "4064"
# Redis 内部有一个定时任务,会定时检测所有 client 的空闲时间是否超过配置的 timeout 值。
127.0.0.1:8381> config get timeout
1) "timeout"
2) "30"
# 如果 Redis 没有开启 tcp-keepalive 的话,服务端直到配置的 timeout 时间后,才会清理释放这个 client fd。
127.0.0.1:8381> config get tcp-keepalive
1) "tcp-keepalive"
2) "180"

查看spring-data-redis配置文件(这里配置有问题, 下文详述)

spring.redis.lettuce.pool.maxIdle: 200
spring.redis.lettuce.pool.minIdle: 10
spring.redis.lettuce.pool.maxActive: 200
spring.redis.lettuce.pool.maxWait: 300
spring.redis.timeout: 200

1.2 解决

首先第一点,应用服务的redis配置存在问题,spring-data-redis在2.X版本后默认使用lettuce作为连接器,底层由netty构成,而lettuce的连接池配置最大连接数最优解应该是CPU的核数的2倍,并非越大越好,连接数过大可能造成CPU上下文切换频繁(压测参考资料)。
其次第二点,应用服务器的配置中缺少了

  • timeBetweenEvictionRuns(默认不开启,不会清理空闲连接,minIdle/maxIdle配置了也不生效)

为了减少spring端重启的风险,这里标注出问题由业务端修改,优先使用redis server的处理解决这个问题,毕竟4千个连接还是有很大空间操作的。

# 修改redis的最大连接数配置
127.0.0.1:8381> config set maxclients 9000
OK

如果到这里可以解决当前问题是最好的,不过还可能碰到这种情况。

127.0.0.1:8381> config set maxclients 10000
(error) ERR The operating system is not able to handle the specified number of clients, try with 4064

这说明当前的机器对于redis进程可以操作的文件上限做了要求,最多可以有4064个连接被创建出来,还需要对机器的配置做些修改。

# 查询当前的redis进程号
ps -ef |grep redis |grep 8381
# 比如获取的pid为 2617
# 查看当前redis进程的相关参数配置
cat /proc/2617/limits
# 在返回的结果中Max open files配置项目是不是很熟悉,正是4064
# 那么下面我们就修改这个限制就行了,可以都尝试下。不同的Linux系统似乎操作不同
# centos Linux version 3.10.0-693.11.6.el7.x86_64
prlimit --nofile=10000:10000 --pid 2617
# centos Linux version 2.6.32-573.7.1.el6.x86_64
echo -n 'Max open files=10000:10000' > /proc/2617/limits

# 修改完成后,查看下配置是否变化,变化即可继续执行config set了
cat /proc/2617/limits

这时可以修改redis配置了,修改完成后,监控显示单节点连接数达到了7千左右。spring客户端不再报错。

2. Redis主从同步导致假死

2.0 现象

客户端在redis读取数据时报错Uncaught RedisException: Redis is LOADING the dataset。
这块业务的redis架构是3主3从,通过对主库的info信息查看,发现从库有时直接显示down.而在服务器的表象上,主从链接也会每隔1分钟左右就断开一次,导致需要重新同步,主库上看到每隔一分钟主库就会进行一次bgsave操作。主机的CPU开始持续高位。

2.1 分析

查看主节点日志如下

26783:M 21 Aug 17:21:22.134 # Client id=336736897 addr=10.4.19.100:35697 fd=824 name= age=5901 idle=5897 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=12741 oll=5135 omem=76378662 events=r cmd=psync scheduled to be closed ASAP for overcoming of output buffer limits.
26783:M 21 Aug 17:21:22.234 # Connection with slave 10.4.19.100:8554 lost.
26783:M 21 Aug 17:22:22.847 * Slave 10.4.19.100:8554 asks for synchronization
26783:M 21 Aug 17:22:22.847 * Full resync requested by slave 10.4.19.100:8554
26783:M 21 Aug 17:22:22.847 * Can't attach the slave to the current BGSAVE. Waiting for next BGSAVE for SYNC
26783:M 22 Aug 02:31:10.293 * Slave 10.4.19.100:8554 asks for synchronization
26783:M 22 Aug 02:31:10.293 * Unable to partial resync with slave 10.4.19.100:8554 for lack of backlog (Slave request was: 1502092728243).
26783:M 22 Aug 02:31:10.293 * Can't attach the slave to the current BGSAVE. Waiting for next BGSAVE for SYNC

这里就涉及了Redis的主从同步策略以及流程

  1. 主从间建立连接
    1. 步骤1:设置master的地址和端口,保存master信息
    2. 步骤2:建立socket连接
    3. 步骤3:发送ping命令(定时器任务)
    4. 步骤4:身份验证
    5. 步骤5:发送slave端口信息
  2. 数据同步阶段工作流程
    1. 步骤1:请求同步数据(slave向master发送psync命令)
    2. 步骤2:创建RDB同步数据(master执行bgsave创建RDB文件)
    3. 步骤3:恢复RDB同步数据(master传送至slave进行加载)
    4. 步骤4:请求部分同步数据(slave通知master已经加载rdb完毕,发送runId和offset至master)
    5. 步骤5:恢复部分同步数据(master校验runId和offset并将最新的offset发送回slave,slave使用bgrewwriteof命令将部分未同步命令进行加载)

从这个过程中我们知道了,集群间节点因为网络闪断或者其他异常状态导致,主从间同步不一致,导致主从开启了全同步

  1. 在同步过程中,全同步因为内存产生的RDB文件过大或者网络状态导致速度缓慢,导致同步部分命令时的加载量过大,导致超出了缓冲区大小,从此主从间开始频繁psync导致该问题。
  2. 或者同步过程中因为原有的配置过小,导致在指定时间段内无法完成psync也会出现该问题。
  3. 另外还需要考虑 cluster-node-timeout 设置过短,慢查询导致了集群判断节点失联,导致出现该问题

日志中显示错误: scheduled to be closed ASAP for overcoming of output buffer limits.此项报错与redis配置文件中的client-output-buffer-limit slave项配置有关系。

2.2 解决

Redis原文文档

Redis原文文档

# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
#
# A client is immediately disconnected once the hard limit is reached, or if
# the soft limit is reached and remains reached for the specified number of
# seconds (continuously).
# So for instance if the hard limit is 32 megabytes and the soft limit is
# 16 megabytes / 10 seconds, the client will get disconnected immediately
# if the size of the output buffers reach 32 megabytes, but will also get
# disconnected if the client reaches 16 megabytes and continuously overcomes
# the limit for 10 seconds.
#
# By default normal clients are not limited because they don't receive data
# without asking (in a push way), but just after a request, so only
# asynchronous clients may create a scenario where data is requested faster
# than it can read.
#
# Instead there is a default limit for pubsub and slave clients, since
# subscribers and slaves receive data in a push fashion.
#
# Both the hard or the soft limit can be disabled by setting them to zero.

# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
# 普通的客户端。默认limit 是0,也就是不限制。
# client-output-buffer-limit normal 0 0 0
client-output-buffer-limit normal 0 0 0
# 从库的复制客户端。默认hard limit 256M,soft limit 64M/60s。
client-output-buffer-limit slave 256mb 64mb 60
# 发布与订阅的客户端的。默认hard limit 32M,soft limit 8M/60s。
client-output-buffer-limit pubsub 32mb 8mb 60

hard limit: 缓冲区大小的硬性限制。
soft limit: 缓冲去大小的软性限制。
soft seconds: 缓冲区大小达到了(超过)soft limit值的持续时间。
client-output-buffer-limit参数限制缓冲区的大小,防止内存无节制的分配,Redis将会做如下自我保护:
client buffer的大小达到了soft limit并持续了soft seconds时间,将立即断开和客户端的连接
client buffer的大小达到了hard limit,server也会立即断开和客户端的连接

对集群内的节点执行以下命令

# 设置 cluster-node-timeout (设置集群节点超时时间,如果超过了指定的超时时间后仍不可达,则节点被认为是失败状态,单位为毫秒)
config set cluster-node-timeout 15000
# 设置缓冲区
set "client-output-buffer-limit slave 0 0 0 
# 将改动保存到redis配置文件
config rewrite

采取措施:
1.单实例的切割到8G以下,否则发生主从切换会耗时很长
2.调整client-output-buffer-limit参数,防止同步进行到一半失败
3.调整cluster-node-timeout,不能少于15s
4.禁止任何耗时超过cluster-node-timeout的慢查询,因为会导致主从切换
5.修复客户端类似SYN攻击的疯狂连接方式

2.3 参考解决

主从复制流程全解析
Redis复制缓冲区案例
主从复制案例
主从场景分析

3. Redis缩容或移除节点失败导致的持续handshake

3.0 现象

Redis客户端 发现redis缓存层大量报出”ERR max number of clients reached“。

3.1 分析

Redis客户端数量达到连接上限,看起来像是1中提到的问题,但是实际发现不是,客户端访问速度极慢。

3.2 解决

在redis-cli所在目录下创建文件 cluster.nodes,并且放入集群节点列表。
同目录下创建文件 clusterForgetFix.sh , 写入以下脚本。
执行脚本 sh clusterForgetFix.sh 即可

#!/bin/bash
for i in `cat cluster.nodes`
do
  host=`echo $i | awk -F ':' '{print $1}'`
  port=`echo $i | awk -F ':' '{print $2}'`
  # 获取指定单节点的node id。主要用于集群节点过多时,forget多个节点时,花费时间过长,导致已被删除的节点又从其他节点同步过来,导致forget失败
  # del_nodeids=$(redis-cli -h $host -p $port cluster nodes|grep -E 'handshake|fail'| grep '127.0.0.1:6379' | awk '{print $1}')
  del_nodeids=$(./redis-cli -h $host -p $port cluster nodes|grep -E 'handshake|fail' | awk '{print $1}')
  if [ -n "$del_nodeids" ];then
    for nodeid in ${del_nodeids[@]}; do
        echo $host $port $nodeid
        ./redis-cli -h $host -p $port cluster forget $nodeid
    done
  fi
done

4. Redis出现警报 "当前内存碎片率为 0.95,理想值为1至1.4之间"

4.0 现象

出现报警 ”当前内存碎片率为 0.95,理想值为1至1.4之间“

4.1 分析

Redis的内存存储出现问题。

  • mem_fragmentation_ratio < 1 :表示Redis内存分配超出了物理内存,操作系统正在进行内存交换,内存交换会引起非常明显的响应延迟;
  • mem_fragmentation_ratio > 1 - 1.4 :是合理的;
  • mem_fragmentation_ratio > 1.5 :说明Redis消耗了实际需要物理内存的150%以上,其中50%是内存碎片率,可能是操作系统或Redis实例中内存管理变差的表现

4.2 解决

限制内存交换: 如果内存碎片率低于1,Redis实例可能会把部分数据交换到硬盘上,应该增加可用物理内存或减少实Redis内存占用,设置maxmemory和回收策略可以避免强制内存交换。

重启Redis服务器:如果内存碎片率超过1.5,重启Redis服务器可以让额外产生的内存碎片失效并重新作为新内存来使用,使操作系统恢复高效的内存管理。额外碎片的产生是由于Redis释放了内存块,但内存分配器并没有返回内存给操作系统。

内存碎片清理:Redis 4.0-RC3 以上版本,使用jemalloc作为内存分配器(默认的) 支持内存碎片清理

支持在运行期进行自动内存碎片清理
设置自动清理 config set activedefrag yes,使用config rewrite 将redis内存中新配置刷新到配置文件
支持通过命令 memory purge 进行手动清理(与自动清理区域不同)

5. 购物车系统迁移架构

5.0 现象

购物车系统因为Redis集群机器过保需要将原有的Redis服务由Twproxy架构迁移为RedisCluster架构

5.1 分析

操作过程中需要注意数据的迁移,客户端的切换问题、存储结构的变化。
迁移工具借助了Redis-Shake进行数据迁移。但是在迁移完成后,发现数据的访问变得缓慢,经过查询是因为购物车涉及了大量的mget和mset操作,twproxy虽然也是对Key进行hash进行数据的分片,但是客户端对于mget和mset等操作做了对应的优化,而直接迁移到RedisCluster上,Spring-data-redis并没有对这部分操作进行额外的适配,造成顺序执行,请求变慢。另外使用的spring-data-redis版本过低,仍然使用Jedis作为底层连接器,使用短连接的方式对Redis发起请求,消耗过大。

5.2 解决

  1. 升级Spring-data-redis 或者使用自研的Lettuce连接器,并使用Lettuce作为连接器,使用长连接的方式对Redis发起服务,这样不仅降低了客户端的连接数,也而且因为对长连接的复用,请求效率也得到了一定提升。
  2. 使用自研的基于Lettuce的连接器,在进行批量操作的时候对涉及的KEY进行Hash分组,大大降低了请求访问的频率。

posted on 2023-03-28 19:52  SethMessenger  阅读(227)  评论(0)    收藏  举报

导航