基于Netty的Redis客户端-Nedis

最近温习了一遍Redis命令,忧伤的是很多东西已交还给老师,正好赶上antirez大神在愚人节发布了Redis
3.0,Redis终于有了支持集群的正式版本,于是心血来潮决定自己实现一个Redis客户端来抚慰我这颗忧伤的心灵。

Jedis已经足够强大,它的网络连接是基于阻塞式IO,实现非常简单易懂,但是OIO和NIO相比性能上有劣势,于是决定通过NIO来实现和Redis服务器的网络连接,现在业界最优秀的NIO框架非Netty莫属了,正好以前也学过Netty框架,所以决定基于Netty来实现这个Redis客户端,这样还可以同时再次熟悉一下Netty,于是一个高大上的名字新鲜出炉-Nedis。关于命令的实现就没什么好纠结,完全参照Redis官方文档来就可以了,也可以参考Jedis代码。

由于本码农平时工作比较忙,在公司工作时是不可能抽时间来搞的,一没时间,大佬们各种催活,二是由于公司的信息安全政策,公司里面写的代码是拿不出来的。所以只有利用晚上下班时间和周末的业余时间来搞,工作日有2-3个小时的时间,大概10点开搞,到1点左右,周末由于要带娃做饭,也只能挤出3-4个小时出来,所以进展比较慢,从4月初到现在将近20天的时间终于完成了key、string、hash、list、set、
SortedSet的所有单机命令以及客户端分片(Sharding),其它的事务、lua脚本、集群等功能还未实现,留到后面版本再实现。

框架组件

首先最优先的是要确定代码的基本骨架,骨架确定之后各个命令的实现就纯粹是工作量上的事了,事实上这种工作非常的机械。经过多番的修改重构最终确定了代码骨架,命令的基本流程,下面是Nedis中几个基础的组件:

  • NedisClient和ShardedNedis:该框架中的两个门面,分别处理单机命令和客户端key分片,对使用提供了所有命令的调用接口,由于事务和集群等功能还没有提供,所有未来可能会增加TransationNedis和ClusterNedis等。所有命令最终都会经过Nedis转发,SharedNedis最终也会调用NedisClient。NedisClient的构建采用build模式,通过NedisClientBuilder来构建,关于客户端参数,目前设计得比较粗糙,参数较少,目前可以给NedisClient设置如下参数:
参数 左右
connectTimeoutMills 连接超时时间
eventLoopGroupSize Netty框架的线程池大小
tcpNoDelay 是否设置TCP_NO_DELAY标识
connectionPoolSize 连接池大小
maxConnectionIdleTimeInMills 空闲连接超时时间
minIdleConnections 最小空闲连接数量
  • ConnectionPool:为了减少重复建连开销,采用连接池复用连接,命令最终通过连接池中的连接发送,通过IdleStateHandler监控空闲的连接。
  • ProtocolDecoder:读取命令响应,对响应进行解析,继承自ReplayingDecoder处理分包传输,命令响应的解析最终委托给RedisProtocol进行处理。
  • ResponseReceiver:接收ProtocolDecoder的响应解析结果,把结果交给ResponeAdapter进行适配,并把最终适配后的结果通过ResponseCallback回调返回给命令调用者,处理完成之后最终把连接返回到连接池。
  • ResponeAdapter:把ProtocolDecoder解析出来的结果适配成对使用者比较友好的类型。
  • ResponseCallback:因为Netty是一个异步处理框架,所以我们提供的命令接口不会阻塞直到命令返回(这样就无法体现NIO和Netty的优势了),而是在命令响应达到时通过ResponseCallback回调通知调用者。

下面是命令发送和响应的时序:

测试

就目前已完成的代码来讲,Nedis中的单测代码远远超出了框架代码,目前的单测代码8000行左右而框架代码只有4000左右,而且最关键的是由于时间有限,测试代码并没有覆盖所有路径,有些命令特别是分片相关的命令接口还没写单测(那些没完成的单元测试代码只有等后面慢慢补上),所以覆盖率估计50%都不到,也就是说正常的比例单元测试代码:框架代码应该大于4:1,是不是很震惊,但是测试代码是必须要写的,这是一件一劳永逸的事儿,有了单测代码后面改代码时很容易验证修改的代码有没有问题会不会引发其它问题,当然前提是测试代码要可靠。

对我们的命令测试,有两个问题:

  • 在测试命令时可能要依赖其它命令提供数据,比如我想测一个get命令,那么在之前需要通过set命令构造数据来支持get命令的测试,这就要求在get命令执行之前必须得先执行set命令,但是由于Netty框架通过线程池来执行任务,set命令和get命令可能会由不同的线程来执行,这样的话命令执行可能会乱序,即使set命令再get命令之前调用,也不能完全保证set命令先到达服务器,所有在调用set命令接口需要做一个小停顿再调用get命令接口,来保证set命令在get命令之前执行,这个停顿时间一般100ms就够了(我设的是200ms)。
  • 因为Netty框架是异步的,所有调用命令接口时不会发生阻塞,所以为了验证测试效果,需要保证单测方法在响应回来之前不会结束,我们通过一个同步控制器CountDownLatch,在方法结束前调用await方法进行阻塞,在最后一个响应回来时调用countDown释放,嗯,灰常不优雅。

下面是一个测试方法,controller就是上面说的CountDownLatch,CMD_PAUSE_TIME是停顿时间,它是一个放在基类中的常量,可以随便修改,修改之后所有的测试方法都会生效:

@Test
public void testSet() {

	doCmdTest(new TestAction() {

		@Override
		public void doTest() throws InterruptedException, NedisException {
			client.flushAll(null);
			Thread.sleep(CMD_PAUSE_TIME);
			client.set(new ResponseCallback<String>() {

				@Override
				public void done(String result) {
					assertEquals("OK", result);
				}

				@Override
				public void failed(Throwable cause) {
					fail(cause);
				}
			}, "key1", "value1");

			Thread.sleep(CMD_PAUSE_TIME);
			client.set(new ResponseCallback<String>() {

				@Override
				public void done(String result) {
					assertEquals("OK", result);
					controller.countDown();
				}

				@Override
				public void failed(Throwable cause) {
					fail(cause);
					controller.countDown();
				}
			}, "key1", "value2");

		}
	});

}

分享

本着人人为我我为人人,框架代码已经上传到github:https://github.com/chenyihan/nedis,代码需要有JDK7进行编译,接下来还会实现剩余的功能。非常惭愧从github上搞到过很多宝贵的资源,但是迄今为止只共享过两份代码,以后一定要为github多多贡献,大家共同进步。

时间: 2024-10-10 16:25:28

基于Netty的Redis客户端-Nedis的相关文章

一文彻底理解Redis序列化协议,你也可以编写Redis客户端

前提 最近学习Netty的时候想做一个基于Redis服务协议的编码解码模块,过程中顺便阅读了Redis服务序列化协议RESP,结合自己的理解对文档进行了翻译并且简单实现了RESP基于Java语言的解析.编写本文的使用使用的JDK版本为[8+]. RESP简介 Redis客户端与Redis服务端基于一个称作RESP的协议进行通信,RESP全称为Redis Serialization Protocol,也就是Redis序列化协议.虽然RESP为Redis设计,但是它也可以应用在其他客户端-服务端(C

Netty学习——基于netty实现简单的客户端聊天小程序

Netty学习——基于netty实现简单的客户端聊天小程序 效果图,聊天程序展示 (TCP编程实现) 后端代码: package com.dawa.netty.chatexample; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEven

Redisson Redis 客户端

Redisson 2.3.0 发布了,勤快学qkxue.net发现Redisson 是基于 Redis 服务之上构建的分布式.可伸缩的 Java 数据结构,高级的 Redis 客户端. 该版本更新内容如下: Feature - new service added RExecutorService. More info about it hereFeature - new service added RScheduledExecutorService. More info about it her

基于Netty的聊天系统(三)协议定制----消息篇

今天我们继续来讨论协议,今天基本就把一对一聊天的协议定制完毕了,上一篇我们讲述了登录的过程,那么登录完毕就是聊天了,首先我们还是以A和B为例子,A发送消息给B,那么这条消息的的协议如下 发送消息协议: {"id":"xxxx","#":"msg","text":"内容","to":"接收用户ID","type":0,"

《Netty Zookeeper Redis 高并发实战》 图书简介

<Netty Zookeeper Redis 高并发实战> 图书简介 ## 重要的重复3遍: 本书 面试必备 + 面试必备 + 面试必备 购买链接 京东商城<Netty Zookeeper Redis 高并发实战 > <Netty Zookeeper Redis 高并发实战> 图书简介 机械工业出版社出版,尼恩编著的<Netty Zookeeper Redis 高并发实战>一书, 从操作系统底层的IO原理入手,同时提供高性能开发的实战案例,是一本高并发Jav

《Java 编写基于 Netty 的 RPC 框架》

一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞I/O在调用InputStream.read()方法是阻塞的,一直等到数据到来时才返回,同样ServerSocket.accept()方法时,也是阻塞,直到有客户端连接才返回,I/O通信模式如下: 缺点:当客户端多时,会创建大量的处理线程,并且为每一个线程分配一定的资源;阻塞可能带来频繁切换上下文,

用Netty解析Redis网络协议

用Netty解析Redis网络协议 根据Redis官方文档的介绍,学习了一下Redis网络通信协议.然后偶然在GitHub上发现了个用Netty实现的Redis服务器,很有趣,于是就动手实现了一下! 1.RESP协议 Redis的客户端与服务端采用一种叫做 RESP(REdis Serialization Protocol)的网络通信协议交换数据.RESP的设计权衡了实现简单.解析快速.人类可读这三个因素.Redis客户端通过RESP序列化整数.字符串.数据等数据类型,发送字符串数组表示参数的命

基于Netty构建高性能的部标808协议的GPS服务器

使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万台车载接入是两码事,除去开发部标808协议的固有复杂性和几个月长周期的协议Bug调试,作为大批量794车载终端接入的服务端,需要能够处理网络的闪断.客户端的重连.安全认证和消息的编解码.半包处理等.如果没有足够的网络编程经验积累和深入了解部标808协议文档,自研的GPS服务器往往需要半年甚至数年的时间才能最终稳定下来,这种成本即便对一个大公司而言也是个严重的挑战.

基于Netty的聊天系统(一)通讯原理篇

今天周六,正好顺便把聊天系统的通讯原理写一下,本来是用XMPP+Openfire做了一个聊天,但是在做群聊那块需要去写插件来主动向表里变去写数据,因为openfire外国人写的,最初设计的群聊是会议室那种形式,和我们现在这种QQ群聊还是有差别的,改造起来比较麻烦,需要去通都源码等等,openfire是基于mina来写的,mina和netty又出自同一作者之手,那么我们就基于netty来写一个吧,首先我们来谈一谈通讯的原理 1:通讯原理 首先比方说A和B两个人要进行聊天(这里我们先讨论一对一这种聊