← Назад к вопросам

Проектировал ли распределенную систему

1.0 Junior🔥 201 комментариев
#ORM и Hibernate#Spring Boot и Spring Data#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проектирование распределённой системы: опыт

Да, я проектировал несколько распределённых систем. Это одна из самых сложных и интересных задач в backend разработке.

Пример 1: Микросервисная архитектура платформы

Контекст: E-commerce платформа с 100k+ активных пользователей, нужна масштабируемость.

Архитектура:

Client → API Gateway → Load Balancer → Микросервисы
                                      ├─ User Service (3 инстанса)
                                      ├─ Order Service (3 инстанса)
                                      ├─ Payment Service (2 инстанса)
                                      ├─ Inventory Service (2 инстанса)
                                      └─ Notification Service (1 инстанс)
                         ↓
                      Message Queue (RabbitMQ)
                      Database per Service
                      Cache Layer (Redis)

Проблемы и решения:

Проблема 1: Консистентность данных

// При создании заказа нужно:
// 1. Создать Order в OrderService
// 2. Уменьшить Inventory в InventoryService
// 3. Списать деньги в PaymentService
// Если шаг 2 упадёт, но шаг 3 пройдёт -> inconsistency

// РЕШЕНИЕ: Saga Pattern с компенсацией
public class OrderSaga {
    public void execute(OrderRequest request) {
        try {
            // Шаг 1
            orderService.create(request);
            try {
                // Шаг 2
                inventoryService.reserve(request.getProductId());
                try {
                    // Шаг 3
                    paymentService.charge(request.getAmount());
                } catch (Exception e) {
                    // Компенсация 2
                    inventoryService.cancel(request.getProductId());
                    throw e;
                }
            } catch (Exception e) {
                // Компенсация 1
                orderService.cancel(request.getOrderId());
                throw e;
            }
        } catch (Exception e) {
            log.error("Order processing failed", e);
            // Всё отменено, состояние консистентно
        }
    }
}

Проблема 2: Асинхронная коммуникация

// Вместо синхронных REST вызовов (медленно и неточно)
restTemplate.postForObject("http://user-service/notify", data, Response.class);

// Используем Message Queue
public class OrderPublisher {
    @Autowired
    private RabbitTemplate template;
    
    public void publishOrderCreated(Order order) {
        // Публикуем событие асинхронно
        template.convertAndSend("orders", new OrderCreatedEvent(order));
        // Возвращаем сразу, не ждём обработки
    }
}

public class NotificationListener {
    @RabbitListener(queues = "orders")
    public void handleOrderCreated(OrderCreatedEvent event) {
        // Notification Service обработает в фоне
        notificationService.sendEmail(event.getOrder());
    }
}

Проблема 3: Единая точка отказа

// API Gateway не должен быть SPOF (Single Point of Failure)
// РЕШЕНИЕ: Несколько инстансов за Load Balancer

public class LoadBalancerConfig {
    // 3 инстанса API Gateway
    // Если один упадёт, трафик идёт на два других
    // Sticky sessions для Session affinity
}

// Database также не должна быть SPOF
// РЕШЕНИЕ: Replication + Failover
public class DatabaseConfig {
    // Primary - Secondary Replication
    // Если Primary упадёт -> Secondary become Primary
    // Используем health checks для failover
}

Пример 2: Распределённое кэширование

Задача: При нагрузке 10k RPS нужно кэшировать часто запрашиваемые данные.

public class DistributedCacheArchitecture {
    // 3 Redis инстанса в Cluster mode
    private final RedisTemplate<String, Object> template;
    
    public void cacheUserData(User user) {
        // Автоматически распределяется по 3 нодам
        template.opsForValue().set(
            "user:" + user.getId(),
            user,
            Duration.ofHours(1)
        );
    }
    
    public User getUserData(Long userId) {
        // Cache-aside pattern
        String key = "user:" + userId;
        User user = (User) template.opsForValue().get(key);
        
        if (user == null) {
            user = database.getUser(userId);  // Fallback to DB
            cacheUserData(user);  // Кэшируем для следующего раза
        }
        return user;
    }
}

// Проблемы распределённого кэша:
// 1. Cache invalidation (сложнее, чем локальный кэш)
// 2. Network latency (каждый access = network call)
// 3. Consistency (разные инстансы видят разные данные в момент обновления)

Пример 3: Распределённые транзакции

Задача: Перевод денег между счётами в разных сервисах.

public class DistributedTransaction {
    // Неправильно: Two-Phase Commit (2PC)
    // Проблемы: блокировки, медленно, нет failover'а
    
    // Правильно: Event Sourcing + CQRS
    @Service
    public class MoneyTransferService {
        @Autowired
        private EventStore eventStore;
        
        public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
            // 1. Сохраняем событие
            TransferInitiated event = new TransferInitiated(
                fromAccount, toAccount, amount
            );
            eventStore.append(event);
            
            // 2. Опубликуем событие
            eventBus.publish(event);
        }
    }
    
    // Обработчики события (в разных сервисах)
    public class AccountDebitHandler {
        @EventHandler
        public void handle(TransferInitiated event) {
            accountService.debit(event.getFromAccount(), event.getAmount());
        }
    }
    
    public class AccountCreditHandler {
        @EventHandler
        public void handle(TransferInitiated event) {
            accountService.credit(event.getToAccount(), event.getAmount());
        }
    }
}

Пример 4: Мониторинг распределённой системы

Проблема: Когда система большая, сложно найти узкие места.

// Distributed Tracing
@RestController
public class OrderController {
    @PostMapping("/orders")
    @Timed("order.creation")  // Метрика
    public Order createOrder(@RequestBody OrderRequest request) {
        // Trace ID автоматически передаётся
        // User -> API Gateway -> OrderService -> PaymentService
        // Все логи связаны одним Trace ID
        
        logger.info("Creating order for user: {}", request.getUserId());
        // LOG: [trace_id=abc123] Creating order...
        
        return orderService.create(request);
    }
}

// В логах видим полную цепочку вызовов
// Сервис А -> Сервис Б -> Сервис В
// С временем выполнения каждого шага

// Tools: Jaeger, Zipkin, DataDog

Ключевые уроки из опыта

1. Не делай распределённую систему, если можно сделать монолит

Распределённая система = 10x сложнее в отладке
Отлагать падения, race conditions, partition tolerance

2. Используй правильные паттерны

- Event-Driven для асинхронности
- Saga для распределённых транзакций
- CQRS для read-heavy операций
- Circuit Breaker для отказоустойчивости

3. Мониторинг — это не опция

Распределённая система без мониторинга = слепота
Тоебуются Distributed Tracing, Metrics, Logs

4. Тестируй отказы

// Chaos Engineering
- Убивай случайные инстансы
- Отключай сеть
- Замедляй响应 времена
- Смотри, как система это переносит

5. Документируй архитектуру

Микросервисы меняются быстро
Документация спасает новых членов команды

Заключение

Проектирование распределённой системы — это hard problem в computer science. Основное:

  1. Консистентность — используй Saga паттерн
  2. Асинхронность — Message Queue, не синхронные REST
  3. Отказоустойчивость — избегай SPOF
  4. Мониторинг — distributed tracing обязателен
  5. Простота — начни с монолита, разбивай только когда нужно

Микросервисы нужны не всем. Начни с простого и вырастай когда система требует.

Проектировал ли распределенную систему | PrepBro