文章目录

  • redis协议简介
  • redis响应格式:
  • 使用wireshark 抓取redis数据包
  • 写一个redistemplate 的测试类, 向redis发送数据
  • 分析sentinel数据包
  • 分析redis server数据包


redis协议简介

redis使用的通信协议是RESP(REdis Serialization Protocol), 是一种简便, 可读性很好的通信协议

以下内容摘自RESP2的文档内容

RESP3的文档地址: https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md, 对RESP2进行了扩充, 比如以","开头的浮点数返回,1.23<CR><LF>, 感兴趣的可以看看, 帮助你更好的了解和使用redis.

redis响应格式:

简单字符串

第一个字节是 “+”

用于非二进制安全的简短字符串应答, 以CRLF结束, 网络字节值为0x0d0x0a, 字符串"\r\n"

注意, 一般情况是"\r\n"的字节编码就是0x0d,0x0a, 但是有些编码格式则不同, 这种情况会发生错误

例如: +PONG<CR><LF> , +OK<CR><LF>

错误信息:

第一个字节是"-"

类似于简单字符串信息, 只是用于表达错误信息, 也是以CRLF结束

例如:

-Error message<CR><LF>

-ERR unknown command 'foobar'<CR><LF>

-WRONGTYPE Operation against a key holding the wrong kind of value<CR><LF>

整数信息:

第一个字节是":"

用于进行整数信息回复, 比如命令: INCR, LLEN, LASTSAVE等

例如:

:0<CR><LF>

:10000<CR><LF>

大容量字符串:

第一个字节是:"$"

单行二进制安全字符串响应, 最大长度512mb, "$"后面的数组表示内容长度

例如:

$6<CR><LF>foobar<CR><LF>

$-1<CR><LF> 回复一个空(NULL)字符串, 相当于null, nil, 客户端请求一个nil对象时, 会返回这个, 这个不同于"", ""表示格式是 $0<CR><LF>

数组列表信息:

第一个字节是: “*”

返回一个列表信息, "*"后面的数字表示元素个数, LRANGE之类的指令会返回这种格式信息

例如:

*0<CR><LF> 表示空列表

*2<CR><LF>$3<CR><LF>foo<CR><LF>$3<CR><LF>bar<CR><LF> 长度为2的列表, 俩个元素内容分别为foo和bar

*-1<CR><LF> NULL列表, BLPOP如果超时了, 就会返回这种信息

*2<CR><LF> *3<CR><LF>:1<CR><LF>:2<CR><LF>:4<CR><LF> *2<CR><LF>+OK<CR><LF>-ERR<CR><LF>这是一个嵌套列表信息

使用wireshark 抓取redis数据包

配置抓包过滤 host 10.229.99.168 10.229.99.168是redis的服务地址

点击开始抓取进行抓包

写一个redistemplate 的测试类, 向redis发送数据

RedisSentinelConfiguration rsc = new RedisSentinelConfiguration();
        rsc.addSentinel(
                new RedisNode("10.229.99.168", 7379));
        rsc.setPassword("我是密码");

        rsc.setMaster("master1");

        JedisPoolConfig jpc = new JedisPoolConfig();

        jpc.setMaxTotal(10);
        jpc.setMaxIdle(5);
        jpc.setTestOnBorrow(true);
        jpc.setMaxWaitMillis(500L);

        JedisConnectionFactory factory =  new JedisConnectionFactory(rsc, jpc);
        factory.afterPropertiesSet();
        RedisTemplate<String, String> redisT = new RedisTemplate<>();
        redisT.setConnectionFactory(factory);
        redisT.setDefaultSerializer(new StringRedisSerializer());
        redisT.afterPropertiesSet();
        redisT.opsForList().leftPush("tlist", "test");
        redisT.delete("tlist");

运行, 这时可以在wireshark中查看redis数据包了

分析sentinel数据包

配置显示过滤, 先过滤出sentinel数据包, tcp.port eq 7379 7379是redis sentinel的接口

首先看到三次握手, 建立tcp连接

1	0.000000	172.24.123.111	10.229.99.168	TCP	66	51606 → 7379 [SYN] Seq=467271664 Win=64680 Len=0 MSS=1470 WS=256 SACK_PERM=1
2	0.043827	10.229.99.168	172.24.123.111	TCP	60	7379 → 51606 [SYN, ACK] Seq=4095492181 Ack=467271665 Win=14600 Len=0 MSS=1460
3	0.043964	172.24.123.111	10.229.99.168	TCP	54	51606 → 7379 [ACK] Seq=467271665 Ack=4095492182 Win=64680 Len=0

接着客户端给redis-sentinel服务发送数据:

4	0.051048	172.24.123.111	10.229.99.168	TCP	115	55750 → 7379 [PSH, ACK] Seq=957963677 Ack=344093169 Win=64680 Len=61

数据内容显示如下

*3
$8
SENTINEL
$23
get-master-addr-by-name
$7
master1

服务端发送ack数据包, 确认收到数据:

5	0.094628	10.229.99.168	172.24.123.111	TCP	60	7379 → 55750 [ACK] Seq=344093169 Ack=957963738 Win=14600 Len=0

服务端接着发送客户端指令get-master-addr-by-name的响应数据

6	0.095346	10.229.99.168	172.24.123.111	TCP	87	7379 → 55750 [PSH, ACK] Seq=344093169 Ack=957963738 Win=14600 Len=33

数据内容显示如下:

*2
$12
10.229.99.168
$4
6480

返回了redis master server 的地址和端口号, 10.229.99.168:6480 , 6480位redis master server的端口号, 接着这个sentinel连接会随着客户端发送rst包强制断开。

新的sentinel连接会开启,顺序如下:

  1. 三次握手, 建立连接
  2. 客户端发送指令 *3$8SENTINEL$23get-master-addr-by-name$7master1, 请求获取主服务地址
  3. 服务端响应, *2$1210.229.99.168$46480
  4. 客户端会发送订阅主服务切换指令
14	0.219555	172.24.123.111	10.229.99.168	TCP	94	55751 → 7379 [PSH, ACK] Seq=2989715116 Ack=3319979599 Win=64647 Len=40

数据包内容如下:

*2
$9
SUBSCRIBE
$14
+switch-master
  1. sentinel服务端返回订阅成功的信息
15	0.267211	10.229.99.168	172.24.123.111	TCP	98	7379 → 55751 [PSH, ACK] Seq=3319979599 Ack=2989715156 Win=14600 Len=44

数据包内容如下:

*3
$9
subscribe
$14
+switch-master
:1
  1. 保持连接, 如果服务器上redis服务发生主从切换, sentinel服务端会返回信息如下:
*3
$7
message
$14
+switch-master
$43
master1 10.229.99.168 6480 10.229.99.168 6479

redis主服务从10.229.99.168:6480切换到0.229.99.168:6479

  1. 这就是redis sentinel的数据交互的过程, 再看下redis服务的数据交互

分析redis server数据包

配置显示过滤为 tcp.port in {6479, 6480} 6480, 6479分别为 redis 主, 从服务端口

这时候, 我们就把redis server交互的数据包过滤出来了。

  1. 首先是三次握手, 建立连接
17	0.489588	172.24.123.111	10.229.99.168	TCP	66	55752 → 6480 [SYN] Seq=2488491565 Win=64680 Len=0 MSS=1470 WS=256 SACK_PERM=1
18	0.533252	10.229.99.168	172.24.123.111	TCP	60	6480 → 55752 [SYN, ACK] Seq=2932666663 Ack=2488491566 Win=14600 Len=0 MSS=1460
19	0.533311	172.24.123.111	10.229.99.168	TCP	54	55752 → 6480 [ACK] Seq=2488491566 Ack=2932666664 Win=64680 Len=0
  1. 客户端向redis server服务端发送认证请求
*2
$4
AUTH
$14
Password-value

Password-value是redis server配置中的认证密码

  1. 服务端返回认证成功的指令+OK<CR><LF>, 如果密码错误的话, 会返回错误信息: -ERR invalid password<CR><LF>
  2. 客户端发送PING指令
*1
$4
PING
  1. 服务端响应PONG
+PONG<CR><LF>
  1. 客户端发送存储列表数据指令
*3
$5
LPUSH
$5
tlist
$4
test
  1. 服务端响应存入列表的元素个数
:1<CR><LF>
  1. 客户端发送PING
  2. 服务端响应PONG
  3. 客户端发送删除tlist指令
*2
$3
DEL
$5
tlist
  1. 服务端响应删除键的个数
:1<CR><LF>