go meetup smotri+ 23.04.2015

19
Использование Go в бэкенде приложения Смотри+ Михаил Салосин

Upload: mikhail-salosin

Post on 27-Jul-2015

26.855 views

Category:

Software


4 download

TRANSCRIPT

Page 1: Go meetup smotri+ 23.04.2015

Использование  Goв  бэкенде приложения  

Смотри+Михаил  Салосин

Page 2: Go meetup smotri+ 23.04.2015

Приложение

Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+ 2

Page 3: Go meetup smotri+ 23.04.2015

Что  использовали• Go• PostgreSQL• Ruby on  Rails  – ActiveAdmin,  импорт  статистики• Python  – системные  тесты• Memcached• Chef• Zabbix• Graylog2• Slate

3Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 4: Go meetup smotri+ 23.04.2015

Выбор  протокола• Данные  на  клиентах  должны  обновлять  в  реальном  времени• Данные,  синхронизируемые  с  клиентами,  не  удаляются• Статистика  и  составы  команд  получаются  обычными  GET  запросами• 100  тыс.  пользователей

4Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 5: Go meetup smotri+ 23.04.2015

Выбор  протокола• Websocket – избыточно• Server-­‐Sent  Events  (SSE)  – в  самый  раз!

5Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 6: Go meetup smotri+ 23.04.2015

Server-­‐Sent  Events

6Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Content-­‐Type:  text/event-­‐stream

event:  Persondata:  {“id”:  10,  name:  ”Илья  Ковальчук”}\n\n

Page 7: Go meetup smotri+ 23.04.2015

Server-­‐Sent  Events

7Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 8: Go meetup smotri+ 23.04.2015

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()

}}

}}

Page 9: Go meetup smotri+ 23.04.2015

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()

}

Page 10: Go meetup smotri+ 23.04.2015

Механизм  отправки  обновлений

10Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 11: Go meetup smotri+ 23.04.2015

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();

Page 12: Go meetup smotri+ 23.04.2015

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

Page 13: Go meetup smotri+ 23.04.2015

Golang:  Fan-­‐out

13Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 14: Go meetup smotri+ 23.04.2015

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()

}

Page 15: Go meetup smotri+ 23.04.2015

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()

}

Page 16: Go meetup smotri+ 23.04.2015

Инфраструктура

16Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 17: Go meetup smotri+ 23.04.2015

Плюсы  Go• Rich  http  library• Каналы• Race  detector• Минимализм  и  простота

17Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 18: Go meetup smotri+ 23.04.2015

Минусы  Go• if  err  !=  nil

18Михаил  Салосин.   Использование  Go в  бэкенде приложения   Смотри+

Page 19: Go meetup smotri+ 23.04.2015

Спасибо!P.S.  WE  ARE  HIRING!