redhub-frame

redhub-frame #

Project introduction #

High-performance Redis-Server multi-threaded framework, based on RawEpoll model.

Features #

  • Ultra high performance
  • Fully multi-threaded support
  • Low CPU resource consumption
  • Compatible with redis protocol
  • Create a Redis compatible server with RawEpoll model in Go

Installing #

go get -u github.com/IceFireDB/redhub

Example #

Here is a simple framework usage example,support the following redis commands:

  • SET key value
  • GET key
  • DEL key
  • PING
  • QUIT

You can run this example in terminal:

package main

import (
	"flag"
	"fmt"
	"log"
	"strings"
	"sync"

	"net/http"
	_ "net/http/pprof"

	"github.com/IceFireDB/redhub"
	"github.com/IceFireDB/redhub/pkg/resp"
)

func main() {
	var mu sync.RWMutex
	var items = make(map[string][]byte)
	var network string
	var addr string
	var multicore bool
	var reusePort bool
	var pprofDebug bool
	var pprofAddr string
	flag.StringVar(&network, "network", "tcp", "server network (default \"tcp\")")
	flag.StringVar(&addr, "addr", "127.0.0.1:6380", "server addr (default \":6380\")")
	flag.BoolVar(&multicore, "multicore", true, "multicore")
	flag.BoolVar(&reusePort, "reusePort", false, "reusePort")
	flag.BoolVar(&pprofDebug, "pprofDebug", false, "open pprof")
	flag.StringVar(&pprofAddr, "pprofAddr", ":8888", "pprof address")
	flag.Parse()
	if pprofDebug {
		go func() {
			http.ListenAndServe(pprofAddr, nil)
		}()
	}

	protoAddr := fmt.Sprintf("%s://%s", network, addr)
	option := redhub.Options{
		Multicore: multicore,
		ReusePort: reusePort,
	}

	rh := redhub.NewRedHub(
		func(c *redhub.Conn) (out []byte, action redhub.Action) {
			return
		},
		func(c *redhub.Conn, err error) (action redhub.Action) {
			return
		},
		func(cmd resp.Command, out []byte) ([]byte, redhub.Action) {
			var status redhub.Action
			switch strings.ToLower(string(cmd.Args[0])) {
			default:
				out = resp.AppendError(out, "ERR unknown command '"+string(cmd.Args[0])+"'")
			case "ping":
				out = resp.AppendString(out, "PONG")
			case "quit":
				out = resp.AppendString(out, "OK")
				status = redhub.Close
			case "set":
				if len(cmd.Args) != 3 {
					out = resp.AppendError(out, "ERR wrong number of arguments for '"+string(cmd.Args[0])+"' command")
					break
				}
				mu.Lock()
				items[string(cmd.Args[1])] = cmd.Args[2]
				mu.Unlock()
				out = resp.AppendString(out, "OK")
			case "get":
				if len(cmd.Args) != 2 {
					out = resp.AppendError(out, "ERR wrong number of arguments for '"+string(cmd.Args[0])+"' command")
					break
				}
				mu.RLock()
				val, ok := items[string(cmd.Args[1])]
				mu.RUnlock()
				if !ok {
					out = resp.AppendNull(out)
				} else {
					out = resp.AppendBulk(out, val)
				}
			case "del":
				if len(cmd.Args) != 2 {
					out = resp.AppendError(out, "ERR wrong number of arguments for '"+string(cmd.Args[0])+"' command")
					break
				}
				mu.Lock()
				_, ok := items[string(cmd.Args[1])]
				delete(items, string(cmd.Args[1]))
				mu.Unlock()
				if !ok {
					out = resp.AppendInt(out, 0)
				} else {
					out = resp.AppendInt(out, 1)
				}
			case "config":
				// This simple (blank) response is only here to allow for the
				// redis-benchmark command to work with this example.
				out = resp.AppendArray(out, 2)
				out = resp.AppendBulk(out, cmd.Args[2])
				out = resp.AppendBulkString(out, "")
			}
			return out, status
		},
	)
	log.Printf("started redhub server at %s", addr)
	err := redhub.ListendAndServe(protoAddr, option, rh)
	if err != nil {
		log.Fatal(err)
	}
}

Benchmarks #

Machine information
        OS : Debian Buster 10.6 64bit 
       CPU : 8 CPU cores
    Memory : 64.0 GiB

Go Version : go1.16.5 linux/amd64

【Redis-server5.0.3】 Single-threaded, no disk persistence. #

$ ./redis-server --port 6380 --appendonly no
$ redis-benchmark -h 127.0.0.1 -p 6380 -n 50000000 -t set,get -c 512 -P 1024 -q
SET: 2306060.50 requests per second
GET: 3096742.25 requests per second

【Redis-server6.2.5】 Single-threaded, no disk persistence. #

$ ./redis-server --port 6380 --appendonly no
$ redis-benchmark -h 127.0.0.1 -p 6380 -n 50000000 -t set,get -c 512 -P 1024 -q
SET: 2076325.75 requests per second
GET: 2652801.50 requests per second

【Redis-server6.2.5】 Multi-threaded, no disk persistence. #

io-threads-do-reads yes
io-threads 8
$ ./redis-server redis.conf
$ redis-benchmark -h 127.0.0.1 -p 6379 -n 50000000 -t set,get -c 512 -P 1024 -q
SET: 1944692.88 requests per second
GET: 2375184.00 requests per second

【RedCon】 Multi-threaded, no disk persistence #

$ go run example/clone.go
$ redis-benchmark -h 127.0.0.1 -p 6380 -n 50000000 -t set,get -c 512 -P 1024 -q
SET: 2332742.25 requests per second
GET: 14654162.00 requests per second

【RedHub】 Multi-threaded, no disk persistence #

$ go run example/server.go
$ redis-benchmark -h 127.0.0.1 -p 6380 -n 50000000 -t set,get -c 512 -P 1024 -q
SET: 4087305.00 requests per second
GET: 16490765.00 requests per second
image
image