Redis之通讯协议

RESP 协议

概述

通信协议

通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。

Redis 使用协议

  1. 连接: 在 Redis 中客户端与服务端之间的通信协议是在TCP协议之上构建的。
  2. 数据格式: Redis 制定了RESP(REdis Serialization Protocol,Redis 序列化协议)实现客户端与服务端的正常交互,这种协议简单高效、既能够被机器解析、又容易被人类识别。

RESP 协议

该协议是专门为 Redis 设计的,可以序列化不同的数据类型,如integers(整数),strings(字符串),arrays(数组)。它还使用了一个特殊的类型来表示errors(错误)。请求以字符串数组的形式来表示要执行命令的参数从客户端发送到 Redis 服务器。Redis 使用command-specific(命令特有)数据类型作为回复。
RESP协议是二进制安全的,并且不需要处理从一个进程传输到另一个进程的块数据的大小,因为它使用prefixed-length(前缀长度)的方式来传输块数据的。 需要注意的是该协议是仅用于 客户端 - 服务器(Client-Server)的通信。 Redis 集群使用不同的二进制协议来交换节点之间的消息。

发送命令格式

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

*< 参数数量 > CRL
$< 参数 1 的字节数量 > CRLF
< 参数 1> CRLF
...
$< 参数 N 的字节数量 > CRLF
< 参数 N> CRLF
复制代码

set hello world 这条命令举例

## 参数数量为3个,因此第一行为:
*3
## 参数字节数分别是355,因此后面几行为:
$3
SET
$5
hello
$5
world
复制代码

需要注意的是,上面只是格式化显示的结果,实际传输格式为如下代码

*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n
复制代码

响应结果格式

Redis 的返回结果类型分为以下五种:

  1. 状态回复:在RESP中第一个字节为+
  2. 错误回复:在RESP中第一个字节为-
  3. 整数回复:在RESP中第一个字节为
  4. 字符串回复:在RESP中第一个字节为$
  5. 多条字符串回复:在RESP中第一个字节为*五种数据结构

redis-cli只能看到最终的执行结果(例如执行set hello world,返回结果是OK,并没有看到类似于+符合),那是因为redis-cli本身就是按照RESP进行结果解析的,所以看不到中间结果,redis-cli.c 源码对命令结果的解析结构如下:

static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
sds out = sdsempty();
switch (r->type) {
case REDIS_REPLY_ERROR:
//  处理错误回复
case REDIS_REPLY_STATUS:
//  处理状态回复
case REDIS_REPLY_INTEGER:
//  处理整数回复
case REDIS_REPLY_STRING:
//  处理字符串回复
case REDIS_REPLY_NIL:
//  处理空
case REDIS_REPLY_ARRAY:
//  处理多条字符串回复
return out;
}
复制代码

那好哥哥要问了,怎么才能看到 Redis 服务端返回的“真正”结果呢?可以使用 nc 命令、telnet 命 令、甚至写一个 socket 程序进行模拟。以nc命令举个栗子

## 连接到Redis
nc 127.0.0.1 6379
## 执行set命令
set hello world
## 状态回复返回
+OK
## 执行一个不存在的命令
sethx
## 错误回复
-ERR unknown command 'sethx'
## 执行加法操作
incr counter
## 整数回复
:1
## 执行取值操作
get hello
## 字符串回复
$5
world
## 批量设置多个键值对
mset java jedis python redis-py
## 批量获取mget
mget java python
## 返回结果条数
*2
##字符串回复
$5
jedis
$8
redis-py
## 如果key不存在,返回的就是 $-1
get not_exist
## 无论是字符串回复还是多条字符串回复,如果有nil值,那么会返回 $-1。
$-1
复制代码

总结

注意点

由于上面讲的协议都是基于请求-响应,但是需要排除以下功能点:

  1. Redis 支持管道操作,不熟悉的好哥哥们可以看看Redis Pipeline 这一篇就够了(整个系列基本基础都有了)。所以客户可以一次发送多个命令,稍后等待回复。
  2. 当 Redis 客户端订阅 Pub/Sub模式的通道时,协议会改变语义变成推送协议,也就是说,客户端不再需要发送命令,因为服务器一旦收到消息就会自动向客户端发送该新消息(对于订阅了通道的客户端)。
  3. Redis 集群使用的是不同的二进制协议来交换节点之间的消息,并不是该协议。

Gossip协议

概述

确定不了解一下 Redis cluster 的通讯协议吗?其实这一块可以选择不弄的,主要吧cluster 的通讯协议有一个别名,叫做疫情传播算法或者流行病协议。受疫情影响,所以就还是一起来了解一下,因为本身我对这一块了解的还是比较少的(我是个只会CRUD 初级攻城狮)。

首先呢,cluster 的通讯协议使用的是Gossip 协议。上面也说了,aka 流行病协议疫情传播算法。为什么这么叫等下看看Gossip 协议对应的原理好哥哥们就懂了。

Gossip协议是一个去中心化思路的分布式通信协议。Gossip 协议于 1987 年在ACM上发表的论文Epidemic Algorithms for Replicated Database Maintenance中被提出,主要用在分布式数据库系统中各个副本节点间的数据同步。这种场景的一个最大特点就是组成网络的节点都是对等的,网络中即使有的节点因宕机而重启,或有新节点加入,但经过一段时间后,这些节点的状态也会与其他节点达成一致,也就是说,Gossip天然具有分布式容错的优点。

另外Gossip是一个带冗余的容错算法,是一个最终一致性算法。虽然无法保证在某个时刻所有节点状态一致,但可以保证在最终(最终是一个现实中存在,但理论上无法证明的时间点)所有节点一致。实际上Gossip可以用于众多能接受最终一致性的领域,比如说失败检测路由同步Pub/Sub等。但是Gossip的缺点也很明显,冗余通信会对网路带宽、CPU资源造成很大的负载,而这些负载又受限于通信频率,该频率又影响着算法收敛的速度,因此,针对不同的应用场景,也有很多的优化方法。

Gossip 传播消息的过程主要如下图:

传播

注意点

  1. Gossip 过程是由一个种子节点(可以理解成“毒王”)发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机的选择周围几个节点散播消息,收到消息的节点也会重复该过程,直至最终网络中所有的节点都收到了消息。
  2. Gossip 过程是异步的,也就是说发消息的节点不会关注对方是否收到,即不等待响应。不管对方有没有收到,它都会每隔 1 秒(假设)向周围节点发消息。异步是它的优点,而消息冗余则是它的缺点。

Cluster 中的 Gossip

Gossip 协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息。在cluster 中,信息的种类分为ping消息pong消息meet消息fail消息等(下面会介绍这几种消息), 整体的节点通讯的过程大概如下:

  1. 集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000
  2. 每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
  3. 接收到ping消息的节点用pong消息作为响应。

集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息变更等事件发生时,通过不断的ping/pong消息通信,经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的(最终一致性)。

Gossip 消息

Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息,常用的Gossip消息就是上面提到的那四种。在cluster 中的每个节点都维护了一份自己视角下的当前整个集群的状态,主要包括:

  • 当前集群状态。

  • 集群中各节点所负责的 slots信息,及其migrate状态。

  • 集群中各节点的master-slave状态。

  • 集群中各节点的存活状态及怀疑fail状态。 节点信息通讯模式如下图:

gossip 消息

  • meet消息用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的 ping、pong 消息交换。

  • ping消息是集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。

  • pong消息是当接收到pingmeet消息时,作为响应消息回复给发送方确认消息正常通信。pong 消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。

  • fail消息是当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。

节点选择

上面已经把常用的消息弄清楚了,在注意点中也提到了Gossip是随机来选择节点。那cluster 是怎么选择节点的呢?

cluster集群下节点通信采用固定频率(定时任务每秒执行 10 次)。因此节点每次选择需要通信的节点列表变得非常重要。通信节点选择过多虽然可以做到信息及时交换但成本过高。节点选择过少会降低集群内所有节点彼此信息交换频率,从而影响故障判定、新节点发现等需求的速度。因此cluster集群的Gossip协议需要兼顾信息交换实时性和成本开销。通讯过程如下图:

节点选择
集群内每个节点维护定时任务默认每秒执行10次,每秒会随机选取5个节点找出最久没有通信的节点发送 ping 消息,用于保证Gossip信息交换的随机性。每100毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2,则立刻发送ping消息,防止该节点信息太长时间未更新。

关于这个定时任务是不是很熟悉,在 图解 Redis 哨兵模式 这一篇中就是用了三个定时任务来监控节点的状态,有不熟悉的好哥哥可以翻一翻。

总结

今天这篇的话针对于cluster的通讯协议写的也不是很深,主要理解一下cluster是怎么通讯的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值