标签:难度 新浪微博 结束 tty ISE 状态 大量 iter 基础
尽管Redis是一个非常快速的内存数据存储媒介,也并不代表Redis不会产生性能问题。
前文中提到过,Redis采用单线程模型,所有的命令都是由一个线程串行执行的,所以当某个命令执行耗时较长时,会拖慢其后的所有命令,这使得Redis对每个任务的执行效率更加敏感。
针对Redis的性能优化,主要从下面几个层面入手:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
Redis绝大多数读写命令的时间复杂度都在O(1)到O(N)之间,在文本和官方文档中均对每个命令的时间复杂度有说明。
通常来说,O(1)的命令是安全的,O(N)命令在使用时需要注意,如果N的数量级不可预知,则应避免使用。例如对一个field数未知的Hash数据执行HGETALL/HKEYS/HVALS命令,通常来说这些命令执行的很快,但如果这个Hash中的field数量极多,耗时就会成倍增长。
又如使用SUNION对两个Set执行Union操作,或使用SORT对List/Set执行排序操作等时,都应该严加注意。
避免在使用这些O(N)命令时发生问题主要有几个办法:
Redis提供了SCAN命令,可以对Redis中存储的所有key进行游标式的遍历,避免使用KEYS命令带来的性能问题。同时还有SSCAN/HSCAN/ZSCAN等命令,分别用于对Set/Hash/Sorted Set中的元素进行游标式遍历。SCAN类命令的使用请参考官方文档:https://redis.io/commands/scan
Redis提供了Slow Log功能,可以自动记录耗时较长的命令。相关的配置参数有两个:
slowlog-log-slower-than xxxms #执行时间慢于xxx毫秒的命令计入Slow Log
slowlog-max-len xxx #Slow Log的长度,即最大纪录多少条Slow Log
使用SLOWLOG GET [number]命令,可以输出最近进入Slow Log的number条命令。
使用SLOWLOG RESET命令,可以重置Slow Log
Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:
Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝24GB / 4kB * 8 = 48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。
可以通过INFO命令返回的latest_fork_usec字段查看上一次fork操作的耗时(微秒)
当Linux将Redis所用的内存分页移至swap空间时,将会阻塞Redis进程,导致Redis出现不正常的延迟。Swap通常在物理内存不足或一些进程在进行大量I/O操作时发生,应尽可能避免上述两种情况的出现。
/proc/<pid>/smaps文件中会保存进程的swap记录,通过查看这个文件,能够判断Redis的延迟是否由Swap产生。如果这个文件中记录了较大的Swap size,则说明延迟很有可能是Swap造成的。
当同一秒内有大量key过期时,也会引发Redis的延迟。在使用时应尽量将key的失效时间错开。
Redis的主从复制能力可以实现一主多从的多节点架构,在这一架构下,主节点接收所有写请求,并将数据同步给多个从节点。
在这一基础上,我们可以让从节点提供对实时性要求不高的读请求服务,以减小主节点的压力。
尤其是针对一些使用了长耗时命令的统计类任务,完全可以指定在一个或多个从节点上执行,避免这些长耗时命令影响其他请求的响应。
关于读写分离的具体说明,请参见后续章节
Redis支持一主多从的主从复制架构。一个Master实例负责处理所有的写请求,Master将写操作同步至所有Slave。
借助Redis的主从复制,可以实现读写分离和高可用:
启用主从复制非常简单,只需要配置多个Redis实例,在作为Slave的Redis实例中配置:
slaveof 192.168.1.1 6379 #指定Master的IP和端口
当Slave启动后,会从Master进行一次冷启动数据同步,由Master触发BGSAVE生成RDB文件推送给Slave进行导入,导入完成后Master再将增量数据通过Redis Protocol同步给Slave。之后主从之间的数据便一直以Redis Protocol进行同步
Redis的主从复制功能本身只是做数据同步,并不提供监控和自动failover能力,要通过主从复制功能来实现Redis的高可用,还需要引入一个组件:Redis Sentinel
Redis Sentinel是Redis官方开发的监控组件,可以监控Redis实例的状态,通过Master节点自动发现Slave节点,并在监测到Master节点失效时选举出一个新的Master,并向所有Redis实例推送新的主从配置。
Redis Sentinel需要至少部署3个实例才能形成选举关系。
关键配置:
sentinel monitor mymaster 127.0.0.1 6379 2 #Master实例的IP、端口,以及选举需要的赞成票数
sentinel down-after-milliseconds mymaster 60000 #多长时间没有响应视为Master失效
sentinel failover-timeout mymaster 180000 #两次failover尝试间的间隔时长
sentinel parallel-syncs mymaster 1 #如果有多个Slave,可以通过此配置指定同时从新Master进行数据同步的Slave数,避免所有Slave同时进行数据同步导致查询服务也不可用
另外需要注意的是,Redis Sentinel实现的自动failover不是在同一个IP和端口上完成的,也就是说自动failover产生的新Master提供服务的IP和端口与之前的Master是不一样的,所以要实现HA,还要求客户端必须支持Sentinel,能够与Sentinel交互获得新Master的信息才行。
为何要做集群分片:
当上述两个问题出现时,就必须要对Redis进行分片了。
Redis的分片方案有很多种,例如很多Redis的客户端都自行实现了分片功能,也有向Twemproxy这样的以代理方式实现的Redis分片方案。然而首选的方案还应该是Redis官方在3.0版本中推出的Redis Cluster分片方案。
本文不会对Redis Cluster的具体安装和部署细节进行介绍,重点介绍Redis Cluster带来的好处与弊端。
其中第三点是基于主从复制来实现的,Redis Cluster的每个数据分片都采用了主从复制的结构,原理和前文所述的主从复制完全一致,唯一的区别是省去了Redis Sentinel这一额外的组件,由Redis Cluster负责进行一个分片内部的节点监控和自动failover。
Redis Cluster中共有16384个hash slot,Redis会计算每个key的CRC16,将结果与16384取模,来决定该key存储在哪一个hash slot中,同时需要指定Redis Cluster中每个数据分片负责的Slot数。Slot的分配在任何时间点都可以进行重新分配。
客户端在对key进行读写操作时,可以连接Cluster中的任意一个分片,如果操作的key不在此分片负责的Slot范围内,Redis Cluster会自动将请求重定向到正确的分片上。
在基础的分片原则上,Redis还支持hash tags功能,以hash tags要求的格式明明的key,将会确保进入同一个Slot中。例如:{uiv}user:1000和{uiv}user:1001拥有同样的hash tag {uiv},会保存在同一个Slot中。
使用Redis Cluster时,pipelining、事务和LUA Script功能涉及的key必须在同一个数据分片上,否则将会返回错误。如要在Redis Cluster中使用上述功能,就必须通过hash tags来确保一个pipeline或一个事务中操作的所有key都位于同一个Slot中。
有一些客户端(如Redisson)实现了集群化的pipelining操作,可以自动将一个pipeline里的命令按key所在的分片进行分组,分别发到不同的分片上执行。但是Redis不支持跨分片的事务,事务和LUA Script还是必须遵循所有key在一个分片上的规则要求。
在设计软件架构时,要如何在主从复制和集群分片两种部署方案中取舍呢?
从各个方面看,Redis Cluster都是优于主从复制的方案
那是不是代表Redis Cluster永远是优于主从复制的选择呢?
并不是。
软件架构永远不是越复杂越好,复杂的架构在带来显著好处的同时,一定也会带来相应的弊端。采用Redis Cluster的弊端包括:
所以说,在主从复制和集群分片两个方案中做出选择时,应该从应用软件的功能特性、数据和访问量级、未来发展规划等方面综合考虑,只在确实有必要引入数据分片时再使用Redis Cluster。
下面是一些建议:
综合上面几点考虑,如果单台主机的可用物理内存完全足以支撑对Redis的容量需求,且Redis面临的并发写压力距离Benchmark值还尚有距离,建议采用主从复制的架构,可以省去很多不必要的麻烦。同时,如果应用中大量使用pipelining和事务,也建议尽可能选择主从复制架构,可以减少设计和开发时的复杂度。
Redis的Java客户端很多,官方推荐的有三种:Jedis、Redisson和lettuce。
在这里对Jedis和Redisson进行对比介绍
Jedis:
Redisson:
对于Jedis和Redisson的选择,同样应遵循前述的原理,尽管Jedis比起Redisson有各种各样的不足,但也应该在需要使用Redisson的高级特性时再选用Redisson,避免造成不必要的程序复杂度提升。
Jedis:
github:https://github.com/xetorthio/jedis
文档:https://github.com/xetorthio/jedis/wiki
Redisson:
github:https://github.com/redisson/redisson
文档:https://github.com/redisson/redisson/wiki
1. 什么是redis
Redis是一个nosql的高性能Key-Value内存数据库,支持网络,亦可本地持久化。3.0.0Beta版已支持集群。
详细资料可见http://www.redis.cn/
2. Redis关键参数
? 客户端最大连接数(maxclients)
可能的错误信息:max number of clients reached。
默认为0,即不限制,一般不需要更改,所以客户端连接限制,取决于操作系统参数ulimit -n(max open files),可通过修改/etc/security/limits.conf文件以永久生效。
以下场景在性能压测时出现,涉及三个参数:
可能的错误信息:scheduled to be closed ASAP for overcoming of output buffer limits。
有时候明明master/slave都活得好好的,突然间就说要重新进行全同步了:
1.Slave显示:# MASTER time out: no data nor PING received…
2.Master显示:# Client addr=10.175.162.123:44670 flags=S oll=104654 omem=2147487792 events=rw cmd=sync scheduled to be closed ASAP for overcoming of output buffer limits.
? 主从响应策略(repl-ping-slave-period/repl-timeout)
slave会每隔repl-ping-slave-period(默认10秒)ping一次master,如果超过repl-timeout(默认 60秒)都没有收到响应,就会认为Master挂了。如果Master明明没挂但被阻塞住了也会报这个错。可以适当调大repl-timeout。
? 客户端输出缓冲区(client-output-buffer-limit)
该参数有三种场景策略,主要是第二种slave场景。当使用主从复制时,性能压测下,数据量会急剧增长,导致从节点需要复制的数据很大,消耗时长增加。slave没挂但被阻塞住了,比如正在loading Master发过来的RDB, Master的指令不能立刻发送给slave,就会放在output buffer中(见oll是命令数量,omem是大小),在配置文件中有如下配置:client-output-buffer-limit slave 256mb 64mb 60, 这是说负责发数据给slave的client,如果buffer超过256m或者连续60秒超过64m,就会被立刻强行关闭。所以此时应该相应调大数值,否则就会出现很悲剧的循环:Master传输一个很大的RDB给Slave,Slave努力地装载,但还没装载完,Master对client的缓存满了,再来一次。
平时可以在master执行 redis-cli client list 找那个cmd=sync,flag=S的client,注意OMem的变化。
? 日志级别和输出(loglevel、logfile)
生产可调整为warning级别,并重定向到某个文件。这对排除问题很有帮助。
3. 性能调优
? 内存分配限制
可能的错误信息:Cannot allocate memory
Redis在主从复制时,需要fork子进程来进行操作,如果你的应用堆积了很大数据在内存中,那么就需要针对这个子进程申请相应的内存空间,此时会受到操作系统的限制。通过更改系统配置文件/etc/sysctl.conf的vm.overcommit_memory=1以永久生效。该参数有0、1、2三个值。1表示允许分配所有的物理内存,而不管当前的内存状态如何。
? 客户端频繁获取连接限制
可能的错误信息:Cannot assign requested address
频繁地连服务器,但每次连接都在短时间内结束,导致很多的TIME_WAIT,以至于用光端口号,所以新连接没办法绑定端口。修改如下2个内核参数:
sysctl -w net.ipv4.tcp_timestamps=1,开启对于TCP时间戳的支持,若该项设置为0,则下面一项设置不起作用;
sysctl -w net.ipv4.tcp_tw_recycle=1,表示开启TCP连接中TIME-WAIT sockets的快速回收。
一、 Redis部署结构优化建议
1. Master不做AOF或RDB持久化,Slave做AOF持久化,建议同时做RDB持久化
2. 所有Master全部增加Slave
3. Master挂载Slave不超过2个,采用M-S-S方式挂载。若想保证高可用,即主从切换,可采用Keepalived机制.
备注:以上是基于Redis部署结构不合理提出的建议,同时也参考了新浪微博、淘宝架构中Redis优化方案给出
二、 Redis配置优化建议
1.tcp-keepalive 60
阻止由于某个command执行过长达到timeout超时时间而被断开连接,且可以提高连接错误的检测.
2.stop-writes-on-bgsave-error no
当bgsave快照操作出错时停止写数据到磁盘,这样后面写操作均会失败,为了不影响后续写操作,故需将该项值改为no.
3.rdbchecksum no
检查RDB数据的正确性,会牺牲10%的性能,故建议关闭.
4.auto-aof-rotate-max-size 20gb
auto-aof-rotate-max-total 4
auto-aof-rewrite-percentage 0 (关闭rewrite模式)
将AOF rewrite模式改为rotate模式,即将AOF在线实时Rewrite的功能,切换到线下操作,1份AOF文件切割成多份(类似日志切割),这样提升了redis性能的同时提升内存的利用率.
5.no-appendfsync-on-rewrite yes
避免新修改数据刷磁盘时出现IO阻塞
备注:以上是基于Redis配置不合理提出的优化建议
三、 系统内核配置优化建议
1.开启了AOF模式,为了缓解IO阻塞
编辑/etc/sysctl.conf ,添加如下配置:
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
然后sysctl -p 使配置文件生效.
2.开启了RDB模式,为了避免Fork失败
编辑/etc/sysctl.conf ,改vm.overcommit_memory=1,
然后sysctl -p 使配置文件生效
备注:以上是基于测试结果给出的系统内核优化建议
后续计划:
以上是从架构角度提出的优化建议,后续会从业务角度,分析内存类型是否合理、冷热数据划分是否合理等
备注:
关于冷热数据划分,可使用如下Redis命令进行统计分析:
OBJECT REFCOUNT 该命令主要用于调试(debugging),它能够返回指定key所对应value被引用的次数.
OBJECT ENCODING 该命令返回指定key对应value所使用的内部表示(representation)(译者注:也可以理解为数据的压缩方式).
OBJECT IDLETIME 该命令返回指定key对应的value自被存储之后空闲的时间,以秒为单位(没有读写操作的请求) ,这个值返回以10秒为单位的秒级别时间,这一点可能在以后的实现中改善
标签:难度 新浪微博 结束 tty ISE 状态 大量 iter 基础
原文地址:https://www.cnblogs.com/AmilyWilly/p/8780085.html