Redis 开发与运维学习笔记(三)

Posted by Waynerv on

category: 数据库

Tags: Redis 读书笔记

6 复制

配置

建立复制

复制的数据流是单向的,只能由主节点复制到从节点。

配置复制的方式有以下三种:

  • 在配置文件中加入slaveof {masterHost} {masterPort},随Redis启动生效。
  • 在redis-server启动命令后加入--slaveof {masterHost} {masterPort}生效。
  • 直接使用命令:slaveof {masterHost} {masterPort}生效。

slaveof配置都是在从节点发起,主从节点复制成功建立后,可以使用info replication命令查看复制相关状态。

断开复制

在从节点执行slaveof no one命令可以断开与主节点复制关系,断开复制后从节点不会丢弃原来同步所得的数据集。

断开复制主要流程:

  • 断开与主节点复制关系
  • 从节点晋升为主节点

执行slaveof {newMasterIp} {newMasterPort}命令即可切换到新的复制主节点,切主后从节点会清空之前所有的数据(因为会进行全量复制)。

安全性

主节点通过设置requirepass参数进行密码验证后,需要配置从节点的masterauth参数与主节点密码保持一致,这样从节点才可以正确地连接到主节点并发起复制流程。

只读

默认情况下,从节点使用slave-read-only=yes配置为只读模式。

复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点会造成主从数据不一致。

建议线上不要修改从节点的只读模式。

传输延迟

Redis提供了repl-disable-tcp-nodelay参数用于控制是否关闭TCP_NODELAY,默认关闭。

  • 关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点。适用于主从之间的网络环境良好的场景,如同机架或同机房部署。
  • 开启时,主节点会合并较小的TCP数据包一起发送从而节省带宽。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。

拓扑

Redis的复制拓扑结构根据复杂性可以分为三种:一主一从、一主多从、树状主从结构。

一主一从结构

最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持。

可以只在从节点上开启AOF,避免持久化对主节点的性能干扰。但注意一定要避免主节点脱机后自动重启(因未开启持久化此时数据集为空),否则从节点数据也会被清空。可以断开与主节点复制关系再重启。

一主多从结构

一主多从结构使得应用端可以利用多个从节点实现读写分离。

  • 对于读占比较大的场景,可以把读命令发送到从节点或在从节点执行耗时的慢查询来分担主节点压力。
  • 对于写并发量较高的场景,多个从节点会导致主节点写命令的多次发送,从而过度消耗网络带宽并且加重主节点的负载。

树状主从结构

树状主从结构使得从节点可以在复制主节点数据的同时,作为其他从节点的主节点继续向下层复制。

当主节点需要挂载多个从节点时,为了避免对主节点的性能干扰,可采用树状主从结构降低主节点压力。

原理

复制过程

复制过程大致分为6个过程:

  1. 保存主节点的地址信息后直接返回(客户端),打印命令日志。

  2. 尝试与主节点建立网络连接,从节点会新建一个网络套接字专门接受主节点的复制命令。

    从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后会尝试连接。如果无法建立连接会无限重试直到连接成功或取消复制。

  3. 发送ping命令进行首次通信。

    检测主从之间网络套接字是否可用以及主节点是否可接受处理命令,如果从节点没有收到主节点的pong回复或者超时,从节点会断开复制连接并在下次定时任务发起重连。

  4. 权限验证。

    如果主节点设置了requirepass参数,从节点必须配置masterauth参数(config set设置或写入配置文件)保证与主节点相同的密码才能通过验证。未通过验证从节点将重新发起复制流程。

  5. 同步数据集。

    首次建立复制时,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。使用命令psync进行数据同步,同步分两种情况: 全量同步和部分同步。

  6. 命令持续复制。

    主节点会持续地把写命令发送给从节点,保证主从数据一致性。

数据同步

Redis在2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。

  • 全量复制:把主节点全部数据一次性发送给从节点,性能及网络开销大。
  • 部分复制:处理主从复制中因网络闪断等原因造成的数据丢失,主节点仅补发丢失数据给从节点。

psync命令运行需要以下组件支持:

  1. 主从节点各自复制偏移量。
  2. 主节点复制积压缓冲区。
  3. 主节点运行id。
复制偏移量

参与复制的主从节点都会维护自身复制偏移量。主节点处理完写入命令后会对命令的字节长度做累加记录,同时保存从节点每秒上报的自身复制偏移量;从节点在接收主节点发送的命令后也会累加记录自身的偏移量。

通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。根据偏移量差值还可判定当前复制的健康度。

复制积压缓冲区

复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为1MB。

主节点(master)响应写命令时,不但会发送命令给从节点,还会写入复制积压缓冲区,用于部分复制和复制命令丢失的数据补救。

主节点运行ID

每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID,运行ID的主要作用是用来唯一识别Redis节点。

当主节点运行ID变化后从节点将做全量复制。

可以使用debug reload命令重新加载RDB并保持运行ID不变,从而有效避免不必要的全量复制。该命令阻塞当前节点主线程,应谨慎使用。

psync命令

从节点使用psync命令完成部分复制和全量复制功能:

psync {runId} {offset}

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

主节点(master)根据psync参数和自身数据情况决定触发全量复制还是部分复制。

全量复制

  1. 从节点发送psync命令进行数据同步,假设为第一次复制,发送 psync -1。

  2. 主节点根据psync-1解析出当前为全量复制,回复+FULLRESYNC {runId} {offset}响应。

  3. 从节点接收主节点的响应数据保存运行ID和偏移量offset。

  4. 主节点执行bgsave保存RDB文件到本地。

  5. 主节点发送RDB文件给从节点,从节点把接收的RDB文件保存在本地并直接作为从节点的数据文件。传输时间超过repl-timeout配置值将导致全量复制失败。

    Redis支持无盘复制,生成的RDB文件不保存到硬盘而是直接通过网络发送给从节点,通过repl-diskless-sync参数控制,默认关闭。

  6. 从节点开始接收RDB快照到接收完成期间,主节点仍然响应读写命令并保存在复制客户端缓冲区内,从节点加载完RDB文件后,主节点再发送缓冲区内的数据,保证主从之间数据一致性。

    可根据主节点数据量和写命令并发量调整client-output-buffer-limit slave配置,避免全量复制期间客户端缓冲区溢出导致同步失败

  7. 从节点接收完主节点传送来的全部数据后会清空自身旧数据。

  8. 从节点清空数据后开始加载RDB文件。此时依然响应读命令,可配置关闭。

  9. 从节点成功加载完RDB后,如果当前节点开启了AOF持久化功能会立刻做bgrewriteaof操作。

全量复制非常耗时,除了第一次复制时其他场景应尽量规避。

部分复制

复制过程中出现网络闪断或命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区内存在这部分数据则直接发送给从节点。

  1. 当主从节点之间网络出现中断时,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接。
  2. 主从连接中断期间主节点依然响应命令,命令存在复制积压缓冲区。
  3. 当主从节点网络恢复后,从节点会再次连上主节点。
  4. 从节点将之前保存的自身已复制的偏移量和主节点的运行ID当作psync参数发送给主节点,要求进行部分复制操作。
  5. 主节点接到psync命令后首先核对参数runId是否与自身一致;之后根据参数offset在查找偏移量之后的数据是否存在缓冲区中,若存在则响应进行部分复制。
  6. 主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。

心跳

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

  • 主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信。
  • 主节点默认每隔10秒对从节点发送ping命令,判断从节点的存活性和连接状态。
  • 从节点在主线程中每隔1秒发送replconf ack {offset}命令,给主节点上报自身当前的复制偏移量。

主节点根据replconf命令判断从节点超时时间,如果延迟超过repl-timeout配置的值(默认60秒),则判定从节点下线并断开复制客户端连接。

异步复制

主节点发送写命令的过程是异步的,也就是说主节点自身处理完写命令后直接返回响应结果给客户端,并不等待从节点复制完成。写命令异步发送给从节点,从节点在主线程中执行复制的命令。

由于主从复制过程是异步的,就会造成从节点的数据相对主节点存在延迟。延迟的字节量可通过主节点执行info replication命令查看主从节点复制偏移量差值获得。

开发与运维中的问题

读写分离

当使用从节点响应读请求时(写操作只对主节点执行),业务端可能会遇到如下问题:

  1. 数据延迟。由于异步复制特性无法避免,需要业务场景允许短时间内的数据延迟。

    对于无法容忍大量延迟的场景,可以编写外部监控程序监听主从节点的复制偏移量,当延迟较大时触发报警或者通知客户端避免读取延迟过高的从节点。成本高,需要单独修改适配Redis的客户端类库。

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

    当主节点存储大量设置超时的数据时,Redis内部需要维护过期数据删除策略。

    惰性删除策略:每次处理读取命令时检查键是否超时,若超时执行del命令删除键,并异步发送到del命令到从节点,从节点自身永远不会主动删除超时数据。

    定时删除策略:主节点在内部定时任务会循环采样一定数量的键,当发现采样的键过期时执行del命令,之后再同步给从节点。

  3. 从节点故障问题。在客户端维护可用从节点列表,当从节点故障时立刻切换到其他从节点或主节点上。需要开发人员改造客户端类库。

开发人员在使用额外的从节点提升读性能之前,应尽量在主节点上做充分优化,比如解决慢查询,持久化阻塞,合理应用数据结构等,当主节点优化空间不大时再考虑扩展,还可以考虑使用Redis Cluster等分布式解决方案。

主从配置不一致

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

规避全量复制

  1. 第一次建立复制:无法避免,建议在低峰时进行操作。
  2. (主节点故障重启后)节点运行ID不匹配:从架构上规避,当主节点发生故障后,手动提升从节点为主节点或者采用支持自动故障转移的哨兵或集群方案。
  3. 复制积压缓冲区不足:根据网络中断时长、写命令数据量设置合理的积压缓冲区大小。

规避复制风暴

  1. 单主节点复制风暴。

    一般发生在主节点挂载多个从节点的场景,同时向多个从节点发送RDB快照可能使主节点的网络带宽消耗严重。

    解决方案:减少挂载从节点的数量,或者采用树状复制结构加入中间层从节点用来保护主节点。

  2. 单机器复制风暴。

    当单机器上的多个Redis实例因机器故障重启恢复后,会有大量从节点针对这台机器的主节点进行全量复制,造成当前机器网络带宽耗尽。

    解决方案:避免在单台机器上部署过多的主节点;提供故障转移机制,避免机器恢复后进行密集的全量复制。

7 阻塞

发现阻塞

应用端统计报警

Redis阻塞时,应用方客户端会收到大量Redis超时异常。

异常统计

常见做法时在应用方加入异常统计并通过邮件/短信/微信报警,可以借助日志系统统计项目中不同API位置的异常。

定位异常节点

修改客户端类库,在异常信息中打印ip和port信息。

服务端监控

借助第三方开源的Redis监控系统发现阻塞问题,当检测到Redis运行期的一些关键指标出现不正常时触发报警。

监控关键指标

命令耗时、慢查询、持久化阻塞、连接拒绝、CPU/内存/网络/磁盘使用过载。

内在原因

API或数据结构使用不合理

尽量避免在大对象上执行算法复杂度超过O(n)的命令。

慢查询

执行 slowlog get {n}命令可以获取最近的n条慢查询命令。

解决方案:将慢查询修改为低算法度的批量命令。

大对象

执行 redis-cli -h {ip} -p {port} bigkeys命令获取大对象统计信息。

解决方案:调整大对象,缩减大对象数据或把大对象拆分为多个小对象。

CPU饱和

单线程的Redis处理命令时只能使用一个CPU。并发量过大时,CPU会接近饱和。

使用统计命令 redis-cli -h {ip} -p {port} --stat获取当前Redis实例的使用情况(每秒)。

需要做集群化水平扩展来分摊OPS压力或分析是否有高算法复杂度的命令。

持久化阻塞

fork阻塞

fork操作发生在RDB和AOF重写时,如果fork操作本身耗时过长,必然会导致主线程的阻塞。

关注指标:执行info stats命令获取latest_fork_usec指标,表示Redis最近一次fork操作耗时

解决方案:避免使用过大的内存实例和规避fork缓慢的操作系统

AOF fsync阻塞

当硬盘压力过大时,fsync操作需要等待,如果主线程发现距离上一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成。

关注指标:查看info persistence统计中的aof_delayed_fsync指标,每次发生fdatasync阻塞主线程时会累加。

HugePage写操作阻塞

子进程在执行重写期间,开启Transparent HugePages的操作系统,每次写命令引起的复制内存页单位由4K变为2MB,放大了512倍,会拖慢写操作的执行时间。

其他参考文档:

外在原因

  • CPU竞争

    其他进程过度消耗CPU或绑定CPU后持久化的父子进程共享使用一个CPU。

  • 内存交换

    可用内存不足时操作系统会把Redis使用的部分内存换出到硬盘,通过info命令查看当前已使用内存。

    查询Redis进程号:

    # redis-cli -p 6383 info server | grep process_id
    process_id:4476
    

    根据进程号查询内存交换信息:

    # cat /proc/4476/smaps | grep Swap
    

    超过4KB说明Redis进程内存被交换。

  • 网络问题

    连接拒绝、网络延迟、网卡软中断等,使用 redis-cli --latency -h host -p port 命令测量网络延迟。

8 理解内存

Redis实际内存消耗主要包括: 键值对象、缓冲区内存、内存碎片。

内存管理

  1. 设置合理的内存上限maxmemory
  2. config set maxmemory动态调整内存上限(执行config rewrite写入配置文件)。
  3. 调整内存回收策略。
    • 删除到达过期时间的键对象。
      • 惰性删除
      • 定时任务删除
    • 内存使用达到maxmemory上限时触发内存溢出控制策略,具体策略受maxmemory-policy参数控制。
      • no-eviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息。
      • volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。
      • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。
      • volatile-random:从已设置过期时间的数据集中随机删除键,直到腾出足够空间为止。
      • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
      • allkeys-random:随机删除所有键,直到腾出足够空间为止。
    • redis 并不是保证取得所有过期时间的表中最快过期的键值对,而只是随机挑选的几个键值对中的。

内存优化

  • 精简键值对大小,键值字面量精简,使用高效二进制序列化工具。
  • 使用对象共享池优化小整数对象。
  • 数据优先使用整数,比字符串类型更节省空间。优化字符串使用,避免预分配造成的内存浪费。
  • 使用ziplist压缩编码优化hash、list等结构,注重效率和空间的平衡。使用intset编码优化整数集合。使用ziplist编码的hash结构降低小对象链规模。
  • 控制键的数量。

注:转载本文,请与作者联系




如果觉得文章对您有价值,请作者喝杯咖啡吧

|
donate qrcode

欢迎通过微信与我联系

wechat qrcode

0 Comments latest

No comments.