Hiredis
キャッシュライブラリ
Hiredis
概要
HiredisはRedisサーバー用の軽量でミニマルなC言語クライアントライブラリです。Redis公式プロジェクトの一部として開発・維持されており、高いパフォーマンスと低いメモリフットプリントを実現しています。同期・非同期両方のAPIを提供し、Redis プロトコルの完全な実装により、Redisの全機能を効率的に活用できます。
詳細
Hiredisは、C言語で書かれたアプリケーションがRedisサーバーと効率的に通信するためのライブラリです。最小限の依存関係で設計されており、組み込みシステムからハイパフォーマンスサーバーまで幅広い環境で利用可能です。同期的なブロッキングAPIと非同期APIの両方をサポートし、開発者のニーズに応じて適切な通信方法を選択できます。
主要な機能
- 同期API:
redisConnect、redisCommand、freeReplyObjectによる簡単な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;
}