集群环境搭建见另外一篇文章https://blog.csdn.net/dongzhongyan/article/details/100141898
连接池工具类,内部使用了Redis连接池,通过源码可以看到,集群操作数据时,通过Redis连接池获取连接,用完后会释放连接,把Redis归还给连接池。
源码:
执行sadd命令
@Override
public Long sadd(final String key, final String... member) {
return new JedisClusterCommand(connectionHandler, maxAttempts) {
@Override
public Long execute(Jedis connection) {
return connection.sadd(key, member);
}
}.run(key);
}
在执行run后,releaseConnection(connection);会释放连接。
private T runWithRetries(final int slot, int attempts, boolean tryRandomNode, JedisRedirectionException redirect) {
if (attempts <= 0) {
throw new JedisClusterMaxAttemptsException("No more cluster attempts left.");
}
Jedis connection = null;
try {
if (redirect != null) {
connection = this.connectionHandler.getConnectionFromNode(redirect.getTargetNode());
if (redirect instanceof JedisAskDataException) {
// TODO: Pipeline asking with the original command to make it faster....
connection.asking();
}
} else {
if (tryRandomNode) {
connection = connectionHandler.getConnection();
} else {
connection = connectionHandler.getConnectionFromSlot(slot);
}
}
return execute(connection);
} catch (JedisNoReachableClusterNodeException jnrcne) {
throw jnrcne;
} catch (JedisConnectionException jce) {
// release current connection before recursion
releaseConnection(connection);
connection = null;
if (attempts <= 1) {
//We need this because if node is not reachable anymore - we need to finally initiate slots
//renewing, or we can stuck with cluster state without one node in opposite case.
//But now if maxAttempts = [1 or 2] we will do it too often.
//TODO make tracking of successful/unsuccessful operations for node - do renewing only
//if there were no successful responses from this node last few seconds
this.connectionHandler.renewSlotCache();
}
return runWithRetries(slot, attempts - 1, tryRandomNode, redirect);
} catch (JedisRedirectionException jre) {
// if MOVED redirection occurred,
if (jre instanceof JedisMovedDataException) {
// it rebuilds cluster's slot cache recommended by Redis cluster specification
this.connectionHandler.renewSlotCache(connection);
}
// release current connection before recursion
releaseConnection(connection);
connection = null;
return runWithRetries(slot, attempts - 1, false, jre);
} finally {
releaseConnection(connection);
}
}
连接池工具类,可能不太严谨,在高并发情况下不一定会保证唯一性,可以做成单例模式。
package utils;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
/**
* @author dongzy
* @Desc
* @date 2019/8/28.
*/
public class RedisClusterUtil {
private static JedisCluster jedis = null;
static {
int connectTimeOut = 10000;//连接超时
int soTimeOut = 5000;//读写超时
int maxAttemts = 10;//重试次数
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(Integer.valueOf(80)); //连接池最大连接数
poolConfig.setMaxIdle(Integer.valueOf(10));//连接池中最大空闲的连接数
//当连接池用尽后,调用者的最大等待时间(单位为毫秒),默认值为-1 表示永不超时,一直等待,一般不建议使用默认值。
//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
poolConfig.setBlockWhenExhausted(true);
//设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数)
poolConfig.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");
//是否启用pool的jmx管理功能, 默认true
poolConfig.setJmxEnabled(true);
//MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默 认为"pool", JMX不熟,具体不知道是干啥的...默认就好.
poolConfig.setJmxNamePrefix("pool");
//是否启用后进先出, 默认true
poolConfig.setLifo(true);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
poolConfig.setMaxWaitMillis(-1);
//逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
poolConfig.setMinEvictableIdleTimeMillis(1800000);
//最小空闲连接数, 默认0
poolConfig.setMinIdle(10);
//每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
//做空闲连接检测时,每次的采样数,默认3
poolConfig.setNumTestsPerEvictionRun(3);
//对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略)
poolConfig.setSoftMinEvictableIdleTimeMillis(1800000);
//在空闲时检查有效性, 默认false
poolConfig.setTestWhileIdle(false);
//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
poolConfig.setTimeBetweenEvictionRunsMillis(-1);
Set nodes = new HashSet();
nodes.add(new HostAndPort("172.16.8.87", 8001));
nodes.add(new HostAndPort("172.16.8.87", 8002));
nodes.add(new HostAndPort("172.16.8.87", 8003));
nodes.add(new HostAndPort("172.16.8.87", 8004));
nodes.add(new HostAndPort("172.16.8.87", 8005));
nodes.add(new HostAndPort("172.16.8.87", 8006));
jedis = new JedisCluster(nodes, poolConfig);
}
/**
* 获取集群连接
* @return
*/
public static JedisCluster JedisCluster() {
if(jedis == null){
return jedis;
}
}
/**
* 关闭集群连接
*/
public static void releaseJedisCluster() {
if (jedis != null) {
jedis.close();
}
}
}
测试类,通过对数据分析,发现集群模式下的节点中数据被随机存储。且主从节点数据一致
package rediscluster;
import redis.clients.jedis.JedisCluster;
import utils.RedisClusterUtil;
/**
* @author dongzy
* @Desc
* @date 2019/8/28.
*/
public class RedisCluster {
public static void main(String[] args) {
for (int i = 0; i < 12; i++) {
int finalI = i;
new Thread() {
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
JedisCluster redisCluster = RedisClusterUtil.JedisCluster();
redisCluster.sadd("thread" + String.valueOf(finalI), "" + j);
System.out.println("thread" + String.valueOf(finalI) + redisCluster);
}
}
}.start();
}
}
}