一、Redis分片机制说明
1.一致性hash算法
1.1 一致性Hash算法介绍
一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。
1.2 一致性Hash原理说明
常识:
1).对相同的数据hash得到的结果相同
2).常见hash值 8位16进制数 2^32种可能性
1.3 平衡性说明
说明: 平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 .
如果发现数据分布不均匀,则采用虚拟节点的方式,实现数据的平衡. 如果一次平衡不能达到目标则多次生成虚拟节点.但是数据平衡没有办法做到绝对的平均.
1.4 单调性说明
单调性是指在新增或者删减节点时,不影响系统正常运行
如果新增或者删除节点,则尽可能不要改变原有的数据结构.
1.5 分散性
分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据
鸡蛋不要放到一个篮子里.
二、redis分片机制配置
1. redis性能优化
说明: 单台redis内存容量是有限的.但是如果有海量的数据要求实现缓存存储,则应该使用多个Redis节点.
2. Redis分片机制定义
3. 配置规划
说明: 准备3台redis服务器 分别为 6379/6380/6381
3.1 准备3个配置文件
3.2 启动redis服务器
3.3 检查redis启动状态
3.4 redis分片入门案例
4. Redis分片机制配置
说明:根据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`
4.1 编辑RedisConfig配置类
@Configuration //标识我是一个配置类 一般与@Bean注解联用
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.nodes}")
private String nodes; //node,node,node
@Bean
public ShardedJedis shardedJedis(){
List shards = new ArrayList<>();
String[] nodeArray = nodes.split(",");
for( String node :nodeArray){ //node=host:port
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
JedisShardInfo info = new JedisShardInfo(host,port);
shards.add(info);
}
return new ShardedJedis(shards);
}
}
4.2 修改CacheAOP中的注入
二、 Redis哨兵机制
1. Redis分片机制问题
说明: 如果redis分片中有一个节点宕机,则可能会影响整个服务的运行. redis分片没有实现高可用.
2. Redis主从结构搭建
2.1 复制配置文件
2.2 准备3台redis
2.3 主从搭建
命令1: info replication
关于主从结构说明:
主和从都知道 当前的主从的状态, 并且只有主机可以写操作.从机只能读操作.
3. Redis哨兵机制工作原理
1).当哨兵启动时,首先会监控主机,从主机中获取当前所有的节点的状态,同时哨兵开启心跳检测机制.
2).当主机发生宕机的现象时,由于哨兵有PING-PONG机制 发现主机宕机,则哨兵开始进行选举.
3).当选举成功之后,新的主机当选之后,其他的节点当新主机的从.
3.1 编辑哨兵配置文件
3.2 启动哨兵服务
命令: redis-sentinel sentinel.conf
3.3 哨兵测试API
/**
* 测试哨兵
*/
@Test
public void testSentinel(){
Set set = new HashSet<>();
set.add("192.168.126.129:26379");
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(1000);
poolConfig.setMaxIdle(40);
poolConfig.setMinIdle(10);
JedisSentinelPool sentinelPool =
new JedisSentinelPool("mymaster",set,poolConfig);
Jedis jedis = sentinelPool.getResource();
jedis.set("sentinel", "哨兵机制测试");
System.out.println(jedis.get("sentinel"));
jedis.close();
}
4.springBoot整合哨兵机制
4.1 编辑pro配置文件
4.2 编辑配置类
4.3 编辑CacheAOP
4.4 关于分片/哨兵总结
1.Redis分片主要的作用实现内存的扩容,缺点:没有实现高可用的效果.
2.Redis哨兵主要的作用实现了redis节点高可用. 缺点:没有实现内存扩容
Redis哨兵机制实质就是引入第三方的监控,但是需要保证第三方的高可用.就必须引入更多的资源.
三、Redis集群
参见搭建文档.
1. 集群搭建错误解决方案
注意事项: redis集群搭建要求节点的数据必须为null.
1).将所有的redis节点全部关闭 sh stop.sh
2).删除多余的文件
3).重启redis集群
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
2 Redis集群入门案例
@Test
public void testCluster(){
Set 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.setMaxTotal(1000);
poolConfig.setMaxIdle(40);
poolConfig.setMinIdle(20);
JedisCluster jedisCluster = new JedisCluster(nodes,poolConfig);
jedisCluster.set("jedisCluster", "redis集群搭建");
System.out.println(jedisCluster.get("jedisCluster"));
}
3. 面试问题
1).redis集群中一共可以存储16384个数据?
不对: redis中存储多少数据完全由内存决定
hash(key1)=3000
hash(key2)=3000
2).redis集群中最多可以有多少个redis主机? 16384台主机 一台主机管理一个槽位.
4. SpringBoot整合Redis集群
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.node=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`
4.2 编辑配置类
@Configuration //标识我是一个配置类 一般与@Bean注解联用
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.nodes}")
private String nodes; //node,node,node
@Bean
public JedisCluster jedisCluster(){
Set nodesSet = new HashSet<>();
String[] nodeArray = nodes.split(",");
for (String node : nodeArray){ //host:port
String host = node.split(":")[0];
int port = Integer.parseInt(node.split(":")[1]);
HostAndPort hostAndPort = new HostAndPort(host, port);
nodesSet.add(hostAndPort);
}
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1000);
config.setMaxIdle(60);
config.setMinIdle(20);
return new JedisCluster(nodesSet,config);
}
}
4.3 编辑CacheAOP
@Aspect //标识是个切面
@Component //将对象交给spring容器管理
public class CacheAop {
@Autowired
//private Jedis jedis;//单台测试
//private ShardedJedis jedis;//实现分片操作
//private JedisSentinelPool sentinelPool; //注入池对象 哨兵机制
private JedisCluster jedis;//注入集群对象
/**
* 实现思路:
* 1.动态获取key 用户自定义的前缀+用户的参数[0,xx]
* 2.判断key是否存在
* 存在: 直接从redis中获取数据 JSON, 需要将json转化为具体对象 按照返回值类型进行转化
* 不存在: 执行目标方法.获得返回值数据. 将返回值结果转化为JSON格式. 之后保存到缓存中.
* @param joinPoint
* @return
*/ @Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
//Jedis jedis=sentinelPool.getResource();
Object result=null;
//1.获取key的前缀
String key = cacheFind.key();
//2.获取方法参数
String argString = Arrays.toString(joinPoint.getArgs());
key = key + "::" + argString;
try {
//3.判断缓存在是否有key
if (jedis.exists(key)){
String json=jedis.get(key);
//5.获取返回值类型
MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
result=ObjectMapperUtil.toObject(json,methodSignature.getReturnType());
System.out.println("AOP查询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("AOP执行数据库调用!!!!");
}
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
//jedis.close(); //还池操作
return result;
}