redcon

package module
v0.0.0-...-5c0b88b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2024 License: MIT Imports: 12 Imported by: 0

README

REDCON
GoDoc

Redis compatible server framework for Go

Features

  • Create a Fast custom Redis compatible server in Go
  • Simple interface. One function ListenAndServe and two types Conn & Command
  • Support for pipelining and telnet commands
  • Works with Redis clients such as redigo, redis-py, node_redis, and jedis
  • TLS Support
  • Compatible pub/sub support
  • Multithreaded

This library is also avaliable for Rust and C.

Installing

go get -u github.com/tidwall/redcon

Example

Here's a full example of a Redis clone that accepts:

  • SET key value
  • GET key
  • DEL key
  • PING
  • QUIT
  • PUBLISH channel message
  • SUBSCRIBE channel

You can run this example from a terminal:

go run example/clone.go
package main

import (
	"log"
	"strings"
	"sync"

	"github.com/tidwall/redcon"
)

var addr = ":6380"

func main() {
	var mu sync.RWMutex
	var items = make(map[string][]byte)
	var ps redcon.PubSub
	go log.Printf("started server at %s", addr)
	err := redcon.ListenAndServe(addr,
		func(conn redcon.Conn, cmd redcon.Command) {
			switch strings.ToLower(string(cmd.Args[0])) {
			default:
				conn.WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'")
			case "ping":
				conn.WriteString("PONG")
			case "quit":
				conn.WriteString("OK")
				conn.Close()
			case "set":
				if len(cmd.Args) != 3 {
					conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command")
					return
				}
				mu.Lock()
				items[string(cmd.Args[1])] = cmd.Args[2]
				mu.Unlock()
				conn.WriteString("OK")
			case "get":
				if len(cmd.Args) != 2 {
					conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command")
					return
				}
				mu.RLock()
				val, ok := items[string(cmd.Args[1])]
				mu.RUnlock()
				if !ok {
					conn.WriteNull()
				} else {
					conn.WriteBulk(val)
				}
			case "del":
				if len(cmd.Args) != 2 {
					conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command")
					return
				}
				mu.Lock()
				_, ok := items[string(cmd.Args[1])]
				delete(items, string(cmd.Args[1]))
				mu.Unlock()
				if !ok {
					conn.WriteInt(0)
				} else {
					conn.WriteInt(1)
				}
			case "publish":
				if len(cmd.Args) != 3 {
					conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command")
					return
				}
				conn.WriteInt(ps.Publish(string(cmd.Args[1]), string(cmd.Args[2])))
			case "subscribe", "psubscribe":
				if len(cmd.Args) < 2 {
					conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command")
					return
				}
				command := strings.ToLower(string(cmd.Args[0]))
				for i := 1; i < len(cmd.Args); i++ {
					if command == "psubscribe" {
						ps.Psubscribe(conn, string(cmd.Args[i]))
					} else {
						ps.Subscribe(conn, string(cmd.Args[i]))
					}
				}
			}
		},
		func(conn redcon.Conn) bool {
			// Use this function to accept or deny the connection.
			// log.Printf("accept: %s", conn.RemoteAddr())
			return true
		},
		func(conn redcon.Conn, err error) {
			// This is called when the connection has been closed
			// log.Printf("closed: %s, err: %v", conn.RemoteAddr(), err)
		},
	)
	if err != nil {
		log.Fatal(err)
	}
}

TLS Example

Redcon has full TLS support through the ListenAndServeTLS function.

The same example is also provided for serving Redcon over TLS.

go run example/tls/clone.go

Benchmarks

Redis: Single-threaded, no disk persistence.

$ redis-server --port 6379 --appendonly no
redis-benchmark -p 6379 -t set,get -n 10000000 -q -P 512 -c 512
SET: 941265.12 requests per second
GET: 1189909.50 requests per second

Redcon: Single-threaded, no disk persistence.

$ GOMAXPROCS=1 go run example/clone.go
redis-benchmark -p 6380 -t set,get -n 10000000 -q -P 512 -c 512
SET: 2018570.88 requests per second
GET: 2403846.25 requests per second

Redcon: Multi-threaded, no disk persistence.

$ GOMAXPROCS=0 go run example/clone.go
$ redis-benchmark -p 6380 -t set,get -n 10000000 -q -P 512 -c 512
SET: 1944390.38 requests per second
GET: 3993610.25 requests per second

Running on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7

Contact

Josh Baker @tidwall

License

Redcon source code is available under the MIT License.

Documentation

Overview

Package redcon implements a Redis compatible server framework

Index

Constants

View Source
const (
	Integer = ':'
	String  = '+'
	Bulk    = '$'
	Array   = '*'
	Error   = '-'
)

Various RESP kinds

Variables

This section is empty.

Functions

func AppendAny

func AppendAny(b []byte, v interface{}) []byte

AppendAny appends any type to valid Redis type.

nil             -> null
error           -> error (adds "ERR " when first word is not uppercase)
string          -> bulk-string
numbers         -> bulk-string
[]byte          -> bulk-string
bool            -> bulk-string ("0" or "1")
slice           -> array
map             -> array with key/value pairs
SimpleString    -> string
SimpleInt       -> integer
Marshaler       -> raw bytes
everything-else -> bulk-string representation using fmt.Sprint()

func AppendArray

func AppendArray(b []byte, n int) []byte

AppendArray appends a Redis protocol array to the input bytes.

func AppendBulk

func AppendBulk(b []byte, bulk []byte) []byte

AppendBulk appends a Redis protocol bulk byte slice to the input bytes.

func AppendBulkFloat

func AppendBulkFloat(dst []byte, f float64) []byte

AppendBulkFloat appends a float64, as bulk bytes.

func AppendBulkInt

func AppendBulkInt(dst []byte, x int64) []byte

AppendBulkInt appends an int64, as bulk bytes.

func AppendBulkString

func AppendBulkString(b []byte, bulk string) []byte

AppendBulkString appends a Redis protocol bulk string to the input bytes.

func AppendBulkUint

func AppendBulkUint(dst []byte, x uint64) []byte

AppendBulkUint appends an uint64, as bulk bytes.

func AppendError

func AppendError(b []byte, s string) []byte

AppendError appends a Redis protocol error to the input bytes.

func AppendInt

func AppendInt(b []byte, n int64) []byte

AppendInt appends a Redis protocol int64 to the input bytes.

func AppendNull

func AppendNull(b []byte) []byte

AppendNull appends a Redis protocol null to the input bytes.

func AppendOK

func AppendOK(b []byte) []byte

AppendOK appends a Redis protocol OK to the input bytes.

func AppendString

func AppendString(b []byte, s string) []byte

AppendString appends a Redis protocol string to the input bytes.

func AppendTile38

func AppendTile38(b []byte, data []byte) []byte

AppendTile38 appends a Tile38 message to the input bytes.

func AppendUint

func AppendUint(b []byte, n uint64) []byte

AppendUint appends a Redis protocol uint64 to the input bytes.

func ListenAndServe

func ListenAndServe(addr string, options Options, rh *redHub) error

func NewRedHub

func NewRedHub(
	onOpened func(c *Conn) (out []byte, action Action),
	onClosed func(c *Conn, err error) (action Action),
	handler func(cmd resp.Command, out []byte) ([]byte, Action),
) *redHub

Types

type Action

type Action int
const (
	None Action = iota
	Close
	Shutdown
)

type Conn

type Conn struct {
	gnet.Conn
	// contains filtered or unexported fields
}

func (*Conn) Flush

func (c *Conn) Flush() error

func (*Conn) ReadCommand

func (c *Conn) ReadCommand() (resp.Command, error)

func (*Conn) WriteArray

func (c *Conn) WriteArray(count int)

func (*Conn) WriteBulkString

func (c *Conn) WriteBulkString(bulk string)

func (*Conn) WriteError

func (c *Conn) WriteError(msg string)

func (*Conn) WriteInt

func (c *Conn) WriteInt(num int)

func (*Conn) WriteNull

func (c *Conn) WriteNull()

func (*Conn) WriteString

func (c *Conn) WriteString(str string)

type Kind

type Kind int

Kind is the kind of command

const (
	// Redis is returned for Redis protocol commands
	Redis Kind = iota
	// Tile38 is returnd for Tile38 native protocol commands
	Tile38
	// Telnet is returnd for plain telnet commands
	Telnet
)

func ReadNextCommand

func ReadNextCommand(packet []byte, argsbuf [][]byte) (
	complete bool, args [][]byte, kind Kind, leftover []byte, err error,
)

ReadNextCommand reads the next command from the provided packet. It's possible that the packet contains multiple commands, or zero commands when the packet is incomplete. 'argsbuf' is an optional reusable buffer and it can be nil. 'complete' indicates that a command was read. false means no more commands. 'args' are the output arguments for the command. 'kind' is the type of command that was read. 'leftover' is any remaining unused bytes which belong to the next command. 'err' is returned when a protocol error was encountered.

type Marshaler

type Marshaler interface {
	MarshalRESP() []byte
}

Marshaler is the interface implemented by types that can marshal themselves into a Redis response type from an *Any call. The return value is not check for validity.

type Options

type Options struct {
	Multicore        bool
	LockOSThread     bool
	ReadBufferCap    int
	LB               gnet.LoadBalancing
	NumEventLoop     int
	ReusePort        bool
	Ticker           bool
	TCPKeepAlive     int
	TCPNoDelay       gnet.TCPSocketOpt
	SocketRecvBuffer int
	SocketSendBuffer int
	TLSConfig        *tls.Config
}

type PubSub

type PubSub struct {
	// contains filtered or unexported fields
}

func NewPubSub

func NewPubSub() *PubSub

func (*PubSub) Psubscribe

func (ps *PubSub) Psubscribe(conn *Conn, channel string)

func (*PubSub) Publish

func (ps *PubSub) Publish(channel, message string) int

func (*PubSub) Subscribe

func (ps *PubSub) Subscribe(conn *Conn, channel string)

type RESP

type RESP struct {
	Type  Type
	Raw   []byte
	Data  []byte
	Count int
}

func ReadNextRESP

func ReadNextRESP(b []byte) (n int, resp RESP)

ReadNextRESP returns the next resp in b and returns the number of bytes the took up the result.

func (RESP) Bytes

func (r RESP) Bytes() []byte

func (RESP) Exists

func (r RESP) Exists() bool

func (RESP) Float

func (r RESP) Float() float64

func (RESP) ForEach

func (r RESP) ForEach(iter func(resp RESP) bool)

ForEach iterates over each Array element

func (RESP) Int

func (r RESP) Int() int64

func (RESP) Map

func (r RESP) Map() map[string]RESP

Map returns a key/value map of an Array. The receiver RESP must be an Array with an equal number of values, where the value of the key is followed by the key. Example: key1,value1,key2,value2,key3,value3

func (RESP) MapGet

func (r RESP) MapGet(key string) RESP

func (RESP) String

func (r RESP) String() string

type SimpleError

type SimpleError error

SimpleError is for representing an error without adding the "ERR" prefix from an *Any call.

type SimpleInt

type SimpleInt int

SimpleInt is for representing a non-bulk representation of a int from an *Any call.

type SimpleString

type SimpleString string

SimpleString is for representing a non-bulk representation of a string from an *Any call.

type Type

type Type byte

Type of RESP

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL