之前分析了jedis中连接对象jedis,ShardedJedis 和连接池jedisPool,JedisSentinelPool源码,jedis和jedisPool都是对单个实例节点的操作,ShardedJedis是对多个节点的操作,jedisSentinelPool是对多个哨兵监控一个主节点,(从节点不关心)的操作,
目前我们遇到一个情况,我们实际场景中,可能有多个主节点,多个从节点,多个哨兵节点,查看jedis源码好像没有对这种场景支持的连接池,所以需要我们手动自己写一个连接池。根据我们前几章节的源码分析,将jedisSentinelPoll和ShardedJedis的情况做一个结合,可以做到支持我们的多个哨兵,多个主节点的分片存储。
首先我们回顾jedisSentinelPool的源码:
public
JedisSentinelPool(String
masterName
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
,
final
int
connectionTimeout
,
final
int
soTimeout
,
final
String
password
,
final
int
database
,
final
String
clientName
) {
this
.
poolConfig
=
poolConfig
;
this
.
connectionTimeout
=
connectionTimeout
;
this
.
soTimeout
=
soTimeout
;
this
.
password
=
password
;
this
.
database
=
database
;
this
.
clientName
=
clientName
;
HostAndPort
master
= initSentinels(
sentinels
,
masterName
);
initPool(
master
);
}
private
HostAndPort
initSentinels
(Set<String>
sentinels
,
final
String
masterName
) {
HostAndPort
master
=
null
;
boolean
sentinelAvailable
=
false
;
log
.info(
"Trying to find master from available Sentinels..."
);
for
(String
sentinel
:
sentinels
) {
final
HostAndPort
hap
= HostAndPort.
parseString
(
sentinel
);
log
.fine(
"Connecting to Sentinel "
+
hap
);
Jedis
jedis
=
null
;
try
{
jedis
=
new
Jedis(
hap
.getHost(),
hap
.getPort());
List<String>
masterAddr
=
jedis
.sentinelGetMasterAddrByName(
masterName
);
// connected to sentinel...
sentinelAvailable
=
true
;
if
(
masterAddr
==
null
||
masterAddr
.size() != 2) {
log
.warning(
"Can not get master addr, master name: "
+
masterName
+
". Sentinel: "
+
hap
+
"."
);
continue
;
}
master
= toHostAndPort(
masterAddr
);
log
.fine(
"Found Redis master at "
+
master
);
break
;
}
catch
(JedisException
e
) {
// resolves #1036, it should handle JedisException there's another chance
// of raising JedisDataException
log
.warning(
"Cannot get master address from sentinel running @ "
+
hap
+
". Reason: "
+
e
+
". Trying next one."
);
}
finally
{
if
(
jedis
!=
null
) {
jedis
.close();
}
}
}
if
(
master
==
null
) {
if
(
sentinelAvailable
) {
// can connect to sentinel, but master name seems to not monitored
throw
new
JedisException(
"Can connect to sentinel, but "
+
masterName
+
" seems to be not monitored..."
);
}
else
{
throw
new
JedisConnectionException(
"All sentinels down, cannot determine where is "
+
masterName
+
" master is running..."
);
}
}
log
.info(
"Redis master running at "
+
master
+
", starting Sentinel listeners..."
);
for
(String
sentinel
:
sentinels
) {
final
HostAndPort
hap
= HostAndPort.
parseString
(
sentinel
);
MasterListener
masterListener
=
new
MasterListener(
masterName
,
hap
.getHost(),
hap
.getPort());
// whether MasterListener threads are alive or not, process can be stopped
masterListener
.setDaemon(
true
);
masterListeners
.add(
masterListener
);
masterListener
.start();
}
return
master
;
}
private
void
initPool
(HostAndPort
master
) {
if
(!
master
.equals(
currentHostMaster
)) {
currentHostMaster
=
master
;
if
(
factory
==
null
) {
factory
=
new
JedisFactory(
master
.getHost(),
master
.getPort(),
connectionTimeout
,
soTimeout
,
password
,
database
,
clientName
,
false
,
null
,
null
,
null
);
initPool(
poolConfig
,
factory
);
}
else
{
factory
.setHostAndPort(
currentHostMaster
);
// although we clear the pool, we still have to check the returned object
// in getResource, this call only clears idle instances, not borrowed instances
internalPool
.clear();
}
log
.info(
"Created JedisPool to master at "
+
master
);
}
}
由上面源码分析有几个关键点,在jedisSentinelPool的构造方法中,初始化配置参数,之后initSentinels方法通过哨兵列表集合对象和master节点名称获取到包含master节点host,port等信息的HostAndPort对象master。又通过initPool方法最终创建连接池。
initSentinels方法接收哨兵集合和单个master名称,返回单个的HostAndPort对象,fainitPool方法接收单个HostAndPort对象作为参数,
如果我们能让initSentinels方法能接收多个master节点名称,返回HostAndPort对象list集合,initPool接收HostAndPort对象list集合参数,就可以达到我们最终的目的实现哨兵监控多节点的连接池,这里有两个地方需要注意,一个masterListener哨兵列表监听和jedisFactory工厂类的改造。
改造后的支持多个主节点的分片储存连接池
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.HashMap;
import
java.util.HashSet;
import
java.util.List;
import
java.util.Map;
import
java.util.Set;
import
java.util.concurrent.atomic.AtomicBoolean;
import
java.util.logging.Level;
import
java.util.logging.Logger;
import
java.util.regex.Pattern;
import
redis.clients.jedis.HostAndPort;
import
redis.clients.jedis.Jedis;
import
redis.clients.jedis.JedisPubSub;
import
redis.clients.jedis.JedisShardInfo;
import
redis.clients.jedis.Protocol;
import
redis.clients.jedis.ShardedJedis;
import
redis.clients.jedis.exceptions.JedisConnectionException;
import
redis.clients.jedis.exceptions.JedisException;
import
redis.clients.util.Hashing;
import
redis.clients.util.Pool;
/**
* 哨兵监控多节点分片存储连接池
*
@author
01478849
*/
public
class
MultiShardedJedisSentinelPool
extends
Pool<ShardedJedis> {
protected
GenericObjectPoolConfig
poolConfig
;
protected
int
connectionTimeout
= Protocol.
DEFAULT_TIMEOUT
;
protected
int
soTimeout
= Protocol.
DEFAULT_TIMEOUT
;
protected
String
password
;
protected
int
database
= Protocol.
DEFAULT_DATABASE
;
protected
String
clientName
;
protected
Set<MasterListener>
masterListeners
=
new
HashSet<MasterListener>();
protected
Logger
log
= Logger.
getLogger
(getClass().getName());
private
volatile
ShardedJedisFactory
factory
;
private
volatile
List<HostAndPort>
currentHostMasters
;
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
) {
this
(
masterNames
,
sentinels
,
new
GenericObjectPoolConfig(), Protocol.
DEFAULT_TIMEOUT
,
null
,
Protocol.
DEFAULT_DATABASE
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
) {
this
(
masterNames
,
sentinels
,
poolConfig
, Protocol.
DEFAULT_TIMEOUT
,
null
,
Protocol.
DEFAULT_DATABASE
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
, String
password
) {
this
(
masterNames
,
sentinels
,
new
GenericObjectPoolConfig(), Protocol.
DEFAULT_TIMEOUT
,
password
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
,
int
timeout
,
final
String
password
) {
this
(
masterNames
,
sentinels
,
poolConfig
,
timeout
,
password
, Protocol.
DEFAULT_DATABASE
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
,
final
int
timeout
) {
this
(
masterNames
,
sentinels
,
poolConfig
,
timeout
,
null
, Protocol.
DEFAULT_DATABASE
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
,
final
String
password
) {
this
(
masterNames
,
sentinels
,
poolConfig
, Protocol.
DEFAULT_TIMEOUT
,
password
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
,
int
timeout
,
final
String
password
,
final
int
database
) {
this
(
masterNames
,
sentinels
,
poolConfig
,
timeout
,
timeout
,
password
,
database
);
}
public
MultiShardedJedisSentinelPool(List<String>
masterNames
, Set<String>
sentinels
,
final
GenericObjectPoolConfig
poolConfig
,
final
int
connectionTimeout
,
final
int
soTimeout
,
final
String
password
,
final
int
database
) {
this
.
poolConfig
=
poolConfig
;
this
.
connectionTimeout
=
connectionTimeout
;
this
.
soTimeout
=
soTimeout
;
this
.
password
=
password
;
this
.
database
=
database
;
List<HostAndPort>
masters
= initSentinels(
sentinels
,
masterNames
);
initPool(
masters
);
}
public
void
destroy() {
for
(MasterListener
m
:
masterListeners
) {
m
.shutdown();
}
super
.destroy();
}
public
List<HostAndPort> getCurrentHostMaster() {
return
currentHostMasters
;
}
private
void
initPool(List<HostAndPort>
masters
) {
if
(!equals(
masters
,
currentHostMasters
)) {
currentHostMasters
=
masters
;
List<JedisShardInfo>
shards
=
new
ArrayList<JedisShardInfo>();
JedisShardInfo
info
=
null
;
for
(HostAndPort
hostAndPort
:
masters
) {
info
=
new
JedisShardInfo(
hostAndPort
.getHost(),
hostAndPort
.getPort());
info
.setPassword(
password
);
shards
.add(
info
);
}
if
(
factory
!=
null
) {
internalPool
.clear();
}
factory
=
new
ShardedJedisFactory(
shards
, Hashing.
MURMUR_HASH
,
null
);
initPool(
poolConfig
,
factory
);
log
.info(
"Created JedisPool to master at "
+
masters
.toString());
}
}
public
boolean
equals(List<HostAndPort>
masters
, List<HostAndPort>
currentHostMasters
) {
if
(
currentHostMasters
!=
null
&&
masters
!=
null
&&
masters
.size() ==
currentHostMasters
.size()) {
for
(HostAndPort
info
:
masters
) {
if
(!
currentHostMasters
.contains(
info
)) {
return
false
;
}
}
return
true
;
}
return
false
;
}
private
List<HostAndPort> initSentinels(Set<String>
sentinels
,
final
List<String>
masterNames
) {
List<HostAndPort>
masterList
=
new
ArrayList<HostAndPort>();
Map<String, HostAndPort>
map
=
new
HashMap<String, HostAndPort>();
log
.info(
"Trying to find master from available Sentinels..."
+
sentinels
.toString());
for
(String
sentinel
:
sentinels
) {
final
HostAndPort
hap
= toHostAndPort(Arrays.
asList
(
sentinel
.split(
":"
)));
log
.info(
"Connecting to Sentinel "
+
hap
);
Jedis
jedis
=
null
;
try
{
jedis
=
new
Jedis(
hap
.getHost(),
hap
.getPort());
for
(String
masterName
:
masterNames
) {
List<String>
masterAddr
=
jedis
.sentinelGetMasterAddrByName(
masterName
);
if
(
masterAddr
!=
null
&&
masterAddr
.size() == 2) {
HostAndPort
master
= toHostAndPort(
masterAddr
);
if
(
master
!=
null
) {
if
(!
masterList
.contains(
master
)) {
masterList
.add(
master
);
}
map
.put(
masterName
,
master
);
}
}
}
log
.info(
"Found Redis master at "
+
masterList
.toString());
}
catch
(JedisException
e
) {
log
.warning(
"Cannot get master address from sentinel running @ "
+
hap
+
". Reason: "
+
e
+
". Trying next one."
);
}
finally
{
if
(
jedis
!=
null
) {
jedis
.close();
}
}
}
log
.info(
"Redis master running at "
+
masterList
.toString() +
", starting Sentinel listeners..."
);
for
(String
sentinel
:
sentinels
) {
final
HostAndPort
hap
= toHostAndPort(Arrays.
asList
(
sentinel
.split(
":"
)));
MasterListener
masterListener
=
new
MasterListener(
hap
.getHost(),
hap
.getPort(),
map
);
masterListener
.setDaemon(
true
);
masterListeners
.add(
masterListener
);
masterListener
.start();
}
return
masterList
;
}
private
HostAndPort toHostAndPort(List<String>
getMasterAddrByNameResult
) {
String
host
=
getMasterAddrByNameResult
.get(0);
int
port
= Integer.
parseInt
(
getMasterAddrByNameResult
.get(1));
return
new
HostAndPort(
host
,
port
);
}
@Override
public
ShardedJedis getResource() {
while
(
true
) {
ShardedJedis
shardedJedis
=
super
.getResource();
shardedJedis
.setDataSource(
this
);
return
shardedJedis
;
}
}
/**
*
@deprecated
starting from
Jedis
3.0 this method will not be exposed. Resource
* cleanup should be done using
@see
*
{@link redis.clients.jedis.Jedis#close()}
*/
@Override
@Deprecated
public
void
returnBrokenResource
(
final
ShardedJedis
resource
) {
if
(
resource
!=
null
) {
returnBrokenResourceObject(
resource
);
}
}
/**
*
@deprecated
starting from
Jedis
3.0 this method will not be exposed. Resource
* cleanup should be done using
@see
*
{@link redis.clients.jedis.Jedis#close()}
*/
@Override
@Deprecated
public
void
returnResource
(
final
ShardedJedis
resource
) {
if
(
resource
!=
null
) {
resource
.resetState();
}
}
protected
class
MasterListener
extends
Thread {
protected
String
host
;
protected
int
port
;
protected
long
subscribeRetryWaitTimeMillis
= 5000;
protected
volatile
Jedis
j
;
protected
AtomicBoolean
running
=
new
AtomicBoolean(
false
);
protected
Map<String, HostAndPort>
map
;
protected
MasterListener() {
}
public
MasterListener(String
host
,
int
port
, Map<String, HostAndPort>
map
) {
super
(String.
format
(
"MasterListener-%s-[%s:%d]"
,
host
+
":"
+
port
,
host
,
port
));
this
.
host
=
host
;
this
.
port
=
port
;
this
.
map
=
map
;
}
public
MasterListener(String
masterName
, String
host
,
int
port
, Map<String, HostAndPort>
map
,
long
subscribeRetryWaitTimeMillis
) {
this
(
host
,
port
,
map
);
this
.
subscribeRetryWaitTimeMillis
=
subscribeRetryWaitTimeMillis
;
}
@Override
public
void
run() {
running
.set(
true
);
while
(
running
.get()) {
j
=
new
Jedis(
host
,
port
);
try
{
// double check that it is not being shutdown
if
(!
running
.get()) {
break
;
}
j
.subscribe(
new
JedisPubSub() {
@Override
public
void
onMessage(String
channel
, String
message
) {
log
.info(
"Sentinel "
+
host
+
":"
+
port
+
" published: "
+
message
+
"."
);
String[]
switchMasterMsg
=
message
.split(
" "
);
log
.info(
"Sentinel masters before change map: "
+
map
.toString() +
"."
);
log
.info(
"Sentinel masters before change: currentHostMasters"
+
currentHostMasters
.toString() +
"."
);
if
(
switchMasterMsg
.
length
> 3) {
// 根据变更主节点名称从map中获取变更之前的地址对象
HostAndPort
master
=
map
.get(
switchMasterMsg
[0]);
HostAndPort
newHostAndPort
= toHostAndPort(Arrays.
asList
(
switchMasterMsg
[3],
switchMasterMsg
[4]));
List<HostAndPort>
shards
=
new
ArrayList<>();
for
(HostAndPort
oldHostAndPort
:
currentHostMasters
) {
if
(
oldHostAndPort
.equals(
master
)) {
shards
.add(
newHostAndPort
);
}
else
{
shards
.add(
oldHostAndPort
);
}
}
log
.info(
"Sentinel masters after change: "
+
shards
.toString() +
"."
);
initPool(
shards
);
}
else
{
log
.info(
"Invalid message received on Sentinel "
+
host
+
":"
+
port
+
" on channel +switch-master: "
+
message
);
}
}
},
"+switch-master"
);
}
catch
(JedisConnectionException
e
) {
if
(
running
.get()) {
log
.log(Level.
SEVERE
,
"Lost connection to Sentinel at "
+
host
+
":"
+
port
+
". Sleeping 5000ms and retrying."
,
e
);
try
{
Thread.
sleep
(
subscribeRetryWaitTimeMillis
);
}
catch
(InterruptedException
e1
) {
log
.log(Level.
SEVERE
,
"Sleep interrupted: "
,
e1
);
}
}
else
{
log
.info(
"Unsubscribing from Sentinel at "
+
host
+
":"
+
port
);
}
}
finally
{
j
.close();
}
}
}
public
void
shutdown() {
try
{
log
.info(
"Shutting down listener on "
+
host
+
":"
+
port
);
running
.set(
false
);
// This isn't good, the
Jedis
object is not thread safe
if
(
j
!=
null
) {
j
.disconnect();
}
}
catch
(Exception
e
) {
log
.log(Level.
SEVERE
,
"Caught exception while shutting down: "
,
e
);
}
}
}
/**
* ShardedJedis对象工厂类
*
*
@author
01478849
*
*/
public
class
ShardedJedisFactory
implements
PooledObjectFactory<ShardedJedis> {
private
List<JedisShardInfo>
shards
;
private
Hashing
algo
;
private
Pattern
keyTagPattern
;
public
ShardedJedisFactory(
final
List<JedisShardInfo>
shards
,
final
Hashing
algo
,
final
Pattern
keyTagPattern
) {
this
.
shards
=
shards
;
this
.
algo
=
algo
;
this
.
keyTagPattern
=
keyTagPattern
;
}
@Override
public
PooledObject<ShardedJedis> makeObject()
throws
Exception {
ShardedJedis
jedis
=
new
ShardedJedis(
shards
,
algo
,
keyTagPattern
);
return
new
DefaultPooledObject<ShardedJedis>(
jedis
);
}
@Override
public
void
destroyObject(PooledObject<ShardedJedis>
pooledShardedJedis
)
throws
Exception {
final
ShardedJedis
shardedJedis
=
pooledShardedJedis
.getObject();
for
(Jedis
jedis
:
shardedJedis
.getAllShards()) {
try
{
try
{
jedis
.quit();
}
catch
(Exception
e
) {
}
jedis
.disconnect();
}
catch
(Exception
e
) {
}
}
}
@Override
public
boolean
validateObject(PooledObject<ShardedJedis>
pooledShardedJedis
) {
try
{
ShardedJedis
jedis
=
pooledShardedJedis
.getObject();
for
(Jedis
shard
:
jedis
.getAllShards()) {
if
(!
shard
.ping().equals(
"PONG"
)) {
return
false
;
}
}
return
true
;
}
catch
(Exception
ex
) {
return
false
;
}
}
@Override
public
void
activateObject(PooledObject<ShardedJedis>
p
)
throws
Exception {
}
@Override
public
void
passivateObject(PooledObject<ShardedJedis>
p
)
throws
Exception {
}
}
}
几天关键地方,一个是SharedJedisFactory工厂类,可以直接使用SharedJedis中ShardedJedisFactory工厂类,直接拷贝过来,另一个是MasterListener哨兵的监听类,通过redis的消息订阅直接订阅哨兵的消息变更,如果哨兵监控的主节点发生变更,listener监听类中subscribe方法监听变更消息,onMessage方法中,接收到变更后的节点信息将变更的节点地址变更。
变更信息数据格式:master1(变更节点名称) host(变更前地址) port(变更后端口) host(变更后地址) port(变更后端口)
数据样例:master1 127.0.0.1 4102 127.0.0.1 4100.
判断替换变更的逻辑,首先在
initSentinels方法初始化的时候将循环遍历的节点信息放入Map中,其中key是节点名称masterName,value值是封装了host,port信息的HostAntPort对象,
之后在循环创建哨兵监听masterListener的将map传入,在listener的onMessage中判断,
首先通过空格分割订阅接收的消息,
String[]
switchMasterMsg
=
message
.split(
" "
);
拿到节点名称
switchMasterMsg
[0]
通过节点名称取到初始化放入map中的节点变更之前的地址信息,重新创建一个新的HostAndPort对象list,循环变更之前的节点地址列表currentHostMasters,这里储存的都是地址对象HostAndPort,将地址对象都放入新的list中,这里有一个判断如果循环的地址对象与从map中取到的地址对象HostAndPort相等,说明就是这个对象地址发生了变更,那么就将新接收到的地址封装成地址对象放入新的list集合中,最后调用initPool方法重新初始化连接池。
public
void
onMessage(String
channel
, String
message
) {
log
.fine(
"Sentinel "
+
host
+
":"
+
port
+
" published: "
+
message
+
"."
);
String[]
switchMasterMsg
=
message
.split(
" "
);
if
(
switchMasterMsg
.
length
> 3) {
HostAndPort
master
=
map
.get(
switchMasterMsg
[0]);
HostAndPort
newHostAndPort
= toHostAndPort(Arrays.
asList
(
switchMasterMsg
[3],
switchMasterMsg
[4]));
List<HostAndPort>
shards
=
new
ArrayList<>();
for
(HostAndPort
oldHostAndPort
:
currentHostMasters
) {
if
(
oldHostAndPort
.equals(
master
)) {
shards
.add(
newHostAndPort
);
}
else
{
shards
.add(
oldHostAndPort
);
}
}
initPool(
shards
);
}
else
{
log
.severe(
"Invalid message received on Sentinel "
+
host
+
":"
+
port
+
" on channel +switch-master: "
+
message
);
}
}
之前在github上有另一种MasterListener监听判断方法,代码:
protected
class
MasterListener
extends
Thread {
protected
List<String>
masters
;
protected
String
host
;
protected
int
port
;
protected
long
subscribeRetryWaitTimeMillis
= 5000;
protected
Jedis
jedis
;
protected
AtomicBoolean
running
=
new
AtomicBoolean(
false
);
protected
MasterListener() {
}
public
MasterListener(List<String>
masters
, String
host
,
int
port
) {
this
.
masters
=
masters
;
this
.
host
=
host
;
this
.
port
=
port
;
}
public
MasterListener(List<String>
masters
, String
host
,
int
port
,
long
subscribeRetryWaitTimeMillis
) {
this
(
masters
,
host
,
port
);
this
.
subscribeRetryWaitTimeMillis
=
subscribeRetryWaitTimeMillis
;
}
public
void
run() {
running
.set(
true
);
while
(
running
.get()) {
jedis
=
new
Jedis(
host
,
port
);
try
{
jedis
.subscribe(
new
JedisPubSubAdapter() {
@Override
public
void
onMessage(String
channel
, String
message
) {
log
.fine(
"Sentinel "
+
host
+
":"
+
port
+
" published: "
+
message
+
"."
);
String[]
switchMasterMsg
=
message
.split(
" "
);
if
(
switchMasterMsg
.
length
> 3) {
int
index
=
masters
.indexOf(
switchMasterMsg
[0]);
if
(
index
>= 0) {
HostAndPort
newHostMaster
= toHostAndPort(
Arrays.
asList
(
switchMasterMsg
[3],
switchMasterMsg
[4]));
List<HostAndPort>
newHostMasters
=
new
ArrayList<HostAndPort>();
for
(
int
i
= 0;
i
<
masters
.size();
i
++) {
newHostMasters
.add(
null
);
}
Collections.
copy
(
newHostMasters
,
currentHostMasters
);
newHostMasters
.set(
index
,
newHostMaster
);
initPool(
newHostMasters
);
}
else
{
StringBuffer
sb
=
new
StringBuffer();
for
(String
masterName
:
masters
) {
sb
.append(
masterName
);
sb
.append(
","
);
}
log
.fine(
"Ignoring message on +switch-master for master name "
+
switchMasterMsg
[0]
+
", our monitor master name are ["
+
sb
+
"]"
);
}
}
else
{
log
.severe(
"Invalid message received on Sentinel "
+
host
+
":"
+
port
+
" on channel +switch-master: "
+
message
);
}
}
},
"+switch-master"
);
}
catch
(JedisConnectionException
e
) {
if
(
running
.get()) {
log
.severe(
"Lost connection to Sentinel at "
+
host
+
":"
+
port
+
". Sleeping 5000ms and retrying."
);
try
{
Thread.
sleep
(
subscribeRetryWaitTimeMillis
);
}
catch
(InterruptedException
e1
) {
e1
.printStackTrace();
}
}
else
{
log
.fine(
"Unsubscribing from Sentinel at "
+
host
+
":"
+
port
);
}
}
}
}
public
void
shutdown() {
try
{
log
.fine(
"Shutting down listener on "
+
host
+
":"
+
port
);
running
.set(
false
);
// This isn't good, the
Jedis
object is not thread safe
jedis
.disconnect();
}
catch
(Exception
e
) {
log
.severe(
"Caught exception while shutting down: "
+
e
.getMessage());
}
}
}
这里在初始化MasterListener的时候传入了List<MasterNames>,通过接收的节点名称确定在list中的位置index,
int
index
=
masters
.indexOf(
switchMasterMsg
[0]);同样创建一个跟list同样大小的List<HostAndPort>集合newHostMasters,将变更前的地址对象集合currentHostMasters通过Collections的copy方法复制到新的newHostMasters中,然后根据之前确定的变更节点的位置index,重新将新值赋值,newHostMasters.set(index, newHostMaster);最后调用initPool重新初始化连接池。
多节点测试连接池示例:本示例两个主节点,一个哨兵节点
import
java.text.SimpleDateFormat;
import
java.util.ArrayList;
import
java.util.Date;
import
java.util.HashSet;
import
java.util.List;
import
java.util.Random;
import
java.util.Set;
import
java.util.logging.Logger;
import
com.redis.demo.MultiShardedJedisSentinelPool;
import
redis.clients.jedis.JedisPoolConfig;
import
redis.clients.jedis.ShardedJedis;
public
class
ShardedJedisSentinelPoolUtil {
private
static
Logger
logger
= Logger.
getLogger
(
"ShardedJedisSentinelPoolUtil"
);
// private static ShardedJedisSentinelPool pool = null;
// private static MyJedisSentinelPool pool = null;
private
static
MultiShardedJedisSentinelPool
pool
=
null
;
/**
* 创建连接池
*
*/
static
{
JedisPoolConfig
config
=
new
JedisPoolConfig();
config
.setMaxIdle(10);
config
.setMaxTotal(200);
config
.setMaxWaitMillis(1000 * 10);
config
.setTestOnBorrow(
true
);
Set<String>
sentinels
=
new
HashSet<String>();
sentinels
.add(
"127.0.0.1:4200"
);
sentinels.add("127.0.0.1:4201");
List<String>
masters
=
new
ArrayList<String>();
masters
.add(
"master1"
);
masters
.add(
"master2"
);
pool
=
new
MultiShardedJedisSentinelPool(
masters
,
sentinels
,
config
);
logger
.info(
"多节点连接池创建成功"
);
}
public
static
synchronized
String set(String
key
, String
value
) {
ShardedJedis
jedis
=
null
;
String
result
=
null
;
try
{
if
(
jedis
==
null
) {
jedis
=
pool
.getResource();
}
result
=
jedis
.set(
key
,
value
);
}
catch
(Exception
e
) {
logger
.info(
e
.getMessage());
}
finally
{
jedis
.close();
}
return
result
;
}
public
static
String get(String
key
) {
ShardedJedis
jedis
=
null
;
String
result
=
null
;
try
{
if
(
jedis
==
null
) {
jedis
=
pool
.getResource();
}
result
=
jedis
.get(
key
);
}
catch
(Exception
e
) {
logger
.info(
e
.getMessage());
}
finally
{
jedis
.close();
}
return
result
;
}
public
static
void
main(String[]
args
) {
Thread1
thread1
=
new
Thread1();
thread1
.start();
Thread1
thread2
=
new
Thread1();
thread2
.start();
Thread1
thread3
=
new
Thread1();
thread3
.start();
Thread1
thread4
=
new
Thread1();
thread4
.start();
Thread1
thread5
=
new
Thread1();
thread5
.start();
Thread1
thread6
=
new
Thread1();
thread6
.start();
Thread1
thread7
=
new
Thread1();
thread7
.start();
Thread1
thread8
=
new
Thread1();
thread8
.start();
Thread1
thread9
=
new
Thread1();
thread9
.start();
Thread1
thread10
=
new
Thread1();
thread10
.start();
}
}
class
Thread1
extends
Thread {
public
void
run() {
while
(
true
) {
String
ts
=
new
SimpleDateFormat(
"hh:mm:ss"
).format(
new
Date());
ShardedJedis
jedis
= ShardedJedisSentinelPoolUtil.
getJedis
();
try
{
String
key
=
"hello"
+ String.
valueOf
(
new
Random().nextInt());
String
value
= String.
valueOf
(
new
Random().nextInt());
System.
out
.println(
"线程id::"
+ Thread.
currentThread
().getId() +
": "
+
ts
+
": hello="
+
jedis
.set(
key
,
value
));
}
catch
(Exception
e
) {
System.
out
.println(
"线程id::"
+ Thread.
currentThread
().getId() +
": "
+
ts
+
": Cannot connect to Redis "
+
e
.getMessage());
}
finally
{
jedis
.close();
}
try
{
Thread.
sleep
(500L);
}
catch
(InterruptedException
e
) {
e
.printStackTrace();
}
}
}
}
注:使用完的资源一定要close关闭,否则会导致无法获取链接资源。多线程并发测试需要在操作方法上加上
synchronized锁,否则会获取不到资源异常。