Разберем каждый этап масштабирования с реальными примерами и псевдокодом. Погнали!
Вертикальное масштабирование. Начинаем с простого — увеличиваем мощность серверов. Мониторим нагрузку через Prometheus:
# CPU под нагрузкой больше 80%
rate(node_cpu_seconds_total{mode="system"}[1m]) > 0.8
# RAM забит под завязку
node_memory_MemFree_bytes / node_memory_MemTotal_bytes < 0.1
Когда метрики в красной зоне — апгрейдим железо. Но это временное решение, пора думать о горизонтальном масштабировании.
Балансировка нагрузки — наш следующий шаг. Простой конфиг Nginx для Round Robin:
upstream backend {
server app1.example.com:8080;
server app2.example.com:8080;
server app3.example.com:8080;
}Теперь запросы равномерно распределяются между серверами. При падении одной ноды трафик автоматически уйдет на живые сервера.
База данных — самое интересное. Начинаем с репликации в PostgreSQL:
-- На мастере
CREATE PUBLICATION app_pub FOR ALL TABLES;
-- На реплике
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=master dbname=app'
PUBLICATION app_pub;
Когда и этого мало — включаем шардинг. Пример логики маршрутизации:
def get_shard(user_id):
shard_num = user_id % TOTAL_SHARDS
return f"shard_{shard_num}"
def save_data(user_id, data):
shard = get_shard(user_id)
shard_db = get_database(shard)
shard_db.save(data)
Асинхронная обработка творит чудеса. Выносим тяжелые задачи в очереди:
# Вместо прямой отправки
def process_order(order):
send_notification() # блокирующая операция
generate_invoice() # тоже долго
update_warehouse() # и это тоже
# Используем очереди
def process_order(order):
queue.publish('notifications', order_id)
queue.publish('invoices', order_id)
queue.publish('warehouse', order_id)
return 'Processing started'
Кэширование — must have для высоких нагрузок. Redis спасает от лишних запросов к базе:
def get_user_data(user_id):
# Сначала смотрим в кэш
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# При промахе идем в базу
data = database.query(user_id)
redis.set(f"user:{user_id}", json.dumps(data), ex=3600)
return data
Оптимизация кода не менее важна. Например, в Go используем горутины для параллельной обработки:
func processItems(items []Item) {
resultChan := make(chan Result, len(items))
for _, item := range items {
go func(i Item) {
result := heavyProcessing(i)
resultChan <- result
}(item)
}
// Собираем результаты
var results []Result
for range items {
results = append(results, <-resultChan)
}
}И наконец, graceful degradation. Отключаем тяжелые фичи при высокой нагрузке:
def search_products(query):
if is_high_load():
# Простой поиск по точному совпадению
return basic_search(query)
else:
# Полнотекстовый поиск с фильтрами
return full_search(query)
def is_high_load():
return cpu_usage > 80 or response_time > 500
Все эти техники работают в комплексе. Мониторим метрики, находим узкие места, применяем подходящие решения. Масштабирование — это марафон, а не спринт. Главное — начать с простых оптимизаций и постепенно наращивать сложность.
PS: Ссылка на картинку в хорошем разрешении
🏴☠️ @happy_devops
