redis线上问题排查解决

1  原文链接:http://carlosfu.iteye.com/blog/2254154

美团网的DBA负责人侯军伟给大家介绍了美团网在redis上踩得一些坑,讲的都是干货和坑。

    分为5个部分:

   一、周期性出现connect timeout

   二、redis bgrewriteaof问题

   三、redis内存占用飙升

   四、redis内存使用优化 

   五、redis cluster遇到的一些问题 

  附赠PPT: 

 (1) 本次:美团在Redis上踩过的一些坑PPT

 (2) 以往:《Redis在新浪的大规模运维经验》-演讲人:侯军伟新浪高级DBA.pdf 

                    美团数据库运维平台介绍.pdf

有关Redis-Cluster的详细介绍有很多这里就不多说了,可以参考:

1. redis-cluster研究和使用

2. Redis Cluster 3.0.5集群实践

3. 本博客的一些Redis-Cluster的介绍(未更新完毕)

4. Redis设计与实现那本书(作者:黄建宏):非常的推荐看这本书。

新书《Redis开发与运维》近期已经截稿。

2  美团在Redis上踩过的一些坑-3.redis内存占用飙升

http://carlosfu.iteye.com/blog/2254571

client_longes_output_list 

3 Redis客户端查询缓冲区和输出缓冲区

https://github.com/ZhuoRoger/blog

每个Redis客户端(以下简称”Client”)都有多个状态属性,而理解和分析这些属性,对于我们设计Redis键空间和运营管理都有帮助。
本文将详细分析Client的两个重要属性:Query buffer(输入缓冲区)、Output buffers(输出缓冲区)


Redis Client属性一览

使用redis client命令可查看当前Redis实例的所有客户端;每行数据对应一个客户端。

 

1

2

3

 

127.0.0.1:6390> client list

id=2 addr=127.0.0.1:53184 fd=8 name= age=33 idle=24 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client

id=3 addr=127.0.0.1:53190 fd=7 name= age=2 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client

以上为两个客户端,每个包含18个字段属性;其中属性的基本含义此处简单说明,后文会对重启指标深入分析。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

 

id:客户端唯一标识, 每新创建一个连接就自增1;redis重启后重置1。

addr: 客户端源ip:port;用于分析异常的客户端,定位是由哪个服务器哪个进程引起的; 如id=2的客户端 netstat -anp | grep 53184

fd: socket的文件描述符;数值同lsof的FD字段相同

name: 客户端的名字,默认不会设置,一般用处不大。可手动执行[clientsetname](http://redis.io/commands/client-setname)

age: 客户端存活的秒数

idle: 空闲的秒数;用于回收客户端和分析大量连接时有用

flages:客户端类型的标志, 共13种,常用的几种:N(普通客户端),M(master),S(slave),O(执行monitor)

db:客户端当前使用的database序号

sub/psub: 快订阅的频道/模式数

multi:当前事务中已执行命令个数

qbuf: query buffer的字节数 重要

qbuf-free: query buffer的剩余字节数

obl:定长Output buffer的使用字节数

oll:可变大小output buffer的对象个数

omem:可变大小output buffer的内存使用字节数 重要

events: 文件描述符事作件(r/w)

cmd:客户端最近一次执行的命令,不包含参数

Redis Client Query Buffer

每个Client都有一个query buffer(查询缓存区或输入缓存区), 它用于保存客户端的发送命令,redis server从query buffer获取命令并执行。

query buffer size

每个客户端query buffer自动动态调整使用内存大小的,范围在0~1GB之间;当某个客户端的query buffer使用超过1GB, server会立即关闭它,为避免过度使用内存,触发oom killer。
很遗憾query buffer的大小限制是硬编码的1GB,没法控制配置参数修改。

 

1

2

3

 

server.h#163

/* Protocol and I/O related defines */

#define PROTO_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */

如果程序的Key设计不合理,客户端使用大量的query buffer,这会导致redis server比较危险,很容易达到maxmeory限制,导致缓存数据被清空、数据无法写入和oom.

query buffer不受maxmeory限制

模拟100个客户端,连续写入大小为500MB(生产建议小于1KB)的Key; redis server设置maxmemory为4gb,但redis实际已用内存43gb(见used_memory)。
结论是query buffer使用内存不受maxmemory的限制,这BUG已经提给官方, 如不能限制redis使用的内存量,
很易导致redis过度使用内存,无法控制出现oom.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

 

127.0.0.1:6390> info memory

# Memory

used_memory:46979129016

used_memory_human:43.75G

used_memory_rss:49898303488

used_memory_rss_human:46.47G

used_memory_peak:54796105584

used_memory_peak_human:51.03G

total_system_memory:134911881216

total_system_memory_human:125.65G

maxmemory:4294967296

maxmemory_human:4.00G

maxmemory_policy:allkeys-random

mem_fragmentation_ratio:1.06

mem_allocator:jemalloc-4.0.3

## 当client断开后,rss会马上释放内存给OS

query buffer占用内存,会计入maxmemory, 如果达到maxmemory限制,会触发KEY的LRU淘汰或无法写入新数据。

 

1

2

 

127.0.0.1:6390> set a b

(error) OOM command not allowed when used memory > 'maxmemory'.

query buffer使用查看

如前文介绍,用client list命令,观察qbuf和qbuf-free两个字段,就是client query buffer使用内存大小。
如下示例(省去部分字段)

 

1

2

3

4

5

 

27.0.0.1:6390> client list

id=169 qbuf=128679888 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL

id=171 qbuf=128679888 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL

id=218 qbuf=128679888 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL

id=151 qbuf=128696272 qbuf-free=425984 obl=0 oll=0 omem=0 events=r cmd=NULL

避免query buffer过度使用

  • 禁用大KEY,尽量保证key小于1KB; 虽redis支持512MB大小string。
  • 监控redis内存使用,如果忽高忽低,极有可能query buffer引起
  • 核心Redis集群定期收集client list并分析qbuf的使用量
  • 建议官方提供query buffer size的设置参数,以保证过载保护

Client Output buffer

客户端输出缓存区:执行命令所返回的结果会保存到output buffer,返回给客户端。
每个客户端都有2个query buffer:

  • 静态定长16KB的缓存区;主要快速存储返回比较小的结果;如简单的get等
  • 动态大小缓冲区;存储返回较大的结果,如大的集合类型:set/list/hash
    因为静态的buffer,一般无性能和风险影响,这里简单介绍。
     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

     

    #define PROTO_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */

    /* With multiplexing we need to take per-client state.

    * Clients are taken in a linked list. */

    typedef struct client {

    uint64_t id; /* Client incremental unique ID. */

    redisDb *db; /* Pointer to currently SELECTed DB. */

    robj *name; /* As set by CLIENT SETNAME. */

    sds querybuf; /* Buffer we use to accumulate client queries. */

    list *reply; /* List of reply objects to send to the client. */

    /* Response buffer */

    int bufpos;

    char buf[PROTO_REPLY_CHUNK_BYTES];

    } client;

我们常说的output buffer都是指“动态大小的输出缓冲区”。

output buffer大小限制

和qeury buffer不同,output buffer提供配置参数”client-output-buffer-limit”设置buffer的使用大小。
下面是limit的设置格式

 

1

2

3

 

client-output-buffer-limit normal 10mb 5mb 60

client-output-buffer-limit slave 256mb 64mb 60

client-output-buffer-limit pubsub 32mb 8mb 60

redis对3种不同客户端类型,可设置对应的buffer limit规则

  • normal: 普通的客户端
  • slave: 从库复制,连接到主库的客户端
  • pubsub: 发布/订阅客户端

设置的limit规则3个值: hard limit size, soft limit size, soft limit second;
只要客户端使用output buffer内存大小超过hard limit限制,redis会立即关闭此客户端;
使用buffer内存大小超过soft limit,并且持续soft limit秒数,redis也会立即关闭此客户端。
被关闭客户端信息会打印到redis日志文件中,格式如下:

 

1

2

3

 

569:M 18 Jun 21:12:57.775 # Client id=972 addr=127.0.0.1:57934 fd=107 name= age=2 idle=0

flags=O db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=366 omem=10492208 events=rw cmd=monitor

scheduled to be closed ASAP for overcoming of output buffer limits.

查看output buffer使用

主要查开client list的obl(静态定长buffer)
omem: 当前客户端使用output buffer的内存字节数
如下客户端执行monitor命令(cmd=monitor), 已使用buffer内存是10492208,超过normal的hard limit 10mb
所以被redis关闭。

 

1

 

id=972 addr=127.0.0.1:57934 idle=0 flags=O db=0 qbuf=0 qbuf-free=0 obl=0 oll=366 omem=10492208 events=rw cmd=monitor

另外output buffer受maxmemory的限制,基本不会超过maxmemory设置值

合理使用output buffer

因为output buffer是每个客户端都有,如使用不当,每个占用1mb * 10000 clients就约使用10G内存;
所以要有效限制程序滥用。

  • 对于normal限制尽量小,可避免程序过度使用output buffer.
  • 监控redis used_memory如果抖动严重,极有可能
  • 增加slave的limit限制,避免slave同步线程被杀,导致无限循环同步数据;且slave线程和挂载的slave个数相同,理论只有几个
  • 禁止生产环境使用monitor命令,在高QPS环境下,monitor很快会产生output query使用

如何监控output buffer和query buffer

从前文可见, 如果业务使用redis不当,两个buffer有可能导致内存爆涨,redis缓存数据被全部淘汰,甚于出现oom.
那么怎么监测两个buffer的使用情况,提前发现系统的异常行为,并告警就显得很重要。
这里提供两种不同监控采集方法:

  • 通过采集client list输出,并分别统计求各所有客户端的(qbuf+ qbuf-free) 和 omem
  • 使用info的clients section中的client_biggest_input_buf和client_longest_output_list两个指标来监控告警

第一种方法可精确统计当前时刻(buffer完全动态分配回收), redis使用的buffer内存容量;但要使用client list命令周期性统计,对于连接数较大redis实例,会导致数十毫秒卡顿(基准测试1w空连接,client list命令耗时约14.5ms);
因为至少每隔几分钟要采集一次,在高并发实例下,这样耗时是不能被接受的,这就是常用的观察者效应
open-falcon的redis监控插件redismon, 我们用第二个方法,通过info采集;
两个指标表示的含义:

  • client_biggest_input_buf:当前实例所有客户端中,最大query buffer内存的字节数。 告警阈值建议10485760(10M),根据业务再调整。
  • client_longest_output_list:当前实例所有客户端中, 最长output buffer的个数。告警阈值建议500长度(前文例子中monitor客户端长度是336,output buffer约10M),不过常用keys,monitor,或复制sync过程,会触发告警。

两个指标只能反映,其中使用buffer最厉害那个客户端的使用的内存量;不能直接反映所有客户端使用两个buffer内存消耗。
但合理设置告警值,也能直接监测试redis系统用于buffer内存有异常,并跟踪定位异常导致的点;
因第二种方法,每分钟监控采集一次对系统无影响;虽没前者直观,也能定位发现问题了,觉得这就是一个tradeoff点。

Redis两个客户端的Buffer就简单介绍这些。后续文章会讲redis的监控和buffer相样的故障问题。

更多:https://github.com/ZhuoRoger/blog/tree/master/2016

猜你喜欢

转载自blog.csdn.net/wabiaozia/article/details/81073625