对redis性能问题的总结

前言

编写背景:

xxx系统因redis内存溢出导致现网故障,结合之前部分项目也用到redis组件且存在或多或少的问题,但都没有形成针对性的有效的分析。考虑到使用redis组件的项目越来越多,需要对redis进行针对性的分析。

编写目的:

1、 方便相关人员了解redis的作用、使用方法,可以对redis的项目中的作用有个清晰的认识

2、 分析redis的运行机制,对redis的缓存过程有个大致认知

3、 罗列redis的优点与不足,对redis常遇到的问题进行细致分析

4、 提供性能测试过程中对redis的监控方法,及如何评估和设置redis相关参数

Redis概述

1. 什么是redis

Redis(REmote DIctionary Server,远程数据字典服务器)是开源的内存数据库,常用作缓存或者消息队列,由于该数据库是存在于系统内存中,因此具体内存占用高,执行速度快等特点

2. Redis的特点

a) Redis存在于内存,使用硬盘作为持久化;每秒十万读写,执行速度快且稳定。

b) 具有丰富的数据结构,字符串、哈希表、列表、集合、有序集合;提供交集、并集、差集等操作。

c) 设置TTL存活时间,到期自动删除。

d) Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。

e) Redis支持主从及集群。

3. Redis在当前Xxx项目中的作用

从“Redis的概述”中可以了解到redis的特点,进而对redis的作用有一定了解。这里在在对redis的作用进行总结,以便提供一个更加清晰的认识。

Redis是一种内存型的数据库,比mysql快的多,通常被用作缓存框架和消息处理队列,能够更快速的处理消息。

在XXX系统中,redis是这样被使用的:系统的业务数据仍然存在mysql数据库中,每当一个新用户登录系统进行查分时,会将数据从mysql数据库中取出并存入redis中,当这个用户第二次在进行查分时,会直接从redis中取数据,而不会在去查mysql数据库。因为数据已经被缓存至redis库中,从redis中读取直接走内存,比mysql更快,可以避免大量查询会导致mysql数据库出现异常。

Redis主要机制介绍及会带来的性能问题

这里介绍的主要机制为容易引起性能问题的机制

1. Redis持久化

Redis提供了两种持久化方式:1 RDB快照方式 2 AOF方式

1.1 RDB持久化介绍及性能
1.1.1 RDB持久化介绍

满足一定条件时,会创建一个子进程,复制当前的数据,把数据写入到硬盘中某个文件,写入完成后替换原来的存储文件,用二进制压缩。数据一般存储在dump.rdb中。UNIX系统中支持写时复制,即刚开始会执行持久化写入磁盘的操作,如果此时有其他的数据发生改变,就复制一份数据执行。

除了这种自动的快照方式,还支持命令方式持久化:

SAVE:通过阻塞的方式,用父进程来持久化,此时无法执行其他的请求。

BGSAVE:通过fork子进程的方式,持久化。

RDB方式的具体过程:

  1. redis调用fork,现在有了子进程和父进程。2. 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照。3. 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出
1.1.2 RDB持久化带来的性能问题

问题1

在保存快照时,Redis调用fork产生父进程和子进程,父进程基础处理client的请求,而子进程则负责将内存写入临时文件。内存消耗会变成原来的两倍(具体原因与子进程的执行内容有关,不在深究)。

问题2

每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

问题3

由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,这种方式实现的持久化有会有问题

1.1.3 RDB持久化性能问题解决思路

针对上述可能出现的性能问题,经过资料收集,总结可对应的处理方法如下:

问题一,两倍内存问题:

必须保留足够的内存。对内存的估算方法:对缓存数据进行评估,预估出最大的缓存数据容量,则给redis预留的缓存空间至少为最大缓存数据容量的两倍

问题二,大量磁盘IO操作:

对于数据量非常大的情况下,建议增加redis服务做主从或者集群,避免大量io导致服务器出现高负载的情况

问题三,最新数据丢失

该问题不属于性能问题。可以通过AOF持久化方式解决

1.2 AOF持久化介绍及性能
1.2.1 AOF持久化介绍

aof 持久化是redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录.

AOF方式的具体过程:

1.  redis调用fork ,现在有父子两个进程 2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 3. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。 4. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。 5. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

1.2.2 AOF持久化的性能问题

问题1

追加log文件可能导致体积过大,当系统重启恢复数据时如果是aof的方式则加载数据会非常慢,会导致这个期间Redis的读写性能会大幅下降

问题2

追加log的频率不同,对磁盘IO的影响也不同,在性能方面的体现也不一样。配置读写频率可以通过一个配置文件进行更改,需要结合服务器实际的内存、数据量等因素进行评估比较合理的方式

问题3

如果不断的将写指令记录到AOF的日志文件中,那么文件体积会越来越大,势必会产生很多冗余。

1.2.2 AOF持久化性能问题处理

针对问题1,大数据量下恢复系统,会导致数据加载缓慢

大数据量只能通过负载或者集群来处理。

针对问题2:如何合理的配置写文件的频率,具体参考下列配置说明

appendonly yes           #启用aof持久化方式

appendfsync always   #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用

appendfsync everysec     #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐

appendfsync no    #完全依赖os,性能最好,持久化没保证

针对问题3:需要触发AOF的rewrite操作,对AOF的文件进行重写,替换原来的文件

Redis触发AOF rewrite机制有三种:

1、Redis Server接收到客户端发送的BGREWRITEAOF指令请求,如果当前AOF/RDB数据持久化没有在执行,那么执行,反之,等当前AOF/RDB数据持久化结束后执行AOF rewrite

2、在Redis配置文件redis.conf中,用户设置了auto-aof-rewrite-percentage和auto-aof-rewrite-min-size参数,并且当前AOF文件大小server.aof_current_size大于auto-aof-rewrite-min-size(server.aof_rewrite_min_size),同时AOF文件大小的增长率大于auto-aof-rewrite-percentage(server.aof_rewrite_perc)时,会自动触发AOF rewrite

3、用户设置“config set appendonly yes”开启AOF的时,调用startAppendOnly函数会触发rewrite

1.3 Redis持久化总结

以下内容为网上原文,解释redis的内存占用:

Redis在物理内存使用比较多,但还没有超过实际物理内存总容量时就会发生不稳定甚至崩溃的问题,有人认为是基于快照方式持久化的fork系统调用造成内存占用加倍而导致的,这种观点是不准确的,因为fork 调用的copy-on-write机制是基于操作系统页这个单位的,也就是只有有写入的脏页会被复制,但是一般你的系统不会在短时间内所有的页都发生了写入而导致复制,那么是什么原因导致Redis崩溃的呢?

答案是Redis的持久化使用了Buffer IO造成的,所谓Buffer IO是指Redis对持久化文件的写入和读取操作都会使用物理内存的Page Cache,而大多数数据库系统会使用Direct IO来绕过这层Page Cache并自行维护一个数据的Cache,而当Redis的持久化文件过大(尤其是快照文件),并对其进行读写时,磁盘文件中的数据都会被加载到物理内存中作为操作系统对该文件的一层Cache,而这层Cache的数据与Redis内存中管理的数据实际是重复存储的,虽然内核在物理内存紧张时会做Page Cache的剔除工作,但内核很可能认为某块Page Cache更重要,而让你的进程开始Swap ,这时你的系统就会开始出现不稳定或者崩溃了。我们的经验是当你的Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了。(Redis本身会有一个page cache ,但是数据库不使用这个cache并自行维护一个cache)

总结:

u 根据业务需要选择合适的数据类型,并为不同的应用场景设置相应的紧凑存储参数。

u 当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能以及最大的内存使用量。

u 如果需要使用持久化,根据是否可以容忍重启丢失部分数据在快照方式与语句追加方式之间选择其一,不要使用虚拟内存以及diskstore方式。

u 不要让你的Redis所在机器物理内存使用超过实际内存总量的3/5

2. Redis的消息队列

2.1 消息队列介绍

Redis用于消息队列,通常有两种使用方式:

LIST:基于列表的方式,所有的消费者数据加起来是列表中的所有数据.

9.png

发布/订阅:每个消费者订阅独立的channel,每个数据都是独立的

10.png

2.2 用redis的list当作队列可能存在的问题

1) redis崩溃的时候队列功能失效

2) 如果入队端一直在塞数据,而出队端没有消费数据,或者是入队的频率大而多,出队端的消费频率慢会导致内存暴涨

3) Redis的队列也可以像rabbitmq那样 即可以做消息的持久化,也可以不做消息的持久化。当做持久话的时候,需要启动redis的dump数据的功能.暂时不建议开启持久化。Redis其实只适合作为缓存,而不是数据库或是存储。

4) 假如有多个消费者同时监听一个队列,其中一个出队了一个元素,另一个则获取不到该元素

5) Redis的队列应用场景是一对多或者一对一的关系,即有多个入队端,但是只有一个消费端(出队)

3. Redis的虚拟内存

3.1 虚拟内存介绍

由于服务器的内存是有限的,且内存容量小于磁盘容量,且结合用户的使用习惯,一般只有少部分的数据是用户经常会用到的,基于以上两点情况,redis采用虚拟内存技术来将不常使用的数据存入磁盘中,将经常用到的数据保存在有限的内存中,一旦磁盘中的数据需要读取时,可以在将需要读取的数据从磁盘中读入内存中,从而达到既节省内存空间,又能提供效率的目的,这就是redis的虚拟内存技术

但是值得注意的有两点:

1) Redis没有使用Linux提供的虚拟内存机制,它是实现了自己的虚拟内存机制

2) 经过查阅资料,redis的虚拟内存技术并不是很成熟稳定

3.2 虚拟内存易引起的性能问题

Redis增加虚拟内存特性本身目的是为了解决redis耗内存所带来的性能问题的,但是由于redis的虚拟内存现在不是很成熟,redis默认关闭虚拟内存,需要手动去开启,但是在redis的虚拟内存技术稳定之前不建议开启。

Redis性能测试自测方法

redis 性能测试的基本命令如下:redis-benchmark [option] [option value]

如使用一个参数来测试性能:

同时执行 10000 个请求来检测性能:redis-benchmark -n 10000

使用了多个参数来测试 redis 性能:

redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 –q

redis 性能测试工具可选参数如下所示:

序号

选项

描述

默认值

1

-h

指定服务器主机名

127.0.0.1

2

-p

指定服务器端口

6379

3

-s

指定服务器 socket

4

-c

指定并发连接数

50

5

-n

指定请求数

10000

6

-d

以字节的形式指定 SET/GET 值的数据大小

2

7

-k

1=keep alive 0=reconnect

1

8

-r

SET/GET/INCR 使用随机 key, SADD 使用随机值

9

-P

通过管道传输 <numreq> 请求

1

10

-q

强制退出 redis。仅显示 query/sec 值

11

--csv

以 CSV 格式输出

12

-l

生成循环,永久执行测试

13

-t

仅运行以逗号分隔的测试命令列表。

14

-I

Idle 模式。仅打开 N 个 idle 连接并等待。

Redis常用的优化方法

1、针对虚拟内存的优化

vm-enabled=no

关闭Redis的虚拟内存功能,并不成熟。

2、针对持久化的优化

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,不建议在Master开持久化设置,可以在Slave开启AOF备份数据,策略设置为每秒同步一次

3、针对主从的优化

(1) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(2) 尽量避免在压力很大的主库上增加从库

(3) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...(这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变)

4、使用场景考量

(1)redis由于是将数据存入内存,由于该特性,redis当前不适合做大容量数据的缓存机制,否则容易导致内存故障

(2)redis的数据类型考量是否合理,如string、list等

(3)持久化方式考量、是否需要主从等

5、限制redis内存大小

(1) 通过redis的info命令查看内存使用情况

如果不设置maxmemory或者设置为0,64位系统不限制内存,32位系统最多使用3GB内存。

修改配置文件中的maxmemory和maxmemory-policy

  1. maxmemory:最大内存
  2. maxmemory-policy:内存不足时,数据清除策略

6、修改linux中TCP 监听的最大容纳数量

在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核默默地将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果。

方法:echo 511 > /proc/sys/net/core/somaxconn

注意:这个参数并不是限制redis的最大链接数。如果想限制redis的最大连接数需要修改maxclients,默认最大连接数为10000。

7、关闭Transparent Huge Pages(THP)

THP会造成内存锁影响redis性能,建议关闭

Transparent HugePages :用来提高内存管理的性能

Transparent Huge Pages在32位的RHEL 6中是不支持的

使用root用户执行下面命令

echo never > /sys/kernel/mm/transparent_hugepage/enabled

Redis其他异常事件实例参考

事件一:Redis开启AOF导致的删库事件

事故详细过程:

http://www.cnblogs.com/wangxin37/p/7084410.html

事件二:redis的延时问题及持久化造成的阻塞

事件详细过程

http://www.cnblogs.com/me115/p/5032177.html

Redis性能测试注意点

对项目进行性能测试时,需要重点关注redis的内存使用情况,目前已遇到的redis性能问题基本都是与内存有关

监控内存的方法:

1、linux系统自带的监控命令,如top、free、vmstat等

2、其他资源监控工具,如:nmon

3、针对redis,有自己的一套内存监控:Redis-cli命令行查看相关info信息

Redis自带组件监控使用方法

由于无论是linux系统自带的内存监控命令,还是nmon监控工具,都是针对系统级别的监控,这里重点介绍redis组件自身带的监控工具。

redis-cli是基本的redis查询工具,如果没有更改redis端口,可以直接通过运行redis-cli

进入该工具命令行,如图:

6.png

可以输入dbsize查看redis的key值数量,如:

7.png

使用info命令查看redis可以查看的信息,如图:

8.png

info命令输出的数据可分为10个类别,分别是:

Ø server

Ø clients

Ø memory

Ø persistence

Ø stats

Ø replication

Ø cpu

Ø commandstats

Ø cluster

Ø keyspace

我们重点关注其中容易引起性能问题的memory和stats选项。

1、可以通过输入:info memory 来直接查看memory相关信息

5.png

我们针对上述关键指标进行分析:

· used_memory 字段数据表示的是:由Redis分配器分配的内存总量,以字节(byte)为单位。 其中used_memory_human上的数据和used_memory是一样的值,它的单位是M

注意:used_memory是Redis使用的内存总量,它包含了实际缓存占用的内存和Redis自身运行所占用的内存(如元数据、lua)。它是由Redis使用内存分配器分配的内存,所以这个数据并没有把内存碎片浪费掉的内存给统计进去。

其他字段代表的含义,都以字节为单位:

· used_memory_rss:从操作系统上显示已经分配的内存总量。

· mem_fragmentation_ratio: 内存碎片率。

备注:mem_fragmentation_ratio = used_memory_rss/used_memory. mem_fragmentation_ratio介于1~1.5之间是正常的,说明内存碎片率比较低。如果大于1.5则说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率。若是内存碎片率低于1的话,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。内存交换会引起非常明显的响应延迟

· used_memory_lua: Lua脚本引擎所使用的内存大小。

· mem_allocator: 在编译时指定的Redis使用的内存分配器,可以是libc、jemalloc、tcmalloc。

异常说明:通过查看used_memory指标可知道Redis正在使用的内存情况,如果used_memory>可用最大内存,那就说明Redis实例正在进行内存交换或者已经内存交换完毕。管理员根据这个情况,执行相对应的应急措施

2、可以通过输入:info stats来直接查看stats相关信息

4.png

这里面需要重点关注total_commands_processed字段,它显示了Redis服务处理命令的总数, total_commands_processed字段的值是递增的。

备注:在Redis实例中,跟踪命令处理总数是解决响应延迟问题最关键的部分,因为Redis是个单线程模型,客户端过来的命令是按照顺序执行的。比较常见的延迟是带宽,通过千兆网卡的延迟大约有200μs。倘若明显看到命令的响应时间变慢,延迟高于200μs,那可能是Redis命令队列里等待处理的命令数量比较多。 如上所述,延迟时间增加导致响应时间变慢可能是由于一个或多个慢命令引起的,这时可以看到每秒命令处理数在明显下降,甚至于后面的命令完全被阻塞,导致Redis性能降低。要分析解决这个性能问题,需要跟踪命令处理数的数量和延迟时间。

3、上述total_commands_processed的介绍中涉及了延迟时间的概念,那么如何查看延迟时间呢,可以用Redis-cli工具加--latency参数运行,如redis-cli --latency -h 127.0.0.1 6379

其host和port是Redis实例的ip及端口。由于当前服务器不同的运行情况,延迟时间可能有所误差,通常1G网卡的延迟时间是200μs。

3.png

以毫秒为单位测量Redis的响应延迟时间,我本机的延迟是90μs。

备注:

由于redis的命令是顺序执行的,一旦出现慢命令,其他命令必须等慢命令执行完毕后才能继续执行,因此在total_commands_processed和延迟时间上会有表现,一旦确定是性能问题后,可以通过以下两个方法进行分析:

l 使用slowlog查出引发延迟的慢命令。Redis中的slowlog命令可以让我们快速定位到那些超出指定执行时间的慢命令,默认情况下命令若是执行时间超过10ms就会被记录到日志。 使用方法:在Redis-cli工具,输入slowlog get命令查看

2.png

图中字段分别意思是:

² 1=日志的唯一标识符

² 2=被记录命令的执行时间点,以 UNIX 时间戳格式表示

² 3=查询执行时间,以微秒为单位。例子中命令使用54毫秒。

² 4= 执行的命令,以数组的形式排列。完整命令是后面几个命令的组合。

l 监控客户端的连接:

因为Redis是单线程模型(只能使用单核),来处理所有客户端的请求, 但由于客户端连接数的增长,处理请求的线程资源开始降低分配给单个客户端连接的处理时间,这时每个客户端需要花费更多的时间去等待Redis共享服务的响应。这种情况下监控客户端连接数是非常重要的,因为客户端创建连接数的数量可能超出预期的数量,也可能是客户端端没有有效的释放连接。

使用方法:在Redis-cli工具中输入info clients,如

1.png

上图中,第一个字段(connected_clients)显示当前实例客户端连接的总数。 Redis默认允许客户端连接的最大数量是10000。若是看到连接数超过5000以上,那可能会影响Redis的性能

Redis常见问题收集

redis overcommit memory(oom)问题

什么是overcommit memory(oom)

Linux系统中,要运行某个程序时,都会先申请内存,但是内存申请后系统并不是马上就运行,中间有一段时间间隔,这种技术就是overcommit。当系统发现内存不够分配时就会发生out of memory报错,即简称OOM,这时系统会选择杀死一些进程以释放内存,即为OOM killer。

问题现象:redis运行一段时间后挂掉了,ps查看进程没有问题,查看redis的log后发现存在warning

[26145] 07 Dec 19:54:54 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

检查redis端口,发现端口不通(端口检查:telnet,查看端口lsof -i:6379)

查看相关资料,了解报错中所涉及的参数含义。参数overcommit_memory是内核参数,用来调整系统的内存分配策略:

overcommit_memory=0表示系统在分配内存前将检查内存是否够用,内存足够则允许分配,内存不足则返回警告信息给应用

overcommit_memory=1 表示允许分配所有物理内存,而不对内存状态做检查

overcommit_memory=2 表示允许分配超过所有物理内存加交换区间内存总和的内存

上述问题现象的解决办法:

1,修改内核参数(3种方法,任选其一)

(1)编辑/etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p 使配置文件生效

(2)sysctl vm.overcommit_memory=1

(3)echo 1 > /proc/sys/vm/overcommit_memory

2,修改redis.conf,然后重启redis

maxmemory 5368709120
maxmemory-policy allkeys-lru
maxmemory-samples 3

设置一下maxmemory,建议设置为物理内存的1/2到3/4,大小不要超过最大物理内存。

附录:

1、redis优化文章推荐:

http://www.cnblogs.com/jandis...


travel
12 声望1 粉丝

好记性不如烂笔头