
前言:
"
过早优化是万恶之源,但过晚优化可能让你失去用户"---这是一篇帮助 你我
更好的做牛马,做更好的牛马的文档---。。。才第二章
📋 目录
- [🎯 文档说明](#🎯 文档说明)
- [📊 性能优化全景图](#📊 性能优化全景图)
-
💾 [第一章:数据库性能优化\](#第一章数据库性能优化)- `点击跳转相应文档`](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 倍,还能节省大量服务器成本。
本手册的特色
- ✅ 实战导向:每个优化点都配有真实代码示例
- ✅ 场景明确:清晰说明每种优化的适用场景
- ✅ 对比鲜明:用 ❌ 和 ✅ 直观展示好坏实践
- ✅ 深入浅出:用生动的比喻解释复杂概念
- ✅ 可操作性强:提供完整的代码和配置示例
如何使用本手册
- 快速诊断:遇到性能问题时,查找对应章节
- 系统学习:按章节顺序学习性能优化知识体系
- 代码审查:用 Checklist 检查现有项目
- 方案设计:参考架构章节设计高性能系统
性能优化的黄金法则
💡 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
}