Раздел 6. Коллекции в Java
Глава 8. Дополнительные аспекты коллекций
Практика: В «Библиотеке» сделать коллекцию книг потокобезопасной (CopyOnWriteArrayList). Реализовать неизменяемый список популярных книг для чтения
Перед началом убедитесь, что проект готов, и вспомните ключевые концепции:
CopyOnWriteArrayList: Thread-safe версия List, где модификации создают копию массива (copy-on-write), а чтение — без locks. Идеально для read-heavy сценариев (много чтения, мало записи).
Преимущества: Безопасность без синхронизации, итераторы не fail-fast (не бросают ConcurrentModificationException при mod).
Недостатки: Высокий overhead на память и время для модификаций (копия всего списка), не подходит для write-heavy.
Неизменяемые коллекции: Collections.unmodifiableList делает List read-only — методы mod бросают UnsupportedOperationException. Полезно для constants или защиты данных.
Импорты: java.util.concurrent.CopyOnWriteArrayList, java.util.Collections.
Откройте проект
Запустите IDE, откройте LibraryProject. Проверьте List<Book> books, методы addBook, printAllBooks и т.д.
Импортируйте пакеты: В Library.java добавьте import java.util.concurrent.CopyOnWriteArrayList; и import java.util.Collections;. IDE поможет.
Планирование: Мы изменим поле books на CopyOnWriteArrayList, добавим метод для создания неизменяемого списка популярных книг и протестируем в multi-thread (опционально).
Замена List на CopyOnWriteArrayList для потокобезопасности
CopyOnWriteArrayList — отличный выбор для библиотеки, где чтение (поиск, вывод) частое, а запись (добавление книг) редкое. Это сделает коллекцию thread-safe без manual locks.
Измените поле books
В классе Library замените private List<Book> books = new ArrayList<>(); на private CopyOnWriteArrayList<Book> books = new CopyOnWriteArrayList<>();.
Это обеспечит thread-safety: при add/remove создается копия внутреннего массива, читатели видят snapshot.
Обновите конструктор Library
Если инициализация в конструкторе — обновите на CopyOnWriteArrayList.
Обновите метод addBook(Book book)
Используйте books.add(book); — это thread-safe (копия под капотом).
Добавьте проверку: if (book == null) return; или бросьте IllegalArgumentException.
Выведите сообщение о добавлении.
Обновите методы, использующие books
В printAllBooks() или findBookByTitle используйте for-each или Iterator — они работают на snapshot, безопасны при параллельных mod.
В removeBookByIndex(int index): books.remove(index); — thread-safe.
Реализация неизменяемого списка популярных книг
Неизменяемый список — это read-only view, полезный для "популярных" книг, которые не должны изменяться.
Добавьте поле для популярных книг:
В Library добавьте приватное поле List<Book> popularBooks = new ArrayList<>();.
В конструкторе или методе initPopularBooks() добавьте 3-5 статических книг (new Book("1984", "Orwell", 1949) и т.д.).
Сделайте список неизменяемым:
После заполнения popularBooks присвойте ему Collections.unmodifiableList(popularBooks);.
Теперь методы mod (add, remove) бросят UnsupportedOperationException.
Добавьте метод getPopularBooks():
Возвращайте unmodifiable список — public List<Book> getPopularBooks() { return popularBooks; }.
Это безопасно — внешний код не сможет изменить.
Добавьте метод printPopularBooks():
Переберите unmodifiable список и выведите детали книг.
Попробуйте в Main popularBooks.add(new Book(...)) — поймайте исключение.
#Java #для_новичков #beginner #immutability #Collection #synchronizedList #ConcurrentHashMap #CopyOnWriteArrayList #Практика
Глава 8. Дополнительные аспекты коллекций
Практика: В «Библиотеке» сделать коллекцию книг потокобезопасной (CopyOnWriteArrayList). Реализовать неизменяемый список популярных книг для чтения
Перед началом убедитесь, что проект готов, и вспомните ключевые концепции:
CopyOnWriteArrayList: Thread-safe версия List, где модификации создают копию массива (copy-on-write), а чтение — без locks. Идеально для read-heavy сценариев (много чтения, мало записи).
Преимущества: Безопасность без синхронизации, итераторы не fail-fast (не бросают ConcurrentModificationException при mod).
Недостатки: Высокий overhead на память и время для модификаций (копия всего списка), не подходит для write-heavy.
Неизменяемые коллекции: Collections.unmodifiableList делает List read-only — методы mod бросают UnsupportedOperationException. Полезно для constants или защиты данных.
Импорты: java.util.concurrent.CopyOnWriteArrayList, java.util.Collections.
Откройте проект
Запустите IDE, откройте LibraryProject. Проверьте List<Book> books, методы addBook, printAllBooks и т.д.
Импортируйте пакеты: В Library.java добавьте import java.util.concurrent.CopyOnWriteArrayList; и import java.util.Collections;. IDE поможет.
Планирование: Мы изменим поле books на CopyOnWriteArrayList, добавим метод для создания неизменяемого списка популярных книг и протестируем в multi-thread (опционально).
Замена List на CopyOnWriteArrayList для потокобезопасности
CopyOnWriteArrayList — отличный выбор для библиотеки, где чтение (поиск, вывод) частое, а запись (добавление книг) редкое. Это сделает коллекцию thread-safe без manual locks.
Измените поле books
В классе Library замените private List<Book> books = new ArrayList<>(); на private CopyOnWriteArrayList<Book> books = new CopyOnWriteArrayList<>();.
Это обеспечит thread-safety: при add/remove создается копия внутреннего массива, читатели видят snapshot.
Обновите конструктор Library
Если инициализация в конструкторе — обновите на CopyOnWriteArrayList.
Обновите метод addBook(Book book)
Используйте books.add(book); — это thread-safe (копия под капотом).
Добавьте проверку: if (book == null) return; или бросьте IllegalArgumentException.
Выведите сообщение о добавлении.
Обновите методы, использующие books
В printAllBooks() или findBookByTitle используйте for-each или Iterator — они работают на snapshot, безопасны при параллельных mod.
В removeBookByIndex(int index): books.remove(index); — thread-safe.
Реализация неизменяемого списка популярных книг
Неизменяемый список — это read-only view, полезный для "популярных" книг, которые не должны изменяться.
Добавьте поле для популярных книг:
В Library добавьте приватное поле List<Book> popularBooks = new ArrayList<>();.
В конструкторе или методе initPopularBooks() добавьте 3-5 статических книг (new Book("1984", "Orwell", 1949) и т.д.).
Сделайте список неизменяемым:
После заполнения popularBooks присвойте ему Collections.unmodifiableList(popularBooks);.
Теперь методы mod (add, remove) бросят UnsupportedOperationException.
Добавьте метод getPopularBooks():
Возвращайте unmodifiable список — public List<Book> getPopularBooks() { return popularBooks; }.
Это безопасно — внешний код не сможет изменить.
Добавьте метод printPopularBooks():
Переберите unmodifiable список и выведите детали книг.
Попробуйте в Main popularBooks.add(new Book(...)) — поймайте исключение.
#Java #для_новичков #beginner #immutability #Collection #synchronizedList #ConcurrentHashMap #CopyOnWriteArrayList #Практика
👍4
Тестирование и отладка (объемно)
Базовое тестирование:
В Main добавьте книги, вызовите printAllBooks — всё как раньше.
Неизменяемость тест:
В Main получите getPopularBooks(), попробуйте add/remove — UnsupportedOperationException.
Переберите и выведите — чтение работает.
Отладка нюансов:
В CopyOnWriteArrayList добавьте breakpoint в add — увидите копию массива.
Тестируйте с большим размером (1000 элементов) — замерьте время (System.nanoTime()) для add в multi-thread.
Ловушки: CopyOnWriteArrayList slow для write-heavy — если много добавлений, используйте synchronized List.
Эксперименты (объемно и превышает текущий уровень экспертизы):
Замените на synchronizedList(new ArrayList<>()) — протестируйте thread-safety, но заметьте locks (медленнее для read-heavy).
Добавьте метод addPopularBook(Book book) — но сделайте так, чтобы он работал только до unmodifiable (или используйте builder для init).
Тестируйте с null — CopyOnWriteArrayList позволяет null элементы.
В multi-thread добавьте System.out в add/print — увидите interleaving, но без ошибок.
Измерьте память (Runtime.getRuntime().totalMemory()) перед/после многих add — увидите overhead копий в CopyOnWrite.
Работу данных коллекций мы будем проверять когда дойдем до многопоточности в java.
#Java #для_новичков #beginner #immutability #Collection #synchronizedList #ConcurrentHashMap #CopyOnWriteArrayList #Практика
Базовое тестирование:
В Main добавьте книги, вызовите printAllBooks — всё как раньше.
Неизменяемость тест:
В Main получите getPopularBooks(), попробуйте add/remove — UnsupportedOperationException.
Переберите и выведите — чтение работает.
Отладка нюансов:
В CopyOnWriteArrayList добавьте breakpoint в add — увидите копию массива.
Тестируйте с большим размером (1000 элементов) — замерьте время (System.nanoTime()) для add в multi-thread.
Ловушки: CopyOnWriteArrayList slow для write-heavy — если много добавлений, используйте synchronized List.
Эксперименты (объемно и превышает текущий уровень экспертизы):
Замените на synchronizedList(new ArrayList<>()) — протестируйте thread-safety, но заметьте locks (медленнее для read-heavy).
Добавьте метод addPopularBook(Book book) — но сделайте так, чтобы он работал только до unmodifiable (или используйте builder для init).
Тестируйте с null — CopyOnWriteArrayList позволяет null элементы.
В multi-thread добавьте System.out в add/print — увидите interleaving, но без ошибок.
Измерьте память (Runtime.getRuntime().totalMemory()) перед/после многих add — увидите overhead копий в CopyOnWrite.
Работу данных коллекций мы будем проверять когда дойдем до многопоточности в java.
#Java #для_новичков #beginner #immutability #Collection #synchronizedList #ConcurrentHashMap #CopyOnWriteArrayList #Практика
👍3
👍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