redis集群及应用场景

java开发面试整理。来源:https://github.com/Snailclimb/JavaGuide

整理自:https://github.com/Snailclimb/JavaGuide,
Guide哥

集群

主从复制:

两种结构:

复制模式:

  • 全量复制:Master 全部同步到 Slave
  • 部分复制:Slave 数据丢失进行备份

问题点:

  • 同步故障

    • 复制数据延迟(不一致)
    • 读取过期数据(Slave 不能删除数据)
    • 从节点故障
    • 主节点故障
  • 配置不一致

    • maxmemory 不一致:丢失数据
    • 优化参数不一致:内存不一致.
  • 避免全量复制

    • 选择小主节点(分片)、低峰期间操作.
    • 如果节点运行 id 不匹配(如主节点重启、运行 id 发送变化),此时要执行全量复制,应该配合哨兵和集群解决.
    • 主从复制挤压缓冲区不足产生的问题(网络中断,部分复制无法满足),可增大复制缓冲区( rel_backlog_size 参数).
  • 复制风暴

    举例:我们master重启,其master下的所有slave检测到RunID发生变化,导致所有从节点向主节点做全量复制。

哨兵机制sentinel:

redis Sentinel是一个分布式系统,为Redis提供高可用性解决方案。可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来 接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故 障迁移, 以及选择哪个从服务器作为新的主服务器。

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance) 该系统执行以下三个任务:

  • 监控(Monitoring): Sentinel 会不断地定期检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automaticfailover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中 一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客 户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主 服务器代替失效服务器。

redis节点下线:

  • 主观下线
    • 即 Sentinel 节点对 Redis 节点失败的偏见,超出超时时间认为 Master 已经宕机。
    • Sentinel 集群的每一个 Sentinel 节点会定时对 Redis 集群的所有节点发心跳包检测节点是否正常。如果一个节点在 down-after-milliseconds 时间内没有回复 Sentinel 节点的心跳包,则该 Redis 节点被该 Sentinel 节点主观下线。
  • 客观下线(每个sentinel进行投票)
    • 所有 Sentinel 节点对 Redis 节点失败要达成共识,即超过 quorum 个统一。
    • 当节点被一个 Sentinel 节点记为主观下线时,并不意味着该节点肯定故障了,还需要 Sentinel 集群的其他 Sentinel 节点共同判断为主观下线才行。
    • 该 Sentinel 节点会询问其它 Sentinel 节点,如果 Sentinel 集群中超过 quorum 数量的 Sentinel 节点认为该 Redis 节点主观下线,则该 Redis 客观下线。

sentinel选举Leader:

  • 选举出一个 Sentinel 作为 Leader,集群中至少要有三个 Sentinel 节点,通过以下选举流程可以进行失败判定或领导者选举。
  • 选举流程
    1. 每个主观下线的 Sentinel 节点向其他 Sentinel 节点发送命令,要求设置它为领导者.
    2. 收到命令的 Sentinel 节点如果没有同意通过其他 Sentinel 节点发送的命令,则同意该请求,否则拒绝。
    3. 如果该 Sentinel 节点发现自己的票数已经超过 Sentinel 集合半数且超过 quorum,则它成为领导者。
    4. 如果此过程有多个 Sentinel 节点成为领导者,则等待一段时间再重新进行选举。

redis节点故障转移:

  • 转移流程
    1. Sentinel 选出一个合适的 Slave 作为新的 Master(slaveof no one 命令)。
    2. 向其余 Slave 发出通知,让它们成为新 Master 的 Slave( parallel-syncs 参数)。
    3. 等待旧 Master 复活,并使之称为新 Master 的 Slave。
    4. 向客户端通知 Master 变化。
  • 从 Slave 中选择新 Master 节点的规则(slave 升级成 master 之后)
    1. 选择 slave-priority(优先级) 最高的节点。
    2. 选择复制偏移量最大的节点(同步数据最多)。
    3. 选择 runId 最小的节点。runid是Redis 服务器的随机标识符,重启后就会改变;当复制时发现和之前的 run_id 不同时,将会对数据全量同步。
  • sentinel的集群运行过程中故障转移完成之后,所有的sentinel又会恢复平等。sentinel选举的leader仅仅是在转移操作过程中起作用。

读写分离:

云数据库Redis版不管主从版还是集群规格,replica作为备库不对外提供服务,只有在发生HA的时候,replica提升为master后才承担读写流量。这种架构读写请求都在master上完成,一致性较高,但性能受到master数量的限制。经常有用户数据较少,但因为流量或者并发太高而不得不升级到更大的集群规格。

为满足读多写少的业务场景,最大化节约用户成本,云数据库Redis版推出了读写分离规格,为用户提供透明、高可用、高性能、高灵活的读写分离服务。

定时任务:

  • 每 1s 每个 Sentinel 对其他 Sentinel 和 Redis 执行 ping,进行心跳检测。
  • 每 2s 每个 Sentinel 通过 Master 的 Channel 交换信息(pub - sub)。
  • 每 10s 每个 Sentinel 对 Master 和 Slave 执行 info,目的是发现 Slave 节点、确定主从关系。

分布式集群:

拓扑图:

通讯方式:

集中式gossip协议两种方式。

  1. 集中式:

    将集群中的某些数据放在某一个节点上。例如日志记录。这样的坏处就是数据集中存储,若发生灾难,一损俱损。

  2. gossip协议:

    从 gossip 单词就可以看到,其中文意思是八卦、流言等意思,我们可以想象下绯闻的传播(或者流行病的传播);gossip 协议的工作原理就类似于这个。gossip 协议利用一种随机的方式将信息传播到整个网络中,并在一定时间内使得系统内的所有节点数据一致。Gossip 其实是一种去中心化思路的分布式协议,解决状态在集群中的传播和状态一致性的保证两个问题。https://www.iteblog.com/archives/2505.html

寻址分片:

详解

应用场景

热点数据:

存取数据优先从 Redis 操作,如果不存在再从文件(例如 MySQL)中操作,从文件操作完后将数据存储到 Redis 中并返回。同时有个定时任务后台定时扫描 Redis 的 key,根据业务规则进行淘汰,防止某些只访问一两次的数据一直存在 Redis 中。

  • 例如使用 Zset 数据结构,存储 Key 的访问次数/最后访问时间作为 Score,最后做排序,来淘汰那些最少访问的 Key。

会话维持 Session:

会话维持 Session 场景,即使用 Redis 作为分布式场景下的登录中心存储应用。每次不同的服务在登录的时候,都会去统一的 Redis 去验证 Session 是否正确。但是在微服务场景,一般会考虑 Redis + JWT 做 Oauth2 模块。

  • 其中 Redis 存储 JWT 的相关信息主要是留出口子,方便以后做统一的防刷接口,或者做登录设备限制等。

分布式锁 SETNX:

什么是分布式锁?

要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。

  • 线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
  • 进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
  • 分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

命令格式:SETNX key value:当且仅当 key 不存在,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX 不做任何动作。

  1. 超时时间设置:获取锁的同时,启动守护线程,使用 expire 进行定时更新超时时间。如果该业务机器宕机,守护线程也挂掉,这样也会自动过期。如果该业务不是宕机,而是真的需要这么久的操作时间,那么增加超时时间在业务上也是可以接受的,但是肯定有个最大的阈值。
  2. 但是为了增加高可用,需要使用多台 Redis,就增加了复杂性

表缓存:

Redis 缓存表的场景有黑名单、禁言表等。访问频率较高,即读高。根据业务需求,可以使用后台定时任务定时刷新 Redis 的缓存表数据。

消息队列 list:

主要使用了 List 数据结构。
List 支持在头部和尾部操作,因此可以实现简单的消息队列。

  1. 发消息:在 List 尾部塞入数据。
  2. 消费消息:在 List 头部拿出数据。

同时可以使用多个 List,来实现多个队列,根据不同的业务消息,塞入不同的 List,来增加吞吐量。

计数器 string:

主要使用了 INCR、DECR、INCRBY、DECRBY 方法。

INCR key:给 key 的 value 值增加一 DECR key:给 key 的 value 值减去一

缓存

缓存更新算法:

  • LRU(Least Recently Used,最近最少使用)、LFU(least frequently used,最不经常使用)、FIFO 算法自动清除:一致性最差,维护成本低。
  • 超时自动清除(key expire):一致性较差,维护成本低。
  • 主动更新:代码层面控制生命周期,一致性最好,维护成本高。

在 Redis 根据在 redis.conf 的参数 maxmemory 来做更新淘汰策略:

  1. noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。大多数写命令都会导致占用更多的内存(有极少数会例外, 如?DEL?命令)。
  2. allkeys-lru: 所有 key 通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
  3. volatile-lru: 只限于设置了?expire?的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
  4. allkeys-random: 所有key通用; 随机删除一部分 key。
  5. volatile-random: 只限于设置了?expire?的部分; 随机删除一部分 key。
  6. volatile-ttl: 只限于设置了?expire?的部分; 优先删除剩余时间(time to live,TTL) 短的key。

更新的一致性:

  • 读请求:先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 写请求:先删除缓存,然后再更新数据库(避免大量地写、却又不经常读的数据导致缓存频繁更新)。

缓存的粒度:

通俗来讲,缓存粒度问题就是我们在使用缓存时,是将所有数据缓存还是缓存部分数据?

需要从以下三个方面进行考虑:(通用性,空间占用,代码维护成本)

  • 通用性:全量属性更好。
  • 占用空间:部分属性更好。
  • 代码维护成本。

缓存穿透:

当大量的请求无命中缓存、直接请求到后端数据库(业务代码的 bug、或恶意攻击),同时后端数据库也没有查询到相应的记录、无法添加缓存。这种状态会一直维持,流量一直打到存储层上,无法利用缓存、还会给存储层带来巨大压力。

解决方案

  1. 请求无法命中缓存、同时数据库记录为空时在缓存添加该 key 的空对象(设置过期时间),缺点是可能会在缓存中添加大量的空值键(比如遭到恶意攻击或爬虫),而且缓存层和存储层数据短期内不一致;
  2. 使用布隆过滤器在缓存层前拦截非法请求、自动为空值添加黑名单(同时可能要为误判的记录添加白名单).但需要考虑布隆过滤器的维护(离线生成/ 实时生成)。

缓存雪崩:

缓存崩溃(例如,redis服务停止)时请求会直接落到数据库上,很可能由于无法承受大量的并发请求而崩溃,此时如果只重启数据库,或因为缓存重启后没有数据,新的流量进来很快又会把数据库击倒。

出现后应对:

  • 事前:Redis 高可用,主从 + 哨兵,Redis Cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix 限流 & 降级,避免数据库承受太多压力。
  • 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

请求过程:

  1. 用户请求先访问本地缓存,无命中后再访问 Redis,如果本地缓存和 Redis 都没有再查数据库,并把数据添加到本地缓存和 Redis;
  2. 由于设置了限流,一段时间范围内超出的请求走降级处理(返回默认值,或给出友情提示)。
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信