Redis

用途

  • 分布式锁:其他方案Redission
  • 限流Redis+Lua脚本实现
  • 消息队列:自带的list数据结构可以作为一个简单的队列;5.0 中新增的Stream更适合用作消息队列(类似于Kafka,有主题和消费组的概念,支持消息持久化即ACK机制)
  • 复杂业务场景:通过bitmap统计活跃用户、通过sorted set维护排行榜等

在线尝试

Try Redis

数据结构

  • string
    • key-value类型
    • 常用命令:set,get,strlen,exists,decr,incr,setex
  • list
    • 链表
    • 常用命令:rpush,lpop,lpush,rpop,lrange,llen
    • 应用场景:发布与订阅或者说消息队列、慢查询
  • hash
    • 类似于JDK1.8前的HashMap
    • 常用命令:hset,hmset,hexists,hget,hgetall,hkeys,hvals
    • 应用场景:特别适合用于存储对象
  • set
    • 类似于Java中的HashSet;可以基于set实现交集、并集、差集
    • 常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion
  • sorted set
    • set相比,sorted set增加了一个权重参数score
      (元素能够按score进行有序排列;通过score范围获取元素列表)
    • 常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem
    • 应用场景:需要对数据根据某个权重进行排序的场景
  • bitmap
    • 存储的是连续的二进制数字(0 和 1)
    • 常用命令:setbitgetbitbitcountbitop
    • 应用场景:需要保存状态信息并需要进一步对这些信息进行分析的场景
      (用户行为分析、活跃用户统计、用户在线状态)
# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
127.0.0.1:6379> setbit mykey 7 1
(integer) 0
127.0.0.1:6379> getbit mykey 7
(integer) 1

缓存过期

通过过期字典来保存数据过期的时间。

键为数据库中的某个key,值为过期时间(long型值,毫秒精度的UNIX时间戳)

过期数据删除策略

  • 惰性删除:取出key时进行过期检查(可能造成太多过期key不能被删除)
  • 定期删除:每隔一段时间抽取一批key执行删除(底层通过限制删除操作执行的时长和频率最小化影响)
  • Redis采用的策略:定期删除+惰性/懒汉式删除

Redis内存淘汰机制

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(最常用的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
  7. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  8. allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

持久化

  • RDBRedis DataBase,快照):默认。创建快照之后,可以对快照进行备份。
    • 将快照复制到其他服务器,创建具有相同数据的服务器副本(主从结构中,主要用来提高性能)
    • 将快照留在原地以便重启服务器的时候使用
  • AOFAppend Only File,追加文件):记录每一条指令并记录到 server.aof_buf 中,再根据 appendfsync 配置将其同步到硬盘中的AOF文件中

对比:

  • AOF文件的保存位置和RDB文件的位置相同
  • AOF实时性能更好

AOF重写:产生一个新的AOF文件,新文件与原文件所保存的数据库状态一样,体积更小

事务

通过 MULTIEXECDISCARDWATCH 等命令来实现事务(transaction)功能

# 开启事务
> MULTI
OK
# 指令排队
> SET USER "Guide"
QUEUED
# 还未真正执行,拿不到存储的值
> GET USER
QUEUED
# 执行指令
> EXEC
1) OK
2) "Guide"

执行过程

  1. 开始事务(MULTI
  2. 命令入队(先进先出(FIFO)的顺序执行)
  3. 执行事务(EXEC

DISCARD 命令取消一个事务,它会清空事务队列中保存的所有命令

WATCH 命令用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败

  • Redis不支持 roll back,不满足原子性(而且不满足持久性)
  • Redis事务可以理解为命令请求打包。再按顺序执行打包的所有命令,且不会被中途打断

缓存穿透

大量请求的key根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层

解决办法

  • 缓存无效key:如果key变化不频繁可以采用;尽量将无效key过期时间设置短一些是(如 1 分钟)
    缓存key示例:表名:列名:主键名:主键值
  • 布隆过滤器:先通过布隆过滤器判断,存在则从缓存获取(及后续步骤);不存在直接返回

缓存雪崩

缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求

(如热点数据在某一时刻大面积失效)

解决办法

  • Redis服务宕机
    1. 集群
    2. 限流
  • 缓存失效
    1. 设置不同的失效时间(比如随机设置)
    2. 缓存永不失效

缓存与数据库一致性

缓存为何只删除不更新

  • 防止并发更新导致的数据不一致
  • 降低数据不一致的概率,不应该更新缓存,而是直接将其删除

先更新数据库还是先删除缓存?

  1. 先删除缓存,再更新数据库
  2. 先更新数据库,再删除缓存,也称为Cache Aside Pattern,适用于绝大多数场景

如果先删除缓存,并发时,线程A删除缓存后,线程B读取数据发生 Cache Miss,从数据库中读取数据并更新至缓存,然后线程A更新数据库。

上述操作导致缓存中存储的是脏数据,之后的读操作获取的都是脏数据(直至key过期或被LRU策略剔除)

解决方法

  • Cache Aside Pattern(旁路缓存模式):写请求时,先更新DB,再直接删除缓存

绝大部分情况下能够做到数据一致,极端情况下仍存在问题。

  • 更新数据库A和删除缓存B都不是原子操作,任何再A之后B之前的读操作,都可能会读到旧数据
    (超高并发的应用可能会出现问题)
  • 数据库更新完成后,线程被意外 kill 掉
  • 线程 A 读数据时 Cache Miss,从数据库读取的数据还未及时同步至缓存;此时线程B更新数据库并删除缓存旧值,随后线程A把之前查到的数据同步到缓存

如果更新DB成功,患处删除失败:

  1. 缩短缓存失效时间:对先操作缓存后操作DB场景不适用
  2. 增加缓存更新重试机制
    • 如果缓存服务宕机,隔一段时间进行重试
    • 将删除失败的key进行入队保存,待服务恢复后,继续重试删除
  • Double Delete

    # 执行过程
    # 先删除缓存
    delete redis cache
    update database
    # 睡眠一段时间后再次删除缓存脏数据
    sleep(500ms)
    delete redis cache
    
  • Read/Write Through Pattern

    将同步数据的任务交给缓存系统完成,应用程序不需要关心。

    • Read Through是指发生cache miss时,缓存系统自动去数据库加载数据。
    • Write Through是指如果cache miss时,直接更新数据库,然后返回;如果cache hit,则更新缓存后,由缓存系统自动同步到数据库
  • Write Behind

    更新数据时,只更新缓存,不更新数据库。由另外一个服务异步的把数据更新到数据库。

    • 纯内存操作,I/O非常快
    • 异步操作,可能存在数据一致性问题
  • 设置缓存过期时间

集群方案

主从复制

基本原理
  • 包含一个主数据库实例(master)和多个从数据库实例(slave)
  • 客户端可对主数据库进行读写操作,对从数据库进行读操作,主数据库写入的数据会实时自动同步给从数据库

在这里插入图片描述

工作流程
  1. slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过 bgsave 保存RDB快照,并使用缓冲区记录保存快照这段时间内执行的写命令
  2. master将保存的快照文件发送给slave,并继续记录执行的写命令
  3. slave接收到快照文件后,加载快照文件,载入数据
  4. master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
  5. 此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性
优缺点
  • 优点
    • master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
    • master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求
  • 缺点
    • 不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
    • master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
    • 难以支持在线扩容,容量受限于单机配置

Sentinel 哨兵模式

基本原理
  • 基于主从复制模式,引入了哨兵来监控与自动处理故障
  • slave 节点不提供服务

在这里插入图片描述

  • 哨兵功能:
    1. 监控master、slave是否正常运行
    2. 当master出现故障时,能自动将一个slave转换为master(大哥挂了,选一个小弟上位)
    3. 多个哨兵可以监控同一个Redis,哨兵之间也会自动监控
  • 哨兵启动后,需要与被监控的master建立两条连接
    1. 一条连接用来订阅master的_sentinel_:hello频道,获取其他监控该master的哨兵节点信息
    2. 另一条连接定期向master发送INFO等命令获取master本身的信息
  • 与 master 建立连接后,哨兵会执行三个操作
    1. 定期(一般10s一次,当master被标记为主观下线时,改为1s一次)向master和slave发送INFO命令
    2. 定期向master和slave的_sentinel_:hello频道发送自己的信息
    3. 定期(1s一次)向master、slave和其他哨兵发送PING命令
  • INFO命令可以获取当前数据库的相关信息从而实现新节点的自动发现
  • 哨兵向主从数据库_sentinel_:hello频道发送信息与同样监控这些数据库的哨兵共享自己的信息
重新选举
  • 哨兵认为master客观下线后,故障恢复的操作需要由选举的领头哨兵来执行,选举采用Raft算法
    1. 发现master下线的哨兵节点(我们称他为A)向每个哨兵发送命令,要求对方选自己为领头哨兵
    2. 如果目标哨兵节点没有选过其他人,则会同意选举A为领头哨兵
    3. 如果有超过一半的哨兵同意选举A为领头,则A当选
    4. 如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票竞选,直至选举出领头哨兵
  • 选出领头哨兵后,领头者开始对系统进行故障恢复,从出现故障的master的从数据库中挑选一个来当选新的master,选择规则如下:
    1. 所有在线的slave中选择优先级最高的,优先级可以通过slave-priority配置
    2. 如果有多个最高优先级的slave,则选取复制偏移量最大(即复制越完整)的当选
    3. 如果以上条件都一样,选取id最小的slave

挑选出需要继任的slave后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行

优缺点
  • 优点
    1. master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
    2. master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求
    3. 哨兵模式下,master挂掉可以自动进行切换,系统可用性更高
  • 缺点
    1. 同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
    2. 需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务

Cluster 模式

基本原理
  • 实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题

在这里插入图片描述

  • 采用无中心结构,特点如下:

    1. 所有节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
    2. 节点的fail是通过集群中超过半数的节点检测失效时才生效
    3. 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • 工作机制

    • Redis 集群没有使用一致性 hash,而是引入了哈希槽【hash slot】的概念
    • 集群的每个节点负责一部分 hash 槽
    • Redis 集群有16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽
    • 每一个节点上,都存在两个东西:插槽(范围:1~16383)和cluster(可以理解为一个集群管理插件)
      (当接收到一个key查询请求时,当前节点根据算法计算得到哈希槽,再根据这个值到对应节点上取值)
  • 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点

  • 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了

  • Cluster模式集群节点最小配置6个节点(3主3从,因为需要半数以上),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值