Redis 缓存

一、缓存的基础概念

1.1缓存类型

1.1.1 客户端缓存

对于BS架构的互联网应用来说客户端缓存主要分为页面缓存和浏览器缓存两种,对于APP而言主要是自身所使用的缓存。

清理缓存的方式:

以网页浏览器为例
1.使用Ctrl+F5可以强制刷新浏览器本地缓存
2.浏览器中的参数设置的时间已过期如:Expires,Cache-control: max-age, Last-Modified标签等

1.1.2 网络中缓存

网络中的缓存主要是指代理服务器对客户端请求数据的缓存,主要分为WEB代理缓存和边缘缓存(CDN边缘缓存)

反向代理缓存

基本介绍

反向代理位于应用服务器机房,处理所有对WEB服务器的请求。

如果用户请求的页面在代理服务器上有缓冲的话,代理服务器直接将缓冲内容发送给用户。如果没有缓冲则先向WEB服务器发出请求,取回数据,本地缓存后再发送给用户。通过降低向WEB服务器的请求数,从而降低了WEB服务器的负载。

应用场景

一般只缓存体积较小静态文件资源,如css、js、图片

开源实现

主要为nginx

1.1.3 服务端缓存

1.堆缓存

堆缓存:使用Java堆内存来存储缓存对象,位于服务本机的内存中。
常用堆缓存HashMap、GuavaCache等

优点:
没有序列化/反序列化,省去网络传输过程是最快的缓存;

缺点:
1)缓存数据量有限,受限于堆空间大小,一般存软应用/弱引用对象即当堆空间不足时GC会强制回收释放空间,数据同步较麻烦;
2)无法共享,单服务是集群部署的时候,应该考虑是否需要做集群中本地缓存的数据同步
3)没有过期时间,需要手动去刷新缓存

应用场景:
性能要求较高,缓存数据量较小

Caffeine框架
Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:https://github.com/ben-manes/caffeine

Caffeine的性能非常好,下图是官方给出的性能对比:
在这里插入图片描述
可以看到Caffeine的性能遥遥领先!

缓存使用的基本API:

@Test
void testBasicOps() {
    // 构建cache对象
    Cache<String, String> cache = Caffeine.newBuilder().build();

    // 存数据
    cache.put("gf", "迪丽热巴");

    // 取数据
    String gf = cache.getIfPresent("gf");
    System.out.println("gf = " + gf);

    // 取数据,包含两个参数:
    // 参数一:缓存的key
    // 参数二:Lambda表达式,表达式参数就是缓存的key,方法体是查询数据库的逻辑
    // 优先根据key查询JVM缓存,如果未命中,则执行参数二的Lambda表达式
    String defaultGF = cache.get("defaultGF", key -> {
        // 根据key去数据库查询数据
        return "柳岩";
    });
    System.out.println("defaultGF = " + defaultGF);
}

Caffeine既然是缓存的一种,肯定需要有缓存的清除策略,不然的话内存总会有耗尽的时候。

Caffeine提供了三种缓存驱逐策略:

基于容量:设置缓存的数量上限

// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
    .maximumSize(1) // 设置缓存大小上限为 1
    .build();

基于时间:设置缓存的有效时间

// 创建缓存对象
Cache<String, String> cache = Caffeine.newBuilder()
    // 设置缓存有效期为 10 秒,从最后一次写入开始计时 
    .expireAfterWrite(Duration.ofSeconds(10)) 
    .build();

基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

注意:在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。

2.堆外缓存

堆外缓存:缓存对象存储在堆外,应用中的缓存组件,应用和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销;

优点:更大容量的缓存空间,减少GC扫描和移动对象;
缺点:读取数据时需要序列化,速度比堆内缓存慢,数据同步较麻烦;
常用堆缓存Ehcahe等

EhCache
特点:
EhCache是进程内的缓存框架,在集群时不推荐使用
EhCache在第一次通过select查询出的结果被加入到EhCache缓存中,第二次查询从EhCache取出的对象与第一次查询对象实际上是同一个对象,因此我们在更新age的时候,实际已经更新了EhCache中的缓存对象。
故EhCache自带刷新缓存能力

3.分布式缓存

分布式缓存:分布式缓存由一个服务端实现管理和控制,有一个或多个客户端节点存储数据的缓存;
优点:
1.具有较大容量的缓存空间,易于扩容;
2.相对于堆(外)缓存、磁盘缓存更容易保证数据一致性;
缺点:
1.需要序列化/反序列化及网络传输,如果网络存在连接超时,则缓存使用不起来。

常用的分布式缓存 Redis Memcached

4.数据库缓存
数据库在设计的时候也有缓存操作,更改相关参数开启查询缓存

1.2 CDN(附加内容,实践中很少用到)

什么是CDN

CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。

CDN的优势:
(1)CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
(2)大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。

引入CDN后的网路架构如图:

在这里插入图片描述

客户端浏览器先检查是否有本地缓存是否过期,如果过期,则向CDN边缘节点发起请求,CDN边缘节点会检测用户请求数据的缓存是否过期,如果没有过期,则直接响应用户请求,此时一个完成http请求结束;如果数据已经过期,那么CDN还需要向源站发出回源请求(back to the source request),来拉取最新的数据。CDN的典型拓扑图如下:

在这里插入图片描述

CDN缓存

浏览器本地缓存失效后,浏览器会向CDN边缘节点发起请求。类似浏览器缓存,CDN边缘节点也存在着一套缓存机制。

CDN缓存的缺点

CDN的分流作用不仅减少了用户的访问延时,也减少的源站的负载。但其缺点也很明显:当网站更新时,如果CDN节点上数据没有及时更新,即便用户再浏览器使用Ctrl +F5的方式使浏览器端的缓存失效,也会因为CDN边缘节点没有同步最新数据而导致用户访问异常。

CDN缓存策略

CDN边缘节点缓存策略因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的Cache-control: max-age的字段来设置CDN边缘节点数据缓存时间。

当客户端向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向源站发出回源请求,从源站拉取最新数据,更新本地缓存,并将最新数据返回给客户端。

CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。

CDN缓存时间会对“回源率”产生直接的影响。若CDN缓存时间较短,CDN边缘节点上的数据会经常失效,导致频繁回源,增加了源站的负载,同时也增大的访问延时;若CDN缓存时间太长,会带来数据更新时间慢的问题。开发者需要增对特定的业务,来做特定的数据缓存时间管理。

CDN缓存刷新

CDN边缘节点对开发者是透明的,相比于浏览器Ctrl+F5的强制刷新来使浏览器本地缓存失效,开发者可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制CDN节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。

更多CDN相关技术请参考:
1.CDN学习笔记一(CDN是什么?)https://www.cnblogs.com/tinywan/p/6067126.html

二、Redis基础概念

2.1 Redis 简介

Redis本质上是一个Key-Value类型的内存数据库,它将整个数据库统统加载在内存当中进行操作,定期通过异步操作把redis数据库数据flush到硬盘上进行保存。
因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作.

2.2 Redis特点

Redis 与其他 key - value 缓存产品有以下三个特点:

  1. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次从磁盘中加载数据进行使用。可以对部分数据加入内存中,下次访问则无需访问数据库,直接访问内存即可,极大的加快了访问速度,对于热点商品,并发量大的极有意义
  2. Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  3. Redis支持数据的备份,即master-slave模式的数据备份

缺点:

1.Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

2.耗内存,当redis内存达到一定容量时,读写性能会下降

Redis相比memcached有哪些优势?

(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型和数据结构
(2) redis可以持久化其数据
3.Redis 拥有更加丰富的附加功能,如:pub/sub 功能, Lua 脚本支持, 序列化支持等等。
4.Redis 在 3.x 版本以后,原生支持了集群模式,而 Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。

查看redis内存使用情况命令(待续)

三、Redis核心特性

3.1Redis事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

一个事务从开始到执行会经历以下三个阶段:

开始事务。
命令入队。
执行事务。

Redis事务特性的典型应用是在分布式锁设计中,基于lua脚本实现加锁和设置锁时长的功能。

四、Redis高可用

redis架构演进

4.1Redis 单机版

在这里插入图片描述

单机版三个问题:
1、内存容量有限
2、处理能力有限
3、无法高可用,即可能存在单点故障

4.2 Redis 多机版

主要有三种方案:1、复制( Replication) 2、哨兵(Sentinel) 3、集群(Cluster)。

Redis 多机版特性功能:
复制:扩展系统对于读的能力
哨兵:为服务器提供高可用特性,减少故障停机出现
集群:扩展内存容量,增加机器,提高性能读写能力和存储以及提供高可用特性

4.2.1 主从Redis

Redis 的复制( replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器( master),而通过复制创建出来的服务器复制品则为从服务器( slave)。

只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同
其架构图如下:
在这里插入图片描述

优点:

降低 master 读压力,将读压力转交给了从库

缺点:

1.人为关注Master是否宕机

2.无法完成自动切换主从

3.没有解决 master 写的压力

1. 主从同步策略

master 和 slave 刚刚连接的时候,进行全量同步。全量同步结束后,进行增量同步。

当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

全量同步:
master 执行 BGSAVE 命令,生成一个 RDB 文件, 发送给 slave , slave 加载 RDB 文件达到与 master 维持一致。

1.slave 连接 master 后,发送 SYNC 命令。
2.master 接收到 SYNC 命名后,开始执行 BGSAVE 生成 RDB 文件,并使用缓冲区记录此后执行的所有写命令。
3.master BGSAVE 执行完后,向所有 slave 发送快照文件,并在发送期间继续记录被执行的写命令。
4.slave 收到快照文件后丢弃所有旧数据,载入收到的快照。
5.master 快照发送完毕后开始向 slave 发送缓冲区中的写命令。
6.salve 完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令。
第一次全量同步完成。

增量同步
Redis 增量复制是指 slave 初始化后开始正常工作时 master 发生的写操作同步到 slave 的过程。

增量复制的过程主要是 master 每执行一个写命令就会向 slave 发送相同的写命令,从服务器接收并执行收到的写命令。

以下会详细内容:

从 redis 2.8 版本以前, 并不支持部分同步, 当主从服务器之间的连接断掉之后, master 服
务器和 slave 服务器之间都是进行全量数据同步。
从 redis 2.8 开始, 即使主从连接中途断掉, 也不需要进行全量同步, 因为从这个版本开始
融入了部分同步的概念。 部分同步的实现依赖于在 master 服务器内存中给每个 slave 服务
器维护了一份同步日志和同步标识, 每个 slave 服务器在跟 master 服务器进行同步时都会
携带自己的同步标识和上次同步的最后位置。 当主从连接断掉之后, slave 服务器隔断时间
( 默认 1s) 主动尝试和 master 服务器进行连接, 如果从服务器携带的偏移量标识还在
master 服务器上的同步备份日志中, 那么就从 slave 发送的偏移量开始继续上次的同步操
作, 如果 slave 发送的偏移量已经不再 master 的同步备份日志中( 可能由于主从之间断掉
的时间比较长或者在断掉的短暂时间内 master 服务器接收到大量的写操作) , 则必须进行
一次全量更新。 在部分同步过程中, master 会将本地记录的同步备份日志中记录的指令依次
发送给 slave 服务器从而达到数据一致。

心跳信息
主从节点互相都会发送心跳信息。

master 默认每隔 10 秒发送一次心跳信息,slave 每隔 1 秒发送一个心跳信息。

注意点:

Redis 使用异步的方式将数据从 master 节点复制到 slave 节点,slave 会周期性地确认自己每次复制的数据量。
slave 做复制的时候,不会 block master 正常工作。
slave 做复制的时候,也不会 block 自己当前的查询工作,只是查询的时候依然会使用旧数据,等到复制完成后,需要删除老数据加载新数据的时候才会 block 当前的查询工作。
slave 主要用来做横向扩容,提升读的吞吐量,一定程度上做到了读的高可用。
slave 不一定要连接到 master ,也可以 slave 连接到 slave 。
slave 不会处理过期 key ,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。

4.2.2 哨兵

产生背景

下面给大家分享一个案例,这是生产环境真实发生过的事情,都是血的教训。

一个主从结构的 Redis 集群,当 master 没有配置开启数据持久化,某个时间,突然 master 节点宕机,然后自动重启,当 master 节点自动重启后,由于没有开启数据持久化,这时的 master 节点中是无数据的,当 salve 节点进行数据同步的时候,会把 salve 节点的数据也做清空操作。这时,整个主从结构的 Redis 集群全都没有数据,大量的请求过来发现 Redis 没有数据,导致请求落到了 DB 上,结果是毁灭性的。

主从模式存在的问题是,它仅仅只做到了读高可用,如果一旦 master 节点挂掉了,就没办法写数据了,只剩下 slave 节点还能读数据,但是数据同步也没有了,只能靠人工干预进行恢复,这并不是我们想要的高可用。

我们还需要写高可用,这就引出了 Redis 的高可用架构:哨兵模式。

简单来讲,哨兵模式就是在 master 节点不可用后,可以自发的进行主备切换,或者叫做故障转移。

哨兵模式概念

master 节点在发生故障后,整个集群可以自动检测,将某个 salve 自动切换成 master 节点,这种模式才算是实现了 Redis 真正意义上的高可用。
当主数据库遇到异常中断服务后,开发者可以通过手动的方式选择一个从数据库来升格为主数据库,以使得系统能够继续提供服务。

然而整个过程相对麻烦且需要人工介入,难以实现自动化。 为此,Redis 2.8开始提供了哨兵工具来实现自动化的系统监控和故障恢复功能。

哨兵的作用就是监控redis主、从数据库是否正常运行,主出现故障自动将从数据库转换为主数据库。

顾名思义,哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下几个。

(1)集群监控:负责监控 redis master 和 slave 进程是否正常工作。
(2)消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员
(3)故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
(4)配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

其模型如图:

在这里插入图片描述

Master与slave的切换过程:

(1)slave leader升级为master
(2)其他slave修改为新master的slave
(3)客户端修改连接
(4)老的master如果重启成功,变为新master的slave

哨兵的高可用

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

  • 故障转移时,判断一个 master node 是否宕机了,需要一半以上的哨兵都同意才行,涉及到了分布式选举
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
  • 哨兵通常为奇数个需要 3 个实例,来保证自己的健壮性。
  • 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
  • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演 练。

主观宕机和客观宕机

sdown: 主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机。
odown: 客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机。

sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。

为何需要奇数个实例

假如我们现在有两个哨兵实例,就长下面这样:

+----+         +----+
| M1 |---------| R1 |
| S1 |         | S2 |
+----+         +----+

再了解两个参数: quorum 、 majority

quorum: 表示认为 master 宕机的哨兵数量。
majority: 表示授权进行主从切换的最少的哨兵数量,而这个数字,需要大于一半。

在只有两个节点的情况下,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。

这时,需要 majority,也就是大多数哨兵都是运行的。

所以此时,如果此时仅仅是 M1 进程宕机了,哨兵 s1 正常运行,此时两个哨兵都认为M1已宕机,那么故障转移是 OK 的。但是如果是整个 M1 和 S1 运行的机器宕机了,那么哨兵只有 1 个,此时就没有 majority 来允许执行故障转移,虽然另外一台机器上还有一个 R1,但是故障转移不会执行。

所以就有了以下这一条建议:

哨兵至少需要 3 个实例,来保证自己的健壮性,并且实例数量最好是奇数。
因为在进行选举的时候,需要超过一半的哨兵同意,也就是 majority 。

2 个哨兵,majority=2
3 个哨兵,majority=2
4 个哨兵,majority=2
5 个哨兵,majority=3
6 个哨兵,majority=3
7 个哨兵,majority=4
...

可以看到,只有在奇数的时候,是可以最大化的利用 majority 数量。

      +----+
       | M1 |
       | S1 |
       +----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+

如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机了,然后选举出一个来执行故障转移,同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移。

Redis 哨兵模式数据丢失问题

首先先说一个结论:

哨兵 + Redis 主从的部署架构,是不保证数据零丢失的,只能保证 Redis 集群的高可用性。
然后我们再说两种会导致数据丢失的情况:

第一种:是异步复制可能会导致数据丢失

由于 master 向 salve 复制数据是异步,有可能,有部分数据还没有向 salve 复制,这时 master 宕机了,那么这部分数据就丢失了。

第二种:脑裂导致的数据丢失

脑裂是指,由于网络波动或者其他因素影响, master 所在的机器突然间无法被其他哨兵梭访问到,但是实际上这个 master 节点还在正常运行中。

此时哨兵会以为这个 master 节点已经宕机,开始进行新的 master 节点的选举,将其他的 salve 节点切换成了 master 节点,这时集群中就会存在两个 master 节点,也就是脑裂产生了。

这时虽然产生了新的 master 节点,但是客户端可能还没进行切换,还在像老的 master 写数据,但是当老的 master 恢复访问的时候,会被作为一个 salve 挂载到新的 master 节点上,自己的数据会被清空,重新从新的 master 复制数据,而在脑裂过程中写入老的 master 的数据就这么没了。

数据丢失的解决方案有么?
没有,因为这个问题是客观存在的,我们解决不了这个问题,只能尽量的去减少这个问题带来的损失,这时,可以使用下面这两个配置:

min-slaves-to-write 1
min-slaves-max-lag 10

这两个配置的意思是:

两个参数的意思:

要求至少有 1 个 slave ,数据复制和同步的延迟不能超过 10 秒。
如果说一旦所有的 slave ,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候, master 就不会再接收任何请求了。

(1) 减少异步复制的数据丢失:

有了 min-slaves-max-lag 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。

(2) 减少脑裂的数据丢失:

如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。

这样脑裂后的旧 master 就不会接受 client 的新数据,也就避免了数据丢失。

上面的配置就确保了,如果跟任何一个 slave 丢了连接,在 10 秒后发现没有 slave 给自己 ack ,那么就拒绝新的写请求

因此在脑裂场景下,最多就丢失 10 秒的数据。

参考:
1.老司机带你玩转面试(4):Redis 高可用之哨兵模式 https://www.cnblogs.com/babycomeon/p/13320801.html

四. redis-cluster集群

产生背景
即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用cluster群集,就是分布式存储。即每台redis存储不同的内容。

1.redis-cluster集群架构设计

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。

其架构如图所示:

在这里插入图片描述
也可以参照此图:
在这里插入图片描述

采用redis-cluster架构正是满足这种分布式存储要求的集群的一种体现。

redis-cluster架构中,被设计成共有16384个hash slot

每个master分得一部分slot,其算法为:hash_slot = crc16(key) mod 16384 ,这就找到对应slot。采用hash slot的算法,实际上是解决了redis-cluster架构下,有多个master节点的时候,数据如何分布到这些节点上去。
key是可用key,如果有{}则取{}内的作为可用key,否则整个可以是可用key。

群集至少需要3主3从,从节点只做备份,不做读写。且每个实例使用不同的配置文件。

2.redis-cluster集群去中心化设计

为什么 Redis Cluster 不适合超大规模集群?

Redis Cluster 的优点是易于使用。分片、主从复制、弹性扩容这些功能都可以做到自动化,通过简单的部署就可以获得一个大容量、高可靠、高可用的 Redis 集群,并且对于应用来说,近乎于是透明的。

所以,Redis Cluster 是非常适合构建中小规模 Redis 集群,这里的中小规模指的是,大概几个到几十个节点这样规模的 Redis 集群。

但是 Redis Cluster 不太适合构建超大规模集群,主要原因是,它采用了去中心化的设计。

刚刚我们讲了,Redis 的每个节点上,都保存了所有槽和节点的映射关系表,客户端可以访问任意一个节点,再通过重定向命令,找到数据所在的那个节点。那你有没有想过一个问题,这个映射关系表,它是如何更新的呢?比如说,集群加入了新节点,或者某个主节点宕机了,新的主节点被选举出来,这些情况下,都需要更新集群每一个节点上的映射关系表。

Redis Cluster 采用了一种去中心化的流言 (Gossip) 协议来传播集群配置的变化。由于此协议传播速度慢,增加和删除节点后同步数据非常慢,所以它并不适合做大规模集群。

一般涉及到协议都比较复杂,这里我们不去深究具体协议和实现算法,我大概给你讲一下这个协议原理。

所谓流言,就是八卦,比如说,我们上学的时候,班上谁和谁偷偷好上了,搞对象,那用不
了一天,全班同学都知道了。咋知道的?张三看见了,告诉李四,李四和王小二特别好,又
告诉了王小二,这样人传人,不久就传遍全班了。这个就是八卦协议的传播原理。
这个八卦协议它的好处是去中心化,传八卦不需要组织,吃瓜群众自发就传开了。
这样部署和维护就更简单,也能避免中心节点的单点故障。

八卦协议的缺点就是传播速度慢,并且是集群规模越大,传播的越慢。这个也很好理解,比如说,换成某两个特别出名的明星搞对象,即使是全国人民都很八卦,但要想让全国每一个人都知道这个消息,还是需要很长的时间。
在集群规模太大的情况下,数据不同步的问题会被明显放大,还有一定的不确定性,如果出现问题很难排查。

3.redis超大规模集群设计

Redis Cluster 不太适合用于大规模集群,所以很多大厂,都选择自己去搭建 Redis 集群。
所以对于大厂来说都会有专门的中间件团队去维护和研发,如果你只是业务架构师不用太过于追牛角尖。

这里面,每一家的解决方案都有自己的特色,但其实总体的架构都是大同小异的。

1.基于代理服务层

一种是基于代理的方式,在客户端和 Redis 节点之间,还需要增加一层代理服务。
在这里插入图片描述
像开源的 Redis 集群方案twemproxy和Codis,都是这种架构的。

这个代理服务有三个作用。

第一个作用是,负责在客户端和 Redis 节点之间转发请求和响应。客户端只和代理服务打交道,代理收到客户端的请求之后,再转发到对应的 Redis 节点上,节点返回的响应再经由代理转发返回给客户端。

第二个作用是,负责监控集群中所有 Redis 节点状态,如果发现有问题节点,及时进行主从切换。

第三个作用就是维护集群的元数据,这个元数据主要就是集群所有节点的主从信息,以及槽和节点关系映射表。

这个架构最大的优点是对客户端透明,在客户端视角来看,整个集群和一个超大容量的单节点 Redis 是一样的。并且,由于分片算法是代理服务控制的,扩容也比较方便,新节点加入集群后,直接修改代理服务中的元数据就可以完成扩容。

不过,这个架构的缺点也很突出,增加了一层代理转发,每次数据访问的链路更长了,必然会带来一定的性能损失。而且,代理服务本身又是集群的一个单点,当然,我们可以把代理服务也做成一个集群来解决单点问题,那样集群就更复杂了。

2.基于客户端(jar包)

另外一种方式是,不用这个代理服务,把代理服务的寻址功能前移到客户端中去。
客户端在发起请求之前,先去查询元数据,就可以知道要访问的是哪个分片和哪个节点,然后直连对应的 Redis 节点访问数据。

在这里插入图片描述
当然,客户端不用每次都去查询元数据,因为这个元数据是不怎么变化的,客户端可以自己缓存元数据,这样访问性能基本上和单机版的 Redis 是一样的。如果某个分片的主节点宕机了,新的主节点被选举出来之后,更新元数据里面的信息。对集群的扩容操作也比较简单,除了迁移数据的工作必须要做以外,更新一下元数据就可以了。

虽然说,这个元数据服务仍然是一个单点,但是它的数据量不大,访问量也不大,相对就比较容易实现。
我们可以用 ZooKeeper、etcd 甚至 MySQL 都能满足要求。

这个方案应该是最适合超大规模 Redis 集群的方案了,在性能、弹性、高可用几方面表现都非常好,缺点
是整个架构比较复杂,客户端不能通用,需要开发定制化的 Redis 客户端,只有规模足够大的企业才负担得起。

5.数据节点分区

1. 数据分区方案演进

1.哈希取余分区

哈希取余分区思路非常简单:计算key的hash值,然后对节点数量进行取余,从而决定数据映射到哪个节点上。

该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中所有的数据都需要重新计算映射关系,引发大规模数据迁移。

2.一致性哈希分区

一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,如下图所示,范围为(0,2^32-1);对于每个数据,根据key计算hash值,确定数据在环上的位置,然后从此位置沿环顺时针行走,找到的第一台服务器就是其应该映射到的服务器

在这里插入图片描述

优点:与哈希取余分区相比,一致性哈希分区将增减节点的影响限制在相邻节点。以上图为例,如果在node1和node2之间增加node5,则只有node2中的一部分数据会迁移到node5;如果去掉node2,则原node2中的数据只会迁移到node4中,只有node4会受影响。

缺点:一致性哈希分区的主要问题在于,当节点数量较少时,增加或删减节点,对单个节点的影响可能很大,主要为被迁移的节点的数据全部给另一个节点了,而没有均匀分布到其他节点,造成数据的严重不平衡。还是以上图为例,如果去掉node2,node4中的数据由总数据的1/4左右变为1/2左右,与其他节点相比负载过高。

3.带虚拟节点的一致性哈希分区

该方案在一致性哈希分区的基础上,引入了虚拟节点的概念。Redis集群使用的便是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念;每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。引入槽以后,数据的映射关系由数据hash->实际节点,变成了数据hash->槽->实际节点。

在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系,增加或删除节点对系统的影响很小,实际只会对槽进行重新分配。仍以上图为例,系统中有4个实际节点,假设为其分配16个槽(0-15); 槽0-3位于node1,4-7位于node2,以此类推。如果此时删除node2,只需要将槽4-7重新分配即可,例如槽4-5分配给node1,槽6分配给node3,槽7分配给node4;可以看出删除node2后,数据在其他节点的分布仍然较为均衡。

槽的数量一般远小于2^32,远大于实际节点的数量;在Redis集群中,槽的数量为16384。

下面这张图很好的总结了Redis集群将数据映射到实际节点的过程:

在这里插入图片描述

(1)Redis对数据的特征值(一般是key)计算哈希值,使用的算法是CRC16。

(2)根据哈希值,计算数据属于哪个槽。使用算法:哈希值对16384取模

(3)根据槽与节点的映射关系,计算数据属于哪个节点。

分区的原理是采用带虚拟节点的一致性哈希分区

一致性哈希相关的原理请参考:

程序员小灰-漫画:什么是一致性哈希?https://blog.csdn.net/xaccpJ2EE/article/details/82993120?utm_source=blogxgwz7

2.客户端读写集群原理

redis-cli

(1)计算key属于哪个槽:CRC16(key) & 16383

(2)判断key所在的槽是否在当前节点:假设key位于第i个槽,clusterState.slots[i]则指向了槽所在的节点,如果clusterState.slots[i]==clusterState.myself,说明槽在当前节点,可以直接在当前节点执行命令;

否则,说明槽不在当前节点,则查询槽所在节点的地址(clusterState.slots[i].ip/port),并将其包装到MOVED错误中返回给redis-cli。

(3)redis-cli收到MOVED错误后,根据返回的ip和port重新发送请求,从对应节点中找到key所对应的数据。

具体例子:

请参考:深入学习Redis(5):集群
https://www.cnblogs.com/kismetv/p/9853040.html

思考

1.redis-cluster集群如何保证高可用?

在redis-cluster架构中,redis-master节点一般用于接收读写,而redis-slave节点则一般只用于备份,其与对应的master拥有相同的slot集合,若某个redis-master意外失效,则再将其对应的slave进行升级为临时redis-master。

数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)

2.redis-cluster集群有何优劣?

优点

  • 无中心架构,支持动态扩容,对业务透明
  • 具备Sentinel的监控和自动Failover(故障转移)能力
  • 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • 高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

  • 运维也很复杂,数据迁移需要人工干预
  • 只能使用0号数据库???
  • 不支持批量操作(pipeline管道操作)
  • 分布式逻辑和存储模块耦合等

3.redis-cluster集群为何对批量多 KEY 命令支持不友好?

这几种集群方案对一些类似于“KEYS”这类的多 KEY 命令,都没法做到百分百支持。
原因很简单,数据被分片了之后,这种多 KEY 的命令很可能需要跨多个分片查询。当你的系统从单个 Redis 库升级到集群时,可能需要考虑一下这方面的兼容性问题。

4.一个 Redis 实例最多能存放多少的 keys? List、 Set、 Sorted Set 他们最多能存放多少元素?

Redis 的存储极限是系统中的可用内存值。

一个Redis实例最多能存放理论上2的32次方个keys(40亿),并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys

任何_hash、_list、set、和sorted set都可以放2的32次方个keys(40亿)个元素。

六.Redis 客户端Sharding

Redis Sharding是一种客户端Sharding技术。

Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。
其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。
Java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的
ShardedJedisPool

优点

优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强

缺点

由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。

客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化

五、Redis应用

热key问题

所谓热key问题就是,突然有⼏⼗万的请求去访问redis上的某个特定key,那么这样会造成流量过于集中,达到物理⽹卡上限,从⽽导致这台redis的服务器宕机引发雪崩。

在这里插入图片描述
针对热key的解决⽅案:

  1. 提前把热key打散到不同的服务器,降低压⼒
  2. 加⼊⼆级缓存,提前加载热key数据到内存中,如果redis宕机,⾛内存查询

5.2 如何解决redis的并发竞争key问题

分析:这个问题大致就是,同时有多个子系统去set一个key。可能会导致的问题:数据库和缓存的数据不一致。即数据库a对应的valueA,在缓存中不能被其他值覆盖。

回答:如下所示
(1)如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(2)如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下

系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}

那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。

参考资料

1.为什么分布式一定要有redis? https://www.cnblogs.com/bigben0123/p/9115597.html
2.redis 五种数据结构详解(string,list,set,zset,hash)https://www.cnblogs.com/sdgf/p/6244937.html
3.redis架构演变与redis-cluster群集读写方案 https://yq.aliyun.com/articles/625912?utm_content=m_1000013190
4.程序员小灰-漫画:什么是一致性哈希?https://blog.csdn.net/xaccpJ2EE/article/details/82993120?utm_source=blogxgwz7
5.深入学习Redis(5):集群https://www.cnblogs.com/kismetv/p/9853040.html

6.缓存穿透,缓存击穿,缓存雪崩解决方案分析https://blog.csdn.net/zeb_perfect/article/details/54135506

7.深入理解分布式系统中的缓存架构(上) https://blog.csdn.net/yelvgou9995/article/details/81079463

8.《深入分布式缓存》之“亿级请求下多级缓存那些事 https://blog.csdn.net/wireless_com/article/details/79134166

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值