Jedis的Sharded源代码分析
概述
Jedis是Redis官方推荐的Java客户端,更多Redis的客户端可以参考Redis官网客户端列表。当业务的数据量非常庞大时,需要考虑将数据存储到多个缓存节点上,如何定位数据应该存储的节点,一般用的是一致性哈希算法。Jedis在客户端角度实现了一致性哈希算法,对数据进行分片,存储到对应的不同的redis实例中。
Jedis对Sharded的实现主要是在ShardedJedis.java和ShardedJedisPool.java中。本文主要介绍ShardedJedis的实现,ShardedJedisPool是基于apache的common-pool2的对象池实现。
继承关系
ShardedJedis--->BinaryShardedJedis--->Sharded <Jedis, JedisShardInfo>
构造函数
查看其构造函数
public ShardedJedis(List<JedisShardInfo> shards, Hashing algo, Pattern keyTagPattern) {
super(shards, algo, keyTagPattern);
}
构造器参数解释:
shards是一个JedisShardInfo的列表,一个JedisShardedInfo类代表一个数据分片的主体。
algo是用来进行数据分片的算法
keyTagPattern,自定义分片算法所依据的key的形式。例如,可以不针对整个key的字符串做哈希计算,而是类似对thisisa{key}中包含在大括号内的字符串进行哈希计算。
JedisShardInfo是什么样的?
public class JedisShardInfo extends ShardInfo<Jedis> {
public String toString() {
return host + ":" + port + "*" + getWeight();
}
private int connectionTimeout;
private int soTimeout;
private String host;
private int port;
private String password = null;
private String name = null;
// Default Redis DB
private int db = 0;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public JedisShardInfo(String host) {
super(Sharded.DEFAULT_WEIGHT);
URI uri = URI.create(host);
if (JedisURIHelper.isValid(uri)) {
this.host = uri.getHost();
this.port = uri.getPort();
this.password = JedisURIHelper.getPassword(uri);
this.db = JedisURIHelper.getDBIndex(uri);
} else {
this.host = host;
this.port = Protocol.DEFAULT_PORT;
}
}
public JedisShardInfo(String host, String name) {
this(host, Protocol.DEFAULT_PORT, name);
}
public JedisShardInfo(String host, int port) {
this(host, port, 2000);
}
public JedisShardInfo(String host, int port, String name) {
this(host, port, 2000, name);
}
public JedisShardInfo(String host, int port, int timeout) {
this(host, port, timeout, timeout, Sharded.DEFAULT_WEIGHT);
}
public JedisShardInfo(String host, int port, int timeout, String name) {
this(host, port, timeout, timeout, Sharded.DEFAULT_WEIGHT);
this.name = name;
}
public JedisShardInfo(String host, int port, int connectionTimeout, int soTimeout, int weight) {
super(weight);
this.host = host;
this.port = port;
this.connectionTimeout = connectionTimeout;
this.soTimeout = soTimeout;
}
public JedisShardInfo(String host, String name, int port, int timeout, int weight) {
super(weight);
this.host = host;
this.name = name;
this.port = port;
this.connectionTimeout = timeout;
this.soTimeout = timeout;
}
public JedisShardInfo(URI uri) {
super(Sharded.DEFAULT_WEIGHT);
if (!JedisURIHelper.isValid(uri)) {
throw new InvalidURIException(String.format(
"Cannot open Redis connection due invalid URI. %s", uri.toString()));
}
this.host = uri.getHost();
this.port = uri.getPort();
this.password = JedisURIHelper.getPassword(uri);
this.db = JedisURIHelper.getDBIndex(uri);
}
@Override
public Jedis createResource() {
return new Jedis(this);
}
/**
* 省略setters和getters
**/
}
可见JedisShardInfo包含了一个redis节点ip地址,端口号,name,密码等等相关信息。要构造一个ShardedJedis,提供一个或多个JedisShardInfo。
最终构造函数的实现在其父类Sharded里面
public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) {
this.algo = algo;
this.tagPattern = tagPattern;
initialize(shards);
}
哈希环的初始化
Sharded类里面维护了一个TreeMap,基于红黑树实现,用来盛放经过一致性哈希计算后的redis节点,另外维护了一个LinkedHashMap,用来保存ShardInfo与Jedis实例的对应关系。
定位的流程如下
先在TreeMap中找到对应key所对应的ShardInfo,然后通过ShardInfo在LinkedHashMap中找到对应的Jedis实例。
Sharded类对这些实例变量的定义如下所示:
public static final int DEFAULT_WEIGHT = 1;
private TreeMap<Long, S> nodes;
private final Hashing algo;
private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<ShardInfo<R>, R>();
/**
* The default pattern used for extracting a key tag. The pattern must have
* a group (between parenthesis), which delimits the tag to be hashed. A
* null pattern avoids applying the regular expression for each lookup,
* improving performance a little bit is key tags aren't being used.
*/
private Pattern tagPattern = null;
// the tag is anything between {}
public static final Pattern DEFAULT_KEY_TAG_PATTERN = Pattern.compile("\\{(.+?)\\}");
接下来看其构造函数中的initialize方法
private void initialize(List<S> shards) {
nodes = new TreeMap<Long, S>();
for (int i = 0; i != shards.size(); ++i) {
final S shardInfo = shards.get(i);
if (shardInfo.getName() == null)
for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
}
else
for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
nodes.put(
this.algo.hash(shardInfo.getName() + "*"
+ shardInfo.getWeight() + n), shardInfo);
}
resources.put(shardInfo, shardInfo.createResource());
}
}
可以看到,它对每一个ShardInfo通过一定规则计算其哈希值,然后存到TreeMap中,这里它实现了一致性哈希算法中虚拟节点的概念,因为我们可以看到同一个ShardInfo不止一次被放到TreeMap中,数量是,权重*160。
增加了虚拟节点的一致性哈希有很多好处,能避免数据在redis节点间分布不均匀。
然后,在LinkedHashMap中放入ShardInfo以及其对应的Jedis实例,通过调用其自身的createSource()来得到jedis实例。
数据定位
从ShardedJedis的代码中可以看到,无论进行什么操作,都要先根据key来找到对应的Redis,然后返回一个可供操作的Jedis实例。
例如其set方法:
public String set(String key, String value) {
Jedis j = getShard(key);
return j.set(key, value);
}
而getShard方法则在Sharded.java中实现,其源代码如下所示:
public R getShard(byte[] key) {
return resources.get(getShardInfo(key));
}
public R getShard(String key) {
return resources.get(getShardInfo(key));
}
public S getShardInfo(byte[] key) {
SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
if (tail.isEmpty()) {
return nodes.get(nodes.firstKey());
}
return tail.get(tail.firstKey());
}
public S getShardInfo(String key) {
return getShardInfo(SafeEncoder.encode(getKeyTag(key)));
}
可以看到,先通过getShardInfo方法从TreeMap中获得对应的ShardInfo,然后根据这个ShardInfo就能够再LinkedHashMap中获得对应的Jedis实例了。
Jedis的Sharded源代码分析的更多相关文章
- Jedis的JedisSentinelPool源代码分析
概述 Jedis是Redis官方推荐的Java客户端,更多Redis的客户端可以参考Redis官网客户端列表.Redis-Sentinel作为官方推荐的HA解决方案,Jedis也在客户端角度实现了对S ...
- android-plugmgr源代码分析
android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...
- Twitter Storm源代码分析之ZooKeeper中的目录结构
徐明明博客:Twitter Storm源代码分析之ZooKeeper中的目录结构 我们知道Twitter Storm的所有的状态信息都是保存在Zookeeper里面,nimbus通过在zookeepe ...
- 转:SDL2源代码分析
1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...
- 转:RTMPDump源代码分析
0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...
- 转:ffdshow 源代码分析
ffdshow神奇的功能:视频播放时显示运动矢量和QP FFDShow可以称得上是全能的解码.编码器.最初FFDShow只是mpeg视频解码器,不过现在他能做到的远不止于此.它能够解码的视频格式已经远 ...
- UiAutomator源代码分析之UiAutomatorBridge框架
上一篇文章<UIAutomator源代码分析之启动和执行>我们描写叙述了uitautomator从命令行执行到载入測试用例执行測试的整个流程.过程中我们也描写叙述了UiAutomatorB ...
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...
- hostapd源代码分析(三):管理帧的收发和处理
hostapd源代码分析(三):管理帧的收发和处理 原文链接:http://blog.csdn.net/qq_21949217/article/details/46004379 这篇文章我来讲解一下h ...
随机推荐
- Hibernate3中将指定的HQL语句转换成SQL语句
import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.hql.ast.QueryTranslatorI ...
- hadoop 2.2.0的datanode中存储block的多个文件夹的负载均衡问题
hadoop的分布式文件系统HDFS的存储方式是,将数据分成block,分布式存储在整个hadoop集群的datanode中,每个block默认的大小是64M,这些block文件的具体存储位置是在ha ...
- lintcode:哈希函数
题目: 哈希函数 在数据结构中,哈希函数是用来将一个字符串(或任何其他类型)转化为小于哈希表大小且大于等于零的整数.一个好的哈希函数可以尽可能少地产生冲突.一种广泛使用的哈希函数算法是使用数值33,假 ...
- [topcoder]LongLongTripDiv2
http://community.topcoder.com/stat?c=problem_statement&pm=13091 解方程,对中国孩子太简单了. #include <vect ...
- JavaWeb项目开发案例精粹-第2章投票系统-005实体层
1. package com.sanqing.bean; /** * * 投票选项类 * */ public class VoteOption { private int voteOptionID; ...
- WCF入门(十二)---WCF异常处理
WCF服务开发者可能会遇到需要以适当的方式向客户端报告一些不可预见的错误.这样的错误,称为异常,通常是通过使用try/catch块来处理,但同样,这是非常具体的技术. 由于客户端的关注领域不是关于如何 ...
- Generic repository pattern and Unit of work with Entity framework
原文 Generic repository pattern and Unit of work with Entity framework Repository pattern is an abstra ...
- 模拟在table中移动鼠标,高亮显示鼠标所在行
在项目中有这样一个需求,在table中移动鼠标时,鼠标所在行高亮显示,其他行正常显示,为此做了一个模拟. 具体代码如下: <!DOCTYPE html> <html xmlns=&q ...
- arcgis engine 开发教程系列
版权声明: <ArcGIS Engine+C#实例开发教程>为3SDN(http://www.3sdn.net)原创教程,版权所有.禁止商业用途转载(如需请联系作者),非商业 ...
- kafka的环境搭建
kafka是一个高吞吐量的消息系统.隔离消息接收和处理过程(可理解为一个缓存) 1.kafka伪分布的部署 1.1.下载并解压 1.2.启动zk bin/zookeeper-server-start. ...