Sidecar на Go: позволь другому заниматься твоими проблемами

от автора

Привет, Хабр!

В распределённых системах каждая служба выполняет свою задачу: одна отвечает за логи, другая за обработку запросов, третья за безопасность. Но не всегда удобно нагружать основной сервис дополнительной логикой. Именно здесь хорошо вписывается Sidecar — отдельный контейнер или процесс, который берёт на себя часть инфраструктурных задач, разгружая основное приложение и позволяя сосредоточиться на главной бизнес-логике.

Сегодня мы рассмотрим реализацию Sidecar на Golang.

Реализация Sidecar на Go

Что мы будем делать: создадим основной микросервис и рядом с ним Sidecar, который будет отвечать за простую задачу — логировать и проксировать запросы.

Начнём с простого HTTP сервера, который будет слушать на порту 8080 и возвращать простое сообщение.

package main  import ( "fmt" "net/http" )  func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from Main Service!") }  func main() { http.HandleFunc("/", handler) fmt.Println("Main Service running on port 8080") http.ListenAndServe(":8080", nil) }

Ничего сложного. Это основной сервис, который принимает HTTP запросы и отвечает на них.

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

package main  import ( "io" "log" "net/http" )  func proxyHandler(w http.ResponseWriter, r *http.Request) { log.Printf("Received request: %s %s", r.Method, r.URL.Path)  resp, err := http.Get("http://localhost:8080" + r.URL.Path) if err != nil { http.Error(w, "Error in Sidecar", http.StatusInternalServerError) return } defer resp.Body.Close()  w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) }  func main() { http.HandleFunc("/", proxyHandler) log.Println("Sidecar Service running on port 8081") http.ListenAndServe(":8081", nil) }

Здесь Sidecar получает запросы на 8081 порт, логирует их и проксирует на основной сервис, который работает на 8080.

Запустим оба сервиса:

go run main_service.go

и в другой консоли:

go run sidecar_service.go

Теперь, если мы отправим HTTP запрос на localhost:8081, мы увидим ответ от основного сервиса и запись в логах Sidecar:

curl localhost:8081 # Output: Hello from Main Service!

Примеры применения Sidecar

Логирование и мониторинг трафика через Sidecar

Предположим, есть микросервис, который обслуживает HTTP-запросы, и нужно добавить логирование всех входящих запросов, но без вмешательства в основной код сервиса. Используем Sidecar для этой задачи.

Основной сервис:

package main  import ( "fmt" "net/http" )  func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from Main Service!") }  func main() { http.HandleFunc("/", handler) fmt.Println("Main Service running on port 8080") http.ListenAndServe(":8080", nil) }

Sidecar для логирования запросов:

package main  import ( "log" "net/http" "io" )  func proxyHandler(w http.ResponseWriter, r *http.Request) { // Логируем запросы log.Printf("Request: %s %s", r.Method, r.URL.Path)  // Прокси запрос на основной сервис resp, err := http.Get("http://localhost:8080" + r.URL.Path) if err != nil { http.Error(w, "Error in Sidecar", http.StatusInternalServerError) return } defer resp.Body.Close()  // Копируем ответ основного сервиса обратно клиенту w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) }  func main() { http.HandleFunc("/", proxyHandler) log.Println("Sidecar running on port 8081") http.ListenAndServe(":8081", nil) }

В этом примере Sidecar работает как прокси между клиентом и основным сервисом, логируя все запросы перед пересылкой их на основной сервис. Запросы отправляются на порт 8081, где работает Sidecar, а затем проксируются на основной сервис, который работает на порту 8080.

Добавление кэширования через Sidecar

Задача: добавить кэширование для часто запрашиваемых данных, но не изменяя логику основного сервиса. Можно юзать Sidecar, который будет кэшировать ответы основного сервиса и выдавать закэшированные данные при повторных запросах.

Основной сервис:

package main  import ( "fmt" "net/http" "time" )  func handler(w http.ResponseWriter, r *http.Request) { // Имитация длительной обработки time.Sleep(2 * time.Second) fmt.Fprintf(w, "Data from Main Service!") }  func main() { http.HandleFunc("/", handler) fmt.Println("Main Service running on port 8080") http.ListenAndServe(":8080", nil) }

Sidecar для кэширования:

package main  import ( "fmt" "io" "log" "net/http" "sync" "time" )  // Простая структура для хранения кэша type Cache struct { data      map[string]string expiry    map[string]time.Time cacheLock sync.RWMutex }  // Инициализация кэша var cache = Cache{ data:   make(map[string]string), expiry: make(map[string]time.Time), }  // Продолжительность хранения данных в кэше const cacheDuration = 10 * time.Second  // Проверка наличия данных в кэше func getFromCache(path string) (string, bool) { cache.cacheLock.RLock() defer cache.cacheLock.RUnlock()  data, found := cache.data[path] if !found || time.Now().After(cache.expiry[path]) { return "", false } return data, true }  // Добавление данных в кэш func saveToCache(path, response string) { cache.cacheLock.Lock() defer cache.cacheLock.Unlock()  cache.data[path] = response cache.expiry[path] = time.Now().Add(cacheDuration) }  // Прокси с кэшированием func proxyHandler(w http.ResponseWriter, r *http.Request) { // Проверяем кэш if cachedData, found := getFromCache(r.URL.Path); found { fmt.Fprintf(w, cachedData) log.Printf("Served from cache: %s", r.URL.Path) return }  // Если в кэше данных нет, делаем запрос на основной сервис resp, err := http.Get("http://localhost:8080" + r.URL.Path) if err != nil { http.Error(w, "Error in Sidecar", http.StatusInternalServerError) return } defer resp.Body.Close()  body, _ := io.ReadAll(resp.Body)  // Сохраняем в кэш saveToCache(r.URL.Path, string(body))  // Возвращаем ответ клиенту w.WriteHeader(resp.StatusCode) w.Write(body) }  func main() { http.HandleFunc("/", proxyHandler) log.Println("Sidecar with Caching running on port 8081") http.ListenAndServe(":8081", nil) }

В этом примере Sidecar кэширует ответы от основного сервиса на 10 секунд. При повторных запросах в течение этого времени клиент получает данные из кэша, а не от основного сервиса.


Заключение

И помните, главное — не перегружать Sidecar и чётко понимать, где заканчиваются задачи основного сервиса и начинаются обязанности Sidecar.

28 октября пройдет открытый урок «Способы разделения микросервисов на компоненты». На практических примерах будет показано, как правильно структурировать микросервисную архитектуру для улучшения масштабируемости и управляемости систем. В том числе, разберем наиболее эффективные подходы к декомпозиции сервисов на основе доменных моделей и данных. Записаться на урок можно на странице курса «Software Architect».


ссылка на оригинал статьи https://habr.com/ru/articles/852642/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *