栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Redis学习之路(三)主从复制、键过期删除策略、内存溢出策略、慢查询

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redis学习之路(三)主从复制、键过期删除策略、内存溢出策略、慢查询

文章目录
    • 一、主从复制
      • 1.1 主从复制示例
      • 1.2 主从复制结构
      • 1.3 主从复制细节
        • 1.3.1 复制过程
        • 1.3.2 数据同步
        • 1.3.3 全量复制
        • 1.3.4 部分复制
        • 1.3.5 心跳
        • 1.3.6 异步复制
      • 1.4 主从复制时的一些问题
        • 1.4.1 读写分离
        • 1.4.2 主从配置不一致
        • 1.4.3 规避全量复制
        • 1.4.4 规避复制风暴
      • 1.5 主从复制的特点总结
        • 1.5.1 主从复制的特点
        • 1.5.2 主从模式的优缺点
    • 二、内存
      • 2.1 内存消耗
        • 2.1.1 内存使用统计
        • 2.1.2 内存消耗划分
      • 2.2 内存管理
        • 2.2.1 设置内存上限
        • 2.2.2 动态调整内存上限
        • 2.2.3 键过期删除策略
        • 2.2.4 内存溢出策略
      • 2.3 内存优化
        • 2.3.1 缩减键值对象
        • 2.3.2 共享对象池
        • 2.3.3 字符串优化
        • 2.3.4 编码优化
        • 2.3.5 控制键的数量
      • 2.4 一些相关问题
        • 2.4.1 Mysql里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据
    • 三、阻塞问题
      • 3.1 内在原因
        • 3.1.1 API或数据结构使用不合理
        • 3.1.2 CPU饱和
        • 3.1.3 持久化阻塞
      • 3.2 外在原因
        • 3.2.1 CPU竞争
        • 3.2.2 内存交换
        • 3.2.3 网络问题

  本系列文章:
    Redis进阶之路(一)5种数据类型、Redis常用命令
    Redis学习之路(二)Redis Java API、Redis客户端常用命令、持久化、事务
    Redis学习之路(三)主从复制、键过期删除策略、内存溢出策略、慢查询

一、主从复制 1.1 主从复制示例

  Redis实例划分为主节点和从节点。默认情况下,Redis都是主节点。每个从节点只能有一个主节点,而主节点可以同时具有多个从节点。复制的数据流是单向的,只能由主节点复制到从节点。
  配置复制的方式有三种:

  1、在配置文件(redis.conf,在Windows系统中是redis.windows.conf)中加入slaveof {masterHost} {masterPort}随Redis启动生效。
  2、在redis-server启动命令后加入--slaveof {masterHost} {masterPort}生效。
  3、直接使用命令:slaveof {masterHost} {masterPort}生效。

  假如本地启动两个端口为6379和6380的Redis节点,在127.0.0.1:6380执行如下命令:

  slaveof配置都是在从节点发起,所以此时6379作为主节点,6380作为从节点。这时在6379节点上存储值,在6380节点上就能取出。
  slaveof本身是异步命令,执行slaveof命令时,节点只保存主节点信息后返回,后续复制流程在节点内部异步执行。
  主从节点复制关系成功建立后,可以使用info replication命令查看复制相关状态。6379主节点示例:

127.0.0.1:6379>info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6379,state=online,offset=43,lag=0

  6380从节点示例:

127.0.0.1:6380>info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0

  slaveof命令不但可以建立复制,还可以在从节点执行slaveof no one来断开与主节点复制关系。
  断开复制主要流程:

  1、断开与主节点复制关系。
  2、从节点晋升为主节点。

  通过slaveof命令还可以实现更新主节点操作,即把当前从节点对主节点的复制切换到另一个主节点。执行slaveof {newMasterIp} {newMasterPort}命令即可。
  更新主节点操作流程:

  1、断开与旧主节点复制关系。
  2、与新主节点建立复制关系。
  3、删除从节点当前所有数据。
  4、对新主节点进行复制操作。

  更新主节点后,从节点会清空之前所有的数据。
  对于数据比较重要的节点,主节点会通过设置requirepass参数进行密码验证,这时所有的客户端访问必须使用auth命令实行校验。
  在Redis的配置文件中有这个参数,不过是被注释掉的:

# requirepass foobared

  requirepass参数和auth命令的使用示例:

  默认情况下,从节点使用slave-read-only=yes配置为只读模式。由于复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点会造成主从数据不一致。因此建议线上不要修改从节点的只读模式。
  主从节点一般部署在不同机器上,因此进行主从复制时,一般会有网络延迟,Redis提供了repl-disable-tcp-nodelay参数,可配置的值为yes或no,默认值为no,在配置文件中的该参数:

repl-disable-tcp-nodelay no

  该参数的含义:

  1、当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机房部署。
  2、当开启时,主节点会合并较小的TCP数据包从而节省带宽。默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。

1.2 主从复制结构

  单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从架构,一主多从较为常见,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
  主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

  Redis的复制结构可以分为三种:一主一从、一主多从、树状主从。

  • 1、一主一从
      一主一从结构是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持。
      需要注意的是,如果主节点在没有将数据存到硬盘的情况下重启了,从节点如果继续复制主节点会导致从节点数据也被清空。因此,主节点要重启时,要先在从节点上执行slaveof no one断开与主节点的复制关系,再重启主节点。
  • 2、一主多从
      一主多从结构用来实现读写分离,即对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。
  • 3、树状主从
      树状主从结构,使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。当主节点需要挂载多个从节点时为了避免对主节点的性能干扰,可以采用树状主从结构降低主节点压力。
1.3 主从复制细节 1.3.1 复制过程

  示例:

  主从复制大致分为6个过程:
 1、保存主节点信息
  执行slaveof命令后,从节点只保存主节点的地址信息,这时还没有开始建立复制流程。
 2、主从建立socket连接
  从节点内有定时任务,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。
  从节点会建立一个socket套接字,专门用于接受主节点发送的复制命令。从节点连接成功后打印如下日志:

  如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行slaveof no one命令取消复制。

 3、发送ping命令
  连接建立成功后从节点发送ping请求进行首次通信,ping请求主要目的:

1、检测主从之间网络套接字是否可用。
2、检测主节点当前是否可接受处理命令。

  如果从节点发送ping命令后,没有收到主节点的pong回复或者超时,比如网络超时或者主节点正在阻塞无法响应命令,从节点会断开复制连接,下次定时任务会发起重连,示例:

 4、权限验证
  如果主节点设置了requirepass参数,则需要密码验证。如果验证失败复制将终止,从节点重新发起复制流程。
 5、同步数据集
  主从复制连接正常通信后,如果是首次建立复制,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。
 6、命令持续复制
  当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。

1.3.2 数据同步

  在上一小节介绍的6个步骤中,第5步是同步数据。Redis在2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。
  全量复制:一般用于初次复制场景,Redis早期支持的复制功能只有全
量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
  部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
  在psync命令中,有几个较关键的点:

  • 1、复制偏移量
      参与复制的主从节点都会维护自身复制偏移量。主节点在处理完写入命令后,会把命令的字节长度做累加记录,统计信息可以用info relication命令查看:

      从节点每秒钟上报自身的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量,指标:

      从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量,指标:

      如果主从之间复制偏移量相差较大,则可能是网络延迟或命令阻塞等原因引起。
  • 2、复制积压缓冲区
      复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为1MB,当主节点有连接的从节点时被创建,这时主节点响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区:

      缓冲区能实现保存最近已复制数据的功能,用于部分复制和复制命令丢失的数据补救。
      复制缓冲区相关统计信息可以在主节点上,通过info replication命令查看:
  • 3、主节点运行ID
      每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。
      可以通过info server命令查看当前节点的运行ID:

      Redis关闭再启动后,运行ID也会改变。
  • 4、psync命令
      从节点使用psync命令完成部分复制和全量复制功能,命令格式:psync {runId} {offset}。

  runId:从节点所复制主节点的运行id。
  offset:当前从节点已复制的数据偏移量。

  psync命令复制流程:

  1. 从节点发送psync命令给主节点,参数runId是当前从节点保存的主节点运行ID,如果没有则默认值为?参数offset是当前从节点保存的复制偏移量,如果是第一次参与复制则默认值为-1。
  2. 主节点根据psync参数和自身数据情况决定响应结果:

  1、如果回复+FULLRESYNC{runId}{offset},那么从节点将触发全量复制流程。
  2、如果回复+CONTINUE,从节点将触发部分复制流程。
  3、如果回复+ERR,说明主节点版本低于Redis2.8,无法识别psync命令,从节点将发送旧版的sync命令触发全量复制流程。

1.3.3 全量复制

  全量复制是主从第一次建立复制时必须经历的阶段。触发全量复制的命令是sync和psync。Redis版本低于2.8时用sync,等于高于2.8版本时用psync。
  全量复制的流程图:

  • 1、发送psync命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点的运行ID,所以发送psync?-1。
  • 2、主节点根据psync?-1解析出当前为全量复制,回复+FULLRESYNC响应。
  • 3、从节点接收主节点的响应数据保存运行ID和偏移量offset,日志示例:
  • 4、主节点执行bgsave保存RDB文件到本地。

  Redis3.0之后在输出的日志开头会有M、S、C等标识,对应的含义是:M=当前为主节点日志,S=当前为从节点日志,C=子进程日志。

  • 5、主节点发送RDB文件给从节点,从节点把接收的RDB文件保存在本地并直接作为从节点的数据文件。主节点发送的数据量示例:

      对于数据量较大的主节点,传输文件这一步操作非常耗时,速度取决于主从节点之间的网络带宽。RDB文件从创建到传输完毕消耗的总时间。如果总时间超过repl-timeout所配置的值(默认60秒),从节点将放弃接受RDB文件并清理已经下载的临时文件,导致全量复制失败。此时日志示例:

      针对数据量较大的节点,可以调大repl-timeout参数防止出现全量同步数据超时。
  • 6、对于从节点开始接收RDB快照到接收完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令数据保存在复制客户端缓冲区内,当从节点加载完RDB文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性。
      如果主节点创建和传输RDB的时间过长,对于高流量写入场景非常容易造成主节点复制客户端缓冲区溢出。Redis配置文件中的默认配置是:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

  第2行配置的含义是:如果60秒内缓冲区消耗持续大于64MB或者直接超过256MB时,主节点将直接关闭复制客户端连接,造成全量同步失败。此时日志示例:

  • 7、从节点接收完主节点传送来的全部数据后会清空自身旧数据,日志示例:
  • 8、从节点清空数据后开始加载RDB文件。
      对于线上做读写分离的场景,从节点也负责响应读命令。如果此时从节点正出于全量复制阶段或者复制中断,那么从节点在响应读命令可能拿到过期或错误的数据。
      此时可以通过设置slave-serve-stale-data参数来处理。该参数如果开启则从节点依然响应所有命令,对于无法容忍不一致的应用场景可以设置no来关闭命令执行。
  • 9、从节点成功加载完RDB后,如果当前节点开启了AOF持久化功能,会立刻做bgrewriteaof操作,为了保证全量复制后AOF持久化文件立刻可用。

  全量复制的时间开销主要包括:

主节点bgsave时间。
RDB文件网络传输时间。
从节点清空数据时间。
从节点加载RDB的时间。
可能的AOF重写时间。

1.3.4 部分复制

  部分复制主要是Redis针对全量复制的过高开销做出的一种优化措施,也是使用psync {runId} {offset}命令实现。部分复制的流程:

  • 1、当主从节点之间网络出现中断时,如果超过repl-timeout(默认60秒)时间,主节点会认为从节点故障并中断复制连接。日志示例:
  • 2、主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点。
  • 3、当主从节点网络恢复后,从节点会再次连上主节点。日志示例:
  • 4、当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。因此会把它们当作psync参数发送给主节点,要求进行部分复制操作。从节点日志示例:
  • 5、主节点接到psync命令后首先核对参数runId是否与自身一致,如果一致,说明之前复制的是当前主节点;之后根据参数offset在自身复制积压缓冲区查找,如果偏移量之后的数据存在缓冲区中,则对从节点发送+CONTINUE响应,表示可以进行部分复制。日志示例:
  • 6、主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。主节点日志示例:
1.3.5 心跳

  主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令:

  主从心跳判断机制:

  1、主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信,通过client list命令查看复制相关客户端信息,主节点的连接状态为flags=M,从节点连接状态为flags=S。
  2、主节点默认每隔10秒对从节点发送ping命令,判断从节点的存活性和连接状态。可通过参数repl-ping-slave-period控制发送频率。
  3、从节点在主线程中每隔1秒发送replconf ack{offset}命令,给主节点上报自身当前的复制偏移量。

  replconf命令的主要作用:

  1、实时监测主从节点网络状态。
  2、上报自身复制偏移量,检查复制数据是否丢失,如果从节点数据丢失,再从主节点的复制缓冲区中拉取丢失数据。
  3、实现保证从节点的数量和延迟性功能.

1.3.6 异步复制

  主节点给从节点发送写命令的过程是异步的,示例:

  主节点复制流程:

  1、主节点6379接收处理命令。
  2、命令处理完之后返回响应结果。
  3、对于修改命令异步发送给6380从节点,从节点在主线程中执行复制的命令。

  由于主从复制过程是异步的,就会造成从节点的数据相对主节点存在延迟。这个延迟造成的数据量差异,可以在主节点执行info replication命令查看:

  offset表示当前从节点的复制偏移量,master_repl_offset表示当前主节点的复制偏移量,两者的差值就是当前从节点复制延迟量。正常情况下,延迟在1秒以内。

1.4 主从复制时的一些问题 1.4.1 读写分离

  即对主节点执行写操作,对从节点执行读操作。当使用从节点响应读请求时,业务端可能会遇到这些问题:复制数据延迟、读到过期数据、从节点故障。

  • 1、数据延迟
      由于异步复制,延迟不可避免,延迟取决于网络带宽和命令阻塞情况。对于无法容忍大量延迟场景,可以写监控程序监听主从节点的复制偏移量,当延迟较大时触发报警,或通知客户端避免读取延迟过高的从节点。这种方案的一些实现细节:

  1、监控程序定期检查主从节点的偏移量,它们的差值就是主从节点延迟的字节量。
  2、当延迟字节量过高时,比如超过10MB。监控程序触发报警并通知客户端从节点延迟过高。可以采用Zookeeper的监听回调机制实现客户端通知。
  3、客户端接到具体的从节点高延迟通知后,修改读命令路由到其他从节点或主节点上。当延迟恢复后,再次通知客户端,恢复从节点的读命令请求。

  • 2、数据过期
      Redis常常用来缓存数据,对这些缓存数据的管理策略称为删除策略,删除策略主要有两种:惰性删除和定时删除。
      惰性删除:主节点每次处理读取命令时,都会检查键是否超时,如果超时则执行del命令删除键对象,之后del命令也会异步发送给从节点。为了保证复制的一致性,从节点自身永远不会主动删除超时数据。

      定时删除:主节点定时检查键是否过期,过期则执行del命令,之后再同步给从节点。

      如果有大量缓存数据超时,主节点检查数据是否超时的速度跟不上过期速度且主节点没有读取过期键的操作,那么从节点将无法收到del命令。这时在从节点上可以读取到已经超时的数据。

  Redis在3.2版本解决了这个问题,方式是:从节点读取数据之前会检查键的过期时间来决定是否返回数据。

1.4.2 主从配置不一致

  主从节点之间,有些配置是可以不一致的,比如:主节点关闭AOF在从节点开启。但对于内存相关的配置必须要一致,比如maxmemory,hash-max-ziplist-entries等参数。

1.4.3 规避全量复制

  需要全量复制的场景主要有以下3个:第一次建立复制、节点运行ID不匹配、复制积压缓冲区不足。

  • 1、第一次建立复制
      这种情况全量复制无法避免。当对数据量较大且流量较高的主节点添加从节点时,可以在低峰时进行操作,或者尽量规避使用大数据量的Redis节点。
  • 2、节点运行ID不匹配
      如果主从复制关系建立后,主节点因故障重启,那么主节点的运行ID会改变,从节点发现主节点运行ID不匹配时,会认为自己复制的是一个新的主节点从而进行全量复制。
      对于这种情况应该从架构上规避,比如提供故障转移功能。当主节点发生故障后,手动提升从节点为主节点或者采用支持自动故障转移的哨兵或集群方案。
  • 3、复制积压缓冲区不足
      当主从节点网络中断后,从节点再次连上主节点时会发送psync {offset} {runId}命令请求部分复制,如果请求的偏移量不在主节点的积压缓冲区内,则无法提供给从节点数据,因此部分复制会退化为全量复制。
1.4.4 规避复制风暴

  复制风暴指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。规避方式有如下几个。

  • 1、单主节点复制风暴
      单主节点复制风暴一般发生在主节点挂载多个从节点的场景。当主节点重启恢复后,从节点会发起全量复制流程,这时主节点就会为从节点创建RDB快照。
      如果主节点同时向多个从节点发送RDB快照,可能使主节点的网络带宽消耗严重,造成主节点的延迟变大,极端情况会发生主从节点连接断开,导致复制失败。
      解决方案首先可以减少主节点挂载从节点的数量,或者采用树状复制结构,加入中间层从节点用来保护主节点。示例:
  • 2、单机器复制风暴
      Redis是单线程架构,通常单台机器会部署多个Redis实例。当一台机器上同时部署多个主节点时,如果这台机器出现故障或网络长时间中断,当它重启恢复后,会有大量从节点针对这台机器的主节点进行全量复制,会造成当前机器网络带宽耗尽。
      避免这种情况的方法:

  1、应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点。
  2、当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密集的全量复制。

1.5 主从复制的特点总结 1.5.1 主从复制的特点
  • 1、Redis 采用异步方式复制数据到从节点,不过 Redis2.8 开始,从节点会周期性地确认自己每次复制的数据量。
  • 2、一个主节点是可以配置多个从节点。
  • 3、从节点也可以连接其他的从节点。
  • 4、从节点做复制的时候,不会阻塞主节点的正常工作。
  • 5、从节点在做复制的时候,也不会阻塞对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了。
  • 6、从节点主要用来进行横向扩容,做读写分离,扩容的从节点可以提高读的吞吐量。
      
      如果采用了主从架构,那么建议必须开启主节点的持久化,不建议用从节点作为 主节点的数据热备。那样的话,如果关掉主节点的持久化,可能在主节点宕机重启的时候数据是空的,然后可能一经过复制,从节点的数据也丢了。
      同时,主节点的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复主节点数据,这样才能确保启动的时候,主节点是有数据的。
1.5.2 主从模式的优缺点
  • 1、优点
  1. 一个主,可以有多个从,并以非阻塞的方式完成数据同步;
  2. 从服务器提供读服务,分散主服务的压力,实现读写分离;
  3. 从服务器之前可以彼此连接和同步请求,减少主服务同步压力。
  • 2、缺点
  1. 不具备容错和恢复功能,主服务存在单点风险;
  2. Redis 的主从复制采用全量复制,需要服务器有足够的空余内存;
  3. 主从模式较难支持在线扩容。
二、内存 2.1 内存消耗 2.1.1 内存使用统计

  可通过info memory命令获取内存相关指标。示例:

  各个属性的含义:

属性说明
used_memoryRedis分配器分配的内存总量,也就是内部存储的所有数据内存占用量
used_memory_human以可读的形式返回used_memory
used_memory_rss从操作系统的角度显示Redis进程占用的物理内存总量
used_memory_peak内存使用的最大值,表示used_memory的峰值
used_memory_peak_human以可读的格式返回used_memory_peak
used_memory_luaLua引擎所消耗的内存大小
mem_fragmentation_ratioused_memory_rss / used_memory比值,表示内存碎片率
mem_allocatorRedis所使用的的内存分配器,默认为jemalloc

  当mem_fragmentation_ratio(used_memory_rss / used_memory)>1时,说明used_memory_rss-used_memory多出的部分内存并没有用于数据存储,而是被内存碎片所消耗。如果两者相差很大,说明碎片率严重。
  当mem_fragmentation_ratio<1时,这种情况一般出现在操作系统把Redis内存交换到硬盘导致,出现这种情况时要格外关注,由于硬盘速度远远慢于内存,Redis性能会变得很差。

2.1.2 内存消耗划分

  Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。

  • 1、对象内存
      对象内存是Redis内存占用最大的一块,对象内存消耗可以简单理解为sizeof(keys)+sizeof(values)。键对象都是字符串,在使用Redis时应当避免使用过长的键。
      
  • 2、缓冲内存
      缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区。
      客户端缓冲指的是所有接入到Redis服务器TCP连接的输入输出缓冲。输入缓冲无法控制,最大空间为1G,如果超过将断开连接。输出缓冲通过
    参数client-output-buffer-limit控制,不同类型客户端的默认值:

      普通客户端:指除了复制和订阅的客户端之外的所有连接。一般情况下,普通客户端的内存消耗可以忽略不计,但是当有大量慢连接客户端接入时这部分内存消耗就不能忽略了,可以设置maxclients(最大连接数)做限制。
      从客户端:当主从节点之间网络延迟较高或主节点挂载大量从节点时这部分内存消耗将占用很大一部分,建议主节点挂载的从节点不要多于2个,主从节点不要部署在较差的网络环境下,如异地跨机房环境。
      订阅客户端:当订阅服务的消息生产快于消费速度时,输出缓冲区会产生积压造成输出缓冲区空间溢出。
      复制积压缓冲区:Redis在2.8版本之后提供了一个可重用的固定大小缓冲区用于实现部分复制功能,由repl-backlog-size参数表示,默认1M,示例:

      整个主节点只有一个复制积压缓冲区,所有的从节点共享此缓冲区,因此可以设置较大的缓冲区空间,如100MB。
      AOF缓冲区:这部分空间用于在Redis重写期间保存最近的写入命令,这部分空间占用通常很小。
  • 3、内存碎片
      Redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、tcmalloc。
      jemalloc在64位系统中将内存空间划分为:小、大、巨大三个范围。每个范围内又划分为多个小的内存块单位。

  小:[8byte],[16byte,32byte,48byte,…,128byte],[192byte,256byte,…512byte],[768byte,1024byte,…,3840byte]
  大:[4KB,8KB,12KB,…,4072KB]
  巨大:[4MB,8MB,12MB,…]

  比如当保存10KB对象时jemalloc可能会采用12KB的块存储,而剩下的3KB空间变为了内存碎片不能再分配给其他对象存储。
  jemalloc正常的碎片率(mem_fragmentation_ratio)在1.03左右。
  以下场景容易出现高内存碎片问题:

  1、频繁做更新操作。
  2、大量过期键删除,键对象过期删除后,释放的空间无法得到充分利用,导致碎片率上升。

  出现高内存碎片问题时常见的解决方式:

  1、数据对齐:在条件允许的情况下尽量做数据对齐,比如数据尽量采用数字类型或者固定长度字符串等。
  2、安全重启:重启节点可以做到内存碎片重新整理。

2.2 内存管理 2.2.1 设置内存上限

  Redis使用maxmemory参数限制最大可用内存。示例:

  如果不设置maxmemory或者设置为0,64位系统不限制内存,32位系统最多使用3GB内存。
  maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存。由于内存碎片的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。
  比如一台24GB内存的服务器,为系统预留4GB内存,预留4GB空闲内存给其他进程或Redis fork进程,留给Redis16GB内存,这样可以部署4个maxmemory=4GB的Redis进程。

2.2.2 动态调整内存上限

  通过config set maxmemory可以设置一个Redis示例的内存上限,示例:

2.2.3 键过期删除策略

  Redis的内存回收机制主要体现在以下两个方面:1、删除到达过期时间的键对象;2、内存使用达到maxmemory上限时触发内存溢出控制策略。这个小节先说第1个策略,下一小节说第2个。

  Redis中有个设置时间过期的功能,即对存储在Redis 数据库中的值可以设置一个过期时间。
  我们执行set key命令 的时候,都可以指定expire time,即过期时间,以此来限定这个 key 可以存活的时间。

  对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。

  键过期策略通常有以下三种:定时过期、惰性过期和定期过期。

  • 1、定时删除
      在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作;

  定时删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放,但是对CPU是最不友好的。因为删除操作会占用CPU的时间,如果刚好碰上了CPU很忙的时候,比如正在做交集或排序等计算的时候,就会给CPU造成额外的压力。

  • 2、惰性删除
      放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,那就返回该键;
  • 3、定期删除
      每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于删除多少过期键,以及要检查多少个数据库,则由算法决定。

  由于维护每个键精准的过期删除机制会导致消耗大量的CPU,成本过高(不采用第1种策略的愿意),因此Redis采用惰性删除和定期删除机制实现过期键的内存回收。

  惰性删除:当客户端读取超时的键时,会执行删除键操作并返回空。单独用这种方式,浪费内存,存在内存泄露的问题(当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放)。正因为如此,Redis还提供另一种定时任务删除机制作为惰性删除的补充。
  定期删除:Redis内部维护一个定时任务,默认每秒运行10次,根据键的过期比例、使用快慢两种速率模式回收键。
  定时任务删除过期键流程:

  1、定时任务(每10秒执行一次,在Redis配置文件中有默认配置:hz 10)在每个数据库空间随机检查20个键,当发现过期时删除对应的键。
  2、如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止,慢模式下超时时间为25毫秒。
  3、如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次。

  此处再简单回顾一下Key的过期时间和永久有效分别怎么设置。
  setex命令可以为key设置value值,并且同时设置过期时间。示例:

  也可以用expire命令,直接为key设置过期时间。示例:

  setex是一个原子操作,设置值,设置过期时间两个动作,会在同一时间完成。

  persist命令用于移除给定 key 的过期时间,使得 key 永不过期。示例:

2.2.4 内存溢出策略

  当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略,由maxmemory-policy参数控制,示例:

  在Redis4.0以前,内存溢出策略有6种,默认值为noeviction,含义为:

  1、noeviction:默认策略,不会删除任何键,拒绝所有写入操作并返回客户端错误信息OOM command not allowed when used memory,只响应读操作。
  2、volatile-lru:根据LRU算法删除超时的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
  3、allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
  4、allkeys-random:随机删除所有键,直到腾出足够空间为止。
  5、volatile-random:随机删除过期键,直到腾出足够空间为止。
  6、volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。

  Redis4.0以后,又增加了以下两种:

  7、volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键(频率最少)。
  8、allkeys-lfu:从所有键中驱逐使用频率最少的键(频率最少)。

  从这些策略的名称可以看出:

volatile表示过期的键
allkeys表示所有键
lru表示使用LRU算法,简单来说是删除过期时间最久的键
lfu表示使用LFU算法,简单来说是删除使用频率最少的键
ttl表示马上要过期

  内存淘汰策略可以通过配置文件来修改,redis.conf对应的配置项是maxmemory-policy 修改对应的值就行。要动态配置的话,可以用config set maxmemory-policy {policy}命令进行修改。
  频繁执行回收内存成本很高,主要包括查找可回收键和删除键的开销。

2.3 内存优化

  Redis存储的所有值对象在内部定义为redisObject结构体:

  Redis存储的数据都使用redisObject来封装,包括string、hash、list、set、zset在内的所有数据类型。
  type:表示当前对象使用的数据类型。
  encoding:表示Redis内部编码类型。
  lru:记录对象最后一次被访问的时间。
  refcount:记录当前对象被引用的次数,用于通过引用次数回收内存,当refcount=0时,可以安全回收当前对象空间。使用object refcount {key}命令可以获取当前对象引用。
  *ptr:如果是整数,直接存储数据;否则表示指向数据的指针。Redis在3.0之后对值对象是字符串且长度<=39字节的数据,内部编码为embstr类型,字符串和redisObject一起分配,从而只要一次内存操作即可。

  高并发写入场景中,在条件允许的情况下,建议字符串长度控制在39字节以内,减少创建redisObject内存分配次数,从而提高性能。

2.3.1 缩减键值对象

  降低Redis内存使用最直接的方式就是缩减键和值的长度。
  在设计键时,在完整描述业务情况下,键值越短越好。
  值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数据放入Redis。值对象缩减常见的做法分为两大类:

  1、首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。
  2、其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。

  Java常见序列化组件占用内存空间对比(单位字节):

  java-built-in-serializer表示Java内置序列化方式。

  值对象除了存储二进制数据之外,通常还会使用通用格式存储数据比如:json、xml等作为字符串存储在Redis中。这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json、xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。

2.3.2 共享对象池

  共享对象池是指Redis内部维护[0-9999]的整数对象池,用于节约内存。
  整数对象池在Redis源码中定义,不能修改。
  是否使用整数对象池内存对比:

  可见使用共享对象池后,相同的数据内存使用降低30%以上。
  当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。
  LRU算法需要获取对象最后被访问时间,以便淘汰最长未访问数据,每个对象最后访问时间存储在redisObject对象的lru字段。对象共享意味着多个引用共享同一个redisObject,这时lru字段也会被共享,导致无法获取每个对象的最后访问时间。因此,使用LUR相关淘汰策略时,无法使用共享对象池。
  如果没有设置maxmemory,直到内存被用尽Redis也不会触发内存回收,所以共享对象池可以正常工作。因此,设置了maxmemory后,无法使用共享对象池。

2.3.3 字符串优化

  Redis没有采用原生C语言的字符串类型而是自己实现了字符串结构,内部简单动态字符串(simple dynamic string,SDS),图示:

  获取字符串长度、已用长度、未用长度的时间复杂度都是·O(1)。
  内部实现空间预分配机制,字符串缩减后的空间不释放,作为预分配空间保留。
  字符串之所以采用预分配的方式是防止修改操作需要不断重分配内存和字节数据拷贝。但同样也会造成内存的浪费。字符串预分配每次并不都是翻倍扩容,空间预分配规则:

  1、第一次创建len属性等于数据实际大小,free等于0,不做预分配。
  2、修改后如果已有free空间不够且数据小于1M,每次预分配一倍容量。如原有len=60byte,free=0,再追加60byte,预分配120byte,总占用空间:60byte+60byte+120byte+1byte。
  3、修改后如果已有free空间不够且数据大于1MB,每次预分配1MB数据。如原有len=30MB,free=0,当再追加100byte,预分配1MB,总占用空间:1MB+100byte+1MB+1byte。

  尽量减少字符串频繁修改操作如append、setrange,改为直接使用set修改字符串,降低预分配带来的内存浪费和内存碎片化。

2.3.4 编码优化

  Redis对外提供了string、list、hash、set、zet等类型,对内也有不同的底层数据结构的实现,可以用object encoding {key}命令查看,示例:

  Redis针对每种对外数据类型可以采用至少两种编码方式来实现,示例:

  不同编码主要是为了实现效率和空间的平衡。
  编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。示例:

  Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU。
  示例中用到了list-max-ziplist-entries参数,这个参数用来决定列表长度在多少范围内使用ziplist编码。
  ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组),内部结构:

  zlbytes:记录整个压缩列表所占字节长度,方便重新调整ziplist空间。类型是int-32,长度为4字节。
  zltail:记录距离尾节点的偏移量,方便尾节点弹出操作。类型是int-32,长度为4字节。
  zllen:记录压缩链表节点数量,当长度超过216-2时需要遍历整个列表获取长度,一般很少见。类型是int-16,长度为2字节。
  entry:记录具体的节点,长度根据实际存储的数据而定。
  zlend:记录列表结尾,占用一个字节。
  Entry的元素:

  prev_entry_bytes_length:记录前一个节点所占空间,用于快速定位上一个节点,可实现列表反向迭代。
  encoding:标示当前节点编码和长度,前两位表示编码类型:字符串/整数,其余位表示数据长度。
  contents:保存节点的值,针对实际数据长度做内存占用优化。

  ziplist的特点:

  1、底层实现为数据紧凑排列的一块连续内存数组。
  2、可以模拟双向链表结构,以O(1)时间复杂度入队和出队。
  3、新增删除操作涉及内存重新分配或释放,加大了操作的复杂性。
  4、读写操作涉及复杂的指针移动,最坏时间复杂度为O(n2)。
  5、适合存储小对象和长度有限的数据。

  intset是集合类型编码的一种,内部表现为存储有序、不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。示例:

  intset结构:

  字段含义:

  1、encoding:整数表示类型,根据集合内最长整数值确定类型,整数
类型划分为三种:int-16、int-32、int-64。
  2、length:表示集合元素个数。
  3、contents:整数数组,按从小到大顺序保存。

  intset保存的整数类型根据长度划分,当保存的整数超出当前类型时,将会触发自动升级操作且升级后不再做回退。因此,使用intset编码的集合时,尽量保持整数范围一致,如都在int-16范围内。防止个别大整数触发集合升级操作,产生内存浪费。
  intset数据结构插入命令复杂度为O(n),查询命令为O(log(n)),由于整数占用空间非常小,所以在集合长度可控的基础上,写入命令执行速度也会非常快,因此当使用整数集合时尽量使用intset编码。

2.3.5 控制键的数量

  对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。比如:通过在客户端预估键规模,把大量键分组映射到多个hash结构中降低键的数量。

  同样的数据使用ziplist编码的hash类型存储比string类型节约内存。当value越小(即小对象)越明显。
  hash-ziplist类型比string类型写入耗时,但随着value空间的减少,耗时逐渐降低。
  hash类型节省内存的原理是使用ziplist编码,如果使用hashtable编码方式反而会增加内存消耗。
  ziplist长度需要控制在1000以内,否则由于存取操作时间复杂度在O(n)到O(n2)之间,否则会导致CPU消耗严重。
  ziplist适合存储小对象,对于大对象不但内存优化效果不明显还会增加命令操作耗时。
  关于hash键和field键的设计:

  1、当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键。如:key=1948480哈希key=group:hash:1948,哈希field=480。
  2、当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值。
  3、尽量减少hash键和field的长度,如使用部分键内容。

2.4 一些相关问题 2.4.1 Mysql里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据

  要考虑两方面内容:1.Redis的内存淘汰策略。2.Redis的最大内存设置。思路:首先计算出20w数据所需的内存空间,设置最大内存(maxmemory),然后选择合适的内存淘汰策略。
  当Redis内存数据集大小上升到一定大小的时候,就会执行内存淘汰策略。示例:

  至于最大内存设置,可以对最大内存进行设置。当使用的内存到达指定的限制时,Redis会根据内存淘汰策略删除键,以释放空间。示例:

三、阻塞问题

  前文已经提过,Redis是单线程架构,因此阻塞问题是需要非常重视的。导致阻塞问题的原因大致分为:

  1、内在原因:不合理地使用API或数据结构、CPU饱和、持久化阻塞等。
  2、外在原因:CPU竞争、内存交换、网络问题等。

  当Redis阻塞时,应用会收到大量Redis超时异常,比如Jedis客户端会抛出JedisConnectionException异常,这时一般会触发告警,何时触发报警一般根据应用的并发量决定,如1分钟内超过10个异常触发报警。

3.1 内在原因

  应该围绕这几个方面去排查:API或数据结构使用不合理;CPU饱和的问题;持久化相关的阻塞。

3.1.1 API或数据结构使用不合理

  什么是典型的不合理使用API和数据结构,比如:对一个包含上万个元素的hash结构执行hgetall操作,由于数据量比较大且命令算法复杂度是O(n),这条命令执行速度必然很慢。

  • 1、慢查询
      所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis中也有这种功能。
      Redis客户端执行一条命令的过程:

      慢查询本身只记录了命令执行时间,不包括数据网络传输时间和命令排队时间,因此客户端发生阻塞异常后,可能不是当前命令缓慢,而是在等待其他命令执行。
      Redis提供了slowlog-log-slower-than和slowlog-max-len配置慢查询阀值和慢查询记录。

  slowlog-log-slower-than单位是微秒(1秒=1000毫秒=1000000微秒),默认值是10000。
  如果slowlog-log-slower-than=0会记录所有的命令,slowlog-log-slower-than<0对于任何命令都不会进行记录。

  Redis使用了一个列表来存储慢查询日志,slowlog-max-len就是列表的最大长度。一个新的命令满足慢查询条件时被插入到这个列表中,当慢查询日志列表已处于其最大长度时,最早插入的一个命令将从列表中移出,例如slowlog-max-len设置为5,当有第6条慢查询插入的话,那么队头的第一条数据就出列,第6条慢查询就会入列。
  总的来说,在Redis中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改。slowlog-log-slower-than和slowlog-max-len自然也可以用这两种方式进行修改。
  1、获取慢查询日志:slowlog get {n},示例:

  慢查询日志有4个属性组成,分别是慢查询日志的标识id、发生时间戳、命令耗时、执行命令和参数。
  2、获取慢查询日志列表当前的长度:slowlog len,示例:

  3、慢查询日志重置(即清空满查询日志列表):slowlog reset。
  使用慢查询的一些建议:

  1、对于·slowlog-max-len,并不会占用太大内存,因为Redis会对慢查询中的长命令做截断操作,线上可设置为1000以上。
  2、对于slowlog-log-slower-than,建议设置为1毫秒。
  3、由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slow get命令将慢查询日志持久化到其他存储中(例如MySQL)。

  --------------------------------------------------------------------------------------------------------
  上面介绍了一些慢查询的简单使用和原理,接下来看下慢查询在项目中的运用。
  执行slowlog get {n}命令可以获取最近的n条慢查询命令,默认对于执行超过10毫秒的命令都会记录到一个定长队列中,线上实例建议设置为1毫秒便于及时发现毫秒级以上的命令。
  如果命令执行时间在毫秒级,则实例实际OPS只有1000左右。慢查询队列长度默认128,可适当调大。

  发现慢查询后,需要作出及时调整。参考:

  1、修改为低复杂度的命令,如hgetall改为hmget等,禁用keys、sort等命令。
  2、调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。大对象拆分过程需要视具体的业务决定。

  • 2、大对象
      查看大对象的命令:redis-cli -h {ip} -p {port} bigkeys。内部原理采用分段进行scan操作,把历史扫描过的最大对象统计出来便于分析优化,示例:
3.1.2 CPU饱和

  单线程的Redis处理命令时只能使用一个CPU,CPU饱和是指Redis把单核CPU使用率跑到接近100%。使用top命令可以看出对应Redis进程的CPU使用率。
  当出现CPU饱和情况时,可以查看当前Redis的并发量是否达到极限,建议使用命令redis-cli -h {ip} -p {port} --stat获取当前Redis实时的使用情况,示例:

  如果只有几百或几千OPS的Redis实例就接近CPU饱和是很不正常的,有可能使用了高算法复杂度的命令。

3.1.3 持久化阻塞

  对于开启了持久化功能的Redis节点,需要排查是否是持久化导致的阻塞。持久化引起主线程阻塞的操作主要有:fork阻塞、AOF刷盘阻塞、HugePage写操作阻塞。

  • 1、fork阻塞
      在RDB和AOF重写时,Redis主线程调用fork操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果fork操作本身耗时过长,必然会导致主线程的阻塞。
      可以执行info stats命令获取到latest_fork_usec指标,表示Redis最近一次fork操作耗时,如果耗时很大,比如超过1秒,则需要做出优化调整,如避免使用过大的内存实例和规避fork缓慢的操作系统等。示例:
  • 2、AOF刷盘阻塞
      开启AOF持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对AOF文件做fsync操作。当硬盘压力过大时,fsync操作需要等待,直到写入完成。如果主线程发现距离上一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成。这种阻塞行为主要是硬盘压力引起。此时的日志示例:

      也可以查看info persistence命令中的aof_delayed_fsync指标,每次发生fdatasync阻塞主线程时会累加。示例:
  • 3、HugePage写操作阻塞
      子进程在执行重写期间利用Linux写时复制技术降低内存开销,因此只有写操作时Redis才复制要修改的内存页。对于开启THP的操作系统,每次写命令引起的复制内存页单位由4K变为2MB,会拖慢写操作的执行时间,导致大量写操作慢查询。
3.2 外在原因

  外在原因主要围绕三个方面进行排查:CPU竞争、内存交换、网络问题。

3.2.1 CPU竞争

  进程竞争:Redis是典型的CPU密集型应用。当其他进程过度消耗CPU时,将严重影响Redis吞吐量。可以通过top等命令定位到CPU消耗的时间点和具体进程,这个问题需要调整服务之间部署结构。
  绑定CPU:部署Redis时为了充分利用多核CPU,通常一台机器部署多个实例。常见的一种优化是把Redis进程绑定到CPU上,用于降低CPU频繁上下文切换的开销。这种优化存在意外情况:

  当Redis父进程创建子进程进行RDB/AOF重写时,如果做了CPU绑定,会与父进程共享使用一个CPU。子进程重写时对单核CPU使用率通常在90%以上,父进程与子进程将产生激烈CPU竞争,极大影响Redis稳定性。因此对于开启了持久化或参与复制的主节点不建议绑定CPU。

3.2.2 内存交换

  如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。
  识别Redis内存交换的检查方法:

  • 1、查询Redis进程号,示例:
  • 2、根据进程号查询内存交换信息,示例:

      如果交换量都是0KB或者个别的是4KB,则是正常现象,说明Redis进程内存没有被交换。预防内存交换的方法有:

  1、保证机器充足的可用内存。
  2、确保所有Redis实例设置最大可用内存(maxmemory),防止极端情况
下Redis内存不可控的增长。
  3、降低系统使用swap优先级,如echo10>/proc/sys/vm/swappiness.

3.2.3 网络问题

  网络问题经常是引起Redis阻塞的问题点。常见的网络问题主要有:连接拒绝、网络延迟、网卡软中断等。

  • 1、连接拒绝
      当出现网络闪断或者连接数溢出时,客户端会出现无法连接Redis的情况。需要区分这三种情况:网络闪断、Redis连接拒绝、连接溢出。
      网络闪断:一般发生在网络割接或者带宽耗尽的情况。对于重要的Redis服务需要充分考虑部署架构的优化,尽量避免客户端与Redis之间异地跨机房调用。
      当Redis连接数大于maxclients时会拒绝新的连接进入,被拒绝的连接数可以用info stats命令查看,示例:

      客户端访问Redis时尽量采用NIO长连接或者连接池的方式。
      连接溢出的原因较多,其中两种:进程限制、backlog队列溢出。
      客户端想成功连接上Redis服务需要操作系统和Redis的限制都通过才可以。
      系统对于特定端口的TCP连接使用backlog队列保存。Redis默认的长度为511,通过tcp-backlog参数设置。系统的backlog默认值为128,使用echo511>/proc/sys/net/core/somaxconn命令进行修改。可以通过netstat-s命令获取因backlog队列溢出造成的连接拒绝统计,示例:
  • 2、网络延迟
      网络延迟取决于客户端到Redis服务器之间的网络环境。主要包括它们之间的物理拓扑和带宽占用情况。常见的物理拓扑按网络延迟由快到慢可分为:同物理机>同机架>跨机架>同机房>同城机房>异地机房。
      可以在redis-cli -h {host} -p {port}命令后面加入如下参数进行延迟测试:

  1、--latency:持续进行延迟测试,分别统计:最小值、最大值、平均值、采样次数。
  2、--latency-history:统计结果同–latency,默认每15秒完成一行统计。
  3、--latency-dist:使用统计图的形式展示延迟统计,每1秒采样一次。

  网络延迟问题经常出现在跨机房的部署结构上。
  带宽瓶颈通常出现在以下几个方面:机器网卡带宽、机架交换机带宽、机房之间专线带宽。
  带宽占用主要根据当时使用率是否达到瓶颈有关,如频繁操作Redis的大对象对于千兆网卡的机器很容易达到网卡瓶颈。

  • 3、网卡软中断
      网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在同一个CPU,导致无法充分利用多核CPU的情况。网卡软中断瓶颈一般出现在网络高流量吞吐的场景。
      相关情况可以用top命令查看,示例:

      可以很明显看到CPU1的软中断指标(si)过高。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/288319.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号