发布于 2026-01-06 1 阅读
0

Golang 中的缓存 🛢️

Golang 中的缓存 🛢️

介绍🚀

缓存是Web开发中用于提高应用程序性能的一项重要技术。它通过将频繁访问的数据存储在临时存储区域(例如内存)中,来减少从数据源获取数据所需的时间。

缓存有多种类型,包括内存缓存、数据库缓存和文件缓存。每种类型都有其优缺点,选择合适的缓存取决于具体的使用场景和应用程序的需求。

内存缓存是最简单、最快的缓存方法之一,因此被许多应用程序广泛采用。它将数据存储在服务器的内存中,使应用程序能够快速访问这些数据,而无需任何外部依赖。

在这篇博文中,我们将重点介绍 Golang 中的内存缓存,并通过一个简单的 API 示例来演示其实现。读完本文后,您将对内存缓存的工作原理有深入的了解。

先决条件

要继续学习本教程,首先需要安装 Golang 和 Fiber。

安装:

  • Golang
  • 光纤:我们将在后面的教程中看到这一点。

入门指南🛠

首先,我们Go-Cache-API使用以下命令创建主项目目录。

(🟥注意,有时我会在代码中添加注释进行解释)

mkdir Go-Cache-API //Creates a 'Go-Cache-API' directory
cd Go-Cache-API //Change directory to 'Go-Cache-API'
Enter fullscreen mode Exit fullscreen mode

现在初始化一个模块文件。(如果您要发布一个模块,这必须是一个 Go 工具可以下载该模块的路径。这也就是您的代码仓库。)

go mod init github.com/<username>/Go-Cache-API //<username> is your github username
Enter fullscreen mode Exit fullscreen mode

要安装 Fiber 框架,请运行以下命令:

go get -u github.com/gofiber/fiber/v2
Enter fullscreen mode Exit fullscreen mode

为了实现内存缓存,我们将使用[此处应填写具体工具名称go-cache],要安装它,请运行以下命令:

go get github.com/patrickmn/go-cache
Enter fullscreen mode Exit fullscreen mode

go-cache是一个类似于 memcached 的内存键值存储/缓存,适用于在单台机器上运行的应用程序。

现在,让我们创建一个main.go用于定义路由的页面。

package main

import (
    "time"
    "github.com/gofiber/fiber/v2"
    "github.com/Siddheshk02/Go-Cache-API/middleware"
    "github.com/Siddheshk02/Go-Cache-API/routes"
    "github.com/patrickmn/go-cache"
)

func main() {
    app := fiber.New() // Creating a new instance of Fiber.

    //cache := cache.New(10*time.Minute, 20*time.Minute) // setting default expiration time and clearance time.

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, World 👋!")
    })
    //app.Get("/posts/:id", middleware.CacheMiddleware(cache),   routes.GetPosts) //commenting this route just to test the "/" endpoint.
    app.Listen(":8080")
}
Enter fullscreen mode Exit fullscreen mode

文件中main.go,第一步是使用该fiber.New()方法初始化一个新的 Fiber 应用。这将创建一个新的 Fiber 框架实例,用于处理 HTTP 请求和响应。

我们将使用https://jsonplaceholder.typicode.com/获取示例数据。我们将从 /posts 端点获取数据。Jsonplaceholder 为许多其他 API 端点提供模拟数据,您可以尝试使用任意端点。

cache.New()函数接受两个参数:

  • 第一个参数是缓存条目在被自动清除之前应该保留多长时间。在本例中,它被设置为 10 分钟。
  • 第二个参数是缓存条目在被自动清除之前可以保持空闲(未被访问)的时间。在本例中,该值设置为 20 分钟。

命令运行后,go run main.go终端将显示如下内容:

输出

现在您可以取消所有代码行的注释。

如您所见,路由中添加了一个中间件函数/posts。当向此端点发出请求时,服务器首先会执行该CacheMiddleware函数并传入一个缓存参数。此中间件函数负责检查请求的数据是否已缓存,如果已缓存,则直接从缓存中返回数据,而无需发起新的 API 调用。如果缓存中不存在所需数据,则该中间件函数会将请求传递给中间件链中的下一个函数routes.GetPosts

routes.GetPosts这是一个当请求到达此处时将执行的函数。该函数会向外部 API 发送 GET 请求来https://jsonplaceholder.typicode.com/posts/:id获取帖子数据。URL:id中的 `<post_id>` 部分是一个占位符,将被替换为所请求帖子的实际 ID。

让我们定义CacheMiddleware()这个函数,在主目录中创建一个名为 middleware 的文件夹。在这个文件夹中创建一个文件cache.go

package middleware

import (
    "encoding/json"
    "time"

    "github.com/gofiber/fiber/v2"
    "github.com/patrickmn/go-cache"
)

type Post struct {
    UserID int    `json:"userId"`
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Body   string `json:"body"`
}

func CacheMiddleware(cache *cache.Cache) fiber.Handler {
    return func(c *fiber.Ctx) error {
        if c.Method() != "GET" {
            // Only cache GET requests
            return c.Next()
        }

        cacheKey := c.Path() + "?" + c.Params("id") // Generate a cache key from the request path and query parameters

        // Check if the response is already in the cache
        if cached, found := cache.Get(cacheKey); found {
            return c.JSON(cached)
        }
        err := c.Next()
        if err != nil {
            return err
        }

        var data Post
        cacheKey := c.Path() + "?" + c.Params("id")

        body := c.Response().Body()
        err = json.Unmarshal(body, &data)
        if err != nil {
            return c.JSON(fiber.Map{"error": err.Error()})
        }

        // Cache the response for 10 minutes
        cache.Set(cacheKey, data, 10*time.Minute)

        return nil
    }
}
Enter fullscreen mode Exit fullscreen mode

中间件函数CacheMiddleware,接受指向对象的指针cache.Cache作为参数,并返回一个fiber.Handler函数。

中间件函数首先检查请求的 HTTP 方法是否为 GET 请求GET。如果不是 POSTGET请求,则直接将请求传递给下一个中间件函数。

如果是GET请求,它会将请求路径和查询参数连接起来生成一个缓存键。然后,它会使用对象的 Get 方法检查缓存中是否已存在与该缓存键对应的响应cache.Cache。如果缓存中存在该响应,则会使用对象的 JSON 方法返回缓存的响应fiber.Ctx

如果缓存中不存在响应,它会调用fiber.Ctx对象的 `Next` 方法,执行下一个中间件函数。然后,它会将请求路径和查询参数连接起来,创建一个新的缓存键,并使用Response().Body()fiber.Ctx对象的方法读取响应体。最后,它会使用 `unmarshal` 方法将响应体解组到一个Post结构体中json.Unmarshal

如果在反序列化响应体时出现错误,它将使用fiber.Ctx对象的 JSON 方法返回错误响应。

如果没有错误,它会使用cache.Cache对象的 Set 方法缓存响应 10 分钟,并返回 nil 以表明中间件函数已完成处理请求。

现在,让我们定义GetPosts()这个函数,routes在主目录中创建一个文件夹。在这个文件夹中创建一个文件routes.go

package routes

import (
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"

    "github.com/gofiber/fiber/v2"
)

type Post struct {
    UserID int    `json:"userId"`
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Body   string `json:"body"`
}

func GetPosts(c *fiber.Ctx) error {
    id := c.Params("id") // Get the post ID from the request URL parameters
    if id == "" {
        log.Fatal("Invalid ID")
    }

    // Fetch the post data from the API
    resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/" + id)
    if err != nil {
        return c.JSON(fiber.Map{"error": err.Error()})
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return c.JSON(fiber.Map{"error": err.Error()})
    }

    var data Post
    err = json.Unmarshal(body, &data)
    if err != nil {
        return c.JSON(fiber.Map{"error": err.Error()})
    }

    return c.JSON(data)

}
Enter fullscreen mode Exit fullscreen mode

此函数首先使用 . 从 URL 中提取 ID 参数c.Params("id")。如果未提供 ID 或 ID 无效,则会记录致命错误。

GET然后,它会向API发送 HTTP请求https://jsonplaceholder.typicode.com/posts/,并将提供的 ID 作为端点。它会在请求过程中检查是否存在任何错误,如果发生错误,则会返回错误响应。

然后使用该函数读取响应体,并使用ioutil.ReadAll()将其反序列化为结构体。如果在反序列化过程中发生错误,则返回错误响应。Postjson.Unmarshal()

最后,该函数返回包含数据的 JSON 响应Post。然后,该数据会被函数存储在缓存中CacheMiddleware()

现在,如果我们想确定响应是来自缓存还是 API 服务器,我们会向响应中添加一个标头,指示响应是否来自缓存。我们会添加一个名为 `cache_server_name` 的自定义标头,并根据响应是否来自缓存Cache-Status将其值设置为 ` HITtrue`或 `false`。因此,该函数将如下所示:MISSCacheMiddleware()

package middleware

import (
    "encoding/json"
    "time"

    "github.com/gofiber/fiber/v2"
    "github.com/patrickmn/go-cache"
)

type Post struct {
    UserID int    `json:"userId"`
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Body   string `json:"body"`
}

func CacheMiddleware(cache *cache.Cache) fiber.Handler {
    return func(c *fiber.Ctx) error {
        if c.Method() != "GET" {
            // Only cache GET requests
            return c.Next()
        }

        cacheKey := c.Path() + "?" + c.Params("id") // Generate a cache key from the request path and query parameters

        // Check if the response is already in the cache
        if cached, found := cache.Get(cacheKey); found {
            c.Response().Header.Set("Cache-Status", "HIT")
            return c.JSON(cached)
        }

        c.Set("Cache-Status", "MISS")
        err := c.Next()
        if err != nil {
            return err
        }

        var data Post
        cacheKey := c.Path() + "?" + c.Params("id")

        body := c.Response().Body()
        err = json.Unmarshal(body, &data)
        if err != nil {
            return c.JSON(fiber.Map{"error": err.Error()})
        }

        // Cache the response for 10 minutes
        cache.Set(cacheKey, data, 10*time.Minute)

        return nil
    }
}
Enter fullscreen mode Exit fullscreen mode

让我们测试一下 API,go run main.go在终端中运行,得到与之前相同的输出后,打开任何 API 测试工具,例如Postman

输入网址,http://127.0.0.1:8080/posts/1然后按回车键。

输出 :

{
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Enter fullscreen mode Exit fullscreen mode

现在点击“标头”选项,您可以看到Cache-StatusMISS。再次按 Enter 键输入相同的 ID,您会看到Cache-Status:。现在它变成了:HIT。这表明第二次传递相同参数时,响应是通过缓存发送的。

这是 Golang 中内存缓存的基本实现。

结论✨💯

结论

您可以在这里找到本教程的完整代码库 👉 Github

在 Go 语言中,你可以使用内存缓存系统来实现缓存,例如我们在本文中讨论的那种。通过使用中间件函数,你可以轻松地将缓存集成到你的 Go 语言 Web 应用程序中,并缩短用户的响应时间。

然而,如果管理不当,缓存也可能导致数据过时。因此,务必考虑缓存过期时间,并在底层数据发生更改时更新缓存。

总而言之,缓存可以成为您的 Web 开发工具包的一个很好的补充,我希望这篇博文能够对 Golang 中的内存缓存进行有用的介绍。

要了解有关 Golang 概念、项目等的更多信息,并随时了解教程的最新动态,请在 TwitterGitHub上关注 Siddhesh 。

在此之前,继续学习继续建设🚀🚀

文章来源:https://dev.to/siddheshk02/caching-in-golang-176j