Download source code:
https://github.com/challenai/wtfcache
Expose high performance TCP API for users #
Different from HTTP protocol, we build TCP service from scratch without any framework,
The main reason is that the TCP only serves in the specific high performance case,
the protocol is not actually standardized due to the consideration of performance.
Since it’s a cache application, we can communicate with the client via redis compatible protocol, so that the redis client can talk to our application directly. By the way, It’s not difficult to design a new protocol for a special use case, but it’s unnecessary to repeat.
The current Redis protocol is called RESP V3 (Redis Serialization Protocol, Version 3).
redis RESP V3 #
Our cache application provides 3 interfaces: get/set/delete.
Therefore we need to provide 3 commands according to RESP.
The set request should be SET mykey myvalue\r\n
.
The successful set response should be +OK\r\n
.
The get request should be GET mykey\r\n
.
The successful get response should be $7\r\nmyvalue\r\n
.
The delete request should be DEL mykey\r\n
.
The successful delete response should be +OK\r\n
.
The failed response with error message should be -ERR xxx\r\n
.
build basic server #
In the case of someone who hasn’t tried TCP programming and feels scared,
actually, there are no magic inside a TCP connection, it’s pretty standard.
If you program with C, you need to create a socket, bind socket to an address, listen and accept.
However I will tell you it’s all bullshit, there are history reasons, the socket concept and all the details are inherited from system calls.
The program itself doesn’t provide any convenience compared with some modern languages like Rust, Go or node.js(2012).
The truth is that, in 99% cases, we create the servers with some template code even in C/C++,
because most of the time we create the TCP server, not local unix domain socket server,
it’s unnecessary to consider too much about hundreads of different parameters and error case,
it’s all covered by language itself.
In golang, we need to listen to some addresses.
And start accepting connections.
It’s simple, but no performance loss because of the internal battery: epoll inside.
And of course, less bugs.
// step 1
server, err := net.Listen("tcp", addr)
// handle error
// step 2
for {
connection, err := server.Accept()
// handle error
}
handle bytes according our protocol #
We don’t have authorization for connections, therefore we can handle requests from the first bytes.
The protocol is line based, therefore we can create a buffer utils to read line and write line, read and write bytes.
type Buffer struct {
w *bufio.Writer
r *bufio.Reader
}
func (c *Buffer) ReadLine() (string, error) {
// read line with standard library api
line, err := c.r.ReadSlice(seperator)
}
func WriteLine(l string) {
// write line with standard library api
c.w.Write(l)
}
As a result, our TCP server could simply run as follows.
type Client struct {
buf *buffer.Buffer
}
func (c *Client) Read() error {
line, err := c.buf.ReadLine()
switch start word of line:
case "GET":
// handleGetRequst
case "SET":
// handleSetRequst
case "DEL":
// handleDelRequst
default:
// error
}