Golang 中的缓存 🛢️
介绍🚀
缓存是Web开发中用于提高应用程序性能的一项重要技术。它通过将频繁访问的数据存储在临时存储区域(例如内存)中,来减少从数据源获取数据所需的时间。
缓存有多种类型,包括内存缓存、数据库缓存和文件缓存。每种类型都有其优缺点,选择合适的缓存取决于具体的使用场景和应用程序的需求。
内存缓存是最简单、最快的缓存方法之一,因此被许多应用程序广泛采用。它将数据存储在服务器的内存中,使应用程序能够快速访问这些数据,而无需任何外部依赖。
在这篇博文中,我们将重点介绍 Golang 中的内存缓存,并通过一个简单的 API 示例来演示其实现。读完本文后,您将对内存缓存的工作原理有深入的了解。
先决条件
要继续学习本教程,首先需要安装 Golang 和 Fiber。
安装:
入门指南🛠
首先,我们Go-Cache-API使用以下命令创建主项目目录。
(🟥注意,有时我会在代码中添加注释进行解释)
mkdir Go-Cache-API //Creates a 'Go-Cache-API' directory
cd Go-Cache-API //Change directory to 'Go-Cache-API'
现在初始化一个模块文件。(如果您要发布一个模块,这必须是一个 Go 工具可以下载该模块的路径。这也就是您的代码仓库。)
go mod init github.com/<username>/Go-Cache-API //<username> is your github username
要安装 Fiber 框架,请运行以下命令:
go get -u github.com/gofiber/fiber/v2
为了实现内存缓存,我们将使用[此处应填写具体工具名称go-cache],要安装它,请运行以下命令:
go get github.com/patrickmn/go-cache
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")
}
文件中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
}
}
中间件函数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)
}
此函数首先使用 . 从 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
}
}
让我们测试一下 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"
}
现在点击“标头”选项,您可以看到Cache-Status:MISS。再次按 Enter 键输入相同的 ID,您会看到Cache-Status:。现在它变成了:HIT。这表明第二次传递相同参数时,响应是通过缓存发送的。
这是 Golang 中内存缓存的基本实现。
结论✨💯
您可以在这里找到本教程的完整代码库 👉 Github。
在 Go 语言中,你可以使用内存缓存系统来实现缓存,例如我们在本文中讨论的那种。通过使用中间件函数,你可以轻松地将缓存集成到你的 Go 语言 Web 应用程序中,并缩短用户的响应时间。
然而,如果管理不当,缓存也可能导致数据过时。因此,务必考虑缓存过期时间,并在底层数据发生更改时更新缓存。
总而言之,缓存可以成为您的 Web 开发工具包的一个很好的补充,我希望这篇博文能够对 Golang 中的内存缓存进行有用的介绍。
要了解有关 Golang 概念、项目等的更多信息,并随时了解教程的最新动态,请在 Twitter和GitHub上关注 Siddhesh 。
在此之前,继续学习,继续建设🚀🚀
文章来源:https://dev.to/siddheshk02/caching-in-golang-176j

