4,哨兵模式:
一个master节点,多个salver节点,从节点默认只能查询,一个哨兵节点,哨兵节点负责监控主节点状态,在主节点服务挂掉之后可以在从节点中选举从新的主节点使缓存服务立刻恢复工作;通过jedis自带的
JedisSentinelPool
实现,但是
JedisSentinelPool
只支持哨兵监听一个主节点,如果我们需要更多的主节点支持,目前jedis版本自带的
JedisSentinelPool
无法很好的支持,所以我们需要自己改造支持多个主节点的pool连接池,
目前的jedis版本自带支持哨兵监听一个主master节点的连接池方式
JedisSentinelPool,通过jedisSentinelPool连接哨兵的demo示例:
import
java.util.HashSet;
import
java.util.Set;
import
java.util.logging.Logger;
import
redis.clients.jedis.Jedis;
import
redis.clients.jedis.JedisPoolConfig;
import
redis.clients.jedis.JedisSentinelPool;
public
class
JedisSentinelPoolUtil {
private
static
Logger
logger
= Logger.
getLogger
(
"JedisPoolUtil"
);
private
static
JedisSentinelPool
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"
);
pool
=
new
JedisSentinelPool(
"master1"
,
sentinels
,
config
);
}
/**
* 获取连接池
jedis
对象
*
@return
*/
public
static
Jedis getJedis() {
return
pool
.getResource();
}
public
static
String set(String
key
, String
value
) {
Jedis
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();
// pool.destroy();
}
return
result
;
}
public
static
String get(String
key
) {
Jedis
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
) {
for
(
int
i
= 0;
i
< 100;
i
++) {
JedisSentinelPoolUtil.
set
(
"key"
+
i
,
"value"
+
i
);
System.
out
.println(JedisSentinelPoolUtil.
get
(
"key"
+
i
) +
" "
);
}
}
}
查看
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
);
}
可以看到源码中在构造方法中通过initSentinel方法
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
;
}
传入哨兵地址集合
sentinels和主节名称
masterName参数,
通过循环哨兵列表,在循环体内然后通过哨兵的jedis对象调用
sentinelGetMasterAddrByName方法获取哨兵监控的主节点的地址信息host和port信息放入list<String>类型的
masterAddr中
,
通过
将
masterAddr传入
toHostAndPort方法
获取到一个简单封装了
主节点地址host和端口port属性的
HostAndPort类
master
,
这里在得到master对象后,再次循环了哨兵列表得到哨兵的
HostAndPort对象,这里创建一个MasterListener线程来通过哨兵监听master节点变化,如果主节点发生变化,重新获取选举的新的主节点信息,重新初始化连接池。
MaserListener实现方法
protected
class
MasterListener
extends
Thread {
protected
String
masterName
;
protected
String
host
;
protected
int
port
;
protected
long
subscribeRetryWaitTimeMillis
= 5000;
protected
volatile
Jedis
j
;
protected
AtomicBoolean
running
=
new
AtomicBoolean(
false
);
protected
MasterListener() {
}
public
MasterListener(String
masterName
, String
host
,
int
port
) {
super
(String.
format
(
"MasterListener-%s-[%s:%d]"
,
masterName
,
host
,
port
));
this
.
masterName
=
masterName
;
this
.
host
=
host
;
this
.
port
=
port
;
}
public
MasterListener(String
masterName
, String
host
,
int
port
,
long
subscribeRetryWaitTimeMillis
) {
this
(
masterName
,
host
,
port
);
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
.fine(
"Sentinel "
+
host
+
":"
+
port
+
" published: "
+
message
+
"."
);
String[]
switchMasterMsg
=
message
.split(
" "
);
if
(
switchMasterMsg
.
length
> 3) {
if
(
masterName
.equals(
switchMasterMsg
[0])) {
initPool(toHostAndPort(Arrays.
asList
(
switchMasterMsg
[3],
switchMasterMsg
[4])));
}
else
{
log
.fine(
"Ignoring message on +switch-master for master name "
+
switchMasterMsg
[0] +
", our master name is "
+
masterName
);
}
}
else
{
log
.severe(
"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
.fine(
"Unsubscribing from Sentinel at "
+
host
+
":"
+
port
);
}
}
finally
{
j
.close();
}
}
}
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
if
(
j
!=
null
) {
j
.disconnect();
}
}
catch
(Exception
e
) {
log
.log(Level.
SEVERE
,
"Caught exception while shutting down: "
,
e
);
}
}
}
run方法中通过哨兵的jedis对象
subscribe方法监听广播消息,并用当前masterName与广播消息中的节点名称比较,如果相等说明产生新的主节点,调用
org
.
apache
.
commons
.
pool2
.
impl
.
GenericObjectPool
类
的
GenericObjectPool方法返回连接池。只有产生新的主节点才会广播信息。
将master对象传入initPool初始化连接池方法中。这里创建一个JedisFactory工厂类,
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
);
}
}
得到jedisFactory工厂类之后,调用实现了Closeable接口的
Pool<T>类的initPool方法
public
void
initPool
(
final
GenericObjectPoolConfig
poolConfig
, PooledObjectFactory<T>
factory
) {
if
(
this
.
internalPool
!=
null
) {
try
{
closeInternalPool();
}
catch
(Exception
e
) {
}
}
this
.
internalPool
=
new
GenericObjectPool<T>(
factory
,
poolConfig
);
}
通过得到的pool连接池调用getResource()得到一个Jedis对象
public
static
Jedis getJedis() {
return
pool
.getResource();
}
在整个过程中有两个比较重要点是一个是创建JedisFactory工厂类对象,一个是MasterLinenter线程监听类;JedisFactory工厂类负责jedis对象的创建、销毁、激活、验证等操作。masterListener监听类负责监听哨兵的广播消息,是否有新的master节点产生,如果生产新的master节点并负责创建新的连接池。