1.Redis集群说明
1.1 为什么要搭建集群
通常,为了提高网站响应速度,总是把热点数据保存在内存中而不是直接从后端数据库中读取。Redis 是一个很好的 Cache 工具。大型网站应用,热点数据量往往巨大,几十 G 上百 G是很正常的事儿。由于内存大小的限制,使用一台 Redis 实例显然无法满足需求,这时就需要使用多台Redis 作为缓存数据库。但是如何保证数据存储的一致性呢,这时就需要搭建 redis 集群. 采用合理的机制,保证用户的正常的访问需求. 采用 redis 集群,可以保证数据分散存储,同时保证数据存储的一致性.并且在内部实现高可用的机制.实现了服务故障的自动迁移.
1.2 Redis集群原理
Redis集群高可用推选原理
原理说明:
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
2.3 编辑配置文件
配置好的redis.conf
https://segmentfault.com/a/11...
说明:
将 redis 根目录中的 redis.conf 文件复制到 cluster/7000/ 并以原名保存
cp redis.conf cluster/7000/
2.4 编辑配置文件
1.打开redis.conf文件
cd 7000
ls
在文件目录窗口中redis.conf文件
2.注释本地绑定 IP 地址
3.关闭保护模式(改为no)
4.修改端口号(改为7000)
5.修改启动方式为后台启动(改为yes)
6.修改 pid 文件
pidfile /usr/local/src/redis/clusters/7000/redis.pid
7.修改持久化文件路径
8.设定内存优化策略
9.关闭 AOF 模式
10.开启集群配置
11.开启集群配置文件
12.修改集群超时时间,并保存退出
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
2.6 批量修改
说明:分别将 7001-7005 文件中的 7000 改为对应的端口号的名称, 修改时注意方向键的使用
vim 7001/redis.conf
使用文件批量修改文件内容
:%s/7000/7001/g
:wq
把7002-7005目录下redis.conf文件一起修
:%s/7000/7002/g
:%s/7000/7003/g
:%s/7000/7004/g
:%s/7000/7005/g
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
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
5.测试启动/关闭 redis 脚本是否正常:
sh start.sh
sh stop.sh
ps -ef | grep redis
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
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
- 关闭 redis 主机.检查是否自动实现故障迁移.
- 再次启动关闭的主机.检查是否能够实现自动的挂载. 一般情况下 能够实现主从挂载个别情况: 宕机后的节点重启,可能挂载到其他主节点中(7001-7002) 正确的
redis-cli -p 7000
info repcation
redis-cli -p 7000 shutdown
redis-cli -p 7003
info repcation
2.10 Redis 集群宕机条件
特点: 集群中如果主机宕机,那么从机可以继续提供服务, 当主机中没有从机时,则向其它主机借用多余的从机.继续提供服务.如果主机宕机时,没有从机可用,则集群崩溃.
例如:
6个redis节点(1主1从分3组),节点宕机2个或以上时集群崩溃。
9个redis节点(1主2从分3组),节点宕机5-7次时集群才崩溃.
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
当向 redis 集群中插入数据时,首先将 key 进行计算.之后将计算结果匹配到具体的某一个槽的区间内,之后再将数据 set 到管理该槽的节点中.
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 缓存穿透
说明:
用户高并发环境下,频繁访问数据库中不存在的数据.导致用户请求直接访问数据库.严重时导致数据库服务器宕机.
解决方案:
- IP限流操作:
API网关中设置,设定用户访问的上限,规定每个IP单位时间内只能发送N次请求. 设置3-5次
治标不治本 (IP代理服务器: 1分钟变化一个IP)
- 布隆过滤器
5.2 缓存击穿
说明:
在高并发环境下 某个热点数据由于删除/超时导致该数据在缓存中失效. 这时有大量的请求直接访问数据库.导致数据库宕机.
如何优化:
1.定期更新热点数据的超时时间.
2.增加多级缓存机制.
5.3 缓存雪崩
概念:
由于Redis中大量的内存数据失效.导致用户访问缓存的命中率太低.大量的请求直接访问数据库.导致数据库宕机.
命令: flushDB/flushAll这样的命令慎用…
解决方案:
1.设定不同的超时时间/动态更新超时时间
2.设定多级缓存.
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 布隆过滤器应用场景
说明:当用户查询服务器时,首先查询布隆过滤器,如果查询存在该数据,则执行后续的流程,
如果查询没有该数据,则直接返回.无需执行后续流程.
6.4 布隆过滤器算法介绍
6.5 关于布隆过滤器优化说明
1.根据hash原则 数据存在hash碰撞的概率. 则使用布隆过滤器容器造成误判. 如何解决?
6.6 优化hash碰撞概率-增加二进制向量
6.7 优化hash碰撞概率-增加hash函数个数
6.8 关于布隆在项目中使用过程
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;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。