Sign inSign up

jchristn77/redish

By jchristn77

โ€ขUpdated 7 days ago

Redish is a Redis-compatible server

Image
API management
Developer tools
Databases & storage
0

1.5K

jchristn77/redish repository overview

โ RedisRespServer

License: MIT .NET 8.0

A .NET 8.0 library for building Redis-compatible servers using the RESP (Redis Serialization Protocol). Provides low-level protocol parsing and event-driven architecture for implementing custom Redis servers.

โ Motivation

The motivation behind building this library is to 1) expose an interface for C# developers to build their own Redis-compatible backend services and 2) have a platform on which to rapidly prototype data path improvements, enhancements, integrations, or anything else they can dream without requiring a thorough understand of Redis and C. The intent of the library is to inspire exploration, and in no uncertain terms, NOT to try and rebuild the wheel or otherwise create an alternative to a beloved, mature platform like Redis.

โ Features

  • ๐Ÿš€ RESP Protocol Support - RESP2 and RESP3 protocol parsing
  • ๐Ÿ”ง Event-Driven Architecture - Subscribe to specific RESP data types (arrays, bulk strings, etc.)
  • ๐Ÿ—๏ธ Modular Design - Build custom Redis servers with RespListener and RespInterface
  • โšก Async/Await Support - Non-blocking TCP server with proper async patterns
  • ๐Ÿงต Multi-Client Support - Handle multiple concurrent client connections
  • ๐Ÿ“Š Data Type Support - String, Hash, List, Set, SortedSet, JSON, Stream value types
  • ๐Ÿ” Authentication Framework - Built-in authentication event handling

โ Testing and Validation

This library and the Redish.Server project specifically have been tested with the redis-cliโ , StackExchange.Redisโ , and Redis Insightโ . Basic functionality and interoperability have been tested. Please file an issue if you encounter any incompatibilities.

โ Quick Start

โ 1. Using Redish.Server (Pre-built Redis Server)

The fastest way to get a Redis-compatible server running:

# Clone and build
git clone https://github.com/jchristn/RedisRespServer.git
cd RedisRespServer/src
dotnet build

# Run the server
cd Redish.Server
dotnet run

Server starts on port 6379 and supports Redis commands like SET, GET, DEL, MSET, MGET, INCR, INCRBY, DECR, HMSET, and more.

โ 2. Using Sample.RedisInterface (Basic Example)
cd Sample.RedisInterface
dotnet run

This starts a simple Redis server on port 6379 with basic command support.

โ Core Architecture

The library has three main components:

โ 1. RespListener (Low-level TCP + Protocol Parser)
  • TCP server listening on configurable port
  • Parses RESP2/RESP3 protocol messages
  • Raises events for each RESP data type
  • Handles client connections and disconnections
โ 2. RespInterface (High-level API)
  • Wraps RespListener with additional functionality
  • Provides both event handlers and functional handlers
  • Includes authentication support
  • Manages client responses
โ 3. Storage Layer (Application-specific)
  • You implement your own storage logic
  • Built-in RedisValue base class with expiration support
  • Sample implementations show in-memory storage

โ Building Your Own Redis Server

โ Basic Server with RespListener
using RedisResp;

var listener = new RespListener(6379);

// Handle array commands (like SET key value)
listener.ArrayReceived += (sender, e) => {
    if (e.Value is object[] args && args.Length >= 2)
    {
        string command = args[0].ToString().ToUpper();
        Console.WriteLine($"Received command: {command}");
        // Implement your command handling here
    }
};

// Handle client connections
listener.ClientConnected += (sender, e) => 
    Console.WriteLine($"Client connected: {e.GUID}");

await listener.StartAsync();
Console.WriteLine("Server running on port 6379");
Console.ReadKey();
โ Using RespInterface for Enhanced Features
using RedisResp;

var listener = new RespListener(6379);
var respInterface = new RespInterface(listener);

// Traditional event-driven approach
respInterface.ArrayReceived += HandleRedisCommand;

// Or functional approach  
respInterface.ArrayHandler = (e) => {
    // Handle command and return response string
    return "+OK\r\n";
};

await respInterface.StartAsync();
โ Implementing Redis Commands

Based on the Sample.RedisInterface project, here's how to handle basic commands:

private static readonly ConcurrentDictionary<string, RedisValue> _storage = new();

private async void HandleRedisCommand(object sender, RespDataReceivedEventArgs e)
{
    if (e.Value is not object[] args || args.Length == 0) return;
    
    string command = args[0].ToString().ToUpper();
    string response = command switch
    {
        "GET" when args.Length >= 2 => HandleGet(args[1].ToString()),
        "SET" when args.Length >= 3 => HandleSet(args[1].ToString(), args[2].ToString()),
        "DEL" when args.Length >= 2 => HandleDel(args[1].ToString()),
        "EXISTS" when args.Length >= 2 => HandleExists(args[1].ToString()),
        "KEYS" when args.Length >= 2 => HandleKeys(args[1].ToString()),
        "MSET" when args.Length >= 3 => HandleMset(args),
        "MGET" when args.Length >= 2 => HandleMget(args),
        "INCR" when args.Length >= 2 => HandleIncr(args[1].ToString()),
        "INCRBY" when args.Length >= 3 => HandleIncrBy(args[1].ToString(), args[2].ToString()),
        "DECR" when args.Length >= 2 => HandleDecr(args[1].ToString()),
        "HMSET" when args.Length >= 4 => HandleHmset(args),
        "PING" => "+PONG\r\n",
        "FLUSHDB" => HandleFlushDB(),
        _ => "-ERR unknown command\r\n"
    };
    
    // Send response back to client
    await SendResponseAsync(e.ClientGUID, response);
}

private string HandleGet(string key)
{
    if (_storage.TryGetValue(key, out var value) && !value.IsExpired && value is StringValue stringVal)
    {
        return $"${stringVal.Value.Length}\r\n{stringVal.Value}\r\n";
    }
    return "$-1\r\n"; // null
}

private string HandleSet(string key, string value)
{
    _storage[key] = new StringValue(value);
    return "+OK\r\n";
}
โ Sending Responses to Clients

The Sample.RedisInterface shows how to send responses by writing directly to the client's NetworkStream:

private async Task SendResponseAsync(Guid clientGuid, string response)
{
    if (_clientConnections.TryGetValue(clientGuid, out var client) && client.Connected)
    {
        var stream = client.GetStream();
        var data = Encoding.UTF8.GetBytes(response);
        await stream.WriteAsync(data, 0, data.Length);
    }
}

โ Custom Storage Backends

The library includes a StorageBase abstract class in Redish.Server for implementing custom storage:

using Redish.Server.Storage;

public class CustomStorageDriver : StorageBase
{
    public override RedisValue this[string key] 
    { 
        get => GetFromYourStorage(key);
        set => SaveToYourStorage(key, value);
    }
    
    public override bool TryAdd(string key, RedisValue value)
    {
        // Implement atomic add operation
    }
    
    public override bool Remove(string key)
    {
        // Implement key removal
    }
    
    // Implement other abstract methods...
}

โ Supported RESP Data Types

The RespListener can parse and raise events for:

โ RESP2 Types
  • Simple Strings (+OK\r\n) - SimpleStringReceived event
  • Errors (-ERR message\r\n) - ErrorReceived event
  • Integers (:100\r\n) - IntegerReceived event
  • Bulk Strings ($5\r\nhello\r\n) - BulkStringReceived event
  • Arrays (*2\r\n$3\r\nSET\r\n$3\r\nkey\r\n) - ArrayReceived event
  • Null ($-1\r\n) - NullReceived event
โ RESP3 Types
  • Doubles (,1.23\r\n) - DoubleReceived event
  • Booleans (#t\r\n, #f\r\n) - BooleanReceived event
  • Big Numbers ((123456789\r\n) - BigNumberReceived event
  • Blob Errors (!5\r\nerror\r\n) - BlobErrorReceived event
  • Verbatim Strings (=15\r\ntxt:hello world\r\n) - VerbatimStringReceived event
  • Maps (%1\r\n+key\r\n+value\r\n) - MapReceived event
  • Sets (~2\r\n+a\r\n+b\r\n) - SetReceived event
  • Attributes (|1\r\n+attr\r\n+val\r\n) - AttributeReceived event
  • Push (>2\r\n+pubsub\r\n+message\r\n) - PushReceived event

โ RedisValue Types

The library defines these value types in RedisValueType enum:

public enum RedisValueType
{
    String,     // Basic string values
    Hash,       // Hash maps  
    List,       // Ordered lists
    Set,        // Unordered sets
    SortedSet,  // Scored sorted sets
    Json,       // JSON documents
    Stream      // Redis streams
}

Each RedisValue has automatic expiration support with ExpiresAt, IsExpired, and SetExpiration() methods.

โ Sample Projects

The repository includes several working examples:

  • Redish.Server - Full-featured Redis server with authentication and advanced data types
  • Sample.RedisInterface - Basic Redis server using RespInterface
  • Sample.RedisServer - Alternative implementation example
  • Test.StackExchangeRedis - Compatibility testing with StackExchange.Redis client

โ Testing Your Server

Test with redis-cli:

redis-cli -h localhost -p 6379
127.0.0.1:6379> SET mykey "Hello"
OK
127.0.0.1:6379> GET mykey
"Hello"
127.0.0.1:6379> MSET key1 "value1" key2 "value2"
OK
127.0.0.1:6379> MGET key1 key2
1) "value1"
2) "value2"
127.0.0.1:6379> SET counter 10
OK  
127.0.0.1:6379> INCR counter
(integer) 11
127.0.0.1:6379> INCRBY counter 5
(integer) 16
127.0.0.1:6379> DECR counter
(integer) 15
127.0.0.1:6379> HMSET user name "John" age "30"
OK
127.0.0.1:6379> PING
PONG

Or run the test projects:

cd Test.StackExchangeRedis
dotnet run

โ Build Requirements

  • .NET 8.0 SDK
  • No external dependencies for core library
  • Redish.Server uses SerializationHelper and SyslogLogging packages

โ Project Structure

src/
โ”œโ”€โ”€ RedisRespServer/           # Core library (RespListener, RespInterface)
โ”œโ”€โ”€ Redish.Server/            # Full Redis server implementation  
โ”œโ”€โ”€ Sample.RedisInterface/    # Basic example server
โ”œโ”€โ”€ Sample.RedisServer/       # Alternative example
โ”œโ”€โ”€ Test.StackExchangeRedis/  # Compatibility tests
โ””โ”€โ”€ Test/                     # Additional test utilities

โ License

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


Build your own Redis-compatible server in minutes! โšก

Tag summary

Content type

Image

Digest

sha256:98eaa50d8โ€ฆ

Size

339.9 MB

Last updated

7 days ago

docker pull jchristn77/redish