使用Redis和Go实现高性能缓存


Go 是构建高性能 Web 应用程序的优秀语言,而高性能 Web 应用程序通常需要集中式缓存。

当今流行的 Go 库缺乏对内存高效流的支持。相反,他们提供了[]byte方式,如果您缓存小对象,这不是问题,但如果您缓存大于 1kb 的对象,则[]byte则慢了。

/ This code uses https://github.com/redis/go-redis, but the same
// restrictions apply with Rueidis and Redigo.
func redisHandler(w http.ResponseWriter, r *http.Request) {
    ctx := context.Background()

    
// Extract key from RequestURI
    key := strings.TrimLeft(r.RequestURI,
"/")

    
//以字节片形式从 Redis 获取值
    val, err := rdb.Get(ctx, key).Bytes()
    if err == redis.Nil {
        http.Error(w,
"Key not found in Redis", http.StatusNotFound)
        return
    } else if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    _, err = w.Write(val)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

流式读取简介
Redis 协议并不妨碍我们构建流 API。因此,我编写了redjet,一个面向性能的 Redis 库。

使用redjet,您可以像这样编写代码:

func redisHandler(w http.ResponseWriter, r *http.Request) {
    ctx := context.Background()

    // 从 RequestURI 中提取键值
    key := strings.TrimLeft(r.RequestURI,
"/")

    
//将值直接从 Redis 流式传输到响应。
    _, err := rdb.Command(
"GET", key).WriteTo(w)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

流式写入
流行的 Redis 库在向 Redis 写入值时也遇到同样的问题。[]byte它们要求您在通过网络发送之前将整个值保存在内存中。

使用redjet,您可以将值流式传输到 Redis,如下所示:

fi := strings.NewReader("Some file contents")

err := rdb.Command(
"SET", "key", fi).Ok()

// handle error

有一个重要的警告。在 Redis 协议中,值是有长度前缀的,所以我们不能流式传输 vanilla io.Reader。我推测这是流行库不支持流写入的主要原因。

为了解决这个问题,redjet需要将其redjet.LenReader定义为:

type LenReader interface {
    Len() int
    io.Reader
}

可使用 redjet.NewLenReader 快速创建。

标准库中的某些类型(例如 bytes.Readerstrings.Readerbytes.Buffer)可以方便地隐式实现redjet.LenReader.

性能测试
对 redjet 进行了基准测试:


完整的基准测试结果可在此处获取