使用TCPDump分析Redis的Pipeline比Multi更快的原因

网上对于Redis的Pipeline和Multi的两种模式的速度对比的文章,都大概只提到了Pipeline比Multi更快,原因是Pipeline是一次性全部发送,一次性全部执行,诸如此类吧啦吧啦。

我的疑问是:依据从哪而来?Pipeline真的就是等所有请求都收到后才一次性执行的吗?难道Multi就不是一次性执行的了吗?

其中一篇参考文章:http://blog.fbbin.com/archives/1277

由于本人太忙不想去阅读Redis Server的源代码(其实是懒),就通过TCPDump来分析吧。

步骤:

1. 先在Redis Server上启动TCPDump: tcpdump -i eno16777736 port 6379 -X

eno16777736是我的虚拟机网卡,吃瓜群众们替换成自己的就行。

2. 测试Multi:

<?php

$redis = new redis();
$result = $redis->connect('master104', 6379);

$multi = $redis->multi();

for($i=0;$i<100;$i++)
$multi->set('testkey' . (string)$i, 'helloworld' . (string)$i);

$replies = $multi->exec();

var_dump($replies);

?>

观察TCPDump数据:

19:04:50.987407 IP slave105.50396 > master104.6379: Flags [P.], seq 1:16, ack 1, win 115, options [nop,nop,TS val 2306103 ecr 29105036], length 15
0x0000: 4500 0043 59ec 4000 4006 5ca7 c0a8 0169 E..CY.@.@.....i
0x0010: c0a8 0168 c4dc 18eb 6289 69dd 89bf b38e ...h....b.i.....
0x0020: 8018 0073 67c1 0000 0101 080a 0023 3037 ...sg........#07
0x0030: 01bc 1b8c 2a31 0d0a 2435 0d0a 4d55 4c54 ....*1..$5..MULT    //客户端请求启动Multi
0x0040: 490d 0a I..

19:04:50.987662 IP master104.6379 > slave105.50396: Flags [P.], seq 1:6, ack 16, win 114, options [nop,nop,TS val 29105037 ecr 2306103], length 5
0x0000: 4500 0039 a803 4000 4006 0e9a c0a8 0168 E..9..@.@......h
0x0010: c0a8 0169 18eb c4dc 89bf b38e 6289 69ec ...i........b.i.
0x0020: 8018 0072 844d 0000 0101 080a 01bc 1b8d ...r.M..........
0x0030: 0023 3037 2b4f 4b0d 0a .#07+OK..                //服务端回应启动Multi成功

19:04:50.988418 IP slave105.50396 > master104.6379: Flags [P.], seq 16:61, ack 6, win 115, options [nop,nop,TS val 2306104 ecr 29105037], length 45
0x0000: 4500 0061 59ee 4000 4006 5c87 c0a8 0169 E..aY.@.@.....i    //客户端请求Set("testkey0")
0x0010: c0a8 0168 c4dc 18eb 6289 69ec 89bf b393 ...h....b.i.....
0x0020: 8018 0073 4e5a 0000 0101 080a 0023 3038 ...sNZ.......#08
0x0030: 01bc 1b8d 2a33 0d0a 2433 0d0a 5345 540d ....*3..$3..SET.
0x0040: 0a24 380d 0a74 6573 746b 6579 300d 0a24 .$8..testkey0..$
0x0050: 3131 0d0a 6865 6c6c 6f77 6f72 6c64 300d 11..helloworld0.
0x0060: 0a .
19:04:50.988551 IP master104.6379 > slave105.50396: Flags [P.], seq 6:15, ack 61, win 114, options [nop,nop,TS val 29105038 ecr 2306104], length 9
0x0000: 4500 003d a804 4000 4006 0e95 c0a8 0168 E..=..@.@......h    //服务端返回testkey0已加入队列。
0x0010: c0a8 0169 18eb c4dc 89bf b393 6289 6a19 ...i........b.j.
0x0020: 8018 0072 8451 0000 0101 080a 01bc 1b8e ...r.Q..........
0x0030: 0023 3038 2b51 5545 5545 440d 0a .#08+QUEUED..
19:04:50.989330 IP slave105.50396 > master104.6379: Flags [P.], seq 61:106, ack 15, win 115, options [nop,nop,TS val 2306105 ecr 29105038], length 45
0x0000: 4500 0061 59ef 4000 4006 5c86 c0a8 0169 E..aY.@.@.....i      //客户端请求Set("testkey1")
0x0010: c0a8 0168 c4dc 18eb 6289 6a19 89bf b39c ...h....b.j.....
0x0020: 8018 0073 4c22 0000 0101 080a 0023 3039 ...sL".......#09
0x0030: 01bc 1b8e 2a33 0d0a 2433 0d0a 5345 540d ....*3..$3..SET.
0x0040: 0a24 380d 0a74 6573 746b 6579 310d 0a24 .$8..testkey1..$
0x0050: 3131 0d0a 6865 6c6c 6f77 6f72 6c64 310d 11..helloworld1.
0x0060: 0a .
19:04:50.989817 IP master104.6379 > slave105.50396: Flags [P.], seq 15:24, ack 106, win 114, options [nop,nop,TS val 29105039 ecr 2306105], length 9
0x0000: 4500 003d a805 4000 4006 0e94 c0a8 0168 E..=..@.@......h    //服务端返回testkey1已加入队列。
0x0010: c0a8 0169 18eb c4dc 89bf b39c 6289 6a46 ...i........b.jF
0x0020: 8018 0072 8451 0000 0101 080a 01bc 1b8f ...r.Q..........
0x0030: 0023 3039 2b51 5545 5545 440d 0a .#09+QUEUED..
19:04:50.990195 IP slave105.50396 > master104.6379: Flags [P.], seq 106:151, ack 24, win 115, options [nop,nop,TS val 2306106 ecr 29105039], length 45
0x0000: 4500 0061 59f0 4000 4006 5c85 c0a8 0169 E..aY.@.@.....i    //客户端请求Set("testkey2")
0x0010: c0a8 0168 c4dc 18eb 6289 6a46 89bf b3a5 ...h....b.jF....
0x0020: 8018 0073 49ea 0000 0101 080a 0023 303a ...sI........#0:
0x0030: 01bc 1b8f 2a33 0d0a 2433 0d0a 5345 540d ....*3..$3..SET.
0x0040: 0a24 380d 0a74 6573 746b 6579 320d 0a24 .$8..testkey2..$
0x0050: 3131 0d0a 6865 6c6c 6f77 6f72 6c64 320d 11..helloworld2.
0x0060: 0a .
19:04:50.990285 IP master104.6379 > slave105.50396: Flags [P.], seq 24:33, ack 151, win 114, options [nop,nop,TS val 29105040 ecr 2306106], length 9
0x0000: 4500 003d a806 4000 4006 0e93 c0a8 0168 E..=..@.@......h    //服务端返回testkey2已加入队列。
0x0010: c0a8 0169 18eb c4dc 89bf b3a5 6289 6a73 ...i........b.js
0x0020: 8018 0072 8451 0000 0101 080a 01bc 1b90 ...r.Q..........
0x0030: 0023 303a 2b51 5545 5545 440d 0a .#0:+QUEUED..
19:04:50.990578 IP slave105.50396 > master104.6379: Flags [P.], seq 151:165, ack 33, win 115, options [nop,nop,TS val 2306106 ecr 29105040], length 14
0x0000: 4500 0042 59f1 4000 4006 5ca3 c0a8 0169 E..BY.@.@.....i
0x0010: c0a8 0168 c4dc 18eb 6289 6a73 89bf b3ae ...h....b.js....
0x0020: 8018 0073 bc17 0000 0101 080a 0023 303a ...s.........#0:
0x0030: 01bc 1b90 2a31 0d0a 2434 0d0a 4558 4543 ....*1..$4..EXEC       //客户端请求exec,既执行整个multi
0x0040: 0d0a ..
19:04:50.990828 IP master104.6379 > slave105.50396: Flags [P.], seq 33:52, ack 165, win 114, options [nop,nop,TS val 29105040 ecr 2306106], length 19
0x0000: 4500 0047 a807 4000 4006 0e88 c0a8 0168 E..G..@.@......h
0x0010: c0a8 0169 18eb c4dc 89bf b3ae 6289 6a81 ...i........b.j.
0x0020: 8018 0072 845b 0000 0101 080a 01bc 1b90 ...r.[..........
0x0030: 0023 303a 2a33 0d0a 2b4f 4b0d 0a2b 4f4b .#0:*3..+OK..+OK      //服务端执行整个multi,并一次性返回所有Set()的结果
0x0040: 0d0a 2b4f 4b0d 0a ..+OK..

3. 测试Pipeline:

<?php

$redis = new redis();
$result = $redis->connect('master104', 6379);

$pipe = $redis->multi(Redis::PIPELINE);

for($i=0;$i<100;$i++)
$pipe->set('testkey' . (string)$i, 'helloworld' . (string)$i);

$replies = $pipe->exec();

var_dump($replies);

 

观察TCPDump数据:

19:13:59.922411 IP slave105.50404 > master104.6379: Flags [.], seq 1:4345, ack 1, win 115, options [nop,nop,TS val 2855039 ecr 29653969], length 4344
0x0000: 4500 112c 48a8 4000 4006 5d02 c0a8 0169 E..,H.@.@.]....i
0x0010: c0a8 0168 c4e4 18eb ebe9 d8dc 3d91 f923 ...h........=..#
0x0020: 8010 0073 9540 0000 0101 080a 002b 907f ...s.@.......+..
0x0030: 01c4 7bd1 2a33 0d0a 2433 0d0a 5345 540d ..{.*3..$3..SET.
0x0040: 0a24 380d 0a74 6573 746b 6579 300d 0a24 .$8..testkey0..$
0x0050: 3131 0d0a 6865 6c6c 6f77 6f72 6c64 300d 11..helloworld0.
0x0060: 0a2a 330d 0a24 330d 0a53 4554 0d0a 2438 .*3..$3..SET..$8
0x0070: 0d0a 7465 7374 6b65 7931 0d0a 2431 310d ..testkey1..$11.
0x0080: 0a68 656c 6c6f 776f 726c 6431 0d0a 2a33 .helloworld1..*3
0x0090: 0d0a 2433 0d0a 5345 540d 0a24 380d 0a74 ..$3..SET..$8..t
0x00a0: 6573 746b 6579 320d 0a24 3131 0d0a 6865 estkey2..$11..he
0x00b0: 6c6c 6f77 6f72 6c64 320d 0a2a 330d 0a24 lloworld2..*3..$
...

(此处省略中间数据包)

...

0x10f0: 0a24 3132 0d0a 6865 6c6c 6f77 6f72 6c64 .$12..helloworld      
0x1100: 3931 0d0a 2a33 0d0a 2433 0d0a 5345 540d 91..*3..$3..SET.
0x1110: 0a24 390d 0a74 6573 746b 6579 3932 0d0a .$9..testkey92..      
0x1120: 2431 320d 0a68 656c 6c6f 776f $12..hellowo              

到testkey92的时候,由于已经达到了Redis应用数据包的大小,所以先只发送到这,接下来看:

19:16:15.521869 IP slave105.50406 > master104.6379: Flags [P.], seq 4345:4681, ack 1, win 115, options [nop,nop,TS val 2990641 ecr 29789570], length 336
0x0000: 4500 0184 3d25 4000 4006 782d c0a8 0169 E...=%@.@.x-...i
0x0010: c0a8 0168 c4e6 18eb c3fc b008 f41e ad81 ...h............
0x0020: 8018 0073 0f61 0000 0101 080a 002d a231 ...s.a.......-.1
0x0030: 01c6 8d82 726c 6439 320d 0a2a 330d 0a24 ....rld92..*3..$
0x0040: 330d 0a53 4554 0d0a 2439 0d0a 7465 7374 3..SET..$9..test
0x0050: 6b65 7939 330d 0a24 3132 0d0a 6865 6c6c key93..$12..hell

...

0x0160: 0d0a 2439 0d0a 7465 7374 6b65 7939 390d ..$9..testkey99.    //此时全部100个发送完毕
0x0170: 0a24 3132 0d0a 6865 6c6c 6f77 6f72 6c64 .$12..helloworld
0x0180: 3939 0d0a 99..

19:16:15.522107 IP master104.6379 > slave105.50406: Flags [P.], seq 1:461, ack 4681, win 204, options [nop,nop,TS val 29789572 ecr 2990641], length 460
0x0000: 4500 0200 2005 4000 4006 94d1 c0a8 0168 E.....@.@......h
0x0010: c0a8 0169 18eb c4e6 f41e ad81 c3fc b158 ...i...........X
0x0020: 8018 00cc 8614 0000 0101 080a 01c6 8d84 ................
0x0030: 002d a231 2b4f 4b0d 0a2b 4f4b 0d0a 2b4f .-.1+OK..+OK..+O    //服务端将100个请求全部执行完成后,一次性返回所有结果
0x0040: 4b0d 0a2b 4f4b 0d0a 2b4f 4b0d 0a2b 4f4b K..+OK..+OK..+OK
0x0050: 0d0a 2b4f 4b0d 0a2b 4f4b 0d0a 2b4f 4b0d ..+OK..+OK..+OK.
0x0060: 0a2b 4f4b 0d0a 2b4f 4b0d 0a2b 4f4b 0d0a .+OK..+OK..+OK..

...

19:16:15.522221 IP master104.6379 > slave105.50406: Flags [P.], seq 461:501, ack 4681, win 204, options [nop,nop,TS val 29789572 ecr 2990641], length 40
0x0000: 4500 005c 2006 4000 4006 9674 c0a8 0168 E....@.@..t...h
0x0010: c0a8 0169 18eb c4e6 f41e af4d c3fc b158 ...i.......M...X
0x0020: 8018 00cc 8470 0000 0101 080a 01c6 8d84 .....p..........
0x0030: 002d a231 2b4f 4b0d 0a2b 4f4b 0d0a 2b4f .-.1+OK..+OK..+O
0x0040: 4b0d 0a2b 4f4b 0d0a 2b4f 4b0d 0a2b 4f4b K..+OK..+OK..+OK
0x0050: 0d0a 2b4f 4b0d 0a2b 4f4b 0d0a ..+OK..+OK..

总结一下:

1. Multi:

  1.1. 每发送一条指令,都需要单独发给服务器,服务器再单独返回“该条指令已加入队列”这个消息。这是比Pipeline慢的原因之一。

  1.2. Multi执行的时候会先暂停其他命令的执行,类似于加了个锁,直到整个Multi结束完成再继续其他客户端的请求。这是Multi能保证一致性的原因,也是比Pipeline慢的原因之二。(需要读Redis Server的代码,从TCPDump上看不出)

2. Pipeline:

  2.1. 将所有命令打包一次性发送。发送成功后,服务端不用返回类似“命令已收到”这样的消息,而是一次性批量执行所有命令,成功后再一次性返回所有处理结果。

  2.2. 服务端处理命令的时候,不需要加锁,而是与其他客户端的命令混合在一起处理,所以无法保证一致性。

适用场景:

1. 如果要顺序执行一组命令(既网上所谓的“Redis事务”),Multi很合适。

2. 如果要往Redis里批量插入Log, 或者使用Redis List做为队列并插入很多消息的话,Pipleline是挺合适的。 

原文地址:https://www.cnblogs.com/harryc/p/6005165.html