Redis RESP 协议实现实例详解

目录
  • 引言
    • 回顾RESP协议
    • 如何拆解RESP协议
    • 功能实现
  • 总结

引言

我们之前已经学习了RESP协议的内容,且已经完成了一个最简单的Redis读写分离中间件,我们来看拆解下该demo, 看看Redis RESP协议实现起来到底有多简单。

回顾RESP协议

RESP是基于TCP来实现的Redis通信协议,该协议是以/r/n(行)进行分割的,协议支持5种类型,具体信息如下:

类型 前缀 备注
简单字符串 + 简单字符串以+开头
错误数据 - 错误数据以-开头
整数 : 整数以:开头
复杂字符串 $ 复杂字符串以$开头
数组 * 数组以*开头

即,我们向redis发送命令:set name pdudo,其实发送的具体信息是

*3
$3
set
$4
name
$5
pdudo

而服务器返回的信息也是类似的,只不过还需要了解+-,这2个前缀分别代表正确消息和错误的消息。

我们准备2个例子,我们来敲一下

例子1

set name pdudo

例子2

lpush pdudo data1
lpush pdudo data2
lrange pdudo 0 -1

快来动动你的小手指,看能不能根据RESP协议规则,将上述例子命令敲出来。现在你体会到了Redis官网介绍RESP协议时所述的 简单易读 可么?

带着来敲一下

对于RESP来说,一定要搞清楚协议后,最好能够手写协议去执行,再考虑写程序去实现协议!!!

如何拆解RESP协议

终于到了喜闻乐见的环节了,我们要拆解和组装协议了。 那我们至少来解决如下3个问题:

  • 该协议是基于TCP流的,我们如何判断整个命令什么时候结束?
  • 如何拆解命令?

协议什么时候结束

一般而言,我们自己在使用TCP传输数据,都会在数据开头定义2个或者4个字节,用于存储该数据有多少个字节,这样方便检验接收,类似于这种情况。

RESP有意思了,它是以/r/n来分割的。最前面会以前缀来判断其类型,例如我们发送命令,其会用到的前缀有*以及$,那么我们如何来判断,我们要读取多少个/r/n呢?

因为上述*代表数组,即有多少组数据需要处理,图中为n

$表示复杂字符串,即需要获取m个字符数据,不包含/r/n

如何拆解RESP协议

若要拆解命令,则我们得获取命令,如上图所示,报文$m,其实记录的有m长度的数据(不包含\r\n),所以我们可以这样来写伪代码。

根据如上,我们很容易写出伪代码

func toArgs(rd *bufio.Reader) {
	data , _ , _ := rd.ReadLine()
	switch data[0] {
	case '*':
		n := data[1:] // 循环n次
		for i:=0;i<n;i++ {
			toArgs(rd)
		}
	case '$':
		m := data[1:] // 获取m个数据
		// 获取m长度的数据即可
	}
}

如上我们先获取前缀为*的,继而获取其值n,我们则循环n次,即可获取该报文的数据。而前缀为$的,我们可以直接获取该m长度的数据即可,这里主要要处理一下\r\n

将命令构建RESP报文规范,根据拆解反操作就可以了,这里暂不介绍了。

上述,我们核心功能已经探讨完毕了。

功能实现

代码已经编写完毕,放置在了gitee上: gitee

如上我们已经学会了如何拆解和组装RESP协议了,我们接着来看,我们如何用go来编写拆解和组装协议的代码呢? 我们可以看。

我们先创建一个字符,然后将其封装为bufio.Reader,我们来看下:

因为我们要使用readLine()函数,所以我们需要将其转换为bufio.Reader类型,若是直接从net.Conn中获取,不用转换,直接可以使用 bufio.Reader的。

我们将上述伪代码编写一下,实现拆解的功能。

其具体执行过程是我们先获取一行数据,放置到data中,而后判断其前缀是什么,若是*则取其后面的数据,将其转为int类型n,而后再递归该函数n次,而后中遇到$,我们则取后面的数据,也是将其转为int类型m,而后再取m长度的实际数据,这就是我们的命令了,最后我们再踢掉命令的\r\n即可。

其中,有一个函数是byteToInt是我们自己写的通过切片转为数字的函数,我们看下

该函数主要的功能是将其[]byte数字转换为int数据。

如上,我们整个RESP协议功能写完了,我们运行下看下实际效果:

很显然,我们成功拆解了该数据。

总结

这篇文章,我们介绍了应该如何使用go简单的拆解RESP协议的内容,为什么我们不介绍如何编写redis主从中间件呢?

最开始是打算这样写的,但是知识多了,介绍起来会很杂,很难把一个点讲清楚,所以我们就单独挑了一个核心点来介绍,我愿意将其称之为面向核心编程(我的基友很早之前告诉我的),所谓的面向核心编程简而易在就是我们在涉及一个功能的时候,要学会拆解该功能,将核心功能先用demo做出来,而后再慢慢丰富周边,从而完成整个需求涉及。

最后我们再来聊聊RESP协议,官网在介绍时将其概括为实现简单快速解析直接可阅读。如果你认真学习这2篇文章,肯定对此深有感悟。

更多关于Redis RESP 协议的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅析redis cluster介绍与gossip协议

    一.redis cluster 介绍 自动将数据进行分片,每个 master 上放一部分数据 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 redis cluster架构下的每个redis都要开放两个端口号,比如一个是6379,另一个就是加1w的端口号16379. 6379端口号就是redis服务器入口. 16379端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测.配置更新.故障转移授权.cluste

  • Redis协议具体用法详解

    前言 我们用过很多redis的客户端,有没有相过自己撸一个redis客户端?其实很简单,基于socket,监听6379端口,解析数据就可以了. redis协议 解析数据的过程主要依赖于redis的协议了.我们写个简单例子看下redis的协议: public class RedisTest { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); jedis.set(&qu

  • redis protocol通信协议及使用详解

    目录 简介 redis的高级用法 Redis中的pipline Redis中的Pub/Sub RESP protocol Simple Strings Bulk Strings RESP Integers RESP Arrays RESP Errors Inline commands 总结 简介 redis是一个非常优秀的软件,它可以用作内存数据库或者缓存.因为他的优秀性能,redis被应用在很多场合中. redis是一个客户端和服务器端的模式,客户端和服务器端是通过TCP协议进行连接的,客户端

  • 使用go net实现简单的redis通信协议

     图解redis通信协议 请求协议: 请求协议一般格式: *<参数数量> CR LF $<参数 1 的字节数量> CR LF <参数 1 的数据> CR LF ... $<参数 N 的字节数量> CR LF <参数 N 的数据> CR LF 例如,一个登录命令: *2 2-> 参数数量 $4 4-> 字节数量 AUTH $13 password@2018 返回结果: +OK 实际上,发送的命令为"*2\r\n$4\r\nAU

  • Redis RESP 协议实现实例详解

    目录 引言 回顾RESP协议 如何拆解RESP协议 功能实现 总结 引言 我们之前已经学习了RESP协议的内容,且已经完成了一个最简单的Redis读写分离中间件,我们来看拆解下该demo, 看看Redis RESP协议实现起来到底有多简单. 回顾RESP协议 RESP是基于TCP来实现的Redis通信协议,该协议是以/r/n(行)进行分割的,协议支持5种类型,具体信息如下: 类型 前缀 备注 简单字符串 + 简单字符串以+开头 错误数据 - 错误数据以-开头 整数 : 整数以:开头 复杂字符串

  • Redis Set 集合的实例详解

     Redis Set 集合的实例详解 Redis的Set是string类型的无序集合.集合成员是唯一的,这就意味着集合中不能出现重复的数据. redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1). 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员). 实例 redis 127.0.0.1:6379> SADD runoobkey redis (integer) 1 redis 127.0.0.1:6379> SADD ru

  • PHP操作Redis常用命令的实例详解

    redis常用命令有: 1.连接操作命令: 2.持久化命令: 3.远程服务控制命令: 4.对value操作命令:5.string命令: 6.list命令: 7.set命令: 8.hash命令等等. Redis 常用命令 登录 redis-cli -p 5566 -a password 检查key是否存在 EXISTS key 搜索某关键字 KSYS *4 返回一个Key所影响的vsl的类型 TYPE key 下面通过代码看下PHP操作Redis命令,代码如下所示: //连接本地的 Redis 服

  • php基于redis的分布式锁实例详解

    在使用分布式锁进行互斥资源访问时候,我们很多方案是采用redis的实现. 固然,redis的单节点锁在极端情况也是有问题的,假设你的业务允许偶尔的失效,使用单节点的redis锁方案就足够了,简单而且效率高. redis锁失效的情况: 客户端1从master节点获取了锁 master宕机了,存储锁的key还没来得及同步到slave节点上 slave升级为master 客户端2从新的master上获取到同一个资源的锁 于是,客户端1和客户端2同事持有了同一个资源的锁,锁的安全性被打破. 如果我们不考

  • springboot+mybatis+redis 二级缓存问题实例详解

    前言 什么是mybatis二级缓存? 二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace. 即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存. 第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率. Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存. 本文讲述的是使用Redi

  • redis实现分布式锁实例详解

    目录 1.业务场景引入 2.基础环境准备 2.1.准备库存数据库 2.2.创建SpringBoot工程,pom.xml中导入依赖,请注意版本. 2.3.application.properties配置文件 2.4.SpringBoot启动类 2.5.添加Redis的配置类 2.6.pojo层 2.7.mapper层 2.8.SpringBoot监听Web启动事件,加载商品数据到Redis中 3.Redis实现分布式锁 3.1分布式锁的实现类 3.2分布式锁的业务代码 4.分布式锁测试 总结 1.

  • C++使用redis的实例详解

    C++使用redis的实例详解 hiredis是redis数据库的C接口,目前只能在linux下使用,几个基本的函数就可以操作redis数据库了. 函数原型:redisContext *redisConnect(const char *ip, int port); 说明:该函数用来连接redis数据库,参数为数据库的ip地址和端口,一般redis数据库的端口为6379: 函数返回值:该函数返回一个结构体redisContext: 类似的提供了一个函数redisContext* redisConn

  • Redis 实现队列原理的实例详解

    Redis 实现队列原理的实例详解 场景说明: ·用于处理比较耗时的请求,例如批量发送邮件,如果直接在网页触发执行发送,程序会出现超时 ·高并发场景,当某个时刻请求瞬间增加时,可以把请求写入到队列,后台在去处理这些请求 ·抢购场景,先入先出的模式 命令: rpush + blpop 或 lpush + brpop rpush : 往列表右侧推入数据 blpop : 客户端阻塞直到队列有值输出 简单队列: simple.php $stmt = $pdo->prepare('select id, c

  • redis数据结构之intset的实例详解

    redis数据结构之intset的实例详解 在redis中,intset主要用于保存整数值,由于其底层是使用数组来保存数据的,因而当对集合进行数据添加时需要对集合进行扩容和迁移操作,因而也只有在数据量不大时redis才使用该数据结构来保存整数集合.其具体的底层数据结构如下: typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; }

  • 实例详解Spring Boot实战之Redis缓存登录验证码

    本章简单介绍redis的配置及使用方法,本文示例代码在前面代码的基础上进行修改添加,实现了使用redis进行缓存验证码,以及校验验证码的过程. 1.添加依赖库(添加redis库,以及第三方的验证码库) <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency&

随机推荐