Получи случайную криптовалюту за регистрацию!

Java: fill the gaps

Логотип телеграм канала @java_fillthegaps — Java: fill the gaps J
Логотип телеграм канала @java_fillthegaps — Java: fill the gaps
Адрес канала: @java_fillthegaps
Категории: Технологии
Язык: Русский
Количество подписчиков: 10.33K
Описание канала:

Привет! Меня зовут Диана, и я занимаюсь java разработкой с 2013г.
Делюсь опытом/знаниями по темам:
- Java Core
- Вопросы с собеседований
- Best practices
Комплименты, вопросы, предложения: @utki_letyat

Рейтинги и Отзывы

4.50

2 отзыва

Оценить канал java_fillthegaps и оставить отзыв — могут только зарегестрированные пользователи. Все отзывы проходят модерацию.

5 звезд

1

4 звезд

1

3 звезд

0

2 звезд

0

1 звезд

0


Последние сообщения

2022-08-26 09:01:00 IDEA: шорткаты для сверхзвуковой навигации по коду

Посмотреть список методов:
Ctrl + F12

Быстро найти нужный метод или узнать, что вообще умеет класс

Найти класс или файл в проекте: Shift-Shift

Откроется строка поиска, можно ввести начало имени или аббревиатуру класса

Перейти к определению: Ctrl + B

Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации

Вернуться в предыдущий класс:
Ctrl + Alt +
Ctrl + Alt +

IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов

Найти строку по номеру: Ctrl + G

Когда коллега пишет: "проверь условие в строке 850", можно не проматывать огромный класс, а быстро перейти на нужную строку

Очень удобно, обязательно попробуйте
2.5K views06:01
Открыть/Комментировать
2022-08-23 09:05:14Как скопировать коллекцию?

Вопрос хоть и звучит просто, но однозначно ответить на него нельзя. У внимательного разработчика сразу возникнут вопросы по ТЗ:

Как связаны исходник и копия? Если исходная коллекция поменяется, отразится ли это на копии?
Нужна изменяемая или неизменяемая копия?

Для любой комбинации ответов у джавы есть решение:

Изменяемый прокси

Прокси означает, что новый объект работает с теми же ссылками, что и старый.

Изменяемый — что манипуляции с новым списком разрешены и приведут к изменениям в исходнике.

Реализация простейшая:
refList = list

Неизменяемый прокси

ummodifiable = Collections.unmodifiableList(list)

Методы add, remove и replace у нового списка выбрасывают исключение. Менять исходную коллекцию никто не запрещает. Все изменения отобразятся во всех прокси.

Теперь перейдём к группе "копии" (collectedList и copy). Сейчас объясню, чем они отличаются от предыдущих вариантов

Каждый список — это набор ссылок. Исходный лист можно представить так:
ref1 → Order1
ref2 → Order2
list → структура данных, которая работает со ссылками ref1 и ref2

В прокси вариантах мы работаем с тем же list и с тем же набором [ref1, ref2].

В команде "копий" создаётся новый набор ссылок на те же объекты:
ref3 → Order1
ref4 → Order2

"Копии" работают с другим набором ссылок: [ref3, ref4]. Изменение исходного набора никак не влияет на набор ссылок в "копиях".

Ну и реализации:

Изменяемая копия

collectedList = list.stream().collect(toList())

Неизменяемая копия

copy = List.copyOf(list)

Правильный ответ на вопрос перед постом: refList, ummodifiable

Важно: речь идёт только о ссылках и наборах ссылок. Объекты Order не копируются и остаются теми же. Если у объекта [order:1] id изменится на 100, то во всех списках будет [order:100]

Для удобства свела все варианты в табличку:
3.1K views06:05
Открыть/Комментировать
2022-08-23 09:05:14
В каком списке будет три элемента после выполнения кода выше?
Anonymous Poll
15%
Ни в одном
73%
refList
8%
copy
7%
collected
15%
unmodifiable
896 voters3.0K views06:05
Открыть/Комментировать
2022-08-23 09:05:13 ​В каком списке будет ТРИ элемента после выполнения кода?
3.0K views06:05
Открыть/Комментировать
2022-08-19 09:00:37 Генерация ID в распределённой системе, часть 2: базы данных

Коротенькое дополнение к предыдущему посту

Каждая вторая статья про id пишет, что генерация через БД подойдёт только пет-проектам или MVP. И для нормальной работы нужен кластер сервисов, чья единственная задача — выдавать другим сервисам id. Отдельный кластер специальных сервисов, не меньше!!1

На большинстве моих проектов ID сущностей создавались через саму БД, и всё было хорошо. В посте покажу несложный приём, который часто используется.

Начнём сначала. Когда в строке с ID пишется строка

ID SERIAL PRIMARY KEY

внутри БД создаётся счётчик, который увеличивается при каждой вставке

Что не так: чтобы id не повторялись, запросы должны проходить через один экземпляр БД. Если сущности создаются часто и объём данных растёт, то такой подход усложняет масштабируемость. Непонятно, как добавить ещё один экземпляр БД

Но если данных не очень много, то вариант отличный.

Следующий шаг: вся последовательность равномерно делится между экземплярами БД.

Например, для 3 экземпляров БД (шардов) шаг будет равен трём и формируются такие id:
В первом шарде: 1, 4, 7, 10, …
Во втором: 2, 5, 8, 11, …
В третьем: 3, 6, 9, 12, …

Скрипт для второго шарда выглядит так:

CREATE SEQUENCE userIdGen INCREMENT BY 3 START WITH 2;

Нагрузка на БД ниже по сравнению с первым вариантом
Несложно мигрировать с первого варианта на второй
Не все БД поддерживают инкремент с шагом
Для каждого экземпляра БД нужен свой скрипт
Менять количество экземпляров БД — очень волнительный процесс

Разумеется, подход с разделением ID не подходит для всех ситуаций. Но этот приём знают не все, поэтому он заслужил отдельный пост:)
4.4K views06:00
Открыть/Комментировать
2022-08-16 09:01:00 Генерация ID в распределённой системе, часть 1

Задача считается сеньорной и входит в категорию system design. Вариантов решения много, и выбирать нужно с умом. Чтобы примерно сориентироваться, обозначу основные варианты и опорные точки.

Над чем подумать в самом начале:

Насколько уникальным должен быть ID?
В рамках одного сервиса
Уникальным в пределах системы в течение какого-то времени
Глобально уникальным в течение всей жизни системы

Два последних варианта влияют на длину id. Чем он короче, тем скорее наступит переполнение. Чем длиннее — тем больше памяти займёт id

Как часто нужно генерировать id?

Влияет на размер и немного на реализацию

Если сущность отдаётся за пределы системы, что видит внешний пользователь?

id как есть: /user/123
Декодированный id через Base64: /user/MTIz
Зашифрованный id: /user/67FA78

Формат

Возрастающая последовательность

Глобальный счётчик, последовательность в БД или местный AtomicLong

Случайный набор цифр

Глобально уникальный UUID или локальный Random
Самый быстрый вариант

Вариации Snowflake

Формат Snowflake придумали в Twitter. В оригинале id формируется как комбинация

timestamp + machine_id + sequence_id
(значения складываются как строки, а не как числа)

timestamp — количество миллисекунд
machine_id — id сервера
sequence_id — возрастающая последовательность

id содержит что-то полезное
Можно сортировать по полям, входящим в id
Глобальная уникальность

Machine id часто меняют на пару (id рабочей машины + id процесса) или (id датацентра + id сервера). Можно вдохновиться и составить свою комбинацию полей

Технические моменты Snowflake

Чтобы timestamp не получался слишком большим, отсчитывайте миллисекунды от какой-то даты отсчёта.

Machine id извлекается в начале работы сервиса
из распределённого счётчика. Например, из Zookeeper
из конфига, если при развёртывании ведётся счётчик

Для возрастающей последовательности подойдёт локальный AtomicLong или sequence в БД.

Генерация ID в базе данных

Часто говорят, что генерация id через БД — плохое решение. Все сущности должны проходить через один экземпляр БД, чтобы не было дубликатов, и это ограничивает масштабируемость.

В следующем посте расскажу, как решить эту проблему:)

Генерация через БД подходит, если объект и так сохраняется в базе данных. Если взаимодействий с БД нет (например, нужно id сообщения для кафки), конечно, нужны другие решения.

Как формировать Snowflake id на основе sequence в БД?

Шок контент: в хранимой процедуре. Формирование id — технический момент, а не бизнес-логика. Поэтому такое решение ок.

Как формируется UUID, может ли он повторяться?

Стандарт UUID описывает 5 стратегий генерации UUID. Метод UUID.randomUUID() использует Version 4, генерацию с помощью случайных чисел. Я тоже не доверяю случайным числам, но формулы обещают, что всё будет ок
4.5K viewsedited  06:01
Открыть/Комментировать
2022-08-05 09:00:12IDEA: как не потерять важные места в коде

В огромном проекте всегда есть места, которые хочется отметить или быстро находить.

Раньше это делали так:
Ставили неактивный брейкпойнт в нужном месте. В принципе нормально, но иногда сложно вспомнить, что где находится
Добавляли файл в favorites. Файл добавляется целиком, что не очень удобно

Недавно в IDEA убрали favorites и добавили закладки. Супер удобно, ни одна важная строчка теперь не потеряется

Курсор на нужной строке → F11 → появляется закладка
Правый щёлк по закладке → Rename bookmark… → ввести что-то осмысленное
Посмотреть закладки: View → Tool Windows → Bookmarks
5.6K views06:00
Открыть/Комментировать
2022-08-03 09:00:11 RegExp capturing groups

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

Базовый синтаксис регулярных выражений можно почитать, например, в этой статье с кучей картинок. Группы — это уже уровень intermediate:)

Допустим, есть строка src:
Stack: Java 17, Spring Boot 2.7.1

Наша задача — вытащить отсюда версию джавы.

Этап 1. Начнём с простого варианта:

String p = "Java\\s\\d+";
Matcher m = Pattern.compile(p).matcher(src);
if (m.find()) {
res = m.group(); // Java 17
}
String version = res.substring(5); // 17

Этап 2: подключаем группы. В паттерн добавляются скобки

String p = "Java\\s(\\d+)";

После m.find() получаем следующее:
m.group() или m.group(0) → Java 17. В нулевой группе всегда содержится целиком найденное выражение
m.group(1) → 17

Получаем сразу версию, и substring нам больше не нужен

Пример посложнее. Из строки
Stack: Java 17, Spring Boot 2.7
нужно извлечь версию java и spring boot. Сделаем паттерн по аналогии:

String p = "Java\\s(\\d+).+Boot\\s(\\d+\\.\\d+)";

Здесь два выражения в скобках и, соответственно, две группы (помимо нулевой):

if (m.find()) {
javaVersion = m.group(1)); // 17
bootVersion = m.group(2); // 2.7
}

Чтобы сделать код более читаемым, дадим группам имена. Вот так:
(?X)
Где name — имя, X — регулярка.

Теперь обновляем паттерн
String p = "Java\\s(?\\d+).+Spring\\sBoot\\s(?\\d+\\.\\d+)";

Извлекаем версии
javaVersion = m.group("java");
bootVersion = m.group("boot");

Готово! Без групп пришлось бы использовать два паттерна и всякие методы с индексами, а с группами получился лаконичный и понятный код
5.1K views06:00
Открыть/Комментировать
2022-07-21 09:00:08Message brokers, часть 2: Kafka

Если RabbitMQ — это 100% очередь, то Kafka больше похожа на список, потому что данные после чтения не удаляются. В принципе это основное отличие двух брокеров, остальное — просто следствие.

Один список называется partition. Несколько partition можно объединить в группу, которая называется topic.

Консьюмеры читают данные из партишена или топика. Для каждого консюмера хранится индекс последнего прочитанного сообщения (offset). Когда получатель прочитает сообщение, Kafka сдвинет его offset. И в следующий раз этот получатель прочитает другие сообщения.

Если в partition 10 сообщений, то
Один консьюмер прочитает сразу всё
Другой прочитает 5 и потом ещё 5
Третий будет вычитывать по одному сообщению

И никто никому не мешает

В рамках одного partition все консюмеры читают сообщения в одном порядке. Иногда это очень важная фича. Для топика из нескольких partition такой гарантии нет.

Поскольку данные не удаляются при чтении, получаются немного другие схемы работы:

Если сообщение должны прочитать несколько однотипных получателей, достаточно записать их в один partition

Если получатели разнотипные, то продюсер должен добавить данные в несколько партишенов.
Пример: чтобы сообщение “A to C vip” прочитали C1 и C4, продюсер отправляет запись в топик orders и vip_orders.

Если нужно распределить сообщения по получателям, то консьюмеры объединяются в consumer group с общим оффсетом

Резюме

В Kafka сообщения не пропадают при чтении, их можно читать несколько раз и пачками
Гарантия порядка сообщений в рамках одного partition
Kafka занимает горааааздо больше места на диске
Kafka использует pull модель — получатели сами решают, когда забрать сообщения. В RabbitMQ инициатива исходит от очереди, чтобы равномерно распределять сообщения
Разные схемы общения с продюсерами и консьюмерами. На картинке ниже я представила аналог схемы из предыдущего поста
Разные сценарии масштабирования и отказоустойчивости
Субъективное мнение — в рэббите проще распределять сообщения по получателям. Kafka подходит для накопления данных и более сложных сценариев
Объективное — Kafka используется на бОльшем количестве проектов, пусть даже в качестве простой очереди

Общие черты двух брокеров:

Отлично поддерживаются спрингом
Можно настроить хранение сообщений на диске
Нужно супер тщательно продумать схему работы и масштабирование

PS Эти посты — самые основы месседж брокеров, прямо вот верхушечка. Для дальнейшего изучения подойдёт эта серия статей, книги "RabbitMQ in Action" и "Kafka in Action".
6.5K views06:00
Открыть/Комментировать
2022-07-19 09:20:00Message brokers, часть 1: RabbitMQ

Оба брокера реализуют паттерн publish/subscribe. Его основные участники это

Producer — отправляет сообщения. Сообщение состоит из ключа, значения и хэдеров
Consumer — принимает сообщения
Message broker — компонент для обмена сообщениями, разворачивается отдельно

Основная структура данных для распределения сообщений — очередь. Обычно в системе целое море продюсеров, очередей и консьюмеров. Чтобы упростить общение между ними, RabbitMQ использует промежуточный слой — exchangers.

Принцип работы:

Продюсеры просто отправляют сообщение в эксченджер. Оттуда оно распределяется в подходящие очереди в зависимости от ключа, хэдеров и настроек эксченджера. Сообщение копируется во все подходящие очереди.

Консьюмер подсоединяется к интересным очередям и забирает оттуда сообщения.

Чтобы было понятно, как это выглядит, посмотрите на картинку внизу поста

Сообщение удаляется из очереди после прочтения. Отсюда идут следующие схемы:

Если сообщение должны прочитать несколько получателей — у каждого должна быть своя очередь, куда это сообщение попадёт.

Пример: сообщение order.from-A.to-C. vip попадает в две очереди — order.from-A.* и order.*.*.vip

Если нужно распределить сообщения между получателями, консьюмеры подключаются к одной очереди. RabbitMQ распределяет сообщения между ними равномерно по принципу round-robin.

Пример: сообщение order.from-A.to-B и order.from-A.to-C распределяются между консюмерами С1 и С2

Рэббит использует push модель — каждое сообщение из очереди вызывает коллбэк на консьюмере. Это нужно, чтобы равномерно распределять сообщения между получателями.

Очереди и связи задаются не в брокере, а в приложении. Роутинг сообщений получается супер гибким, и добавлять новых участников в систему легко. Из разных типов эксченджеров и очередей собирается огромное количество паттернов.

Главное в RabbitMQ:
Основной компонент — exchanger и связанные с ним очереди
Сообщения удаляются после прочтения
Push-модель
Гибкий роутинг сообщений
5.0K viewsedited  06:20
Открыть/Комментировать