知乎面试题
redis
nosql,key-value,基于内存,实现缓存,可持久化,非 关系型,数据库(数据读写)
五中数据结构
- string:字符串
- Hash:面向对象的结构
- List:双向链表
- Set:集合
- Zset:有序集合
hash取余算法
准备三个jedis连接对象,通过对key的hash取余算法,来决定把key-value存储在哪个jedis连接对象中
key.hashCode()&Integer.MAX_VALUE 32位的hashcode和31个1进程位与运算(保真31位运算),符号位变0,其他位不变,相当于取绝对值,但这种方式效率更高
%3对三取余结果只有0,1,2三种,对应不同的结果存储进不同的连接对象中
public void hashN(){
//模拟生成大量的数据,存储6379 6380 6381
//准备三个连接对象
Jedis jedis1=new Jedis("10.42.141.101",6379);
Jedis jedis2=new Jedis("10.42.141.101",6380);
Jedis jedis3=new Jedis("10.42.141.101",6381);
for(int i=0;i<100;i++){
String key=UUID.randomUUID().toString();
String value="value_"+i;
int result=(key.hashCode()&Integer.MAX_VALUE)%3;
if(result==0){
//存进6379
jedis1.set(key, value);
}else if(result==1){
//存进6380
jedis2.set(key, value);
}else{
//存进6381
jedis3.set(key, value);
}
}
缺陷
在进行集群的扩容缩容时,数据的迁移量会很大,不迁移就会造成数据未命中大 比如增加一个,对四取余,得到0的概率是25%,未命中概率是75%
hash一致性算法
将内存数据映射到一个整数区间 0-(2^32-1) 43亿
//收集节点信息
public void pool(){
List<JedisShardInfo> list=new ArrayList<JedisShardInfo>();
list.add(new JedisShardInfo("10.42.141.101",6379));
list.add(new JedisShardInfo("10.42.141.101",6380));
list.add(new JedisShardInfo("10.42.141.101",6381));
//使用连接池的配置对象,配置连接池的各种属性
GenericObjectPoolConfig config=new GenericObjectPoolConfig();
config.setMaxIdle(8);//最大空闲
config.setMinIdle(3);,//最小空闲
config.setMaxTotal(200);//最大连接数量
//list config 构造一个包装了多个 分片连接对象的连接池
ShardedJedisPool pool=new ShardedJedisPool(config,list);
//从池中获取连接资源
ShardedJedis sJedis=pool.getResource();
sJedis.set("location", "北京");
}
先创建了一个list,把所有的节点放进去,然后配置连接池的相关属性
原理
基于hash散列算法得到整数区间–hash环
利用提供的节点信息做hash环的对应整数映射,比如10.9.39.13:6379映射到环上的一个点
对key值做hash散列计算得到映射结果,key值得整数顺时针寻找最近的节点整数存入
节点越多迁移量越小
数据平衡权重
jedis通过引入虚拟节点概念,模拟生成大量虚拟节点,虚拟节点也在hash环上找到自己的映射位置,key值依然通过hash散列算法找到自己的映射位置,顺时针找到最近的节点进行存储
jedis在创建分片对象封装hash一致性时,将虚拟节点的个数设置为160*weight,weight的默认是1
springboot整合分片连接池
application.properties
redis.1906.nodes=10.42.141.101:6379,10.42.141.101:6380,10.42.141.101:6381
redis.1906.maxTotal=200
redis.1906.maxIdle=8
redis.1906.minIdle=3
@Configuration
@ConfigurationProperties(prefix="redis.1906")
public class PoolConfigRedis {
private List<String> nodes;
//10.42.141.101:6379,10.42.141.101:6380,10.42.141.101:6381
private Integer maxTotal;
private Integer maxIdle;
private Integer minIdle;
//构造一个连接池初始化方法
@Bean
public ShardedJedisPool initShardPool(){
//收集节点信息
List<JedisShardInfo> list=new ArrayList<JedisShardInfo>();
for(String node:nodes){
//node="10.42.141.101:6379"
String host=node.split(":")[0];
int port=Integer.parseInt(node.split(":")[1]);
list.add(new JedisShardInfo(host,port));
}
//配置对象
GenericObjectPoolConfig config=new GenericObjectPoolConfig();
config.setMaxIdle(maxIdle);
config.setMaxTotal(maxTotal);
config.setMinIdle(minIdle);
return new ShardedJedisPool(config,list);
}
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
public Integer getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(Integer maxTotal) {
this.maxTotal = maxTotal;
}
public Integer getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(Integer maxIdle) {
this.maxIdle = maxIdle;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
}
高可用集群
单节点故障时,会导致该节点数据不可用,需要哨兵集群
哨兵集群
主从结构的故障转移:主节点故障宕机,从节点顶替 主从的数据备份:主从关系一旦搭建,从节点时刻备份主节点的数据,高可用的基础
在客户端中写>slaveof 10.9.39.13 6382,表示把该节点作为6382节点的子节点,主节点的所有信息会同步到子节点
从节点只关心主从的任务,没有故障转移替换的机制,高可用个结构单独使用主从的复制无法完成.
哨兵进程
哨兵进程是一个单独的,特殊的redis进程,和redisserver相互独立运行,可以实现对主从结构的监听和管理,实现故障转移的控制
当主节点宕机时,会投票在从节点中选出一个节点作为主节点
原理:
- 监听原理 初始化时访问主节点,调用info命令从主节点获取所有从节点的信息,维护在内存中进行监控管理,所有节点的状态都会通过哨兵进行维护
- 心跳机制 每秒钟通过rpc(远程通信协议)心跳检测 访问集群的所有节点,一旦发现心跳响应是空的,达到一定时间判断节点故障宕机
- 投票机制 主节点宕机,选举一个从节点为新的主节点,通过管理的权限,将这个节点的角色转化为master,将其他集群节点挂接到这个心的主节点,必须通过投票完成(哨兵也是集群),必须过半投票的结果才能执行,否则将会进入到重新判断循环
缺点
每个哨兵集群管理一个主从架构,如果有多个主从-哨兵集群架构,这些集群就无法保证数据的高可靠,高可用,因为他们是不互通的
redis-cluster
特性:
-
实现了主节点,从节点之间的两两互联
- 哨兵逻辑整合到master,集群中master最小数量是3个,因为是投票过半制,当master宕机,需要投票从节点中顶替master
- 客户端连接一个节点就能获得所有信息
槽道原理
槽道结构
集群的槽道结构由2部分组成
- 16384位的二进制(byte[2048]形式驻留在内存)
- 一个16384个元素的数组(数组中保存的是所有节点对象的引用变量)
如何在节点计算完key值的取模结果后,使用槽道号判断所属权.(16384位的二进制的作用之一)?
- 二进制: 每个节点中,都会管理一个2048个元素的byte数组 (16384位的二进制);从头到尾赋予下标概念(位移计算) 0-16383
- 数组: 每个下标对应一个槽道号,可以利用槽道号从二进制获取下标对应的二进制的值,1/0,如果是 1,表示当前槽道归属true,0表示不归属false
16384个元素的数据可以记录0-16383下标的元素值每个下标对应槽道号,每个节点中通过通信获取二进制,整理使用这个数组,每个元素内容,记录了引用当前槽道号管理者的对象数据.
过程
- 客户端连接节点发送命令set name haha
- 8000接收到
- 计算槽道 – 5798
- 二进制判断对应下标值是1/0 0false
- 找到数组拿到5798下标元素 8001节点对象变量引用
- ip:port返回客户端重定向
- 8001接收命令set name haha
- 计算槽道 – 5798
- 二进制判断对应下标值是1/0 1 true
- redis服务端接收set命令处理