Redis的应用已经如火如荼了,你要是搞服务端的,说你还没用过,一些人又要从心里鄙视你n遍了。刚好在项目中实践了,有一点点心得,在这里跟大家交流一下。由于时间的关系,Redis的源码还未读完,今天先把客户端jedis的源码研究一下吧。看完代码后其实你可以自己实现一个了。代码一定要剖析到每一行,吸取精华才算凑效。jedis,在各种客户端中算比较优秀的。代码风格也很好,读来如沐春风,心旷神怡啊:)
1.Redis的通信协议
Redis采用自定义的二进制通信协议。有一个基本规范
发送命令规范:
<参数个数>\r\n
$<参数1字节数>\r\n
<参数1>\r\n
...
$<参数n字节数>\r\n
<参数n>\r\n
响应规范
响应类型有返回数据的第一个字节决定的
+代表一个状态信息,+ok
-代表错误
:返回的是一个整数
$返回一个块数据,跟发送命令的规范一样。$<长度>\r\n<数据>\r\n
*返回多个数据块。同上,后面跟着参数的个数和每个参数的数据。
2.jedis的整体架构
解剖jedis架构之前,先自己尝试设计一个客户端。如果用最简单的方式,你会如何设计?
用最简单的Socket就可以实现了,socket跟服务端通信后,就可以按上面的格式发命令了。然后阻塞获得服务端返回的数
据。听起来很简单吧。
实际上jedis也是这样实现的客户端连接数很大,怎么办?每次新建一个socket,开销会成线性增长。我们想到了连接池方案。先新建一批连接,使用时从连接池取一个,用完再回收。连接池实现起来有多复杂?用 apache 的连接池接口,可以简化开发成本。
一台机容量有限,想将数据存储到多个节点。有很多种方案啦。在业务层面,我们可以将不同的业务数据存储在不同的节点(水平分割)。根据用户容量分割到不同的节点(纵向分割)。有没有通用一点的,我们想到的"一致性hash”方案。根据key,定位到目标节点,存储相关的数据。
吹了那么多,其实上面就是jedis的核心了。其他的只是处理各种的命令返回值而已了
3.源代码剖析
连接redis,非常简单,跟传统的socket通信一样。new一个socket,设置相关的参数,connect获取相关的输入输出流进行通信。它的特别之处在于,封装了这些流,方便操作
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);
}
}
}
发送命令,就是按照上面的所说的通信协议发送相关的命令
要注意的是要将字符串转成二进制流
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本身是单线程阻塞的。所以你可以从socket的InputStream中直接读取数据。
获得二进制流,根据不同的命令,将它转成相应的数据类型即可
private static Object process(final RedisInputStream is) {
try {
byte b = is.readByte();
if (b == MINUS_BYTE) {
processError(is);//报错
} else if (b == ASTERISK_BYTE) {
return processMultiBulkReply(is);//*多个数据块
} else if (b == COLON_BYTE) {
return processInteger(is);//:整数
} else if (b == DOLLAR_BYTE) {
return processBulkReply(is);//$一个数据块
} else if (b == PLUS_BYTE) {
return processStatusCodeReply(is);//+状态
} else {
throw new JedisConnectionException("Unknown reply: " + (char) b);
}
} catch (IOException e) {
throw new JedisConnectionException(e);
}
return null;
}
连接池的实现。现在要实现连接池,一般都是用apache的pool接口来实现了。只需要两个步骤。
1.实现Config 就是连接池的配置参数,包括最大连接数,初始连接数,空闲时间.
2.实现 BasePoolableObjectFactory,提供新建连接,废弃连接,测试连接方法
public Object makeObject() throws Exception {//产生redis实例
final Jedis jedis = new Jedis(this.host, this.port, this.timeout);
jedis.connect();
if (null != this.password) {
jedis.auth(this.password);
}
if( database != 0 ) {
jedis.select(database);
}
return jedis;
}
public void destroyObject(final Object obj) throws Exception {//销毁jedis实例
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
if (jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) {
}
}
}
}
public boolean validateObject(final Object obj) {//验证jedis是否有效,简单的ping命令
if (obj instanceof Jedis) {
final Jedis jedis = (Jedis) obj;
try {
return jedis.isConnected() && jedis.ping().equals("PONG");
} catch (final Exception e) {
return false;
}
} else {
return false;
}
}
一致性hash的实现。关键是如何使得key均匀散列。jedis使用了murmurhash 2.0
public R getShard(String key) {
return resources.get(getShardInfo(key));
}
public S getShardInfo(byte[] key) {
SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));//找到key所在的节点
if (tail.size() == 0) {
return nodes.get(nodes.firstKey());
}
return tail.get(tail.firstKey());
}
4.经验教训
这里谈谈实践中遇到的几个问题,都是血和泪的教训啊。
1.jedis的连接池获得的连接,进行通信时候出错了,一定要记得销毁该连接。因为它的inputstream里面可能还残留数据。下次从连接池获得的时候都是dirty data了。一般采用以下的方案:
try{
//do something
pool.returnConnection;//返回正常连接
}catch(Exception e){
pool.returnBrokenConnection;//销毁连接
}
2.使用一致性hash的时候,使用批量查询命令mget的时候,ShardedJedis本身不支持的,只能用一个个key去取数据,性能低下。有一个比较土的办法。先将key对应的节点分类合并,然后单独用mget去获取数据,再将返回值合并给用户。可以显著减少网络连接。
5.总结
jedis简单,高效,本身代码也可以当做一个网络客户端的典型实现范例。
卖个小广告:个人网站已经开通,里面有不少水货和干货,欢迎多多指点:)
分享到:
相关推荐
Jedis是Java语言中使用最广泛的Redis客户端库,它提供了丰富的API来操作Redis服务器。在这个"jedis-2.9.0"的最新版本中,我们包含了三个重要的文件: 1. `jedis-2.9.0.jar`:这是Jedis的二进制发行版,包含了所有...
4. **Jedis**:Jedis是Java语言的Redis客户端库,它提供了丰富的API,使Java开发者能够方便地与Redis进行交互。Jedis支持各种Redis命令,包括数据类型操作、事务处理、发布/订阅、持久化等。在实际项目中,通过Jedis...
Common-Pool2-source.rar则包含了Common-Pool2的源代码,开发者可以通过阅读源码来了解其内部工作原理,进行更深入的定制或优化。 在使用Redis3.2+jedis2.8.jar进行Java开发时,首先需要在项目中引入Jedis库,配置...
在本压缩包"redis-desktop-client-master.zip"中,包含的是Redis Desktop Client的源代码或构建版本。 Redis Desktop Client提供了图形用户界面(GUI),使得开发者可以更直观、便捷地进行键值操作,查看数据,执行...
这个压缩包“jedis-jedis-2.2.0.tar.gz”包含了Jedis 2.2.0版本的所有源代码、文档和其他相关资源。在这个版本中,Jedis提供了与Redis服务器通信的各种功能,包括基本的数据操作、事务处理、发布订阅、脚本执行等。 ...
首先,我们需要从Redis官方网站或者镜像站点下载Redis 4.0.2的源代码包。下载完成后,解压文件,一般命令如下: ```bash wget http://download.redis.io/releases/redis-4.0.2.tar.gz tar -zxvf redis-4.0.2.tar.gz...
Jedis是Java开发的一款高效的Redis客户端库,广泛用于与Redis服务器进行交互,提供了一系列API来操作Redis中的数据结构,如字符串、哈希、列表、集合、有序集合等。`jedis-jedis-1.5.0-RC1.zip`是一个包含Jedis ...
首先,从 Redis 官方网站下载最新稳定版的 Redis 源代码或预编译二进制包。解压缩后,通常包含以下文件: 1. `redis-server`: 这是 Redis 服务器的主进程,负责处理客户端的请求。 2. `redis-cli`: 一个命令行工具...
- 使用Redis客户端库,如StackExchange.Redis,这是一套.NET Framework和.NET Core下的完整Redis客户端,支持多种操作和高级特性。 - 通过命令行工具"redis-cli.exe"直接发送命令给Redis服务器,进行数据操作和测试...
Jedis是Java开发的一款高效的Redis客户端,用于与Redis服务器进行通信。这个压缩包"jedis-jedis-1.5.0-RC1.tar.gz"包含了Jedis 1.5.0 Release Candidate 1版本的源代码和相关资源。在本文中,我们将深入探讨Jedis的...
这个版本可能包含源代码、编译后的类库、文档以及示例代码等资源,方便开发者在项目中集成和使用Jedis。 1. **Jedis简介**: Jedis是开源的Redis客户端,由Pascal Lesier开发,最初发布于2009年。它支持多种Redis...
Jedis是Java开发的一款高效的Redis客户端库,广泛用于与Redis服务器进行交互,支持各种Redis命令。版本2.8.2是其稳定版本之一,提供了丰富的功能和改进。在这个压缩包"jedis-jedis-2.8.2.tar.gz"中,包含了Jedis库的...
Jedis是Java开发人员广泛使用的Redis客户端,用于操作和管理Redis键值存储系统。在这个项目中,“fac-datapoint”可能是指数据采集或数据点的处理,暗示着它涉及到数据的存取、分析或者处理。 描述虽然简洁,但我们...
Jedis是Java开发的一款高效的Redis客户端,用于与Redis服务器进行通信。版本2.8.0是Jedis的一个稳定版本,提供了丰富的Redis操作功能,并优化了性能和稳定性。在这个压缩包"jedis-jedis-2.8.0.tar.gz"中,包含了...
"jedis-jedis-1.3.1.tar"则是解压后的文件名,通常包含源代码、文档和其他相关资源。 Redis是一个开源的,基于键值对的数据存储系统,常被用作数据库、缓存和消息中间件。Jedis作为其Java客户端,提供了丰富的API,...
Jedis是Java开发的一款高效的Redis客户端,用于连接和操作Redis服务器。版本2.5.0是Jedis的一个稳定版本,提供了丰富的Redis命令支持,适用于多种数据结构操作,如字符串、哈希、列表、集合和有序集合。在本压缩包...
在解压"jedis-jedis-2.6.2.tar.gz"后,你会得到一个名为"jedis-jedis-2.6.2"的目录,其中包含了Jedis库的源代码、文档、示例以及编译后的jar文件。开发者可以通过阅读源码学习Jedis的设计思路,参考示例了解如何在...
Jedis是Java开发的一款高效的Redis客户端库,广泛用于与Redis服务器进行交互,提供了一系列API来操作Redis中的数据结构,如字符串、哈希、列表、集合、有序集合等。标题"jedis-jedis-3.6.0.tar.gz"表明这是Jedis的一...
Jedis是Java开发的一款高效的Redis客户端库,广泛用于与Redis服务器进行交互,提供了一系列API来操作Redis中的数据结构,如字符串、哈希、列表、集合、有序集合等。标题"jedis-jedis-3.1.0-rc.zip"表明这是一个关于...
这个文件"jedis-jedis-3.2.0.tar"包含的是Jedis库的源代码、文档、测试用例等相关资源。 Redis是一个开源的、基于键值对的数据存储系统,常用于实现数据缓存、消息队列、主从复制等多种功能。Jedis作为Redis的Java...