👍2
Что выведет код?
#Tasks
import java.util.concurrent.ConcurrentHashMap;
public class Task060126 {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
Integer result = map.computeIfAbsent("a", k -> map.computeIfAbsent("a", k2 -> 2));
System.out.println(result);
}
}
#Tasks
👍3
Вопрос с собеседований
Почему hashCode() должен быть согласован с equals()?🤓
Ответ:
Если equals возвращает true, hashCode обязан быть одинаковым.
Иначе HashMap/HashSet не смогут корректно находить элементы.
Нарушение контракта приводит к потерянным объектам и трудноуловимым багам.
#собеседование
Почему hashCode() должен быть согласован с equals()?
Ответ:
Иначе HashMap/HashSet не смогут корректно находить элементы.
Нарушение контракта приводит к потерянным объектам и трудноуловимым багам.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
История IT-технологий сегодня — 07 января
ℹ️ Кто родился в этот день
Иоганн Филипп Рейс (нем. Johann Philipp Reis; 7 января 1834, Гельнхаузен, Великое герцогство Гессен — 14 января 1874, Фридрихсдорф, Германия) — немецкий физик и изобретатель, первым в 1860 году сконструировавший электрический телефон, который в его честь сейчас называется телефоном Рейса. Впервые это изобретение было продемонстрировано публике 25 октября 1861 года.
🌐 Знаковые события
1610 — открытие четырёх крупнейших спутников Юпитера Галилео Галилеем.
1954 – Эксперимент Джорджтаун-IBM: Первая публичная демонстрация системы машинного перевода состоялась в Нью-Йорке в головном офисе IBM.
#Biography #Birth_Date #Events #07Января
Иоганн Филипп Рейс (нем. Johann Philipp Reis; 7 января 1834, Гельнхаузен, Великое герцогство Гессен — 14 января 1874, Фридрихсдорф, Германия) — немецкий физик и изобретатель, первым в 1860 году сконструировавший электрический телефон, который в его честь сейчас называется телефоном Рейса. Впервые это изобретение было продемонстрировано публике 25 октября 1861 года.
1610 — открытие четырёх крупнейших спутников Юпитера Галилео Галилеем.
1954 – Эксперимент Джорджтаун-IBM: Первая публичная демонстрация системы машинного перевода состоялась в Нью-Йорке в головном офисе IBM.
#Biography #Birth_Date #Events #07Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Паттерны использования и гарантии доставки в RabbitMQ
RabbitMQ предоставляет гибкие модели взаимодействия, позволяющие реализовывать различные архитектурные паттерны. Понимание этих паттернов и связанных с ними гарантий доставки критически важно для построения надежных распределенных систем.
Work Queues: распределение нагрузки между потребителями
Work Queues (очереди задач) используются для распределения трудоемких задач между несколькими worker-процессами. Этот паттерн идеален для обработки фоновых задач, таких как генерация отчетов, обработка изображений или отправка email.
Ключевые характеристики:
Одна очередь, несколько потребителей
Каждое сообщение обрабатывается только одним потребителем
Балансировка нагрузки через настройку prefetch count
Реализация на Java с Spring AMQP
Producer:
Consumer с ручными подтверждениями:
Конфигурация для равномерного распределения:
#Java #middle #RabbitMQ
RabbitMQ предоставляет гибкие модели взаимодействия, позволяющие реализовывать различные архитектурные паттерны. Понимание этих паттернов и связанных с ними гарантий доставки критически важно для построения надежных распределенных систем.
Work Queues: распределение нагрузки между потребителями
Work Queues (очереди задач) используются для распределения трудоемких задач между несколькими worker-процессами. Этот паттерн идеален для обработки фоновых задач, таких как генерация отчетов, обработка изображений или отправка email.
Ключевые характеристики:
Одна очередь, несколько потребителей
Каждое сообщение обрабатывается только одним потребителем
Балансировка нагрузки через настройку prefetch count
Реализация на Java с Spring AMQP
Producer:
@Service
public class TaskProducer {
private final RabbitTemplate rabbitTemplate;
@Value("${app.queues.task-queue}")
private String taskQueue;
public void sendTask(Task task) {
rabbitTemplate.convertAndSend(taskQueue, task, message -> {
// Установка приоритета задачи
message.getMessageProperties().setPriority(task.getPriority());
// Время жизни сообщения
message.getMessageProperties().setExpiration("3600000"); // 1 час
return message;
});
}
}
Consumer с ручными подтверждениями:
@Component
public class TaskWorker {
@RabbitListener(queues = "${app.queues.task-queue}")
public void processTask(Task task, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
try {
// Обработка задачи
boolean success = executeTask(task);
if (success) {
// Подтверждение успешной обработки
channel.basicAck(deliveryTag, false);
log.info("Task {} processed successfully", task.getId());
} else {
// Отказ без повторной очереди (перемещение в DLQ)
channel.basicNack(deliveryTag, false, false);
log.error("Task {} failed, moved to DLQ", task.getId());
}
} catch (Exception e) {
// При ошибке - повторная очередь
channel.basicNack(deliveryTag, false, true);
log.error("Error processing task {}, requeued", task.getId(), e);
}
}
private boolean executeTask(Task task) {
// Логика обработки задачи
return true;
}
}
Конфигурация для равномерного распределения:
@Configuration
public class WorkQueueConfig {
@Bean
public Queue taskQueue() {
return QueueBuilder.durable("tasks.queue")
.withArgument("x-max-priority", 10) // Поддержка приоритетов
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.build();
}
@Bean
public SimpleRabbitListenerContainerFactory workerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory =
new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// Критичная настройка для равномерного распределения
factory.setPrefetchCount(1); // По одному сообщению на consumer
factory.setConcurrentConsumers(3); // Три параллельных worker'а
factory.setMaxConcurrentConsumers(10); // Автомасштабирование при нагрузке
// Ручные подтверждения для контроля
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
}
#Java #middle #RabbitMQ
👍4
Publish/Subscribe: широковещательная рассылка
Publish/Subscribe (публикация/подписка) используется, когда одно сообщение должно быть доставлено множеству потребителей. Типичные сценарии: уведомления, аудит-логи, обновления кэша.
Архитектура:
Publisher отправляет сообщение в exchange
Exchange копирует сообщение во все привязанные очереди
Каждый consumer имеет свою собственную очередь
Реализация Fanout Exchange
Конфигурация обменника и очередей:
Publisher:
Consumer для аудита (пример одного из многих):
Routing/Topics: селективная подписка
Topic Exchange позволяет выполнять сложную маршрутизацию на основе routing key и patterns. Используется, когда потребители интересуются только определенными типами сообщений.
Pattern syntax:
* (звездочка) заменяет одно слово
# (решетка) заменяет ноль или более слов
Пример: stock.usd.* или stock.#
Реализация Topic Exchange
Конфигурация:
#Java #middle #RabbitMQ
Publish/Subscribe (публикация/подписка) используется, когда одно сообщение должно быть доставлено множеству потребителей. Типичные сценарии: уведомления, аудит-логи, обновления кэша.
Архитектура:
Publisher отправляет сообщение в exchange
Exchange копирует сообщение во все привязанные очереди
Каждый consumer имеет свою собственную очередь
Реализация Fanout Exchange
Конфигурация обменника и очередей:
@Configuration
public class PubSubConfig {
@Bean
public FanoutExchange notificationsExchange() {
return new FanoutExchange("notifications.fanout", true, false);
}
@Bean
public Queue auditQueue() {
return new Queue("notifications.audit.queue", true);
}
@Bean
public Queue cacheQueue() {
return new Queue("notifications.cache.queue", true);
}
@Bean
public Queue analyticsQueue() {
return new Queue("notifications.analytics.queue", true);
}
@Bean
public Binding auditBinding() {
return BindingBuilder.bind(auditQueue())
.to(notificationsExchange());
}
@Bean
public Binding cacheBinding() {
return BindingBuilder.bind(cacheQueue())
.to(notificationsExchange());
}
@Bean
public Binding analyticsBinding() {
return BindingBuilder.bind(analyticsQueue())
.to(notificationsExchange());
}
}
Publisher:
@Service
public class NotificationPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishNotification(Notification notification) {
// Отправка в fanout exchange - все подписчики получат копию
rabbitTemplate.convertAndSend(
"notifications.fanout",
"", // routing key игнорируется для fanout
notification
);
}
}
Consumer для аудита (пример одного из многих):
@Component
public class AuditNotificationConsumer {
@RabbitListener(queues = "notifications.audit.queue")
public void handleNotification(Notification notification) {
// Каждый consumer обрабатывает свою копию сообщения
auditService.logEvent(notification);
}
}
Routing/Topics: селективная подписка
Topic Exchange позволяет выполнять сложную маршрутизацию на основе routing key и patterns. Используется, когда потребители интересуются только определенными типами сообщений.
Pattern syntax:
* (звездочка) заменяет одно слово
# (решетка) заменяет ноль или более слов
Пример: stock.usd.* или stock.#
Реализация Topic Exchange
Конфигурация:
@Configuration
public class TopicRoutingConfig {
@Bean
public TopicExchange stockExchange() {
return new TopicExchange("stock.topic", true, false);
}
@Bean
public Queue usdStockQueue() {
return new Queue("stock.usd.queue", true);
}
@Bean
public Queue eurStockQueue() {
return new Queue("stock.eur.queue", true);
}
@Bean
public Queue allStockQueue() {
return new Queue("stock.all.queue", true);
}
@Bean
public Binding usdBinding() {
// Будет получать: stock.usd.nyse, stock.usd.nasdaq
return BindingBuilder.bind(usdStockQueue())
.to(stockExchange())
.with("stock.usd.*");
}
@Bean
public Binding eurBinding() {
// Будет получать: stock.eur.lse, stock.eur.euronext
return BindingBuilder.bind(eurStockQueue())
.to(stockExchange())
.with("stock.eur.*");
}
@Bean
public Binding allStocksBinding() {
// Будет получать все сообщения о stock
return BindingBuilder.bind(allStockQueue())
.to(stockExchange())
.with("stock.#");
}
}
#Java #middle #RabbitMQ
👍4
Publisher с различными routing keys:
RPC over AMQP: синхронные вызовы через асинхронный транспорт
Remote Procedure Call (RPC) поверх AMQP позволяет выполнять синхронные запросы-ответы через ассинхронную систему сообщений. Используется когда нужен немедленный ответ, но нельзя установить прямое соединение.
Механизм работы:
Клиент отправляет запрос с уникальным correlationId
Сервер обрабатывает запрос и отправляет ответ в очередь ответов
Клиент ожидает ответ с matching correlationId
Реализация RPC
Клиентская сторона:
Серверная сторона:
#Java #middle #RabbitMQ
@Service
public class StockPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishUSDPrice(String exchange, BigDecimal price) {
String routingKey = "stock.usd." + exchange.toLowerCase();
StockUpdate update = new StockUpdate("USD", exchange, price);
rabbitTemplate.convertAndSend(
"stock.topic",
routingKey,
update
);
}
public void publishEURPrice(String exchange, BigDecimal price) {
String routingKey = "stock.eur." + exchange.toLowerCase();
StockUpdate update = new StockUpdate("EUR", exchange, price);
rabbitTemplate.convertAndSend(
"stock.topic",
routingKey,
update
);
}
}
RPC over AMQP: синхронные вызовы через асинхронный транспорт
Remote Procedure Call (RPC) поверх AMQP позволяет выполнять синхронные запросы-ответы через ассинхронную систему сообщений. Используется когда нужен немедленный ответ, но нельзя установить прямое соединение.
Механизм работы:
Клиент отправляет запрос с уникальным correlationId
Сервер обрабатывает запрос и отправляет ответ в очередь ответов
Клиент ожидает ответ с matching correlationId
Реализация RPC
Клиентская сторона:
@Service
public class CalculatorRpcClient {
private final RabbitTemplate rabbitTemplate;
public CalculatorRpcClient(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
// Настройка reply listener
this.rabbitTemplate.setReplyTimeout(30000);
this.rabbitTemplate.setUseDirectReplyToContainer(false);
}
public BigDecimal calculate(CalculationRequest request) {
// Создание correlation ID
String correlationId = UUID.randomUUID().toString();
// Отправка запроса и ожидание ответа
CalculationResponse response = (CalculationResponse)
rabbitTemplate.convertSendAndReceive(
"rpc.exchange",
"calculator.rpc",
request,
message -> {
message.getMessageProperties()
.setCorrelationId(correlationId);
message.getMessageProperties()
.setReplyTo("amq.rabbitmq.reply-to"); // Временная очередь
return message;
}
);
if (response == null) {
throw new RuntimeException("RPC timeout");
}
return response.getResult();
}
}
Серверная сторона:
@Component
public class CalculatorRpcServer {
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "rpc.calculator.queue", durable = "true"),
exchange = @Exchange(value = "rpc.exchange", type = ExchangeTypes.DIRECT),
key = "calculator.rpc"
)
)
public CalculationResponse calculate(CalculationRequest request,
Message message) {
String correlationId = message.getMessageProperties()
.getCorrelationId();
String replyTo = message.getMessageProperties()
.getReplyTo();
log.debug("Processing RPC request {} from {}",
correlationId, replyTo);
// Выполнение расчета
BigDecimal result = performCalculation(request);
// Возврат результата с сохранением correlationId
CalculationResponse response = new CalculationResponse(result);
// CorrelationId автоматически копируется в ответ
return response;
}
private BigDecimal performCalculation(CalculationRequest request) {
// Логика расчета
return BigDecimal.ZERO;
}
}
#Java #middle #RabbitMQ
👍4
Гарантии доставки
At-most-once: риск потери сообщений
Механизм: Сообщение отправляется без подтверждений, сразу удаляется из очереди.
Использовать когда:
Потеря сообщений допустима
Высокая производительность критична
Система имеет механизмы компенсации
At-least-once: риск дублирования
Механизм: Гарантирует доставку минимум один раз, но возможны дубликаты.
Реализация с ручными подтверждениями:
#Java #middle #RabbitMQ
At-most-once: риск потери сообщений
Механизм: Сообщение отправляется без подтверждений, сразу удаляется из очереди.
Использовать когда:
Потеря сообщений допустима
Высокая производительность критична
Система имеет механизмы компенсации
// Producer без подтверждений
@Bean
public RabbitTemplate atMostOnceTemplate(ConnectionFactory cf) {
RabbitTemplate template = new RabbitTemplate(cf);
template.setMandatory(false);
// Отключение publisher confirms
((CachingConnectionFactory) cf)
.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.NONE);
return template;
}
// Consumer с auto-ack
@RabbitListener(queues = "at.most.once.queue")
public void handleAutoAck(Message message) {
// Сообщение уже удалено из очереди при доставке
// При падении здесь - сообщение потеряно
processMessage(message);
}
At-least-once: риск дублирования
Механизм: Гарантирует доставку минимум один раз, но возможны дубликаты.
Реализация с ручными подтверждениями:
@Component
public class AtLeastOnceConsumer {
@RabbitListener(queues = "orders.queue")
public void processOrder(Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
try {
// 1. Обработка заказа
processOrderSafely(order);
// 2. Подтверждение после успешной обработки
channel.basicAck(tag, false);
log.info("Order {} processed and acknowledged", order.getId());
} catch (Exception e) {
// 3. При ошибке - повторная очередь
channel.basicNack(tag, false, true);
log.error("Order processing failed, requeued", e);
// Идемпотентность критична!
// При повторной обработке должна быть сохранена консистентность
}
}
private void processOrderSafely(Order order) {
// Идемпотентная обработка
if (orderRepository.existsById(order.getId())) {
log.warn("Duplicate order {}, skipping", order.getId());
return;
}
orderRepository.save(order);
inventoryService.reserveItems(order);
}
}
#Java #middle #RabbitMQ
👍4
Exactly-once: сложная реализация
Реальность: RabbitMQ не предоставляет exactly-once гарантий на уровне протокола. Достигается комбинацией механизмов.
Паттерн для pseudo-exactly-once:
Механизмы надежности
Publisher Confirms
Механизм: Брокер подтверждает получение сообщения.
Реализация:
#Java #middle #RabbitMQ
Реальность: RabbitMQ не предоставляет exactly-once гарантий на уровне протокола. Достигается комбинацией механизмов.
Паттерн для pseudo-exactly-once:
@Service
@Transactional
public class ExactlyOnceProcessor {
private final RabbitTemplate rabbitTemplate;
private final OrderRepository orderRepository;
public void processWithExactlyOnceSemantics(OrderMessage message) {
// 1. Проверка идемпотентности
if (processedMessageRepository.existsById(message.getId())) {
return; // Уже обработано
}
// 2. Сохранение в outbox паттерн
OrderOutbox outbox = new OrderOutbox();
outbox.setMessageId(message.getId());
outbox.setOrderData(message.getPayload());
outbox.setStatus(OutboxStatus.PROCESSING);
orderRepository.save(outbox);
// 3. Бизнес-логика в транзакции
Order order = createOrder(message);
orderRepository.save(order);
// 4. Обновление статуса в той же транзакции
outbox.setStatus(OutboxStatus.PROCESSED);
orderRepository.save(outbox);
// 5. Отправка подтверждения (после коммита транзакции)
// RabbitTemplate работает после завершения транзакции
rabbitTemplate.convertAndSend(
"order.processed.exchange",
"order.processed",
new OrderProcessedEvent(order.getId())
);
// 6. Идемпотентность на стороне получателя подтверждения
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderProcessedEvent event) {
// Только после успешного коммита
rabbitTemplate.convertAndSend(
"order.completed.queue",
event
);
}
}
Механизмы надежности
Publisher Confirms
Механизм: Брокер подтверждает получение сообщения.
Реализация:
@Configuration
public class PublisherConfirmConfig {
@Bean
public CachingConnectionFactory confirmedConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
// Включение publisher confirms
factory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
factory.setPublisherReturns(true);
return factory;
}
@Bean
public RabbitTemplate confirmedRabbitTemplate(
CachingConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// Callback для подтверждений
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
metrics.increment("publisher.confirms.success");
} else {
metrics.increment("publisher.confirms.failure");
log.error("Message not confirmed: {}", cause);
// Логика повторной отправки
if (correlationData != null) {
retryService.scheduleRetry(correlationData.getId());
}
}
});
// Callback для возвращенных сообщений
template.setReturnsCallback(returned -> {
log.error("Message returned: {}", returned.toString());
returnedMessageService.handleReturned(returned);
});
return template;
}
}
#Java #middle #RabbitMQ
👍4
Consumer Acknowledgements
Типы подтверждений:
Transactional Channels
Использование транзакций:
#Java #middle #RabbitMQ
Типы подтверждений:
public class AcknowledgementExamples {
// 1. Автоматическое подтверждение (ненадежно)
@RabbitListener(queues = "auto.ack.queue")
public void autoAck(Message message) {
// Подтверждение происходит автоматически при возврате метода
}
// 2. Ручное подтверждение (рекомендуется)
@RabbitListener(queues = "manual.ack.queue")
public void manualAck(Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
try {
process(order);
channel.basicAck(tag, false); // Положительное подтверждение
} catch (BusinessException e) {
// Не requeue для бизнес-ошибок
channel.basicNack(tag, false, false);
} catch (TechnicalException e) {
// Requeue для технических ошибок
channel.basicNack(tag, false, true);
}
}
// 3. Подтверждение с транзакцией
@Transactional
@RabbitListener(queues = "transactional.queue")
public void transactionalAck(Payment payment, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
// Сохранение в БД
paymentRepository.save(payment);
// Подтверждение произойдет только после коммита транзакции
// При откате транзакции - сообщение вернется в очередь
}
}Transactional Channels
Использование транзакций:
@Service
public class TransactionalMessageService {
private final RabbitTemplate rabbitTemplate;
@Transactional
public void processInTransaction(Order order) {
// 1. Сохранение в базу данных
orderRepository.save(order);
// 2. Отправка сообщения в той же транзакции
rabbitTemplate.execute(channel -> {
// Начало транзакции RabbitMQ
channel.txSelect();
try {
// Отправка сообщения
channel.basicPublish(
"orders.exchange",
"order.created",
null,
serialize(order)
);
// Коммит транзакции RabbitMQ
channel.txCommit();
return null;
} catch (Exception e) {
// Откат транзакции RabbitMQ
channel.txRollback();
throw new RuntimeException("Transaction failed", e);
}
});
// 3. Если произойдет исключение здесь,
// откатятся и БД и сообщение RabbitMQ
inventoryService.updateStock(order);
}
}
#Java #middle #RabbitMQ
👍4
Dead Letter Exchanges: обработка проблемных сообщений
Dead Letter Exchange (DLX) — специальный exchange, куда перенаправляются сообщения, которые не могут быть обработаны.
Типичные сценарии:
Сообщение отбраковано (nack без requeue)
Превышено максимальное количество попыток обработки
Истек TTL сообщения
Очередь заполнена (при overflow поведении)
Реализация DLX
Конфигурация основной очереди с DLX:
Обработчик проблемных сообщений:
#Java #middle #RabbitMQ
Dead Letter Exchange (DLX) — специальный exchange, куда перенаправляются сообщения, которые не могут быть обработаны.
Типичные сценарии:
Сообщение отбраковано (nack без requeue)
Превышено максимальное количество попыток обработки
Истек TTL сообщения
Очередь заполнена (при overflow поведении)
Реализация DLX
Конфигурация основной очереди с DLX:
@Configuration
public class DeadLetterConfig {
// Основной DLX
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlx.exchange", true, false);
}
// Очередь для мертвых писем
@Bean
public Queue dlQueue() {
return QueueBuilder.durable("dead.letter.queue")
.withArgument("x-max-length", 10000) // Ограничение размера
.withArgument("x-message-ttl", 86400000) // 24 часа хранения
.build();
}
@Bean
public Binding dlBinding() {
return BindingBuilder.bind(dlQueue())
.to(dlxExchange())
.with("#"); // Все routing keys
}
// Рабочая очередь с настройкой DLX
@Bean
public Queue orderProcessingQueue() {
return QueueBuilder.durable("orders.processing.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.withArgument("x-dead-letter-routing-key", "orders.failed")
.withArgument("x-max-retries", 3) // Кастомный аргумент
.build();
}
}
Обработчик проблемных сообщений:
@Component
public class DeadLetterProcessor {
@RabbitListener(queues = "dead.letter.queue")
public void handleDeadLetter(Message failedMessage,
Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag,
@Header(AmqpHeaders.RECEIVED_ROUTING_KEY) String routingKey,
@Header(AmqpHeaders.RECEIVED_EXCHANGE) String exchange,
@Header("x-death") List<Map<String, Object>> deaths) {
// Анализ причины попадания в DLQ
String reason = analyzeFailureReason(deaths);
log.error("Dead letter received: routingKey={}, reason={}, deaths={}",
routingKey, reason, deaths);
// Логика обработки в зависимости от причины
if (isRecoverable(failedMessage, deaths)) {
handleRecoverableMessage(failedMessage);
} else {
handlePermanentFailure(failedMessage);
}
// Подтверждение обработки DLQ
channel.basicAck(tag, false);
}
private String analyzeFailureReason(List<Map<String, Object>> deaths) {
if (deaths != null && !deaths.isEmpty()) {
Map<String, Object> lastDeath = deaths.get(0);
return (String) lastDeath.get("reason");
}
return "unknown";
}
private void handleRecoverableMessage(Message message) {
// Пример: повторная отправка после задержки
String originalQueue = extractOriginalQueue(message);
long delay = calculateRetryDelay(message);
retryService.scheduleRetry(message, originalQueue, delay);
}
private void handlePermanentFailure(Message message) {
// Архивирование неудачных сообщений
archiveService.archiveFailedMessage(message);
// Уведомление команды поддержки
alertService.notifySupportTeam(message);
}
}
#Java #middle #RabbitMQ
👍4
Продвинутая обработка с задержкой повторных попыток:
Практические рекомендации
Выбор паттерна
Work Queues — для фоновой обработки задач с балансировкой нагрузки
Publish/Subscribe — для широковещательных уведомлений
Routing/Topics — для сложной маршрутизации сообщений
RPC — для синхронных запросов в асинхронной среде
Гарантии доставки
At-most-once — только для non-critical данных
At-least-once — стандартный выбор для большинства систем
Exactly-once — достигается через идемпотентность и транзакции
#Java #middle #RabbitMQ
@Configuration
public class DelayedRetryConfig {
// Exchange для отложенных повторных попыток
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(
"delayed.retry.exchange",
"x-delayed-message",
true,
false,
args
);
}
// Очередь для отложенных повторных попыток
@Bean
public Queue delayedRetryQueue() {
return new Queue("delayed.retry.queue", true);
}
@Bean
public Binding delayedBinding() {
return BindingBuilder.bind(delayedRetryQueue())
.to(delayedExchange())
.with("retry.key")
.noargs();
}
// Сервис для отложенных повторных попыток
@Service
public class DelayedRetryService {
private final RabbitTemplate rabbitTemplate;
public void scheduleRetry(Message message, int attempt) {
long delay = calculateExponentialBackoff(attempt);
rabbitTemplate.convertAndSend(
"delayed.retry.exchange",
"retry.key",
message,
m -> {
// Установка задержки
m.getMessageProperties()
.setHeader("x-delay", delay);
// Сохранение номера попытки
m.getMessageProperties()
.setHeader("retry-attempt", attempt);
return m;
}
);
}
private long calculateExponentialBackoff(int attempt) {
return (long) Math.pow(2, attempt) * 1000; // Экспоненциальная задержка
}
}
}
Практические рекомендации
Выбор паттерна
Work Queues — для фоновой обработки задач с балансировкой нагрузки
Publish/Subscribe — для широковещательных уведомлений
Routing/Topics — для сложной маршрутизации сообщений
RPC — для синхронных запросов в асинхронной среде
Гарантии доставки
At-most-once — только для non-critical данных
At-least-once — стандартный выбор для большинства систем
Exactly-once — достигается через идемпотентность и транзакции
#Java #middle #RabbitMQ
👍4
Что выведет код?
#Tasks
public class Task070126 {
public static void main(String[] args) {
new Child070126();
}
}
class Parent070126 {
Parent070126() {
print();
}
void print() {
System.out.println("Parent");
}
}
class Child070126 extends Parent070126 {
private String value = "Hello";
Child070126() {
System.out.println("Child constructor");
}
@Override
void print() {
System.out.println(value.length());
}
}#Tasks
👍2
Варианты ответа:
Anonymous Quiz
31%
5 Child constructor
8%
0 Child constructor
31%
Child constructor 5
31%
NullPointerException
👍1😱1
Очередная статья на Хабре.
Поставьте там лайков, если статья понравится и Вам не сложно)))
Всех обнял-приподнял🎄
Поставьте там лайков, если статья понравится и Вам не сложно)))
Всех обнял-приподнял
Please open Telegram to view this post
VIEW IN TELEGRAM
Хабр
Field vs Constructor Injection в Java: ошибка объектного дизайна или вопрос синтаксиса?
Знаю, знаю... Прочитав заголовок, хочется голосом волка из мультфильма "Жил был пёс" сказать - "Шо, опять?" . Ведь битва этих подходов давно закончилась и разработчики Spring уже поставили точку. Но...
👍8
Как HashMap превращает список в дерево? 🤓
Ответ:
При превышении порога коллизий (обычно 8) бакет преобразуется в красно-чёрное дерево.
Это снижает сложность операций с O(n) до O(log n), защищая от деградации производительности и атак на хеш-функцию.
#собеседование
Ответ:
Это снижает сложность операций с O(n) до O(log n), защищая от деградации производительности и атак на хеш-функцию.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История IT-технологий сегодня — 08 января
ℹ️ Кто родился в этот день
Сти́вен Уи́льям Хо́кинг (англ. Stephen William Hawking; 8 января 1942, Оксфорд — 14 марта 2018, Кембридж) — британский физик-теоретик, космолог, астрофизик и писатель.
Алекса́ндр Льво́вич Минц (27 декабря 1894 [8 января 1895], Ростов-на-Дону — 29 декабря 1974, Москва) — советский радиофизик, инженер и организатор науки. Разработчик систем связи и радиолокации; один из создателей РЛС дальнего обнаружения и советского синхрофазотрона в Дубне.
🌐 Знаковые события
1851 — французский физик Жан Бернар Леон Фуко доказал, что Земля вращается вокруг своей оси.
#Biography #Birth_Date #Events #08Января
Сти́вен Уи́льям Хо́кинг (англ. Stephen William Hawking; 8 января 1942, Оксфорд — 14 марта 2018, Кембридж) — британский физик-теоретик, космолог, астрофизик и писатель.
Алекса́ндр Льво́вич Минц (27 декабря 1894 [8 января 1895], Ростов-на-Дону — 29 декабря 1974, Москва) — советский радиофизик, инженер и организатор науки. Разработчик систем связи и радиолокации; один из создателей РЛС дальнего обнаружения и советского синхрофазотрона в Дубне.
1851 — французский физик Жан Бернар Леон Фуко доказал, что Земля вращается вокруг своей оси.
#Biography #Birth_Date #Events #08Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 7. Алгоритмы
Глава 1: Основы анализа алгоритмов
Алгоритм — это формализованная последовательность действий, гарантированно приводящая к решению задачи за конечное число шагов. Детерминированность означает, что при одинаковых входных данных алгоритм всегда производит одинаковый результат. Каждый алгоритм обладает двумя фундаментальными характеристиками: корректность (способность решать поставленную задачу) и эффективность (количество ресурсов, необходимых для решения).
Пример задачи: поиск информации
Рассмотрим простую задачу поиска телефонного номера по имени в списке контактов. На первый взгляд тривиальная задача раскрывает фундаментальные различия в подходах к проектированию алгоритмов.
Подход 1: Полный перебор (линейный поиск)
Это наивный подход, основанный на последовательном сравнении искомого значения с каждым элементом коллекции.
Реализация на Java выглядит следующим образом:
Асимптотическая сложность этого алгоритма — O(n), где n — количество контактов. Это означает, что в худшем случае нам потребуется проверить все элементы списка. При поиске в списке из 10 контактов мы выполним до 10 сравнений, в списке из 1 000 000 контактов — до миллиона сравнений.
Подход 2: Индексный поиск (использование хеш-таблиц)
Индексирование — это техника предварительной обработки данных для ускорения последующих операций поиска. Хеш-таблица преобразует ключ (в нашем случае имя контакта) в индекс массива с помощью хеш-функции.
Асимптотическая сложность поиска по хеш-таблице — O(1) в среднем случае. Хеш-функция вычисляет позицию элемента, и мы получаем прямой доступ к нему. Однако этот подход требует дополнительной памяти для хранения индекса и времени на его предварительное построение.
Подход 3: Кэширование результатов
Кэширование — это сохранение результатов выполненных операций для их повторного использования. В отличие от индексации, которая оптимизирует все возможные запросы, кэширование оптимизирует только повторяющиеся запросы.
Этот подход демонстрирует классический компромисс между временем и памятью. При частых повторных запросах к одним и тем же данным эффективность поиска стремится к O(1), но мы тратим дополнительную память на хранение кэша.
#Java #для_новичков #beginner #algorithm
Глава 1: Основы анализа алгоритмов
Алгоритм — это формализованная последовательность действий, гарантированно приводящая к решению задачи за конечное число шагов. Детерминированность означает, что при одинаковых входных данных алгоритм всегда производит одинаковый результат. Каждый алгоритм обладает двумя фундаментальными характеристиками: корректность (способность решать поставленную задачу) и эффективность (количество ресурсов, необходимых для решения).
Пример задачи: поиск информации
Рассмотрим простую задачу поиска телефонного номера по имени в списке контактов. На первый взгляд тривиальная задача раскрывает фундаментальные различия в подходах к проектированию алгоритмов.
Подход 1: Полный перебор (линейный поиск)
Это наивный подход, основанный на последовательном сравнении искомого значения с каждым элементом коллекции.
Реализация на Java выглядит следующим образом:
public class LinearSearch {
public static Contact findContact(List<Contact> contacts, String name) {
for (Contact contact : contacts) {
if (contact.getName().equals(name)) {
return contact;
}
}
return null; // Контакт не найден
}
}Асимптотическая сложность этого алгоритма — O(n), где n — количество контактов. Это означает, что в худшем случае нам потребуется проверить все элементы списка. При поиске в списке из 10 контактов мы выполним до 10 сравнений, в списке из 1 000 000 контактов — до миллиона сравнений.
Подход 2: Индексный поиск (использование хеш-таблиц)
Индексирование — это техника предварительной обработки данных для ускорения последующих операций поиска. Хеш-таблица преобразует ключ (в нашем случае имя контакта) в индекс массива с помощью хеш-функции.
public class IndexedSearch {
private Map<String, Contact> contactIndex;
public IndexedSearch(List<Contact> contacts) {
// Предварительная обработка: построение индекса
contactIndex = new HashMap<>();
for (Contact contact : contacts) {
contactIndex.put(contact.getName(), contact);
}
}
public Contact findContact(String name) {
// Поиск по индексу за постоянное время
return contactIndex.get(name);
}
}Асимптотическая сложность поиска по хеш-таблице — O(1) в среднем случае. Хеш-функция вычисляет позицию элемента, и мы получаем прямой доступ к нему. Однако этот подход требует дополнительной памяти для хранения индекса и времени на его предварительное построение.
Подход 3: Кэширование результатов
Кэширование — это сохранение результатов выполненных операций для их повторного использования. В отличие от индексации, которая оптимизирует все возможные запросы, кэширование оптимизирует только повторяющиеся запросы.
public class CachedSearch {
private List<Contact> contacts;
private Map<String, Contact> cache;
private int cacheHits = 0;
private int cacheMisses = 0;
public CachedSearch(List<Contact> contacts) {
this.contacts = contacts;
this.cache = new HashMap<>();
}
public Contact findContact(String name) {
// Проверка кэша
Contact cached = cache.get(name);
if (cached != null) {
cacheHits++;
return cached;
}
// Кэш-промах: выполняем линейный поиск
cacheMisses++;
for (Contact contact : contacts) {
if (contact.getName().equals(name)) {
// Сохраняем результат в кэш для будущих запросов
cache.put(name, contact);
return contact;
}
}
return null;
}
public double getCacheHitRatio() {
int total = cacheHits + cacheMisses;
return total > 0 ? (double) cacheHits / total : 0.0;
}
}Этот подход демонстрирует классический компромисс между временем и памятью. При частых повторных запросах к одним и тем же данным эффективность поиска стремится к O(1), но мы тратим дополнительную память на хранение кэша.
#Java #для_новичков #beginner #algorithm
👍4🔥1
Корректный vs Подходящий: фундаментальное различие
Корректность алгоритма — обязательное, но недостаточное условие для его применения. Алгоритм считается корректным, если он удовлетворяет спецификации и всегда возвращает ожидаемый результат для любых допустимых входных данных.
Подходящий алгоритм учитывает контекст применения:
Объем и структура входных данных
Частоту выполнения операции
Ограничения по времени отклика
Доступные вычислительные ресурсы
Требования к потребляемой памяти
Рассмотрим влияние выбора алгоритма на различные аспекты системы:
Скорость работы пользовательского интерфейса
В интерактивных системах время отклика критически важно для пользовательского опыта. Исследования в области человеко-компьютерного взаимодействия показывают, что задержки более 100 миллисекунд воспринимаются как нарушение плавности работы интерфейса.
Для поиска в небольшом списке контактов (десятки элементов) линейный поиск может быть вполне приемлем. Однако при работе с большими наборами данных (тысячи элементов) даже асимптотически эффективный алгоритм O(n) становится проблемой. Представьте поле автодополнения, которое должно отфильтровать результаты при каждом нажатии клавиши. Здесь необходим алгоритм с сублинейной сложностью, например, использование префиксного дерева (trie) для мгновенного поиска.
Время пакетной обработки больших данных
В системах обработки данных, где операции выполняются над миллионами или миллиардами записей, разница в асимптотической сложности становится определяющей. Алгоритм O(n²) для миллиарда элементов потребует порядка 10¹⁸ операций, что на современных процессорах займет десятки лет.
Рассмотрим задачу дедупликации записей. Наивный алгоритм попарного сравнения всех элементов имеет сложность O(n²). Алгоритм с предварительной сортировкой (O(n log n)) и последующим линейным проходом (O(n)) сокращает время выполнения на несколько порядков для больших наборов данных.
#Java #для_новичков #beginner #algorithm
Корректность алгоритма — обязательное, но недостаточное условие для его применения. Алгоритм считается корректным, если он удовлетворяет спецификации и всегда возвращает ожидаемый результат для любых допустимых входных данных.
Подходящий алгоритм учитывает контекст применения:
Объем и структура входных данных
Частоту выполнения операции
Ограничения по времени отклика
Доступные вычислительные ресурсы
Требования к потребляемой памяти
Рассмотрим влияние выбора алгоритма на различные аспекты системы:
Скорость работы пользовательского интерфейса
В интерактивных системах время отклика критически важно для пользовательского опыта. Исследования в области человеко-компьютерного взаимодействия показывают, что задержки более 100 миллисекунд воспринимаются как нарушение плавности работы интерфейса.
Для поиска в небольшом списке контактов (десятки элементов) линейный поиск может быть вполне приемлем. Однако при работе с большими наборами данных (тысячи элементов) даже асимптотически эффективный алгоритм O(n) становится проблемой. Представьте поле автодополнения, которое должно отфильтровать результаты при каждом нажатии клавиши. Здесь необходим алгоритм с сублинейной сложностью, например, использование префиксного дерева (trie) для мгновенного поиска.
Время пакетной обработки больших данных
В системах обработки данных, где операции выполняются над миллионами или миллиардами записей, разница в асимптотической сложности становится определяющей. Алгоритм O(n²) для миллиарда элементов потребует порядка 10¹⁸ операций, что на современных процессорах займет десятки лет.
Рассмотрим задачу дедупликации записей. Наивный алгоритм попарного сравнения всех элементов имеет сложность O(n²). Алгоритм с предварительной сортировкой (O(n log n)) и последующим линейным проходом (O(n)) сокращает время выполнения на несколько порядков для больших наборов данных.
// Неэффективный алгоритм дедупликации O(n²)
public List<Record> deduplicateNaive(List<Record> records) {
List<Record> unique = new ArrayList<>();
for (Record r1 : records) {
boolean isDuplicate = false;
for (Record r2 : unique) {
if (r1.equals(r2)) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
unique.add(r1);
}
}
return unique;
}
// Эффективный алгоритм дедупликации O(n log n)
public List<Record> deduplicateEfficient(List<Record> records) {
if (records.isEmpty()) return Collections.emptyList();
// Сортировка позволяет находить дубликаты за один проход
List<Record> sorted = new ArrayList<>(records);
Collections.sort(sorted);
List<Record> unique = new ArrayList<>();
Record previous = sorted.get(0);
unique.add(previous);
for (int i = 1; i < sorted.size(); i++) {
Record current = sorted.get(i);
if (!current.equals(previous)) {
unique.add(current);
previous = current;
}
}
return unique;
}
#Java #для_новичков #beginner #algorithm
👍3
Стоимость инфраструктуры
В облачных средах вычислительные ресурсы измеряются в денежном эквиваленте. Неэффективный алгоритм напрямую влияет на эксплуатационные расходы.
Рассмотрим пример обработки запросов в веб-приложении.
Алгоритм с временной сложностью O(n) для обработки одного запроса при увеличении нагрузки в 10 раз потребует в 10 раз больше вычислительных ресурсов. Алгоритм с оптимизированной сложностью O(log n) при таком же росте нагрузки увеличит потребление ресурсов лишь на постоянную величину.
Важным аспектом является также потребление памяти. Алгоритмы, работающие in-place (без дополнительной памяти), предпочтительнее для обработки больших данных. Однако иногда расход памяти оправдан для достижения лучшего времени выполнения. Этот компромисс известен как trade-off между временем и памятью.
Масштабируемость системы
Масштабируемость — это способность системы справляться с ростом нагрузки. Алгоритмы с неоптимальной асимптотической сложностью становятся узким местом при горизонтальном масштабировании.
Например, алгоритм, требующий полной синхронизации всех узлов кластера для выполнения операции, имеет фундаментальное ограничение на масштабируемость.
В распределенных системах предпочтение отдается алгоритмам, которые:
Минимизируют коммуникацию между узлами
Допускают параллельное выполнение
Обладают свойством идемпотентности (многократное выполнение дает тот же результат)
Рассмотрим алгоритм согласованного хеширования (consistent hashing), используемый в распределенных кэшах и базах данных. Вместо перераспределения всех данных при изменении количества узлов кластера, этот алгоритм перемещает только O(1/n) данных, где n — количество узлов. Это обеспечивает предсказуемую производительность при масштабировании системы.
Практические рекомендации по выбору алгоритма
Профилирование перед оптимизацией: Используйте инструменты профилирования (такие как JProfiler, YourKit, Async Profiler) для идентификации реальных узких мест. Преждевременная оптимизация часто приводит к усложнению кода без значительного выигрыша в производительности.
Учет распределения данных: Эффективность алгоритмов может зависеть от характеристик данных. Хеш-таблицы обеспечивают среднее время O(1), но при плохом распределении хеш-функции могут деградировать до O(n). Деревья поиска гарантируют O(log n), но имеют большую константу.
Анализ частоты операций: Оптимизируйте операции, которые выполняются чаще всего. Если чтение происходит в 100 раз чаще, чем запись, имеет смысл использовать более сложные структуры данных для ускорения чтения, даже в ущерб производительности записи.
Учет аппаратных особенностей: Современные процессоры имеют многоуровневые кэши. Алгоритмы, обладающие локальностью ссылок (обращение к соседним элементам памяти), работают значительно быстрее из-за уменьшения промахов кэша.
Компромисс между разработкой и выполнением: Иногда простой алгоритм с чуть худшей асимптотической сложностью предпочтительнее сложного оптимизированного алгоритма, если он проще в реализации, отладке и поддержке.
#Java #для_новичков #beginner #algorithm
В облачных средах вычислительные ресурсы измеряются в денежном эквиваленте. Неэффективный алгоритм напрямую влияет на эксплуатационные расходы.
Рассмотрим пример обработки запросов в веб-приложении.
Алгоритм с временной сложностью O(n) для обработки одного запроса при увеличении нагрузки в 10 раз потребует в 10 раз больше вычислительных ресурсов. Алгоритм с оптимизированной сложностью O(log n) при таком же росте нагрузки увеличит потребление ресурсов лишь на постоянную величину.
Важным аспектом является также потребление памяти. Алгоритмы, работающие in-place (без дополнительной памяти), предпочтительнее для обработки больших данных. Однако иногда расход памяти оправдан для достижения лучшего времени выполнения. Этот компромисс известен как trade-off между временем и памятью.
Масштабируемость системы
Масштабируемость — это способность системы справляться с ростом нагрузки. Алгоритмы с неоптимальной асимптотической сложностью становятся узким местом при горизонтальном масштабировании.
Например, алгоритм, требующий полной синхронизации всех узлов кластера для выполнения операции, имеет фундаментальное ограничение на масштабируемость.
В распределенных системах предпочтение отдается алгоритмам, которые:
Минимизируют коммуникацию между узлами
Допускают параллельное выполнение
Обладают свойством идемпотентности (многократное выполнение дает тот же результат)
Рассмотрим алгоритм согласованного хеширования (consistent hashing), используемый в распределенных кэшах и базах данных. Вместо перераспределения всех данных при изменении количества узлов кластера, этот алгоритм перемещает только O(1/n) данных, где n — количество узлов. Это обеспечивает предсказуемую производительность при масштабировании системы.
Практические рекомендации по выбору алгоритма
Профилирование перед оптимизацией: Используйте инструменты профилирования (такие как JProfiler, YourKit, Async Profiler) для идентификации реальных узких мест. Преждевременная оптимизация часто приводит к усложнению кода без значительного выигрыша в производительности.
Учет распределения данных: Эффективность алгоритмов может зависеть от характеристик данных. Хеш-таблицы обеспечивают среднее время O(1), но при плохом распределении хеш-функции могут деградировать до O(n). Деревья поиска гарантируют O(log n), но имеют большую константу.
Анализ частоты операций: Оптимизируйте операции, которые выполняются чаще всего. Если чтение происходит в 100 раз чаще, чем запись, имеет смысл использовать более сложные структуры данных для ускорения чтения, даже в ущерб производительности записи.
Учет аппаратных особенностей: Современные процессоры имеют многоуровневые кэши. Алгоритмы, обладающие локальностью ссылок (обращение к соседним элементам памяти), работают значительно быстрее из-за уменьшения промахов кэша.
Компромисс между разработкой и выполнением: Иногда простой алгоритм с чуть худшей асимптотической сложностью предпочтительнее сложного оптимизированного алгоритма, если он проще в реализации, отладке и поддержке.
#Java #для_новичков #beginner #algorithm
👍4
OrderHub. Эволюция проекта из монолита к production-ready микросервису
1. Старт серии. Создаём основу для production-системы.
Я начинаю большой практический курс, в котором мы с нуля спроектируем и разработаем полноценную, отказоустойчивую микросервисную систему на современном Java-стеке.
Главная цель курса — дать вам не разрозненные знания, а целостный опыт разработчика: от написания кода до расследования инцидентов в работающей системе.
Сегодня:
🔹 Полный анонс курса: зачем это всё нужно и что вы получите в итоге.
🔹 Постановка бизнес-задачи и проектирование доменной модели.
🔹 Инициализация Spring Boot 3 проекта с правильной структурой пакетов.
🔹 Создание основных сущностей (Order, OrderItem) и REST API для работы с ними.
🔹 Закладываем фундамент для будущего масштабирования.
Исходный код проекта на GitHub очень ждет Ваших звезд.
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
1. Старт серии. Создаём основу для production-системы.
Я начинаю большой практический курс, в котором мы с нуля спроектируем и разработаем полноценную, отказоустойчивую микросервисную систему на современном Java-стеке.
Главная цель курса — дать вам не разрозненные знания, а целостный опыт разработчика: от написания кода до расследования инцидентов в работающей системе.
Сегодня:
🔹 Полный анонс курса: зачем это всё нужно и что вы получите в итоге.
🔹 Постановка бизнес-задачи и проектирование доменной модели.
🔹 Инициализация Spring Boot 3 проекта с правильной структурой пакетов.
🔹 Создание основных сущностей (Order, OrderItem) и REST API для работы с ними.
🔹 Закладываем фундамент для будущего масштабирования.
Исходный код проекта на GitHub очень ждет Ваших звезд.
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1