多層キャッシュ
キャシュとしてRedisサーバーを別で建てることが多いと思いますが、今回は多層キャッシュとしてキャッシュのキャッシュを作成し、Redisサーバーへのアクセスを節約してみようと思います。
インメモリキャッシュはgo-cache
を使用します。
実装方針
インメモリキャッシュに存在すれば、利用。
存在しなければ、redisサーバーから取得し、インメモリキャッシュを作成します。
Redisサーバーの起動
redisの立ち上げに関しては以前こちらで説明してあります。
version: '3'
services:
golang:
build: .
tty: true
volumes:
- ./:/app/
redis:
image: "redis:latest"
ports:
- "6381:6379"
volumes:
- "./data/redis:/data"
FROM golang:1.21.5
WORKDIR /app
ディレクトリ構成
$ tree -N .
.
├── Dockerfile
├── adapters
│ ├── memory_cache_adapter.go
│ └── redis_adapter.go
├── data
│ └── redis
│ └── dump.rdb
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
└── interfaces
└── cache.go
4 directories, 9 files
インメモリキャッシュのアダプターとRedisのアダプターをadapters
内に作成しました。
adapters/redis_adapter.go
package adapters
import "github.com/go-redis/redis"
type RedisAdapter struct {
client *redis.Client
}
func NewRedisAdapter() *RedisAdapter {
return &RedisAdapter{
client: redis.NewClient(&redis.Options{
Addr: "redis:6379",
Password: "",
DB: 0,
}),
}
}
func (adapter *RedisAdapter) Get(key string) string {
val, err := adapter.client.Get(key).Result()
if err != nil {
return ""
}
return val
}
func (adapter * RedisAdapter) Set(key string, value string) {
adapter.client.Set(key, value, 0)
}
func (adapter * RedisAdapter) Delete(key string) {
adapter.client.Del(key)
}
adapters/memory_cache_adapter.go
package adapters
import (
"time"
"github.com/patrickmn/go-cache"
)
type MemoryCacheAdapter struct {
client *cache.Cache
}
func NewMemoryCacheAdapter() *MemoryCacheAdapter {
return &MemoryCacheAdapter{
client: cache.New(30*time.Second, 30*time.Second),
}
}
func (adapter *MemoryCacheAdapter) Get(key string) string {
val, found := adapter.client.Get(key)
if found {
return val.(string)
}
return ""
}
func (adapter *MemoryCacheAdapter) Set(key string, value string) {
adapter.client.Set(key, value, cache.DefaultExpiration)
}
func (adapter *MemoryCacheAdapter) Delete(key string) {
adapter.client.Delete(key)
}
次にinterfaceを定義します。
interfaces/cache.go
package interfaces
type Cache interface {
Get(key string) string
Set(key string, value string)
Delete(key string)
}
func GetData(cache Cache, key string) string {
return cache.Get(key)
}
func SetData(cache Cache, key string, value string) {
cache.Set(key, value)
}
func DeleteData(cache Cache, key string) {
cache.Delete(key)
}
最後にmain.go
です。
繰り返しキャッシュからの取得を試みて、どこから取得しているのか、いつ揮発するのかを確認します。
main.go
package main
import (
"fmt"
"in_memory_cache_demo/adapters"
"in_memory_cache_demo/interfaces"
"time"
)
func main() {
memory_cache_adapter := adapters.NewMemoryCacheAdapter()
redis_adapter := adapters.NewRedisAdapter()
totalTime := 0
for {
value := interfaces.GetData(memory_cache_adapter, "key")
if value == "" {
fmt.Println("インメモリキャッシュに無いのでRedisから取得します")
value = interfaces.GetData(redis_adapter, "key")
interfaces.SetData(memory_cache_adapter, "key", value)
} else {
fmt.Println("インメモリキャッシュにありました")
}
fmt.Println(value)
// 3秒待機
time.Sleep(3 * time.Second)
// 開始から何秒経過したかを表示
totalTime += 3
fmt.Println(totalTime, "秒経過")
}
}
検証
Redisサーバーにキャッシュを作る
最初に手動でキャッシュを作っておきます。
$ redis-cli -p 6381
127.0.0.1:6381> SET key hogehgoehoge
OK
127.0.0.1:6381> get key
"hogehgoehoge"
main.goの実行
最初はredisにしかキャッシュが存在しないため、redisから取得。以降はインメモリキャッシュから取得されるはずです。
インメモリキャッシュは30秒で揮発するように設定しているので、開始から30秒経過後、再びredisから取得されるはずです。
root@db2ef987e544:/app# go run main.go
インメモリキャッシュに無いのでRedisから取得します
hogehgoehoge
3 秒経過
インメモリキャッシュにありました
hogehgoehoge
6 秒経過
インメモリキャッシュにありました
hogehgoehoge
9 秒経過
インメモリキャッシュにありました
hogehgoehoge
12 秒経過
インメモリキャッシュにありました
hogehgoehoge
15 秒経過
インメモリキャッシュにありました
hogehgoehoge
18 秒経過
インメモリキャッシュにありました
hogehgoehoge
21 秒経過
インメモリキャッシュにありました
hogehgoehoge
24 秒経過
インメモリキャッシュにありました
hogehgoehoge
27 秒経過
インメモリキャッシュにありました
hogehgoehoge
30 秒経過
インメモリキャッシュに無いのでRedisから取得します
hogehgoehoge
33 秒経過
インメモリキャッシュにありました
hogehgoehoge
よさそうです。
この場合、redisサーバーへのアクセスが1/10になりました。