Ответ
Масштабируемость — это проектирование системы с учетом роста нагрузки. В моем опыте ключевыми являются декомпозиция, асинхронность и правильный выбор хранилищ данных.
1. Декомпозиция на сервисы (не обязательно микросервисы). Я разделяю монолит на логические модули с четкими контрактами. Например, выделяю сервис аутентификации и сервис отчетов, которые могут масштабироваться независимо.
// Сервис заказов (Order Service) - отдельный процесс/контейнер
package main
import (
"encoding/json"
"net/http"
"github.com/nats-io/nats.go"
)
var nc *nats.Conn
func main() {
var err error
nc, err = nats.Connect(nats.DefaultURL)
// ...
http.HandleFunc("/order", createOrderHandler)
http.ListenAndServe(":8081", nil)
}
func createOrderHandler(w http.ResponseWriter, r *http.Request) {
var order Order
json.NewDecoder(r.Body).Decode(&order)
// 1. Сохраняем заказ в своей БД
saveOrder(order)
// 2. Асинхронно уведомляем другие сервисы через событие
event, _ := json.Marshal(OrderCreatedEvent{OrderID: order.ID})
nc.Publish("order.created", event) // Неблокирующая отправка
w.WriteHeader(http.StatusCreated)
}
2. Асинхронная коммуникация через события. Использую брокеры сообщений (NATS, Kafka) для слабой связанности. Это позволяет сервисам обрабатывать события в своем темпе.
// Сервис нотификаций (Notification Service) - подписывается на события
nc.Subscribe("order.created", func(m *nats.Msg) {
var event OrderCreatedEvent
json.Unmarshal(m.Data, &event)
// Отправляем email пользователю
sendOrderConfirmationEmail(event.OrderID)
// Масштабируем этот сервис, запуская больше инстансов
})
3. Stateless-сервисы и внешнее состояние. Все сервисы не хранят состояние в памяти между запросами. Сессии выношу в Redis Cluster. Это позволяет легко добавлять инстансы за балансировщиком нагрузки.
4. Стратегии масштабирования баз данных:
- Чтение/Запись: Использую репликацию. Запись идет на мастер, чтение — на несколько реплик. В коде это разделение через разные connection strings.
- Шардинг: Для очень больших данных (например, логов событий) применяю шардинг по ключу (user_id, date). Использую специализированные БД вроде ClickHouse для аналитики.
- Кэширование: Активно использую Redis для кэширования результатов тяжелых запросов и часто читаемых данных (каталог товаров).
5. Горизонтальное масштабирование инфраструктуры. Все сервисы упакованы в Docker-контейнеры и оркестрируются через Kubernetes. HPA (Horizontal Pod Autoscaler) автоматически добавляет поды при росте CPU/памяти или кастомных метрик из Prometheus (например, длины очереди в RabbitMQ).
6. Устойчивость к сбоям (Resilience). Масштабируемость бесполезна, если система ломается под нагрузкой. Я внедряю паттерны: Circuit Breaker (Hystrix/go-breaker), Retry с экспоненциальной задержкой и Bulkheads для изоляции ресурсов.