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

最近工作中一直在用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这个方法也是这个类里面最重要的一个方法

@Override
public 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);
}
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());
}
}


上面的初始化动作做完之后这样在后面做缓存的时候或者从缓存中取值的时候都会首先通过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_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add

socket.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源码的一点简单理解,还需要后续的深入学习使用,欢迎各位拍砖。




Jedis使用总结 前段时间细节的了解了Jedis的使用,Jedis是redis的java版本的客户端实现。 本文做个总结,主要分享如下内容: 【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】 好了,一个一个来。 一、 Pipeline 官方的说明是:starts a pipeline,which is a very efficient way to send lots of command and read all the responses when you finish sending them。简单点说pipeline适用于批处理。当有大量的操作需要一次性执行的时候,可以用管道。 示例: Jedis jedis = new Jedis(String, int); Pipeline p = jedis.pipelined(); p.set(key,value);//每个操作都发送请求给redis-server p.get(key,value); p.sync();//这段代码获取所有的response 这里我进行了20w次连续操作(10w读,10w写),不用pipeline耗时:187242ms,用pipeline耗时:1188ms,可见使用管道后的性能上了一个台阶。看了代码了解到,管道通过一次性写入请求,然后一次性读取响应。也就是说jedis是:request response,request response,...;pipeline则是:request request... response response的方式。这样无需每次请求都等待server端的响应。 二、 跨jvm的id生成器 谈到这个话题,首先要知道redis-server端是单线程来处理client端的请求的。 这样来实现一个id生成器就非常简单了,只要简单的调用jdeis.incr(key);就搞定了。 你或许会问,incr是原子操作吗,能保证不会出现并发问题吗,不是说了吗,server端是单线程处理请求的。 三、 【跨jvm的锁实现【watch】【multi】】 首先说下这个问题的使用场景,有些时候我们业务逻辑是在不同的jvm进程甚至是不同的物理机上的jvm处理的。这样如何来实现不同jvm上的同步问题呢,其实我们可以基于redis实现一个锁。 具体事务和监听请参考文章:redis学习笔记之事务 暂时找到三种实现方式: 1. 通过jedis.setnx(key,value)实现 import java.util.Random; import org.apache.commons.pool.impl.GenericObjectPool.Config; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; /** * @author Teaey */ public class RedisLock { //加锁标志 public static final String LOCKED = "TRUE"; public static final long ONE_MILLI_NANOS = 1000000L; //默认超时时间(毫秒) public static final long DEFAULT_TIME_OUT = 3000; public static JedisPool pool; public static final Random r = new Random(); //锁的超时时间(秒),过期删除 public static final int EXPIRE = 5 * 60; static { pool = new JedisPool(new Config(), "host", 6379); } private Jedis jedis; private String key; //锁状态标志 private boolean locked = false; public RedisLock(String key) { this.key = key; this.jedis = pool.getResource(); } public boolean lock(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { if (jedis.setnx(key, LOCKED) == 1) { jedis.expire(key, EXPIRE); locked = true; return locked; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } public boolean lock() { return lock(DEFAULT_TIME_OUT); } // 无论是否加锁成功,必须调用 public void unlock() { try { if (locked) jedis.del(key); } finally { pool.returnResource(jedis); } } } 2. 通过事务(multi)实现 由于采纳第一张方法,第二种跟第三种实现只贴了关键代码,望谅解。^_^ public boolean lock_2(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { Transaction t = jedis.multi(); // 开启事务,当server端收到multi指令 // 会将该client的命令放入一个队列,然后依次执行,知道收到exec指令 t.getSet(key, LOCKED); t.expire(key, EXPIRE); String ret = (String) t.exec().get(0); if (ret == null || ret.equals("UNLOCK")) { return true; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 3. 通过事务+监听实现 public boolean lock_3(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { jedis.watch(key); // 开启watch之后,如果key的值被修改,则事务失败,exec方法返回null String value = jedis.get(key); if (value == null || value.equals("UNLOCK")) { Transaction t = jedis.multi(); t.setex(key, EXPIRE, LOCKED); if (t.exec() != null) { return true; } } jedis.unwatch(); // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 最终采用第一种实现,因为加锁只需发送一个请求,效率最高。 四、 【redis分布式】 最后一个话题,jedis的分布式。在jedis的源码里发现了两种hash算法(MD5,MURMUR Hash(默认)),也可以自己实现redis.clients.util.Hashing接口扩展。 List hosts = new ArrayList(); //server1 JedisShardInfo host1 = new JedisShardInfo("", 6380, 2000); //server2 JedisShardInfo host2 = new JedisShardInfo("", 6381, 2000); hosts.add(host1); hosts.add(host2); ShardedJedis jedis = new ShardedJedis(hosts); jedis.set("key", "");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值