Hiredis

RedisC言語クライアントライブラリ高性能軽量非同期処理

キャッシュライブラリ

Hiredis

概要

HiredisはRedisサーバー用の軽量でミニマルなC言語クライアントライブラリです。Redis公式プロジェクトの一部として開発・維持されており、高いパフォーマンスと低いメモリフットプリントを実現しています。同期・非同期両方のAPIを提供し、Redis プロトコルの完全な実装により、Redisの全機能を効率的に活用できます。

詳細

Hiredisは、C言語で書かれたアプリケーションがRedisサーバーと効率的に通信するためのライブラリです。最小限の依存関係で設計されており、組み込みシステムからハイパフォーマンスサーバーまで幅広い環境で利用可能です。同期的なブロッキングAPIと非同期APIの両方をサポートし、開発者のニーズに応じて適切な通信方法を選択できます。

主要な機能

  • 同期API: redisConnectredisCommandfreeReplyObjectによる簡単なRedis操作
  • 非同期API: イベントループとの統合による高性能な非ブロッキング通信
  • パイプライニング: 複数のコマンドを一度に送信してネットワーク往復を最小化
  • SSL/TLS対応: OpenSSLとの統合による暗号化通信のサポート
  • カスタムメモリアロケータ: メモリ管理のカスタマイズが可能
  • Reply パーサーAPI: Redis プロトコルの直接解析機能
  • PUSH回答対応: Redis 6.0以降のPUSH回答への対応

アーキテクチャ

  • redisContext: 同期的な接続とコマンド実行を管理
  • redisAsyncContext: 非同期接続とコールバックベースの処理を管理
  • redisReader: Redis プロトコルの解析を担当
  • redisReply: Redis サーバーからの回答を表現する構造体

メリット・デメリット

メリット

  • 高性能: C言語による最適化されたパフォーマンス
  • 軽量: 最小限の依存関係とメモリフットプリント
  • 公式サポート: Redis公式プロジェクトとしての継続的な開発・保守
  • 柔軟性: 同期・非同期APIの選択、カスタムメモリアロケータ対応
  • 安定性: 長期間にわたる実績と幅広い採用事例
  • 完全性: Redis プロトコルの完全実装によるRedis全機能の利用

デメリット

  • C言語専用: 他の言語での直接利用はバインディングが必要
  • 低レベルAPI: 高レベルな抽象化は提供されず、直接的なRedis操作が必要
  • エラーハンドリング: 手動でのエラーチェックと適切なメモリ管理が必要
  • 学習コスト: C言語とRedis プロトコルに関する知識が必要

参考ページ

書き方の例

基本的な同期接続とコマンド実行

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

int main() {
    // Redisサーバーに接続
    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;
    }

    // Redisコマンドの実行
    redisReply *reply = redisCommand(c, "SET foo bar");
    if (reply == NULL) {
        printf("Error in redis command\n");
        redisFree(c);
        return 1;
    }
    freeReplyObject(reply);

    // データの取得
    reply = redisCommand(c, "GET foo");
    if (reply->type == REDIS_REPLY_STRING) {
        printf("Value: %s\n", reply->str);
    }
    freeReplyObject(reply);

    // 接続をクリーンアップ
    redisFree(c);
    return 0;
}

文字列補間とバイナリセーフなデータ処理

// 文字列補間を使用したコマンド
const char *key = "user:1001";
const char *value = "John Doe";
reply = redisCommand(c, "SET %s %s", key, value);
freeReplyObject(reply);

// バイナリセーフなデータの処理
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);

// 複数の補間を使用
const char *hash_key = "user:profile";
const char *field = "name";
reply = redisCommand(c, "HSET %s %s %s", hash_key, field, value);
freeReplyObject(reply);

パイプライニングによる効率的なコマンド実行

#include <hiredis/hiredis.h>

void pipeline_example(redisContext *c) {
    redisReply *reply;
    
    // 複数のコマンドをパイプラインで送信
    redisAppendCommand(c, "SET key1 value1");
    redisAppendCommand(c, "SET key2 value2");
    redisAppendCommand(c, "GET key1");
    redisAppendCommand(c, "GET key2");
    
    // レスポンスを順次取得
    redisGetReply(c, (void**)&reply); // SET key1の結果
    printf("SET key1: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisGetReply(c, (void**)&reply); // SET key2の結果
    printf("SET key2: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisGetReply(c, (void**)&reply); // GET key1の結果
    printf("GET key1: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisGetReply(c, (void**)&reply); // GET key2の結果
    printf("GET key2: %s\n", reply->str);
    freeReplyObject(reply);
}

非同期API使用例

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

// コマンド完了時のコールバック
void commandCallback(redisAsyncContext *c, void *reply, void *privdata) {
    redisReply *r = reply;
    if (reply == NULL) return;
    
    printf("コマンド完了: %s\n", r->str);
    // 注意: 非同期では freeReplyObject を呼ばない
}

// 接続完了時のコールバック
void connectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("接続エラー: %s\n", c->errstr);
        return;
    }
    printf("接続完了\n");
}

// 切断時のコールバック
void disconnectCallback(const redisAsyncContext *c, int status) {
    if (status != REDIS_OK) {
        printf("切断エラー: %s\n", c->errstr);
        return;
    }
    printf("切断完了\n");
}

int async_example() {
    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
    if (c->err) {
        printf("エラー: %s\n", c->errstr);
        redisAsyncFree(c);
        return 1;
    }
    
    // コールバック設定
    redisAsyncSetConnectCallback(c, connectCallback);
    redisAsyncSetDisconnectCallback(c, disconnectCallback);
    
    // 非同期コマンド実行
    redisAsyncCommand(c, commandCallback, NULL, "SET foo bar");
    redisAsyncCommand(c, commandCallback, NULL, "GET foo");
    
    // イベントループの実行が必要(libevent、libuv等)
    // この例では簡略化
    
    return 0;
}

SSL/TLS接続の例

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

int ssl_example() {
    redisSSLContext *ssl_context;
    redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
    
    // OpenSSLの初期化
    redisInitOpenSSL();
    
    // SSL contextの作成
    ssl_context = redisCreateSSLContext(
        "ca-bundle.crt",        // CA証明書ファイル
        "/path/to/certs",       // 信頼できる証明書のパス
        "client-cert.pem",      // クライアント証明書
        "client-key.pem",       // クライアント秘密鍵
        "redis.example.com",    // サーバー名(SNI)
        &ssl_error
    );
    
    if (ssl_context == NULL || ssl_error != REDIS_SSL_CTX_NONE) {
        printf("SSL context作成エラー\n");
        return 1;
    }
    
    // Redis接続を確立
    redisContext *c = redisConnect("localhost", 6443);
    if (c == NULL || c->err) {
        printf("接続エラー\n");
        return 1;
    }
    
    // SSL/TLSハンドシェイク
    if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) {
        printf("SSLハンドシェイクエラー: %s\n", c->errstr);
        redisFree(c);
        return 1;
    }
    
    // SSL接続でのコマンド実行
    redisReply *reply = redisCommand(c, "PING");
    printf("PING: %s\n", reply->str);
    freeReplyObject(reply);
    
    redisFree(c);
    return 0;
}

カスタムメモリアロケータの設定

#include <hiredis/hiredis.h>

// カスタムメモリ関数
void* my_malloc(size_t size) {
    printf("カスタムmalloc: %zu bytes\n", size);
    return malloc(size);
}

void my_free(void* ptr) {
    printf("カスタムfree\n");
    free(ptr);
}

void setup_custom_allocators() {
    hiredisAllocFuncs custom_funcs = {
        .mallocFn = my_malloc,
        .callocFn = calloc,      // 標準のcallocを使用
        .reallocFn = realloc,    // 標準のreallocを使用
        .strdupFn = strdup,      // 標準のstrdupを使用
        .freeFn = my_free,
    };
    
    // カスタムアロケータを設定
    hiredisAllocFuncs original = hiredisSetAllocators(&custom_funcs);
    
    // 必要に応じて元の設定に戻す
    // hiredisSetAllocators(&original);
    
    // またはデフォルトに戻す
    // hiredisResetAllocators();
}

エラーハンドリングとリソース管理

int safe_redis_operation() {
    redisContext *c = NULL;
    redisReply *reply = NULL;
    int result = 0;
    
    // 接続の確立
    c = redisConnect("127.0.0.1", 6379);
    if (c == NULL) {
        printf("メモリ不足\n");
        result = -1;
        goto cleanup;
    }
    
    if (c->err) {
        printf("接続エラー: %s\n", c->errstr);
        result = -1;
        goto cleanup;
    }
    
    // コマンドの実行
    reply = redisCommand(c, "SET test_key test_value");
    if (reply == NULL) {
        printf("コマンド実行エラー\n");
        result = -1;
        goto cleanup;
    }
    
    if (reply->type == REDIS_REPLY_ERROR) {
        printf("Redisエラー: %s\n", reply->str);
        result = -1;
        goto cleanup;
    }
    
    printf("操作成功\n");
    
cleanup:
    if (reply) freeReplyObject(reply);
    if (c) redisFree(c);
    return result;
}