Go: GroupCache

В большинстве web-приложений встречаются медленные операции (SQL запросы, обращение к внешним ресурсам, дисковые операции, операции генерации страниц и т.д.), которые могут значительно затормозить систему особенно при большой интенсивности запросов. Для оптимизации таких операций и применяется кэширование данных. Оно позволяет выполнять меньше таких операций, а большинству пользователей показывать заранее подготовленные данные.

Наиболее популярная технология кэширования для Web приложений - это MemCache. Но сегодня мы поговорим не о ней. А о разработанной в Google кэширующей библиотеке GroupCache, которая была написана на Go и используется в Google для замены MemCache в таких сервисах, как dl.google.com, Google Fiber, Google Monitoring и др.

К характерным особенностям GroupCache стоит отнести:

  • Написан на Go. Работы по портированию для других языков ведутся, но на данный момент они не завершены.
  • Из коробки поддерживается шардирование по ключу между пирами
  • В отличие от MemCache значительно снижена вероятность потери данных, благодаря механизму обработки данных, который реплицирует данных на другие пиры. А также в случае отсутствие данных в кэше способен прочитать данные из первоисточника.
  • Для запуска не требуется отдельный сервер, который требует поддержки. GroupCache - это встраиваемая библиотека
  • Отсутствует версионирование значений, т.е. если в кэш попал ключ K со значением V, то все время жизни K в кэше у него будет значение равное V. Также отсутствуют операции явного удаления значений из кэша, операция задания времени жизни. Последнюю проблему легко обойти приписывая к ключам метки периода.
  • Поддерживается заркалирование самых горячих элементов кэша между всеми процессами

Рассмотрим на примере, как можно использовать GroupCache. Пусть у нас есть некоторый web-сервер, который отдает небольшие файлы с диска. В простейшем случае функция получения такого файла будет иметь вид:

import "io/ioutil"

func GetFile( filename string ) ( []byte, error ) {
  data, err := ioutil.ReadFile( filename )
  if err != nil {
      return nil, err
  }
  return data, nil
}

Если предположить, что файлов много и к некоторым из них достаточно часто происходит обращение, то можно применить кэширование для оптимизации повторяющихся запросов. Для подключения GroupCache нужно импортировать библиотеку для работы с ним

go get "github.com/golang/groupcache"

После чего импортировать в программу

import "github.com/golang/groupcache"

Дальше для того мы должны создать группу (назовем ее files, размером 100MB)

fCache := groupcache.NewGroup( "files", 10*1024*1024, groupcache.GetterFunc(getSource) )

Здесь getSource функция получения данных из первоисточника (в нашем случае файловой системы) и передачи его в GroupCache.

func getSource(ctx groupcache.Context, key string, dst groupcache.Sink) error {
  data, err := ioutil.ReadFile( key )
  if err != nil {
    return err
  }
  dst.SetBytes(data)
  return nil
}

Функция принимает на вход ctx - контекст вызывающего модуля, который может быть nil; key - ключ кэша, который равен в нашем случае имени файла и объект dst, с помощью которого данные передаются в кэш. В случае неудачной загрузки данных функция возвращает объект ошибку (error), в случае успеха - nil.

После этого наша функция получения файла через кэш перепишется следующим образом:

func GetFile( filename string ) ( []byte, error ) {
    var data []byte
    if fCache.Get( nil, filename , groupcache.AllocatingByteSliceSink( &data ) ) != nil {
      return nil, errors.New( "no data" )
    }
    return data, nil
}

Все достаточно просто. Но это только случай когда у нас один инстанс. В случае нескольких инстансов нужно дополнительно определить пиры и идентифицировать себя.

p := []string {"http://127.0.0.1:8080","http://127.0.0.1:8081"}
pool := groupcache.NewHTTPPool(p[0])
pool.Set(p...)

Первый элемент в массиве p - это текущий пир. Также надо понимать, что в этом случае доступ к нашей функции должен осуществляться через обработчик HTTP-вызова.

 

14.07.2017









 
архив

подписка