Redis集群

JayX

1.Redis集群说明

1.1 为什么要搭建集群

通常,为了提高网站响应速度,总是把热点数据保存在内存中而不是直接从后端数据库中读取。Redis 是一个很好的 Cache 工具。大型网站应用,热点数据量往往巨大,几十 G 上百 G是很正常的事儿。由于内存大小的限制,使用一台 Redis 实例显然无法满足需求,这时就需要使用多台Redis 作为缓存数据库。但是如何保证数据存储的一致性呢,这时就需要搭建 redis 集群. 采用合理的机制,保证用户的正常的访问需求. 采用 redis 集群,可以保证数据分散存储,同时保证数据存储的一致性.并且在内部实现高可用的机制.实现了服务故障的自动迁移.

1.2 Redis集群原理

Redis集群高可用推选原理
image.png

原理说明:
Redis 的所有节点都会保存当前 redis 集群中的全部主从状态信息.并且每个节点都能够相互通信.当一个节点发生宕机现象.则集群中的其他节点通过 PING-PONG 检测机制 检查Redis节点是否宕机.当有半数以上的节点认为宕机.则认为主节点宕机.同时由Redis剩余的主节点进入选举机制.投票选举连接宕机的主节点的从机.实现故障迁移.

2.集群搭建

2.1 集群搭建计划

主从划分:
主机:3台
从机:3台
端口:7000-7005

2.2 准备集群文件夹

1.准备集群文件夹

mkdir  cluster

2.在 cluster 文件夹中分别创建 7000-7005 文件夹

cd  cluster
mkdir  7000  7001  7002  7003  7004  7005

image.png

2.3 编辑配置文件

配置好的redis.conf
https://segmentfault.com/a/11...

说明:
将 redis 根目录中的 redis.conf 文件复制到 cluster/7000/ 并以原名保存

cp  redis.conf  cluster/7000/

image.png

2.4 编辑配置文件

1.打开redis.conf文件

cd  7000
ls

在文件目录窗口中redis.conf文件
image.png

2.注释本地绑定 IP 地址
image.png

3.关闭保护模式(改为no)
image.png

4.修改端口号(改为7000)
image.png

5.修改启动方式为后台启动(改为yes)
image.png

6.修改 pid 文件
pidfile /usr/local/src/redis/clusters/7000/redis.pid
image.png

7.修改持久化文件路径
image.png

8.设定内存优化策略
image.png

9.关闭 AOF 模式
image.png

10.开启集群配置
image.png

11.开启集群配置文件
image.png

12.修改集群超时时间,并保存退出
image.png

2.5 复制修改后的配置文件

说明:将 7000 文件夹下的 redis.conf 文件分别复制到 7001-7005 中

cp  7000/redis.conf  7001
cp  7000/redis.conf  7002
cp  7000/redis.conf  7003
cp  7000/redis.conf  7004
cp  7000/redis.conf  7005

image.png

2.6 批量修改

说明:分别将 7001-7005 文件中的 7000 改为对应的端口号的名称, 修改时注意方向键的使用

vim  7001/redis.conf

image.png

使用文件批量修改文件内容

:%s/7000/7001/g
:wq

image.png
image.png
image.png

把7002-7005目录下redis.conf文件一起修

:%s/7000/7002/g
:%s/7000/7003/g
:%s/7000/7004/g
:%s/7000/7005/g

image.png

2.7 通过脚本编辑启动/关闭指令

1.创建启动脚本:

vim  start.sh

2.编辑启动命令:

#!/bin/sh
redis-server  7000/redis.conf &
redis-server  7001/redis.conf &
redis-server  7002/redis.conf &
redis-server  7003/redis.conf &
redis-server  7004/redis.conf &
redis-server  7005/redis.conf &
:wq

image.png

3.创建关闭脚本:

vim  stop.sh

4.编辑关闭命令:

#!/bin/sh
redis-cli -p 7000 shutdown &
redis-cli -p 7001 shutdown &
redis-cli -p 7002 shutdown &
redis-cli -p 7003 shutdown &
redis-cli -p 7004 shutdown &
redis-cli -p 7005 shutdown &
:wq

image.png

5.测试启动/关闭 redis 脚本是否正常:

sh  start.sh
sh  stop.sh
ps  -ef  |  grep  redis

image.png

2.8 创建 redis 集群

5.0版本执行 --cluster-replicas 1 一主一从的结构(1组1主1从)

redis-cli --cluster create --cluster-replicas 1 192.168.126.129:7000 192.168.126.129:7001 192.168.126.129:7002 192.168.126.129:7003 192.168.126.129:7004 192.168.126.129:7005
yes

image.png
image.png

2.9 Redis 集群高可用测试

删除 appendonly.aof dump.rdb nodes.conf 文件

cd  7000
ls
rm  -rf  700*/*.aof
rm  -rf  700*/*.rdb
rm  -rf  700*/nodes.conf

image.png

  1. 关闭 redis 主机.检查是否自动实现故障迁移.
  2. 再次启动关闭的主机.检查是否能够实现自动的挂载. 一般情况下 能够实现主从挂载个别情况: 宕机后的节点重启,可能挂载到其他主节点中(7001-7002) 正确的
redis-cli  -p  7000
info  repcation

image.png

redis-cli  -p  7000  shutdown
redis-cli  -p  7003
info  repcation

image.png

2.10 Redis 集群宕机条件

特点: 集群中如果主机宕机,那么从机可以继续提供服务, 当主机中没有从机时,则向其它主机借用多余的从机.继续提供服务.如果主机宕机时,没有从机可用,则集群崩溃.

例如:
6个redis节点(1主1从分3组),节点宕机2个或以上时集群崩溃。
9个redis节点(1主2从分3组),节点宕机5-7次时集群才崩溃.
image.png

3.Redis分区算法

3.1 集群测试入门案例

package com.jt.test;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;
public class TestCluster {
     //分片机制类似   一致性hash算法 hash槽算法
     @Test
     public void test01(){
         Set<HostAndPort> set = new HashSet<>();
         set.add(new HostAndPort("192.168.126.129", 7000));
         set.add(new HostAndPort("192.168.126.129", 7001));
         set.add(new HostAndPort("192.168.126.129", 7002));
         set.add(new HostAndPort("192.168.126.129", 7003));
         set.add(new HostAndPort("192.168.126.129", 7004));
         set.add(new HostAndPort("192.168.126.129", 7005));
         JedisCluster jedisCluster = new JedisCluster(set);
         jedisCluster.set("cluster", "redis集群测试");
         System.out.println(jedisCluster.get("cluster"));
     }
}

3.2 Redis hash 槽存储数据原理

说明: RedisCluster 采用此分区,所有的键根据哈希函数(CRC16[key]%16384)映射到 0-16383 槽内,共 16384 个槽位,每个节点维护部分槽及槽所映射的键值数据.根据主节点的个数,均衡划分区间.

算法:
哈希函数: Hash()=CRC16[key]%16384

image.png

当向 redis 集群中插入数据时,首先将 key 进行计算.之后将计算结果匹配到具体的某一个槽的区间内,之后再将数据 set 到管理该槽的节点中.

image.png

4.关于算法面试题

4.1 关于集群/分片算法说明

问题:一个数据很大.一个槽位不够存怎么办???
错误?? A 逻辑错误 B. 有道理

解答:
1.一致性hash算法 hash(key) 43亿 按照顺时针方向找到最近的节点 进行set操作.
2.Hash槽算法 crc16(key)%16384 (0-16383) 计算的结果归哪个节点管理,则将数据保存到节点中.

核心知识: 一致性hash算法/hash槽算法 都是用来确定 数据归谁管理的问题. 最终的数据都会存储到node节点中.

4.2 面试题1

问: Redis集群中一共可以存储16384个数据?
A 对 B 错 为什么???

小明猜想: 由于redis中共有16384个槽位,所以每个槽位存储一个key.那么不就是16384个key吗??
答案: 错误

原因:
Redis集群中确实有16384个槽位.但是这些槽位是用来划分数据归谁管理的.不是用来存储数据的. 并且根据hash计算的规则肯能出现碰撞的问题.

例如:
hash(key1)%16384=3000
hash(key2)%16384=3000

说明key1和key2归同一个node管理.

node.set(key1,value1);
node.set(key2,value2);

由于槽位只是用来区分数据,数据到底能存储多少个完成由redis内存决定.

4.3 面试题2

问题: 为Redis集群中最多有多少台主机?? 16384台主机

5.缓存相关面试题

5.1 缓存穿透

说明:
用户高并发环境下,频繁访问数据库中不存在的数据.导致用户请求直接访问数据库.严重时导致数据库服务器宕机.
image.png

解决方案:

  1. IP限流操作:

API网关中设置,设定用户访问的上限,规定每个IP单位时间内只能发送N次请求. 设置3-5次
治标不治本 (IP代理服务器: 1分钟变化一个IP)

  1. 布隆过滤器

5.2 缓存击穿

说明:
在高并发环境下 某个热点数据由于删除/超时导致该数据在缓存中失效. 这时有大量的请求直接访问数据库.导致数据库宕机.
image.png

如何优化:
1.定期更新热点数据的超时时间.
2.增加多级缓存机制.

5.3 缓存雪崩

概念:
由于Redis中大量的内存数据失效.导致用户访问缓存的命中率太低.大量的请求直接访问数据库.导致数据库宕机.

命令: flushDB/flushAll这样的命令慎用…

解决方案:
1.设定不同的超时时间/动态更新超时时间
2.设定多级缓存.

image.png

6.布隆过滤器

6.1 说明:

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

核心知识点:
用法: 由二进制向量,hash函数组合.
作用: 判断一个元素是否存在于集合中.
优点: 占用空间更小/效率更高
缺点: 有一定的误判率(hash碰撞), 删除困难.

当数据库的一些数据被删除时,布隆过滤器的记录不能删除,因hash碰撞,不同的数据经过hash运算,可能得出相同的hash值。

6.2 优点说明

问题: 如果有1000万的热点数据需要保存到redis缓存中. 问:是否可行?
计算1: 1000万的数据如果需要存储 大约需要20G左右的空间…

知识铺垫:
1 Byte = 8 bit (二进制)。
1KB (Kilobyte 千)=1024B,
  1MB (Megabyte 兆)=1024KB,
  1GB (Gigabyte 吉)=1024MB,
  1TB (Trillionbyte 太)=1024GB,

转变: 假设可以通过 0/1的方式,判断数据是否存在.同时占用的空间较小.那么这个问题就解决了.
计算2: 假设1个数据占用1个bit 问占用空间多大?
1.19M

6.3 布隆过滤器应用场景

说明:当用户查询服务器时,首先查询布隆过滤器,如果查询存在该数据,则执行后续的流程,
如果查询没有该数据,则直接返回.无需执行后续流程.

image.png

6.4 布隆过滤器算法介绍

image.png

6.5 关于布隆过滤器优化说明

1.根据hash原则 数据存在hash碰撞的概率. 则使用布隆过滤器容器造成误判. 如何解决?
image.png

6.6 优化hash碰撞概率-增加二进制向量

6.7 优化hash碰撞概率-增加hash函数个数

image.png

6.8 关于布隆在项目中使用过程

image.png

7.SpringBoot整合Redis集群

7.1 编辑properties配置文件

# 准备redis节点信息
redis.host=192.168.126.129
redis.port=6379

# 分片 准备3台redis
#redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

# redis集群  准备6个redis节点
redis.nodes=192.168.126.129:7000,192.168.126.129:7001,192.168.126.129:7002,192.168.126.129:7003,192.168.126.129:7004,192.168.126.129:7005

7.2 编辑配置类

@Configuration //表示一个配置类 一般会与@bean注解一起使用
@PropertySource("classpath:redis.properties") //导入配置文件
public class RedisConfig {
     @Value("${redis.nodes}")
     private String nodes;
     
     //Springboot整合redis集群
     @Bean
     public JedisCluster jedisCluster(){
         Set<HostAndPort> nodesSet = new HashSet<>();
         String[] clusterStr = nodes.split(",");
         for (String node:clusterStr){
         //分割出ip
         String host = node.split(":")[0];
         //分割出端口,并转为int类型
         int port = Integer.parseInt(node.split(":")[1]);
         nodesSet.add(new HostAndPort(host,port));
     }
     return new JedisCluster(nodesSet);
 }

7.3 编辑CacheAOP

@Component //组件 将类交给spring容器管理
@Aspect //表示我是一个切面
public class RedisAOP {

     @Autowired
    //  private Jedis jedis;  //使用单台中redis
    //  private ShardedJedis jedis;  //使用分片多台redis
     private JedisCluster jedis; //使用分片多台redis
     
     /**实现AOP业务调用
     *  1.拦截指定的注解
     *  2.利用环绕通知实现
     * 实现步骤:
     *          1.获取 key 必须先获取注解
     * ProceedingJoinPoint:只能在@Around注解中使用,并且必须位于参数第一位
     */
     @Around("@annotation(cacheFind)")
     public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
         Object result =null;
         //1.获取key=业务名称::参数
         String key = cacheFind.key();
         String args = Arrays.toString(joinPoint.getArgs());
         key = key+"::"+args;
         System.out.println(key);
         //2.校验是否有值
         if (jedis.exists(key)){
             String json = jedis.get(key);
             MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
             Class returnType = methodSignature.getReturnType();
             result = ObjectMapperUtil.toObject(json,returnType);
             System.out.println("AOP查询redis缓存");
         }else {
             //redis中没有数据,要先查询数据库,将数据保存到缓存中
             try {
                 result = joinPoint.proceed();
                 String json = ObjectMapperUtil.toJson(result);
                 //是否设定超时时间
                 if (cacheFind.seconds()>0){
                 jedis.setex(key,cacheFind.seconds(),json);
             }else {
                 jedis.set(key,json);
             }
                 System.out.println("AOP查询数据库");
             } catch (Throwable throwable) {
                 throwable.printStackTrace();
                 throw new RuntimeException(throwable);
             }
         }
         return result;
     }
 }
阅读 177
4 声望
0 粉丝
0 条评论
你知道吗?

4 声望
0 粉丝
宣传栏