This file provides comprehensive guidance for AI coding agents working on the RedHub codebase.
RedHub is a high-performance RESP (Redis Serialization Protocol) server framework built in Go. It uses the RawEpoll model via the gnet library to achieve ultra-high throughput with multi-threaded support while maintaining low CPU resource consumption.
Key Features:
- Ultra high performance (exceeds Redis single-threaded and multi-threaded implementations)
- Fully multi-threaded support using event loops
- Low CPU resource consumption
- Full Redis protocol (RESP) compatibility
- Supports RESP, Tile38, and Telnet protocols
- Create Redis-compatible servers with minimal code
RedHub implements an event-driven architecture using the gnet framework:
- Event Loops: Multiple event loops run in parallel (multi-threaded mode)
- Connection Pool: Each connection has an associated buffer for command accumulation
- Command Pipeline: Supports pipelining of multiple commands in a single read
- Handler Callbacks: Three primary handlers for application logic:
onOpened: Called when a new connection is establishedonClosed: Called when a connection is closedhandler: Called for each parsed command
- Single-core mode: All connections handled by a single event loop
- Multi-core mode: Multiple event loops distribute connections using load balancing
- Connection Buffering: Each connection maintains its own buffer and command queue
- Thread Safety: Uses RWMutex for connection map synchronization (see redhub.go:53)
redhub/
├── redhub.go # Main framework core (RedHub server implementation)
├── redhub_test.go # Core framework tests
├── go.mod # Go module definition
├── go.sum # Dependency checksums
├── pkg/
│ └── resp/
│ ├── resp.go # RESP protocol serialization/deserialization
│ ├── comparse.go # Command parsing logic
│ ├── resp_test.go # RESP protocol tests
│ └── comparse_test.go # Command parsing tests
└── example/
└── memory_kv/
└── server.go # Example Redis-compatible server (SET/GET/DEL/PING/QUIT)
Key Types:
RedHub: Main server structure (line 48-54)Options: Server configuration options (line 30-45)Conn: Connection wrapper around gnet.Conn (line 25-27)Action: Post-event action type (line 13-22)
Action Values:
None: No actionClose: Close the connectionShutdown: Shutdown the server
Server Options:
type Options struct {
Multicore bool // Enable multi-core support
LockOSThread bool // Lock OS thread
ReadBufferCap int // Read buffer capacity
LB gnet.LoadBalancing // Load balancing strategy
NumEventLoop int // Number of event loops
ReusePort bool // Enable port reuse
Ticker bool // Enable ticker
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
SocketSendBuffer int // Socket send buffer
EdgeTriggeredIO bool // Edge-triggered I/O
}Main Functions:
NewRedHub(onOpened, onClosed, handler): Create new RedHub instance (line 63-75)ListenAndServe(addr, options, rh): Start the server (line 161-205)
Event Handlers:
OnBoot(eng): Called when engine is ready (line 78-80)OnShutdown(eng): Called when engine is shutting down (line 83-84)OnOpen(c): Called when new connection opens (line 87-93)OnClose(c, err): Called when connection closes (line 96-101)OnTraffic(c): Called when data is received (line 104-153)OnTick(): Called on timer ticks (line 156-158)
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)
)Key Functions:
Reading/Parsing:
ReadNextRESP(b []byte) (n int, resp RESP): Parse next RESP value (line 45-134)ReadNextCommand(packet, argsbuf): Parse next command (line 159-226)ForEach(iter func(resp RESP) bool): Iterate over array elements (line 32-41)
Writing/Serializing:
AppendInt(b []byte, n int64): Append integer (line 378-380)AppendString(b []byte, s string): Append simple string (line 402-406)AppendBulk(b []byte, bulk []byte): Append bulk bytes (line 388-392)AppendBulkString(b []byte, bulk string): Append bulk string (line 395-399)AppendArray(b []byte, n int): Append array header (line 383-385)AppendError(b []byte, s string): Append error (line 409-413)AppendNull(b []byte): Append null value (line 440-442)AppendOK(b []byte): Append OK response (line 416-418)AppendAny(b []byte, v interface{}): Append any Go type (line 503-598)
AppendAny Type Mapping:
nil→ nullerror→ error (adds "ERR " prefix if needed)string→ bulk string[]byte→ bulk bytesbool→ bulk string ("0" or "1")- Numbers (int, int64, uint64, float64) → bulk string
[]Type→ arraymap[K]V→ array with key/value pairsSimpleString→ simple stringSimpleInt→ integerMarshaler→ custom RESP
Key Types:
type Command struct {
Raw []byte // Raw RESP message
Args [][]byte // Command arguments
}Key Functions:
ReadCommands(buf []byte) ([]Command, []byte, error): Parse multiple commands from buffer (line 40-78)parseRESPCommand(b []byte): Parse RESP format command (line 81-134)parsePlainTextCommand(b []byte): Parse plain text command (line 137-155)
Protocol Support:
- Standard RESP (Redis protocol): Commands starting with
* - Tile38 native protocol: Commands starting with
$ - Telnet protocol: Plain text commands
Command Parsing Flow:
- Check first byte to determine protocol type
- Parse according to protocol specification
- Return parsed command or incomplete data (if more bytes needed)
- Return error for malformed commands
go get -u github.com/IceFireDB/redhubgo run example/memory_kv/server.gopackage main
import (
"github.com/IceFireDB/redhub"
"github.com/IceFireDB/redhub/pkg/resp"
)
func main() {
// Create RedHub instance with handlers
rh := redhub.NewRedHub(
// OnOpen: Connection opened
func(c *redhub.Conn) (out []byte, action redhub.Action) {
return nil, redhub.None
},
// OnClose: Connection closed
func(c *redhub.Conn, err error) (action redhub.Action) {
return redhub.None
},
// Handler: Process commands
func(cmd resp.Command, out []byte) ([]byte, redhub.Action) {
// Process command and return response
return out, redhub.None
},
)
// Start server
err := redhub.ListenAndServe("tcp://127.0.0.1:6379", redhub.Options{
Multicore: true,
}, rh)
if err != nil {
panic(err)
}
}- Go version: 1.24.0 (see go.mod:3)
github.com/panjf2000/gnet/v2 v2.9.7: High-performance event-loop networking frameworkgithub.com/stretchr/testify v1.11.1: Testing framework with assertions- Indirect dependencies:
github.com/valyala/bytebufferpool: Byte buffer poolinggo.uber.org/zap: Logginggo.uber.org/multierr: Multi-error handlinggolang.org/x/sync: Sync utilities
go buildRun all tests:
go test ./...Run with coverage:
go test -cover ./...Run with verbose output:
go test -v ./...Run specific package:
go test ./pkg/resp/...Run specific test:
go test -run TestNewRedHub .Test files:
redhub_test.go: Core framework tests (313 lines)pkg/resp/resp_test.go: RESP protocol testspkg/resp/comparse_test.go: Command parsing tests
- Follow standard Go conventions (gofmt)
- Use standard Go idioms
- Add tests for all code changes
- Ensure all tests pass before committing
Command handlers receive a resp.Command struct:
type Command struct {
Raw []byte // Raw RESP bytes
Args [][]byte // Parsed arguments
}Example handler:
func(cmd resp.Command, out []byte) ([]byte, redhub.Action) {
// Command name is first argument (case-insensitive)
cmdName := strings.ToLower(string(cmd.Args[0]))
switch cmdName {
case "set":
// Validate arguments
if len(cmd.Args) != 3 {
return resp.AppendError(out, "ERR wrong number of arguments"), redhub.None
}
key := cmd.Args[1]
value := cmd.Args[2]
// Store value...
return resp.AppendString(out, "OK"), redhub.None
case "get":
if len(cmd.Args) != 2 {
return resp.AppendError(out, "ERR wrong number of arguments"), redhub.None
}
key := cmd.Args[1]
// Retrieve value...
return resp.AppendBulk(out, value), redhub.None
case "quit":
return resp.AppendString(out, "OK"), redhub.Close
}
return resp.AppendError(out, "ERR unknown command '"+string(cmd.Args[0])+"'"), redhub.None
}Use the Conn context to store connection-specific data:
onOpened := func(c *redhub.Conn) (out []byte, action redhub.Action) {
c.SetContext(&ConnectionData{
Authenticated: false,
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
}- Locking: Use appropriate synchronization for shared data
- Connection Buffers: Each connection has its own buffer (thread-safe)
- Event Loops: Handlers execute in event loop threads
- Avoid Blocking: Never block in handlers - use async operations
- Thread-local Data: Use
Conn.SetContext()for per-connection data
Simple String:
+OK\r\n
Error:
-ERR unknown command\r\n
Integer:
:1000\r\n
Bulk String:
$6\r\nfoobar\r\n
Null Bulk String:
$-1\r\n
Array:
*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n
Null Array:
*-1\r\n
- Redis protocol version 2 (RESP2)
- Supports pipelining (multiple commands in one network packet)
- Supports multi-bulk commands
- Client compatibility with standard Redis clients
Based on benchmark results (Debian Buster, 8 CPU cores, 64GB RAM):
- RedHub SET: ~4,087,305 req/sec (vs Redis: ~2,300,000)
- RedHub GET: ~16,490,765 req/sec (vs Redis: ~3,000,000)
Performance exceeds Redis single-threaded and multi-threaded implementations.
- Avoid Memory Allocations: Reuse buffers when possible
- Use Multi-core: Enable
Options.Multicorefor production - Tune Buffers: Adjust
ReadBufferCap,SocketRecvBuffer,SocketSendBuffer - Load Balancing: Choose appropriate
LBstrategy - Event Loops: Set
NumEventLoopbased on CPU cores - Profile: Use pprof to identify bottlenecks
Enable pprof in example:
import _ "net/http/pprof"
go func() {
http.ListenAndServe(":8888", nil)
}()Benchmark:
redis-benchmark -h 127.0.0.1 -p 6379 -n 10000000 -t set,get -c 512 -P 1024 -q- Create an issue to discuss your change
- Fork the repository
- Create a new branch from main/master
- Make your changes with tests
- Ensure all tests pass:
go test ./... - Commit with DCO sign-off
- Push to your fork
- Create a pull request
- Every commit must be signed with DCO (Developer Certificate of Origin)
- Sign automatically:
git commit -s -m "message" - Or add manually:
Signed-off-by: Your Name <your.email@example.com>" - If you forgot to sign:
git commit --amend --no-edit --signoffthengit push --force-with-lease - Write clear, descriptive commit messages following conventional commits
- Reference the related issue in your PR description
- All code changes must include tests
- Wait for CI checks to complete and pass
- Maintainers review and merge within a few days
- Be responsive to review comments
- Ensure Go 1.24.0 is installed:
go version - Run
go mod tidyto resolve dependencies
- Check Go version compatibility
- Run
go mod tidy - Verify test environment
- Profile with pprof before and after changes
- Compare with baseline benchmarks
- Consider memory allocation patterns
- Check TCP socket options
- Verify load balancing configuration
- Review event loop settings
- Ensure proper cleanup in
onClosedhandler - Check for context data cleanup
- Profile with pprof
- Test each function independently
- Use table-driven tests for multiple cases
- Mock external dependencies
- Test edge cases and error conditions
- Test full request/response cycle
- Test pipelining scenarios
- Test connection lifecycle
- Use real Redis clients for compatibility testing
- Aim for high test coverage
- Test all code paths
- Test error handling
- Test concurrent scenarios
redhub.go- Core server implementationpkg/resp/resp.go- RESP protocol handlingexample/memory_kv/server.go- Complete working example
- All handlers return
(response []byte, action Action) - Use
resp.Append*functions to build responses - Use
Conn.SetContext()for per-connection data - Handlers should be non-blocking
- Maintain RESP protocol compatibility
- Consider performance impact
- Add/update tests
- Update documentation if needed
- Run
go test ./...before committing - Test with Redis clients
- Benchmark if performance-critical
- Check for memory leaks
Supported Protocols:
- RESP (Redis): Full support, primary protocol
- Tile38 Native: Partial support for native Tile38 commands
- Telnet: Basic plain-text command support
Redis Commands (in example):
SET key value- Set key-value pairGET key- Get value by keyDEL key- Delete keyPING- Ping server (responds PONG)QUIT- Close connection
Extend with your own commands by implementing handlers.