Необходимо реализовать package для распределённого кеша приложения. Интерфейс: middleware с передачей анонимной функции для получения данных:
func Get(key string, result interface{}, executionLimit, expire time.Duration, getter func()) (error)
Данные представляют собой нормально сериализуемые json структуры. Сложность в том, чтобы в нормальных условиях гарантировать уникальность вызова функции в распределённой системе в пределах expire (т.е. все прочие вызовы должны запросить кеш).
Использован банальный memcahed для хранения кеша + схема лока для гарантирования уникальности вычисления значения ключа.
Лок реализован на атомарной оперции add из memcached.
Демонстрационная инсталяция состоит из 3ех memcached серверов и клиента, который эмулирует конкурентный доступ к ним.
Клиенты вычисляют факториалы больших чисел (эмуляция вычисления значений) и складывают их в кеш.
- библиотека клиент gomemcache использует для распределение ключей по кластеру модуль от хеша ключа, поэтому будет сложно добавить в кластер новые ноды. Решение: поискать или написать клиент который использует алгоритм consistent hashing.
- executionLimit учитывает только случай с непопаданием в кеш, она не учитывает случай когда getter Будет выполняться долго. Решение: завернуть в горутину.
- не делались никакие оптимизации кода клиента (аллокации памяти и прочее)
- не учитывается распределение запросов к ключам. Решение: Теоретически схема с consisstent hashing позволит лучше решать эту проблему разбиением горячх участков хеш таблицы на разные ноды.
- не реализована fault tolerance. Решение: все что нужно для него сделать - трекать сломанные ноды и убирать их из списка серверов, их часть таблицы подхватят другие ноды.
- не измерял скорость ответа и пропускную способность, бенчмарки тоже не делал, поскольку клиентского кода минимум и все упирается в производительность memcached (котороый ну ооочень быстрый).
- все хранится в памяти. Но для создания производительного решения этот вариант лучший.
- Полинг memcache если не удалось захватить лок на запись. Решение: поставить таймауты между запросами, либо переделать лок на что-то с pub-sub, redis например.
- нет тестов и код местами не очень.
- Нет единой точки отказа
- Хорошая масштабируемость
- Производительность ограничена производительность одной ноды с memcache (если не брать в расчет сеть)
- Просто лучше чем сложно :)
fig up
в main.go можно поменять несколько констант, кажется все интуитивно понятно:
const (
MaxExecutionTime = time.Minute
ExpireKeyTime = 10 * time.Second
WorkersCount = 10
// for factorial calculation
MaxNum = 100010
MinNum = 100000
)
отключен сторинг значения факториала в memcache (слишком большое число :)), иначе все ресурсы трятяться на перегон данных. Можно включить так:
type Result struct {
Num int `json:"num"`
Value *big.Int `json:"value"`
}
в логах можно будет видеть как часть клиентов начнут вычислять значения, а часть будут ждать значения из кеша, потом кеш прогреется и большая часть данных будет получаться из кеша.