您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

「go-zero」 防止缓存击穿之进程内共享调用

时间:2020-10-18 11:56:44  来源:  作者:

go-zero微服务框架中提供了许多开箱即用的工具,好的工具不仅能提升服务的性能而且还能提升代码的鲁棒性避免出错,实现代码风格的统一方便他人阅读等等。

本文主要讲述进程内共享调用神器SharedCalls。

使用场景

并发场景下,可能会有多个线程(协程)同时请求同一份资源,如果每个请求都要走一遍资源的请求过程,除了比较低效之外,还会对资源服务造成并发的压力。举一个具体例子,比如缓存失效,多个请求同时到达某服务请求某资源,该资源在缓存中已经失效,此时这些请求会继续访问DB做查询,会引起数据库压力瞬间增大。而使用SharedCalls可以使得同时多个请求只需要发起一次拿结果的调用,其他请求"坐享其成",这种设计有效减少了资源服务的并发压力,可以有效防止缓存击穿。

高并发场景下,当某个热点key缓存失效后,多个请求会同时从数据库加载该资源,并保存到缓存,如果不做防范,可能会导致数据库被直接打死。针对这种场景,go-zero框架中已经提供了实现,具体可参看sqlc和mongoc等实现代码。

为了简化演示代码,我们通过多个线程同时去获取一个id来模拟缓存的场景。如下:

func main() {
  const round = 5
  var wg sync.WaitGroup
  barrier := syncx.NewSharedCalls()

  wg.Add(round)
  for i := 0; i < round; i++ {
    // 多个线程同时执行
    go func() {
      defer wg.Done()
      // 可以看到,多个线程在同一个key上去请求资源,获取资源的实际函数只会被调用一次
      val, err := barrier.Do("once", func() (interface{}, error) {
        // sleep 1秒,为了让多个线程同时取once这个key上的数据
        time.Sleep(time.Second)
        // 生成了一个随机的id
        return stringx.RandId(), nil
      })
      if err != nil {
        fmt.Println(err)
      } else {
        fmt.Println(val)
      }
    }()
  }

  wg.Wait()
}

运行,打印结果为:

837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db

可以看出,只要是同一个key上的同时发起的请求,都会共享同一个结果,对获取DB数据进缓存等场景特别有用,可以有效防止缓存击穿。

关键源码分析

  • SharedCalls interface提供了Do和DoEx两种方法的抽象
// SharedCalls接口提供了Do和DoEx两种方法
type SharedCalls interface {
  Do(key string, fn func() (interface{}, error)) (interface{}, error)
  DoEx(key string, fn func() (interface{}, error)) (interface{}, bool, error)
}
  • SharedCalls interface的具体实现sharedGroup
// call代表对指定资源的一次请求
type call struct {
  wg  sync.WaitGroup  // 用于协调各个请求goroutine之间的资源共享
  val interface{}     // 用于保存请求的返回值
  err error           // 用于保存请求过程中发生的错误
}

type sharedGroup struct {
  calls map[string]*call
  lock  sync.Mutex
}
  • sharedGroup的Do方法
    • key参数:可以理解为资源的唯一标识。
    • fn参数:真正获取资源的方法。
    • 处理过程分析:
// 当多个请求同时使用Do方法请求资源时
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
  // 先申请加锁
  g.lock.Lock()

  // 根据key,获取对应的call结果,并用变量c保存
  if c, ok := g.calls[key]; ok {
    // 拿到call以后,释放锁,此处call可能还没有实际数据,只是一个空的内存占位
    g.lock.Unlock()
    // 调用wg.Wait,判断是否有其他goroutine正在申请资源,如果阻塞,说明有其他goroutine正在获取资源
    c.wg.Wait()
    // 当wg.Wait不再阻塞,表示资源获取已经结束,可以直接返回结果
    return c.val, c.err
  }

  // 没有拿到结果,则调用makeCall方法去获取资源,注意此处仍然是锁住的,可以保证只有一个goroutine可以调用makecall
  c := g.makeCall(key, fn)
  // 返回调用结果
  return c.val, c.err
}
  • sharedGroup的DoEx方法
    • 和Do方法类似,只是返回值中增加了布尔值表示值是调用makeCall方法直接获取的,还是取的共享成果
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
  g.lock.Lock()
  if c, ok := g.calls[key]; ok {
    g.lock.Unlock()
    c.wg.Wait()
    return c.val, false, c.err
  }

  c := g.makeCall(key, fn)
  return c.val, true, c.err
}
  • sharedGroup的makeCall方法
    • 该方法由Do和DoEx方法调用,是真正发起资源请求的方法。
// 进入makeCall的一定只有一个goroutine,因为要拿锁锁住的
func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call {
  // 创建call结构,用于保存本次请求的结果
  c := new(call)
  // wg加1,用于通知其他请求资源的goroutine等待本次资源获取的结束
  c.wg.Add(1)
  // 将用于保存结果的call放入map中,以供其他goroutine获取
  g.calls[key] = c
  // 释放锁,这样其他请求的goroutine才能获取call的内存占位
  g.lock.Unlock()

  defer func() {
    // delete key first, done later. can't reverse the order, because if reverse,
    // another Do call might wg.Wait() without get notified with wg.Done()
    g.lock.Lock()
    delete(g.calls, key)
    g.lock.Unlock()

    // 调用wg.Done,通知其他goroutine可以返回结果,这样本批次所有请求完成结果的共享
    c.wg.Done()
  }()

  // 调用fn方法,将结果填入变量c中
  c.val, c.err = fn()
  return c
}

最后

本文主要介绍了go-zero框架中的 SharedCalls工具,对其应用场景和关键代码做了简单的梳理,希望本篇文章能给大家带来一些收获。



Tags:缓存击穿   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
go-zero微服务框架中提供了许多开箱即用的工具,好的工具不仅能提升服务的性能而且还能提升代码的鲁棒性避免出错,实现代码风格的统一方便他人阅读等等。本文主要讲述进程内共...【详细内容】
2020-10-18  Tags: 缓存击穿  点击:(203)  评论:(0)  加入收藏
随着互联网的越来越普及,用户越来越多,系统性能瓶颈成了越来越热门的话题。要解决性能问题的技术手段有很多,比如:缓存、CDN加速、页面静态化、集群、分布式、异步等。缓存通常...【详细内容】
2020-06-27  Tags: 缓存击穿  点击:(33)  评论:(0)  加入收藏
为什么引入我们的业务中经常会遇到穿库的问题,通常可以通过缓存解决。如果数据维度比较多,结果数据集合比较大时,缓存的效果就不明显了。因此为了解决穿库的问题,我们引入Bloom...【详细内容】
2020-05-05  Tags: 缓存击穿  点击:(46)  评论:(0)  加入收藏
缓存(内存 or Memcached or Redis.....)在互联网项目中广泛应用,本篇博客将讨论下缓存击穿这一个话题,涵盖缓存击穿的现象、解决的思路、以及通过代码抽象方式来处理缓存击穿。...【详细内容】
2019-12-13  Tags: 缓存击穿  点击:(76)  评论:(0)  加入收藏
本篇文章主要谈谈Redis中很容易出现的三大问题现象:缓存击穿、缓存穿透以及缓存雪崩。不过在介绍这三个问题现象之前,我们首先需要先来了解下Redis中key的过期淘汰机制。众所...【详细内容】
2019-10-12  Tags: 缓存击穿  点击:(111)  评论:(0)  加入收藏
一、缓存穿透1.何为缓存穿透?缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查...【详细内容】
2019-09-23  Tags: 缓存击穿  点击:(166)  评论:(0)  加入收藏
01前言在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如...【详细内容】
2019-08-26  Tags: 缓存击穿  点击:(198)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(11)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(26)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(55)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条