vlambda博客
学习文章列表

干货总结--Redis运维全面讲解

Redis分享

Redis分享数据结构集群模式优点缺点基本原理主从切换主从数据同步数据丢失问题针对cluster模式的坑其他模式主从模式哨兵模式故障恢复代理模式持久化机制Redis使用场景缓存一致性缓存击穿缓存穿透缓存雪崩运维注意问题排查常见的运维故障规范冷热数据区分业务数据分离消息大小限制连接数限制缓存 Key 设置失效时间缓存不能有中间态扩展方式首选客户端 hashKey 规范操作限制严禁使用 Keys严禁使用 Flush严禁作为消息队列使用严禁不设置范围的批量操作禁用 select 函数禁用事务禁用 lua 脚本扩展禁止长时间 monitorRedis为什么这么快


数据结构

一共有10种,常用的有5种

  • String字符串

  • Hash 字典

  • List 列表

  • Set集合

  • ZSet 有序集合


  • Pubsub 发布订阅 (不推荐使用,坑很多)

  • Bitmap 位图

  • GEO 地理位置 (有限使用,附近的人)

  • Stream 流(5.0) (与Kafka非常像)

  • Hyperloglog 基数统计,HyperLogLog 提供不精确的去重计数方案




集群模式

redis提供了非常丰富的集群模式:主从哨兵cluster,满足服务高可用的需求。

其中cluster模式为目前大多数公司所采用的方式。redis cluster是redis亲生的集群方案,它的主要特点就是去中心化,无需proxy代理。其中一个主要设计目标就是达到线性可扩展性。



优点

  1. 不再需要额外的Sentinel集群,为使用者提供了一致的方案,减少了学习成本。

  2. 去中心架构,节点对等,集群可支持上千个节点。

  3. 副本功能能够实现自动故障转移,大部分情况下无需人工介入。

  4. 可水平线性扩展




缺点

  1. 从库是完全的冷备,无法分担读操作

  2. 数据是通过异步复制的,不能保证数据的强一致性。(其实不单是cluster模式的问题)

  3. 资源隔离困难,经常流量不均衡,尤其是多个业务共用集群的时候。可能存在热点数据问题。

  4. 数据迁移是基于key而不是基于slot的,过程较慢。

  5. 一些批量操作。由于key被hash到多台机器上,所以mget、hmset、sunion等操作就非常的不友好,经常发生性能问题。



基本原理

redis cluster采用虚拟槽的概念,把所有的key映射到 0~16383一共16384个整数槽内,当需要在其中存取一个key时,redis客户端会首先对这个key采用crc16算法算出一个值,然后对这个值进行mod操作。

crc16(key)mod 16384





redis cluster所有的集群操作都是围绕slot进行。所以必须存储slot的存储关系。

redis节点发送心跳包时,需要把所有的槽信息放在这个心跳包里,所以必须优化使数据量最小。

使用使用bitmap来存储是最节省空间的。这个数组的长度为 16384/8=2048 Byte,2K字节。






请求随机落到一个主节点,该节点检查key是否在自己负责的slot上,是就处理。不是就根据信息将请求转发到对应的节点上。所以,客户端连接集群中的任意一台机器,都能够完成操作。

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。(可以配置,使集群强制可读)



主从切换

redis-cluster可以自动完成一定程度的故障转移。

集群中的每个节点都会定期地向集群中的其他节点发送ping消息,以此来检测对方是否在线,如果接收ping消息的节点没有在规定的时间内返回pong消息,那么发送ping消息的节点就会将接收ping消息的节点标记为疑似下线(PFAIL)。

如果在一个集群里面,半数以上节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线(FAIL),将x标记为FAIL的节点会向集群广播一条关于x的FAIL消息,所有收到这条FAIL消息的节点都会立即将x标记为FAIL。



大家可以注意到这个过程,与es和zk的节点判断类似,都是半数以上才进行判断,所以主节点的数量一般都是奇数。

当一个节点发现自己的主节点进入fail状态,将会从这个主节点的从节点当中,选出一台,执行slaveof no one命令,变身为主节点。

新的节点完成自己的槽指派以后,会向集群广播一条pong消息,以便让其他节点立即知道自己的这些变化。它告诉别人:我已经是主节点了,我已经接管了有问题的节点,成为了新的主节点。



主从数据同步

当一台从机连接到master之后,会发送一个sync指令。master在收到这个指令后,会在后台启动存盘进程。执行完毕后,master将整个数据库文件传输到slave,这样就完成了第一次全量同步。

接下来,master会把自己收到的变更指令,依次传送给slave,从而达到数据的最终同步。从redis 2.8开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。



数据丢失问题

redis cluster中节点之间使用异步复制,并没有类似kafka这种ack的概念。节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升,完成这个过程注定了需要时间。在发生故障的过程中就容易存在窗口,导致丢失写入的数据。比如以下两种情况。



一、命令已经到到master,此时数据并没有同步到slave,master会对客户端回复ok。如果这个时候主节点宕机,那么这条数据将会丢失。redis这样做会避免很多问题,但对一个对数据可靠性要求较高的系统,是不可忍受的。



二、由于路由表是在客户端存放的,存在一个时效问题。如果分区导致一个节点不可达,提升了某个从节点,但原来的主节点在这个时候又可以用了(并未完成failover)。这个时候一旦客户端的路由表并没有更新,那么它将会把数据写到错误的节点,造成数据丢失。

所以redis cluster在通常情况下运行的很好,在极端情况下某些值丢失问题,目前无解



针对cluster模式的坑

1、redis cluster号称能够支持1k个节点,但你最好不要这么做。当节点数量增加到10,就能够感受到集群的一些抖动。

2、一定要避免产生热点,如果流量全部打到了某个节点,后果一般很严重。

3、大key不要放redis,它会产生大量的慢查询,影响正常的查询。

4、如果你不是作为存储,缓存一定要设置过期时间。占着茅坑不拉屎的感觉是非常讨厌的。

5、大流量,不要开aof,开rdb即可。

6、redis cluster的操作,少用pipeline,少用multi-key,它们会产生大量不可预料的结果。



其他模式



主从模式

干货总结--Redis运维全面讲解



redis最早支持的,就是M-S模式,也就是一主多从。redis单机qps可达到10w+,但是在某些高访问量场景下,依然不太够用。一般通过读写分离来增加slave,减少主机的压力。



数据同步问题



先进行全量同步,再进行增量同步。主从同步是异步进行的。

要注意的一个点是:

run_id是master唯一标示,slave连接master时会传runid,master每次重启runid都发生变化,当slave发现master的runid变化时都会触发全量复制流程。



哨兵模式


哨兵模式是主从的升级版,因为主从的出现故障后,不会自动恢复,需要人为干预。所以在主从的基础上,实现哨兵模式就是为了监控主从的运行状况,对主从的健壮进行监控。并且当master出现故障的时候,会自动选举一个slave作为master顶上去。



故障恢复

当master被认为客观下线后,首先需要在哨兵中选出一个老大哨兵进行故障恢复。选举老大哨兵的算法还是Raft算法

选出大佬哨兵后,大佬哨兵就会对故障进行自动恢复,即从slave中选出一名slave作为master

  1. 所有的slave中   slave-priority优先级最高的会被选中。

  2. 若是优先级相同,会选择偏移量最大的,因为偏移量记录着数据的复制的增量,越大表示数据越完整。

  3. 若是以上两者都相同,选择RunID最小的。



代理模式

代理模式在redis cluster出现之前,非常流行,比如codis,predixy。



持久化机制

redis提供了两种持久化方式:aofrdb,常用的是rdb。RDB 和 AOF 两种方式也可以同时使用,也可以都不用,也可以只用一个。



RDB

Redis DataBase。简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘上;

AOF

则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了



对于 RDB 方式,redis 会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何 IO 操作的,这样就确保了 redis 极高的性能。

RDB 有不少优点,但它的缺点也是不容忽视的。

即使你每 5 分钟都持久化一次,当 redis 故障时,仍然会有近 5 分钟的数据丢失


默认的 AOF 持久化策略是每秒钟 fsync 一次(fsync 是指把缓存中的写指令记录到磁盘中),

因为在这种情况下,redis 仍然可以保持很好的处理性能,即使 redis 故障,也只会丢失最近 1 秒钟的数据。

也可以配置每次执行命令都fsync一次。

因为采用了追加方式,如果不做任何处理的话,AOF 文件会变得越来越大,为此,redis 提供了 AOF 文件重写(rewrite)机制,即当 AOF 文件的大小超过所设定的阈值时,redis 就会启动 AOF 文件的内容压缩


rdb的优点是文件小,恢复快。但是存在数据丢失较多的风险。

aof的有点是数据丢失风险较小,缺点是文件较大。



Redis使用场景

  • 缓存 (缓存一致性 缓存穿透 缓存击穿 缓存雪崩)

  • 分布式锁 (redlock)

  • 分布式限流

  • Session共享



缓存一致性

为什么有一致性问题?

  • 写入。缓存和数据库是两个不同的组件,只要涉及到双写,就存在只有一个写成功的可能性,造成数据不一致。

  • 更新。更新的情况类似,需要更新两个不同的组件。

  • 读取。读取要保证从缓存中读到的信息是最新的,是和数据库中的是一致的。

  • 删除。当删除数据库记录的时候,如何把缓存中的数据也删掉?



建议使用:Cache Aside Pattern

读请求:

  • 先读cache,再读db

变更操作:

  • 不要更新缓存,直接淘汰缓存

  • 先操作数据库,再 淘汰 缓存

涉及到复杂的事务和回滚操作,可以把淘汰放在finally里。



缓存击穿

影响,轻微。

高流量下 大量请求读取一个失效的Key -> Redis Miss -> 穿透到DB

解决方式:采用分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存



缓存穿透

影响,一般。

故意访问一个不存在的Key(恶意攻击)-> Redis Miss -> 穿透到DB

解决方式:

  1. 给相应的Key设置一个Null值,放在缓存中

  2. BloomFilter预先判断。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。



缓存雪崩

影响:严重。

大量Key同时失效 | Redis当机 -> Redis Miss -> 压力打到DB

解决方式:

  1. 给失效时间加上相对的随机数

  2. 保证Redis的高可用




运维注意

问题排查

  • monitor指令 回显所有执行的指令。可以使用grep配合过滤

  • keyspace-events 订阅某些Key的事件。比如,删除某条数据的事件,底层实现基于pubsub

  • slow log 顾名思义,慢查询,非常有用

  • --bigkeys启动参数 Redis大Key健康检查。使用的是scan的方式执行, 不用担心阻塞

  • memory usage keymemory stats 指令

  • info指令,关注instantaneous_ops_per_secused_memory_humanconnected_clients

  • redis-rdb-tools rdb线下分析



常见的运维故障

  • 使用 keys * 把库堵死,——使用别名把这个命令改名

    rename



  • 超过最大内存(maxmemory)后,部分数据被删除——这个有删除策略的,选择适合自己的即可

    1. volatile-lru 从设置过期数据集里查找最近最少使用

    2. volatile-ttl 从设置过期的数据集里面优先删除剩余时间短的Key

    3. volatile-random 从设置过期的数据集里面任意选择数据淘汰

    4. volatile-lfu 从过期的数据集里删除 最近不常使用 的数据淘汰

    5. allkeys-lru

    6. allkeys-lfu

    7. allkeys-random

    8. no-enviction 禁止删除,默认策略




  • 没开持久化,却重启了实例,数据全掉

    记得非缓存的信息需要打开持久化RDB 的持久化需要 vm.overcommit_memory=1,否则有可能会持久化失败没有持久化情况下,主从,主重启太快,从还没认为主挂的情况下,从会清空自己的数据——人为重启主节点前,先关闭从节点的同步



  • 如果不设置maxmemory,Redis将一直使用内存,直到触发操作系统的OOM-KILLER。



规范

冷热数据区分

虽然 Redis支持持久化,但将所有数据存储在 Redis 中,成本非常昂贵。建议将热数据 (如 QPS超过 5k) 的数据加载到 Redis 中。低频数据可存储在 Mysql、 ElasticSearch中。

业务数据分离

不要将不相关的数据业务都放到一个 Redis中。一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。

消息大小限制

由于 Redis 是单线程服务,消息过大会阻塞并拖慢其他操作。保持消息内容在 1KB 以下是个好的习惯。严禁超过 50KB 的单条记录。消息过大还会引起网络带宽的高占用,持久化到磁盘时的 IO 问题。

连接数限制

连接的频繁创建和销毁,会浪费大量的系统资源,极限情况会造成宿主机当机。请确保使用了正确的 Redis 客户端连接池配置。

缓存 Key 设置失效时间

作为缓存使用的 Key,必须要设置失效时间。失效时间并不是越长越好,请根据业务性质进行设置。注意,失效时间的单位有的是秒,有的是毫秒,这个很多同学不注意容易搞错。

缓存不能有中间态

缓存应该仅作缓存用,去掉后业务逻辑不应发生改变,万不可切入到业务里。第一,缓存的高可用会影响业务;第二,产生深耦合会发生无法预料的效果;第三,会对维护行产生负效果。

扩展方式首选客户端 hash

小应用就算了

如单 redis 集群并不能为你的数据服务,不要着急扩大你的 redis 集群(包括 M/S 和 Cluster),集群越大,在状态同步和持久化方面的性能越差。优先使用客户端 hash 进行集群拆分。如:根据用户 id 分 10 个集群,用户尾号为 0 的落在第一个集群。

Key 规范

Redis 的 Key 一定要规范,这样在遇到问题时,能够进行方便的定位。Redis 属于无 scheme 的 KV 数据库,所以,我们靠约定来建立其 scheme 语义。其好处:

1、能够根据某类 key 进行数据清理

2、能够根据某类 key 进行数据更新

3、能够方面了解到某类 key 的归属方和应用场景

4、为统一化、平台化做准备,减少技术变更

一般,一个 key 需要带以下维度:业务、key 用途、变量等,各个维度使用 : 进行分隔

操作限制

严禁使用 Keys

Keys 命令效率极低,属于 O(N)操作,会阻塞其他正常命令,在 cluster 上,会是灾难性的操作。严禁使用,DBA 应该 rename 此命令,从根源禁用。

严禁使用 Flush

flush 命令会清空所有数据,属于高危操作。严禁使用,DBA 应该 rename 此命令,从根源禁用,仅 DBA 可操作。

严禁作为消息队列使用

如没有非常特殊的需求,严禁将 Redis 当作消息队列使用。Redis 当作消息队列使用,会有容量、网络、效率、功能方面的多种问题。如需要消息队列,可使用高吞吐的 Kafka 或者高可靠的 RocketMQ。

严禁不设置范围的批量操作

redis 那么快,慢查询除了网络延迟,就属于这些批量操作函数。大多数线上问题都是由于这些函数引起。

1、[zset] 严禁对 zset 的不设范围操作

ZRANGE、 ZRANGEBYSCORE等多个操作 ZSET 的函数,严禁使用 ZRANGE myzset 0 -1 等这种不设置范围的操作。请指定范围,如 ZRANGE myzset 0 100。如不确定长度,可使用 ZCARD 判断长度

2、[hash] 严禁对大数据量 Key 使用 HGETALL

HGETALL会取出相关 HASH 的所有数据,如果数据条数过大,同样会引起阻塞,请确保业务可控。如不确定长度,可使用 HLEN 先判断长度

3、[key] Redis Cluster 集群的 mget 操作

Redis Cluster 的 MGET 操作,会到各分片取数据聚合,相比传统的 M/S架构,性能会下降很多,请提前压测和评估

4、[其他] 严禁使用 sunion, sinter, sdiff等一些聚合操作

禁用 select 函数

select函数用来切换 database,对于使用方来说,这是很容易发生问题的地方,cluster 模式也不支持多个 database,且没有任何收益,禁用。

禁用事务

redis 本身已经很快了,如无大的必要,建议捕获异常进行回滚,不要使用事务函数,很少有人这么干。

禁用 lua 脚本扩展

lua 脚本虽然能做很多看起来很 cool 的事情,但它就像是 SQL 的存储过程,会引入性能和一些难以维护的问题,禁用。

禁止长时间 monitor

monitor函数可以快速看到当前 redis 正在执行的数据流,但是当心,高峰期长时间阻塞在 monitor 命令上,会严重影响 redis 的性能。此命令不禁止使用,但使用一定要特别特别注意。


Redis为什么这么快

基于内存

单线程,没有cpu的上下文切换,无锁

io多路复用