Skip to content

IceFireDB/redhub

REDHUB

RedHub

GoDoc Reference FOSSA Status Go Report Card License

RedHub is a high-performance RESP (Redis Serialization Protocol) server framework built in Go. It leverages the RawEpoll model via the gnet library to achieve ultra-high throughput with multi-threaded support while maintaining low CPU resource consumption.

Features

  • Ultra High Performance - Exceeds Redis single-threaded and multi-threaded implementations in benchmarks
  • Fully Multi-threaded - Native support for multiple CPU cores with efficient event loop distribution
  • Low Resource Consumption - Optimized memory usage and CPU efficiency
  • Full RESP Protocol Support - Compatible with Redis protocol (RESP2)
  • Multi-Protocol Support - Supports RESP, Tile38 native, and Telnet protocols
  • Easy to Use - Create Redis-compatible servers with minimal code
  • Production Ready - Robust error handling, connection management, and extensibility

Architecture

RedHub implements an event-driven architecture based on the gnet framework:

┌─────────────────────────────────────────────────────────────┐
│                         Client Connections                  │
└─────────────────────────────┬───────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                     Event Loops (gnet)                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ Event Loop 1│  │ Event Loop 2│  │ Event Loop N│        │
│  │ (Thread 1)  │  │ (Thread 2)  │  │ (Thread N)  │        │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘        │
│         │                 │                 │               │
│         └─────────────────┼─────────────────┘               │
│                           │                                  │
│                           ▼                                  │
│                  ┌──────────────┐                           │
│                  │ RedHub Core  │                           │
│                  │   Handler    │                           │
│                  └──────┬───────┘                           │
└───────────────────────────┼──────────────────────────────────┘
                            │
                            ▼
                  ┌─────────────────┐
                  │ Application     │
                  │ Logic & Storage │
                  └─────────────────┘

Threading Model

  • Single-core mode: All connections handled by a single event loop
  • Multi-core mode: Multiple event loops distribute connections using configurable load balancing strategies
  • Connection Buffering: Each connection maintains its own buffer for command accumulation
  • Thread Safety: Uses RWMutex for connection map synchronization

Installation

go get -u github.com/IceFireDB/redhub

Quick Start

Here's a simple example showing how to create a Redis-compatible server with SET, GET, DEL, PING, and QUIT commands:

Example Code

package main

import (
    "log"
    "strings"
    "sync"

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

func main() {
    var mu sync.RWMutex
    var items = make(map[string][]byte)

    // Create a new RedHub instance
    rh := redhub.NewRedHub(
        // OnOpen: Called when a new connection is established
        func(c *redhub.Conn) (out []byte, action redhub.Action) {
            // Initialize connection-specific data here
            return nil, redhub.None
        },
        // OnClose: Called when a connection is closed
        func(c *redhub.Conn, err error) (action redhub.Action) {
            // Clean up connection-specific data here
            return redhub.None
        },
        // Handler: Called for each parsed command
        func(cmd resp.Command, out []byte) ([]byte, redhub.Action) {
            // Get command name (case-insensitive)
            cmdName := strings.ToLower(string(cmd.Args[0]))

            switch cmdName {
            case "set":
                // SET key value
                if len(cmd.Args) != 3 {
                    return resp.AppendError(out, 
                        "ERR wrong number of arguments for 'set' command"), redhub.None
                }
                mu.Lock()
                items[string(cmd.Args[1])] = cmd.Args[2]
                mu.Unlock()
                return resp.AppendString(out, "OK"), redhub.None

            case "get":
                // GET key
                if len(cmd.Args) != 2 {
                    return resp.AppendError(out, 
                        "ERR wrong number of arguments for 'get' command"), redhub.None
                }
                mu.RLock()
                val, ok := items[string(cmd.Args[1])]
                mu.RUnlock()
                if !ok {
                    return resp.AppendNull(out), redhub.None
                }
                return resp.AppendBulk(out, val), redhub.None

            case "del":
                // DEL key
                if len(cmd.Args) != 2 {
                    return resp.AppendError(out, 
                        "ERR wrong number of arguments for 'del' command"), redhub.None
                }
                mu.Lock()
                _, ok := items[string(cmd.Args[1])]
                delete(items, string(cmd.Args[1]))
                mu.Unlock()
                if !ok {
                    return resp.AppendInt(out, 0), redhub.None
                }
                return resp.AppendInt(out, 1), redhub.None

            case "ping":
                // PING
                return resp.AppendString(out, "PONG"), redhub.None

            case "quit":
                // QUIT
                return resp.AppendString(out, "OK"), redhub.Close

            default:
                // Unknown command
                return resp.AppendError(out, 
                    "ERR unknown command '"+string(cmd.Args[0])+"'"), redhub.None
            }
        },
    )

    // Start the server
    err := redhub.ListenAndServe("tcp://127.0.0.1:6379", redhub.Options{
        Multicore: true,  // Enable multi-core support
    }, rh)
    if err != nil {
        log.Fatal(err)
    }
}

Run the Example

# Navigate to the example directory
cd example/memory_kv

# Run the server
go run server.go

# In another terminal, test with redis-cli
redis-cli -p 6379

# Or test with redis-benchmark
redis-benchmark -h 127.0.0.1 -p 6379 -n 1000000 -t set,get -c 512 -P 1024 -q

Configuration

RedHub provides various configuration options through the Options struct:

type Options struct {
    Multicore        bool              // Enable multi-core support (default: false)
    LockOSThread     bool              // Lock OS thread (default: false)
    ReadBufferCap    int               // Read buffer capacity (default: 64KB)
    LB               gnet.LoadBalancing // Load balancing strategy (default: RoundRobin)
    NumEventLoop     int               // Number of event loops (default: runtime.NumCPU())
    ReusePort        bool              // Enable port reuse (default: false)
    Ticker           bool              // Enable ticker (default: false)
    TCPKeepAlive     time.Duration     // TCP keep-alive interval
    TCPKeepCount     int               // TCP keep-alive count
    TCPKeepInterval  time.Duration     // TCP keep-alive interval
    TCPNoDelay       gnet.TCPSocketOpt // TCP no-delay option
    SocketRecvBuffer int               // Socket receive buffer size
    SocketSendBuffer int               // Socket send buffer size
    EdgeTriggeredIO  bool              // Edge-triggered I/O (default: false)
}

Example Configuration

options := redhub.Options{
    Multicore:        true,              // Enable multi-core
    NumEventLoop:     8,                 // Use 8 event loops
    ReadBufferCap:    64 * 1024,         // 64KB read buffer
    SocketRecvBuffer: 128 * 1024,        // 128KB socket receive buffer
    SocketSendBuffer: 128 * 1024,        // 128KB socket send buffer
    TCPKeepAlive:     30 * time.Second,  // 30s keep-alive
    LB:               gnet.LeastConnections, // Load balancing strategy
}

API Reference

Core Types

Action

Action represents the action to take after an event handler completes.

const (
    None     // No action
    Close    // Close the connection
    Shutdown // Shutdown the server
)

RedHub

RedHub is the main server structure that manages connections and command processing.

Conn

Conn wraps a gnet.Conn and provides additional functionality for connection management.

Command

Command represents a parsed RESP command with raw bytes and arguments.

type Command struct {
    Raw  []byte   // Raw RESP message
    Args [][]byte // Parsed arguments
}

Main Functions

NewRedHub

Creates a new RedHub instance with the specified event handlers.

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

Parameters:

  • onOpened: Called when a new connection is established
  • onClosed: Called when a connection is closed
  • handler: Called for each parsed command

ListenAndServe

Starts the RedHub server with the specified address and options.

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

Parameters:

  • addr: Server address in format "tcp://host:port"
  • options: Server configuration options
  • rh: RedHub instance

RESP Protocol Package

The resp package provides comprehensive support for the Redis Serialization Protocol (RESP).

RESP Types

const (
    Integer = ':'  // Integers (e.g., :1000\r\n)
    String  = '+'  // Simple strings (e.g., +OK\r\n)
    Bulk    = '$'  // Bulk strings (e.g., $6\r\nfoobar\r\n)
    Array   = '*'  // Arrays (e.g., *2\r\n$3\r\nGET\r\n$3\r\nkey\r\n)
    Error   = '-'  // Errors (e.g., -ERR unknown command\r\n)
)

RESP Serialization Functions

The resp package provides functions for serializing various Go types to RESP format:

  • AppendInt(b []byte, n int64) []byte - Append integer
  • AppendString(b []byte, s string) []byte - Append simple string
  • AppendBulk(b []byte, bulk []byte) []byte - Append bulk bytes
  • AppendBulkString(b []byte, bulk string) []byte - Append bulk string
  • AppendArray(b []byte, n int) []byte - Append array header
  • AppendError(b []byte, s string) []byte - Append error
  • AppendNull(b []byte) []byte - Append null value
  • AppendOK(b []byte) []byte - Append OK response
  • AppendAny(b []byte, v interface{}) []byte - Append any Go type

Example: Building Responses

var out []byte

// Simple string
out = resp.AppendString(out, "OK")

// Bulk string
out = resp.AppendBulkString(out, "Hello World")

// Integer
out = resp.AppendInt(out, 42)

// Array
out = resp.AppendArray(out, 3)
out = resp.AppendBulkString(out, "item1")
out = resp.AppendBulkString(out, "item2")
out = resp.AppendBulkString(out, "item3")

// Error
out = resp.AppendError(out, "ERR something went wrong")

// Null value
out = resp.AppendNull(out)

// Any type
out = resp.AppendAny(out, map[string]interface{}{
    "name": "Redis",
    "version": 7.0,
    "features": []string{"persistence", "replication"},
})

Advanced Usage

Connection Context

Store connection-specific data using Conn.SetContext():

type ConnectionData struct {
    Authenticated bool
    Database      int
    ClientID      string
}

onOpened := func(c *redhub.Conn) (out []byte, action redhub.Action) {
    c.SetContext(&ConnectionData{
        Authenticated: false,
        Database:      0,
        ClientID:      generateID(),
    })
    return nil, redhub.None
}

onClosed := func(c *redhub.Conn, err error) (action redhub.Action) {
    ctx := c.Context().(*ConnectionData)
    // Cleanup connection data
    return redhub.None
}

Command Pipelining

RedHub naturally supports command pipelining (sending multiple commands in a single network packet):

# Client sends multiple commands in one request
echo -e '*2\r\n$3\r\nSET\r\n$3\r\nkey1\r\n$5\r\nvalue1\r\n*2\r\n$3\r\nSET\r\n$3\r\nkey2\r\n$5\r\nvalue2\r\n*2\r\n$3\r\nGET\r\n$3\r\nkey1\r\n' | nc localhost 6379

Multi-Protocol Support

RedHub supports three protocol types:

  1. RESP (Redis) - Standard Redis protocol (commands starting with *)
  2. Tile38 Native - Native Tile38 protocol (commands starting with $)
  3. Telnet - Plain text commands

Performance Benchmarks

Test Environment

OS:     Debian Buster 10.6 64bit
CPU:    8 CPU cores
Memory: 64.0 GiB
Go:     go1.16.5 linux/amd64

Benchmark Results

Implementation SET (req/sec) GET (req/sec)
Redis 5.0.3 (single-threaded) 2,306,060 3,096,742
Redis 6.2.5 (single-threaded) 2,076,325 2,652,801
Redis 6.2.5 (multi-threaded) 1,944,692 2,375,184
RedCon (multi-threaded) 2,332,742 14,654,162
RedHub (multi-threaded) 4,087,305 16,490,765

Benchmark Command

redis-benchmark -h 127.0.0.1 -p 6379 -n 50000000 -t set,get -c 512 -P 1024 -q

REDHUB Benchmarks

REDHUB Benchmarks

Testing

Run All Tests

go test ./...

Run Tests with Coverage

go test -cover ./...

Run Tests with Verbose Output

go test -v ./...

Run Specific Package Tests

go test ./pkg/resp/...

Run Specific Test

go test -run TestNewRedHub .

Best Practices

Performance Optimization

  1. Enable Multi-core: Always enable Multicore: true in production
  2. Tune Buffer Sizes: Adjust ReadBufferCap, SocketRecvBuffer, and SocketSendBuffer based on your workload
  3. Choose Load Balancing: Use appropriate load balancing strategy (RoundRobin, LeastConnections, etc.)
  4. Avoid Blocking: Never block in event handlers - use async operations
  5. Reuse Buffers: Use buffer pools for temporary allocations

Thread Safety

  1. Shared Data: Always protect shared data with appropriate synchronization (mutexes)
  2. Connection Context: Use Conn.SetContext() for per-connection data (thread-safe)
  3. Event Loop Handlers: Handlers execute in event loop threads - avoid heavy computations

Error Handling

  1. Protocol Errors: Return proper RESP error messages using resp.AppendError()
  2. Connection Errors: Log errors in onClosed handler
  3. Graceful Shutdown: Handle server shutdown properly

Contributing

We welcome contributions! Please follow these guidelines:

  1. Fork the repository
  2. Create a new branch from main/master
  3. Make your changes with tests
  4. Ensure all tests pass: go test ./...
  5. Commit with DCO sign-off: git commit -s -m "message"
  6. Push to your fork
  7. Create a pull request

Development Setup

# Clone the repository
git clone https://github.com/IceFireDB/redhub.git
cd redhub

# Install dependencies
go mod download

# Run tests
go test ./...

# Run the example
go run example/memory_kv/server.go

License

FOSSA Status

This project is licensed under the MIT License - see the LICENSE file for details.

Disclaimer

When you use this software, you agree and acknowledge that the author, maintainer, and contributor of this software are not responsible for any risks, costs, or problems you encounter. If you find a software defect or bug, please submit a patch to help improve it!

Related Projects

  • IceFireDB - A distributed database based on RedHub
  • gnet - High-performance event-loop networking framework

Documentation

Support

Acknowledgments

About

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

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 7

Languages