Привет, Хабр!
В распределённых системах каждая служба выполняет свою задачу: одна отвечает за логи, другая за обработку запросов, третья за безопасность. Но не всегда удобно нагружать основной сервис дополнительной логикой. Именно здесь хорошо вписывается 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/
Добавить комментарий