文档章节

Redis序列化协议

trayvon
 trayvon
发布于 2017/06/03 17:00
字数 1367
阅读 181
收藏 1

RESP 发送命令格式

发送命令格式 RESP的规定一条命令的格式如下,CRLF代表"\r\n":

*<参数数量> CRLF
$<参数1的字节数量> CRLF
<参数1> CRLF
...
$<参数N的字节数量> CRLF
<参数N> CRLF

以set hello world为例,发送的就是

*3
$3
SET
$5
hello
$5
world

第一行*3表示有3个参数,$3表示接下来的一个参数有3个字节,接下来是参数,$5表示下一个参数有5个字节,接下来是参数,$5表示下一个参数有5个字节,接下来是参数。

所以set hello world最终发送给redis服务器的命令是:

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

RESP 响应命令格式

Redis的返回结果类型分为以下五种:
        正确回复:在RESP中第一个字节为"+"
        错误回复:在RESP中第一个字节为"-"
        整数回复:在RESP中第一个字节为":"
        字符串回复:在RESP中第一个字节为"$"
        多条字符串回复:在RESP中第一个字节为"*"

(+) 表示一个正确的状态信息,具体信息是当前行+后面的字符。
(-)  表示一个错误信息,具体信息是当前行-后面的字符。
(*) 表示消息体总共有多少行,不包括当前行,*后面是具体的行数。
($) 表示下一行数据长度,不包括换行符长度\r\n,$后面则是对应的长度的数据。
(:) 表示返回一个数值,:后面是相应的数字节符。

所以set hello world收到的响应是+OK

RESP

我们使用redis客户端看到的数据都是解析过后的数据,可以windows可以使用telnet,Linux可以使用nc来处理。

telnet

telnet_cmd

使用Java Socket连接Redis服务器

既然知道了redis使用的是RESP协议,而RESP的底层使用的是TCP协议,当然可以利用Java的socket来连接redis服务器。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;

public class RespStart {

    private static final String host = "127.0.0.1";

    private static final int port = 6379;

    private static final String cmd = "*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n";
    
    public static void main(String[] args) {
        byte [] b = new byte[8192];
        Socket socket = initSocket();
        try {
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(cmd.getBytes());
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        try {
            InputStream inputStream = socket.getInputStream();
            inputStream.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(new String(b));
        
    }
    
    private static Socket initSocket(){
        Socket socket = new Socket();
        try {
//          close方法关闭Socket连接后,Socket对象所绑定的端口并不一定马上释放
//          系统有时在Socket连接关闭才会再确认一下是否有因为延迟面未到达的数据包
            //避免重启时,有时候的port already bind
            socket.setReuseAddress(true);
            socket.setKeepAlive(true);//保持TCP连接,不释放资源
            socket.setTcpNoDelay(true);//立即发送数据,不合并数据包
            socket.setSoLinger(true, 0);//强制关闭连接,不阻塞close(阻塞0s)
            socket.connect(new InetSocketAddress(host, port), 3000);
            socket.setSoTimeout(3000);//读取数据阻塞超时时间3s(0是一直阻塞)
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return socket;
    }

}

上面的例子就是利用socket连接redis服务器,并且获取到redis服务器的返回值+OK

我们可以通过Wireshark来抓取数据包看一下。Wireshark使用WinPcap是不能抓取到本地包的,可以在npcap下载一个npcap。

loopback

我们可以看到有8个TCP包。前3个TCP包是3次握手,第4次是发送命令数据,后4次是TCP关闭的4次握手。

下面使用jedis来执行set hello world:

import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.Jedis;

public class SimpleRedisTest {
    
    private Jedis jedis ;
    
    @Before
    public void setUp(){
        jedis = new Jedis("127.0.0.1");
    }
    
    @Test
    public void testSet(){
        jedis.set("hello", "world");
    }

}

可以发现使用jedis抓包和上面我们自己使用socket的包是一样的。

loopback

其实这就是Jedis的原理,jedis对RESP进行了较好的封装。下面就看一下jedis是如何封装RESP的。

Jedis对RESP的封装

redis.clients.jedis.Protocol(RESP协议封装) redis.clients.jedis.Connection(连接管理) redis.clients.util.RedisOutputStream(继承FilterOutputStream) redis.clients.util.RedisInputStream(继承FilterInputStream) 发送命令是封装在Protocol中,利用的方法是sendCommand

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);
    }
  }

处理也是封装在Protocol中,利用的方式是方法是process

private static Object process(final RedisInputStream is) {

    final byte b = is.readByte();
    if (b == PLUS_BYTE) {//+
      return processStatusCodeReply(is);
    } else if (b == DOLLAR_BYTE) {//$
      return processBulkReply(is);
    } else if (b == ASTERISK_BYTE) {//*
      return processMultiBulkReply(is);
    } else if (b == COLON_BYTE) {//:
      return processInteger(is);
    } else if (b == MINUS_BYTE) {//-
      processError(is);
      return null;
    } else {
      throw new JedisConnectionException("Unknown reply: " + (char) b);
    }
  }

sendCommand就是将命令转换为RESP协议格式,process是根据响应的第一个字节进行不同处理的。对比上面的RESP协议图来看就非常清除了。

RedisOutputStream和RedisInputStream是在Connection设置代的,RedisOutputStream和RedisInputStream分别继承FilterOutputStream和FilterInputStream,继承这2个类的典型的装饰模式。是对socket获取的InputStream和OutputStream的装饰。

jedis把命令和数据读写分开了,一个命令对应一次响应。然而jedis不是线程安全的,所以多线程下很容易出现Unknown reply: x和ERR Protocol error: invalid bulk length这样的错误。一个是读缓冲区并发引起的错误,一个是写缓冲区并发引起的错误。这个具体的分析放在下一次来介绍。

参考

npcap下载

© 著作权归作者所有

共有 人打赏支持
下一篇: Zookeeper ACL
trayvon
粉丝 15
博文 124
码字总数 184644
作品 1
程序员
私信 提问
Netty 源码中对 Redis 协议的实现

近期一直在做网络协议相关的工作,所以博客也就与之相关的比较多,今天楼主结合 Redis的协议 RESP 看看在 Netty 源码中是如何实现的。 RESP 协议 RESP 是 Redis 序列化协议的简写。它是一种直...

haifeiWu
08/09
0
0
Spring Data操作Redis时,发现key值出现\xAC\xED\x00\x05t\x00

最近在研究Redis,以及spring data对redis的支持发现了一个奇怪的现象 先说现象吧,通过redisTemplate下的opsForHash方法存储hash类型的值,操作成功以后,去redis控制台显示keys * 的时候,...

楠木楠
2016/12/10
488
0
小路/async-redis-client

async-redis-client 基于netty实现的非阻塞redis客户端 AsyncRedisClient client = new NettyRedisClient("172.16.3.213:6379", 1, null);String result = client.set("TEST_KEY2", "CACHED......

小路
2015/03/02
0
0
PHP异步协程框架 - Group-Co

PHP异步协程框架,支持SOA服务化调用,支持并行、串行调用。 支持异步日志,异步文件读写,异步Mysql,异步Redis,Mysql,Redis连接池。 为什么写这个框架? 利用协程特性以同步方式来编写异步代码...

coco1225
2017/11/02
0
0
Dubbo 升级扩展 --Dubbo-G

Dubbo-G 详细介绍 Dubbo是一个被国内很多互联网公司广泛使用的开源分布式服务框架,即使从国际视野来看应该也是一个非常全面的SOA基础框架。作为一个重要的技术研究课题,在联想电商我们根据...

技术专家
2017/05/26
5.5K
23

没有更多内容

加载失败,请刷新页面

加载更多

WebSocketdemo

WebSocket是html5提供的一种在单个tcp连接上进行全双工通讯的协议。 Http协议是无状态、无连接的、单向的应用层协议,采用了请求响应模型,通信请求智能有客户端发起,服务端对请求做出应答处...

qiang123
18分钟前
0
0
谷歌推迟公布Google+漏洞遭参议员不满

参议院商务委员会主席约翰·图恩和另外两位参议员杰瑞·莫兰和罗杰·维克发出信函,要求谷歌解释推迟披露此问题的原因。信中称:“谷歌如果要保持或重获用户对其服务的信任,就必须在公众和立...

linuxCool
25分钟前
0
0
最重要的是做什么,而不是怎么做。

最重要的是做什么,而不是怎么做。 做什么是战略,怎么做是战术。将军下令说,天黑前拿下这座山头,这是战略。手下的士兵可以不知道为什么要拿下这座山头,还非得是天黑之前,但士兵必须知道...

我是菜鸟我骄傲
今天
6
0
w, vmstat, top, sar, nload命令查看系统状态信息

w/uptime 查看系统负载 cat /proc/cpuinfo 查看cpu核数 vmstat 监控系统状态,用法 vmstat 1,关键的几列: r, b, swpd, si, so, bi, bo, us, wa top 查看进程使用资源情况 top -c 显示详细的...

野雪球
今天
2
0
小白创建一个spring boot项目

进入 https://start.spring.io/

lilugirl
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部