Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Правильная организация рутины

Правильная организация рутины

by Александр Кирюхин

Iskander (Alex) Sharipov

June 29, 2019
Tweet

More Decks by Iskander (Alex) Sharipov

Other Decks in Programming

Transcript

  1. Содержание Разработка микросервисов на потоке ✦ Проблема копипаста ✦ Проблема

    архитектуры ✦ Проблема владения кодовой базой Путь нашей команды ✦ Первые шаги ✦ Управление корутинами на WaitGroup ✦ ErrGroup
  2. Содержание Разработка микросервисов на потоке ✦ Проблема копипаста ✦ Проблема

    архитектуры ✦ Проблема владения кодовой базой Путь нашей команды ✦ Первые шаги ✦ Управление корутинами на WaitGroup ✦ ErrGroup Итоговое решение ✦ Rutina как «скелет» приложения ✦ Политики окончания ✦ Rutina для пакетных задач ✦ Примеси
  3. Проблема копипаста большого количества кода При этом, в условиях активного

    развития, подходы, а значит и код, могут активно изменяться или даже заменяться другими решениями. Эту проблему решили тривиальным образом, создав общую библиотеку собственных пакетов для работы с БД, очередями и т.п. и на ней останавливаться мы не будем. Самая первая и самая очевидная проблема – копипаст общих частей из проекта в проект.
  4. Проблема общей архитектуры Решить это общими пакетами уже не получится,

    а на каждое изменение приходится либо копить техдолг, либо тратить время на рефакторинг. Этого никто не хочет. Активное развитие заставляет изменяться не только коду, но и самой архитектуре микросервисов.
  5. Проблема владения кодовой базой Вне зависимости от размера команды, при

    микросервисном подходе становится актуальна проблема владения кодом всех разработчиков.
  6. Проблема владения кодовой базой Вне зависимости от размера команды, при

    микросервисном подходе становится актуальна проблема владения кодом всех разработчиков. Приложения должны быть устроены так, чтобы любой разработчик мог быстро: ✦ Разобраться в их устройстве ✦ Найти интересующее место бизнес-логики, не отвлекаясь на сервисный код ✦ Выполнить поставленную задачу
  7. Первые шаги - архитектура «в лоб» func main() { app

    := App{ Logger: logger, Config: &cfg, Service: service, DB: db, } msgs := app.Service.GetMessages() for { msg := <-msgs err, catch := handler(app, msg) if err != nil { app.Logger.Error("cannot handle event", zap.Error(err)) if catch { app.Service.Catch(msg, err) } } } } func handler(app *App, msg *babex.Message) (error) { ... полезная работа ... }
  8. Первые шаги - архитектура «в лоб» func main() { app

    := App{ Logger: logger, Config: &cfg, Service: service, DB: db, } msgs := app.Service.GetMessages() for { msg := <-msgs err, catch := handler(app, msg) if err != nil { app.Logger.Error("cannot handle event", zap.Error(err)) if catch { app.Service.Catch(msg, err) } } } } func handler(app *App, msg *babex.Message) (error) { ... полезная работа ... } Хоть и просто, но много проблем
  9. func main() { app := App{ Logger: logger, Config: &cfg,

    Service: service, DB: db, } msgs := app.Service.GetMessages() wg := sync.WaitGroup{} ctx, cancel := context.WithCancel(context.Background()) // Процесс обработки сообщений wg.Add(1) go func () { defer wg.Done() for { select { case msg := <-msgs: if err := handler(app, msg); err != nil { cancel() return err } case <-ctx.Done(): return } } } // Процесс отслеживания сигналов ОС wg.Add(1) go func () { defer wg.Done() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) select { case <- ctx.Done: return case <- sig: cancel() return } } // Ждем завершения всех горутин wg.Wait() // Завершаем работу... app.Service.Stop() app.Logger.Sync() } func handler(app *App, msg *babex.Message) error { ... полезная работа ... } Управление с помощью WaitGroup
  10. func main() { app := App{ Logger: logger, Config: &cfg,

    Service: service, DB: db, } msgs := app.Service.GetMessages() ctx := context.Background() wg := errgroup.WithContext(ctx) // Процесс обработки сообщений wg.Go(func () error { for { select { case msg := <-msgs: if err := handler(app, msg); err != nil { return err } case <-ctx.Done(): return nil } } }) // Процесс отслеживания сигналов ОС wg.Go(func () error { defer wg.Done() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) select { case <- ctx.Done: return nil case <- sig: return errors.New("os signal") } }) // Ждем завершения всех горутин if err := wg.Wait();err != nil { app.Logger.Error("app error", zap.Error(err)) } // Завершаем работу... app.Service.Stop() app.Logger.Sync() } func handler(app *App, msg *babex.Message) error { ... полезная работа ... } Управление с помощью ErrGroup
  11. Итоговый шаблон приложения func main() { app := App{ Logger:

    logger, Config: &cfg, Service: service, DB: db, } msgs := app.Service.GetMessages() r := rutina.New() // Процесс обработки сообщений r.Go(func (ctx context.Context) error { msg := <- msgs return handler(app, msg) }, rutina.RestartIfDone, rutina.ShutdownIfError) // Процесс отслеживания сигналов ОС r.ListenOsSignals() // Ждем завершения всех горутин if err := r.Wait();err != nil { app.Logger.Error("app error", zap.Error(err)) } // Завершаем работу... app.Service.Stop() app.Logger.Sync() } func handler(app *App, msg *babex.Message) error { ... полезная работа ... }
  12. Политики окончания Rutina поддерживает политики действий при завершении контролируемой горутины

    без ошибок: ✦ DoNothingOnDone – не производится дополнительных действий ✦ RestartOnDone – горутина перезапускается с теми же аргументами ✦ ShutdownOnDone – закрывается контекст, тем самым отправляется сигнал на завершение другим горутинам
  13. Политики окончания Rutina поддерживает политики действий при завершении контролируемой горутины

    без ошибок: ✦ DoNothingOnDone – не производится дополнительных действий ✦ RestartOnDone – горутина перезапускается с теми же аргументами ✦ ShutdownOnDone – закрывается контекст, тем самым отправляется сигнал на завершение другим горутинам Политики при завершении горутины с ошибкой: ✦ DoNothingOnError – не производится дополнительных действий ✦ RestartOnError – горутина перезапускается с теми же аргументами ✦ ShutdownOnError – закрывается контекст, тем самым отправляется сигнал на завершение другим горутинам
  14. Политики окончания Rutina поддерживает политики действий при завершении контролируемой горутины

    без ошибок: ✦ DoNothingOnDone – не производится дополнительных действий ✦ RestartOnDone – горутина перезапускается с теми же аргументами ✦ ShutdownOnDone – закрывается контекст, тем самым отправляется сигнал на завершение другим горутинам Политики при завершении горутины с ошибкой: ✦ DoNothingOnError – не производится дополнительных действий ✦ RestartOnError – горутина перезапускается с теми же аргументами ✦ ShutdownOnError – закрывается контекст, тем самым отправляется сигнал на завершение другим горутинам Поведение по умолчанию совпадает с таковым у ErrGroup: ✦ ShutdownOnDone ✦ ShutdownOnError
  15. Обработка пакетных задач r := rutina.New() tasks := []string{ "http://url1.ru/....",

    "http://url2.ru/....", "http://url3.ru/....", ... "http://urlN.ru/....", } for _, task := range tasks { task := task r.Go(func (ctx context.Context) error { // Некий парсинг со страницы return worker(ctx, task) }, rutina.RestartIfError) } r.Wait()
  16. Обработка пакетных задач r := rutina.New() tasks := []string{ "http://url1.ru/....",

    "http://url2.ru/....", "http://url3.ru/....", ... "http://urlN.ru/....", } for _, task := range tasks { task := task r.Go(func (ctx context.Context) error { // Некий парсинг со страницы return worker(ctx, task) }, rutina.RestartIfError) } r.Wait() Но что делать, если мы хотим залогировать или как-то обработать ошибки, а не заглушить их?
  17. Обработка пакетных задач r := rutina.New(rutina.WithErrChan()) tasks := []string{ "http://url1.ru/....",

    "http://url2.ru/....", "http://url3.ru/....", ... "http://urlN.ru/....", } for _, task := range tasks { task := task r.Go(func (ctx context.Context) error { // Некий парсинг со страницы return worker(ctx, task) }, rutina.RestartIfError) } r.Go(func (ctx context.Context) error { select { case <-ctx.Done: return nil case err := <-r.Errors(): log.Error(err) } }, rutina.RestartIfDone) r.Wait()
  18. Миксины Rutina поддерживает следующие миксины, меняющие поведение: ✦ WithErrChan() –

    создает канал с ошибками горутин с политиками DoNothingOnError, RestartOnError ✦ WithStdLogger и WithLogger(logger log.Logger) – добавляет логирование стандартным или собственным логгером ✦ WithContext(ctx context.Context) – заставляет Rutina использовать внешний контекст, например, контекст запроса или родительского экземпляра Rutina ✦ WithLifecycleListener(func (event rutina.Event, rid int)) – позволяет повесить функцию-слушатель на внутренние события Rutina ✦ WithListenOsSignals() – автоматически слушает сигналы ОС, нет необходимости вручную запускать r.ListenOsSignals()