Redis高级部分(集群,发布订阅)
Redis高级部分(集群,发布订阅)
1. Redis 主从复制
1.1 主从复制
主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据
无法解决: 1.master节点出现故障的自动故障转移
1.2 主从复制架构图
1.3 搭建主从复制(做之前拍快照)(带同学们结合官网看redis7.0.0的做法)
第一步,创建三个目录代表三台机器,master,node1,node2
拷贝源码中的redis.conf分别到master,node1,node2中
[root@master redis-install]# cp redis-7.0.0/redis.conf ./master/ [root@master redis-install]# cp redis-7.0.0/redis.conf ./node1/ [root@master redis-install]# cp redis-7.0.0/redis.conf ./node2/
# 1.准备3台机器并修改配置,修改端口号,开启远程连接,配置主节点是谁
- master
将127.0.0.1注释
port 8000
protected-mode no
- node1
port 8001
protected-mode no
replicaof <masterip> <masterport> # replicaof 192.168.40.110 8000
- node2
port 8002
protected-mode no
replicaof <masterip> <masterport> # replicaof 192.168.40.110 8000
# 2.启动3台机器进行测试
- cd /usr/local/soft/bigdata17/redis-install
- redis-server ./master/redis.conf
- redis-server ./node1/redis.conf
- redis-server ./node2/redis.conf
2. Redis哨兵机制(Hadoop HA)
2.1 哨兵Sentinel机制
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说哨兵就是带有自动故障转移功能的主从架构。
无法解决: 1.单节点并发压力问题 2.单节点内存和磁盘物理上限
2.2 哨兵架构原理
3. Redis集群
3.1 集群
Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard )等特性。reshard
PING PONG协议(心跳机制)
3.2 集群架构图
3.3 集群细节
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效.(半数机制,后面hadoop也会说到 )
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
3.4 集群搭建
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点。
# 1.准备环境安装ruby以及redis集群依赖
- yum install -y ruby rubygems
# https://rubygems.org/gems/redis/versions
- gem install redis-xxx.gem
# 2.在一台机器创建7个目录
# 3.每个目录复制一份配置文件
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7000/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7001/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7002/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7003/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7004/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7005/
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/redis.conf ./7007/
# 4.修改不同目录配置文件
- port 7000 ..... //修改端口
- # bind 127.0.0.1 -::1 //开启远程连接
- protected-mode no
- daemonize yes //开启守护进程
- dbfilename dump-7000.rdb //每台机器的文件不能一样
- cluster-enabled yes //开启集群模式
- cluster-config-file nodes-7000.conf //集群节点配置文件
- cluster-node-timeout 5000 //集群节点超时时间
- appendonly yes //开启AOF持久化
- appendfilename "appendonly-7000.aof" //修改aof文件名
- appenddirname "appendonlydir-7000" //redis7.0 以上的版本需要配置
# 5.指定不同目录配置文件启动七个节点(7003,7004的忘记修改了守护进程)
[root@master redis-cluster]# redis-server 7000/redis.conf
[root@master redis-cluster]# redis-server 7001/redis.conf
[root@master redis-cluster]# redis-server 7002/redis.conf
[root@master redis-cluster]# redis-server 7003/redis.conf
[root@master redis-cluster]# redis-server 7004/redis.conf
[root@master redis-cluster]# redis-server 7005/redis.conf
[root@master redis-cluster]# redis-server 7006/redis.conf
# 6.查看进程
- [root@localhost bin]# ps aux|grep redis
1.创建集群
# 1.复制集群操作脚本到bin目录中
[root@master redis-cluster]# cp /usr/local/soft/bigdata17/redis-install/redis-7.0.0/src/redis-trib.rb /usr/local/soft/redis/bin/
# 2.创建集群
早期的命令:redis-trib.rb create --replicas 1 192.168.169.100:7000 192.168.169.100:7001 192.168.169.100:7002 192.168.169.100:7003 192.168.169.100:7004 192.168.169.100:7005
新的的命令:redis-cli --cluster create 192.168.220.100:7000 192.168.220.100:7001 192.168.220.100:7002 192.168.220.100:7003 192.168.220.100:7004 192.168.220.100:7005 --cluster-replicas 1
# 3.集群创建成功出现如下提示
2.查看集群状态
# 1.查看集群状态 check [原始集群中任意节点] [无]
redis-cli --cluster check 192.168.254.100:7000
# 2.集群节点状态说明
- 主节点
主节点存在hash slots,且主节点的hash slots 没有交叉
主节点不能删除
一个主节点可以有多个从节点
主节点宕机时多个副本之间自动选举主节点
- 从节点
从节点没有hash slots
从节点可以删除
从节点不负责数据的写,只负责数据的同步
使用集群(演示其中一个主节点宕机的状态,然后从节点接管)
3.添加子节点
# 1.添加主节点 add-node [新加入节点] [原始集群中任意节点]
redis-cli --cluster add-node 192.168.21.150:7006 192.168.21.150:7001 --cluster-slave
- 注意:
1.该节点必须以集群模式启动
2.默认情况下该节点就是以master节点形式添加
4.删除副本节点
# 1.删除节点 del-node [集群中任意节点] [删除节点id]
- redis-cli --cluster del-node 192.168.169.100:7002 f303c5ae2065accd204c96739fb0bf8e1ba0880e
- 注意:
1.被删除的节点必须是从节点或没有被分配hash slots的节点
Redis面试题与理解:穿透、雪崩、击穿
# 1、缓存穿透:(要查询的数据根本不存在)
是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
当请求查询一个不存在于缓存中,也不存在于后端数据源中的数据时,每次请求都会直接访问后端数据源。这可能导致后端系统负载增加,甚至引起拒绝服务攻击。
# 2、缓存雪崩:(数据有,但是过期时间到了)
简单的理解为:由于原有缓存失效,新缓存未到时间 (例如:设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。指的是当针对一个热点数据的并发请求同时失效,导致大量请求落到后端数据源上,压垮后端数据库或造成服务不可用。
# 3、缓存击穿:(针对某一个数据突然过期,直接查数据库)
某个 key 非常非常热,访问非常的频繁,高并发访问的情况下,当这个 key在失效(可能expire过期了,也可能LRU淘汰了)的瞬间,大量的请求进来,这时候就击穿了缓存,直接请求到了数据库,一下子来这么多,数据库肯定受不了,这就叫缓存击穿。某个key突然失效,然后这时候高并发来访问这个key,结果缓存里没有,都跑到db了。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。缓存雪崩指的是当缓存中大量的数据同时过期,导致大量请求落到后端数据源上,造成数据库压力过大甚至服务不可用。
# 三者出现的根本原因是:Redis缓存命中率下降,请求直接打到DB上了
正常情况下,大量的资源请求都会被redis响应,在redis得不到响应的小部分请求才会去请求DB,这样DB的压力是非常小的,是可以正常工作的:画图
如果大量的请求在redis上得不到响应,那么就会导致这些请求会直接去访问DB,导致DB的压力瞬间变大而卡死或者宕机。
- 大量的高并发的请求打在redis上
- 这些请求发现redis上并没有需要请求的资源,redis命中率降低
- 因此这些大量的高并发请求转向DB(数据库服务器)请求对应的资源
- DB压力瞬间增大,直接将DB打垮,进而引发一系列“灾害”
画图:不正常工作的情况
解决方案:
1. 缓存穿透
根本原因(结合上文)就是:请求根本不存在的资源
举例:客户端发送大量的不可响应的请求(如下图)
解决方案:
# 对空值进行缓存 类似于上面的例子,虽然数据库中没有id=-1234的用户的数据,但是在redis中对他进行缓存(key=-1234,value=null),这样当请求到达redis的时候就会直接返回一个null的值给客户端,避免了大量无法访问的数据直接打在DB上。 # 实时监控 对redis进行实时监控,当发现redis中的命中率下降的时候进行原因的排查,配合运维人员对访问对象和访问数据进行分析查询,从而进行黑名单的设置限制服务。 # 使用布隆过滤器 使用BitMap作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截。 # 接口校验 类似于用户权限的拦截,对于id=-1234这些无效访问就直接拦截,不允许这些请求到达Redis、DB上。
2. 缓存雪崩
产生的原因:redis中大量的key集体过期
比如:当redis中的大量key集体过期,可以理解为redis中的大部分数据都被清空了(失效了),那么这时候如果有大量并发的请求来到,那么redis就无法进行有效的响应(命中率急剧下降),请求就都打到DB上了,到时DB直接崩溃。
解决方案:
1. 使用互斥锁(Mutex Lock)或分布式锁,只允许一个请求去访问后端数据源,其他请求等待并共享结果。 2. 将失效时间分散开, 通过使用自动生成随机数使得key的过期时间是随机的,防止集体过期 3. 使用多级架构,使用nginx缓存+redis缓存+其他缓存,不同层使用不同的缓存,可靠性更强 4. 设置缓存标记,记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际的key 5. 设置热点数据的永远不过期或过期时间较长,以减少热点数据失效的机会。
3. 缓存击穿
产生的原因:redis中的某个热点key过期,但是此时有大量的用户访问该过期key。
比如:类似于“刘某某今日结婚”上了热搜,这时候大量的“粉丝”都在访问该热点事件,但是可能由于某种原因,redis的这个热点key过期了,那么这时候大量高并发对于该key的请求就得不到redis的响应,那么就会将请求直接打在DB服务器上,导致整个DB瘫痪。
解决方案:
1. 为缓存数据设置不同的过期时间,使其在不同时间点过期,避免集中失效。监控数据,适时调整,监控哪些数据是热门数据,实时的调整key的过期时长 2. 引入两级缓存架构,例如使用本地缓存(如Guava Cache)作为第一级缓存,并设置较短的过期时间,Redis作为第二级缓存,并设置较长的过期时间。 3. 针对热点数据,可以提前进行预加载,保证其缓存不会在同一时间全部失效。
Redis的发布与订阅
Redis有两种发布/订阅模式:
- 基于频道(Channel)的发布/订阅
- 基于模式(pattern)的发布/订阅
序号 | 命令与描述 |
---|---|
1 | subscribe channel [channel ... ] 订阅给定的一个或多个频道 |
2 | unsubscribe channel [channel ... ] 退订给定的频道 说明:若没有指定channel,则默认退订所有频道 |
3 | publish channel message 将消息发送给指定频道 channel 返回结果:接收到信息的订阅者数量,无订阅者返回0 |
4 | pubsub channels [argument [atgument ...] ] 查看订阅与发布系统的状态 说明:返回活跃频道列表(即至少有一个订阅者的频道,订阅模式的客户端除外) |
5 | psubscribe pattern1 [pattern...] 订阅一个或多个符合给定模式的频道 说明:每个模式以 * 作为匹配符;例如 shujia* 匹配所有以shujia开头的频道:shujia.java、shujia.hadoop |
6 | punsubscribe [pattern [pattern ...] ] 退订所有给定模式的频道 说明:pattern 未指定,则订阅的所有模式都会被退订,否则只退订指定的订阅的模式 |
说到发布订阅就不得不提前说到消息队列了
redis中的List数据类型其实可以将数据进行排队,然后按照顺序取出,但是没法精确获取(俗称 双端队列)
这种方式存在两个局限性:双端队列模式只能有一个或多个消费者轮着去消费,却不能将消息同时发给其他消费者
- 不能支持一对多的消息分发。
- 如果生产者生成的速度远远大于消费者消费的速度,易堆积大量未消费的消息
redis中的发布订阅模式,是一种消息传递模式,可用于实现多个客户端之间的实时消息通信。在这种模式下,消息发送者称为发布者,而接收消息的客户端称为订阅者。
角色:
发布者(Publisher)
- 使用
PUBLISH
命令将消息发布到指定的频道。订阅者(Subscriber)
- 使用
SUBSCRIBE
命令订阅一个或多个频道以接收消息。所有订阅同一频道的订阅者都会收到此消息。频道(Channel)
- 频道是消息的通道,发布者通过指定频道来发布消息,订阅者通过订阅频道来接收相关消息。
消息(Message)
- 消息是由发布者发送给订阅者的数据。
# 注意事项: 1. 订阅者使用 SUBSCRIBE 命令订阅频道后,将会一直等待新消息。可以使用 UNSUBSCRIBE 命令取消订阅。 2. Redis 的发布订阅模式不保证消息的可靠传递。如果订阅者在消息发布之前已断开连接,则无法接收到已发布的消息。换句话说,如果是先发布消息,再订阅频道,不会收到订阅之前就发布到该频道的消息! 3. Redis 提供了其他相关的命令和功能,如按模式订阅(PSubscribe)、取消订阅(Unsubscribe)等。可以查阅 Redis 文档以获取更多信息。 # 请注意,在高吞吐量和大规模发布订阅场景下,Redis 的发布订阅模式可能不是最佳选择,可以考虑使用专门的消息队列系统来替代。
状态解释:
localhost:7000> subscribe xiaohu1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" # 返回值类型:表示订阅成功! 2) "xiaohu1" # 订阅频道的名称 3) (integer) 1 # 当前客户端已订阅频道的数量
底层C语言源码:
struct redisServer { /* General */ pid_t pid; //省略百十行 // 将频道映射到已订阅客户端的列表(就是保存客户端和订阅的频道信息) dict *pubsub_channels; /* Map channels to list of subscribed clients */ }
带同学们画图理解
频道订阅:订阅频道时先检查字段内部是否存在;不存在则为当前频道创建一个字典且创建一个链表存储客户端id;否则直接将客户端id插入到链表中。
取消频道订阅:取消时将客户端id从对应的链表中删除;如果删除之后链表已经是空链表了,则将会把这个频道从字典中删除。
发布:首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端
案例2:Redis当作缓存,mysql查询案例
案例3:Redis当作索引存储,mysql存储详细数据(二级索引)
案例1:Redis充当短信验证码,设置过期时间
Maven依赖
【阿里巴巴】恭喜您,入职阿里巴巴!! 【中国电信】您的余额已不足,请及时充值!
<dependencies> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.4.3</version> </dependency> <!-- https://mvnrepository.com/artifact/com.aliyun/dysmsapi20170525 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.23</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-openapi</artifactId> <version>0.2.8</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-console</artifactId> <version>0.0.1</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-util</artifactId> <version>0.2.16</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea</artifactId> <version>1.1.14</version> </dependency> </dependencies>
测试号码:
178***6091 刘*保 158***0170 洪*霁 187***0510 唐*峰