redisとgo-cacheで多層キャッシュを実現する

# 多層キャッシュ
　キャシュとしてRedisサーバーを別で建てることが多いと思いますが、今回は多層キャッシュとしてキャッシュのキャッシュを作成し、Redisサーバーへのアクセスを節約してみようと思います。
 インメモリキャッシュは`go-cache`を使用します。
 
 https://qiita.com/shuyaeer/items/a96aeb29c95933f5e2d9

 ## 実装方針
インメモリキャッシュに存在すれば、利用。
存在しなければ、redisサーバーから取得し、インメモリキャッシュを作成します。

 ## Redisサーバーの起動
 redisの立ち上げに関しては以前こちらで説明してあります。
 
 https://qiita.com/shuyaeer/items/93bac4a42083c5517773

```yml
version: '3'
services:
  golang:
    build: .
    tty: true
    volumes:
      - ./:/app/
  redis:
    image: "redis:latest"
    ports:
      - "6381:6379"
    volumes:
      - "./data/redis:/data"
```

```Dockerfile
FROM golang:1.21.5
WORKDIR /app

```
 
## ディレクトリ構成
```bash
$ 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`内に作成しました。
```go: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を定義します。
```go: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`です。
繰り返しキャッシュからの取得を試みて、どこから取得しているのか、いつ揮発するのかを確認します。

```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サーバーにキャッシュを作る
最初に手動でキャッシュを作っておきます。

```bash
$ 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から取得されるはずです。

```bash
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になりました。