Going to Failover Как язык влияет на архитектуру и почему отказоустойчивые проекты выбирают Go ITooLabs PaaS Подходы к отказоустойчивости • Выбор первый: Никогда не падать! – – – – – тотальный контроль над всеми аспектами детальное понимание задачи детальное предварительное проектирование верификация кода готовность потратить МНОГО денег (Авионика, космос, системы безопасности АЭС)* * - в теории Подходы к отказоустойчивости • Выбор второй*: Надеяться на лучшее! – – – – – как можно больше компонент с задачей можно разбираться по ходу проект? какой проект? после тестирования проблем не бывает! зато бесплатно “Move fast and break things” * - на самом деле, результат избегания сознательного выбора Подходы к отказоустойчивости • Выбор третий: Принять неизбежность отказов* – – – – – контроль – частичный, но над всем стеком хорошее понимание архитектуры проектирование архитектуры обнаружение и устранение сбоев не бесплатно, но посильно “Let It Crash” (a.k.a. “Welcome to real world”) * - и что-то с ними делать, разумеется! Что означает выбор подхода к отказоустойчивости? – выбор процесса разработки? – выбор инструментов? – выбор процессов развертывания? Все это, и много больше: КУЛЬТУРА Гипотеза лингвистической относительности Сепира-Уорфа: “Язык определяет мышление, то есть, лингвистические категории ограничивают и определяют когнитивные категории” В версии для языков программирования: “Программисты удовлетворены любым языком, которым им пришлось пользоваться, потому что язык диктует им, как они должны думать о программах.” – Пол Грэм (paulgraham.com/avg.html) Лучший язык разработки, если вы собираетесь… Доводить код до Писать много, совершенства: быстро, и будь, что будет: Ada Eiffel OCaml Node.JS PHP Ruby … Примириться с отказами и полюбить их: Erlang Akka (Java / Scala) Go Go: история Хронология Инициатор • • • • • • • • • • стартовал в 2007 открыт в 2009 Go 1 – апрель 2012 Go 1.1 – май 2013 Go 1.2 – декабрь 2013 Go 1.3 – июль 2014 Go 1.4 – декабрь 2014 Go 1.5 – август 2015 Go 1.6 – февраль 2016 Google Авторы • • • • Роб Пайк (UTF-8, Plan 9, Inferno, Limbo) Кен Томсон (UNIX, UTF-8, Plan 9) Роберт Гризмер (Java HotSpot, V8 Code Generation) > 700 участников! Go: первый взгляд 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const n = 1024 const str = "строка" const ( bit0, mask0 uint32 = 1<<iota, 1<<iota - 1; bit1, mask1 uint32 = 1<<iota, 1<<iota - 1; ) // Целая константа // Строковая константа // Объявление блока констант // bit0 = 1, mask0 = 0 // bit1 = 2, mask2 = 1 var x, y *float var z = 1.0 // Объявление с типом // Объявление без указания типа type Point struct { x, y int } // Описание типа (структуры) var p1 Point p2 := Point{ 0, 0 } // Объявление переменной типа Point // Объявление с инициализацией var pp1 *Point = new(Point) pp2 := &Point{} // Объявление переменной – указателя на Point // Объявление указателя с указателем объекта points := []T{ Point{1,1}, Point{2,2} } // Объявление и инициализация массива pointsMap := map[string]Point{ "start": Point{0,0}, } // Объявление и инициализации ассоциативного массива Go: Hello, World 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package main import ( "os" "flag" ) var word = flag.String("w", "world", "word") var noEOL = flag.Bool("n", false, `no \n`) func main() { flag.Parse() s := "Hello, " + *word if !*noEOL { s += "\n" } os.Stdout.WriteString(s) } • • • • • • • C-подобный синтаксис Точка с запятой необязательна Скобки () в управляющих структурах необязательны Фигурные скобки {} обязательны Код организован в пакеты Типизация статическая Тип выводится автоматически (в большинстве случаев) growler:~$ go run hello.go -w "Go World" Hello, Go World growler:~$ go build hello.go growler:~$ ./hello Hello, world growler:~$ Go: Hello, World (over HTTP) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package main import ( "net/http" "encoding/json" stats "github.com/c9s/goprocinfo/linux" ) func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/json") stats, _ := stats.ReadLoadAvg("/proc/loadavg") resp, _ := json.MarshalIndent(stats, "", " ") w.Write(resp) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } • • • • Функция выступает параметром import ссылается сразу на репозиторий github http и json в штатной библиотеке Есть интроспeкция (reflection, используется в encoding/json) growler:~$ export GOPATH=`pwd` growler:~$ go get github.com/c9s/goprocinfo/linux growler:~$ go build test.go growler:~$ ./test & [1] 18110 growler:~$ curl -s http://localhost:8080 { "last1min": 0.66, "last5min": 0.58, "last15min": 0.65, "process_running": 1, "process_total": 2038, "last_pid": 18121 } growler:~$ kill %1 [1]+ Exit 2 ./test growler:~$ Go: методы и интерфейсы 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Rect struct { xl, yl, xu, yu float32 } type Circ struct { x, y, r float32 } func (r *Rect) Square() float32 { return math.Pow((c.xu - c.xl), 2) + math.Pow((c.yu - c.yl), 2) } func (c *Rect) Square() float32 { return math.Pi * math.Pow(c.r, 2) } // Объявление метода type Shape interface { func Square() float32 } // Объявление интерфейса var s1 Shape = &Rect{} var s2 Shape = &Circ{} // Объявление и инициализация переменной типа Shape // -”- var any interface{} = nil // Объявление переменной, которая может ссылаться // на любой объект // type assertion (множественное присваивание!) v, ok := any.(T) // Объявление метода Go: goroutines 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import "net" func serve(conn net.Conn) { var ( buf = make([]byte, 1024); r int; err error ) defer conn.Close() for { if r, err = conn.Read(buf); err != nil { break } if _, err = conn.Write(buf[0:r]); err != nil { break } } } func main() { sock, _ := net.Listen("tcp", ":5000") for { conn, _ := sock.Accept(); go serve(conn) } } • • • • • Легковесные потоки выполнения (<2Kb) Ничтожные затраты на создание Собственный планировщик Передача управления: • Выражение go • Блокирующий вызов (I/O) • Сборка мусора • Операции с каналами Параллельное выполнение в несколько потоков (по умолчанию число потоков == числу ядер CPU) growler:~$ go run test.go & [1] 14694 growler:~$ echo Hello | nc localhost 5000 Hello growler:~$ kill %1 [1]+ Exit 2 go run test.go growler:~ $ Go: channels 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( "net/http" "strconv" ) var ch chan int func count() { i := 0 for { ch <- i; i += 1 } } func handler(w http.ResponseWriter, r *http.Request) { i := <- ch w.Write([]byte(strconv.Itoa(i) + "\n")) } func main() { ch = make(chan int); go count() http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } • • • • • • Канал – средство коммуникации между goroutines Однонаправленная очередь в N элементов По умолчанию N == 1 Канал может быть параметром или членом структуры Канал является итератором (for i := range ch) Канал можно передать через канал! growler:~$ go run test.go & [1] 23469 growler:~$ ( while [ $(curl -s http://localhost:8080) -lt 1000 ]; do true; done ) & [2] 23476 growler:~$ ( while [ $(curl -s http://localhost:8080) -lt 1000 ]; do true; done ) & [3] 23515 growler:~$ curl -s http://localhost:8080 1002 [2]- Done [3]+ Done growler:~$ Go: select 1 func server(service chan *request, quit chan bool) { 2 for { 3 select { 4 case req := <-service: 5 go process(req) 6 case <-quit: 7 break 8 } 9 } 10 } Go реализует модель CSP (Communicating Sequential Process) Оператор select похож на оператор switch и позволяет выбрать первое пришедшее сообщение из нескольких каналов. Go: C API (CGO) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main /* #ifdef __APPLE__ #include <sys/types.h> #include <pwd.h> #include <uuid/uuid.h> #else #include <sys/types.h> #include <stdlib.h> #endif #include <pwd.h> */ import "C" import "fmt" CGO дает возможность использовать вызовы C непосредственно в коде и подключать сторонние библиотеки (сторонний C код может заблокировать весь поток!) func GetName(uid uint32) string { cpw := C.getpwuid(C.uid_t(uid)) return C.GoString(cpw.pw_name) } func main() { fmt.Printf("%s\n", GetName(2000)) } growler:~$ go run test.go growler growler:~$ Go: инструменты • • • • • • • • • • Одна команда (go) Сборка: go build <package> Тест: go test <package> Запуск: go run <package> Обновление зависимостей: go get <package> Форматирование исходников: go fmt Документация: go doc Профайлинг: go tool pprof Генератор парсеров: go tool yacc … и много других Go: IDE • IntelliJ IDEA CE 15 + Go Plugin • Eclipse + Go Plugin • NetBeans + Go Plugin (GoWorks) • VIM • Emacs • Atom/Sublime/Notepad++/… IntelliJ IDEA CE 15 + Go Plugin – лучшее Go IDE на текущий момент (верьте, я пробовал все!) Go vs Node.js Go Node.js 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 • • • import ( "fmt"; "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } Компилируется (пусть и быстро) Много потоков Усложнение логики не усложняет код: просто пишем в ResponseWriter! • • • var http = require('http'); var handler = function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); } var webServer = http.createServer(handler) webServer.listen(8080, '127.0.0.1'); Интерпретатор (запустили – сразу работает) Один поток (чтобы было много – нужен cluster) Усложнение логики? callbacks? promise? Q? async.js? yield? Go: эксплуатация • Статическая сборка – нет run-time зависимостей • Пакет, контейнер – любой способ развертывания (single binary – это удобно) • Сборщик мусора – с каждым релизом все лучше, но его надо иметь в виду – (если вы работали с JVM, то знаете, что делать!) • Когда код перестает быть узким местом, находятся все остальные узкие места! Go: библиотеки Go: кто использует? Go: кто использует? • • • • • Google: – YouTube – dl.google.com – … и многое другое Twitter (https://blog.twitter.com/2015/handling-fivebillion-sessions-a-day-in-real-time) Dropbox (https://blogs.dropbox.com/tech/2014/07/opensourcing-our-go-libraries/) Gogs (https://gogs.io) Koding, Node.js->Golang (https://www.quora.com/Why-did-Koding-switchfrom-Node-js-to-Go) Источник: https://github.com/golang/go/wiki/GoUsers • • • • • • • Parse, RoR->Golang (http://blog.parse.com/learn/how-we-moved-our-apifrom-ruby-to-go-and-saved-our-sanity/) Medium (https://medium.com/medium-eng/howmedium-goes-social-b7dbefa6d413) Rackspace (https://github.com/rackspace/rack) Baidu (https://twitter.com/jbuberel/status/61777622943798 0673) Tidb (https://github.com/pingcap/tidb) ITooLabs (https://itoolabs.com) ... и еще многие Go: сообщество Go: недостатки? Множество! • Нет parametric polymorphysm/templates/generics (только специальные операции для работы с array/map) • Провоцирует cut’n’paste • Жестко диктует правила (включая форматирование!) • Нет исключений (есть panic/recover, но его плохо использовать для управления) • Очень (слишком!) простой синтаксис • Нет места фантазии! (на самом деле, есть) • Нет отладчика (появился delve, им можно, с оговорками, пользоваться) • Внутренний планировщик не вытесняет, возможно заблокировать весь поток (особенно с Cgo) Как Go помогает принять культуру работы с отказами? • • • • • быстрая (БЫСТРАЯ) сборка заставляет все делать в Go (контроль) развертывание одним бинарником конкурентность естественное разделение на функциональные модули (a.k.a. микросервисы) • быстрый старт/стоп процесса Есть ли еще причины, почему Go? • • • • • эффективная утилизация multicore CPU? очень простой код? быстрое развитие? могучее сообщество? поддержка google? Да, всё это. Но не только. Всё проще. Go РАБОТАЕТ. Go – предельно практичный язык для инженеров* *занятых разработкой веб-сервисов и распределенных систем ITooLabs Centrex • • • • • • • • Динамический all-active кластер Равномерное распределение нагрузки (hash ring) Protobuf для внутрикластерных коммуникаций SIP/HTTP/WebSocket/… для внешних Медиа-процессор (ok, это на C++) Встроенный интерпретатор JavaScript (не node и вообще не V8!) Генератор нагрузки/тестировщик вызовов! DevOps-friendly: installapp default git ”git@git.site:app/app” 0a3124f • 1 год с принятия решения до первого развертывания! Применение: голосовая почта на 70 mln абонентов • • • • • • • • • • Мобильный оператор в Индонезии, 70 mln абонентов 1800 вызовов в секунду / 50 000 одновременных диалогов (6 480 000 BHCA!) на тесте 50 000 одновременных диалогов на тесте ~280 вызовов в секунду дневная текущая нагрузка (~1 000 000 BHCA, ~36 mln абонентов, +2 mln каждую неделю) 100 000 000 вызовов в неделю 600 вызовов в секунду дневная целевая нагрузка 16 серверов (7 AS на Go, 9 MG на C++; можно было меньше!) Ceph в качестве хранилища записей (тройная репликация) Выдерживает системный сбой до 2 серверов Rolling updates Довольны ли мы своим выбором? Да, безусловно. Go не только отлично подходит для отказоустойчивых проектов. Go помогает придумывать отказоустойчивые архитектуры. ВОПРОСЫ? Алексей Найденов alexey.naidyonov@itoolabs.com fb.com/alexey.naidyonov