candycache/candycache.go

315 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package candycache
import (
"encoding/json"
"errors"
"io"
"reflect"
"sync"
"time"
)
// JSON структура для создания/загрузки дампов
type Dump struct {
Key string `json:"key"`
DestroyTimestamp int64 `json:"destroyTimestamp"`
Data interface{} `json:"data"`
}
// Структура виде ключ-значение для возвращения списка элементов кэша с их ключами.
type KeyItemPair struct {
Key string
Item Item
}
// Элемент в кэше - это данные и время их жизни.
type Item struct {
destroyTimestamp int64 // Момент в Unix-секундах, когда элемент становится устаревшим
data interface{} // Данные
}
// Кэш - это хранилище элементов и инервал его очистки (ну и мьютекс на всякий случай).
// Интервал очистки хранилища укахывается в НАНОСЕКУНДАХ (используй множители для преобразования во что-то другое).
type Cache struct {
sync.RWMutex // Мьютекс ждя реализации безопасного доступа к общим данным
storage map[string]Item // Хранилище элементов
cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах
}
// Создает новый экземпляр Cache с интервалом очистки cleanupInterval.
// Если cleanupInterval < 0, то кэш не будет очищаться автоматически.
func Cacher(cleanupInterval time.Duration) *Cache {
cache := &Cache{
storage: make(map[string]Item),
cleanupInterval: cleanupInterval,
}
if cleanupInterval > 0 {
go cache.gc(cleanupInterval)
}
return cache
}
// gc = Garbage Collector.
func (c *Cache) gc(cleanupInterval time.Duration) {
ticker := time.NewTicker(cleanupInterval)
defer ticker.Stop()
for range ticker.C {
c.Cleanup()
}
}
// Перебирает все элементы в кэше, удаляет устаревшие.
func (c *Cache) Cleanup() {
c.Lock()
defer c.Unlock()
for key, item := range c.storage {
if item.destroyTimestamp <= time.Now().UnixNano() {
delete(c.storage, key)
}
}
}
// Удаление всех элементов из кэша.
func (c *Cache) Flush() {
c.Lock()
defer c.Unlock()
for key := range c.storage {
delete(c.storage, key)
}
}
// Получение элемента из кэша по ключу.
func (c *Cache) Get(key string) (interface{}, error) {
c.RLock()
defer c.RUnlock()
item, found := c.storage[key]
if !found {
return nil, errors.New("key not found")
}
return item.data, nil
}
// Определяет является ли элемент устаревшим.
// Вторым аргументов возвращается есть элемент в кэше или нет.
// Первым - устаревший элемент или нет.
func (c *Cache) IsExpired(key string) (bool, error) {
c.RLock()
defer c.RUnlock()
item, found := c.storage[key]
if !found {
return false, errors.New("key not found")
}
if item.destroyTimestamp <= time.Now().UnixNano() {
return true, nil
} else {
return false, nil
}
}
// Удаление элемента по ключу.
func (c *Cache) Delete(key string) error {
c.Lock()
defer c.Unlock()
if _, found := c.storage[key]; !found {
return errors.New("key not found")
}
delete(c.storage, key)
return nil
}
// Добавление элемента в кэш.
// key - ключ.
// data - данные.
// ttl - время жизни элемента (time to life) в наносекундах.
func (c *Cache) Set(key string, data interface{}, ttl time.Duration) {
c.Lock()
defer c.Unlock()
c.storage[key] = Item{
destroyTimestamp: time.Now().UnixNano() + int64(ttl),
data: data,
}
}
// Вернет количество элементов в кэше.
func (c *Cache) Count() int {
c.RLock()
defer c.RUnlock()
return len(c.storage)
}
// Возвращает список всех элементов кэша.
func (c *Cache) List() []KeyItemPair {
c.RLock()
defer c.RUnlock()
items := []KeyItemPair{}
for key, item := range c.storage {
items = append(items, KeyItemPair{Key: key, Item: item})
}
return items
}
// Возвращает список всех устаревших элементов кэша.
func (c *Cache) ExpiredList() []KeyItemPair {
c.RLock()
defer c.RUnlock()
items := []KeyItemPair{}
for key, item := range c.storage {
if item.destroyTimestamp <= time.Now().UnixNano() {
items = append(items, KeyItemPair{Key: key, Item: item})
}
}
return items
}
// Вернет размер всего кэша в байтах.
func (c *Cache) Size() int {
c.RLock()
defer c.RUnlock()
size := 0
for key, item := range c.storage {
size += isize(key) + isize(item.data) + isize(item.destroyTimestamp)
}
return size
}
// Save сохраняет кэш в io.Writer в формате JSON, записывая каждый элемент по отдельности.
func (c *Cache) Save(w io.Writer) error {
c.RLock()
defer c.RUnlock()
if _, err := w.Write([]byte("[\n")); err != nil {
return err
}
encoder := json.NewEncoder(w)
first := true
for key, item := range c.storage {
entry := Dump{
Key: key,
DestroyTimestamp: item.destroyTimestamp,
Data: item.data,
}
if !first {
if _, err := w.Write([]byte(",\n")); err != nil {
return err
}
}
first = false
if err := encoder.Encode(entry); err != nil {
return err
}
}
if _, err := w.Write([]byte("]")); err != nil {
return err
}
return nil
}
// Load загружает кэш из io.Reader в формате JSON.
func (c *Cache) Load(r io.Reader) error {
c.Lock()
defer c.Unlock()
decoder := json.NewDecoder(r)
if _, err := decoder.Token(); err != nil {
return err
}
for decoder.More() {
entry := Dump{}
if err := decoder.Decode(&entry); err != nil {
return err
}
c.storage[entry.Key] = Item{
destroyTimestamp: entry.DestroyTimestamp,
data: entry.Data,
}
}
if _, err := decoder.Token(); err != nil {
return err
}
return nil
}
func isize(i interface{}) int {
if i == nil {
return 0
}
val := reflect.ValueOf(i)
kind := val.Kind()
size := 0
switch kind {
case reflect.Slice, reflect.Array, reflect.String:
len := val.Len()
for i := 0; i < len; i++ {
size += isize(val.Index(i).Interface())
}
return size
case reflect.Map:
for _, key := range val.MapKeys() {
size += isize(key.Interface()) + isize(val.MapIndex(key).Interface())
}
return size
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
size += isize(val.Field(i).Interface())
}
return size
default:
return int(reflect.TypeOf(i).Size())
}
}
// Возвращает данные элемента кэша.
func (i *Item) Data() interface{} {
return i.data
}
// Возвращает момент смерти элемента кэша.
func (i *Item) DestroyTimestamp() int64 {
return i.destroyTimestamp
}
// Определяет является ли элемент устаревшим.
func (i *Item) IsExpired() bool {
if i.destroyTimestamp <= time.Now().UnixNano() {
return true
} else {
return false
}
}