1 Redis分片机制说明
1.1一致性hash算法
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 。
1)对相同数据hash得到的结果相同
2)常见hash值 8位16进制 2的32次方
常识:
- 一般的hash由8位16进制数组成的. 共有2^32种可能性!!!
- hash算法对相同的数据进行hash运算时 结果必然相同.
00000000-FFFFFFFF 0-F=(2^4)8 = 2^32
进制:
1).二进制 取值 : 0-1 规则满2进1
2).八进制 取值 : 0-7 规则满8进1 2^3=8 每三位二进制数就是一个8进制数
3).十六进制 取值 : 0-9 A-F 规则满16进一 2^4=16 每四位二进制数就是一个16进制数
1.2iphas特性说明
1.2.1平衡性
说明:平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题
如果发现数据分布不均匀,则采用虚拟节点的方式,实现数据的平衡. 如果一次平衡不能达到目标则多次生成虚拟节点.但是数据平衡没有办法做到绝对的平均.
虚拟节点(代表主节点,主节点位置不变)
1.2.2单调性
单调性是指在新增或者删除节点时,不影响系统正常运行
如果新增或者删除节点,尽可能不改变原有数据的
1.2.3分散性
分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据
2 哨兵机制
2.1redis分片机制存在的问题
说明:Redis分片机制可以实现内存数据的扩容.但是如果Redis服务器发生了宕机的现象,则会影响整个分片使用.
问题:Redis分片机制没有实现高可用. 当主机宕机之后.由从机自动的实现故障迁移.用户访问不受任何影响.
2.2实现高可用
2.2.1启动三台服务
此时数据没有扩容但是实现了备份
1 关闭所有redis服务
2 创建sentinel文件
3 删除持久化文件
4 启动三台redis
2.2.2实现Redis主从
命令1 : info replication 检查redis的主从状态
检查Redis默认条件下的主从关系 默认都是主
命令2:slave of 主机IP 主机端口 实现主从挂载
检查状态6380状态:
实现6381的挂载
此时从库不允许写数据
检查主机状态:
关于主从结构说明:
主和从都知道,当前的主从状态,只有主机可以实现写操作,从机只能读操作
2.3Redis哨兵工作原理
1)当哨兵启动时,首先会监控主机,从主机中获取当前所有节点状态,同时哨兵开启心跳检测机制;
2)当主机发生宕机的时候,由于哨兵有PING-PONG机制,如果连续3次主机没有响应.则哨兵判断主机宕机,则哨兵开始进行选举;
3)根据从主机中获取的从机信息.之后利用 选举机制算法.挑选新的主机.
4)之后将剩余的redis修改为当前主机的的从.并且修改配置文件
2.4编辑哨兵的配置文件
1)复制sentinel.conf文件到sentinel
2)检查是否复制成功:
修改文件
1.关闭保护模式
2.哨兵连接的端口
3 开启后台运行
4 修改监控的主机 改为192.168.126.129 6379端口,2 表示投票的次数
将投票数改为1
5 修改哨兵选举的时间
当redis主机宕机之后,哨兵多久开始选举. 10S
6 选举超时时间的设置,默认三分钟
说明:如果当选主机的服务器在规定的时间内宕机,需要重新选举
启动哨兵服务
redis-sentinel sentinel.conf
关闭主机,等待10S,查看6380成为主机
重新开启6379,是否当选位新主机的从机
哨兵的配置成功后会对配置文件产生污染,需要删除后重新复制再进行配置
2.5测试哨兵的API
用户连接哨兵,由哨兵负责连接具体redis,哨兵端口默认26379
编写配置类
/**
* 哨兵机制
* */
@Value("${redis.node}")
private String node;
@Bean
public JedisSentinelPool jedisSentinalPool(){
Set<String> sentinels=new HashSet<>();
sentinels.add(node);
JedisPoolConfig poolConfig=new JedisPoolConfig();
poolConfig.setMaxTotal(1000);
poolConfig.setMinIdle(60);
poolConfig.setMinIdle(40);
return new JedisSentinelPool("mymaster", sentinels,poolConfig);
}
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;
@Aspect //标识是一个切面
@Component //交给容器管理
public class CacheAOP {
// private Jedis jedis;
// private ShardedJedis jedis;
@Autowired
private JedisSentinelPool sentinelPool;
// @Around("@annotation(com.jt.annotation.CacheFind)")
@Around("@annotation(cacheFind)")
public Object doAround(ProceedingJoinPoint joinPoint, CacheFind cacheFind){
Jedis jedis=sentinelPool.getResource();
Object result=null;
//1.获取key前缀
String key=cacheFind.key();
//2.获取方法参数
String argsString=Arrays.toString(joinPoint.getArgs());
key=key+"::"+argsString;
System.out.println(key);
System.out.println("环绕通知开始");
try {
//3.判断缓存中是否有数据
if(jedis.exists(key)){
String json=jedis.get(key);
//5 获取返回值类型
MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
result=ObjectMapperUtil.toObject(json,methodSignature.getReturnType());
System.out.println("redis缓存查询");
}else{
//表示缓存中没有数据
result=joinPoint.proceed();
String json= ObjectMapperUtil.toJSON(result);
//4.判断数据是否超时
if(cacheFind.seconds()>0){
jedis.setex(key, cacheFind.seconds(), json);
}else{
jedis.set(key, json);
}
System.out.println("执行数据库调用");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
System.out.println("环绕通知结束");
jedis.close();
return result;
}
/* //切面=切入点表达式+通知方法
//表达式1: @Pointcut("bean(itemCatServiceImpl)") ItemCatServiceImpl类
//表达式2: @Pointcut("within(com.jt.service.*)")
//表达式3: @Pointcut("execution(* com.jt.service.*add(..))") .*一级包下的类 ..*所有子孙后代的包和类
//返回值类型任意 com.jt.service.下所有add方法
//execution(* com.jt.service..*.*(long)) 参数类型严格区分大小写
@Pointcut("bean(itemCatServiceImpl)")
public void pointCut(){};
*//**
*joinPoint 代表连接点对象,一般使用于除around之外的通知
*ProceedingJoinPoint 只用于around通知
* *//*
@Before("pointCut()")
public void before(JoinPoint joinPoint){
//1.获取目标对象
Object target=joinPoint.getTarget();
System.out.println(target);
//2.获取目标对象的路径 包名.类名
String className=joinPoint.getSignature().getDeclaringTypeName();
String method=joinPoint.getSignature().getName();
System.out.println("目标方法的路径:"+(className+"."+method));
//获取带参方法的参数
System.out.println(Arrays.toString(joinPoint.getArgs()));
}
@Pointcut("bean(itemCatServiceImpl)")
public void testAop(){}
@Around("testAop()")
public Object testAround(ProceedingJoinPoint joinpoint){
System.out.println("环绕通知执行");
Object data=null;
try {
data=joinpoint.proceed();//执行目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return data;
}
*/
}
2.6关于分片/哨兵总结
1.Redis分片主要的作用实现内存的扩容,缺点:没有实现高可用的效果.
2.Redis哨兵主要的作用实现了redis节点高可用. 缺点:没有实现内存扩容
Redis哨兵机制实质就是引入第三方的监控,但是需要保证第三方的高可用.就必须引入更多的资源.
3 Redis集群实现
关掉哨兵服务
文件修改以及集群搭建过程见京淘项目day14
集群搭建错误的解决方案:
1)关闭所有的redis服务
sh shutdown.sh
2)删除多余文件
3)重启redis
sh start.sh
4)复制代码
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
3.2 SpringgBoot 整合集群
测试类
@Test
public void testCluster(){
Set<HostAndPort> nodes=new HashSet<>();
nodes.add(new HostAndPort("192.168.126.129", 7000));
nodes.add(new HostAndPort("192.168.126.129", 7001));
nodes.add(new HostAndPort("192.168.126.129", 7002));
nodes.add(new HostAndPort("192.168.126.129", 7003));
nodes.add(new HostAndPort("192.168.126.129", 7004));
nodes.add(new HostAndPort("192.168.126.129", 7005));
JedisPoolConfig poolConfig=new JedisPoolConfig();
poolConfig.setMinIdle(20);
poolConfig.setMaxTotal(1000);
poolConfig.setMaxIdle(40);
JedisCluster jedisCluster=new JedisCluster(nodes,poolConfig);
jedisCluster.set("jedisCluster","redis集群的搭建");
System.out.println(jedisCluster.get("jedisCluster"));
}
3.3集群宕机的条件
集群宕机的条件:当主机的数量不能保证时集群崩溃
1).redis集群中一共可以存储16384个数据?
不对: redis中存储多少数据完全由内存决定
hash(key1)=3000
hash(key2)=3000
2).redis集群中最多可以有多少个redis主机? 16384台主机 一台主机管理一个槽位.
3.4SpringBoot整合Redis集群
#配置单台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.node=192.168.126.129:26379
#配置集群的配置文件
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
RedisConfig
@Value("${redis.nodes}")
private String nodes;
@Bean
public JedisCluster jedisCluster(){
Set<HostAndPort> nodesSet=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);
nodesSet.add(hostAndPort);
}
return new JedisCluster(nodesSet);
}
@Aspect //标识是一个切面
@Component //交给容器管理
public class CacheAOP {
// private Jedis jedis;
// private ShardedJedis jedis;
@Autowired
//private JedisSentinelPool sentinelPool;
private JedisCluster jedis;
// @Around("@annotation(com.jt.annotation.CacheFind)")
@Around("@annotation(cacheFind)")
public Object doAround(ProceedingJoinPoint joinPoint, CacheFind cacheFind){
//Jedis jedis=sentinelPool.getResource();
Object result=null;
//1.获取key前缀
String key=cacheFind.key();
//2.获取方法参数
String argsString=Arrays.toString(joinPoint.getArgs());
key=key+"::"+argsString;
System.out.println(key);
System.out.println("环绕通知开始");
try {
//3.判断缓存中是否有数据
if(jedis.exists(key)){
String json=jedis.get(key);
//5 获取返回值类型
MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
result=ObjectMapperUtil.toObject(json,methodSignature.getReturnType());
System.out.println("redis缓存查询");
}else{
//表示缓存中没有数据
result=joinPoint.proceed();
String json= ObjectMapperUtil.toJSON(result);
//4.判断数据是否超时
if(cacheFind.seconds()>0){
jedis.setex(key, cacheFind.seconds(), json);
}else{
jedis.set(key, json);
}
System.out.println("执行数据库调用");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
System.out.println("环绕通知结束");
// jedis.close();
return result;
}