Going to Failover Как язык влияет на архитектуру и почему отказоустойчивые проекты

advertisement
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
Download