shardedjedispool配置如果宕机一台怎么办

Nosql学习(21)
转载自:http://warm-/blog/2020413
本文主要介绍一种通过Jedis&Sentinel实现Redis集群高可用方案,该方案需要使用Jedis2.2.2及以上版本(强制),Redis2.8及以上版本(可选,Sentinel最早出现在Redis2.4中,Redis2.8中Sentinel更加稳定),Redis集群是以分片(Sharding)加主从的方式搭建,满足可扩展性的要求;
Redis Sentinel介绍
Redis Sentinel是Redis官方提供的集群管理工具,主要有三大功能:&
监控,能持续监控Redis的主从实例是否正常工作;&
通知,当被监控的Redis实例出问题时,能通过API通知系统管理员或其他程序;&
自动故障恢复,如果主实例无法正常工作,Sentinel将启动故障恢复机制把一个从实例提升为主实例,其他的从实例将会被重新配置到新的主实例,且应用程序会得到一个更换新地址的通知。&
Redis Sentinel是一个分布式系统,可以部署多个Sentinel实例来监控同一组Redis实例,它们通过Gossip协议来确定一个主实例宕机,通过Agreement协议来执行故障恢复和配置变更,一般在生产环境中部署多个实例来提高系统可用性,只要有一个Sentinel实例运行正常,就能保证被监控的Redis实例运行正常(类似Zookeeper,通过多个Zookeeper来提高系统可用性);&
本文不涉及Sentinel的实现细节和工作原理,读者可以阅读其他文章了解;
Redis HA方案
HA的关键在于避免单点故障及故障恢复,在Redis Cluster未发布之前,Redis一般以主/从方式部署(这里讨论的应用从实例主要用于备份,主实例提供读写,有不少应用是读写分离的,读写操作需要取不同的Redis实例,该方案也可用于此种应用,原理都是相通的,区别在于数据操作层如何封装),该方式要实现HA主要有如下几种方案:&
1,keepalived:通过keepalived的虚拟IP,提供主从的统一访问,在主出现问题时,通过keepalived运行脚本将从提升为主,待主恢复后先同步后自动变为主,该方案的好处是主从切换后,应用程序不需要知道(因为访问的虚拟IP不变),坏处是引入keepalived增加部署复杂性;&
2,zookeeper:通过zookeeper来监控主从实例,维护最新有效的IP,应用通过zookeeper取得IP,对Redis进行访问;&
3,sentinel:通过Sentinel监控主从实例,自动进行故障恢复,该方案有个缺陷:因为主从实例地址(IP&PORT)是不同的,当故障发生进行主从切换后,应用程序无法知道新地址,故在Jedis2.2.2中新增了对Sentinel的支持,应用通过redis.clients.jedis.JedisSentinelPool.getResource()取得的Jedis实例会及时更新到新的主实例地址。&
笔者所在的公司先使用了方案1一段时间后,发现keepalived在有些情况下会导致数据丢失,keepalived通过shell脚本进行主从切换,配置复杂,而且keepalived成为新的单点,后来选用了方案3,使用Redis官方解决方案;(方案2需要编写大量的监控代码,没有方案3简便,网上有人使用方案2读者可自行查看)
选用Sentinel出现的问题
Sentinel&Jedis看上去是个完美的解决方案,这句话只说对了一半,在无分片的情况是这样,但我们的应用使用了数据分片-sharing,数据被平均分布到4个不同的实例上,每个实例以主从结构部署,Jedis没有提供基于Sentinel的ShardedJedisPool,也就是说在4个分片中,如果其中一个分片发生主从切换,应用所使用的ShardedJedisPool无法获得通知,所有对那个分片的操作将会失败。&
本文提供一个基于Sentinel的ShardedJedisPool,能及时感知所有分片主从切换行为,进行连接池重建,源码见
ShardedJedisSentinelPool实现分析
&类似之前的Jedis Pool的构造方法,需要参数poolConfig提供诸如maxIdle,maxTotal之类的配置,masters是一个List,用来保存所有分片Master在Sentinel中配置的名字(注意master的顺序不能改变,因为Shard算法是依据分片位置进行计算,如果顺序错误将导致数据存储混乱),sentinels是一个Set,其中存放所有Sentinel的地址(格式:IP:PORT,如127.0.0.1:26379),顺序无关;
初始化连接池
在构造函数中,通过方法
&取得当前所有分片的master地址(IP&PORT),对每个分片,通过顺次连接Sentinel实例,获取该分片的master地址,如果无法获得,即所有Sentinel都无法连接,将休眠1秒后继续重试,直到取得所有分片的master地址,代码块如下:&
&初始化连接池,到此连接池中的所有连接都指向分片的master;
监控每个Sentinel
&最后,会为每个Sentinel启动一个Thread来监控Sentinel做出的更改:&
该线程的run方法通过Jedis Pub/Sub API(实现JedisPubSub接口,并通过jedis.subscribe进行订阅)向Sentinel实例订阅“+switch-master”频道,当Sentinel进行主从切换时,该线程会得到新Master地址的通知,通过master name判断哪个分片进行了切换,将新master地址替换原来位置的地址,并调用initPool(List masters)进行Jedis连接池重建;后续所有通过该连接池取得的连接都指向新Master地址,对应用程序透明;
本文通过现实中遇到的问题,即在Redis数据分片的情况下,在使用Sentinel做HA时,如何做到主从的切换对应用程序透明,通过Jedis的Pub/Sub功能,能同时监控多个分片的主从切换情况,并通过监听到的新地址重新构造连接池,后续从连接池中取得的所有连接都指向新地址。该方案的关键是:使用sentinel做HA,Jedis版本必须2.2.2及以上,所有访问Redis实例的连接都必须从连接池中获取;
该项目的GitHub主页:&
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:6060次
排名:千里之外
原创:22篇
转载:38篇
(12)(9)(39)31183人阅读
Redis(21)
目前没有提供集群的功能,作者在博客中说将在中实现集群机制。目前实现集群的方法主要是采用一致性哈稀分片(),将不同的分配到不同的上,达到横向扩展的目的。下面来介绍一种比较常用的分布式场景:
在读写操作比较均匀且实时性要求较高,可以用下图的分布式模式:
在读操作远远多于写操作时,可以用下图的分布式模式:
对于一致性哈稀分片的算法,已经提供了,下面是使用示例代码(以为例):
package com.jd.redis.
import java.util.ArrayL
import java.util.L
import redis.clients.jedis.JedisPoolC
import redis.clients.jedis.JedisShardI
import redis.clients.jedis.ShardedJ
import redis.clients.jedis.ShardedJedisP
import redis.clients.util.H
import redis.clients.util.S
publicclass RedisShardPoolTest {
static ShardedJedisPoolpool;
JedisPoolConfig config =new JedisPoolConfig();//Jedis池配置
config.setMaxActive(500);//最大活动的对象个数
&&config.setMaxIdle(1000 * 60);//对象最大空闲时间
&&&config.setMaxWait(1000 * 10);//获取对象时最大等待时间
&&&&config.setTestOnBorrow(true);
String hostA = &10.10.224.44&;
&&&int portA = 6379;
&&&String hostB = &10.10.224.48&;
&&&int portB = 6379;
List&JedisShardInfo& jdsInfoList =new ArrayList&JedisShardInfo&(2);
JedisShardInfo infoA = new JedisShardInfo(hostA, portA);
infoA.setPassword(&redis.360buy&);
JedisShardInfo infoB = new JedisShardInfo(hostB, portB);
infoB.setPassword(&redis.360buy&);
jdsInfoList.add(infoA);
jdsInfoList.add(infoB);
pool =new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH,
Sharded.DEFAULT_KEY_TAG_PATTERN);
&* @param args
publicstaticvoid main(String[] args) {
for(int i=0; i&100; i++){
&&&&&&&&&&&
String key = generateKey();
&&&&&&&&&&&
//key += &{aaa}&;
&&&&&&&&&&&
ShardedJedis jds = null;
&&&&&&&&&&&
&&&&&&&&&&&&&&&
jds = pool.getResource();
&&&&&&&&&&&
&&& System.out.println(key+&:&+jds.getShard(key).getClient().getHost());
&&&&&&&&&&&&&&&
System.out.println(jds.set(key,&1111111&));
&&&&&&&&&&&
} catch (Exception e) {
&&&&&&&&&&&&&&&
e.printStackTrace();
&&&&&&&&&&&
&&&&&&&&&&&
&&&&&&&&&&&&&&&
pool.returnResource(jds);
&&&&&&&&&&&
privatestaticintindex = 1;
publicstatic String generateKey(){
return String.valueOf(Thread.currentThread().getId())+&_&+(index++);
从运行结果中可以看到,不同的被分配到不同的上去了。
实际上,上面的集群模式还存在两个问题:
&&&&&& 扩容问题:
因为使用了一致性哈稀进行分片,那么不同的分布到不同的上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况,对于这种情况,的作者提出了一种名为的方式:
方法是将每一个台物理机上,运行多个不同断口的实例,假如有三个物理机,每个物理机运行三个实际,那么我们的分片列表中实际有个实例,当我们需要扩容时,增加一台物理机,步骤如下:
&&&& 在新的物理机上运行;
&&&&& 该从属于分片列表中的某一(假设叫);
&&&&& 等主从复制完成后,将客户端分片列表中的和端口改为新物理机上的和端口;
&&&& 停止。
这样相当于将某一转移到了一台新机器上。实际上是一种在线扩容的办法,但还是很依赖本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。
&&&&&& 单点故障问题:
还是用到主从复制的功能,两台物理主机上分别都运行有,其中一个是另一个的从库,采用双机热备技术,客户端通过虚拟访问主库的物理,当主库宕机时,切换到从库的物理。只是事后修复主库时,应该将之前的从库改为主库(使用命令),主库变为其从库(使命令),这样才能保证修复期间新增数据的一致性。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:345882次
积分:2890
积分:2890
排名:第8436名
原创:26篇
转载:39篇
评论:34条
(5)(1)(1)(1)(12)(4)(3)(10)(13)(15)Redis-2.4.15目前没有提供集群的功能,Redis作者在博客中说将在3.0中实现集群机制。目前Redis实现集群的方法主要是采用一致性哈稀分片(Shard),将不同的key分配到不同的redis server上,达到横向扩展的目的。下面来介绍一种比较常用的分布式场景:
在读写操作比较均匀且实时性要求较高,可以用下图的分布式模式:
在读操作远远多于写操作时,可以用下图的分布式模式:
&&&&&&&对于一致性哈稀分片的算法,Jedis-2.0.0已经提供了,下面是使用示例代码(以ShardedJedisPool为例):
package&com.jd.redis.
import&java.util.ArrayL
import&java.util.L
import&redis.clients.jedis.JedisPoolC
import&redis.clients.jedis.JedisShardI
import&redis.clients.jedis.ShardedJ
import&redis.clients.jedis.ShardedJedisP
import&redis.clients.util.H
import&redis.clients.util.S
publicclass&RedisShardPoolTest {
&&&&static&ShardedJedisPoolpool;
&&&&static{
&&&&&&&&JedisPoolConfig config =new&JedisPoolConfig();//Jedis池配置
&&&&&&&&config.setMaxActive(500);//最大活动的对象个数
&&&&&&&&&&config.setMaxIdle(1000 * 60);//对象最大空闲时间
&&&&&&&&&&config.setMaxWait(1000 * 10);//获取对象时最大等待时间
&&&&&&&&&&config.setTestOnBorrow(true);
&&&&&&&&String hostA =&"10.10.224.44";
&&&&&&&&&&int&portA = 6379;
&&&&&&&&&&String hostB =&"10.10.224.48";
&&&&&&&&&&int&portB = 6379;
&&&&&&&&List&JedisShardInfo& jdsInfoList =new&ArrayList&JedisShardInfo&(2);
&&&&&&&&JedisShardInfo infoA =&new&JedisShardInfo(hostA, portA);
&&&&&&&&infoA.setPassword("redis.360buy");
&&&&&&&&JedisShardInfo infoB =&new&JedisShardInfo(hostB, portB);
&&&&&&&&infoB.setPassword("redis.360buy");
&&&&&&&&jdsInfoList.add(infoA);
&&&&&&&&jdsInfoList.add(infoB);
&&&&&&&&pool&=new&ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH,
Sharded.DEFAULT_KEY_TAG_PATTERN);
&&&&&*&@param&args
&&&&publicstaticvoid&main(String[] args) {
&&&&&&&&for(int&i=0; i&100; i++){
&&&&&&&&&&&String key =generateKey();
&&&&&&&&&&&//key += "{aaa}";
&&&&&&&&&&&ShardedJedis jds =null;
&&&&&&&&&&&try&{
&&&&&&&&&&&&&&&jds =pool.getResource();
&&&&&&&&&&&&&& System.out.println(key+":"+jds.getShard(key).getClient().getHost());
&&&&&&&&&&&&&&&System.out.println(jds.set(key,"1111111"));
&&&&&&&&&&&}catch&(Exception e) {
&&&&&&&&&&&&&&&e.printStackTrace();
&&&&&&&&&&&}
&&&&&&&&&&&finally{
&&&&&&&&&&&&&&&pool.returnResource(jds);
&&&&&&&&&&&}
&&&&privatestaticintindex&= 1;
&&&&publicstatic&String generateKey(){
&&&&&&&&return&String.valueOf(Thread.currentThread().getId())+"_"+(index++);
从运行结果中可以看到,不同的key被分配到不同的Redis-Server上去了。
&总结: 客户端jedis的一致性哈稀进行分片原理:初始化ShardedJedisPool的时候,会将上面程序中的jdsInfoList数据进行一个算法技术,主要计算依据为list中的index位置来计算,我大概看了一下其源码如下:
(如果亲还是不信的话,可以将上面程序中的&jdsInfoList在add的时候,先add第二个,在add第一个,绝对取不出数据,原因很简单,第一次set值的时候,是按list下标来hash计算出一个服务器的,所以取值的时候,list顺序不能变动)
实际上,上面的集群模式还存在两个问题:
1.&&&&&& 扩容问题:
因为使用了一致性哈稀进行分片,那么不同的key分布到不同的Redis-Server上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的key算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况,对于这种情况,Redis的作者提出了一种名为Pre-Sharding的方式:
Pre-Sharding方法是将每一个台物理机上,运行多个不同断口的Redis实例,假如有三个物理机,每个物理机运行三个Redis实际,那么我们的分片列表中实际有9个Redis实例,当我们需要扩容时,增加一台物理机,步骤如下:
A.&&&& 在新的物理机上运行Redis-Server;
B.&&&&& 该Redis-Server从属于(slaveof)分片列表中的某一Redis-Server(假设叫RedisA);
C.&&&&& 等主从复制(Replication)完成后,将客户端分片列表中RedisA的IP和端口改为新物理机上Redis-Server的IP和端口;
D.&&&& 停止RedisA。
这样相当于将某一Redis-Server转移到了一台新机器上。Prd-Sharding实际上是一种在线扩容的办法,但还是很依赖Redis本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。
&再总结一下这里的扩容:其实这里的扩容很简单的思想:就是前期我们可能只用到两三个服务器,但是但是担心后期要扩容,所以前期就现在每一个机器上面再装两个redis,这样就有9个redis嘛,后面如果确实服务器不够,需要扩容,就重新找一台新机来代替9个中的一个redis,有人说,这样不还是9个么,是的,但是以前服务器上面有三个redis,压力很大的,这样做,相当于单独分离出来并且将数据一起copy给新的服务器。值得注意的是,还需要修改客户端被代替的redis的IP和端口为现在新的服务器,只要顺序不变,不会影响一致性哈希分片(刚才上面刚说了哈)。
2.&&&&&& 单点故障问题:
还是用到Redis主从复制的功能,两台物理主机上分别都运行有Redis-Server,其中一个Redis-Server是另一个的从库,采用双机热备技术,客户端通过虚拟IP访问主库的物理IP,当主库宕机时,切换到从库的物理IP。只是事后修复主库时,应该将之前的从库改为主库(使用命令slaveof no one),主库变为其从库(使命令slaveof IP PORT),这样才能保证修复期间新增数据的一致性。Redis(41)
目前没有提供集群的功能,作者在博客中说将在中实现集群机制。目前实现集群的方法主要是采用一致性哈稀分片(),将不同的分配到不同的上,达到横向扩展的目的。下面来介绍一种比较常用的分布式场景:
在读写操作比较均匀且实时性要求较高,可以用下图的分布式模式:
在读操作远远多于写操作时,可以用下图的分布式模式:
对于一致性哈稀分片的算法,已经提供了,下面是使用示例代码(以为例):import java.util.ArrayL
import java.util.L
import redis.clients.jedis.JedisPoolC
import redis.clients.jedis.JedisShardI
import redis.clients.jedis.ShardedJ
import redis.clients.jedis.ShardedJedisP
import redis.clients.util.H
import redis.clients.util.S
publicclass RedisShardPoolTest {
static ShardedJedisP
JedisPoolConfig config =new JedisPoolConfig();//Jedis池配置
config.setMaxActive(500);//最大活动的对象个数
config.setMaxIdle(1000 * 60);//对象最大空闲时间
config.setMaxWait(1000 * 10);//获取对象时最大等待时间
config.setTestOnBorrow(true);
String hostA = &10.10.224.44&;
int portA = 6379;
String hostB = &10.10.224.48&;
int portB = 6379;
List&JedisShardInfo& jdsInfoList =new ArrayList&JedisShardInfo&(2);
JedisShardInfo infoA = new JedisShardInfo(hostA, portA);
infoA.setPassword(&redis.password&);
JedisShardInfo infoB = new JedisShardInfo(hostB, portB);
infoB.setPassword(&redis.password&);
jdsInfoList.add(infoA);
jdsInfoList.add(infoB);
pool =new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH,Sharded.DEFAULT_KEY_TAG_PATTERN);
publicstaticvoid main(String[] args) {
for(int i=0; i&100; i++){
String key = generateKey();
//key += &{aaa}&;
ShardedJedis jds =
jds = pool.getResource();
System.out.println(key+&:&+jds.getShard(key).getClient().getHost());
System.out.println(jds.set(key,&1111111&));
} catch (Exception e) {
e.printStackTrace();
pool.returnResource(jds);
privatestaticintindex = 1;
publicstatic String generateKey(){
return String.valueOf(Thread.currentThread().getId())+&_&+(index++);
从运行结果中可以看到,不同的被分配到不同的上去了。
实际上,上面的集群模式还存在两个问题:
扩容问题:
因为使用了一致性哈稀进行分片,那么不同的分布到不同的上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况,对于这种情况,的作者提出了一种名为的方式:
方法是将每一个台物理机上,运行多个不同断口的实例,假如有三个物理机,每个物理机运行三个实际,那么我们的分片列表中实际有个实例,当我们需要扩容时,增加一台物理机,步骤如下:
在新的物理机上运行;
&该从属于分片列表中的某一(假设叫);
&等主从复制完成后,将客户端分片列表中的和端口改为新物理机上的和端口;
这样相当于将某一转移到了一台新机器上。实际上是一种在线扩容的办法,但还是很依赖本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。
&单点故障问题:
还是用到主从复制的功能,两台物理主机上分别都运行有,其中一个是另一个的从库,采用双机热备技术,客户端通过虚拟访问主库的物理,当主库宕机时,切换到从库的物理。只是事后修复主库时,应该将之前的从库改为主库(使用命令),主库变为其从库(使命令),这样才能保证修复期间新增数据的一致性。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
--------------代码托管---------------&&国之画&&&&&&
&& &&&&&&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!

我要回帖

更多关于 shardedjedis api 的文章

 

随机推荐