Golang中Redis缓存的实现位置在哪里

Golang中Redis缓存的实现位置在哪里 在哪里实现Redis缓存最合适? API服务器还是前端?还是两者都需要?

4 回复

API 将在网页和移动端使用。我认为我需要在各处实现缓存。这样将减少网络调用(如果在前端实现)和数据库调用(如果在 API 中实现)。

更多关于Golang中Redis缓存的实现位置在哪里的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


嗯,这真的取决于你的需求。

首先,你应该在后端层面实现,或者,也许更好的是,在“API网关”层面实现。 一旦你在后端层面有了它,你也可以考虑在前端层面做一些缓存。不过,在那种情况下,我不会为此使用Redis,而只会使用内存缓存。

嗯,Redis是一款只能运行在服务器上的软件,你无法将其交付给浏览器运行,如果这就是你所说的“前端”的话。

同样,运行在浏览器中的JavaScript也无法直接连接到Redis实例。

因此,如果你想将Redis用作缓存,这对其部署位置施加了一些限制。

当然,还有很多其他的缓存策略可供选择,其中一些可以在服务器端使用,另一些则可以在客户端使用。

它们都有各自不同的使用场景、优点和缺点。所有策略都必须处理缓存失效问题,这是计算机科学中两大难题之一……

  1. 命名
  2. 缓存失效
  3. 差一错误

在Golang项目中实现Redis缓存的最佳位置是API服务器层。前端通常不直接与Redis交互,而是通过API服务器作为中介。以下是典型的实现方式:

1. 服务层/业务逻辑层实现

在服务层实现Redis缓存是最常见的做法:

package service

import (
    "context"
    "encoding/json"
    "time"
    
    "github.com/go-redis/redis/v8"
)

type UserService struct {
    redisClient *redis.Client
    userRepo    UserRepository
}

func (s *UserService) GetUserByID(ctx context.Context, userID string) (*User, error) {
    cacheKey := "user:" + userID
    
    // 1. 尝试从Redis获取缓存
    cachedData, err := s.redisClient.Get(ctx, cacheKey).Result()
    if err == nil {
        var user User
        if err := json.Unmarshal([]byte(cachedData), &user); err == nil {
            return &user, nil
        }
    }
    
    // 2. 缓存未命中,从数据库查询
    user, err := s.userRepo.FindByID(ctx, userID)
    if err != nil {
        return nil, err
    }
    
    // 3. 将结果存入Redis缓存
    userJSON, _ := json.Marshal(user)
    s.redisClient.Set(ctx, cacheKey, userJSON, 10*time.Minute)
    
    return user, nil
}

2. 仓储层(Repository)封装

也可以在仓储层实现缓存逻辑:

package repository

import (
    "context"
    "encoding/json"
    "time"
    
    "github.com/go-redis/redis/v8"
    "gorm.io/gorm"
)

type CachedUserRepository struct {
    db          *gorm.DB
    redisClient *redis.Client
}

func (r *CachedUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    cacheKey := "user:" + id
    
    // 检查缓存
    cached, err := r.redisClient.Get(ctx, cacheKey).Result()
    if err == nil {
        var user User
        json.Unmarshal([]byte(cached), &user)
        return &user, nil
    }
    
    // 数据库查询
    var user User
    if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil {
        return nil, err
    }
    
    // 设置缓存
    userJSON, _ := json.Marshal(user)
    r.redisClient.Set(ctx, cacheKey, userJSON, 10*time.Minute)
    
    return &user, nil
}

3. 中间件实现

对于HTTP请求级别的缓存:

package middleware

import (
    "context"
    "net/http"
    "time"
    
    "github.com/go-redis/redis/v8"
)

func RedisCacheMiddleware(redisClient *redis.Client, ttl time.Duration) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            cacheKey := "http:" + r.Method + ":" + r.URL.Path
            
            // 只缓存GET请求
            if r.Method == http.MethodGet {
                cached, err := redisClient.Get(r.Context(), cacheKey).Bytes()
                if err == nil {
                    w.Header().Set("X-Cache", "HIT")
                    w.Write(cached)
                    return
                }
            }
            
            // 缓存未命中,继续处理
            recorder := &responseRecorder{ResponseWriter: w}
            next.ServeHTTP(recorder, r)
            
            // 缓存响应
            if r.Method == http.MethodGet && recorder.status == http.StatusOK {
                redisClient.Set(r.Context(), cacheKey, recorder.body, ttl)
            }
        })
    }
}

4. 缓存策略示例

package cache

import (
    "context"
    "fmt"
    "time"
    
    "github.com/go-redis/redis/v8"
)

type CacheManager struct {
    client *redis.Client
}

// 带锁的缓存获取,防止缓存击穿
func (cm *CacheManager) GetOrSet(ctx context.Context, key string, ttl time.Duration, 
    fetchFunc func() (interface{}, error)) (string, error) {
    
    // 尝试获取缓存
    val, err := cm.client.Get(ctx, key).Result()
    if err == nil {
        return val, nil
    }
    
    // 获取分布式锁
    lockKey := "lock:" + key
    locked, err := cm.client.SetNX(ctx, lockKey, "1", 5*time.Second).Result()
    if err != nil {
        return "", err
    }
    
    if locked {
        defer cm.client.Del(ctx, lockKey)
        
        // 执行数据获取函数
        data, err := fetchFunc()
        if err != nil {
            return "", err
        }
        
        // 序列化并存储
        dataStr := fmt.Sprintf("%v", data)
        cm.client.Set(ctx, key, dataStr, ttl)
        return dataStr, nil
    }
    
    // 等待其他goroutine设置缓存
    time.Sleep(100 * time.Millisecond)
    return cm.client.Get(ctx, key).Result()
}

总结

Redis缓存在Golang中通常在API服务器的服务层或仓储层实现。前端不直接操作Redis,而是通过API接口获取已缓存的数据。这种架构确保了数据一致性和安全性,同时提供了良好的性能优化。

回到顶部