Redis是怎样通讯的?

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis 协议模型就是简单的请求-响应模型,和平常的 Http 协议有点类似。客户端发送 Redis 命令,然后服务端处理命令并返回结果给客户端。Redis 官方说这可能是最简单的网络协议模型了。

网络异常,图片无法展示
|

模型

Redis 协议模型就是简单的请求-响应模型,和平常的 Http 协议有点类似。客户端发送 Redis 命令,然后服务端处理命令并返回结果给客户端。Redis 官方说这可能是最简单的网络协议模型了。

网络异常,图片无法展示
|

有两种情况下不 不适用这个模型,一个是批量流水线命令,一个是发布/订阅功能。

网络异常,图片无法展示
|

协议描述

Redis 协议一般简单的分为 5 类数据结构,简单字符串、错误信息、数值、大字符串、数组。每种数据类型在第一个字节用不同的符号来区分:

  • 简单字符串(Simple Strings):开头第一个符号为 +,对应 HEX 值为:0x2b
  • 错误信息(Errors):第一个字节符号为 -,对应 HEX 值为:0x2d
  • 数值(Integers):第一个字节符号为 :,对应 HEX 值为:0x3a
  • 大字符串(Bulk Strings):第一个字节符号为 $,对应 HEX 值为:0x24
  • 数组(Arrays):第一个字节符号为 *,对应 HEX 值为:0x2a

这 5 种数据类型可以组合起来使用,每种数据类型通过 CRLF 结尾,就是平常的 \r\n,对应的 HEX 值为:0x0d,0x0a。一般我们判断一种数据类型是否结束时,只要判断是否有 \r 出现就可以了。Redis 客户端和服务端之间就是通过这些规则来进行通信的。

简单字符串

一般简单字符串用于返回 Redis 服务端的系统响应,如果要响应用户存储的数据时,一般会用大字符串(Bulk Strings)的数据类型来返回。

比如说客户端发送 set 命令新增一个 Key 来存储字符串,此时客户端就会返回 +OK。这种方式返回的数据不能有空格和换行,因为空格和换行表示该类型的数据结尾。

redis:0>set name 灰灰
"OK"
# Redis 服务端响应数据
0000  2b 4f 4b 0d 0a                                     +OK··

错误信息

当我们执行的命令发生错误时,Redis 服务端就会返回错误信息

redis:0>incr name
"ERR value is not an integer or out of range"
# Redis 服务端响应数据
0000  2d 45 52 52 20 76 61 6c  75 65 20 69 73 20 6e 6f   -ERR val ue is no
0010  74 20 61 6e 20 69 6e 74  65 67 65 72 20 6f 72 20   t an int eger or 
0020  6f 75 74 20 6f 66 20 72  61 6e 67 65 0d 0a         out of r ange··

数值

返回数值的其中一种情况就是执行 exists 命令来判断某个 Key 存不存在,返回 1 表示存在,返回 0 表示不存在

redis:0>exists name
"1"
# Redis 服务端响应数据
0000  3a 31 0d 0a                                        :1··
---------------------------------------------------------------
redis:0>exists hui
"0"
# Redis 服务端响应数据
0000   3a 30 0d 0a                                       :0..

大字符串

大字符串返回值有两部分组成,一部分是表示字符串长度的数据,一部分是字符串本身数据。它由 $ 符号开头,后面跟着的是表示字符串长度的数据,该数值直接用字符串的形式表示,也就说读取该字节数据的时候,要用读取字符串数据的方式来读取。读取完后再转换为数值数据,然后再根据这个数值来读取相对应长度的字节数据。这样数据中就可以包含空格和换行了,因为是根据开头的长度数值来读取相对应的字节数据的,而不是通过判断 \r\n 符号来读取。

比如客户端获取前面设置的 Key 为 name 的数据:

redis:0>get name
"灰灰"
# Redis 服务端响应数据
0000   24 36 0d 0a e7 81 b0 e7 81 b0 0d 0a               $6..........

其中 e7 81 b0 e7 81 b0 就是 灰灰 字符的字节数据

数组

当服务端返回数组数据时,它由 * 符号开头,后面紧跟着的是这个数值的长度,和大字符串的字节长度一样,该长度也是以字符串的形式返回。数组中的每个元素再通过相对应的数据类型来表示。

*2
+value1
+value2
# or
*3
:22
:52
:99
# 当然也可以表示嵌套数组
*2
*1
:123
*2
:433
:92

比如客户端设置一个 list 数据,然后获取它

redis:0>lpush mylist value1 value2
2
# 获取 list
redis:0>lrange mylist 0 1
["value2","value1"]
0000   2a 32 0d 0a 24 36 0d 0a 76 61 6c 75 65 32 0d 0a   *2..$6..value2..
0010   24 36 0d 0a 76 61 6c 75 65 31 0d 0a               $6..value1..

发送命令

当客户端发送命令给服务端时,客户端一般会把命令组装成上面的数组大字符串的数据格式再发送给服务端。比如上面的我们发送一个简单的新增一个 Key 命令:

redis:0>set name 灰灰
# 客户端发送给服务端端数据
0000   2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 34 0d   *3..$3..set..$4.
0010   0a 6e 61 6d 65 0d 0a 24 36 0d 0a e7 81 b0 e7 81   .name..$6.......
0020   b0 0d 0a                                          ...

Redis 数据结构

Redis 一般常用的有 5 种数据类型,下面看看 5 种数据类型对应的客户端和服务端之间的数据是怎么交互的。

字符串 String

redis:0>set name greycode
0000   2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 34 0d   *3..$3..set..$4.
0010   0a 6e 61 6d 65 0d 0a 24 38 0d 0a 67 72 65 79 63   .name..$8..greyc
0020   6f 64 65 0d 0a                                    ode..
# 响应
0000   2b 4f 4b 0d 0a                                    +OK..
-------------------------------------------------------------------------
redis:0>get name
0000   2a 32 0d 0a 24 33 0d 0a 67 65 74 0d 0a 24 34 0d   *2..$3..get..$4.
0010   0a 6e 61 6d 65 0d 0a                              .name..
# 响应
0000   24 38 0d 0a 67 72 65 79 63 6f 64 65 0d 0a         $8..greycode..

哈希表 Hash

redis:0>hset myHash name huihui
0000   2a 34 0d 0a 24 34 0d 0a 68 73 65 74 0d 0a 24 36   *4..$4..hset..$6
0010   0d 0a 6d 79 48 61 73 68 0d 0a 24 34 0d 0a 6e 61   ..myHash..$4..na
0020   6d 65 0d 0a 24 36 0d 0a 68 75 69 68 75 69 0d 0a   me..$6..huihui..
# 响应
0000   3a 31 0d 0a                                       :1..
-------------------------------------------------------------------------
redis:0>hget myHash name
0000   2a 33 0d 0a 24 34 0d 0a 68 67 65 74 0d 0a 24 36   *3..$4..hget..$6
0010   0d 0a 6d 79 48 61 73 68 0d 0a 24 34 0d 0a 6e 61   ..myHash..$4..na
0020   6d 65 0d 0a                                       me..
# 响应
0000   24 36 0d 0a 68 75 69 68 75 69 0d 0a               $6..huihui..
-------------------------------------------------------------------------
redis:0>hgetall myHash
0000   2a 32 0d 0a 24 37 0d 0a 68 67 65 74 61 6c 6c 0d   *2..$7..hgetall.
0010   0a 24 36 0d 0a 6d 79 48 61 73 68 0d 0a            .$6..myHash..
# 响应
0000   2a 32 0d 0a 24 34 0d 0a 6e 61 6d 65 0d 0a 24 36   *2..$4..name..$6
0010   0d 0a 68 75 69 68 75 69 0d 0a                     ..huihui..

列表 List

redis:0>lpush lists huihui greycode
0000   2a 34 0d 0a 24 35 0d 0a 6c 70 75 73 68 0d 0a 24   *4..$5..lpush..$
0010   35 0d 0a 6c 69 73 74 73 0d 0a 24 36 0d 0a 68 75   5..lists..$6..hu
0020   69 68 75 69 0d 0a 24 38 0d 0a 67 72 65 79 63 6f   ihui..$8..greyco
0030   64 65 0d 0a                                       de..
# 响应
0000   3a 32 0d 0a                                       :2..
-------------------------------------------------------------------------
redis:0>lrange lists 0 1
0000   2a 34 0d 0a 24 36 0d 0a 6c 72 61 6e 67 65 0d 0a   *4..$6..lrange..
0010   24 35 0d 0a 6c 69 73 74 73 0d 0a 24 31 0d 0a 30   $5..lists..$1..0
0020   0d 0a 24 31 0d 0a 31 0d 0a                        ..$1..1..
# 响应
0000   2a 32 0d 0a 24 38 0d 0a 67 72 65 79 63 6f 64 65   *2..$8..greycode
0010   0d 0a 24 36 0d 0a 68 75 69 68 75 69 0d 0a         ..$6..huihui..

集合 Set

redis:0>sadd myset hello hi
0000   2a 34 0d 0a 24 34 0d 0a 73 61 64 64 0d 0a 24 35   *4..$4..sadd..$5
0010   0d 0a 6d 79 73 65 74 0d 0a 24 35 0d 0a 68 65 6c   ..myset..$5..hel
0020   6c 6f 0d 0a 24 32 0d 0a 68 69 0d 0a               lo..$2..hi..
# 响应
0000   3a 32 0d 0a                                       :2..
-------------------------------------------------------------------------
redis:0>smembers myset
0000   2a 32 0d 0a 24 38 0d 0a 73 6d 65 6d 62 65 72 73   *2..$8..smembers
0010   0d 0a 24 35 0d 0a 6d 79 73 65 74 0d 0a            ..$5..myset..
#响应
0000   2a 32 0d 0a 24 35 0d 0a 68 65 6c 6c 6f 0d 0a 24   *2..$5..hello..$
0010   32 0d 0a 68 69 0d 0a                              2..hi..

有序集合 ZSet

redis:0>zadd myZset 1 hello 2 world
0000   2a 36 0d 0a 24 34 0d 0a 7a 61 64 64 0d 0a 24 36   *6..$4..zadd..$6
0010   0d 0a 6d 79 5a 73 65 74 0d 0a 24 31 0d 0a 31 0d   ..myZset..$1..1.
0020   0a 24 35 0d 0a 68 65 6c 6c 6f 0d 0a 24 31 0d 0a   .$5..hello..$1..
0030   32 0d 0a 24 35 0d 0a 77 6f 72 6c 64 0d 0a         2..$5..world..
# 响应
0000   3a 32 0d 0a                                       :2..
-------------------------------------------------------------------------
redis:0>zrange myset 0 -1
0000   2a 34 0d 0a 24 36 0d 0a 7a 72 61 6e 67 65 0d 0a   *4..$6..zrange..
0010   24 36 0d 0a 6d 79 5a 73 65 74 0d 0a 24 31 0d 0a   $6..myZset..$1..
0020   30 0d 0a 24 32 0d 0a 2d 31 0d 0a                  0..$2..-1..
# 响应
0000   2a 32 0d 0a 24 35 0d 0a 68 65 6c 6c 6f 0d 0a 24   *2..$5..hello..$
0010   35 0d 0a 77 6f 72 6c 64 0d 0a                     5..world..

Pipeline

Redis 执行 Pipeline 命令,可以一次用一个数据包发送多条命令,然后服务端在把所有命令的执行结果打包成一个数据包返回给客户端,这样在执行大量命令的时候可以减少系统开销。比如我们要执行 set num 998incr num 这两条命令,那么我们就可以把这两条命令分别组装然后拼接在一起发送给服务端。

0000   2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 33 0d   *3..$3..set..$3.
0010   0a 6e 75 6d 0d 0a 24 33 0d 0a 39 39 38 0d 0a 2a   .num..$3..998..*
0020   32 0d 0a 24 34 0d 0a 69 6e 63 72 0d 0a 24 33 0d   2..$4..incr..$3.
0030   0a 6e 75 6d 0d 0a                                 .num..

服务端接收到数据包后,处理每条命令,然后把结果打包返回。

0000   2b 4f 4b 0d 0a 3a 39 39 39 0d 0a                  +OK..:999..

其中, set num 998 命令的结果为:OK

incr num 的结果为:999

代码实现

了解了以上交互协议的规则后,我们就可以自己手写一个 Redis 客户端来与 Redis 服务器通讯了。

完整代码地址:gist.github.com/greycodee/4…

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
19天前
|
NoSQL Redis C++
VS2017的redis客户端实现
VS2017的redis客户端实现
|
NoSQL IDE 网络协议
Redis高级客户端Lettuce详解(下)
Lettuce是一个Redis的Java驱动包,初识她的时候是使用RedisTemplate的时候遇到点问题Debug到底层的一些源码,发现spring-data-redis的驱动包在某个版本之后替换为Lettuce。既然能被Spring生态所认可,Lettuce想必有过人之处,于是笔者花时间阅读她的官方文档,整理测试示例,写下这篇文章。
4229 0
|
19天前
|
NoSQL Linux 网络安全
Redis 客户端工具
Redis 客户端工具
33 0
|
12月前
|
监控 NoSQL Linux
Redis的网络通信模型
Redis的网络通信模型
105 0
|
NoSQL 网络协议 安全
redis03——Redis的网络配置与心跳机制
redis03——Redis的网络配置与心跳机制
|
NoSQL 安全 Java
Redis学习笔记:使用第三方框架连接Redis服务器前需要做哪些事?
通过Lettuce连接远程Redis服务器需要在客户端配置文件中配置Redis服务器的地址、端口、密码等信息。
163 0
|
NoSQL 测试技术 Redis
Redis服务与连接那些事儿
Redis服务与连接那些事儿
156 0
Redis服务与连接那些事儿
|
移动开发 NoSQL 网络协议
带你走进Redis的世界 - Redis的客户端服务端交互
带你走进Redis的世界 - Redis的客户端服务端交互
236 0
带你走进Redis的世界 - Redis的客户端服务端交互
|
移动开发 NoSQL 网络协议
Redis:我是如何与客户端进行通信的
Redis:我是如何与客户端进行通信的
139 0
Redis:我是如何与客户端进行通信的
|
移动开发 NoSQL 网络协议
Redis竟然还有自定义网络通信协议?
Redis竟然还有自定义网络通信协议?
114 0
Redis竟然还有自定义网络通信协议?