Redis中灵活结合SET和SETEX的方法及多语言工具库实现

发布于:2025-08-14 ⋅ 阅读:(12) ⋅ 点赞:(0)

Redis中灵活结合SET和SETEX的方法及多语言工具库实现

本文介绍如何结合Redis的SETSETEX命令实现灵活的键值操作,并提供Python、C++和Golang的封装工具库源码。


一、设计思路

通过创建统一的set函数整合两种操作:

  1. 支持永不过期(使用SET)和可过期数据(使用SETEX)
  2. 支持批量操作和异步执行
  3. 封装连接池管理
  4. 支持标准JSON序列化

二、Python实现

# redis_tool.py
import json
import redis
from typing import Union, Any

class RedisTool:
    def __init__(self, host='localhost', port=6379, db=0, pool_size=10):
        self.pool = redis.ConnectionPool(
            host=host, port=port, db=db, max_connections=pool_size
        )

    def _get_conn(self):
        return redis.Redis(connection_pool=self.pool)

    def set(
        self,
        key: str,
        value: Union[dict, list, str, bytes],
        expire: int = 0  # 0表示永不过期
    ) -> bool:
        """存储数据(支持JSON和二进制)"""
        conn = self._get_conn()
        
        # 序列化处理
        if isinstance(value, (dict, list)):
            value = json.dumps(value)
        elif not isinstance(value, (str, bytes)):
            raise TypeError("Unsupported value type")
        
        # 选择命令
        if expire > 0:
            return conn.setex(key, expire, value)
        return conn.set(key, value)

    def get(self, key: str, parse_json: bool = False) -> Any:
        """获取数据(支持JSON解析)"""
        conn = self._get_conn()
        result = conn.get(key)
        if result and parse_json:
            return json.loads(result)
        return result

    def batch_set(self, items: dict, expire: int = 0):
        """批量设置键值"""
        conn = self._get_conn()
        pipe = conn.pipeline()
        for key, value in items.items():
            if isinstance(value, (dict, list)):
                value = json.dumps(value)
            if expire > 0:
                pipe.setex(key, expire, value)
            else:
                pipe.set(key, value)
        pipe.execute()

三、C++实现(基于hiredis)

// redis_tool.hpp
#include <hiredis/hiredis.h>
#include <string>
#include <unordered_map>
#include <nlohmann/json.hpp>

class RedisTool {
public:
    RedisTool(const std::string& host = "127.0.0.1", int port = 6379) 
        : context_(redisConnect(host.c_str(), port)) {}
    
    ~RedisTool() {
        redisFree(context_);
    }

    bool set(const std::string& key, const std::string& value, int expire = 0) {
        redisReply* reply = nullptr;
        if (expire > 0) {
            reply = (redisReply*)redisCommand(
                context_, "SETEX %s %d %b", key.c_str(), expire, value.data(), value.size()
            );
        } else {
            reply = (redisReply*)redisCommand(
                context_, "SET %s %b", key.c_str(), value.data(), value.size()
            );
        }
        bool success = reply && reply->type == REDIS_REPLY_STATUS;
        freeReplyObject(reply);
        return success;
    }

    bool set_json(const std::string& key, const nlohmann::json& value, int expire = 0) {
        return set(key, value.dump(), expire);
    }

    std::string get(const std::string& key) {
        redisReply* reply = (redisReply*)redisCommand(context_, "GET %s", key.c_str());
        std::string result;
        if (reply && reply->type == REDIS_REPLY_STRING) {
            result = std::string(reply->str, reply->len);
        }
        freeReplyObject(reply);
        return result;
    }

    template<typename T>
    T get_json(const std::string& key) {
        auto str = get(key);
        return nlohmann::json::parse(str).get<T>();
    }

private:
    redisContext* context_;
};

四、Golang实现

// redis_tool.go
package redis_tool

import (
	"context"
	"encoding/json"
	"time"

	"github.com/go-redis/redis/v8"
)

type RedisClient struct {
	client *redis.Client
	ctx    context.Context
}

func NewRedisClient(addr string, db int) *RedisClient {
	return &RedisClient{
		client: redis.NewClient(&redis.Options{
			Addr: addr,
			DB:   db,
		}),
		ctx: context.Background(),
	}
}

func (rc *RedisClient) Set(key string, value interface{}, expiration time.Duration) error {
	// 自动序列化JSON
	var val interface{}
	switch v := value.(type) {
	case string, []byte:
		val = v
	default:
		data, err := json.Marshal(v)
		if err != nil {
			return err
		}
		val = data
	}

	return rc.client.Set(rc.ctx, key, val, expiration).Err()
}

func (rc *RedisClient) Get(key string) (string, error) {
	return rc.client.Get(rc.ctx, key).Result()
}

func (rc *RedisClient) GetJSON(key string, v interface{}) error {
	data, err := rc.client.Get(rc.ctx, key).Bytes()
	if err != nil {
		return err
	}
	return json.Unmarshal(data, v)
}

func (rc *RedisClient) BatchSet(items map[string]interface{}, expiration time.Duration) {
	pipe := rc.client.Pipeline()
	for k, v := range items {
		pipe.Set(rc.ctx, k, v, expiration)
	}
	pipe.Exec(rc.ctx)
}

五、使用示例

Python:
tool = RedisTool()
tool.set("user:1", {"name": "John", "age": 30}, expire=3600)  # 带过期时间
user = tool.get("user:1", parse_json=True)
C++:
RedisTool rt;
rt.set_json("config:app", {{"theme", "dark"}, {"lang", "en"}}, 86400);
auto config = rt.get_json<nlohmann::json>("config:app");
Golang:
client := NewRedisClient("localhost:6379", 0)
client.Set("cache:homepage", "<html>...</html>", 10*time.Minute)

var user User
client.GetJSON("user:100", &user)

六、设计优势

  1. 智能序列化​:自动处理JSON转换
  2. 过期策略统一​:expire=0表示永不过期
  3. 连接复用​:内置连接池管理
  4. 批量操作​:减少网络开销
  5. 类型安全​:C++模板强化类型检查

完整项目地址:https://github.com/example/redis-toolbox

通过此设计,开发者可以灵活选择存储策略,简化了缓存管理和持久化存储的统一操作,同时保证了多语言环境下API的一致性。

https://github.com/0voice


网站公告

今日签到

点亮在社区的每一天
去签到