go meetup smotri+ 23.04.2015
TRANSCRIPT
Использование Goв бэкенде приложения
Смотри+Михаил Салосин
Приложение
Михаил Салосин. Использование Go в бэкенде приложения Смотри+ 2
Что использовали• Go• PostgreSQL• Ruby on Rails – ActiveAdmin, импорт статистики• Python – системные тесты• Memcached• Chef• Zabbix• Graylog2• Slate
3Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Выбор протокола• Данные на клиентах должны обновлять в реальном времени• Данные, синхронизируемые с клиентами, не удаляются• Статистика и составы команд получаются обычными GET запросами• 100 тыс. пользователей
4Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Выбор протокола• Websocket – избыточно• Server-‐Sent Events (SSE) – в самый раз!
5Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Server-‐Sent Events
6Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Content-‐Type: text/event-‐stream
event: Persondata: {“id”: 10, name: ”Илья Ковальчук”}\n\n
Server-‐Sent Events
7Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Server-‐Sent Events
8Михаил Салосин. Использование Go в бэкенде приложения Смотри+
func (h *APIHandler) UpdatesLive(rw http.ResponseWriter, req *http.Request) {updates := make(chan interface{}, 100)h.DataUpdates.Subscribe(updates)defer h.DataUpdates.Unsubscribe(updates)
h.setSseHeaders(rw)h.pingAndFlush(rw, strconv.FormatInt(time.Now().UnixNano(), 10))
for {select {case data, ok := <-updates:
if !ok {return
}
switch v := data.(type) {case int64: // unix time pingh.pingAndFlush(rw, strconv.FormatInt(v, 10))
case reform.Struct:h.writeSseStruct(rw, req, v)rw.(http.Flusher).Flush()
}}
}}
Server-‐Sent Events
9Михаил Салосин. Использование Go в бэкенде приложения Смотри+
func (*APIHandler) setSseHeaders(rw http.ResponseWriter) {rw.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
}
func (h *APIHandler) writeSseStruct(rw http.ResponseWriter, req *http.Request, strreform.Struct) {
b, err := json.Marshal(str)_, err = fmt.Fprint(rw, "event:"+str.View().Name()+"\n")_, err = fmt.Fprint(rw, "data:")_, err = rw.Write(b)_, err = fmt.Fprint(rw, "\n\n")
}
func (*APIHandler) pingAndFlush(rw http.ResponseWriter, data string) {_, err := fmt.Fprintf(rw, ":%s\n\n", data)rw.(http.Flusher).Flush()
}
Механизм отправки обновлений
10Михаил Салосин. Использование Go в бэкенде приложения Смотри+
PostgreSQL: Listen/Notify
11Михаил Салосин. Использование Go в бэкенде приложения Смотри+
CREATE OR REPLACE FUNCTION data_updated() RETURNS trigger AS $$BEGIN
IF TG_OP = 'INSERT' OR OLD <> NEW THENPERFORM pg_notify('data_updates', TG_TABLE_NAME || ' ' ||
NEW.id);END IF;RETURN NULL;
END;$$ LANGUAGE plpgsql;
CREATE TRIGGER on_season_updateAFTER INSERT OR UPDATEON seasonsFOR EACH ROWEXECUTE PROCEDURE data_updated();
PostgreSQL: Listen/Notifyfunc ListenForDataUpdates(config *utils.DbConfig) (*utils.Fanout, error) {
f := utils.NewFanout()l := pq.NewListener("user=smotri-api dbname=smotri", 50*time.Millisecond, 10*time.Second)err := l.Listen("data_updates")err = l.Ping()
go func() {pingTicker := time.Tick(15 * time.Second)for {
var n *pq.Notificationselect {case t := <-pingTicker:un := t.UnixNano()logger.Printf("data_updates: ping %d", un)f.Publish(un)continue
case n = <-l.Notify:if n == nil {
logger.Printf("data_updates: reconnected")continue
}}
...f.Publish(str)
}}return f, nil
}12
Golang: Fan-‐out
13Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Golang: Fan-‐out
14
type Fanout struct {m sync.Mutexs map[chan<- interface{}]struct{}c bool
}
func NewFanout() *Fanout {return &Fanout{s: make(map[chan<- interface{}]struct{})}
}
func (f *Fanout) Connected() {f.m.Lock()f.c = truef.m.Unlock()
}
func (f *Fanout) Disconnected() {f.m.Lock()f.c = falsefor c := range f.s {
delete(f.s, c)close(c)
}f.m.Unlock()
}
Golang: Fan-‐out
15
func (f *Fanout) Subscribe(c chan<- interface{}) {f.m.Lock()defer f.m.Unlock()if !f.c {
panic(errors.New("fanout: not connected"))}f.s[c] = struct{}{}
}
func (f *Fanout) Unsubscribe(c chan<- interface{}) {f.m.Lock()_, ok := f.s[c]if ok {
delete(f.s, c)close(c)
}f.m.Unlock()
}
func (f *Fanout) Publish(v interface{}) {f.m.Lock()for c := range f.s {
select {case c <- v:default:delete(f.s, c)close(c)
}}f.m.Unlock()
}
Инфраструктура
16Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Плюсы Go• Rich http library• Каналы• Race detector• Минимализм и простота
17Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Минусы Go• if err != nil
18Михаил Салосин. Использование Go в бэкенде приложения Смотри+
Спасибо!P.S. WE ARE HIRING!