Goのメモリにシンプルなキャッシュマネージャーを書く

小さなプロジェクトで作業するプロセスでは、データのキャッシュが必要になることが多く、RedisやMemcacheを使用できない場合があります。 このような状況では、追加のツールを使用しないシンプルでかなり効果的な方法が適切です-RAMにキャッシュします。
この記事では、Go自分でメモリ内のキャッシュマネージャを書き始める場所を説明します。


注意! この記事は、アカデミック目的のみの初心者デベロッパーを対象としており、Redis、Memcacheなどのツールは対象としていません。
また、メモリの割り当ての問題については掘り下げません。


簡単にするために、 Set GetおよびDelete 3つの主要な方法に限定しています。


データはキー/値の形式で保存されます。


構造


最初に行うことは、ストレージコンテナーを記述する構造を作成することです。


 type Cache struct { sync.RWMutex defaultExpiration time.Duration cleanupInterval time.Duration items map[string]Item } 


次に、要素の構造について説明します。


 type Item struct { Value interface{} Created time.Time Expiration int64 } 


ボールトの初期化


新しいストレージコンテナを初期化することから始めましょう。


 func New(defaultExpiration, cleanupInterval time.Duration) *Cache { //  (map)   (string)/(Item) items := make(map[string]Item) cache := Cache{ items: items, defaultExpiration: defaultExpiration, cleanupInterval: cleanupInterval, } //     0,  GC (  ) if cleanupInterval > 0 { cache.StartGC() //     } return &cache } 

新しいキャッシュインスタンスの初期化には、 defaultExpirationcleanupInterval 2つの引数が必要cleanupInterval



出力では、 Cache構造を持つコンテナを取得します


これらのパラメーターを設定するときは、値が小さすぎるか大きすぎると、たとえばcleanupInterval = 1 * time.Secondを設定した場合に、望ましくない結果につながる可能性があることに注意してください。 逆に、 cleanupInterval = 168 * time.Hour設定すると、未使用の要素がメモリに蓄積されます。


設定値


コンテナが作成されたら、それにデータを書き込むことができると便利です。このため、 Setメソッドの実装を記述します


 func (c *Cache) Set(key string, value interface{}, duration time.Duration) { var expiration int64 //     0 -   - if duration == 0 { duration = c.defaultExpiration } //     if duration > 0 { expiration = time.Now().Add(duration).UnixNano() } c.Lock() defer c.Unlock() c.items[key] = Item{ Value: value, Expiration: expiration, Created: time.Now(), } } 

Setは、新しいアイテムをキャッシュに追加するか、既存のアイテムを置き換えます。 ただし、キーの存在の確認は行われません。 引数として、文字列key 、value valueおよびキャッシュ寿命の形式のキー識別子を受け入れます。


値を取得する


Setの助けを借りて、リポジトリにデータを記録し、今度はそれらを受信するメソッドを実装します。


 func (c *Cache) Get(key string) (interface{}, bool) { c.RLock() defer c.RUnlock() item, found := c.items[key] //    if !found { return nil, false } //     ,      if item.Expiration > 0 { //        nil if time.Now().UnixNano() > item.Expiration { return nil, false } } return item.Value, true } 

Getは値(またはnil )を返し、キーが見つかった場合は2番目のboolパラメーターはtrue 、キーが見つからないかキャッシュが古い場合はfalseになります。


キャッシュの削除


インストールとレシートができたので、キャッシュを削除できるようにする必要があります(不要になった場合)。このためにDeleteメソッドを記述します。


 func (c *Cache) Delete(key string) error { c.Lock() defer c.Unlock() if _, found := c.items[key]; !found { return errors.New("Key not found") } delete(c.items, key) return nil } 

Deleteキーを使用してアイテムDelete削除します;キーが存在しない場合はエラーを返します。


ガベージコレクション


追加、受信、削除します。 有効期限が切れたキーの検索とその後のクリーニング(GC)の実装は引き続き行われます
これを行うために、 StartGCの新しいインスタンスの初期化が開始され、プログラムが完了するまで機能するStartGCメソッドを作成します。


 func (c *Cache) StartGC() { go c.GC() } func (c *Cache) GC() { for { //     cleanupInterval <-time.After(c.cleanupInterval) if c.items == nil { return } //           if keys := c.expiredKeys(); len(keys) != 0 { c.clearItems(keys) } } } // expiredKeys   ""  func (c *Cache) expiredKeys() (keys []string) { c.RLock() defer c.RUnlock() for k, i := range c.items { if time.Now().UnixNano() > i.Expiration && i.Expiration > 0 { keys = append(keys, k) } } return } // clearItems     ,    "" func (c *Cache) clearItems(keys []string) { c.Lock() defer c.Unlock() for _, k := range keys { delete(c.items, k) } } 

使用例


 import ( memorycache "github.com/maxchagin/go-memorycache-example" ) //      -  5       10  cache := memorycache.New(5 * time.Minute, 10 * time.Minute) //     "myKey"    5  cache.Set("myKey", "My value", 5 * time.Minute) //     "myKey" i := cache.Get("myKey") 

次は?


これで、最小限の機能を備えたキャッシュマネージャーができました。これは、最も単純なタスクには十分です。 これで十分でない場合(そして95%の場合)、次のステップとして、メソッドを個別に実装できます。


カウント-キャッシュ内のアイテムの数を取得する
GetItem-キャッシュアイテムの取得
名前の変更-キーの名前を変更
コピー-アイテムをコピーします
増分-増分
デクリメント-デクリメント
存在-要素の存在を確認
有効期限-有効期限のキャッシュを確認
FlushAll-すべてのデータを消去します
SaveFile-データをファイルに保存します
LoadFile-ファイルからデータをロードする


これは完全なリストではありませんが、基本的な機能については十分である可能性があります。


GitHubの例を含むソース


メモリに既製のキャッシュマネージャーが必要な場合は、次のプロジェクトに注意することをお勧めします。
Patrickmn go-cacheの実装
beegoによるMemoryCache



Source: https://habr.com/ru/post/J359078/


All Articles