使用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
性能优化建议
-
连接池配置:
- 根据并发量调整PoolSize
- 保持足够的MinIdleConns
- 合理设置超时时间
-
缓存策略:
- 使用合适的数据结构(String/Hash/List)
- 设置合理的TTL
- 实现缓存穿透保护
-
并发控制:
- 使用goroutine池
- 实现请求合并
- 使用context管理生命周期
通过本教程,你已掌握使用Go和Redis构建高性能API的核心技术。这个架构可支撑高并发、低延迟的生产环境需求。