0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go Cache Fetcherクライアント

Last updated at Posted at 2021-03-08

よくあるRedisやmemcacheでキャッシュするパターンをサポートするライブラリのGoクライアントを作りました。
こんなやつです。

アクセスしたい情報や変数からキー名を生成する
if (Redisにデータがある) {
  RedisからGetする
} else {
  DatabaseからGetしたり、APIからfetchする
  RedisへSetする
}

Redisで使う想定ですが、Clientを実装すればどのようなキャッシュにも使えます。
functionを渡せばそれがキャッシュされるので、DBでもAPIアクセスでも使えます。
データをserializeして set, getすることもできます。

詳しい使い方は、READMEとテストコードを見てください。

go-cache-fetcher-client

Golangのキャッシュフェッチャーです。

Installation

go get github.com/peutes/cachefetcher

✅ キャッシュフェッチャークライアントの基本ロジックをサポート

様々なファンクションのレスポンスをRedis, Memcached、その他のキャッシュシステムにキャッシュできます。
たとえば、最初にファンクションからレスポンスを取得する際にRedisに保存し、ファンクションのレスポンスを取得し、
次にもしキャッシュがあったらRedisから取得できます。

✅ 要素からキャッシュキーを自動生成

キャッシュキーの生成ロジックについて考える必要はありません。
キー生成ロジックはメインロジックから隠蔽できます。

✅ シンプルなキャッシュコントロール

キャッシュコントロールはとてもシンプルで、キーをセットし、フェッチャー関数をフェッチするだけです。

このフェッチャークライアントは使用するためには、 SetKeyFetch 関数のみで十分に使用できます。
Fetch には、フェッチャー関数、取得用値のポインタ、キャッシュ有効期限をセットします。

  • SetKey()
  • Fetch()

インストール

go get github.com/peutes/go-cache-fetcher-client

使い方

fetcher := cachefetcher.NewCacheFetcher(
  &ClientImpl{
    Rdb: redis.NewClient(&redis.Options{Addr: "localhost:6379"}),
  },
  nil
)
fetcher.SetKey([]string{"prefix", "str"}, "hoge")
// fetcher.Key() == "prefix_str_hoge"

// フェッチャー関数。たとえば、DBから読み込みを想定します。
read := func(s string) string {
  return s + " fetch!!"
}

// 最初のフェッチはフェッチャー関数から呼ばれます。キャッシュは呼ばれません。
var dst string
err := fetcher.Fetch(10*time.Second, &dst, func() (string, error) {
  return read("first"), nil
})
// dst == "first fetch!!" <- get from function

// 2回目のフェチは有効期限内のキャッシュから呼ばれます。フェッチャー関数からは呼ばれません。
err = fetcher.Fetch(10*time.Second, &dst, func() (string, error) {
  return read("second"), nil
})
// dst == "first fetch!!" <- get from cache

サポートされてる型

キー要素は、stringの他、 int, float, bool, complex, byte, time, slice, array, String() メソッドがある struct に対応しています。
このクライアントは gob serializer によるシリアライズをサポートしています。
キャッシュにはシリアライズされた文字列が保存されます。

fetcher.SetKey([]string{"prefix", "any"}, 1, 0.1, true, &[]string{"a", "b"}, time.Unix(0, 0).In(time.UTC))
_ = fetcher.Key() // "prefix_any_1_0.1_true_a_b_1970-01-01_00:00:00_+0000_UTC"
_ = fetcher.HashKey() // "prefix_any_c94a415eb6e20585f4fbc856b6edcf52007259522967c4bea548515e71531663"

read := func() ([]int, error) {
  return []int{1, 2, 3, 4, 5}
}
var dst []int  
err := fetcher.Fetch(10*time.Second, &dst, read)
// dst == []int{1, 2, 3, 4, 5}

要素は、文字列タイプ以外もサポートしています。
もし interface{} や独自のタイプを使用したい場合は、 GobRegister() を使って登録します。

    i := 10
    b := true
    s := "abc"
    ft := 0.123
    i8 := int8(20)
    i64 := int64(30)
    ui8 := uint8(40)
    ui64 := uint64(50)

    e := &testStruct{
        I:     i,
        B:     b,
        S:     s,
        F:     ft,
        I8:    i8,
        I64:   i64,
        UI8:   ui8,
        UI64:  ui64,
        IP:    &i,
        BP:    &b,
        SP:    &s,
        FP:    &ft,
        I8P:   &i8,
        I64P:  &i64,
        UI8P:  &ui8,
        UI64P: &ui64,
        IS:    []int{i, i, i},
        BS:    []bool{b, b, b},
        SS:    []string{s, s, s},
        FS:    []float64{ft, ft, ft},
        IM:    map[int]int{1: i, 2: i, 3: i},
        BM:    map[bool]bool{true: b, false: b},
        SM:    map[string]string{"a": s, "bb": s, "ccc": s},
        FM:    map[float64]float64{0.1: ft, 0.2: ft, 0.3: ft},
    }

    var dst testStruct
    f := cachefetcher.NewCacheFetcher(redisClient, options)
    err := fetcher.SetKey([]string{"prefix", "key"}, "struct1")
    err = fetcher.Set(e, 10*time.Second)
    err = fetcher.Get(&dst)

他のキャッシュコントロール

もし、ハッシュキーが必要であれば、 SetKey のかわりに、 SetHashKey を使います。

個々に、 Set(), Get(), Del() も使えます。
もし、キーが欲しい場合は、Key()を使います。
もし、キャッシュされているか?の結果をboolで欲しい場合は、IsCached()を使います。

  • SetHashKey()
  • Set()
  • Get()
  • SetString()
  • GetString()
  • Del()
  • Key()
  • IsCached()
  • GobRegister()

キャッシュクライアントの実装

このキャッシュフェッチャークライアントは、キャッシュクライアントの実装が求められますが、
独自実装する代わりに必要最低限のシンプルなRedisクライアントを用意しているので、このクライアントを使えば動かすことができます。

// SimpleRedisClientImpl はサンプルクライアントの実装です。
type SimpleRedisClientImpl struct {
    Rdb *redis.Client
}

// サンプルクライアントの `Set` 実装です。
func (i *SimpleRedisClientImpl) Set(key string, value interface{}, expiration time.Duration) error {
    // You need an implementation to set from the cache.
    return i.Rdb.Set(ctx, key, value, expiration).Err()
}

// サンプルクライアントの `Get` 実装です。
func (i *SimpleRedisClientImpl) Get(key string, dst interface{}) error {
    // You need an implementation to get from the cache.
    v, err := i.Rdb.Get(ctx, key).Result()
    if err != nil {
        return err
    }

    reflect.ValueOf(dst).Elem().SetString(v)
    return nil
}

// サンプルクライアントの `Del` 実装です。
func (i *SimpleRedisClientImpl) Del(key string) error {
    return i.Rdb.Del(ctx, key).Err()
}

// サンプルクライアントの `IsErrCacheMiss` 実装です。
// キャッシュミスエラーの判定を返してください。
func (i *SimpleRedisClientImpl) IsErrCacheMiss(err error) bool {
    return errors.Is(err, redis.Nil)
}

オプション

このフェッチャークライアントは single flight を設定で使えます。

もし、 DebugPrintMode を有効にすると、ターミナルにキーが出力されます。

cachefetcher.Options{
    Group:          &singleflight.Group{}, // default
    GroupTimeout:   30 * time.Second,      // default
    DebugPrintMode: true,                  // default is false
})
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?