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

Как обеспечивал доставку данных при нестабильной работе микросервиса

3.0 Senior🔥 241 комментариев
#REST API и микросервисы

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

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

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

# Обеспечение доставки данных при нестабильности микросервисов

Это критичная задача в распределённых системах. Рассмотрю практические подходы, которые я применял в production.

1. Retry Logic с Exponential Backoff

Первая линия защиты — повторные попытки с экспоненциальной задержкой:

spring:
  cloud:
    circuitbreaker:
      resilience4j:
        instances:
          userService:
            registerHealthIndicator: true
            slidingWindowSize: 20
            slowCallRateThreshold: 50.0
            slowCallDurationThreshold: 2s
            failureRateThreshold: 50.0
            waitDurationInOpenState: 30s
            permittedNumberOfCallsInHalfOpenState: 3

resilience4j:
  retry:
    configs:
      default:
        maxAttempts: 3
        waitDuration: 1000
        intervalFunction: exponential
        exponentialRandomizationFactor: 0.5
    instances:
      userService:
        baseConfig: default
@Service
public class UserServiceClient {
    
    @Retry(name = "userService", fallback = "fallbackGetUser")
    @CircuitBreaker(name = "userService", fallback = "fallbackGetUser")
    public User getUser(String userId) {
        return restTemplate.getForObject(
            "http://user-service/api/users/" + userId, 
            User.class
        );
    }
    
    public User fallbackGetUser(String userId, Exception ex) {
        log.error("Failed to get user, returning cached data", ex);
        return userCache.get(userId);
    }
}

2. Circuit Breaker паттерн

@Service
public class OrderService {
    
    @CircuitBreaker(name = "paymentService", fallback = "processOffline")
    public PaymentResult processPayment(Order order) {
        return paymentClient.charge(order);
    }
    
    public PaymentResult processOffline(Order order, Exception ex) {
        // Когда сервис payment недоступен, сохраняем в очередь на обработку позже
        asyncQueue.enqueue(new PendingPayment(order));
        return PaymentResult.PENDING;
    }
}

3. Message Queue (RabbitMQ / Kafka)

Для асинхронной доставки данных — очень важно:

@Service
public class OrderPublisher {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void publishOrder(Order order) {
        try {
            rabbitTemplate.convertAndSend(
                "orders.exchange",
                "orders.created",
                order,
                message -> {
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    message.getMessageProperties().setExpiration("3600000"); // 1 час
                    return message;
                }
            );
        } catch (Exception e) {
            // Fallback: сохранить в БД на обработку
            deadLetterQueue.save(order);
        }
    }
}

@Component
public class OrderConsumer {
    
    @RabbitListener(queues = "orders.queue", ackMode = MANUAL)
    public void processOrder(Order order, Channel channel, @Header(name = "amqp_deliveryTag") long deliveryTag) {
        try {
            orderService.process(order);
            channel.basicAck(deliveryTag, false); // Confirm успешной обработки
        } catch (Exception e) {
            log.error("Error processing order", e);
            // Не подтверждаем - сообщение вернётся в очередь
            try {
                channel.basicNack(deliveryTag, false, true);
            } catch (IOException ex) {
                log.error("Error nacking message", ex);
            }
        }
    }
}

4. Distributed Tracing (OpenTelemetry)

Для мониторинга цепи вызовов:

@Configuration
public class TracingConfig {
    
    @Bean
    public OpenTelemetry openTelemetry() {
        return AutoConfiguredOpenTelemetrySdkBuilder.initialize().getOpenTelemetrySdk();
    }
}

@Service
public class OrderService {
    
    @Autowired
    private Tracer tracer;
    
    public void createOrder(Order order) {
        try (Scope scope = tracer.spanBuilder("createOrder")
                .setAttribute("order.id", order.getId())
                .setAttribute("user.id", order.getUserId())
                .startScope()) {
            // business logic
        }
    }
}

5. Outbox Pattern (для гарантированной доставки)

@Service
public class OrderService {
    
    @Transactional
    public void createOrder(Order order) {
        // 1. Сохранить заказ
        orderRepository.save(order);
        
        // 2. Сохранить событие в той же транзакции
        OutboxEvent event = new OutboxEvent(
            "OrderCreated",
            objectMapper.writeValueAsString(order),
            false
        );
        outboxRepository.save(event);
    }
}

// Отдельный процесс публикует события из outbox
@Component
public class OutboxPublisher {
    
    @Scheduled(fixedDelay = 1000)
    public void publishPendingEvents() {
        List<OutboxEvent> pending = outboxRepository.findByPublishedFalse(PageRequest.of(0, 100));
        
        pending.forEach(event -> {
            try {
                publisher.publish(event.getEventType(), event.getPayload());
                event.setPublished(true);
                outboxRepository.save(event);
            } catch (Exception e) {
                log.error("Failed to publish event", e);
                // Повторим позже
            }
        });
    }
}

6. Dead Letter Queue

@Configuration
public class RabbitConfig {
    
    @Bean
    public Queue ordersQueue() {
        return QueueBuilder.durable("orders.queue")
            .deadLetterExchange("orders.dlx")
            .deadLetterRoutingKey("orders.dead-letter")
            .ttl(3600000) // 1 час TTL
            .build();
    }
    
    @Bean
    public Queue deadLetterQueue() {
        return new Queue("orders.dead-letter.queue");
    }
}

// Обработчик мёртвых писем
@Component
public class DeadLetterHandler {
    
    @RabbitListener(queues = "orders.dead-letter.queue")
    public void handleDeadLetter(Order order) {
        log.warn("Processing dead letter: {}", order);
        // Отправить alert, сохранить для ручной обработки, etc.
        alertService.notifyFailedOrder(order);
    }
}

7. Persistent Storage как fallback

@Service
public class DataDeliveryService {
    
    public void ensureDelivery(DataPacket packet) {
        // Попытка 1: отправить напрямую
        try {
            remoteService.send(packet);
            return;
        } catch (ServiceUnavailableException e) {
            log.warn("Remote service unavailable, saving to database");
        }
        
        // Попытка 2: сохранить для отложенной доставки
        PendingDelivery delivery = new PendingDelivery(
            packet,
            LocalDateTime.now(),
            0
        );
        pendingDeliveryRepository.save(delivery);
    }
    
    @Scheduled(fixedDelay = 30000) // 30 сек
    public void retryFailedDeliveries() {
        pendingDeliveryRepository.findByAttemptsLessThan(5)
            .forEach(delivery -> {
                try {
                    remoteService.send(delivery.getPacket());
                    pendingDeliveryRepository.delete(delivery);
                } catch (Exception e) {
                    delivery.incrementAttempts();
                    if (delivery.getAttempts() >= 5) {
                        alertService.notifyPermanentFailure(delivery);
                    }
                    pendingDeliveryRepository.save(delivery);
                }
            });
    }
}

Summary

Ключевые компоненты стратегии:

  • Retry + Circuit Breaker — быстрое восстановление
  • Message Queue — асинхронная обработка
  • Outbox Pattern — гарантированная доставка
  • Dead Letter Queue — обработка исключений
  • Persistent Storage — резервный механизм
  • Monitoring & Alerting — видимость в систему

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