Redis Java客户端有很多的开源产品比如Redission、Jedis、lettuce
1.jedis是redis的java实现的客户端,其API提供了比较全面的Redis命令的支持;
2.Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排
序、事务、管道、分区等Redis特性。Redisson主要是促进使用者对Redis的关注分离,从而让使用者能够将精力更
集中地放在处理业务逻辑上。
3.lettuce是基于Netty构建的一个可伸缩的线程安全的Redis客户端,支持同步、异步、响应式模式。多个线程可以
共享一个连接实例,而不必担心多线程并发问题;
jedis-sentinel原理分析
原理
客户端通过连接到哨兵集群,通过发送Protocol.SENTINEL_GET_MASTER_ADDR_BY_NAME 命令,从哨兵机器中
询问master节点的信息,拿到master节点的ip和端口号以后,再到客户端发起连接。连接以后,需要在客户端建
立监听机制,当master重新选举之后,客户端需要重新连接到新的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...");
// 有多个sentinels,遍历这些个sentinels
for (String sentinel : sentinels) {
// host:port表示的sentinel地址转化为一个HostAndPort对象。
final HostAndPort hap = HostAndPort.parseString(sentinel);
log.fine("Connecting to Sentinel " + hap);
Jedis jedis = null;
try {
// 连接到sentinel
jedis = new Jedis(hap.getHost(), hap.getPort());
// 根据masterName得到master的地址,返回一个list,host= list[0], port =// list[1]
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;
}
// 如果在任何一个sentinel中找到了master,不再遍历sentinels
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();
}
}
}
// 到这里,如果master为null,则说明有两种情况,一种是所有的sentinels节点都down掉了,一种是master节点没有被存活的sentinels监控到
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...");
}
}
//如果走到这里,说明找到了master的地址
log.info("Redis master running at " + master + ", startingSentinellisteners...");
//启动对每个sentinels的监听为每个sentinel都启动了一个监听者MasterListener。MasterListener本身是一个线程,它会去订阅sentinel
上关于master节点地址改变的消息。
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;
}
从哨兵节点获取master信息的方法
public List<String> sentinelGetMasterAddrByName(String masterName) {
client.sentinel(Protocol.SENTINEL_GET_MASTER_ADDR_BY_NAME, masterName);
final List<Object> reply = client.getObjectMultiBulkReply();
return BuilderFactory.STRING_LIST.build(reply);
}
Jedis-cluster原理分析
连接方式
Set<HostAndPort> hostAndPorts=new HashSet<>();
HostAndPort hostAndPort=new HostAndPort("192.168.**.***",****);
HostAndPort hostAndPort1=new HostAndPort("192.168.**.***",****);
HostAndPort hostAndPort2=new HostAndPort("192.168.**.***",****);
HostAndPort hostAndPort3=new HostAndPort("192.168.**.***",****);
hostAndPorts.add(hostAndPort);
hostAndPorts.add(hostAndPort1);
hostAndPorts.add(hostAndPort2);
hostAndPorts.add(hostAndPort3);
JedisCluster jedisCluster=new JedisCluster(hostAndPorts,6000);
jedisCluster.set("ma","hello");
原理分析
程序启动初始化集群环境
1.读取配置文件中的节点配置,无论是主从,无论多少个,只拿第一个,获取redis链接实例
2.用获取的redis连接实例执行clusterNodes()方法,实际执行redis服务端cluster noder命令,获取主从配置信息
3.解析主从配置信息,先把所有节点存放到nodes的map集合中,key为节点的ip:port,value为当前节点的jedisPool
4.解析主节点分配的slots区间段,把slot对应的索引值作为key,第三步中拿到的jedisPool作为value,存储在
slots的map集合中
就实现了slot槽索引值与jedisPool的映射,这个jedisPool包含了master的节点信息,所以槽和节点是对应的,与
redis服务端一致
从集群环境存取值
1)、把key作为参数,执行CRC16算法,获取key对应的slot值
2)、通过该slot值,去slots的map集合中获取jedisPool实例
3)、通过jedisPool实例获取jedis实例,最终完成redis数据存取工作