Netty开发redis客户端,Netty发送redis命令,netty解析redis消息

时间:2022-08-30 12:58:47

关键字:Netty开发redis客户端,Netty发送redis命令,netty解析redis消息, netty redis ,redis RESP协议。redis客户端,netty redis协议

我们可以使用redis-cli这个客户端来操作redis,也可以使用window的命令行telnet连接redis。本文,我们的目标是使用netty来实现redis客户端,实现目标为:

1. 启动netty程序
2. 在命令行输入 set mykey hello,由netty发送给redis服务器
3. 在命令行输入 get mykey hello,得到结果:hello
4. 在命令行输入 quit,程序退出

前言

Redis在TCP端口6379(默认,可修改端口)上监听到来的连接,客户端连接到来时,Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以\r\n结尾。当redis服务启动之后,我们可以使用TCP与之链接,连接之后便可以发消息,也会受到redis服务器的消息。而这个消息是有格式的,这个格式是事先商量好的,我们称之为协议,redis的协议叫做RESP,比方说我们有一条redis命令 set hello 123,这条命令我们知道它是一条设置命令,通过RESP协议“翻译”一下,他就是这样的:

*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$3\r\n123\r\n

然后,这条协议通过网络传输(二进制形式),传到redis服务器,被redis服务器解析,最后完成设置。关于RESP协议的详细介绍可以看这里.

思路

上面我们介绍了redis是基于TCP传输,并使用了其自己的协议——RESP。RESP其实是数据交换可解析的协议,你可以理解为数据交换的格式,按照此格式组装好要传输的命令,并以二进制的形式由client端发往redis服务端。服务端接收这个消息之后,解析消息,执行命令,并将结果以协议好的格式组装好,传输给client端。client端接收到响应,解释成人类可以看懂的结果展示。

因此,我们可以整理一下思路:

1. 我们需要连接redis服务端,因此需要编写一个netty client端(此处联想一下netty client端的样板代码)。
2. 我们需要向redis服务端发送redis命令,很简单,获取channel,然后write。即channel.write(...)
3. 我们所编写的直白的命令,如set xx,get xx之类的需要编码之后才能传输给redis服务器。
因此,我们需要 **编码器**。很荣幸netty自带了,可以直接使用。
这里是 【输出】 所以要有 outbound handler.
4. redis会响应结果给我们,因此我们需要在 chanelRead方法中处理数据。
这里是 【输入】 所以要有 inbound handler.

编写代码

上的思路整理好了之后,我们可以写代码了。得益于netty的良好设计,我们只需要把netty client的“样板代码”拷贝过来生成一个client端代码即可。剩下的就是 handler ,decoder ,encoder 。我们需要编写的类有:

  • RedisClient 见名知义,我们的主类,包含client bootstrap信息。 接收用户控制台输入redis命令。
  • RedisClientInitializer 初始化器,在此添加 handler,decoder,encoder
  • RedisClientHandler 核心逻辑,需要处理 inbound ,outbound 两种类型事件。

RedisClient 代码如下:

public class RedisClient {

    String host;    //   目标主机
int port; // 目标主机端口 public RedisClient(String host,int port){
this.host = host;
this.port = port;
} public void start() throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new RedisClientInitializer()); Channel channel = bootstrap.connect(host, port).sync().channel();
System.out.println(" connected to host : " + host + ", port : " + port);
System.out.println(" type redis's command to communicate with redis-server or type 'quit' to shutdown ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
ChannelFuture lastWriteFuture = null;
for (;;) {
String s = in.readLine();
if(s.equalsIgnoreCase("quit")) {
break;
}
System.out.print(">");
lastWriteFuture = channel.writeAndFlush(s);
lastWriteFuture.addListener(new GenericFutureListener<ChannelFuture>() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
System.err.print("write failed: ");
future.cause().printStackTrace(System.err);
}
}
});
}
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
System.out.println(" bye ");
}finally {
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception{
RedisClient client = new RedisClient("redis-cache2.228",5001);
client.start();
} }

上面代码很长,但是,我们要熟悉netty的套路,它的样板代码就是如此。我们只需要看handler(new RedisClientInitializer()); 这一行,下面的就是一个 for(;;)循环,用来接收我们在控制台输入的redis命令。

RedisClientInitializer代码如下:

public class RedisClientInitializer extends ChannelInitializer<Channel>{

    @Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new RedisDecoder());
pipeline.addLast(new RedisBulkStringAggregator());
pipeline.addLast(new RedisArrayAggregator());
pipeline.addLast(new RedisEncoder());
pipeline.addLast(new RedisClientHandler());
}
}

这个类,很简单,上面的几个addLast方法,除了最后一个外,其他都是netty自带的redis协议实现相关的编解码。最后一个是我们自定义的业务逻辑处理器。源码如下:

public class RedisClientHandler extends ChannelDuplexHandler {

    // 发送 redis 命令
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
String[] commands = ((String) msg).split("\\s+");
List<RedisMessage> children = new ArrayList<>(commands.length);
for (String cmdString : commands) {
children.add(new FullBulkStringRedisMessage(ByteBufUtil.writeUtf8(ctx.alloc(), cmdString)));
}
RedisMessage request = new ArrayRedisMessage(children);
ctx.write(request, promise);
} // 接收 redis 响应数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
RedisMessage redisMessage = (RedisMessage) msg;
// 打印响应消息
printAggregatedRedisResponse(redisMessage);
// 是否资源
ReferenceCountUtil.release(redisMessage);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.err.print("exceptionCaught: ");
cause.printStackTrace(System.err);
ctx.close();
} private static void printAggregatedRedisResponse(RedisMessage msg) {
if (msg instanceof SimpleStringRedisMessage) {
System.out.println(((SimpleStringRedisMessage) msg).content());
} else if (msg instanceof ErrorRedisMessage) {
System.out.println(((ErrorRedisMessage) msg).content());
} else if (msg instanceof IntegerRedisMessage) {
System.out.println(((IntegerRedisMessage) msg).value());
} else if (msg instanceof FullBulkStringRedisMessage) {
System.out.println(getString((FullBulkStringRedisMessage) msg));
} else if (msg instanceof ArrayRedisMessage) {
for (RedisMessage child : ((ArrayRedisMessage) msg).children()) {
printAggregatedRedisResponse(child);
}
} else {
throw new CodecException("unknown message type: " + msg);
}
} private static String getString(FullBulkStringRedisMessage msg) {
if (msg.isNull()) {
return "(null)";
}
return msg.content().toString(CharsetUtil.UTF_8);
} }

注意,上面我们讨论过,我们需要两个handler,分别是inbound handler 和outbound handler 。这里我们使用的是ChannelDuplexHandler。这个ChannelDuplexHandler 支持处理 inbound 和 outbound,其定义如下:

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {
....
}

运行演示

按照开篇的思路分析,上面我们已经编写好了netty redis client所需要的代码。下面我们需要运行看看。main函数如下:

    public static void main(String[] args) throws Exception{
RedisClient client = new RedisClient("your-redis-server-ip",6379);
client.start();
}

我在本地运行了一下,演示了一些命令:

  • get ,set 以及 错误的命令
  • expire命令设置超时时间,及 ttl 命令查看超时时间
  • del 命令删除可以
  • quit退出程序。

结果如下:

 connected to host : 192.168.2.120, port : 6379
type redis's command to communicate with redis-server or type 'quit' to shutdown
get hello
>(null)
set hello
>ERR wrong number of arguments for 'set' command
set hello 123
>OK
expire hello 10
>1
ttl hello
>6
ttl hello
>4
get hello
>(null)
set hello world
>OK
get hello
>world
del hello
>1
quit
bye Process finished with exit code 0

如此,我们便用netty实现了redis的client端。代码下载

如果你觉得还可以,给点个推荐吧!


使用Netty实现HTTP服务器

Netty实现心跳机制

Netty系列

spring如何启动的?这里结合spring源码描述了启动过程

SpringMVC是怎么工作的,SpringMVC的工作原理

spring 异常处理。结合spring源码分析400异常处理流程及解决方法

Mybatis Mapper接口是如何找到实现类的-源码分析

Lua脚本在redis分布式锁场景的运用

redis单点、redis主从、redis集群cluster配置搭建与使用

CORS详解,CORS原理分析

Keep-Alive 是什么?

Netty开发redis客户端,Netty发送redis命令,netty解析redis消息的更多相关文章

  1. 【Azure Redis 缓存 Azure Cache For Redis】使用Redis自带redis-benchmark&period;exe命令测试Azure Redis的性能

    问题描述 关于Azure Redis的性能问题,在官方文档中,可以查看到不同层级Redis的最大连接数,每秒处理请求的性能. 基本缓存和标准缓存 C0 (250 MB) 缓存 - 最多支持 256 个 ...

  2. Redis客户端命令

    Redis客户端命令 Redis 命令用于在 redis 服务上执行操作. 要在 redis 服务上执行命令需要一个 redis 客户端.Redis 客户端在我们之前下载的的 redis 的安装包中. ...

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

    前提 最近学习Netty的时候想做一个基于Redis服务协议的编码解码模块,过程中顺便阅读了Redis服务序列化协议RESP,结合自己的理解对文档进行了翻译并且简单实现了RESP基于Java语言的解析 ...

  4. 用Netty开发中间件:网络编程基础

    用Netty开发中间件:网络编程基础 <Netty权威指南>在网上的评价不是很高,尤其是第一版,第二版能稍好些?入手后快速翻看了大半本,不免还是想对<Netty权威指南(第二版)&g ...

  5. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  6. Java 实现Redis客户端,服务端

    Java 实现Redis客户端,服务端 1.Java实现Redis发布订阅 1.1实例 2.[Redis]Java实现redis消息订阅/发布(PubSub) 3.java实现 redis的发布订阅 ...

  7. 看完这篇文章,再次遇到Jedis「Redis客户端」异常相信你不再怕了!

    本文导读: [1] 疫情当前 [2] 应用异常监控 [3] Redis客户端异常分析 [4] Redis客户端问题引导分析 [5] 站在Redis客户端视角分析 [6] 站在Redis服务端视角分析 ...

  8. netty系列之&colon;使用netty搭建websocket客户端

    目录 简介 浏览器客户端 netty对websocket客户端的支持 WebSocketClientHandshaker WebSocketClientCompressionHandler netty ...

  9. Redis简介以及如何在Windows上安装Redis

    Redis简介 Redis是一个速度非常快的非关系型内存数据库. Redis提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erla ...

  10. Redis学习笔记(一)Windows下redis的安装和启动

    在Windows上安装redis 下载地址:https://github.com/microsoftarchive/redis/releases 选择图中红框标出来的下载,解压到磁盘上,文件夹命名为r ...

随机推荐

  1. 转载:《TypeScript 中文入门教程》 14、输入&period;d&period;ts文件

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 当使用外部JavaScript库或新的宿主API时,你需要一个声明文件(.d.t ...

  2. jsp导出Excel功能的实现

    借助POI的excel接口,可以方便得实现excel导出功能: 首先需要引入poi对应的jar包 1.前端jsp页面需要一个a链接. web页面文件MIM类型的下载,只需要一个a元素,该a可以链到该文 ...

  3. Java常见错误

    1.NullPointerExceptin 空指针异常 a.引用没有初始化就使用 b.引用置空了,仍然被使用 2.IndexOutofBoundsException 下标越界 a.数组下标小于0 或者 ...

  4. 线程间操作无效&colon; 从不是创建控件&OpenCurlyDoubleQuote;label4”的线程访问它。

    //主线程 public delegate void UpdateMessage(string mes); public void UpdatePortMessage(string mes) { th ...

  5. 解决:error&colon; &&num;39&semi;Can&&num;39&semi;t connect to local MySQL server through socket &&num;39&semi;&sol;var&sol;run&sol;mysqld&sol;mysqld&period;sock&&num;39&semi; &lpar;2&rpar;&&num;39&semi;

    在使用 deamon@deamon-H55M-S2:/usr/bin$ mysqladmin -u root -p shutdown 关闭MySQL之后试图通过: deamon@deamon-H55M ...

  6. 使用jeesite org&period;springframework&period;beans&period;NotReadablePropertyException&colon; Invalid property &&num;39&semi;tfxqCmsAccount&period;id&&num;39&semi; of bean class

    一对多 对子表添加时在form表单 path="tfxqCmsAccount.id"页面报错,对比了下其他可行的,发现其自动生成的子类少了个构造方法 加上 public TfxqC ...

  7. POJ 3280 Cheapest Palindrome 简单DP

    观察题目我们可以知道,实际上对于一个字母,你在串中删除或者添加本质上一样的,因为既然你添加是为了让其对称,说明有一个孤立的字母没有配对的,也就可以删掉,也能满足对称. 故两种操作看成一种,只需要保留花 ...

  8. CSS设置input placeholder文本的样式

    placeholder是HTML5  input的新属性,英文意思是占位符,它一般表示input输入框的默认提示值. 下面是设置placeholder的文本样式的选择器的示例: /* webkit 浏 ...

  9. &lbrack;Tensorflow&rsqb; RNN - 03&period; MultiRNNCell for Digit Prediction

    Ref: http://blog.csdn.net/u014595019/article/details/52759104 Time: 2min Successfully downloaded tra ...

  10. 良好的GUI设计指南

    一.设计指南 摘自:<需求分析与系统设计(第3版)> 7.1.2. 1. 用户控制 用户事件(菜单动作.鼠标点击.屏幕光标移动等)打开GUI窗口或调用程序:程序执行需要反馈到用户. 2.  ...