Консольные приложения на go
DESCRIPTION
Доклад с Golang Meetup 24.07.2014TRANSCRIPT
Консольные приложения на Go
Андрей Смирнов, Go Meetup July 2014
aptly• Система управления репозиториями Debian-пакетов
• Зеркалирование репозиториев, управление репозиториями своих пакетов
• Создание snapshotов репозиториев, слияние, перемещение пакетов
• Публикация как Debian-репозиториев
Почему Go?• Fun!
• Большие объемы метаданных (30Мб/архитектура)
• Статическая компиляция
• Отличная HTTP-библиотека
• Там нет genericов? (контейнеры)
Как вышло
• Отличный набор библиотек (всех!)
• Высокая производительность
• Аккуратно работать с памятью
• Язык позволяет правильно структурировать программу
Параллельныйdownload
Задача
• Скачивать файлы в несколько потоков (асинхронно)
• Скачивать файл и ожидать результата
Скачиватель
channel: queue
Worker 1(goroutine)
Worker 2(goroutine)
Worker 3(goroutine)
Worker 4(goroutine)
Возврат результата
func (d *Downloader) Download(url, destination string) error !
Возврат результата
func (d *Downloader) Download(url, destination string) error !
!func (d *Downloader) Download(url, destination string) <-chan error !!
Возврат результата
func (d *Downloader) Download(url, destination string) error !
!func (d *Downloader) Download(url, destination string) <-chan error !!
func (d *Downloader) Download(url, destination string, result chan<- error)
Синхронный вариант
func (d *Downloader) DownloadWait(url, destination string) error { ch := make(chan error) d.Download(url, destination, ch) return <-ch }
Скачивание по спискуlist := ... !results := make(chan error, len(list)) !for _, l := range list { d.Download(l.url, l.destination, results) } !for i := 0; i < len(list); i++ { err := <-results if err != nil { fmt.Printf("Download error: %s\n") } }
Приостановкаfunc NewDownloader(N int) *Downloader {!! return &Downloader{!! ! queue: make(chan *Task, 1000),!! ! pause: make(chan struct{}),!! ! unpause: make(chan struct{}),!! ! threads: N,!! }!}!!func (downloader *Downloader) Pause() {!! for i := 0; i < downloader.threads; i++ {!! ! downloader.pause <- struct{}{}!! }!}!!!! for i := 0; i < downloader.threads; i++ {!! ! go func() {!! ! ! for {!! ! ! ! select {!! ! ! ! case <-downloader.pause:!! ! ! ! ! <-downloader.unpause!! ! ! ! case task := <-downloader.queue:!! ! ! ! ! downloader.handleTask(task)!! ! ! ! }!! ! ! }!! ! }()!! }!
Прогресс
Прогресс
Архитектура
ProgressBaratomic N 200 msredraw
goroutine pb
goroutine main
updateupdate
update
Архитектура
ProgressBaratomic N 200 msredraw
goroutine pb
goroutine main
updateupdate
update
channel
goroutine console
write to stdout
io.MultiWriter!resp, err = http.Get(url) outfile, err = os.Create(destination) _, err = io.Copy(outfile, resp.Body)
io.MultiWriter!resp, err = http.Get(url) outfile, err = os.Create(destination) _, err = io.Copy(outfile, resp.Body)
resp, err = http.Get(task.url) outfile, err = os.Create(temppath) !_, err = io.Copy( io.MultiWriter(outfile, progressbar), resp.Body)
Writer
type Writer interface { Write(p []byte) (n int, err error) } !func (p* ProgressBar) Increment(n int) { ... } !func (p* ProgressBar) Write(p []byte) (n int, err error) { n = len(p) p.Increment(n) return }
CheckSummertype ChecksumWriter struct { hashes []hash.Hash } !func NewChecksumWriter() *ChecksumWriter { return &ChecksumWriter{ hashes: []hash.Hash{md5.New(), sha1.New(), sha256.New()}, } } !func (c *ChecksumWriter) Write(p []byte) (n int, err error) { for _, h := range c.hashes { h.Write(p) } ! return len(p), nil }
Ограничение скорости• Задача: ограничить общую скорость скачивания
flow = flowcontrol.NewWriter(downloadLimit) !... !_, err = io.Copy( io.MutliWriter(outfile, flow, progress, checksummer), resp)
Вложенные команды• Или история и о первом pull request в aptly
• Что получилось:
• aptly mirror create …
• aptly mirror list
• aptly snapshot create from mirror …
commander
• Простой способ создавать подкоманды
• История про первый pull-request или версионирование библиотек Go
• Большое количество патчей
• Альтернатива (лучше!) - cobra
GC
• mark-and-sweep
• precise (1.3), parallel, concurrent sweep (1.3)
aptly• Обновление зеркала репозитория
• Парсинг мета-информации о пакетах, обработка, сохранение в БД
• 40Мб/архитектура
• При создании зеркала используется 800Мб памяти! 😭
Анализ• runtime-статистика об использовании кучи
• runtime.ReadMemStats():
• HeapSys
• HeapAlloc
• HeapIdle
Что делать?
• Долго работающие функции: освобождать ненужные объекты (x = nil)
• Повторное использование объектов вместо выделения новых
Было
func (p *Package) Encode() []byte {! var buf bytes.Buffer!! encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})! encoder.Encode(p)!! return buf.Bytes()!}!
Сталоvar (!! encodeBuf bytes.Buffer!! codecHandle = &codec.MsgpackHandle{}!)!!func (p *Package) Encode() []byte {!! encodeBuf.Reset()!!! encoder := codec.NewEncoder(&encodeBuf, codecHandle)!! encoder.Encode(p)!!! return encodeBuf.Bytes()!}!
А что дальше?
• Профилируем программу по памяти и процессору
• (Много думаем)
• Придумываем решение!
Сборка и релиз
• aptly - кроссплатформенный инструмент
• Статическая компиляция
• go cross compiling
• Запуск системных тестов
Решение “в лоб”
• Виртуалки CentOS/Debian/FreeBSD/32/64
• Сборка: установим go с помощью gvm
• Запуск системных тестов на каждой платформе
• Распространение: просто скачать исполняемый файл
Создание пакетов ОС
• Сложно упаковать “корректно” со всеми зависимостями
• “Бинарные” пакеты: fpm
Контейнеры• Фобия? (Страх до начала разработки)
• Slices + package sort = 90% решений
• В реальность модуль из 141 строки с общими операциями над []string
• Сложные алгоритмы не выражаются в терминах genericов
• Шанс на микрооптимизацию
Вопросы?
• @smira
• Skype: smirnov.andrey
• http://smira.ru/
Ссылки• aptly: github.com/smira/aptly
• ProgressBar: github.com/cheggaaa/pb
• flowcontrol: code.google.com/p/mxk/go1/flowcontrol
• commander: github.com/gonuts/commander
• cobra: github.com/spf13/cobra