Hiredis

RedisC languageclient libraryhigh performancelightweightasynchronous processing

Cache Library

Hiredis

Overview

Hiredis is a lightweight and minimal C client library for Redis servers. Developed and maintained as part of the official Redis project, it achieves high performance and low memory footprint. It provides both synchronous and asynchronous APIs and enables efficient utilization of all Redis features through complete implementation of the Redis protocol.

Details

Hiredis is a library that enables applications written in C to communicate efficiently with Redis servers. Designed with minimal dependencies, it can be used in a wide range of environments from embedded systems to high-performance servers. It supports both synchronous blocking API and asynchronous API, allowing developers to choose appropriate communication methods according to their needs.

Key Features

  • Synchronous API: Simple Redis operations using redisConnect, redisCommand, and freeReplyObject
  • Asynchronous API: High-performance non-blocking communication through integration with event loops
  • Pipelining: Send multiple commands at once to minimize network round trips
  • SSL/TLS Support: Encrypted communication support through OpenSSL integration
  • Custom Memory Allocators: Customizable memory management
  • Reply Parser API: Direct Redis protocol parsing functionality
  • PUSH Reply Support: Support for PUSH replies in Redis 6.0 and later

Architecture

  • redisContext: Manages synchronous connection and command execution
  • redisAsyncContext: Manages asynchronous connection and callback-based processing
  • redisReader: Responsible for Redis protocol parsing
  • redisReply: Structure representing replies from Redis server

Pros and Cons

Pros

  • High Performance: Optimized performance through C language implementation
  • Lightweight: Minimal dependencies and memory footprint
  • Official Support: Continuous development and maintenance as official Redis project
  • Flexibility: Choice between synchronous/asynchronous APIs, custom memory allocator support
  • Stability: Long-term track record and widespread adoption
  • Completeness: Complete Redis protocol implementation enables use of all Redis features

Cons

  • C Language Only: Direct use in other languages requires bindings
  • Low-Level API: No high-level abstractions provided, requires direct Redis operations
  • Error Handling: Manual error checking and proper memory management required
  • Learning Curve: Requires knowledge of C language and Redis protocol

Reference Links

Code Examples

Basic Synchronous Connection and Command Execution

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis/hiredis.h>

int main() {
    // Connect to Redis server
    redisContext *c = redisConnect("127.0.0.1", 6379);
    if (c == NULL || c->err) {
        if (c) {
            printf("Error: %s\n", c->errstr);
            redisFree(c);
        } else {
            printf("Can't allocate redis context\n");
        }
        return 1;
    }

    // Execute Redis command
    redisReply *reply = redisCommand(c, "SET foo bar");
    if (reply == NULL) {
        printf("Error in redis command\n");
        redisFree(c);
        return 1;
    }
    freeReplyObject(reply);

    // Retrieve data
    reply = redisCommand(c, "GET foo");
    if (reply->type == REDIS_REPLY_STRING) {
        printf("Value: %s\n", reply->str);
    }
    freeReplyObject(reply);

    // Clean up connection
    redisFree(c);
    return 0;
}

String Interpolation and Binary-Safe Data Processing

// Using string interpolation in commands
const char *key = "user:1001";
const char *value = "John Doe";
reply = redisCommand(c, "SET %s %s", key, value);
freeReplyObject(reply);

// Processing binary-safe data
const char *binary_data = "\x00\x01\x02\x03";
size_t data_len = 4;
reply = redisCommand(c, "SET binary_key %b", binary_data, data_len);
freeReplyObject(reply);

// Using multiple interpolations
const char *hash_key = "user:profile";
const char *field = "name";
reply = redisCommand(c, "HSET %s %s %s", hash_key, field, value);
freeReplyObject(reply);

Efficient Command Execution with Pipelining

#include <hiredis/hiredis.h>

void pipeline_example(redisContext *c) {
    redisReply *reply;
    
    // Send multiple commands in pipeline
    redisAppendCommand(c, "SET key1 value1");
    redisAppendCommand(c, "SET key2 value2");
    redisAppendCommand(c, "GET key1");
    redisAppendCommand(c, "GET key2");
    
    // Retrieve responses sequentially
    redisGetReply(c, (void**)&reply); // Result of SET key1
    printf("SET key1: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisGetReply(c, (void**)&reply); // Result of SET key2
    printf("SET key2: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisGetReply(c, (void**)&reply); // Result of GET key1
    printf("GET key1: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisGetReply(c, (void**)&reply); // Result of GET key2
    printf("GET key2: %s\n", reply->str);
    freeReplyObject(reply);
}

Asynchronous API Usage Example

#include <hiredis/hiredis.h>
#include <hiredis/async.h>

// Callback for command completion
void commandCallback(redisAsyncContext *c, void *reply, void *privdata) {
    redisReply *r = reply;
    if (reply == NULL) return;
    
    printf("Command completed: %s\n", r->str);
    // Note: Don't call freeReplyObject in async mode
}

// Callback for connection completion
void connectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("Connection error: %s\n", c->errstr);
        return;
    }
    printf("Connected\n");
}

// Callback for disconnection
void disconnectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("Disconnection error: %s\n", c->errstr);
        return;
    }
    printf("Disconnected\n");
}

int async_example() {
    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
    if (c->err) {
        printf("Error: %s\n", c->errstr);
        redisAsyncFree(c);
        return 1;
    }
    
    // Set callbacks
    redisAsyncSetConnectCallback(c, connectCallback);
    redisAsyncSetDisconnectCallback(c, disconnectCallback);
    
    // Execute asynchronous commands
    redisAsyncCommand(c, commandCallback, NULL, "SET foo bar");
    redisAsyncCommand(c, commandCallback, NULL, "GET foo");
    
    // Event loop execution required (libevent, libuv, etc.)
    // Simplified in this example
    
    return 0;
}

SSL/TLS Connection Example

#include <hiredis/hiredis.h>
#include <hiredis/hiredis_ssl.h>

int ssl_example() {
    redisSSLContext *ssl_context;
    redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
    
    // Initialize OpenSSL
    redisInitOpenSSL();
    
    // Create SSL context
    ssl_context = redisCreateSSLContext(
        "ca-bundle.crt",        // CA certificate file
        "/path/to/certs",       // Path to trusted certificates
        "client-cert.pem",      // Client certificate
        "client-key.pem",       // Client private key
        "redis.example.com",    // Server name (SNI)
        &ssl_error
    );
    
    if (ssl_context == NULL || ssl_error != REDIS_SSL_CTX_NONE) {
        printf("SSL context creation error\n");
        return 1;
    }
    
    // Establish Redis connection
    redisContext *c = redisConnect("localhost", 6443);
    if (c == NULL || c->err) {
        printf("Connection error\n");
        return 1;
    }
    
    // SSL/TLS handshake
    if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) {
        printf("SSL handshake error: %s\n", c->errstr);
        redisFree(c);
        return 1;
    }
    
    // Execute commands over SSL connection
    redisReply *reply = redisCommand(c, "PING");
    printf("PING: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisFree(c);
    return 0;
}

Custom Memory Allocator Configuration

#include <hiredis/hiredis.h>

// Custom memory functions
void* my_malloc(size_t size) {
    printf("Custom malloc: %zu bytes\n", size);
    return malloc(size);
}

void my_free(void* ptr) {
    printf("Custom free\n");
    free(ptr);
}

void setup_custom_allocators() {
    hiredisAllocFuncs custom_funcs = {
        .mallocFn = my_malloc,
        .callocFn = calloc,      // Use standard calloc
        .reallocFn = realloc,    // Use standard realloc
        .strdupFn = strdup,      // Use standard strdup
        .freeFn = my_free,
    };
    
    // Set custom allocators
    hiredisAllocFuncs original = hiredisSetAllocators(&custom_funcs);
    
    // Reset to original if needed
    // hiredisSetAllocators(&original);
    
    // Or reset to default
    // hiredisResetAllocators();
}

Error Handling and Resource Management

int safe_redis_operation() {
    redisContext *c = NULL;
    redisReply *reply = NULL;
    int result = 0;
    
    // Establish connection
    c = redisConnect("127.0.0.1", 6379);
    if (c == NULL) {
        printf("Out of memory\n");
        result = -1;
        goto cleanup;
    }
    
    if (c->err) {
        printf("Connection error: %s\n", c->errstr);
        result = -1;
        goto cleanup;
    }
    
    // Execute command
    reply = redisCommand(c, "SET test_key test_value");
    if (reply == NULL) {
        printf("Command execution error\n");
        result = -1;
        goto cleanup;
    }
    
    if (reply->type == REDIS_REPLY_ERROR) {
        printf("Redis error: %s\n", reply->str);
        result = -1;
        goto cleanup;
    }
    
    printf("Operation successful\n");
    
cleanup:
    if (reply) freeReplyObject(reply);
    if (c) redisFree(c);
    return result;
}