为什么 redis-benchmark 命令没有遵循 Redis 协议?

huangapple go评论71阅读模式
英文:

How come the redis-benchmark command is not following the redis protocol?

问题

我正在直接从运行redis-benchmark命令后的TCP连接中读取数据,据我所知,redis-benchmark并没有遵循Redis协议。

Redis协议在其网站上的说明如下:

> RESP在Redis中作为请求-响应协议的使用方式如下:
>
> - 客户端将命令作为RESP数组的批量字符串发送给Redis服务器。
> - 服务器根据命令的实现情况回复一个RESP类型。

这意味着正确的客户端实现必须始终发送RESP数组的批量字符串

如果这是真的,那么任何不以*开头的内容都被视为语法错误(因为它不是RESP数组)。

因此,如果要向Redis服务器发送ping命令,它必须作为长度为1的RESP数组发送,其中包含一个批量字符串,该字符串包含单词ping。例如:

> "*1\r\n$4\r\nPING\r\n"

然而,当我直接监听redis-benchmark命令并读取其TCP连接时,我得到的是:

> "PING\r\n"

不符合Redis协议。这是一个bug还是Redis协议中有一些隐含的特殊情况?据我所知,我找不到任何关于ping命令特殊或长度为1的命令特殊的说明。有人知道发生了什么吗?

要自己复现这些结果,你可以复制我的代码进行直接检查:

package main

import (
	"fmt"
	"log"
	"net"
)

func main() {
	RedisBenchmark()
}

func RedisBenchmark() {
	url := "127.0.0.1:6379"
	fmt.Println("listen: ", url)
	ln, err := net.Listen("tcp", url) //在本地网络上进行通告
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := ln.Accept() //等待并返回到侦听器的下一个连接
		if err != nil {
			log.Fatal(err)
		}

		tcpConn := conn.(*net.TCPConn)
		go HandleConnection(tcpConn)
	}
}

func HandleConnection(tcpConn *net.TCPConn) {
	b := make([]byte, 256) //TODO 一次应该读取多少字节?
	n, err := tcpConn.Read(b)
	if err != nil {
		fmt.Println("n: ", n)
		log.Fatal(err)
	}
	fmt.Printf("+++++> 原始输入字符串(b):%q\n", string(b))
	msg := string(b[:n])
	fmt.Printf("+++++> 原始输入消息:%q\n", msg)
}

使用以下命令在go中运行它:

go run main.go

然后在另一个终端(或tmux窗格)中运行:

redis-benchmark

如果你只想使用一个客户端运行ping命令:

redis-benchmark -c 1 -t ping -n 1

你可以在这里查看我使用的标志的详细信息:http://redis.io/topics/benchmarks

英文:

I was reading in directly from a tcp connection after running the redis-benchmark command and as far as I can tell, redis-benchmark is NOT following the redis protocol.

The redis protocol is as stated in its website:

> The way RESP is used in Redis as a request-response protocol is the
> following:
>
> - Clients send commands to a Redis server as a RESP Array of Bulk Strings.
> - The server replies with one of the RESP types according to the command implementation.

Meaning that a correct client implementation must always send RESP arrays of bulk strings.

If that is true, then, anything that does not start with a * is considered a syntax error (since its not an RESP array).

Thus, if one were to send a ping command to a redis-server, then it must be sent as a resp array of length 1 with 1 bulk string containing the word ping. For example:

> "*1\r\n$4\r\nPING\r\n"

However, whenever I listen directly to the redis-benchmark command and read its tcp connection I get instead:

> "PING\r\n"

which does not follow the redis protocol. Is that a bug or is there something implied in the redis protocol that makes pings special? As far as I could tell I couldn't find anything that said that pings were special, nor that length 1 commands were special. Does someone know whats going on?

To see reproduce these results yourself you can copy my code to inspect it directly:

package main

import (
	"fmt"
	"log"
	"net"
)

func main() {
	RedisBenchmark()
}

func RedisBenchmark() {
	url := "127.0.0.1:6379"
	fmt.Println("listen: ", url)
	ln, err := net.Listen("tcp", url) //announces on local network
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := ln.Accept() //waits and returns the next connection to the listener
		if err != nil {
			log.Fatal(err)
		}

		tcpConn := conn.(*net.TCPConn)
		go HandleConnection(tcpConn)
	}
}

func HandleConnection(tcpConn *net.TCPConn) {
	b := make([]byte, 256) //TODO how much should I read at a time?
	n, err := tcpConn.Read(b)
	if err != nil {
		fmt.Println("n: ", n)
		log.Fatal(err)
	}
	fmt.Printf("+++++> raw input string(b): %q\n", string(b))
	msg := string(b[:n])
	fmt.Printf("+++++> raw input msg: %q\n", msg)
}

and run it using go with:

go run main.go

followed on a different terminal (or tmux pane):

redis-benchmark

for all the test or if you only want to run ping with 1 client:

redis-benchmark -c 1 -t ping -n 1

you can see the details of how I am running it with the flags at: http://redis.io/topics/benchmarks

答案1

得分: 5

那被称为内联命令。请查看Redis协议文章中的内联命令部分。

英文:

That is called an inline command. Check the Inline Commands section of the Redis Protocol article.

答案2

得分: 0

你可以参考源代码来了解内联命令和RESP之间的区别。

readQueryFromClient
|-->  如果命令以 * 开头 --> processInlineBuffer()将其处理为RESP
|
|-->  如果命令不以 * 开头 --> processMultibulkBuffer():将其处理为内联命令

RESP是解析Redis服务器命令的一种更高效的方式。

英文:

You can refer to the source code to find out the differences between inline command and RESP.

readQueryFromClient
|-->  if command begins with * --> processInlineBuffer()process it as RESP
|
|-->  if command not begins with * --> processMultibulkBuffer():process it as inline command 

RESP is a more efficent way to parse the command for the Redis Server

huangapple
  • 本文由 发表于 2014年8月10日 11:43:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/25225333.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定