康心伴Logo
康心伴WellAlly
Backend Development

使用Go和Redis构建高性能API

5 分钟阅读

使用Go和Redis构建高性能API

Go语言以其出色的并发性能和简洁的语法,成为构建高性能API的理想选择。结合Redis的内存存储能力,可以实现毫秒级响应的API服务。本文将教你如何从零开始构建一个生产级的Go+Redis API。

为什么选择Go + Redis?

特性Go优势Redis优势
性能原生并发、GC优化内存操作、微秒延迟
简洁性单一二进制、依赖少丰富数据结构
可扩展性水平扩展友好Cluster支持
生态丰富的标准库广泛的客户端支持

性能对比:

  • Go HTTP服务: ~50K-100K req/s
  • Redis GET: ~100K ops/s
  • 组合使用: 亚毫秒级P99延迟

项目结构

code
go-redis-api/
├── cmd/
│   └── server/
│       └── main.go                 # 应用入口
├── internal/
│   ├── config/
│   │   └── config.go               # 配置管理
│   ├── handlers/
│   │   ├── health.go               # 健康检查
│   │   ├── user.go                 # 用户处理器
│   │   └── cache.go                # 缓存处理器
│   ├── models/
│   │   ├── user.go                 # 用户模型
│   │   └── response.go             # 响应模型
│   ├── repository/
│   │   ├── redis.go                # Redis仓储
│   │   └── cache.go                # 缓存仓储
│   ├── services/
│   │   └── user_service.go         # 用户服务
│   ├── middleware/
│   │   ├── auth.go                 # 认证中间件
│   │   ├── rate_limit.go           # 限流中间件
│   │   ├── logging.go              # 日志中间件
│   │   └── recovery.go             # 恢复中间件
│   └── utils/
│       ├── jwt.go                  # JWT工具
│       └── response.go             # 响应工具
├── pkg/
│   └── redis/
│       └── client.go               # Redis客户端
├── config/
│   ├── config.yaml                 # 配置文件
│   └── config.example.yaml
├── scripts/
│   ├── build.sh
│   └── run.sh
├── go.mod
├── go.sum
├── Dockerfile
└── docker-compose.yml
Code collapsed

1. 项目初始化

go.mod

code
module github.com/example/go-redis-api

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/redis/go-redis/v9 v9.3.0
    github.com/spf13/viper v1.17.0
    go.uber.org/zap v1.26.0
    github.com/golang-jwt/jwt/v5 v5.0.0
    github.com/google/uuid v1.3.1
)
Code collapsed

docker-compose.yml

code
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      - REDIS_ADDR=redis:6379
      - REDIS_PASSWORD=
      - REDIS_DB=0
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes

volumes:
  redis_data:
Code collapsed

2. Redis客户端封装

pkg/redis/client.go

code
package redis

import (
	"context"
	"time"

	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

type Client struct {
	client *redis.Client
	logger *zap.Logger
}

type Config struct {
	Addr         string
	Password     string
	DB           int
	PoolSize     int
	MinIdleConns int
	DialTimeout  time.Duration
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
	PoolTimeout  time.Duration
}

func NewClient(cfg Config, logger *zap.Logger) (*Client, error) {
	client := redis.NewClient(&redis.Options{
		Addr:         cfg.Addr,
		Password:     cfg.Password,
		DB:           cfg.DB,
		PoolSize:     cfg.PoolSize,
		MinIdleConns: cfg.MinIdleConns,
		DialTimeout:  cfg.DialTimeout,
		ReadTimeout:  cfg.ReadTimeout,
		WriteTimeout: cfg.WriteTimeout,
		PoolTimeout:  cfg.PoolTimeout,
	})

	// 测试连接
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := client.Ping(ctx).Err(); err != nil {
		return nil, err
	}

	logger.Info("Redis连接成功", zap.String("addr", cfg.Addr))

	return &Client{
		client: client,
		logger: logger,
	}, nil
}

func (c *Client) Close() error {
	return c.client.Close()
}

// 基础操作
func (c *Client) Get(ctx context.Context, key string) (*redis.StringCmd, error) {
	return c.client.Get(ctx, key), nil
}

func (c *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
	return c.client.Set(ctx, key, value, expiration).Err()
}

func (c *Client) Del(ctx context.Context, keys ...string) error {
	return c.client.Del(ctx, keys...).Err()
}

func (c *Client) Exists(ctx context.Context, keys ...string) (int64, error) {
	return c.client.Exists(ctx, keys...).Result()
}

// 列表操作
func (c *Client) LPush(ctx context.Context, key string, values ...interface{}) error {
	return c.client.LPush(ctx, key, values...).Err()
}

func (c *Client) RPop(ctx context.Context, key string) *redis.StringCmd {
	return c.client.RPop(ctx, key)
}

// 哈希操作
func (c *Client) HSet(ctx context.Context, key, field string, value interface{}) error {
	return c.client.HSet(ctx, key, field, value).Err()
}

func (c *Client) HGet(ctx context.Context, key, field string) *redis.StringCmd {
	return c.client.HGet(ctx, key, field)
}

func (c *Client) HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd {
	return c.client.HGetAll(ctx, key)
}

// 集合操作
func (c *Client) SAdd(ctx context.Context, key string, members ...interface{}) error {
	return c.client.SAdd(ctx, key, members...).Err()
}

func (c *Client) SMembers(ctx context.Context, key string) *redis.StringSliceCmd {
	return c.client.SMembers(ctx, key)
}

// 有序集合操作
func (c *Client) ZAdd(ctx context.Context, key string, members ...*redis.Z) error {
	return c.client.ZAdd(ctx, key, members...).Err()
}

func (c *Client) ZRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd {
	return c.client.ZRange(ctx, key, start, stop)
}

func (c *Client) ZRevRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd {
	return c.client.ZRevRange(ctx, key, start, stop)
}

// 缓存操作
func (c *Client) SetJSON(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
	return c.client.JSONSet(ctx, key, "$", value).Err()
}

func (c *Client) GetJSON(ctx context.Context, key string) *redis.Cmd {
	return c.client.JSONGet(ctx, key)
}

// Pipeline操作
func (c *Client) Pipeline() redis.Pipeliner {
	return c.client.Pipeline()
}

// 事务操作
func (c *Client) TxPipeline() redis.Pipeliner {
	return c.client.TxPipeline()
}
Code collapsed

3. 配置管理

internal/config/config.go

code
package config

import (
	"time"

	"github.com/spf13/viper"
)

type Config struct {
	Server   ServerConfig
	Redis    RedisConfig
	JWT      JWTConfig
	Cache    CacheConfig
}

type ServerConfig struct {
	Port            int
	Mode            string
	ReadTimeout     time.Duration
	WriteTimeout    time.Duration
	ShutdownTimeout time.Duration
}

type RedisConfig struct {
	Addr         string
	Password     string
	DB           int
	PoolSize     int
	MinIdleConns int
	DialTimeout  time.Duration
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
}

type JWTConfig struct {
	Secret     string
	ExpireTime time.Duration
}

type CacheConfig struct {
	UserInfoTTL     time.Duration
	DefaultTTL      time.Duration
	MaxCacheSize    int64
	EvictionPolicy  string
}

func Load(configPath string) (*Config, error) {
	viper.SetConfigFile(configPath)
	viper.SetConfigType("yaml")

	// 读取环境变量
	viper.AutomaticEnv()

	if err := viper.ReadInConfig(); err != nil {
		return nil, err
	}

	var cfg Config
	if err := viper.Unmarshal(&cfg); err != nil {
		return nil, err
	}

	return &cfg, nil
}
Code collapsed

config/config.yaml

code
server:
  port: 8080
  mode: release  # debug, release, test
  readTimeout: 60s
  writeTimeout: 60s
  shutdownTimeout: 10s

redis:
  addr: localhost:6379
  password: ""
  db: 0
  poolSize: 100
  minIdleConns: 10
  dialTimeout: 5s
  readTimeout: 3s
  writeTimeout: 3s

jwt:
  secret: your-secret-key
  expireTime: 24h

cache:
  userInfoTTL: 1h
  defaultTTL: 30m
  maxCacheSize: 100MB
  evictionPolicy: allkeys-lru
Code collapsed

4. 用户模型

internal/models/user.go

code
package models

import (
	"time"

	"github.com/google/uuid"
)

type User struct {
	ID        uuid.UUID `json:"id"`
	Username  string    `json:"username"`
	Email     string    `json:"email"`
	Password  string    `json:"-"` // 不输出JSON
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type UserCreate struct {
	Username string `json:"username" binding:"required,min=3,max=50"`
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required,min=8"`
}

type UserUpdate struct {
	Email string `json:"email" binding:"omitempty,email"`
}

type UserResponse struct {
	ID        uuid.UUID `json:"id"`
	Username  string    `json:"username"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
}

func (u *User) ToResponse() UserResponse {
	return UserResponse{
		ID:        u.ID,
		Username:  u.Username,
		Email:     u.Email,
		CreatedAt: u.CreatedAt,
	}
}

type CacheUser struct {
	ID       string `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
}

func (u *User) ToCache() CacheUser {
	return CacheUser{
		ID:       u.ID.String(),
		Username: u.Username,
		Email:    u.Email,
	}
}
Code collapsed

5. 缓存仓储

internal/repository/cache.go

code
package repository

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

	"github.com/example/go-redis-api/internal/models"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

type CacheRepository struct {
	client *redis.Client
	logger *zap.Logger
	ttl    time.Duration
}

func NewCacheRepository(client *redis.Client, logger *zap.Logger, ttl time.Duration) *CacheRepository {
	return &CacheRepository{
		client: client,
		logger: logger,
		ttl:    ttl,
	}
}

func (r *CacheRepository) SetUser(ctx context.Context, user *models.User) error {
	key := r.userKey(user.ID.String())
	data, err := json.Marshal(user.ToCache())
	if err != nil {
		return err
	}

	return r.client.Set(ctx, key, data, r.ttl).Err()
}

func (r *CacheRepository) GetUser(ctx context.Context, userID string) (*models.CacheUser, error) {
	key := r.userKey(userID)
	data, err := r.client.Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, nil // 缓存未命中
		}
		return nil, err
	}

	var user models.CacheUser
	if err := json.Unmarshal(data, &user); err != nil {
		return nil, err
	}

	return &user, nil
}

func (r *CacheRepository) DeleteUser(ctx context.Context, userID string) error {
	key := r.userKey(userID)
	return r.client.Del(ctx, key).Err()
}

func (r *CacheRepository) SetSession(ctx context.Context, sessionID string, data interface{}, ttl time.Duration) error {
	key := r.sessionKey(sessionID)
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	return r.client.Set(ctx, key, jsonData, ttl).Err()
}

func (r *CacheRepository) GetSession(ctx context.Context, sessionID string) (string, error) {
	key := r.sessionKey(sessionID)
	return r.client.Get(ctx, key).Result()
}

func (r *CacheRepository) DeleteSession(ctx context.Context, sessionID string) error {
	key := r.sessionKey(sessionID)
	return r.client.Del(ctx, key).Err()
}

// 分布式锁
func (r *CacheRepository) AcquireLock(ctx context.Context, key string, ttl time.Duration) (bool, error) {
	return r.client.SetNX(ctx, r.lockKey(key), "1", ttl).Result()
}

func (r *CacheRepository) ReleaseLock(ctx context.Context, key string) error {
	return r.client.Del(ctx, r.lockKey(key)).Err()
}

// 计数器
func (r *CacheRepository) Increment(ctx context.Context, key string) (int64, error) {
	return r.client.Incr(ctx, key).Result()
}

func (r *CacheRepository) IncrementWithExpire(ctx context.Context, key string, ttl time.Duration) (int64, error) {
	return r.client.Incr(ctx, key).Result()
}

// 集合操作
func (r *CacheRepository) AddToSet(ctx context.Context, key, member string) error {
	return r.client.SAdd(ctx, key, member).Err()
}

func (r *CacheRepository) IsMemberOfSet(ctx context.Context, key, member string) (bool, error) {
	return r.client.SIsMember(ctx, key, member).Result()
}

// 辅助方法
func (r *CacheRepository) userKey(id string) string {
	return fmt.Sprintf("user:%s", id)
}

func (r *CacheRepository) sessionKey(id string) string {
	return fmt.Sprintf("session:%s", id)
}

func (r *CacheRepository) lockKey(key string) string {
	return fmt.Sprintf("lock:%s", key)
}
Code collapsed

6. 中间件实现

internal/middleware/rate_limit.go

code
package middleware

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
)

type RateLimiter struct {
	client *redis.Client
	limits map[string]int // endpoint -> requests per minute
}

func NewRateLimiter(client *redis.Client) *RateLimiter {
	return &RateLimiter{
		client: client,
		limits: map[string]int{
			"/api/v1/users": 100,
			"/api/v1/login": 10,
			"default":       60,
		},
	}
}

func (rl *RateLimiter) Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx := context.Background()
		key := rl.getKey(c)
		limit := rl.getLimit(c)

		// 使用Redis INCR实现计数
		current, err := rl.client.Incr(ctx, key).Result()
		if err != nil {
			c.Next()
			return
		}

		// 首次请求,设置过期时间
		if current == 1 {
			rl.client.Expire(ctx, key, time.Minute)
		}

		// 设置响应头
		c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", limit))
		c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", max(0, limit-current)))
		c.Header("X-RateLimit-Reset", time.Now().Add(time.Minute).Format(time.RFC1123))

		// 超出限制
		if current > int64(limit) {
			c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
				"error": "请求频率超限,请稍后再试",
			})
			return
		}

		c.Next()
	}
}

func (rl *RateLimiter) getKey(c *gin.Context) string {
	ip := c.ClientIP()
	path := c.FullPath()
	return fmt.Sprintf("ratelimit:%s:%s", ip, path)
}

func (rl *RateLimiter) getLimit(c *gin.Context) int {
	path := c.FullPath()
	if limit, ok := rl.limits[path]; ok {
		return limit
	}
	return rl.limits["default"]
}
Code collapsed

internal/middleware/cache.go

code
package middleware

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
)

type CacheMiddleware struct {
	client *redis.Client
	ttl    time.Duration
}

func NewCacheMiddleware(client *redis.Client, ttl time.Duration) *CacheMiddleware {
	return &CacheMiddleware{
		client: client,
		ttl:    ttl,
	}
}

func (cm *CacheMiddleware) Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 只缓存GET请求
		if c.Request.Method != http.MethodGet {
			c.Next()
			return
		}

		ctx := context.Background()
		key := cm.getCacheKey(c)

		// 尝试从缓存获取
		cached, err := cm.client.Get(ctx, key).Result()
		if err == nil {
			var response interface{}
			if err := json.Unmarshal([]byte(cached), &response); err == nil {
				c.Header("X-Cache", "HIT")
				c.JSON(http.StatusOK, response)
				c.Abort()
				return
			}
		}

		// 使用ResponseWriter包装器捕获响应
		w := &responseWriter{ResponseWriter: c.Writer, body: []byte{}}
		c.Writer = w

		c.Next()

		// 缓存成功响应
		if c.Writer.Status() == http.StatusOK && len(w.body) > 0 {
			cm.client.Set(ctx, key, w.body, cm.ttl)
			c.Header("X-Cache", "MISS")
		}
	}
}

func (cm *CacheMiddleware) getCacheKey(c *gin.Context) string {
	return fmt.Sprintf("cache:%s:%s", c.Request.URL.Path, c.Request.URL.RawQuery)
}

type responseWriter struct {
	gin.ResponseWriter
	body []byte
}

func (w *responseWriter) Write(b []byte) (int, error) {
	w.body = append(w.body, b...)
	return w.ResponseWriter.Write(b)
}
Code collapsed

7. HTTP处理器

internal/handlers/user.go

code
package handlers

import (
	"net/http"
	"time"

	"github.com/example/go-redis-api/internal/models"
	"github.com/example/go-redis-api/internal/repository"
	"github.com/example/go-redis-api/internal/utils"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

type UserHandler struct {
	cacheRepo *repository.CacheRepository
	logger    *zap.Logger
}

func NewUserHandler(cacheRepo *repository.CacheRepository, logger *zap.Logger) *UserHandler {
	return &UserHandler{
		cacheRepo: cacheRepo,
		logger:    logger,
	}
}

// CreateUser 创建用户
func (h *UserHandler) CreateUser(c *gin.Context) {
	var req models.UserCreate
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.ErrorResponse(c, http.StatusBadRequest, "无效的请求参数", err)
		return
	}

	// 创建用户
	user := &models.User{
		ID:        uuid.New(),
		Username:  req.Username,
		Email:     req.Email,
		Password:  utils.HashPassword(req.Password),
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// 存储到缓存
	ctx := c.Request.Context()
	if err := h.cacheRepo.SetUser(ctx, user); err != nil {
		h.logger.Error("存储用户到缓存失败", zap.Error(err))
		utils.ErrorResponse(c, http.StatusInternalServerError, "创建用户失败", err)
		return
	}

	utils.SuccessResponse(c, http.StatusCreated, user.ToResponse(), "用户创建成功")
}

// GetUser 获取用户
func (h *UserHandler) GetUser(c *gin.Context) {
	userID := c.Param("id")
	ctx := c.Request.Context()

	// 先从缓存获取
	cachedUser, err := h.cacheRepo.GetUser(ctx, userID)
	if err != nil {
		utils.ErrorResponse(c, http.StatusInternalServerError, "获取用户失败", err)
		return
	}

	if cachedUser != nil {
		utils.SuccessResponse(c, http.StatusOK, cachedUser, "获取用户成功(缓存)")
		return
	}

	// 缓存未命中,返回404
	utils.ErrorResponse(c, http.StatusNotFound, "用户不存在", nil)
}

// DeleteUser 删除用户
func (h *UserHandler) DeleteUser(c *gin.Context) {
	userID := c.Param("id")
	ctx := c.Request.Context()

	if err := h.cacheRepo.DeleteUser(ctx, userID); err != nil {
		utils.ErrorResponse(c, http.StatusInternalServerError, "删除用户失败", err)
		return
	}

	utils.SuccessResponse(c, http.StatusOK, nil, "用户删除成功")
}
Code collapsed

8. 主程序

cmd/server/main.go

code
package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/example/go-redis-api/internal/config"
	"github.com/example/go-redis-api/internal/handlers"
	"github.com/example/go-redis-api/internal/middleware"
	"github.com/example/go-redis-api/pkg/redis"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func main() {
	// 加载配置
	cfg, err := config.Load("./config/config.yaml")
	if err != nil {
		panic(fmt.Sprintf("加载配置失败: %v", err))
	}

	// 初始化日志
	logger, _ := zap.NewProduction()
	defer logger.Sync()

	// 初始化Redis
	redisClient, err := redis.NewClient(redis.Config{
		Addr:         cfg.Redis.Addr,
		Password:     cfg.Redis.Password,
		DB:           cfg.Redis.DB,
		PoolSize:     cfg.Redis.PoolSize,
		MinIdleConns: cfg.Redis.MinIdleConns,
		DialTimeout:  cfg.Redis.DialTimeout,
		ReadTimeout:  cfg.Redis.ReadTimeout,
		WriteTimeout: cfg.Redis.WriteTimeout,
	}, logger)
	if err != nil {
		logger.Fatal("Redis连接失败", zap.Error(err))
	}
	defer redisClient.Close()

	// 设置Gin模式
	gin.SetMode(cfg.Server.Mode)

	// 创建路由
	router := gin.New()

	// 全局中间件
	router.Use(gin.Recovery())
	router.Use(middleware.LoggerMiddleware(logger))
	router.Use(middleware.CorsMiddleware())

	// 限流中间件
	rateLimiter := middleware.NewRateLimiter(redisClient.client)
	router.Use(rateLimiter.Middleware())

	// 健康检查
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status": "healthy",
			"time":   time.Now().Format(time.RFC3339),
		})
	})

	// API路由
	v1 := router.Group("/api/v1")
	{
		userHandler := handlers.NewUserHandler(nil, logger)

		users := v1.Group("/users")
		{
			users.POST("", userHandler.CreateUser)
			users.GET("/:id", userHandler.GetUser)
			users.DELETE("/:id", userHandler.DeleteUser)
		}
	}

	// 启动HTTP服务器
	srv := &http.Server{
		Addr:         fmt.Sprintf(":%d", cfg.Server.Port),
		Handler:      router,
		ReadTimeout:  cfg.Server.ReadTimeout,
		WriteTimeout: cfg.Server.WriteTimeout,
	}

	// 优雅关闭
	go func() {
		logger.Info("启动HTTP服务器", zap.Int("port", cfg.Server.Port))
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Fatal("HTTP服务器启动失败", zap.Error(err))
		}
	}()

	quit := make(chan(os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	logger.Info("开始关闭服务器...")

	ctx, cancel := context.WithTimeout(context.Background(), cfg.Server.ShutdownTimeout)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		logger.Error("服务器关闭失败", zap.Error(err))
	}

	logger.Info("服务器已退出")
}
Code collapsed

性能优化建议

  1. 连接池配置:

    • 根据并发量调整PoolSize
    • 保持足够的MinIdleConns
    • 合理设置超时时间
  2. 缓存策略:

    • 使用合适的数据结构(String/Hash/List)
    • 设置合理的TTL
    • 实现缓存穿透保护
  3. 并发控制:

    • 使用goroutine池
    • 实现请求合并
    • 使用context管理生命周期

通过本教程,你已掌握使用Go和Redis构建高性能API的核心技术。这个架构可支撑高并发、低延迟的生产环境需求。

#

文章标签

Go
Golang
Redis
高性能API
后端开发
缓存

觉得这篇文章有帮助?

立即体验康心伴,开始您的健康管理之旅