比较有名的是jedis( 连接池是基于apache-commons pool2实现的)和Lettuce(基于netty实现),Redisson(基于netty实现,有不少高级功能,比如分布式锁)
1.jedis
Jedis连接方式有三种Jedis/JedisPool 连接、ShardedJedis/ShardedJedisPool 连接、JedisCluster 连接。
Jedis直连相当于一个TCP连接,数据传输完成后关闭连接
JedisPool 是一个链接池,存储了一批jedis对象,多次使用时,不需要反复创建Jedis。
ShardedJedis 要根据key返回映射到的jedis,再调用jedis对象。
public String set(String key, String value) {
Jedis j = getShard(key);
return j.set(key, value);
}
ShardedJedisPool 构造了一组ShardedJedis对象,每个ShardedJedis对象包含一个redis分片对应的jedis对象。
从ShardedJedisPool选取一个ShardedJedis,实际包含了到每个redis分片的链接。这里是否可以考虑基于jedisPool数组来实现?用一个map记录shard信息,和shard对应的redis的分片的jedispool, 通过key取到对应的jedispool,再从jedispool里取出一个jedis链接。
public class ShardedJedisPool extends Pool<ShardedJedis>
@Override
public PooledObject<ShardedJedis> makeObject() hrows Exception {
ShardedJedis jedis = new ShardedJedis(shards, algo, keyTagPattern);
return new DefaultPooledObject<ShardedJedis>(jedis);
}
ShardedJedisPool 在验证到red is 服务器的链接的有效性时,只要到一个分片的链接失效,这个ShardedJedis就认为失效,哪怕到其他redis分片链接是好的。
@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;
}
}
JedisSentinelPool 要用 sentinel中读取redis主节点的Ip端口,还需要一个监听器(线程和redis订阅发布机制)来监听 master的变化。
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) {
protected class MasterListener extends Thread {
...
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);
}
jedis2.8不支持ShardedJedisSentinelPool,以及读写分离的ShardedJedisSentinelPool,需要自己参考JedisSentinelPool和ShardedJedisPool来实现
ShardedJedisSentinelPool可以参考ShardedJedisPool实现方式,本质还是通过setinel拿到一组master节点的ip端口等信息后,建一个Pool<ShardedJedis>,当然,还需要启动监听器线程,监听master的变化,master变化后要重新初始化链接。
利用apache commons pool 创建池时,需要传入一个对象创建工厂,实际传入的事ShardedJedisFactory,里面实现了makeObject函数用来创建ShardedJedis对象,放入DefaultPooledObject中。
this.internalPool = new GenericObjectPool<T>(factory, poolConfig);
@Override
public PooledObject<ShardedJedis> makeObject() throws Exception {
ShardedJedis jedis = new ShardedJedis(shards, algo, keyTagPattern);
return new DefaultPooledObject<ShardedJedis>(jedis);
}