go-zero(十四)实践:缓存一致性保证、缓存击穿、缓存穿透与缓存雪崩解决方案

go zero 实践:缓存一致性保证、缓存击穿、缓存穿透与缓存雪崩解决方案

缓存 作为一种重要的技术手段,可以有效提高系统的响应速度,降低对数据库的压力。但是缓存的使用伴随一些常见问题,如缓存一致性缓存击穿缓存穿透缓存雪崩。下面我们将结合 go zero 框架,深入剖析这些问题的概念以及对应的解决方案。

一、项目构建

本文项目都基于下面的文件构建,通过文章的增删改查,来演示缓存实践

1.SQL

CREATE TABLE `article` (`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',`title` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '标题' COLLATE 'utf8mb4_bin',`content` TEXT NOT NULL COMMENT '内容' COLLATE 'utf8_unicode_ci',`cover` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封面' COLLATE 'utf8mb4_bin',`description` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '描述' COLLATE 'utf8mb4_bin',`author_id` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '作者ID',`status` TINYINT NOT NULL DEFAULT '0' COMMENT '状态 0:待审核 1:审核不通过 2:可见 3:用户删除',`comment_num` INT NOT NULL DEFAULT '0' COMMENT '评论数',`like_num` INT NOT NULL DEFAULT '0' COMMENT '点赞数',`collect_num` INT NOT NULL DEFAULT '0' COMMENT '收藏数',`view_num` INT NOT NULL DEFAULT '0' COMMENT '浏览数',`share_num` INT NOT NULL DEFAULT '0' COMMENT '分享数',`tag_ids` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '标签ID' COLLATE 'utf8mb4_bin',`publish_time` TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '发布时间',`create_time` TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP) COMMENT '创建时间',`update_time` TIMESTAMP NOT NULL DEFAULT (CURRENT_TIMESTAMP) ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',PRIMARY KEY (`id`) USING BTREE,INDEX `ix_author_id` (`author_id`) USING BTREE,INDEX `ix_update_time` (`update_time`) USING BTREE
)
COMMENT='文章表'
COLLATE='utf8mb4_bin'
ENGINE=InnoDB
AUTO_INCREMENT=103
;

2.API文件

syntax = "v1"type (Token {AccessToken  string `json:"access_token"`AccessExpire int64  `json:"access_expire"`}VerificationRequest {Mobile string `json:"mobile"`}VerificationResponse  {}RegisterRequest {Name             string `json:"name"`Mobile           string `json:"mobile"`Password         string `json:"password"`VerificationCode string `json:"verification_code"`}RegisterResponse {UserId int64 `json:"user_id"`Token  Token `json:"token"`}LoginRequest {Mobile           string `json:"mobile"`Password         string `json:"password"`VerificationCode string `json:"verification_code"`}LoginResponse {UserId int64 `json:"userId"`Token  Token `json:"token"`}UserInfoResponse {UserId   int64  `json:"user_id"`Username string `json:"username"`Avatar   string `json:"avatar"`}
)@server (prefix: /v1
)
service user-api {@handler RegisterHandlerpost /register (RegisterRequest) returns (RegisterResponse)@handler VerificationHandlerpost /verification (VerificationRequest) returns (VerificationResponse)@handler LoginHandlerpost /login (LoginRequest) returns (LoginResponse)
}@server (prefix:    /v1/usersignature: truejwt:       Auth
)
service user-api {@handler UserInfoHandlerget /info returns (UserInfoResponse)
}

3.PROTO文件

syntax = "proto3";package userpb;
option go_package="./userpb";service User {rpc Register(RegisterRequest) returns (RegisterResponse);rpc FindById(FindByIdRequest) returns (FindByIdResponse);rpc FindByMobile(FindByMobileRequest) returns (FindByMobileResponse);rpc SendSms(SendSmsRequest) returns (SendSmsResponse);
}message RegisterRequest {string username = 1;string mobile = 2;string avatar = 3;string password = 4;
}message RegisterResponse {int64 userId = 1;
}message FindByIdRequest {int64 userId = 1;
}message FindByIdResponse {int64 userId = 1;string username = 2;string password =3;string mobile = 4;string avatar = 5;
}message FindByMobileRequest {string mobile = 1;
}message FindByMobileResponse {int64 userId = 1;string username = 2;string password =3;string mobile = 4;string avatar = 5;
}message SendSmsRequest {int64 userId = 1;string mobile = 2;
}message SendSmsResponse {string code =1;
}

二、缓存一致性

1.概念

缓存一致性 指缓存数据与数据库中的数据保持一致性。如果缓存数据过时或被修改后未及时更新,可能导致业务逻辑错误。

2.现象

  • 缓存中存储了过期数据,而数据库已经更新,导致查询结果不一致。
  • 多服务环境下,缓存与数据库之间的数据同步问题尤为显著。

3.解决方案

  1. 读操作

    • 先从缓存读取数据。
    • 如果缓存中存在,直接返回。
    • 如果缓存中不存在(缓存未命中),则从数据库查询,返回结果后将数据写入缓存。
  2. 写操作(更新、删除):

    • 先更新数据库中的数据。
    • 再删除或更新缓存中的数据,保证缓存中的数据是最新的。

4.代码演示

go-zero 除了提供 sqlx.SqlConn, 我们也提供了一个 sqlc.CachedConn 的封装,用于sql 数据库缓存的支持。

当我们使用goctl model -c生成model的代码,model的方法都带有缓存管理,所以我们不需要对单独的数据做缓存处理,我们使用Redis有序集合,来做文章列表的缓存。

发布文章
下面通过文章的发布,向有序集合写入缓存(即向文章列表缓存添加文章信息),来演示缓存的一致性:

func (l *PublishLogic) Publish(in *pb.PublishRequest) (*pb.PublishResponse, error) {// todo: add your logic here and delete this lineif in.UserId <= 0 {return nil, errors.New("用户ID不合法")}if len(in.Title) == 0 || len(in.Content) == 0 {return nil, errors.New("标题或者文章内容不能为空")}//文章数据插入数据库//调用Insert方法,自动写入行缓存result, err := l.svcCtx.ArticleModel.Insert(l.ctx, &model.Article{Title:       in.Title,Content:     in.Content,AuthorId:    uint64(in.UserId),Cover:       "",TagIds:      "",Status:      1, //  0 未发布 1 代表已发布 2 待审核  3 仅自己可见PublishTime: time.Now(),CreateTime:  time.Now(),UpdateTime:  time.Now(),})if err != nil {return nil, err}//获取插入后的文章 IDarticleId, err := result.LastInsertId()if err != nil {return nil, errors.New("返回ID失败")}//缓存键生成// 0为发布时间排序  1为点赞数排序  -默认按发布时间排序//"biz#articles#ID#SortType"publishTimeKey := fmt.Sprintf("biz#articles#%d#0", in.UserId)likeNumKey := fmt.Sprintf("biz#articles#%d#1", in.UserId)articleIdStr := strconv.FormatInt(articleId, 10)//缓存存在性检查与更新//如果对应的缓存键存在,使用 Redis 的 ZADD 命令将文章 ID 和分数添加到有序集合中。isExits, _ := l.svcCtx.Rds.ExistsCtx(l.ctx, publishTimeKey)if isExits {//Redis Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。_, err := l.svcCtx.Rds.ZaddCtx(l.ctx, publishTimeKey, time.Now().Unix(), articleIdStr)if err != nil {return nil, err}}isExits, _ = l.svcCtx.Rds.ExistsCtx(l.ctx, likeNumKey)if isExits {//Redis Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。_, err := l.svcCtx.Rds.ZaddCtx(l.ctx, likeNumKey, time.Now().Unix(), articleIdStr)if err != nil {return nil, err}}return &pb.PublishResponse{ArticleId: articleId}, nil
}

删除文章

func (l *ArticleDeleteLogic) ArticleDelete(in *pb.ArticleDeleteRequest) (*pb.ArticleDeleteResponse, error) {// todo: add your logic here and delete this lineif in.UserId <= 0 {return nil, errors.New("用户ID不合法")}if in.ArticleId <= 0 {return nil, errors.New("文章ID不合法")}//判断文章ID是否存在article, err := l.svcCtx.ArticleModel.FindOne(l.ctx, uint64(in.ArticleId))if err != nil {return nil, err}//检查是否是自己的文章if article.AuthorId != uint64(in.UserId) {return nil, errors.New("您没有权限删除该文章")}//删除采用软删除,修改文章状态为4 不可见状态,article.Status = 4err = l.svcCtx.ArticleModel.Update(l.ctx, article)if err != nil {return nil, err}//从有序集合中删除该条文章缓存publishTimeKey := fmt.Sprintf("biz#articles#%d#0", in.UserId)likeNumKey := fmt.Sprintf("biz#articles#%d#1", in.UserId)//Redis Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。//删除不需要检查是否存在,因为不存在也不会报错l.svcCtx.Rds.ZremCtx(l.ctx, publishTimeKey, in.ArticleId)l.svcCtx.Rds.ZremCtx(l.ctx, likeNumKey, in.ArticleId)return &pb.ArticleDeleteResponse{}, nil
}

三、缓存击穿

1.概念

缓存击穿 是指当热点数据的缓存过期时,大量并发请求同时查询该数据,导致数据库瞬间负载过高。

2.现象

  • 热点数据过期导致大量请求打到数据库,瞬时压力增大。
  • 容易导致数据库响应变慢甚至崩溃。

3.解决方案

  1. 设置热点数据的合理过期时间,每次查询缓存的时候使用Exists来判断key是否存在,如果存在就使用Expire给缓存续期,既然是热点数据通过不断地续期也就不会过期了
  2. 利用互斥锁:只允许一个请求更新缓存,其他请求等待。

4.代码演示

方法一:延迟缓存过期时间


// 缓存续期函数
func (l *ArticlesLogic) extendCacheExpiration(ctx context.Context, key string) error {exists, err := l.svcCtx.Rds.ExistsCtx(ctx, key)if err != nil || !exists {return err}return l.svcCtx.Rds.ExpireCtx(ctx, key, articlesExpire+rand.Intn(60))
}

方法二:加锁

在singleflight 包提供了重复函数调用抑制机制。github.com/golang/groupcache/singleflight

在svc引用singleflight:

type ServiceContext struct {SingleFlightGroup singleflight.Group
}

如果缓存没有命中,只允许一个请求更新缓存:

	/*......*/articlesT, _ := l.svcCtx.SingleFlightGroup.Do(fmt.Sprintf("ArticlesByUserId:%d:%d", in.UserId, in.SortType),func() (interface{}, error) {//最大查询200条//ArticlesByUserId 为自定义方法return l.svcCtx.ArticleModel.ArticlesByUserId(l.ctx, in.UserId, sortLikeNum, sortPublishTime, sortField, 200)})if articlesT != nil {//将查询结果转换为 []*model.Article 类型articles = articlesT.([]*model.Article)}/*......*/

四、缓存穿透

1.概念

缓存穿透 是指查询的数据在缓存和数据库中都不存在,导致每次查询都需要访问数据库。

2.现象

  • 恶意用户频繁查询不存在的数据,导致缓存被绕过,数据库压力过大。 恶意用户频繁请求 article🆔99999(数据库和缓存均不存在)
  • 这种情况容易造成数据库崩溃。

3.解决方案

  1. 缓存空值:当数据库中查询结果为空时,将空值缓存起来,避免重复查询数据库。
  2. 布隆过滤器:使用布隆过滤器拦截无效请求,过滤掉不存在的数据。

4.代码演示

方法一:缓存空值
这部分功能,go zero以及帮我们实现,当我们访问不存在的数据的时候,go-zero框架会帮我们自动加上空缓存,go zero会把不存在的数据的值设置为"*"

方法二:布隆过滤器
布隆过滤器的核心思想是用一个空间高效的位数组快速判断一个元素是否可能存在。如果布隆过滤器认为某个键不存在,则可以直接返回,不再查询缓存或数据库

go zero也为我们提供了 布隆过滤器 ,github.com/zeromicro/go-zero/core/bloom

import ("fmt""github.com/zeromicro/go-zero/core/bloom""github.com/zeromicro/go-zero/core/stores/redis"
)func main() {redisStore := redis.MustNewRedis(redis.RedisConf{Host: "redis-16976.c340.ap-northeast-2-1.ec2.redns.redis-cloud.com:16976",Pass: "lb8ZWuQwJENyzRiHUFjNnGJG0fgnKx5y",Type: "node",})filter := bloom.New(redisStore, "articleId", 10000)//模拟从数据库中添加数据到布隆过滤器for i := 1; i <= 50; i++ {key := fmt.Sprintf("article:id:%d", i)err := filter.Add([]byte(key))if err != nil {return}}//从布隆过滤器中查询KEY是否存在for i := 40; i <= 60; i++ {key := fmt.Sprintf("article:id:%d", i)b, _ := filter.Exists([]byte(key))fmt.Printf("%s  %v\n", key, b)}
}

五、缓存雪崩

1.概念

缓存雪崩 是指大量的请求,无法在Redis中进行处理,然后所有的请求同时访问数据库,导致数据库被打挂。

2.现象

  • 短时间内大量缓存失效,导致数据库瞬间压力过大。
  • 系统性能明显下降,甚至导致宕机。

3.解决方案

  • 设置随机过期时间:为缓存的过期时间增加随机值,避免大量缓存同时过期。
  • 分批加载缓存:将缓存重建的任务分批进行,减少瞬时压力。
  • 预热缓存:系统上线或重启时,提前加载热点数据到缓存。
  • 熔断处理 :让数据库压力比较大的时候就触发熔断,忽略部分请求,尽可能的维持服务。但是这个方法是有损的

4.代码演示

方法一:设置随机过期时间

	key := fmt.Sprintf("biz#articles#%d#%d, userId, sortType)//检查缓存是否存在isExists, err := l.svcCtx.Rds.ExistsCtx(ctx, key)if err != nil {return nil, err}//给缓存设置随机过期时间if isExists {err := l.svcCtx.Rds.ExpireCtx(ctx, key, 3600 * 24 * 2+rand.Intn(60))if err != nil {return nil, err}}

方法二:熔断处理

go zero 自带熔断处理中间件


// BreakerHandler returns a break circuit middleware.
func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handler) http.Handler {brk := breaker.NewBreaker(breaker.WithName(strings.Join([]string{method, path}, breakerSeparator)))return func(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {promise, err := brk.Allow()if err != nil {metrics.AddDrop()logx.Errorf("[http] dropped, %s - %s - %s",r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent())w.WriteHeader(http.StatusServiceUnavailable)return}cw := response.NewWithCodeResponseWriter(w)defer func() {if cw.Code < http.StatusInternalServerError {promise.Accept()} else {promise.Reject(fmt.Sprintf("%d %s", cw.Code, http.StatusText(cw.Code)))}}()next.ServeHTTP(cw, r)})}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/493331.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React:闭包陷阱产生和解决

在 React 中&#xff0c;闭包陷阱是一个常见的问题&#xff0c;尤其是在处理异步操作、事件处理器、或是定时器时。理解闭包的工作原理以及它在 React 中如何与状态和渲染交互&#xff0c;可以帮助你避免陷入一些常见的错误。 一、闭包陷阱的产生 1、什么是闭包陷阱&#xff1…

使用xjar 对Spring-Boot JAR 包加密运行

1 Xjar 介绍 Spring Boot JAR 安全加密运行工具&#xff0c;同时支持的原生JAR。 基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动&#xff0c;动态解密运行的方案&#xff0c;避免源码泄露或反编译。 功能特性 无需侵入代码&#xff0c;只需要把编译好的…

[LeetCode-Python版] 定长滑动窗口1(1456 / 643 / 1343 / 2090 / 2379)

思路 把问题拆解成三步&#xff1a;入-更新-出。 入&#xff1a;下标为 i 的元素进入窗口&#xff0c;更新相关统计量。如果 i<k−1 则重复第一步。更新&#xff1a;更新答案。一般是更新最大值/最小值。出&#xff1a;下标为 i−(k-1) 的元素离开窗口&#xff0c;更新相关…

【AIGC-ChatGPT进阶副业提示词】末日生存指南 2.0:疯狂科学家的荒诞智慧

引言 在这个不断变化的世界中&#xff0c;末日似乎总是lurking在角落。但是&#xff0c;亲爱的幸存者们&#xff0c;不要害怕&#xff01;因为我&#xff0c;疯狂科学家2099&#xff0c;正在这里为你们带来最新版本的末日生存指南。这不是你祖母的应急手册&#xff0c;而是一本…

Web3.0安全开发实践:探索比特币DeFi生态中的PSBT

近年来&#xff0c;部分签名比特币交易&#xff08;PSBT&#xff09;在比特币生态系统中获得了显著关注。随着如Ordinal和基于铭文的资产等创新的兴起&#xff0c;安全的多方签名和复杂交易的需求不断增加&#xff0c;这使得PSBT成为应对比特币生态不断发展中不可或缺的工具。 …

Edge Scdn防御网站怎么样?

酷盾安全Edge Scdn&#xff0c;即边缘式高防御内容分发网络&#xff0c;主要是通过分布在不同地理位置的多个节点&#xff0c;使用户能够更快地访问网站内容。同时&#xff0c;Edge Scdn通过先进的技术手段&#xff0c;提高了网上内容传输的安全性&#xff0c;防止各种网络攻击…

oracle client linux服务器安装教程

p13390677_112040_Linux-x86-64_4of7.zip 安装前&#xff0c;确认/etc/hosts文件已配置正确 cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.2…

【前端】Jquery拍照,通过PHP将base64编码数据转换成PNG格式,并保存图像到本地

目录 一、需求 二、开发语言 三、效果 四、业务逻辑&#xff1a; 五、web端调用摄像头 六、示例代码 1、前端 2、后端 一、需求 web端使用jquery调用摄像头拍照&#xff0c;并使用PHP把base64编码转换成png格式图片&#xff0c;下载到本地。 由于js不能指定图片存储的…

腾讯云云开发 Copilot 深度探索与实战分享

个人主页&#xff1a;♡喜欢做梦 欢迎 &#x1f44d;点赞 ➕关注 ❤️收藏 &#x1f4ac;评论 目录 一、引言 二、产品介绍 三、产品体验过程 四、整体总结 五、给开发者的复用建议 六、对 AI 辅助开发的前景展望 一、引言 在当今数字化转型加速的时代&#xff0c;…

提炼关键词的力量:AI驱动下的SEO优化策略

内容概要 在当今数字化营销的环境中&#xff0c;关键词对于提升网站的可见性和流量起着至关重要的作用。企业和个人必须重视有效的关键词策略&#xff0c;以便在竞争激烈的网络市场中脱颖而出。本文将深入探讨如何利用人工智能技术来优化SEO策略&#xff0c;特别是在关键词选择…

W25Q128读写实验(一)

十二、SPI 1. IIC与SPI对比 1. IIC 是半双工通讯&#xff0c;无法同时收发信息&#xff1b;SPI 是全双工通讯&#xff0c;可以同时收发信息&#xff1b; 2. IIC 通讯协议较复杂&#xff0c;而 SPI 通讯协议较简单&#xff1b; 3. IIC 需要通过地址选择从机&#xff0c;而 SPI …

uniApp使用腾讯地图提示未添加maps模块

uniApp使用腾讯地图&#xff0c;打包提示未添加maps模块解决方案 这是报错信息&#xff0c;在标准基座运行的时候是没问题的&#xff0c;但是打包后会提示未添加&#xff0c;可以通过在mainfest里面把地图插件上腾讯地图的key更换高德地图的key&#xff0c;定位服务可以继续用腾…

【开源项目】数字孪生轨道~经典开源项目数字孪生智慧轨道——开源工程及源码

飞渡科技数字孪生轨道可视化平台&#xff0c;基于国产数字孪生引擎&#xff0c;结合物联网IOT、大数据、激光雷达等技术&#xff0c;对交通轨道进行超远距、高精度、全天侯的监测&#xff0c;集成轨道交通运营数据&#xff0c;快速准确感知目标&#xff0c;筑牢轨交运营生命线。…

【HarmonyOS之旅】DevEco Studio的安装与环境配置

目录 1 -> 下载与安装DevEco Studio 1.1 -> 运行环境要求 1.2 -> 下载和安装DevEco Studio 2 -> 配置环境变量 3 -> 配置开发环境 4 -> 开发项目准备 5 -> 实用小技巧 5.1 -> 中文插件 2 -> 简化工程目录栏 1 -> 下载与安装DevEco Stud…

Word使用分隔符实现页面部分分栏

文章目录 Word使用分隔符实现页面部分分栏分隔符使用页面设置 Word使用分隔符实现页面部分分栏 分隔符使用 word中的分隔符&#xff1a; 前面不分栏&#xff0c;后面分栏(或前面分栏&#xff0c;后面不分栏)&#xff0c;只需要在分隔位置处插入分隔符&#xff1a;“连续”即…

多协议视频监控汇聚/视频安防系统Liveweb搭建智慧园区视频管理平台

智慧园区作为现代化城市发展的重要组成部分&#xff0c;不仅承载着产业升级的使命&#xff0c;更是智慧城市建设的重要体现。随着产业园区竞争的逐渐白热化&#xff0c;将项目打造成完善的智慧园区是越来越多用户关注的内容。 然而我们往往在规划前期就开始面临众多难题&#…

PHP接入美团联盟推广

美团给的文档没有PHP的示例代码&#xff0c;下面是以Javascript示例更改的PHP代码&#xff0c;并且已经跑通 一、计算签名 签名类&#xff0c;因为接口不多&#xff0c;所以这里只写了获取请求头 class Meituan {private $APP_KEY 你的APP_KEY;private $APP_SECRET 你的APP…

ChatGPT重大更新:新增实时搜索和高级语音

12月17日消息&#xff0c;据报道&#xff0c;OpenAI开启了第八天技术分享直播&#xff0c;对ChatGPT搜索功能进行了大量更新。 此次ChatGPT新增的功能亮点纷呈。其中&#xff0c;实时搜索功能尤为引人注目。OpenAI对搜索算法进行了深度优化&#xff0c;使得用户提出问题后&…

Vue3组件封装技巧与心得

摘要&#xff1a; 日常开发中&#xff0c;用Vue组件进行业务拆分&#xff0c;代码解耦是一个很好的选择&#xff1b; 今天就来分享一下我在使用Vue3进行组件封装的一些技巧和心得&#xff0c;希望能够帮助到大家&#xff1b; 1. 组件特性&#xff1a; 在Vue中组件是一个独立的…

数据分析实战—鸢尾花数据分类

1.实战内容 (1) 加载鸢尾花数据集(iris.txt)并存到iris_df中,使用seaborn.lmplot寻找class&#xff08;种类&#xff09;项中的异常值&#xff0c;其他异常值也同时处理 。 import pandas as pd from sklearn.datasets import load_iris pd.set_option(display.max_columns, N…