Golang后端性能优化手册(第二章:缓存策略与优化)

前言:

"过早优化是万恶之源,但过晚优化可能让你失去用户"

---这是一篇帮助 你我 更好的做牛马,做更好的牛马 的文档

---。。。才第二章

📋 目录

  • [🎯 文档说明](#🎯 文档说明)
  • [📊 性能优化全景图](#📊 性能优化全景图)
  • 💾 [第一章:数据库性能优化\](#第一章数据库性能优化)- `点击跳转相应文档`](https://blog.csdn.net/bojinyuan00/article/details/156270988) * [1.1 SQL 执行分析与优化](#1.1 SQL 执行分析与优化) * [1.2 索引优化的艺术](#1.2 索引优化的艺术) * [1.3 查询优化技巧](#1.3 查询优化技巧) * [1.4 连接池优化](#1.4 连接池优化) * [1.5 读写分离与分库分表](#1.5 读写分离与分库分表)

    • [2.1 缓存设计原则](#2.1 缓存设计原则)
    • [2.2 多级缓存架构](#2.2 多级缓存架构)
    • [2.3 缓存三大问题及解决方案](#2.3 缓存三大问题及解决方案)
    • [2.4 缓存更新策略](#2.4 缓存更新策略)
    • [2.5 Redis 性能优化](#2.5 Redis 性能优化)
  • 🎨 第三章:[代码层面性能优化\](#第三章代码层面性能优化) `点击跳转相应文档`](https://blog.csdn.net/bojinyuan00/article/details/156294990?spm=1001.2014.3001.5502) * [3.1 内存管理与优化](#3.1 内存管理与优化) * [3.2 并发编程最佳实践](#3.2 并发编程最佳实践) * [3.3 字符串处理优化](#3.3 字符串处理优化) * [3.4 数据结构选择](#3.4 数据结构选择) * [3.5 对象复用与内存池](#3.5 对象复用与内存池)

    • [4.1 异步编程模式](#4.1 异步编程模式)
    • [4.2 消息队列选型](#4.2 消息队列选型)
    • [4.3 任务队列设计](#4.3 任务队列设计)
    • [4.4 异步回调机制](#4.4 异步回调机制)
  • [🌐 第五章:网络 I/O 优化](#🌐 第五章:网络 I/O 优化) 点击跳转相应文档
    • [5.1 HTTP 性能优化](#5.1 HTTP 性能优化)
    • [5.2 gRPC 高性能实践](#5.2 gRPC 高性能实践)
    • [5.3 WebSocket 优化](#5.3 WebSocket 优化)
    • [5.4 连接复用与池化](#5.4 连接复用与池化)
  • [📈 第六章:监控、分析与调优](#📈 第六章:监控、分析与调优) 点击跳转相应文档
    • [6.1 性能监控体系](#6.1 性能监控体系)
    • [6.2 pprof 深度使用](#6.2 pprof 深度使用)
    • [6.3 链路追踪](#6.3 链路追踪)
    • [6.4 日志优化](#6.4 日志优化)
  • [🏗️ 第七章:架构层面优化](#🏗️ 第七章:架构层面优化) 点击跳转相应文档
    • [7.1 服务治理](#7.1 服务治理)
    • [7.2 限流与熔断](#7.2 限流与熔断)
    • [7.3 负载均衡策略](#7.3 负载均衡策略)
    • [7.4 CDN 与边缘计算](#7.4 CDN 与边缘计算)
  • [💡 第八章:高级优化技巧](#💡 第八章:高级优化技巧) 点击跳转相应文档
    • [8.1 CPU 缓存友好的代码](#8.1 CPU 缓存友好的代码)
    • [8.2 减少 GC 压力](#8.2 减少 GC 压力)
    • [8.3 编译优化](#8.3 编译优化)
    • [8.4 性能测试与压测](#8.4 性能测试与压测)
  • [📝 第九章:实战案例分析](#📝 第九章:实战案例分析) 点击跳转相应文档
  • [✅ 第十章:性能优化 Checklist](#✅ 第十章:性能优化 Checklist) 点击跳转相应文档

🎯 文档说明

为什么需要这份手册?

在微服务盛行的今天,后端接口性能直接影响用户体验和系统稳定性。一个响应时间从 3 秒优化到 300 毫秒的接口,不仅能让用户体验提升 10 倍,还能节省大量服务器成本。

本手册的特色

  • 实战导向:每个优化点都配有真实代码示例
  • 场景明确:清晰说明每种优化的适用场景
  • 对比鲜明:用 ❌ 和 ✅ 直观展示好坏实践
  • 深入浅出:用生动的比喻解释复杂概念
  • 可操作性强:提供完整的代码和配置示例

如何使用本手册

  1. 快速诊断:遇到性能问题时,查找对应章节
  2. 系统学习:按章节顺序学习性能优化知识体系
  3. 代码审查:用 Checklist 检查现有项目
  4. 方案设计:参考架构章节设计高性能系统

性能优化的黄金法则

💡 80/20 原则:80% 的性能问题通常来自 20% 的代码

💡 测量先行:没有测量就没有优化,先用数据说话

💡 渐进式优化:先优化瓶颈,再优化细节


📊 性能优化全景图

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        性能优化层次模型                          │
├─────────────────────────────────────────────────────────────────┤
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  架构层 🏗️  │ 服务拆分 • 负载均衡 • 限流熔断 • CDN      │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  存储层 💾  │ 数据库优化 • 缓存策略 • 读写分离           │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  应用层 ⚡  │ 代码优化 • 并发控制 • 异步处理            │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  网络层 🌐  │ 协议优化 • 连接池 • 序列化优化             │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  监控层 📈  │ 性能监控 • 链路追踪 • 日志分析            │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

⚡ 第二章:缓存策略与优化

"缓存是性能优化的银弹,但也是一致性问题的根源"

2.1 缓存设计原则

📌 什么数据适合缓存?
go 复制代码
// ✅ 适合缓存的数据:
// 1. 读多写少的数据(读写比 > 10:1)
// 2. 计算结果耗时的数据
// 3. 热点数据(20%的数据被访问80%的次数)
// 4. 不经常变化的配置数据

// 示例1:缓存配置数据
func GetSystemConfig(cache *redis.Client, db *gorm.DB, key string) (string, error) {
    cacheKey := fmt.Sprintf("config:%s", key)
    
    // 从缓存获取
    if val, err := cache.Get(context.Background(), cacheKey).Result(); err == nil {
        return val, nil
    }
    
    // 缓存未命中,从数据库查询
    var config SystemConfig
    if err := db.Where("key = ?", key).First(&config).Error; err != nil {
        return "", err
    }
    
    // 写入缓存,24小时过期
    cache.Set(context.Background(), cacheKey, config.Value, 24*time.Hour)
    
    return config.Value, nil
}

// 示例2:缓存热点商品信息
func GetHotProduct(cache *redis.Client, db *gorm.DB, productID int64) (*Product, error) {
    cacheKey := fmt.Sprintf("product:%d", productID)
    
    // 从缓存获取
    var product Product
    if val, err := cache.Get(context.Background(), cacheKey).Result(); err == nil {
        json.Unmarshal([]byte(val), &product)
        return &product, nil
    }
    
    // 从数据库查询
    if err := db.First(&product, productID).Error; err != nil {
        return nil, err
    }
    
    // 序列化并缓存
    data, _ := json.Marshal(product)
    cache.Set(context.Background(), cacheKey, data, time.Hour)
    
    return &product, nil
}

// 示例3:缓存复杂计算结果
func GetUserStatistics(cache *redis.Client, db *gorm.DB, userID int64) (*UserStats, error) {
    cacheKey := fmt.Sprintf("user:stats:%d", userID)
    
    // 从缓存获取
    var stats UserStats
    if val, err := cache.Get(context.Background(), cacheKey).Result(); err == nil {
        json.Unmarshal([]byte(val), &stats)
        return &stats, nil
    }
    
    // 复杂计算:统计用户的订单数、总消费、平均客单价等
    stats = calculateUserStats(db, userID) // 耗时操作
    
    // 缓存30分钟
    data, _ := json.Marshal(stats)
    cache.Set(context.Background(), cacheKey, data, 30*time.Minute)
    
    return &stats, nil
}
📌 缓存的 Key 设计规范
go 复制代码
// ✅ 良好的 Key 命名规范:
// 格式:业务模块:对象类型:对象ID:可选属性
// 示例:
// - user:info:12345                  // 用户基本信息
// - product:detail:67890             // 商品详情
// - order:list:user:12345            // 用户订单列表
// - category:tree:all                // 分类树
// - ranking:hot:product:daily        // 每日热门商品排行

// ❌ 不好的 Key 命名:
// - userInfo12345                    // 没有分隔符,难以管理
// - u:12345                          // 过于简短,不知道是什么
// - product_detail_for_user_67890    // 过于冗长

// Key 设计的最佳实践:
const (
    KeyPrefixUser    = "user"
    KeyPrefixProduct = "product"
    KeyPrefixOrder   = "order"
)

func MakeUserCacheKey(userID int64) string {
    return fmt.Sprintf("%s:info:%d", KeyPrefixUser, userID)
}

func MakeProductCacheKey(productID int64) string {
    return fmt.Sprintf("%s:detail:%d", KeyPrefixProduct, productID)
}

func MakeOrderListCacheKey(userID int64, page int) string {
    return fmt.Sprintf("%s:list:user:%d:page:%d", KeyPrefixOrder, userID, page)
}

2.2 多级缓存架构

go 复制代码
// 📌 三级缓存架构设计

// Level 1: 本地缓存(进程内)
// - 适合:极热数据、配置数据
// - 优点:访问速度最快(纳秒级)
// - 缺点:容量有限、数据不共享

// Level 2: 分布式缓存(Redis)
// - 适合:热点数据、共享数据
// - 优点:容量大、可共享
// - 缺点:网络延迟(毫秒级)

// Level 3: 数据库
// - 适合:持久化数据
// - 优点:数据可靠
// - 缺点:访问最慢

import (
    "github.com/bluele/gcache"
    "github.com/go-redis/redis/v8"
)

type CacheManager struct {
    localCache gcache.Cache      // 本地缓存
    redis      *redis.Client     // Redis
    db         *gorm.DB          // 数据库
}

func NewCacheManager(redis *redis.Client, db *gorm.DB) *CacheManager {
    // 创建本地缓存:LRU策略,最多10000个元素
    localCache := gcache.New(10000).
        LRU().
        Build()
    
    return &CacheManager{
        localCache: localCache,
        redis:      redis,
        db:         db,
    }
}

// 三级缓存查询
func (cm *CacheManager) GetUser(userID int64) (*User, error) {
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // Level 1: 本地缓存
    if val, err := cm.localCache.Get(cacheKey); err == nil {
        return val.(*User), nil
    }
    
    // Level 2: Redis
    var user User
    if val, err := cm.redis.Get(context.Background(), cacheKey).Result(); err == nil {
        json.Unmarshal([]byte(val), &user)
        
        // 写入本地缓存
        cm.localCache.SetWithExpire(cacheKey, &user, time.Minute)
        
        return &user, nil
    }
    
    // Level 3: 数据库
    if err := cm.db.First(&user, userID).Error; err != nil {
        return nil, err
    }
    
    // 写入 Redis
    data, _ := json.Marshal(user)
    cm.redis.Set(context.Background(), cacheKey, data, time.Hour)
    
    // 写入本地缓存
    cm.localCache.SetWithExpire(cacheKey, &user, time.Minute)
    
    return &user, nil
}

// 性能对比:
// Level 1(本地缓存):0.001 ms
// Level 2(Redis):    1 ms
// Level 3(数据库):   10 ms
// 三级缓存可以将99%的请求控制在毫秒级!

2.3 缓存三大问题及解决方案

📌 问题1:缓存穿透(查询不存在的数据)

现象:恶意请求查询不存在的数据,导致每次都打到数据库

go 复制代码
// ❌ 没有防护:容易被攻击
func GetUserBad(cache *redis.Client, db *gorm.DB, userID int64) (*User, error) {
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // 缓存中没有
    if val, err := cache.Get(context.Background(), cacheKey).Result(); err == nil {
        var user User
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 数据库中也没有(但每次都会查询数据库!)
    var user User
    if err := db.First(&user, userID).Error; err != nil {
        return nil, err
    }
    
    return &user, nil
}

// ✅ 方案1:缓存空值
func GetUserWithNullCache(cache *redis.Client, db *gorm.DB, userID int64) (*User, error) {
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // 检查缓存
    val, err := cache.Get(context.Background(), cacheKey).Result()
    if err == nil {
        if val == "NULL" {
            return nil, errors.New("user not found") // 缓存的空值
        }
        var user User
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 查询数据库
    var user User
    if err := db.First(&user, userID).Error; err != nil {
        // 缓存空值,防止穿透(设置较短的过期时间)
        cache.Set(context.Background(), cacheKey, "NULL", 5*time.Minute)
        return nil, err
    }
    
    // 缓存正常数据
    data, _ := json.Marshal(user)
    cache.Set(context.Background(), cacheKey, data, time.Hour)
    
    return &user, nil
}

// ✅ 方案2:布隆过滤器(推荐)
import "github.com/bits-and-blooms/bloom/v3"

type UserService struct {
    bloomFilter *bloom.BloomFilter
    cache       *redis.Client
    db          *gorm.DB
}

func NewUserService(cache *redis.Client, db *gorm.DB) *UserService {
    // 创建布隆过滤器:预计100万个元素,误判率0.01%
    bf := bloom.NewWithEstimates(1000000, 0.0001)
    
    // 初始化:将所有存在的用户ID加入布隆过滤器
    var userIDs []int64
    db.Model(&User{}).Pluck("id", &userIDs)
    for _, id := range userIDs {
        bf.Add([]byte(fmt.Sprintf("%d", id)))
    }
    
    return &UserService{
        bloomFilter: bf,
        cache:       cache,
        db:          db,
    }
}

func (s *UserService) GetUser(userID int64) (*User, error) {
    // 先查布隆过滤器
    if !s.bloomFilter.Test([]byte(fmt.Sprintf("%d", userID))) {
        return nil, errors.New("user not found") // 一定不存在,直接返回
    }
    
    // 可能存在,继续查缓存和数据库
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // 查缓存...
    // 查数据库...
    
    return nil, nil
}

// 布隆过滤器的优势:
// - 内存占用极小(100万数据只需约1.2MB)
// - 查询速度极快(O(1))
// - 一定不存在的数据可以100%拦截
📌 问题2:缓存击穿(热点数据过期)

现象:某个热点数据过期的瞬间,大量请求同时打到数据库

go 复制代码
// ❌ 没有防护:热点数据过期瞬间压垮数据库
func GetHotDataBad(cache *redis.Client, db *gorm.DB, key string) (string, error) {
    // 缓存过期瞬间,1000个并发请求同时打到数据库!
    if val, err := cache.Get(context.Background(), key).Result(); err == nil {
        return val, nil
    }
    
    // 查询数据库(1000个请求同时执行)
    val := queryFromDB(db, key)
    cache.Set(context.Background(), key, val, time.Hour)
    return val, nil
}

// ✅ 方案1:互斥锁(singleflight)
import "golang.org/x/sync/singleflight"

type CacheService struct {
    cache *redis.Client
    db    *gorm.DB
    sf    singleflight.Group // 单飞模式
}

func (s *CacheService) GetHotData(key string) (string, error) {
    cacheKey := fmt.Sprintf("hot:%s", key)
    
    // 查缓存
    if val, err := s.cache.Get(context.Background(), cacheKey).Result(); err == nil {
        return val, nil
    }
    
    // 使用 singleflight 保证同一时刻只有一个请求查询数据库
    val, err, _ := s.sf.Do(cacheKey, func() (interface{}, error) {
        // 双重检查
        if val, err := s.cache.Get(context.Background(), cacheKey).Result(); err == nil {
            return val, nil
        }
        
        // 查询数据库
        result := queryFromDB(s.db, key)
        
        // 写入缓存
        s.cache.Set(context.Background(), cacheKey, result, time.Hour)
        
        return result, nil
    })
    
    if err != nil {
        return "", err
    }
    
    return val.(string), nil
}

// singleflight 的效果:
// 1000个并发请求 -> 只有1个请求查询数据库
// 其他999个请求等待并共享结果

// ✅ 方案2:永不过期 + 异步更新
func (s *CacheService) GetHotDataWithAsyncUpdate(key string) (string, error) {
    cacheKey := fmt.Sprintf("hot:%s", key)
    
    // 查缓存
    val, err := s.cache.Get(context.Background(), cacheKey).Result()
    if err == nil {
        // 检查是否快过期了(例如还有5分钟过期)
        ttl, _ := s.cache.TTL(context.Background(), cacheKey).Result()
        if ttl < 5*time.Minute {
            // 异步更新缓存
            go s.refreshCache(cacheKey, key)
        }
        return val, nil
    }
    
    // 缓存不存在,同步查询
    result := queryFromDB(s.db, key)
    s.cache.Set(context.Background(), cacheKey, result, time.Hour)
    return result, nil
}

func (s *CacheService) refreshCache(cacheKey, key string) {
    result := queryFromDB(s.db, key)
    s.cache.Set(context.Background(), cacheKey, result, time.Hour)
}
📌 问题3:缓存雪崩(大量缓存同时过期)

现象:大量缓存在同一时刻过期,导致数据库压力突增

go 复制代码
// ❌ 所有缓存同时过期
func SetCacheBad(cache *redis.Client, keys []string, values []string) {
    for i, key := range keys {
        // 所有缓存都是1小时过期,可能同时失效
        cache.Set(context.Background(), key, values[i], time.Hour)
    }
}

// ✅ 方案1:设置随机过期时间
func SetCacheWithRandomExpire(cache *redis.Client, key, value string) {
    // 基础过期时间1小时 + 随机0-10分钟
    baseExpire := time.Hour
    randomExpire := time.Duration(rand.Intn(600)) * time.Second
    expireTime := baseExpire + randomExpire
    
    cache.Set(context.Background(), key, value, expireTime)
}

// ✅ 方案2:使用多级缓存
// 即使 Redis 雪崩,本地缓存还能抗一部分流量

// ✅ 方案3:缓存预热 + 定时刷新
func WarmUpCache(cache *redis.Client, db *gorm.DB) {
    // 系统启动时预热热点数据
    hotProducts := getHotProducts(db)
    for _, product := range hotProducts {
        data, _ := json.Marshal(product)
        cache.Set(context.Background(), 
            fmt.Sprintf("product:%d", product.ID), 
            data, 
            time.Hour)
    }
}

// 定时刷新缓存
func RefreshCachePeriodically(cache *redis.Client, db *gorm.DB) {
    ticker := time.NewTicker(30 * time.Minute)
    defer ticker.Stop()
    
    for range ticker.C {
        WarmUpCache(cache, db)
    }
}

// ✅ 方案4:Redis 集群 + 高可用
// - 主从复制:一主多从
// - 哨兵模式:自动故障转移
// - 集群模式:水平扩展

2.4 缓存更新策略

go 复制代码
// 📌 策略1:Cache Aside(旁路缓存)- 最常用

// 读操作:
func GetUserCacheAside(cache *redis.Client, db *gorm.DB, userID int64) (*User, error) {
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // 1. 先查缓存
    var user User
    if val, err := cache.Get(context.Background(), cacheKey).Result(); err == nil {
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 2. 缓存没有,查数据库
    if err := db.First(&user, userID).Error; err != nil {
        return nil, err
    }
    
    // 3. 写入缓存
    data, _ := json.Marshal(user)
    cache.Set(context.Background(), cacheKey, data, time.Hour)
    
    return &user, nil
}

// 写操作:
func UpdateUserCacheAside(cache *redis.Client, db *gorm.DB, user *User) error {
    cacheKey := fmt.Sprintf("user:%d", user.ID)
    
    // 1. 先更新数据库
    if err := db.Save(user).Error; err != nil {
        return err
    }
    
    // 2. 删除缓存(而不是更新缓存)
    cache.Del(context.Background(), cacheKey)
    
    // 为什么删除而不是更新?
    // - 避免并发更新导致数据不一致
    // - 如果数据不是热点,更新缓存是浪费
    // - 下次读取时会自动加载最新数据
    
    return nil
}

// 📌 策略2:Read/Write Through(读写穿透)

// 应用程序只与缓存交互,缓存负责与数据库同步

type CacheThroughService struct {
    cache *redis.Client
    db    *gorm.DB
}

func (s *CacheThroughService) Get(userID int64) (*User, error) {
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // 查缓存
    var user User
    if val, err := s.cache.Get(context.Background(), cacheKey).Result(); err == nil {
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 缓存未命中,缓存服务自动加载数据
    if err := s.db.First(&user, userID).Error; err != nil {
        return nil, err
    }
    
    data, _ := json.Marshal(user)
    s.cache.Set(context.Background(), cacheKey, data, time.Hour)
    
    return &user, nil
}

func (s *CacheThroughService) Update(user *User) error {
    cacheKey := fmt.Sprintf("user:%d", user.ID)
    
    // 同时更新缓存和数据库
    if err := s.db.Save(user).Error; err != nil {
        return err
    }
    
    data, _ := json.Marshal(user)
    s.cache.Set(context.Background(), cacheKey, data, time.Hour)
    
    return nil
}

// 📌 策略3:Write Behind(写回)

// 先更新缓存,异步批量写入数据库

type WriteBehindService struct {
    cache      *redis.Client
    db         *gorm.DB
    writeQueue chan *User
}

func NewWriteBehindService(cache *redis.Client, db *gorm.DB) *WriteBehindService {
    s := &WriteBehindService{
        cache:      cache,
        db:         db,
        writeQueue: make(chan *User, 1000),
    }
    
    // 启动异步写入协程
    go s.flushToDB()
    
    return s
}

func (s *WriteBehindService) Update(user *User) error {
    cacheKey := fmt.Sprintf("user:%d", user.ID)
    
    // 立即更新缓存
    data, _ := json.Marshal(user)
    s.cache.Set(context.Background(), cacheKey, data, time.Hour)
    
    // 加入写入队列
    s.writeQueue <- user
    
    return nil
}

func (s *WriteBehindService) flushToDB() {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()
    
    batch := make([]*User, 0, 100)
    
    for {
        select {
        case user := <-s.writeQueue:
            batch = append(batch, user)
            if len(batch) >= 100 {
                s.batchWrite(batch)
                batch = batch[:0]
            }
        case <-ticker.C:
            if len(batch) > 0 {
                s.batchWrite(batch)
                batch = batch[:0]
            }
        }
    }
}

func (s *WriteBehindService) batchWrite(users []*User) {
    // 批量写入数据库
    s.db.Transaction(func(tx *gorm.DB) error {
        for _, user := range users {
            if err := tx.Save(user).Error; err != nil {
                return err
            }
        }
        return nil
    })
}

// Write Behind 的优点:
// - 写入性能极高
// - 可以批量操作,减少数据库压力
// 缺点:
// - 数据可能丢失(如果服务崩溃)
// - 数据一致性较弱
// 适用场景:
// - 允许一定延迟的场景(如浏览次数、点赞数)

2.5 Redis 性能优化

go 复制代码
// 📌 使用连接池

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

func NewRedisClient() *redis.Client {
    return redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
        
        // 连接池配置
        PoolSize:     100,              // 连接池大小
        MinIdleConns: 10,               // 最小空闲连接数
        MaxConnAge:   time.Hour,        // 连接最大存活时间
        PoolTimeout:  time.Minute,      // 获取连接超时时间
        IdleTimeout:  5 * time.Minute,  // 空闲连接超时时间
        
        // 超时配置
        DialTimeout:  5 * time.Second,  // 连接超时
        ReadTimeout:  3 * time.Second,  // 读超时
        WriteTimeout: 3 * time.Second,  // 写超时
    })
}

// 📌 使用 Pipeline 批量操作

// ❌ 逐个操作:慢
func SetMultiKeysBad(cache *redis.Client, data map[string]string) {
    for key, val := range data {
        cache.Set(context.Background(), key, val, time.Hour)
    }
}
// 1000个key需要1000次网络往返,耗时约 1000ms

// ✅ 使用 Pipeline:快
func SetMultiKeysGood(cache *redis.Client, data map[string]string) {
    pipe := cache.Pipeline()
    
    for key, val := range data {
        pipe.Set(context.Background(), key, val, time.Hour)
    }
    
    _, err := pipe.Exec(context.Background())
    if err != nil {
        // 处理错误
    }
}
// 1000个key只需1次网络往返,耗时约 10ms

// 性能提升 100 倍!

// 📌 选择合适的数据结构

// String:适合简单的键值对
cache.Set(ctx, "user:name:123", "张三", time.Hour)

// Hash:适合对象存储(减少key数量)
cache.HSet(ctx, "user:123", "name", "张三")
cache.HSet(ctx, "user:123", "age", 25)
cache.HSet(ctx, "user:123", "email", "test@example.com")

// List:适合消息队列、时间线
cache.LPush(ctx, "notifications:123", "新消息1")
cache.LPush(ctx, "notifications:123", "新消息2")

// Set:适合去重、交集/并集操作
cache.SAdd(ctx, "tags:article:123", "Go", "Redis", "性能优化")

// ZSet:适合排行榜、带权重的数据
cache.ZAdd(ctx, "ranking:hot", &redis.Z{Score: 100, Member: "article:1"})
cache.ZAdd(ctx, "ranking:hot", &redis.Z{Score: 95, Member: "article:2"})

// 📌 避免大 Key

// ❌ 一个 Hash 存储100万条数据
cache.HSet(ctx, "all_users", userID, userData) // 单个key过大

// ✅ 分片存储
func GetUserShardKey(userID int64) string {
    shardID := userID % 100 // 分成100个分片
    return fmt.Sprintf("users:shard:%d", shardID)
}

cache.HSet(ctx, GetUserShardKey(userID), fmt.Sprintf("%d", userID), userData)

// 📌 使用 Lua 脚本保证原子性

// 场景:扣减库存
const decrStockScript = `
local stock = redis.call('GET', KEYS[1])
if not stock then
    return -1
end
stock = tonumber(stock)
if stock >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return stock - tonumber(ARGV[1])
end
return -2
`

func DecrStock(cache *redis.Client, productID int64, count int) (int, error) {
    script := redis.NewScript(decrStockScript)
    result, err := script.Run(
        context.Background(),
        cache,
        []string{fmt.Sprintf("stock:%d", productID)},
        count,
    ).Int()
    
    if err != nil {
        return 0, err
    }
    
    if result == -1 {
        return 0, errors.New("商品不存在")
    }
    if result == -2 {
        return 0, errors.New("库存不足")
    }
    
    return result, nil
}

相关推荐
挖矿大亨2 小时前
c++中值传递时是如何触发拷贝构造函数的
开发语言·c++
掘金酱2 小时前
🏆2025 AI/Vibe Coding 对我的影响 | 年终技术征文
前端·人工智能·后端
郝亚军2 小时前
顺序栈C语言版本
c语言·开发语言·算法
成为大佬先秃头2 小时前
渐进式JavaScript框架:Vue
开发语言·javascript·vue.js
yugi9878382 小时前
基于MATLAB实现神经网络电能扰动信号特征识别
开发语言·神经网络·matlab
小CC吃豆子2 小时前
Redis 缓存雪崩
数据库
追光天使2 小时前
元组、列表、字符串、字典定义及切割
开发语言·python
狗头大军之江苏分军2 小时前
2026年了,前端到底算不算“夕阳行业”?
前端·javascript·后端
雪花desu2 小时前
深入 LangChain LCEL 的 10 个核心特性
数据库·人工智能·深度学习·langchain