Skip to content
Scott Mansfield edited this page Mar 7, 2017 · 5 revisions

Overview

The server component is the piece that establishes the main REPL for each connection. The interface has only one method:

type Server interface {
	Loop()
}

This Loop method is expected to be able to handle a connection from the data contained in the implementation. It is expected to service the connection for the life of the connection. In other words, it's likely to have a common structure:

10 parse
20 process
30 GOTO 10

The constructor function shows some of the pieces that a server is expected to make use of:

type ServerConst func(conns []io.Closer, rp common.RequestParser, o orcas.Orca) Server
  • conns are the external connection and backend connection(s) that will need to be closed if the server is closed. Typically this happens when a client disconnects and the read for the next request fails.
  • rp is an instance of the common.RequestParser interface, which is described below.
  • o is a value that implements the orcas.Orca interface. The listen loop will instantiate the orca from the orcas.OrcaConst that it has. the Orca will contain the references to the handlers it needs to do its job.

Structure and operation

Rend, as it stands today (March 2017), has only one listen / accept loop implementation which is not pluggable. It's a basic accept loop that pulls off new connections and spins off an intermediate goroutine which then spins off a server instance constructed from the given ServerConst. This intermediate goroutine does protocol disambiguation, which is one of the quirks of the Memcached protocol. It must check the first byte to be received over the connection in order to determine if the connection is using the text or the binary protocol. In the future it may be possible to provide custom code to disambiguate in order to support additional protocols, but that is not available yet. The intermediate goroutine is needed in order to prevent a single connection that never sends a request from blocking the accept loop.

Rend server accept loop and protocol disambiguation

Internally the server loop is very straightforward:

  1. Parse the next request
  2. Figure out what kind of request it is
  3. Hand off to the orchestrator
  4. Collect metrics
  5. Repeat

Rend server basic structure

Creating your own server

The server abstraction is not perfect, but the basics are there. You are able to replace most of the logic that the server would perform except for protocol disambiguation. It is recommended to copy the default server if you want to create a different implementation and go from there. The default server has some side effects that are desirable to keep, such as collecting histogram latency metrics and maintaining counts of different types of front-door requests.

RequestParser interface

The common.RequestParser interface is responsible for supplying a stream of requests to the server loop. It has a relatively complex set of return values. The server makes use of all of these in order to fulfill a request.

type RequestParser interface {
	Parse() (Request, RequestType, uint64, error)
}

The return values are as follows:

  • common.Request is an interface that all request types implement. This value's type is given by the second field.
  • common.RequestType is effectively an enum that tells what type of request accompanies it. This return value is born of a misunderstanding of the speed of type switches, but it also allows one to create more readable code.
  • uint64 is the exact timestamp of when a request was received. This is returned from the protocol parser because otherwise the server loop has no visibility into the actual timing and may produce wildly inaccurate timing metrics.
  • error is any error encountered while parsing the request.
Clone this wiki locally