Go (Gin Web)でサーバーキャッシュを扱う
Goでサーバーキャッシュを扱うときの方法を簡単にまとめます。
大規模のトラフィックを捌くときにお役に立てばと思います。
フレームワークはGin Webを利用する想定です。
main.goでキャッシュの接続を記述
main.go
package main
import (
"os"
"time"
"tkol-lite/controllers"
"tkol-lite/models"
"tkol-lite/services"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
os.Setenv("TZ", "UTC") // タイムゾーンの設定
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{ // アクセスを許可したいアクセス元
"http://example.com",
},
AllowMethods: []string{ // アクセスを許可したいHTTPメソッド
"POST",
"GET",
"PUT",
"DELETE",
},
AllowHeaders: []string{ // 許可したいHTTPリクエストヘッダ
"Access-Control-Allow-Credentials",
"Access-Control-Allow-Headers",
"Content-Type",
"Content-Length",
"Accept-Encoding",
"Authorization",
},
AllowCredentials: true, // cookieなどの情報を必要とするかどうか
MaxAge: 24 * time.Hour, // preflightリクエストの結果をキャッシュする時間
}))
models.ConnectDatabase() // DB初期化
models.CreateCache() // キャッシュ初期化
// (中略)
r.GET("/article/:id", controllers.FetchArticleById)
// (中略)
r.Run()
}
Controllerからキャッシュの読み込み、書き込み
キャッシュに記憶するデータをユニークのキーで管理するようにし、該当キャッシュが存在する場合はDBへのアクセスより、キャッシュから取り出すことを優先する
controllers/article.go
package controllers
import (
"fmt"
"net/http"
"strconv"
"tkol-lite/models"
"tkol-lite/services"
"github.com/gin-gonic/gin"
)
func FetchArticleById(c *gin.Context) { // アイテムのIDを指定して取得
cache_key := "article_" + c.Param("id")
article, cache_err := models.GetCache(cache_key) // キャッシュがあれば、キャッシュから取得
if cache_err != nil { // キャッシュ存在しない場合、DBからデータを取得して、さらにキャッシュに保存
article, _ = services.FetchArticleById(c.Param("id"))
models.SetCache(cache_key, article)
}
c.JSON(http.StatusOK, gin.H{"data": article})
}
キャッシュのベースファイル
インメモリキャッシュを使う場合
個人的に使うケースはないと思いますが、簡易のテスト実行するときにいいかもしれません。
インメモリキャッシュですので、もちろんRedisなどよりはパフォーマンスはよいですが、そもそも大量のトラフィックを捌くために、サーバーが複数台にわけることとなるため、実際のところ、実用性はないです。
models/cache.go
package models
import (
"errors"
"time"
"github.com/patrickmn/go-cache"
)
var go_cache *cache.Cache
func CreateCache() {
c := cache.New(30*time.Minute, 60*time.Minute)
go_cache = c
}
func SetCache(k string, v interface{}) { // キャッシュを保存
go_cache.Set(k, v, 30*time.Second) // 保存期間を指定
}
func GetCache(k string) (interface{}, error) { // キャッシュを取得
err := errors.New("no cache")
r, ok := go_cache.Get(k)
if ok {
err = nil
}
return r, err
}
func DeleteCache(k string) { // キャッシュを削除
go_cache.Delete(k)
}
Redisなどのキャッシュストレージを使う場合
複数台のサーバーを利用すると、インメモリキャッシュがほとんど無意味、というよりも返ってカオスになってしまいますので、Redisなどのキャッシュストレージが必要となります。
Docker環境で試したところ、インメモリキャッシュの速度の1/3となります(当たり前ですが)。
models/cache.go
package models
import (
"time"
"context"
"encoding/json"
"os"
"github.com/joho/godotenv"
"github.com/redis/go-redis/v9"
)
var go_cache *redis.Client
var ctx = context.Background()
func CreateCache() {
godotenv.Load(".env") // 環境ファイルを読み込み
go_cache = redis.NewClient(&redis.Options{
Addr: os.Getenv("CACHE"), // redisの接続先
Password: "", // パスワード
DB: 0, // デフォルトDBを使う
PoolSize: 1000, // サイズの上限
})
if err := go_cache.Ping(ctx).Err(); err != nil {
panic(err)
}
go_cache.FlushAll(ctx)
}
func SetCache(k string, v interface{}) (interface{}, error) { // キャッシュを保存
data, err := json.Marshal(v) // オブジェクト式をラッパーしたjson形式へ変換
result, err := go_cache.Set(ctx, k, data, 0).Result()
go_cache.Expire(ctx, k, 30*time.Second) // 保存期間を指定
return result, err
}
func GetCache(k string) (interface{}, error) { // キャッシュを取得
var data interface{}
result, err := go_cache.Get(ctx, k).Result()
json.Unmarshal([]byte(result), &data) // キャッシュに保存されたjson形式へまたオブジェクトへ変換
return data, err
}
func DeleteCache(k string) (interface{}, error) { // キャッシュを削除
result, err := go_cache.Del(ctx, k).Result()
return result, err
}