Home

Awesome

<p align="center"> <img src="logo.png" width="336" border="0" alt="REDCON"> <br> </p> <p align="center">Fast Redis compatible server framework for C</p>

Redcon is a custom Redis server framework that is fast and simple to use. This is a C version of the original Redcon, and is built on top of evio.c.

Features

This library is also avaliable for Go and Rust.

Install

Clone this respository and then use pkg.sh to import dependencies.

$ git clone https://github.com/tidwall/redcon.c
$ cd redcon.c/
$ pkg.sh import

Example

Here's a simple Redis clone. Save the following file to clone.c

Check out example/clone.c for a more robust example.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "redcon.h"
#include "hashmap.h"

void *zmalloc(size_t size);

// keyspace is a collection of all keys. (github.com/tidwall/hashmap.c)
struct hashmap *keyspace; 

void cmdSET(struct redcon_conn *conn, struct redcon_args *args, void *udata) {
    if (redcon_args_count(args) != 3) {
        redcon_conn_write_error(conn, "ERR wrong number of arguments");
    } else {
        // Each key/value item is a single allocation of two contiguous 
        // series of bytes, the first is a c-string and the second is binary.
        int vallen;
        const char *key = redcon_args_at(args, 1, NULL);
        const char *val = redcon_args_at(args, 2, &vallen);
        char *item = zmalloc(strlen(key)+5+vallen);
        strcpy(item, key); 
        *(int32_t*)(item+strlen(item)+1) = vallen;
        memcpy(item+strlen(item)+5, val, vallen);
        char **prev = hashmap_set(keyspace, &item);
        if (prev) free(*prev);
        redcon_conn_write_string(conn, "OK");
    }
}

void cmdGET(struct redcon_conn *conn, struct redcon_args *args, void *udata) {
    if (redcon_args_count(args) != 2) {
        redcon_conn_write_error(conn, "ERR wrong number of arguments");
    } else {
        const char *key = redcon_args_at(args, 1, NULL);
        char **item = hashmap_get(keyspace, &key);
        if (!item) {
            redcon_conn_write_null(conn);
        } else {
            char *val = *item+strlen(*item)+5;
            int vallen = *(int32_t*)(*item+strlen(*item)+1);
            redcon_conn_write_bulk(conn, val, vallen);
        }
    }
}

void cmdDEL(struct redcon_conn *conn, struct redcon_args *args, void *udata) {
    if (redcon_args_count(args) != 2) {
        redcon_conn_write_error(conn, "ERR wrong number of arguments");
    } else {
        const char *key = redcon_args_at(args, 1, NULL);
        char **prev = hashmap_delete(keyspace, &key);
        if (!prev) {
            redcon_conn_write_int(conn, 0);
        } else {
            free(*prev);
            redcon_conn_write_int(conn, 1);
        }
    }
}

void cmdPING(struct redcon_conn *conn, struct redcon_args *args, void *udata) {
    redcon_conn_write_string(conn, "PONG");
}

void command(struct redcon_conn *conn, struct redcon_args *args, void *udata) {
         if (redcon_args_eq(args, 0, "set"))  cmdSET(conn, args, udata);
    else if (redcon_args_eq(args, 0, "get"))  cmdGET(conn, args, udata);
    else if (redcon_args_eq(args, 0, "del"))  cmdDEL(conn, args, udata);
    else if (redcon_args_eq(args, 0, "ping")) cmdPING(conn, args, udata);
    else redcon_conn_write_error(conn, "ERR unknown command");
}

void serving(const char **addrs, int naddrs, void *udata) {
    printf("* Listening at %s\n", addrs[0]);
}

void error(const char *msg, bool fatal, void *udata) {
    fprintf(stderr, "- %s\n", msg);
}

uint64_t hash(const void *item, uint64_t seed0, uint64_t seed1) {
    return hashmap_murmur(*(char**)item, strlen(*(char**)item), seed0, seed1);
}

int compare(const void *a, const void *b, void *udata) {
    return strcmp(*(char**)a, *(char**)b);
}

void *zmalloc(size_t size) {
    void *ptr = malloc(size);
    if (!ptr) abort();
    return ptr;
}

int main() {
    // use do-or-die allocator
    redcon_set_allocator(zmalloc, free);
    hashmap_set_allocator(zmalloc, free);

    keyspace = hashmap_new(sizeof(char *), 0, 0, 0, hash, compare, NULL);
    struct redcon_events evs = {
        .serving = serving,
        .command = command,
        .error = error,
    };
    const char *addrs[] = { 
        "tcp://0.0.0.0:6380",
    };
    redcon_main(addrs, 1, evs, NULL);
}

Then build and run the server.

$ cc *.c && ./a.out

And connect using the Redis cli.

$ redis-cli -p 6380

Multithreading

Use the redcon_main_mt function to start the server using multiple threads.

// Use five threads
redcon_main_mt(addrs, 1, evs, NULL, 5);

// Use ten threads
redcon_main_mt(addrs, 1, evs, NULL, 10);

// Use the number of threads equal to the number of cores on the machine.
redcon_main_mt(addrs, 1, evs, NULL, 0);

Benchmarks

For following results were generated using the redis-benchmark tool that is provided by the official Redis distribution.

CmdPipeline 1Pipeline 8Pipeline 16Pipeline 32Pipeline 64Pipeline 128
Redis 6.06SET114,467618,432765,284872,984947,724990,307
Redis 6.06GET114,888692,764898,7241,048,9871,151,2771,208,751
Redis cloneSET114,554895,5021,310,7871,670,9021,954,2952,122,295
Redis cloneGET114,602894,8621,346,2761,735,8092,046,2452,230,151

In my above benchmark test the Redis clone used about 7% less memory than Redis 6.06 -- 79.7 MB to 84.8 MB.

The machine was an AWS c5.2xlarge instance (Intel Xeon 3.6 GHz).

The benchmark command looked like:

redis-benchmark -q -t set,get -r 1000000 -n 10000000 -p 6379  -P 16   # redis 6.06
redis-benchmark -q -t set,get -r 1000000 -n 10000000 -p 6380  -P 16   # redis clone