1. Redis哨兵机制
1.1 分片机制存在的问题
如果redis分片机制其中有一台redis宕机,则整个Redis分片将不能正常的使用. 分片机制没有高可用的效果.
1.2 哨兵机制说明
1.2.1 配置哨兵的前提
说明:实现redis数据同步,是实现哨兵配置的前提条件.
1.2.2 复制文件
1.2.3 删除多余的持久化文件
删除完成之后,依次启动redis.如图
1.2.4 实现主从挂载
主机: 6379
从机: 6380/6381
命令:
1. 检查节点的状态 info replication
2.主从挂载命令
3. 主从检查
1.3 哨兵工作原理
步骤:
1).当哨兵启动时,会动态的监控主机,之后利用PING-PONG 心跳检测机制,检查主机是否正常.
2).哨兵链接主机之后,获取相关的主从的服务信息. 方便以后选举.
3).当哨兵发现主机宕机,之后采用随机算法 选择新的主机. 之后其他节点当新主机的从.
1.4 哨兵配置
1.4.1 复制文件
1.4.2 修改哨兵配置文件
1).关闭保护模式
2).开启后端运行
3).监控主机
4. 设定哨兵选举的时间
1.4.3 哨兵高可用测试
1.启动哨兵
2.哨兵高可用测试
第一步: 检查redis主机的状态.
第二步: 关闭redis主机.
第三步: 等待10秒 检查从机是否切换为主机.
第四步: 重启主机,检查是否为新主机的从.
1.4.4 哨兵入门案例
/**
* 实现redis哨兵测试
*/
@Test
public void testSentinel(){
//1.链接哨兵的集合
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.126.129:26379");
JedisSentinelPool pool
= new JedisSentinelPool("mymaster",sentinels);
Jedis jedis = pool.getResource();
jedis.set("aaa", "aaaaaaa");
System.out.println(jedis.get("aaa"));
}
1.5 SpringBoot整合哨兵
1.5.1 编辑pro配置文件
1.5.2 编辑配置类
package com.jt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Configuration //标识我是一个配置类 一般和@Bean注解联用
@PropertySource("classpath:/redis.properties")
public class RedisConfig {
//配置哨兵机制
@Value("${redis.sentinel}")
public String sentinel;
@Bean
public JedisSentinelPool jedisSentinelPool(){
//1.设定连接池大小
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMinIdle(5); //设定最小的空闲数量
poolConfig.setMaxIdle(10); //设定最大空闲数量
poolConfig.setMaxTotal(100); //最大链接数
//2.链接哨兵的集合
Set<String> sentinels = new HashSet<>();
sentinels.add(sentinel);
return new JedisSentinelPool("mymaster",sentinels,poolConfig);
}
/* @Value("${redis.nodes}")
private String nodes; //指定分片节点 node,node,node.....
@Bean
public ShardedJedis shardedJedis(){
List<JedisShardInfo> shards = new ArrayList<>();
String[] nodeArray = nodes.split(",");
for (String node : nodeArray){ //node=host:port
String host = node.split(":")[0]; //获取节点IP地址
int port = Integer.parseInt(node.split(":")[1]); //获取节点端口号
JedisShardInfo jedisShardInfo = new JedisShardInfo(host,port);
shards.add(jedisShardInfo);
}
return new ShardedJedis(shards);
}*/
/* @Value("${redis.host}")
private String host;
@Value("${redis.port}")
private Integer port;
@Bean
public Jedis jedis(){
return new Jedis(host,port);
}*/
}
1.5.3 编辑CacheAOP
package com.jt.aop;
import com.jt.annotation.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.ShardedJedis;
import java.util.Arrays;
@Component //交给Spring容器管理
@Aspect //标识AOP切面类
public class RedisAOP {
@Autowired
private JedisSentinelPool sentinelPool;
//private ShardedJedis jedis;
//private Jedis jedis;
//1. 定义切入点表达式 2.定义通知方法
/**
* 实现AOP缓存:
* 1.准备key = 获取key的前缀 + "动态拼接参数"
* 2.从redis中获取数据
* 结果1: 没有数据,查询数据库,之后将数据保存到缓存中
* 结果2: 有数据, 直接将缓存数据返回
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind) throws Throwable {
//从池中, 动态获取数据
Jedis jedis = sentinelPool.getResource();
String perkey = cacheFind.key();
String args = Arrays.toString(joinPoint.getArgs());
String key = perkey + "::" + args;
Object result = null;
//2.判断redis中是否有数据
if(jedis.exists(key)){
String json = jedis.get(key);
//利用工具API动态获取返回值类型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Class returnType = methodSignature.getReturnType();
//如果采用object.class形式 则只能转化简单对象,不能转化嵌套对象
result = ObjectMapperUtil.toObj(json,returnType);
System.out.println("AOP查询Redis缓存!!!");
}else{
//表示缓存中没有数据,应该查询数据库动态获取
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查询数据库!!!");
}
jedis.close(); //关闭链接
return result;
}
//1.定义切入点表达式
//@Pointcut("bean(itemCatServiceImpl)")
//@Pointcut("within(com.jt.service.*)") //按照某个类匹配
/* @Pointcut("execution(* com.jt.service.*.*(..))")
public void pointCut(){
}
//joinPoint连接点???????
@Before("pointCut()")
public void before(JoinPoint joinPoint){
Class targetClass = joinPoint.getTarget().getClass();
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
String ClassName = joinPoint.getSignature().getDeclaringTypeName();
System.out.println("获取目标对象的类型:"+targetClass);
System.out.println("获取目标参数:"+ Arrays.toString(args));
System.out.println("获取目标方法名称:"+ methodName);
System.out.println("获取目标类的路径:"+ ClassName);
System.out.println("我是前置通知!!!");
}*/
}
1.5.4 分片哨兵总结
1.分片主要实现了内存扩容, 没有高可用的效果.
2.哨兵主要实现了高可用效果, 没有实现内存数据的扩容. 哨兵本身没有高可用的效果.
如何优化: 内存扩容,节点实现高可用 redis集群实现.
2.Redis集群
2.1 关于Redis集群失败说明
如果redis集群搭建失败,则按照如下的步骤完成配置, 前提条件: 搭建集群节点必须为null
1).关闭所有的redis节点
2).删除多余的配置文件 dump.rdb/nodes.conf
3).检查redis.conf配置文件 参考文档…
4).重启redis节点
5).执行搭建命令
2.2 Redis分区算法
具体详情参见文档.
2.3 面试题
1).Redis集群中由于有16384个槽位,所有redis集群中只能存储16384个key? B错误
结论: 分区只是分片了数据归谁管理 到底能存储多少由内存大小决定.
hash(key1)%16384 = 2000
hash(key2)%16384 = 2000
2).通过redis-cli -p 7000-7005 链接任意的节点都可以执行set操作 B 错误 从库不能写
3).通过redis-cli -p 7000-7005 链接任意的主机都可以执行set操作 B 错误 数据存储严格按照分区算法完成.
4).redis集群中最多能够支持多少台主机? 16384台
5).redis集群一旦搭建,如果redis节点全部关机再次重启时,需要重新搭建集群? B 错误 集群的状态都已经写入nodes.conf文件中.所以重启之后集群恢复.
知识点: Redis集群崩溃的条件是主机缺失集群崩溃
6).如果有1主1从共3组组成了redis集群. 问题: redis节点至少宕机几台.集群崩溃?? 2台…
7).如果有1主2从共3组组成了redis集群. 问题: redis节点至少宕机几台.集群崩溃?? 5台
2.4 SpringBoot整合Redis集群
2.4.1 编辑pro配置文件
#设定redis数据
#redis.host=192.168.126.129
#redis.port=6379
# 配置redis分片机制
#redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
#配置redis哨兵机制
#redis.sentinel=192.168.126.129:26379
#配置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
2.4.2 编辑配置类
package com.jt.config;
import org.apache.catalina.Host;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Configuration //标识我是一个配置类 一般和@Bean注解联用
@PropertySource("classpath:/redis.properties")
public class RedisConfig {
@Value("${redis.nodes}")
private String nodes; //node,node,node.....
@Bean
public JedisCluster jedisCluster(){
Set<HostAndPort> nodeSet = new HashSet<>();
String[] nodeArray = nodes.split(",");
for (String node : nodeArray){
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
HostAndPort hostAndPort = new HostAndPort(host, port);
nodeSet.add(hostAndPort);
}
return new JedisCluster(nodeSet);
}
/* //配置哨兵机制
@Value("${redis.sentinel}")
public String sentinel;
@Bean
public JedisSentinelPool jedisSentinelPool(){
//1.设定连接池大小
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMinIdle(5); //设定最小的空闲数量
poolConfig.setMaxIdle(10); //设定最大空闲数量
poolConfig.setMaxTotal(100); //最大链接数
//2.链接哨兵的集合
Set<String> sentinels = new HashSet<>();
sentinels.add(sentinel);
return new JedisSentinelPool("mymaster",sentinels,poolConfig);
}*/
/* @Value("${redis.nodes}")
private String nodes; //指定分片节点 node,node,node.....
@Bean
public ShardedJedis shardedJedis(){
List<JedisShardInfo> shards = new ArrayList<>();
String[] nodeArray = nodes.split(",");
for (String node : nodeArray){ //node=host:port
String host = node.split(":")[0]; //获取节点IP地址
int port = Integer.parseInt(node.split(":")[1]); //获取节点端口号
JedisShardInfo jedisShardInfo = new JedisShardInfo(host,port);
shards.add(jedisShardInfo);
}
return new ShardedJedis(shards);
}*/
/* @Value("${redis.host}")
private String host;
@Value("${redis.port}")
private Integer port;
@Bean
public Jedis jedis(){
return new Jedis(host,port);
}*/
}
2.4.3 编辑CacheAOP
2.5 关于Redis面试问题(二)
2.5.1 什么是缓存穿透
概念: 在高并发环境下,用户长时间访问数据库中不存在的数据,称之为缓存穿透.
原理:
解决方案:
1. IP限流 单位时间内设定IP的请求的次数.
2. 布隆过滤器
2.5.1.0 关于计算机进制换算
1 B/byte(字节) = 8 bit(比特)(二进制位) 。
1 KB(千字节) = 1024 B/byte(字节) 。
1 MB = 1024 KB 。
1 GB = 1024 MB 。
1TB =1024 GB 。
1 PB = 1024 TB 。
1 EB = 1024 PB。
2.5.1.1 布隆过滤器介绍
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
2.5.1.2 布隆过滤器算法
2.5.1.3 算法优化
说明:由于hahs碰撞带来的问题.所以需要对算法进行优化 降低hash碰撞的概率.
解决方案:
1. 增加二进制向量的位数. 8/16/32/1024…
2. 增加hash函数的个数.
总结: 适当的增加二进制向量的位数和hash函数个数,可以有效的降低hash碰撞的概率
guva 包 谷歌大神手写布隆过滤器算法…