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

Kate

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

 
Сверху