前言:本文章为慕课网上Java企业级电商项目架构演进之路Tomcat集群与Redis分布式的学习笔记.
目录
1.1 一致性hash算法(Consistent hashing)
第一章 Redis分布式算法原理
当我们有20个数据,4个redis结点时,我可以按照取模的方法,对这20个数据进行一一分配.即1%4=1,所以1数据分配到1节点上,4%4=0所以4分配到0节点上,分配完后如下图所示.
当我们想按如上规则增加一个redis节点时,我们发现只有四个节点的位置不需要重新分配,这样命中率只有4/20=20%,即百分之20的节点不需要重新分配.这样效率有点低,我们需要一个新的算法来分配数据节点,来保证新增或删除redis节点时,不需要有太多的数据节点被重新分配.
1.1 一致性hash算法(Consistent hashing)
通常hash算法是将value映射到一个32位的key值当中,我们将数轴卷起变为收尾相接的圆环,取值范围位从0-2^32-1.也就是这个环能存储2^32-1个元素.
考虑四个对象object1~object4,通过hash函数计算出hash值key,,比如object1经过hash后的值称为key1,如下图,于是四个对象就都被映射到了哈希空间中.
我们的object需要cache来进行存储,所以我们要将object和cache都映射到同一个hash数值空间中,并且使用相同的hash算法,对于cache的哈希计算,可以使用机器的ip,机器名作为hash的输入,也可以引入更多的因子,如端口号.
假设我们有三个cache,映射后就如下图所示.
如何把对象映射到cache中呢?沿着key所在的位置顺时针出发所碰到的第一个cache节点是CacheA,那么key1(简称1)就要存到CacheA(简称A)中,这样就找到了对象映射到缓存中的方案.都映射完如下图所示.
我们的生产环境可能会调整,传统的取模方式对后台服务器造成了巨大的冲击,我们现在移除Cache节点,移除CacheB,object4就会按照原来的算法找到CacheC,存到Cache C中.
所以移除CacheB后影响的范围就是橙色框中的节点.
增加CacheD后影响的就是橙色框中的节点.
1.2 哈希倾斜
根据上面的论述,Cache节点的位置影响着重新分配数据时的效率,我们理想的Cache节点分布如下图.
现实可能是这样的.如果时这样大量的数据就会落到A上面,导致A很忙,B,C很倾斜.同时去掉A时也会影响大量节点.
为了解决这个问题,引入了虚拟节点.我们先将我们的数据映射到分布均匀的虚拟节点上,再将虚拟节点映射到真正的Cache节点上.
我们增加虚拟节点A,B,C,加完后如下图,这样1,3落在A上,2,6落在C上,5,4落在B上.可是这样虚拟节点也会有哈希倾斜性,我们可以增加大量的虚拟节点,给虚拟节点分配一个良好的比例,随着节点越来越多,数据越来越多,增加和删除节点的影响就会降到最低.
命中率计算公式,服务器台数是n,新增的服务器台数是m
(1-n/(n+m))*100%
之前用取模的方式命中率只有20%,虽然都是百分之20,但是那是因为数据量很小,当我们扩大到百万级,我们变动的服务器台数越大,命中率就会越高,所以这个算法是说随着我们的分布式集群越来越大的时候,这个算法的优点就会体现出来.Consistant已经最大限制的抑制了哈希的重新分布,而且我们还可以运用虚拟节点的思想,让每个实际节点都配置个100-500个虚拟节点,这样就能抑制分布不均匀了.
第二章 Redis分布式环境配置
2.1 配置两个redis
我们将windows中的redis分成两个文件夹
第一个直接启动
第二个修改配置文件端口为6380然后启动
第三章 Springboot2.13操作分布式redis
3.1 配置redis参数
两个redis都是架在本地的所以地址都是127.0.0.1,一个端口是6379,一个端口是6380.
redis2.maxtotal = 20
redis2.maxIdle = 10
redis2.minIdle = 2
redis2.testOnReturn = true
redis2.testOnBorrow = true
redis2.redisPort1 = 6379
redis2.redisIp1 = 127.0.0.1
redis2.redisPort2 = 6380
redis2.redisIp2 = 127.0.0.1
3.2 创建redis的连接池
@Component
@ConfigurationProperties(prefix = "redis2")
@Slf4j
@Getter
@Setter
public class RedisShardedPool {
private ShardedJedisPool pool;//jedis连接池
private Integer maxtotal ; //最大连接数
private Integer maxIdle ; //在jedispool中最大的idle(空闲的)状态的jedis实例的个数
private Integer minIdle ; //在jedispool中最小的idle(空闲的)状态的jedis实例的个数
private Boolean testOnBorrow ;
private Boolean testOnReturn ;
private Integer redisPort1 ;
private String redisIp1;
private Integer redisPort2 ;
private String redisIp2;
public RedisShardedPool(){
log.info("构造方法开始执行");
}
@PostConstruct
public void initPool(){
log.info("属性已经注入");
log.info("开始创建连接池");
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxtotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
config.setBlockWhenExhausted(true);
JedisShardInfo info1 = new JedisShardInfo(redisIp1,redisPort1,1000*2);
JedisShardInfo info2 = new JedisShardInfo(redisIp2,redisPort2,1000*2);
List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>(2);
jedisShardInfoList.add(info1);
jedisShardInfoList.add(info2);
//Hashing.MURMUR_HASH是分篇策略,应用的是一致性分布算法.
pool= new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
}
public ShardedJedis getJedis(){
return pool.getResource();
}
}
3.3 操作redis
我们向redis中插入了10个值
@RequestMapping("/testSharedJedis")
public String testSharedJedis(){
ShardedJedis shardedJedis = redisShardedPool.getJedis();
for(int i=0;i<10;i++){
shardedJedis.set("hey"+i,"value"+i);
}
return "index";
}
分别查看两个redis库中的值
接口为6379的数据库:
接口为6380的数据库:
可以看到已经向两个数据库中插入了值,操作成功!