建设网站主题wordpress 插件 漏洞
2026/4/6 2:37:05 网站建设 项目流程
建设网站主题,wordpress 插件 漏洞,微信网页版下载,网页微信版怎设置字体大小面试必问三件套#xff1a;缓存穿透、缓存雪崩、缓存击穿。但实际生产中踩过坑才知道#xff0c;这三个问题不只是面试题#xff0c;是真的会让服务挂掉的。先搞清楚概念问题原因后果缓存穿透查询不存在的数据请求全打到数据库缓存雪崩大量缓存同时失效瞬间压垮数据库缓存击…面试必问三件套缓存穿透、缓存雪崩、缓存击穿。但实际生产中踩过坑才知道这三个问题不只是面试题是真的会让服务挂掉的。先搞清楚概念问题原因后果缓存穿透查询不存在的数据请求全打到数据库缓存雪崩大量缓存同时失效瞬间压垮数据库缓存击穿热点key突然过期并发请求打穿数据库一张图理解正常情况 请求 → Redis命中 → 返回 缓存穿透 请求(不存在的key) → Redis未命中 → DB未命中 → 每次都查DB 缓存雪崩 大量请求 → Redis大面积失效 → 全部打到DB → DB崩溃 缓存击穿 大量并发请求(同一个热点key) → key刚好过期 → 全部打到DB缓存穿透什么是穿透查询一个根本不存在的数据。缓存里没有数据库里也没有。请求: GET /user/999999999 Redis: 没有这个key MySQL: SELECT * FROM users WHERE id 999999999 → 空 返回: null 下次请求: 还是走MySQL恶意攻击者可以用大量不存在的ID疯狂请求每个请求都打到数据库。解决方案一缓存空值func GetUser(ctx context.Context, userID int64) (*User, error) { key : fmt.Sprintf(user:%d, userID) // 1. 查缓存 val, err : rdb.Get(ctx, key).Result() if err nil { if val { return nil, nil // 空值说明DB也没有 } var user User json.Unmarshal([]byte(val), user) return user, nil } // 2. 查数据库 user, err : db.GetUser(userID) if err ! nil { return nil, err } // 3. 写缓存 if user nil { // 缓存空值设置较短过期时间 rdb.Set(ctx, key, , 5*time.Minute) return nil, nil } data, _ : json.Marshal(user) rdb.Set(ctx, key, data, 30*time.Minute) return user, nil }缺点如果攻击者用随机key空值缓存会占用大量内存。解决方案二布隆过滤器布隆过滤器可以判断一个元素一定不存在或可能存在。import github.com/bits-and-blooms/bloom/v3 // 初始化布隆过滤器存放所有存在的用户ID var userBloom *bloom.BloomFilter func InitBloom() { userBloom bloom.NewWithEstimates(10000000, 0.01) // 1000万数据1%误判率 // 加载所有用户ID userIDs, _ : db.GetAllUserIDs() for _, id : range userIDs { userBloom.AddString(fmt.Sprintf(%d, id)) } } func GetUser(ctx context.Context, userID int64) (*User, error) { // 先过布隆过滤器 if !userBloom.TestString(fmt.Sprintf(%d, userID)) { return nil, nil // 一定不存在 } // 可能存在走正常逻辑 // ... }Redis也有布隆过滤器模块# 安装RedisBloom模块后 BF.ADD users 123 BF.EXISTS users 123 # 返回1 BF.EXISTS users 999 # 返回0// Go代码 func CheckUserExists(ctx context.Context, userID int64) bool { exists, _ : rdb.Do(ctx, BF.EXISTS, users, userID).Bool() return exists }解决方案三参数校验最简单但最有效func GetUser(ctx context.Context, userID int64) (*User, error) { // 参数校验 if userID 0 || userID 10000000000 { return nil, errors.New(invalid user id) } // 正常逻辑... }缓存雪崩什么是雪崩大量缓存在同一时间失效请求全部打到数据库。常见原因缓存设置了相同的过期时间Redis服务宕机解决方案一过期时间加随机值func SetUserCache(ctx context.Context, userID int64, user *User) error { key : fmt.Sprintf(user:%d, userID) data, _ : json.Marshal(user) // 基础过期时间 随机时间 baseExpire : 30 * time.Minute randomExpire : time.Duration(rand.Intn(300)) * time.Second // 0~5分钟随机 expire : baseExpire randomExpire return rdb.Set(ctx, key, data, expire).Err() }这样缓存过期时间分散开不会同时失效。解决方案二多级缓存请求 → 本地缓存(L1) → Redis(L2) → 数据库import github.com/patrickmn/go-cache var localCache cache.New(5*time.Minute, 10*time.Minute) func GetUser(ctx context.Context, userID int64) (*User, error) { key : fmt.Sprintf(user:%d, userID) // 1. 查本地缓存 if val, found : localCache.Get(key); found { return val.(*User), nil } // 2. 查Redis val, err : rdb.Get(ctx, key).Result() if err nil { var user User json.Unmarshal([]byte(val), user) localCache.Set(key, user, cache.DefaultExpiration) // 写入本地缓存 return user, nil } // 3. 查数据库 user, err : db.GetUser(userID) if err ! nil { return nil, err } // 4. 写缓存 data, _ : json.Marshal(user) rdb.Set(ctx, key, data, 30*time.Minute) localCache.Set(key, user, cache.DefaultExpiration) return user, nil }即使Redis挂了本地缓存还能顶一会。解决方案三Redis高可用Redis Sentinel哨兵模式自动故障转移Redis Cluster集群模式数据分片高可用// Sentinel模式连接 rdb : redis.NewFailoverClient(redis.FailoverOptions{ MasterName: mymaster, SentinelAddrs: []string{sentinel1:26379, sentinel2:26379, sentinel3:26379}, })解决方案四熔断降级Redis不可用时走降级逻辑import github.com/sony/gobreaker var cb *gobreaker.CircuitBreaker func init() { cb gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: redis, MaxRequests: 3, // 半开状态允许的请求数 Interval: 10 * time.Second, Timeout: 30 * time.Second, // 熔断后多久尝试恢复 ReadyToTrip: func(counts gobreaker.Counts) bool { failureRatio : float64(counts.TotalFailures) / float64(counts.Requests) return counts.Requests 3 failureRatio 0.6 }, }) } func GetUser(ctx context.Context, userID int64) (*User, error) { result, err : cb.Execute(func() (interface{}, error) { // 正常的Redis查询逻辑 return getFromRedis(ctx, userID) }) if err ! nil { // 熔断了走降级逻辑 return getFromDB(ctx, userID) } return result.(*User), nil }缓存击穿什么是击穿某个热点key突然过期大量并发请求同时打到数据库。和雪崩的区别雪崩是大面积失效击穿是单个热点key失效。热点key: hot_product:123 过期瞬间: 请求1 → Redis Miss → 查DB 请求2 → Redis Miss → 查DB 请求3 → Redis Miss → 查DB ... 1000个请求同时查DB解决方案一互斥锁只让一个请求去查数据库其他请求等待。func GetHotProduct(ctx context.Context, productID int64) (*Product, error) { key : fmt.Sprintf(product:%d, productID) lockKey : fmt.Sprintf(lock:product:%d, productID) // 1. 查缓存 val, err : rdb.Get(ctx, key).Result() if err nil { var product Product json.Unmarshal([]byte(val), product) return product, nil } // 2. 获取分布式锁 locked, err : rdb.SetNX(ctx, lockKey, 1, 10*time.Second).Result() if err ! nil { return nil, err } if !locked { // 没拿到锁等待后重试 time.Sleep(50 * time.Millisecond) return GetHotProduct(ctx, productID) // 递归重试 } defer rdb.Del(ctx, lockKey) // 释放锁 // 3. 双重检查可能别人已经写入缓存了 val, err rdb.Get(ctx, key).Result() if err nil { var product Product json.Unmarshal([]byte(val), product) return product, nil } // 4. 查数据库 product, err : db.GetProduct(productID) if err ! nil { return nil, err } // 5. 写缓存 data, _ : json.Marshal(product) rdb.Set(ctx, key, data, 30*time.Minute) return product, nil }解决方案二逻辑过期不设置真正的过期时间而是在value里存逻辑过期时间。type CacheValue struct { Data json.RawMessage json:data ExpireTime int64 json:expire_time // 逻辑过期时间戳 } func GetHotProduct(ctx context.Context, productID int64) (*Product, error) { key : fmt.Sprintf(product:%d, productID) val, err : rdb.Get(ctx, key).Result() if err ! nil { // 缓存不存在需要预热 return nil, errors.New(cache not found, need warm up) } var cv CacheValue json.Unmarshal([]byte(val), cv) var product Product json.Unmarshal(cv.Data, product) // 检查是否逻辑过期 if time.Now().Unix() cv.ExpireTime { // 过期了异步刷新 go refreshCache(ctx, productID) } // 返回旧数据不阻塞 return product, nil } func refreshCache(ctx context.Context, productID int64) { key : fmt.Sprintf(product:%d, productID) lockKey : fmt.Sprintf(refresh:product:%d, productID) // 获取刷新锁 locked, _ : rdb.SetNX(ctx, lockKey, 1, 30*time.Second).Result() if !locked { return // 已经有人在刷新了 } defer rdb.Del(ctx, lockKey) // 查数据库 product, _ : db.GetProduct(productID) // 写缓存 cv : CacheValue{ ExpireTime: time.Now().Add(30 * time.Minute).Unix(), } cv.Data, _ json.Marshal(product) data, _ : json.Marshal(cv) rdb.Set(ctx, key, data, 0) // 不设置过期时间 }特点用户永远不会等待总是返回数据可能是旧的。解决方案三热点数据永不过期对于真正的热点数据不设置过期时间通过后台任务定时更新。// 后台任务定时刷新热点数据 func RefreshHotData() { ticker : time.NewTicker(5 * time.Minute) for range ticker.C { hotProductIDs : getHotProductIDs() for _, id : range hotProductIDs { product, _ : db.GetProduct(id) key : fmt.Sprintf(product:%d, id) data, _ : json.Marshal(product) rdb.Set(context.Background(), key, data, 0) } } }实战热点Key问题除了击穿热点Key还有另一个问题单个Redis节点压力过大。发现热点Key# Redis 4.0 redis-cli --hotkeys # 或者用monitor命令生产慎用性能影响大 redis-cli monitor | head -n 10000 | awk {print $4} | sort | uniq -c | sort -rn | head -20解决方案本地缓存多副本// 热点key多副本分散请求 func GetHotProduct(ctx context.Context, productID int64) (*Product, error) { // 先查本地缓存 localKey : fmt.Sprintf(product:%d, productID) if val, found : localCache.Get(localKey); found { return val.(*Product), nil } // 随机选一个副本查询 replica : rand.Intn(3) key : fmt.Sprintf(product:%d:r%d, productID, replica) val, err : rdb.Get(ctx, key).Result() if err nil { var product Product json.Unmarshal([]byte(val), product) localCache.Set(localKey, product, 30*time.Second) return product, nil } // 查数据库... } // 写入时写多个副本 func SetHotProduct(ctx context.Context, productID int64, product *Product) error { data, _ : json.Marshal(product) pipe : rdb.Pipeline() for i : 0; i 3; i { key : fmt.Sprintf(product:%d:r%d, productID, i) pipe.Set(ctx, key, data, 30*time.Minute) } _, err : pipe.Exec(ctx) return err }总结问题原因解决方案穿透查不存在的数据缓存空值、布隆过滤器、参数校验雪崩大量缓存同时失效过期时间随机化、多级缓存、Redis高可用、熔断降级击穿热点key过期互斥锁、逻辑过期、永不过期后台刷新实际生产中这三个问题往往要组合解决所有缓存都加随机过期时间防雪崩查询前做参数校验防穿透缓存空值防穿透热点数据用互斥锁或逻辑过期防击穿Redis做好高可用防雪崩加上熔断降级兜底

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询