Compare commits
62 Commits
Author | SHA1 | Date |
---|---|---|
|
f66e67e4d7 | |
|
c70143ac5b | |
|
67278d8e46 | |
|
bfbf496836 | |
|
666e8ef986 | |
|
8026821dad | |
|
7c74d58155 | |
|
6ff57692ce | |
|
7a2a846273 | |
|
0bb6297df3 | |
|
cae8611d7b | |
|
aa69c168f8 | |
|
ffce25b166 | |
|
69769f8c22 | |
|
f70e396bca | |
|
f4d9a23e79 | |
|
afa9d82c59 | |
|
469c273292 | |
|
d369267c21 | |
|
9ddbc94e9c | |
|
43985017e8 | |
|
34afa23b01 | |
|
237da6e002 | |
|
7ce16788c6 | |
|
c5863cb2b6 | |
|
30e8a0aa17 | |
|
653c140664 | |
|
6548230029 | |
|
60fbb406c0 | |
|
1b64c2955e | |
|
b1c6e058da | |
|
083d1147a5 | |
|
c6d0823ff6 | |
|
e4c2c92d95 | |
|
de6b44bcd2 | |
|
a54debddcc | |
|
78183ec9b2 | |
|
f3d84635e9 | |
|
7e009d870c | |
|
75c0f387bb | |
|
fca8b3d9ff | |
|
09941f7723 | |
|
eae6ab252d | |
|
485806dc57 | |
|
615a682595 | |
|
71aa7454f6 | |
|
86a95fe61d | |
|
5a4db2dfd4 | |
|
995f93f94b | |
|
6a0fe5a584 | |
|
f79243bc42 | |
|
da8b0916a1 | |
|
b26c217492 | |
|
73c8a8860e | |
|
88d3780d57 | |
|
950f254211 | |
|
19f7143373 | |
|
692b011eda | |
|
54f2505afb | |
|
e1ae0ba924 | |
|
e81d2acb68 | |
|
d8c7d57bf3 |
|
@ -0,0 +1,235 @@
|
|||
# Candy Cache
|
||||
|
||||
**CandyCache** — это простой и эффективный кэш на языке Go, который позволяет хранить данные с ограниченным временем жизни (**TTL**).
|
||||
|
||||
# Установка
|
||||
|
||||
Для использования CandyCache в вашем проекте, установите его, используя ```go get git.hikan.ru/serr/candycache```, далее просто добавьте ```git.hikan.ru/serr/candycache``` в блок импорта.
|
||||
|
||||
# Основные возможности
|
||||
|
||||
- Автоматическая очистка устаревших элементов и возможность ее отключения.
|
||||
- Кэшем можно управлять вручную.
|
||||
- Конкурентный доступ к данным возможен благодаря мьютексам.
|
||||
- Кэш может хранить данные любых типов.
|
||||
- Можно создавать и загружать дампы.
|
||||
|
||||
# Использование
|
||||
|
||||
## Создание кэша
|
||||
|
||||
### С автоматической очисткой
|
||||
|
||||
Для создания нового экземпляра кэша используйте функцию **Cacher**, передавая интервал очистки в наносекундах.
|
||||
|
||||
Если требуется указать интервал в секундах/минутах/часах и т.д. - используйте множители из пакета **time**:
|
||||
```go
|
||||
cache := candycache.Cacher(10 * time.Minute) // Очистка каждые 10 минут
|
||||
```
|
||||
|
||||
### Без автоматической очистки
|
||||
|
||||
Если автоматичская очистка не нужна - просто передайте параметром любое отрицательное число:
|
||||
|
||||
```go
|
||||
cache := candycache.Cacher(-1) // Кэш не будет очищаться автоматически
|
||||
```
|
||||
|
||||
## Добавление элемента
|
||||
|
||||
Для добавления элемента в кэш используйте метод **Set**:
|
||||
```go
|
||||
cache.Set("key", "value", 5 * time.Minute) // Элемент будет считаться устаревшим через 5 минут
|
||||
```
|
||||
В случае, если по указанном ключу уже что-то хранится, оно будет заменено на новый элемент.
|
||||
|
||||
## Получение элемента
|
||||
|
||||
Для получения элемента из кэша используйте метод **Get**:
|
||||
|
||||
```go
|
||||
value, err := cache.Get("key") // Получение значения по ключу "key"
|
||||
```
|
||||
Если элемент найден, то в переменную **value** будет записано значение, а в **err** - **nil**. Если элемент не найден, то в **err** будет записано **key not found**, а значением вернется **nil**.
|
||||
|
||||
## Удаление элемента
|
||||
|
||||
Для удаления элемента по ключу используйте метод **Delete**:
|
||||
|
||||
```go
|
||||
err := cache.Delete("key1")
|
||||
if err != nil {
|
||||
fmt.Println("Ошибка:", err) // Не найдено записи по ключу
|
||||
}
|
||||
```
|
||||
|
||||
Элемент будет удален, не смотря на то, устаревший он или нет.
|
||||
|
||||
## Массовое удаление элементов
|
||||
|
||||
### Удаление устаревших элементов
|
||||
|
||||
Для удаления устаревших элементов используйте метод **Cleanup**:
|
||||
|
||||
```go
|
||||
cache.Cleanup() // Перебирает все элементы кэша, удаляет устаревшие
|
||||
```
|
||||
|
||||
### Удаление всех элементов кэша
|
||||
|
||||
Для полной очистки кэша используйте метод **Flush**:
|
||||
|
||||
```go
|
||||
cache.Flush() // Удаляет все элементы кэша, не смотря на то, устаревшие они или нет
|
||||
```
|
||||
|
||||
## Получение информации о кэше
|
||||
|
||||
### Получение списка элементов
|
||||
|
||||
Для получения списка всех элементов в кэше используйте метод **List**:
|
||||
|
||||
```go
|
||||
items := cache.List() // Список всех элементов кэша
|
||||
for _, item := range items {
|
||||
fmt.Printf("Ключ: %s, Значение: %v, Момент устаревания: %d\n", item.Key, item.Item.Data(), item.Item.DestroyTimestamp())
|
||||
}
|
||||
```
|
||||
|
||||
Получить список устаревших элементов можно так
|
||||
```go
|
||||
items := ExpiredList()
|
||||
```
|
||||
|
||||
### Получение количества элементов
|
||||
|
||||
Для получения количества элементов в кэше используйте метод **Count**:
|
||||
|
||||
```go
|
||||
count := cache.Count() // Количество элементов в кэше
|
||||
```
|
||||
|
||||
### Получение размера кэша
|
||||
|
||||
Для получения размера всего кэша в байтах используйте метод **Size**:
|
||||
|
||||
```go
|
||||
size := cache.Size() // Размер кэша в байтах
|
||||
```
|
||||
|
||||
Данный метод возвращает корректное значение, если в кэше элементы представлены этими типами данных:
|
||||
|
||||
```go
|
||||
bool
|
||||
int, int8, int16, int32, int64
|
||||
uint, uint8, uint16, uint32, uint64, uintptr
|
||||
float32, float64
|
||||
complex64, complex128
|
||||
array, slice, string
|
||||
map, struct, func
|
||||
```
|
||||
|
||||
**И композициями этих типов**.
|
||||
|
||||
В противном случае значение может быть не точным.
|
||||
|
||||
## Работа с дампами
|
||||
|
||||
В модуле присутствуют методы **Save** и **Load**, позволяющие создавать и загружать дампы. Эти методы удовлетворяют интерфейсам **io.Writer** и **io.Reader** соответственно, т.е. их можно использовать и для работы с файлами, и для работы с буфферами.
|
||||
|
||||
### Сценарий 1
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Hobbies []string
|
||||
}
|
||||
|
||||
cache := candycache.Cacher(10 * time.Minute) // Создаем кэш с интервалом очистки 10 минут
|
||||
|
||||
cache.Set("key1", "string", 5*time.Minute)
|
||||
cache.Set("key2", 2, 10*time.Minute)
|
||||
cache.Set("key7", -2.1231, 10*time.Minute)
|
||||
cache.Set("key3", []string{"string1", "string2"}, 10*time.Minute)
|
||||
cache.Set("key4", map[string]int{"a": 1, "b": 2}, 10*time.Minute)
|
||||
cache.Set("key5", Person{Name: "Alice", Age: 30, Hobbies: []string{"reading", "swimming"}}, 10*time.Minute)
|
||||
cache.Set("key6", []Person{
|
||||
{Name: "Bob", Age: 25, Hobbies: []string{"coding", "gaming"}},
|
||||
{Name: "Charlie", Age: 35, Hobbies: []string{"hiking", "photography"}},
|
||||
}, 10*time.Minute)
|
||||
|
||||
file, err := os.Create("cache_dump.json")
|
||||
if err != nil {
|
||||
log.Fatal("error creating file: ", err)
|
||||
}
|
||||
|
||||
if err := cache.Save(file); err != nil { // Сохранение кэша в файл
|
||||
log.Fatal("error saving cache: ", err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
cache.Flush() // Удаление всех элементов из кэша
|
||||
|
||||
file, err = os.Open("cache_dump.json")
|
||||
if err != nil {
|
||||
log.Fatal("error opening file: ", err)
|
||||
}
|
||||
|
||||
if err := cache.Load(file); err != nil { // Загрузка кэша из файла
|
||||
fmt.Println("error load cache:", err)
|
||||
}
|
||||
|
||||
list := cache.List() // Получаю список элементов кэша
|
||||
|
||||
for _, i := range list {
|
||||
fmt.Println(i.Key, i.Item.Data(), i.Item.DestroyTimestamp())
|
||||
}
|
||||
```
|
||||
|
||||
### Сценарий 2
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Hobbies []string
|
||||
}
|
||||
|
||||
cache := candycache.Cacher(10 * time.Minute) // Создаем кэш с интервалом очистки 10 минут
|
||||
|
||||
cache.Set("key1", "string", 5*time.Minute)
|
||||
cache.Set("key2", 2, 10*time.Minute)
|
||||
cache.Set("key7", -2.1231, 10*time.Minute)
|
||||
cache.Set("key3", []string{"string1", "string2"}, 10*time.Minute)
|
||||
cache.Set("key4", map[string]int{"a": 1, "b": 2}, 10*time.Minute)
|
||||
cache.Set("key5", Person{Name: "Alice", Age: 30, Hobbies: []string{"reading", "swimming"}}, 10*time.Minute)
|
||||
cache.Set("key6", []Person{
|
||||
{Name: "Bob", Age: 25, Hobbies: []string{"coding", "gaming"}},
|
||||
{Name: "Charlie", Age: 35, Hobbies: []string{"hiking", "photography"}},
|
||||
}, 10*time.Minute)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
if err := cache.Save(&buffer); err != nil { // Сохранение бэкапа
|
||||
log.Fatal("error saving cache: ", err)
|
||||
}
|
||||
|
||||
cache.Set("key1", "lost", -1)
|
||||
cache.Set("key2", "lost", -1)
|
||||
cache.Set("key3", "lost", -1)
|
||||
cache.Set("key4", "lost", -1)
|
||||
cache.Set("key5", "lost", -1)
|
||||
cache.Set("key6", "lost", -1)
|
||||
cache.Set("key7", "lost", -1)
|
||||
|
||||
if err := cache.Load(&buffer); err != nil { // Восстановление бэкапа
|
||||
log.Fatal("error loading cache: ", err)
|
||||
}
|
||||
|
||||
list := cache.List() // Получаю список элементов кэша
|
||||
|
||||
for _, i := range list {
|
||||
fmt.Println(i.Key, i.Item.Data(), i.Item.DestroyTimestamp())
|
||||
}
|
||||
```
|
|
@ -1,13 +1,21 @@
|
|||
package simplecache
|
||||
package candycache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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
|
||||
|
@ -28,16 +36,14 @@ type Cache struct {
|
|||
cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах
|
||||
}
|
||||
|
||||
// NewCache создает новый экземпляр Cache с интервалом очистки cleanupInterval.
|
||||
// Создает новый экземпляр Cache с интервалом очистки cleanupInterval.
|
||||
// Если cleanupInterval < 0, то кэш не будет очищаться автоматически.
|
||||
func NewCache(cleanupInterval time.Duration) *Cache {
|
||||
func Cacher(cleanupInterval time.Duration) *Cache {
|
||||
cache := &Cache{
|
||||
storage: make(map[string]Item),
|
||||
cleanupInterval: cleanupInterval,
|
||||
}
|
||||
|
||||
// Запускаем Garbage Collector если интервал очистки больше 0
|
||||
// Иначе (если он отрицательный) кэш будет жить до ручного вызова Cleanup
|
||||
if cleanupInterval > 0 {
|
||||
go cache.gc(cleanupInterval)
|
||||
}
|
||||
|
@ -61,7 +67,7 @@ func (c *Cache) Cleanup() {
|
|||
defer c.Unlock()
|
||||
|
||||
for key, item := range c.storage {
|
||||
if item.destroyTimestamp <= time.Now().Unix() {
|
||||
if item.destroyTimestamp <= time.Now().UnixNano() {
|
||||
delete(c.storage, key)
|
||||
}
|
||||
}
|
||||
|
@ -78,18 +84,37 @@ func (c *Cache) Flush() {
|
|||
}
|
||||
|
||||
// Получение элемента из кэша по ключу.
|
||||
func (c *Cache) Get(key string) (interface{}, bool) {
|
||||
func (c *Cache) Get(key string) (interface{}, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
item, found := c.storage[key]
|
||||
|
||||
// Элемент не найден в кэше
|
||||
if !found {
|
||||
return nil, false
|
||||
return nil, errors.New("key not found")
|
||||
}
|
||||
|
||||
return item.data, true
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление элемента по ключу.
|
||||
|
@ -110,12 +135,12 @@ func (c *Cache) Delete(key string) error {
|
|||
// key - ключ.
|
||||
// data - данные.
|
||||
// ttl - время жизни элемента (time to life) в наносекундах.
|
||||
func (c *Cache) Add(key string, data interface{}, ttl time.Duration) {
|
||||
func (c *Cache) Set(key string, data interface{}, ttl time.Duration) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.storage[key] = Item{
|
||||
destroyTimestamp: time.Now().Unix() + int64(ttl.Seconds()),
|
||||
destroyTimestamp: time.Now().UnixNano() + int64(ttl),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
@ -128,15 +153,13 @@ func (c *Cache) Count() int {
|
|||
return len(c.storage)
|
||||
}
|
||||
|
||||
// Печать всех элементов кэша (ключ и время уничтожения).
|
||||
// Возвращает список всех элементов кэша.
|
||||
func (c *Cache) List() []KeyItemPair {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
// Создаем срез для хранения пар ключ-значение
|
||||
items := make([]KeyItemPair, 0, len(c.storage))
|
||||
items := []KeyItemPair{}
|
||||
|
||||
// Заполняем срез парами ключ-значение
|
||||
for key, item := range c.storage {
|
||||
items = append(items, KeyItemPair{Key: key, Item: item})
|
||||
}
|
||||
|
@ -144,6 +167,22 @@ func (c *Cache) List() []KeyItemPair {
|
|||
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()
|
||||
|
@ -157,36 +196,80 @@ func (c *Cache) Size() int {
|
|||
return size
|
||||
}
|
||||
|
||||
// ПОДДЕРЖИВАЕМЫЕ ТИПЫ:
|
||||
// Bool +
|
||||
// Int +
|
||||
// Int8 +
|
||||
// Int16 +
|
||||
// Int32 +
|
||||
// Int64 +
|
||||
// Uint +
|
||||
// Uint8 +
|
||||
// Uint16 +
|
||||
// Uint32 +
|
||||
// Uint64 +
|
||||
// Uintptr +
|
||||
// Float32 +
|
||||
// Float64 +
|
||||
// Complex64 +
|
||||
// Complex128 +
|
||||
// Array +
|
||||
// Func +
|
||||
// Map +
|
||||
// Slice +
|
||||
// String +
|
||||
// Struct
|
||||
// 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()
|
||||
fmt.Println("val =", val, "kind =", kind)
|
||||
size := 0
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array, reflect.String:
|
||||
|
@ -219,3 +302,12 @@ func (i *Item) Data() interface{} {
|
|||
func (i *Item) DestroyTimestamp() int64 {
|
||||
return i.destroyTimestamp
|
||||
}
|
||||
|
||||
// Определяет является ли элемент устаревшим.
|
||||
func (i *Item) IsExpired() bool {
|
||||
if i.destroyTimestamp <= time.Now().UnixNano() {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue