博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis 5通信协议解析以及手写一个Jedis客户端
阅读量:3783 次
发布时间:2019-05-22

本文共 8062 字,大约阅读时间需要 26 分钟。

在这里插入图片描述

Redis 5通信协议解析以及手写一个Jedis客户端


Redis的基础介绍与安装使用步骤:

Redis的基础数据结构与使用:
Redis核心原理:
Redis 5 之后版本的高可用集群搭建:
Redis 5 版本的高可用集群的水平扩展:
Redis 5 集群选举原理分析:


优秀博客:

Redis Protocol specification:

通信协议(protocol):


redis的通信协议是什么?我的理解是双方约定了一种编码方式,客户端将要发送的命令进行编码,然后服务端收到后,使用同样的协议进行解码,服务端处理完成后,再次编码返回给客户端,客户端解码拿到返回结果,这样就完成了一次通信。如下图:

在这里插入图片描述

Redis 协议在以下三个目标之间进行折中:
  • 易于实现
  • 可以高效地被计算机分析(parse)
  • 可以很容易地被人类读懂
简单来说:简单,高效,易读。
来看一下redis的通信协议:
  • 客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。
  • 客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。
  • 在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。
请求协议:
*
<参数数量>
CR LF$
<参数 1 的字节数量>
CR LF
<参数 1 的数据>
CR LF...$
<参数 n 的字节数量>
CR LF
<参数 n 的数据>
CR LF
举个例子, 以下是一个命令协议的打印版本:
*3$3SET$5mykey$7myvalue
这个命令的实际协议值如下:
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
返回协议:
Redis 命令会返回多种不同类型的回复。
通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:
状态回复(status reply)的第一个字节是 "+"错误回复(error reply)的第一个字节是 "-"整数回复(integer reply)的第一个字节是 ":"批量回复(bulk reply)的第一个字节是 "$"多条批量回复(multi bulk reply)的第一个字节是 "*"
状态回复
一个状态回复(或者单行回复,single line reply)是一段以 “+” 开始、 “\r\n” 结尾的单行字符串。
例如:
+OK

引用:

具体其他的也可以看下官网的介绍

我们看下Jedis是如何连接后台redis服务的
启动后台redis服务
[root@localhost redis-5.0.2]# src/redis-server redis.conf2800:C 17 Dec 2018 22:53:50.981 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo2800:C 17 Dec 2018 22:53:50.982 # Redis version=5.0.2, bits=64, commit=00000000, modified=0, pid=2800, just started2800:C 17 Dec 2018 22:53:50.982 # Configuration loaded[root@localhost redis-5.0.2]# ps -ef|grep redisroot       2801      1  0 22:53 ?        00:00:00 src/redis-server *:6379root       2806   2674  0 22:53 pts/0    00:00:00 grep --color=auto redis[root@localhost redis-5.0.2]#
注意:
1、如果出现下面这种异常:
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
linux执行下面命令,开放6379端口:
/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
2、关闭redis的保护模式
vim redis.conf
修改:
protected-mode no

Jedis代码:
pom依赖,我们目前使用jedis-2.9.0,可以连接单台redis,也可以连接集群,也可以开监控:
redis.clients
jedis
2.9.0

代码很简单:

package com.demo.redis.client;import redis.clients.jedis.Jedis;public class RedisClient {public static void main(String[] args) {    Jedis jedis = new Jedis("192.168.5.100",6379);    System.out.println(jedis.set("name","xxx"));    System.out.println(jedis.get("name"));}}
返回:
OKxxx
先看下Jedis的类图:

在这里插入图片描述

大家可以自己点进去看一下,其实很清晰。
具体Jedis是怎么调用的,如果我们点进去看一下:
set方法:
> redis.clients.jedis.Jedis#set(java.lang.String, java.lang.String)    >redis.clients.jedis.Client#set(java.lang.String, java.lang.String)        >redis.clients.jedis.BinaryClient#set(byte[], byte[])        >redis.clients.jedis.Connection#sendCommand(redis.clients.jedis.Protocol.Command, byte[]...)            >redis.clients.jedis.Protocol#sendCommand(redis.clients.util.RedisOutputStream, redis.clients.jedis.Protocol.Command, byte[]...)            >redis.clients.jedis.Protocol#sendCommand(redis.clients.util.RedisOutputStream, byte[], byte[]...)
就大致这么几步调用,我们尝试自己写一个试试看

核心代码如下:

package com.demo.redis.client;import com.demo.redis.connection.Connection;import com.demo.redis.protocol.Protocol;/** *  提供api服务 *  @author zyy *  @date 2018年12月17日 * */public class Client {    private Connection connection;    public Client(String host, int port) {        connection = new Connection(host, port);    }    public String set(String key, String value) {        set(SafeEncoder.encode(key), SafeEncoder.encode(value));        return connection.getStatusReply();    }    public void set(byte[] key, byte[] value) {        this.connection.sendCommand(Protocol.Command.SET,new byte[][]{key,value});    }    public String get(String key) {        this.connection.sendCommand(Protocol.Command.GET,SafeEncoder.encode(key));        return connection.getStatusReply();    }}

package com.demo.redis.client;import redis.clients.jedis.exceptions.JedisDataException;import redis.clients.jedis.exceptions.JedisException;import java.io.UnsupportedEncodingException;/** *  编码 *  @author zyy *  @date 2018年12月17日 * */public class SafeEncoder {    public static byte[] encode(String str) {        try {            if (str == null) {                throw new JedisDataException("value sent to redis cannot be null");            } else {                return str.getBytes("UTF-8");            }        } catch (UnsupportedEncodingException var2) {            throw new JedisException(var2);        }    }}

package com.demo.redis.connection;import com.demo.redis.protocol.Protocol;import redis.clients.jedis.exceptions.JedisConnectionException;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;/** * 建立连接 * * @author zyy * @date 2018年12月17日 */public class Connection {    private Socket socket;    private String host;    private int port;    private OutputStream outputStream;    private InputStream inputStream;    public Connection(String host, int port) {        this.host = host;        this.port = port;    }    //发送命令    public Connection sendCommand(Protocol.Command cmd, byte[]... args) {        try {            this.connect();            Protocol.sendCommand(this.outputStream, cmd, args);            //++this.pipelinedCommands;            return this;        } catch (JedisConnectionException var6) {            throw var6;        }    }    //如果未建立连接,则scoket 连接    public void connect() {        try {            if (!isConnected()) {                socket = new Socket(host, port);                inputStream = socket.getInputStream();                outputStream = socket.getOutputStream();            }        } catch (IOException e) {            e.printStackTrace();        }    }    //判断是否已建立连接    public boolean isConnected() {        return socket != null && socket.isBound() && !socket.isClosed() && socket.isConnected()                && !socket.isInputShutdown() && !socket.isOutputShutdown();    }    //获取返回信息    public String getStatusReply() {        byte b[] = new byte[1024];        try {            socket.getInputStream().read(b);        } catch (IOException e) {            e.printStackTrace();        }        return new String(b);    }}

package com.demo.redis.protocol;import java.io.IOException;import java.io.OutputStream;/** *  进行协议编码 *  @author zyy *  @date 2018年12月17日 * */public class Protocol {    /**     * *    
<参数数量>
CR LF * $
<参数 1 的字节数量>
CR LF *
<参数 1 的数据>
CR LF * ... * $
<参数 n 的字节数量>
CR LF *
<参数 n 的数据>
CR LF * */ public static final String PARAM_BYTE_NUM = "$"; public static final String PARAM_NUM = "*"; public static final String TERMINATION = "\r\n"; public static void sendCommand(OutputStream outputStream, Command command, byte[]... b) { /* 照着 SET mykey myvalue 的格式进行编码: *3 $3 SET $5 mykey $7 myvalue 最终如下: "*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n" */ StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append(PARAM_NUM).append(b.length + 1).append(TERMINATION); stringBuffer.append(PARAM_BYTE_NUM).append(command.name().length()).append(TERMINATION); stringBuffer.append(command).append(TERMINATION); for (byte[] arg : b) { stringBuffer.append(PARAM_BYTE_NUM).append(arg.length).append(TERMINATION); stringBuffer.append(new String(arg)).append(TERMINATION); } try { outputStream.write(stringBuffer.toString().getBytes()); } catch (IOException e) { e.printStackTrace(); } } public static enum Command { SET, GET; }}

ok,我们调用下自己写的client,试试能否成功。
package com.demo.redis.client;public class Jedis {    public static void main(String[] args) {        Client client = new Client("192.168.5.100",6379);        System.out.println(client.set("name","xxxx"));        System.out.println(client.get("name"));    }}
返回结果:
+OK$4xxxx

ok,成功了!Jedis客户端比较简陋,不过核心的功能还是有的,如果有兴趣,可以自己写一个试试看:)。
如果感觉有帮助,可以点个赞:)。
如需转载,请注明出处,谢谢:)。
你可能感兴趣的文章
结构型模式——装饰者设计模式
查看>>
线程的同步——Synchronized和ReentrantLock
查看>>
网络编程基础
查看>>
python实现快速排序
查看>>
python实现归并排序
查看>>
ptqt5控件了解(三)
查看>>
自学C++(一)
查看>>
51单片机介绍(二)
查看>>
STM32F103 入门篇-5-初识STM32
查看>>
后台框架的frameset
查看>>
Spring Jdbc
查看>>
Spring 事务管理
查看>>
spring与mybatis的整合
查看>>
json数据交换和RESTful支持
查看>>
spring中的拦截器
查看>>
文件上传和下载
查看>>
Oracle指令,软件架构,
查看>>
oracle5:oracle的图形界面操作,分页查询,练习
查看>>
密码学基础之对称密码体制和公钥密码体制
查看>>
Spark Streaming进阶
查看>>