缓存请求执行结果

GolangGolangBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在这个项目中,你将学习如何基于 Go 语言的 singleflight 包实现一个支持过期时间的缓存库。这个缓存库可用于分布式系统中缓存请求的执行结果,从而提升你的应用程序性能。

👀 预览

$ /usr/local/go/bin/go test --race
PASS
ok      cacheflight     1.263s

🎯 任务

在这个项目中,你将学习:

  • 如何初始化一个 Go 模块并安装所需的包
  • 如何使用 singleflight 包实现缓存库
  • 如何测试缓存库以确保其按预期工作

🏆 成果

完成这个项目后,你将能够:

  • 理解缓存的基本概念以及 Go 语言中的 singleflight
  • 实现一个支持过期时间的缓存库
  • 将缓存库集成到你的分布式系统中以提升应用程序性能

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("`Golang`")) -.-> go/ConcurrencyGroup(["`Concurrency`"]) go(("`Golang`")) -.-> go/AdvancedTopicsGroup(["`Advanced Topics`"]) go(("`Golang`")) -.-> go/TestingandProfilingGroup(["`Testing and Profiling`"]) go(("`Golang`")) -.-> go/CommandLineandEnvironmentGroup(["`Command Line and Environment`"]) go/ConcurrencyGroup -.-> go/goroutines("`Goroutines`") go/ConcurrencyGroup -.-> go/channels("`Channels`") go/ConcurrencyGroup -.-> go/mutexes("`Mutexes`") go/AdvancedTopicsGroup -.-> go/time("`Time`") go/TestingandProfilingGroup -.-> go/testing_and_benchmarking("`Testing and Benchmarking`") go/CommandLineandEnvironmentGroup -.-> go/command_line("`Command Line`") subgraph Lab Skills go/goroutines -.-> lab-301256{{"`缓存请求执行结果`"}} go/channels -.-> lab-301256{{"`缓存请求执行结果`"}} go/mutexes -.-> lab-301256{{"`缓存请求执行结果`"}} go/time -.-> lab-301256{{"`缓存请求执行结果`"}} go/testing_and_benchmarking -.-> lab-301256{{"`缓存请求执行结果`"}} go/command_line -.-> lab-301256{{"`缓存请求执行结果`"}} end

初始化模块并安装所需包

在这一步中,你将学习如何初始化模块并为项目安装所需的包。

  1. /home/labex/project 目录中打开终端。

  2. 使用以下命令初始化模块:

    $ /usr/local/go/bin/go mod init cacheflight
  3. 使用以下命令安装所需的包:

    $ /usr/local/go/bin/go get github.com/golang/groupcache/singleflight
    $ /usr/local/go/bin/go get github.com/stretchr/testify/assert

实现缓存库

在这一步中,你将基于 singleflight 包实现缓存库。

  1. /home/labex/project 目录中打开 cacheflight.go 文件。

  2. import 中添加必要的内容。

    import (
        "sync"
        "time"
    
        "github.com/golang/groupcache/singleflight"
    )
  3. type Grouptype cacheResult 中添加必要的内容。

    // cacheResult 是缓存结果
    type cacheResult struct {
        data      interface{} // 缓存数据
        ctime     time.Time   // 缓存创建时间
        isRunning chan bool   // 函数是否正在运行
    }
    
    // Group 是核心结构体
    type Group struct {
        cacheExpiration time.Duration          // 缓存过期时间
        sfg             *singleflight.Group    // singleflight 组
        cache           map[string]cacheResult // 缓存结果
        mu              sync.Mutex             // 互斥锁
    }
  4. 实现 NewGroup 函数,以返回一个具有指定缓存过期时间的新缓存组。

    func NewGroup(cacheExpiration time.Duration) (group *Group) {
        group = &Group{
            sfg:             &singleflight.Group{},
            cache:           make(map[string]cacheResult),
            cacheExpiration: cacheExpiration,
        }
        return
    }
  5. 实现 do 函数来处理缓存逻辑。

    func (g *Group) do(key string, fn func() (interface{}, error)) (ret interface{}, err error) {
        return g.sfg.Do(key, func() (interface{}, error) {
            g.mu.Lock()
            result, ok := g.cache[key]
            if result.isRunning!= nil {
                g.mu.Unlock()
                // 等待 fn 完成
                <-result.isRunning
                return g.do(key, fn)
            } else if!ok || result.ctime.Add(g.cacheExpiration).Before(time.Now()) {
                // 如果缓存不存在或已过期
                var run = make(chan bool)
                result.isRunning = run
                g.cache[key] = result
                // 当 fn 完成并关闭 run 通道时,让其他 goroutine 可以获取缓存
                defer close(run)
            } else {
                // 如果缓存存在且未过期
                g.mu.Unlock()
                return result.data, nil
            }
    
            g.mu.Unlock()
    
            // 不同的键可以同时运行
            ret, err = fn()
            if err!= nil {
                return ret, nil
            }
    
            result = cacheResult{
                data:  ret,
                ctime: time.Now(),
            }
            g.mu.Lock()
            g.cache[key] = result
            g.mu.Unlock()
    
            return ret, nil
        })
    }
  6. 实现 Do 函数作为 do 函数的包装器。

    func (g *Group) Do(key string, fn func() (interface{}, error)) (ret interface{}, err error) {
        return g.do(key, fn)
    }

测试缓存库

在这一步中,你将测试缓存库以确保其按预期工作。

  1. /home/labex/project 目录中打开终端。

  2. 运行以下命令来执行测试:

    $ cd /home/labex/project
    $ /usr/local/go/bin/go test --race
  3. 如果缓存函数实现正确,你将看到以下输出:

    PASS
    ok      cacheflight     1.263s
  4. 如果你遇到超时错误,请在终端中执行以下命令,然后点击重新检查按钮以通过检查:

    $ cd /home/labex/project
    $ /usr/local/go/bin/go test --race

恭喜!你已成功基于 singleflight 包实现了缓存库。现在,你可以在分布式系统中使用此库来缓存请求的执行结果并提升应用程序的性能。

✨ 查看解决方案并练习

总结

恭喜!你已完成这个项目。你可以在 LabEx 中练习更多实验来提升你的技能。

您可能感兴趣的其他 Golang 教程