1. Redis高级

2. 过期数据

Redis是一个内存级别的数据库 所有数据都存放在内存中 我们可以通过指令TTL获取指定数据状态

  • XX 具有时效性的数据
  • -1 永久有效的数据
  • -2 已经过期的数据 或被 已删除 或 未定义的数据

03. Redis高级 - 图1

我们通过SETEX设置的有效时间 redis会新开辟一个键值对 用来存储key对应的value地址值 以及存活时间

2.1. 定时删除

创建一个定时器,当key设置的过期时间到达时,由定时器执行的对键删除操作

优化:节约内存 快速释放不必要的内容

缺点:cpu负载量大 会影响redis服务器响应时间和指令吞吐量

使用处理性能换取存储空间

2.2. 惰性删除

数据到达过期时间,不做处理,等下次访问该数据时删除

  • 如果未过期 返回数据
  • 如果已过期 删除 返回不存在

通过get方法查询时,get绑定expirelfNeeded()方法 此方法会检测数据是否过期

优点:节约cpu性能 只有访问时才进行判别删除

缺点:内存压力大 出现长期占用内容

用存储空间换取处理器性能

2.3. 定期删除

通过设置redis.config文件中的 hz 属性 默认为10

为每秒执行hz的次数

设置后服务器每秒执行hz次 serverCron() —> databasesCron() —> activeExpireCycle()

activeExpireCycle() 对每个expires* 逐一进行检测 每次执行250ms/hz

对某个expires[*]检测时,随机挑选W个key检测

  • 如果key超时,删除key
  • 如果一轮中删除key的数据>W*25%,循环该过程
  • 如果一轮中删除key的数据<=W25%,则检查下一个expires[]
  • W取值=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值

参数current_db用于记录activeExpireCycle()进入哪个expires[*]执行

如果activeExpireCyle()执行时间到期,下次从current_db继续向下执行

优点:cpu性能占用设置峰值 检测频度可自定义

内容压力不是很大 长期占用内存的冷数据会被清理掉

2.4. 淘汰策略

当redis使用内容存储数据,执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足.如果不满足加入数据的最低存储要求,则redis会为当前指令清理存储空间.清理数据的策略又称为逐出算法

逐出数据的过程不是百分百能够清理出足够的可使用空间,如果不成功则反复执行.当对所有数据尝试完毕,如果仍然不能到达内存清理要求,将出现错误信息

而redis中内存空间是根据物理内存和配置信息来指定

  • maxmenory ?mb 默认值为0不做限制 通常设置在50%以上
  • maxmemory-samples count 每次选取待删除数据的个数,采用随机获取数据的方式作为待检测删除数据
  • maxmemory-policy policy 对数据进行删除的选择策略
    • 检测易失数据(可能会过期的数据集server.db[i].expires)
      • volatile-lru 挑选最近最少使用的数据淘汰
      • valatile-lfu 挑选最近使用次数最少的数据淘汰
      • valatile-tfl 挑选将要过期的数据淘汰
      • volatile-random 任意选择数据淘汰
      • 03. Redis高级 - 图2
    • 检测全库数据(所有数据集server.db[i].dict)
      • allkeys-lru 挑选最近最少使用的数据淘汰
      • allkeys-lfu 挑选最近使用次数最少的数据淘汰
      • allkeys-random 任意选择数据淘汰
    • 放弃数据驱逐
      • no-enviction(驱逐) 禁止驱逐数据(redis4.0中默认策略) 会引发错误OOM(Out Of Memory)

使用INFO命令输出监控信息,查询缓存hit和miss的次数,根据业务需求调用Redis配置

3. 主从复制

03. Redis高级 - 图3

3.1. 互联网 “三高” 架构

  • 高并发
  • 高性能
  • 高可用
    • 可用性 = (1年的总时间 - 1年宕机的总时间) / 1年的总时间 * 100% : 业界可用性目标5个9 即99.999% 即服务器年宕机时长低于315秒 约5.25分钟

3.2. 阶段一:建立连接阶段

建立slave到master的连接,使用master能够识别slave,并保存slave端口号

  1. saveof ip port : salve发送指令给master
  2. master接受到指令,响应对方
  3. 保存master的ip与端口 masterhost 和 masterport
  4. 根据保存的信息创建连接master的socket
  5. 周期性发送ping指令
  6. 响应pong
  7. 发送指令 auth password (如无密码则忽略)
  8. 验证授权
  9. replconf listening-port <port-number>
  10. 保存slave的端口号

3.2.1. 主从连接 (slave连接mazster)

  • 方式一 从机客户端发送命令 slaveof masterip masterport
  • 方式二 启动服务器时连接 redis-server -slaveof masterip masterport
  • 方式三 服务器配置 在从机服务器的redis服务器配置文件 加上 slaveof masterip masterport

3.2.2. 主从断开连接

  • 断开slave与master的连接 不会删除已有数据 只是不再接受master发送的数据 salveof no one 在从机上执行

3.2.3. 授权访问

03. Redis高级 - 图4

3.3. 阶段二:数据同步阶段工作流程

03. Redis高级 - 图5

  1. 如果master数据量巨大,数据同步阶段应该避开流量高峰期,避免造成阻塞
  2. 如果复制缓冲区设置不合理,会导致数据溢出. 如果全量复制周期太长,进行部分复制时发现数据已经存在丢失,必须进行第二次全量复制 导致slave陷入死循环
    1. #在主服务器配置文件配置 配置缓存区大小
    2. repl-backlog-size ?mb
  1. master单机内存占用主机内存比例应在50%-70%,留下30-50作为bgsave命令和创建复制缓冲区
  2. 在slave进行全量复制/部分复制时 建议关闭此时对外服务
    1. #在从服务器配置文件中配置 关闭和开启
    2. slave-serve-stale-data yes|no
  1. 数据同步阶段 master发送给slave信息可以理解为master是slave的一个客户端 主动向slave发送命令
  2. 多个slave同时对master请求数据同步 master发送的rdb文件增多 如果master带宽不足 会对带宽造成巨大冲击 建议适量错峰请求同步
  3. salve过多时 建议调整拓扑结构 由一主多从结构变为树状结构 中间的节点即使master 也salve 使用树状结构时 由于层级深度 导致深度越高的slave与最顶层master间数据同步延迟较大 数据一致性变差 应谨慎选择

3.4. 阶段三:命令传播阶段

命令传播阶段出现了断网现象:

网络闪断闪连:忽略

短时间网络中断:部分复制

长时间网络中断:全量复制

工作原理

  • 通过offset区分不同的slave当前数据传播的差异
  • master记录已发送的信息对应的offset
  • slave记录已接收的信息对应的offset

03. Redis高级 - 图6

3.4.1. 流程更新

03. Redis高级 - 图7

3.4.2. 心跳机制

进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线

master心跳:

  • 内部指令:PING
  • 周期:由repl-ping-slave-period决定,默认10秒
  • 作用:判断slave是否在线
  • 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常

slave心跳任务

  • 内部指令:REPLCONF ACK {offset}
  • 周期:1秒
  • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
  • 作用2:判断master是否在线

心跳阶段注意事项:

  • 当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步
  1. #在主服务器配置文件中配置
  2. min-slaves-to-write 2
  3. min-slaves-max-lag 8

slave数量少于2个,或者所有slave的延迟都大于等于8秒时,强制关闭master写功能,停止数据同步

  • slave数量由slave发送REPLCONF ACK命令做确认
  • slave延迟由slave发送REPLCONF ACK命令做确认

03. Redis高级 - 图8

3.5. 频繁的全量复制

  • 伴随着系统的运行,master的数据量会越来越大,一旦master重启,runid将发生变化,会导致全部slave的全量复制操作

内部优化调整方案:

1:master内部创建master_replid变量,使用runid相同的策略生成,长度41位,并发送给所有slave

2:在master关闭时执行命令shutdown save,进行RDB持久化,将runid与offset保存到RDB文件中

  • repl-id repl-offset
  • 通过redis-check-rdb命令可以查看该信息

3:master重启后加载RDB文件,恢复数据,重启后,将RDB文件中保存的repl-id与repl-offset加载到内存中

  • master_repl_id=repl master_repl_offset =repl-offset
  • 通过info命令可以查看该信息

作用:本机保存上次runid,重启后恢复该值,使所有slave认为还是之前的master

  • 第二种出现频繁全量复制的问题现象:网络环境不佳,出现网络中断,slave不提供服务

问题原因:复制缓冲区过小,断网后slave的offset越界,触发全量复制

最终结果:slave反复进行全量复制

解决方案:修改复制缓冲区大小

  1. repl-backlog-size ?mb

建议设置如下:

1.测算从master到slave的重连平均时长second

2.获取master平均每秒产生写命令数据总量write_size_per_second

3.最优复制缓冲区空间 = 2 second write_size_per_second

3.5.1. 频繁的网络中断

  • 问题现象:master的CPU占用过高 或 slave频繁断开连接

问题原因

  • slave每1秒发送REPLCONFACK命令到master
  • 当slave接到了慢查询时(keys * ,hgetall等),会大量占用CPU性能
  • master每1秒调用复制定时函数replicationCron(),比对slave发现长时间没有进行响应

最终结果:master各种资源(输出缓冲区、带宽、连接等)被严重占用

解决方案:通过设置合理的超时时间,确认是否释放slave

  1. repl-timeout seconds

该参数定义了超时时间的阈值(默认60秒),超过该值,释放slave

3.5.2. 数据不一致

问题现象:多个slave获取相同数据不同步

问题原因:网络信息不同步,数据发送有延迟

解决方案

  • 优化主从间的网络环境,通常放置在同一个机房部署,如使用阿里云等云服务器时要注意此现象
  • 监控主从节点延迟(通过offset)判断,如果slave延迟过大,暂时屏蔽程序对该slave的数据访问
  1. slave-serve-stale-data yes|no

开启后仅响应info、slaveof等少数命令(慎用,除非对数据一致性要求很高)

4. 哨兵模式

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master

哨兵也是一台redis服务器 只是不提供数据相关服务 通常哨兵的数量配置为单数

4.1. 配置哨兵

通过sentinel.conf 配置

  • sentinel monitor master_name master_host master_port sentinel_number 设置哨兵监听的主服务器信息 sentinel_number表示参与投票的哨兵数量 设置超过哨兵半数即可
  • sentinel down-after-milliseconds master_name million_seconds 设置判定服务器宕机时长 该设置控制是否进行主从切换
  • sentinel failover-timeout master_name million_seconds 设置故障切换的最大超时时长
  • sentinel parallel-syncs master_name sync_slave_number 设置主从切换后,同时进行数据的slave数量 值越大 要求网络资源越高 值越小 同步时间越长
  • redis-sentinel filename(sentinel.conf配置文件路径) 启动哨兵

4.2. 阶段一:监控阶段

  • 获取各个sentinel的状态(是否在线)
  • 获取master的状态
  • 获取所有slave的状态(根据master中的slave信息)

03. Redis高级 - 图9

4.3. 阶段二:通知阶段

sentinel在通知阶段要不断的去获取master/slave的信息,然后在各个sentinel之间进行共享,具体的流程如下:

03. Redis高级 - 图10

4.4. 阶段三:故障转移

4.4.1. 判断master宕机

03. Redis高级 - 图11

主观下线:任意一个sentine认为master已经下线

客观下线:半数以上的sentine认为master已经下线

4.4.2. 选举master

每个sentine都有一票 最终票最多的称为处理事故的哨兵服务

03. Redis高级 - 图12

首先它有一个在服务器列表中挑选备选master的原则

  • 不在线的OUT
  • 响应慢的OUT
  • 与原master断开时间久的OUT
  • 优先原则
    优先级
    offset
    runid

总结:故障转移阶段

  1. 发现问题,主观下线与客观下线
  2. 竞选负责人
  3. 优选新master
  4. 新master上任,其他slave切换master,原master作为slave故障恢复后连接

5. Cluster集群

集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果

03. Redis高级 - 图13

5.1. Cluster集群结构设计

数据存储设计:

  1. 通过算法设计,计算出key应该保存的位置
  2. 将所有的存储空间计划切割成16384份,每台主机保存一部分
    注意:每份代表的是一个存储空间,不是一个key的保存空间
  3. 将key按照计算出的结果放到对应的存储空间

03. Redis高级 - 图14那redis的集群是如何增强可扩展性的呢?譬如我们要增加一个集群节点

03. Redis高级 - 图15

当我们查找数据时,集群是如何操作的呢?

  • 各个数据库相互通信,保存各个库中槽的编号数据
  • 一次命中,直接返回
  • 一次未命中,告知具体位置

03. Redis高级 - 图16

5.2. 配置

在redis-6401.conf中配置

  • cluster-enabled yes|no 是否启用cluster,加入cluster节点
  • cluster-config-file cluster-6503.conf cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容
  • cluster-node-timeout milliseconds 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点 毫秒数
  • cluster-migration-barrier min_slave_number master连接的slave最小数量

5.3. 启动Culster

  1. redis-cli –-cluster create masterhost1:masterport1 masterhost2:masterport2 masterhost3:masterport3 [masterhostn:masterportn …] slavehost1:slaveport1 slavehost2:slaveport2 slavehost3:slaveport3 -–cluster-replicas n

n为多少个主服务器 前n个ip为主服务器 后面为slave服务器

  • cluster nodes 查询集群节点信息

5.4. 节点增删

  • 添加master到当前集群中,连接时可以指定任意现有节点地址与端口
    1. redis-cli --cluster add-node new-master-host:new-master-port now-host:now-port
  • 添加slave
    1. redis-cli --cluster add-node new-slave-host:new-slave-port master-host:master-port --cluster-slave --cluster-master-id masterid
  • 删除节点,如果删除的节点是master,必须保障其中没有槽slot
    1. redis-cli --cluster del-node del-slave-host:del-slave-port del-slave-id
  • 重新分槽,分槽是从具有槽的master中划分一部分给其他master,过程中不创建新的槽
    1. redis-cli --cluster reshard new-master-host:new-master:port --cluster-from src- master-id1, src-master-id2, src-master-idn --cluster-to target-master-id -- cluster-slots slots
    2. #将需要参与分槽的所有masterid不分先后顺序添加到参数中,使用,分隔
    3. #指定目标得到的槽的数量,所有的槽将平均从每个来源的master处获取
  • 重新分配槽,从具有槽的master中分配指定数量的槽到另一个master中,常用于清空指定master中的槽
    1. redis-cli --cluster reshard src-master-host:src-master-port --cluster-from src- master-id --cluster-to target-master-id --cluster-slots slots --cluster-yes

6. 缓存预热

1.请求数量较高,大量的请求过来之后都需要去从缓存中获取数据,但是缓存中又没有,此时从数据库中查找数据然后将数据再存入缓存,造成了短期内对redis的高强度操作从而导致问题

2.主从之间数据吞吐量较大,数据同步操作频度较高

  • 前置准备工作:

1.日常例行统计数据访问记录,统计访问频度较高的热点数据

2.利用LRU数据删除策略,构建数据留存队列例如:storm与kafka配合

  • 准备工作:

1.将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据

2.利用分布式多服务器同时进行数据读取,提速数据加载过程

3.热点数据主从同时预热

  • 实施:

4.使用脚本程序固定触发数据预热过程

5.如果条件允许,使用了CDN(内容分发网络),效果会更好

总的来说:缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

7. 缓存雪崩

1.系统平稳运行过程中,忽然数据库连接量激增

2.应用服务器无法及时处理请求

3.大量408,500错误页面出现

4.客户反复刷新页面获取数据

5.数据库崩溃

6.应用服务器崩溃

7.重启应用服务器无效

8.Redis服务器崩溃

9.Redis集群崩溃

10.重启数据库后再次被瞬间流量放倒

解决方案

  • 思路:

1.更多的页面静态化处理

2.构建多级缓存架构

  1. Nginx缓存+redis缓存+ehcache缓存

3.检测Mysql严重耗时业务进行优化

  1. 对数据库的瓶颈排查:例如超时查询、耗时较高事务等

4.灾难预警机制

  1. 监控redis服务器性能指标
  2. CPU占用、CPU使用率
  3. 内存容量
  4. 查询平均响应时间
  5. 线程数

5.限流、降级

短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问

  • 落地实践:

1.LRU与LFU切换

2.数据有效期策略调整

  1. 根据业务数据有效期进行分类错峰,A90分钟,B80分钟,C70分钟
  2. 过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量

3.超热数据使用永久key

4.定期维护(自动+人工)

  1. 对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时

5.加锁:慎用!

总的来说:缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的 出现(约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。

8. 缓存击穿

1.系统平稳运行过程中

2.数据库连接量瞬间激增

3.Redis服务器无大量key过期

4.Redis内存平稳,无波动

5.Redis服务器CPU正常

6.数据库崩溃

问题排查:

1.Redis中某个key过期,该key访问量巨大

2.多个数据请求从服务器直接压到Redis后,均未命中

3.Redis在短时间内发起了大量对数据库中同一数据的访问

总而言之就两点:单个key高热数据,key过期

解决方案

1.预先设定

  1. 以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息key的过期时长 注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低的趋势

2.现场调整

  1. 监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key

3.后台刷新数据

  1. 启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失

4.二级缓存

  1. 设置不同的失效时间,保障不会被同时淘汰就行

5.加锁

  1. 分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!

总的来说:缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数 据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过 期监控难度较高,配合雪崩处理策略即可。

9. 缓存穿透

1.系统平稳运行过程中

2.应用服务器流量随时间增量较大

3.Redis服务器命中率随时间逐步降低

4.Redis内存平稳,内存无压力

5.Redis服务器CPU占用激增

6.数据库服务器压力激增

7.数据库崩溃

问题排查:

1.Redis中大面积出现未命中

2.出现非正常URL访问

问题分析

  • 获取的数据在数据库中也不存在,数据库查询未得到对应数据
  • Redis获取到null数据未进行持久化,直接返回
  • 下次此类数据到达重复上述过程
  • 出现黑客攻击服务器

解决方案

1.缓存null

  1. 对查询结果为null的数据进行缓存(长期使用,定期清理),设定短时限,例如30-60秒,最高5分钟

2.白名单策略

  1. 提前预热各种分类数据id对应的bitmapsid作为bitmapsoffset,相当于设置了数据白名单。当加载正常数据时放行,加载异常数据时直接拦截(效率偏低)
  2. 使用布隆过滤器(有关布隆过滤器的命中问题对当前状况可以忽略)

2.实施监控

  1. 实时监控redis命中率(业务正常范围时,通常会有一个波动值)与null数据的占比
  2. 非活动时段波动:通常检测3-5倍,超过5倍纳入重点排查对象
  3. 活动时段波动:通常检测10-50倍,超过50倍纳入重点排查对象
  4. 根据倍数不同,启动不同的排查流程。然后使用黑名单进行防控(运营)

4.key加密

  1. 问题出现后,临时启动防灾业务key,对key进行业务层传输加密服务,设定校验程序,过来的key校验
  2. 例如每天随机分配60个加密串,挑选23个,混淆到页面数据id中,发现访问key不满足规则,驳回数据访问

总的来说:缓存击穿是指访问了不存在的数据,跳过了合法数据的redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力。通常此类数据的出现量是一个较低的值,当出现此类情况以毒攻毒,并及时报警。应对策略应该在临时预案防范方面多做文章。

无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除。

10. 性能指标监控

性能指标

  • latency 响应请求的平均时间
  • instantaneous_ops_per_sec 平均每秒处理请求总数
  • hit_rate(calculated) 缓存查询命中率(通过查询总次数与查询得到非nil数据总次数计算而来)

内存指标

  • used_memory 当前内存使用量
  • mem_fragmentation_ratio 内存碎片率(关系到是否进行碎片整理)
  • evicted_keys 为避免内存溢出删除的key的总数量
  • blocked_clients 基于阻塞操作(BLPOP等)影响的客户端数量

活动指标

  • connected_clients 当前客户端连接总数
  • connected_slaves 当前连接slave总数
  • master_last_io_seconds_ago 最后一次主从信息交换距现在的秒
  • keyspace key的总数

持久性指标

  • rdb_last_save_time 当前服务器最后一次RDB持久化的时间
  • rdb_changes_since_last_save 当前服务器最后一次RDB持久化后数据变化总量

错误指标

  • rejected_connections 被拒绝连接的客户端总数(基于达到最大连接值的因素)
  • keyspace_misses key未命中的总次数
  • master_link_down_since_seconds 主从断开的秒数

11. 性能指标工具

测试当前服务器的并发性能

  1. redis-benchmark [-h ] [-p ] [-c ] [-n <requests]> [-k ]

03. Redis高级 - 图17

启动服务器调试信息

  1. monitor

慢日志 记录查询慢的日志

  • slowlog [operator]
    • get 获取慢日志信息
    • len 获取慢日志条目数
    • reset 重置慢查询日志

慢日志通过redis-xxx.conf配置

  1. slowlog-log-slower-than 1000 #设置慢查询的时间下线,单位:微妙
  2. slowlog-max-len 100 #设置慢查询命令对应的日志显示长度,单位:命令数