redis源码解读总结(redis一致性哈希实现)

来源:互联网 发布:数据库原理第五版 编辑:程序博客网 时间:2024/06/05 11:15
最近工作中一直在用redis进行缓存功能的实现,redis的源码虽然只有一万多行,但是确实值得研究一下,以下个人的一点研究和看法(本来打算用图表示,实在找不到一种好的画图工具来描述,因此就用文字描述了),希望能跟各位共勉之。
一、1.构建JedisShardInfo列表List<JedisShardInfo> jedisShardInfoList,其中JedisShardInfo包含了服务器IP,端口等信息,其源码如下:

public JedisShardInfo(String host, int port, String name) {this(host, port, 2000, name);}


2.构建GenericObjectPoolConfig poolConfig这个里面主要设置缓存池的配置:最大连接数,最大等待时间等配置信息。
3.基于1和2去构建ShardedJedisPool对象,主要源码如下:

public ShardedJedisPool(final GenericObjectPoolConfig poolConfig,List<JedisShardInfo> shards) {this(poolConfig, shards, Hashing.MURMUR_HASH);}


构建此对象的过程中,同时构建了一个ShardedJedisFactory对象,这个对象很重要,后续会有详细讲解,构建此对象源码如下:

public ShardedJedisPool(final GenericObjectPoolConfig poolConfig,List<JedisShardInfo> shards, Hashing algo, Pattern keyTagPattern) {super(poolConfig, new ShardedJedisFactory(shards, algo, keyTagPattern));}


调用父类构造函数:

public Pool(final GenericObjectPoolConfig poolConfig,PooledObjectFactory<T> factory) {initPool(poolConfig, factory);}


调用初始化池initPool()方法,下面的代码只留关键源码:

public void initPool(final GenericObjectPoolConfig poolConfig,PooledObjectFactory<T> factory) {this.internalPool = new GenericObjectPool<T>(factory, poolConfig);}


上面的代码将之前说的一个非常重要的ShardedJedisFactory对象,装载到了GenericObjectPool类里面。
以上的所有信息都是为了后面的重要操作,像各种数据结构的操作,做铺垫。
二、在"一"的基础上进行以下操作,上面已经构建了ShardedJedisPool
1.调用ShardedJedisPool的getResource方法返回ShardedJedis进行get或者set操作

public T getResource() {return internalPool.borrowObject();}


此处的internalPool对象即为1中最后的GenericObjectPool这个类的对象实例,最终会调用到此类中的create方法

private PooledObject<T> create() throws Exception {final PooledObject<T> p;try {p = factory.makeObject();} catch (Exception e) {createCount.decrementAndGet();throw e;}return p;}


上面说过ShardedJedisFactory这个对象非常重要就在于此,factory.makeObject()操作中的factory就是
ShardedJedisFactory这个类,之前已经装载进去了,上面有说明;makeObject这个方法也是这个类里面最重要的一个方法

@Overridepublic PooledObject<ShardedJedis> makeObject() throws Exception {ShardedJedis jedis = new ShardedJedis(shards, algo, keyTagPattern);return new DefaultPooledObject<ShardedJedis>(jedis);}

makeObject这个方法(1)第一步就是构建了一个ShardedJedis对象,就是调用getResource()方法返回的对象,这个对象包含对数据结构的各种操作,
通过构造函数ShardedJedis(shards, algo, keyTagPattern)调用父类的构造方法,然后调用initialize方法,这个初始化方法就是redis中使用一致性哈希的
地方所在,循环shards(此处的shards就是上面的jedisShardInfoList这个变量,比如说list的size为4,相当于有四台服务器),每一个shards设置160个
节点,n台服务器就会有n*160个节点,节点的key值就是通过Hashing计算出来,这些节点其实就是已经被均匀的分部到一个闭合的
环中了,这样服务器多话就会增加利用率;nodes设置完之后,就会将n个shards与对应的jedis设置到MAP中去了:

public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) {this.algo = algo;this.tagPattern = tagPattern;initialize(shards);}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);}elsefor (int n = 0; n < 160 * shardInfo.getWeight(); n++) {nodes.put(this.algo.hash(shardInfo.getName() + "*"+ shardInfo.getWeight() + n), shardInfo);}resources.put(shardInfo, shardInfo.createResource());}}


上面的初始化动作做完之后这样在后面做缓存的时候或者从缓存中取值的时候都会首先通过key调用Hashing类中相同的算法计算出一致性哈希值,计算出
哈希值之后,根据TreeMap的tailMap方法从上面的nodes中获取到大于或者等于这个哈希值的SortedMap,然后获取第一个key以及对应的shardInfo,
最终会从resources中获取到jedis:

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 String set(String key, String value) {Jedis j = getShard(key);return j.set(key, value);}

(2)第二步构建了DefaultPooledObject对象,通过此对象返回ShardedJedis,这个对象就是第一步要做的事情。

2.通过上面获取到的ShardedJedis进行数据的缓存或者获取,现举一例进行简单说明之,调用set方法,首先根据上面说的一致性哈希算法获取到jedis
jedis里面定义了一个客户端Client:



调用Jedis的set方法,首先完成客户端的连接,然后发送命令,进行数据的缓存:

protected Connection sendCommand(final Command cmd, final byte[]... args) {connect();Protocol.sendCommand(outputStream, cmd, args);pipelinedCommands++;return this;}public void connect() {if (!isConnected()) {try {socket = new Socket();// ->@wjw_addsocket.setReuseAddress(true);socket.setKeepAlive(true); // Will monitor the TCP connection is// validsocket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to// ensure timely delivery of datasocket.setSoLinger(true, 0); // Control calls close () method,// the underlying socket is closed// immediately// <-@wjw_addsocket.connect(new InetSocketAddress(host, port), timeout);socket.setSoTimeout(timeout);outputStream = new RedisOutputStream(socket.getOutputStream());inputStream = new RedisInputStream(socket.getInputStream());} catch (IOException ex) {throw new JedisConnectionException(ex);}}}public static void sendCommand(final RedisOutputStream os,final Command command, final byte[]... args) {sendCommand(os, command.raw, args);}private static void sendCommand(final RedisOutputStream os,final byte[] command, final byte[]... args) {try {os.write(ASTERISK_BYTE);os.writeIntCrLf(args.length + 1);os.write(DOLLAR_BYTE);os.writeIntCrLf(command.length);os.write(command);os.writeCrLf();for (final byte[] arg : args) {os.write(DOLLAR_BYTE);os.writeIntCrLf(arg.length);os.write(arg);os.writeCrLf();}} catch (IOException e) {throw new JedisConnectionException(e);}}

三、总结:以上就是本人对redis源码的一点简单理解,还需要后续的深入学习使用,欢迎各位拍砖。




0 0
原创粉丝点击