Лента постов канала Java: fill the gaps (@java_fillthegaps) https://t.me/java_fillthegaps Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat ru https://linkbaza.com/catalog/-1001451569711 Wed, 13 Aug 2025 09:41:32 +0300
Зачем Redis для задач по расписанию?

На прошлой неделе писала про задачки по расписанию и аннотацию Scheduled. Сегодня расскажу интересный кейс, когда для такой задачи используется очередь Redis.

Типичная реакция джавистов: "Чего? Как? Зачем???"🤯

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

Как вы помните по прошлому посту, сервис на питоне работает в одном потоке ОС. Чтобы задачки выполнялись параллельно по-настоящему, запускаются дополнительные процессы. Как это организовано для отложенных задач:

✍️ Основной процесс описывает задачу, которую нужно выполнить по расписанию
✍️ Отдельный сервис-планировщик следит, когда наступит указанное время
✍️ В нужный момент задача сериализуется и отправляется в очередь Redis
✍️ Сервис-исполнитель забирает задачу из очереди и выполняет
✍️ При необходимости результат отправляется обратно в Redis, и основной сервис его забирает

Основной сервис и сервис-исполнитель - это разные процессы, у них нет разделяемой памяти. Redis нужен, чтобы передать задачу из одного процесса в другой. Ну и как бонус — распределить задачки между исполнителями.

Очередь для такой схемы очень упрощает реализацию. Планировщик просто кидает задачу в очередь. Процесс-исполнитель ничего не выбирает, не сортирует, просто достаёт задачу из начала, и она тут же удаляется. Минимум усилий с обеих сторон.

Плюсы и минусы питоновской реализации:
👎 Больше компонентов, больше инфраструктуры
👎 В рэдисе добавляется служебная очередь для обмена данными
👎 Проблемы конкретных библиотек влияют на инфраструктуру

Например, если задача запланирована НЕ в ближайшие полчаса, она может выполниться несколько раз. Проблема известная и лечится настройками редиса.

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

Зачем изучать подходы в других языках?

Как я писала в прошлом посте, модель многопоточности влияет на архитектуру и инфраструктуру. У каждого языка свои "стандартные решения". Если сервис написан на Python, надо подкручивать определенные настройки Redis. В JS модель многопоточности как в питоне, но задачи по расписанию чаще реализуют через crontab и Mongo.

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

И, конечно, инженерный интерес! У разных инструментов разные подходы, свои преимущества и ограничения. Разбираться, как и за счёт чего решаются задачи, оценивать трейдоффы и выбирать подходящее решение очень интересно. Такие задачки - мои самые любимые🥰
Подробнее
]]>
https://linkbaza.com/catalog/-1001451569711 Mon, 11 Aug 2025 10:06:13 +0300
Как устроена многопоточность в разных языках

Если хотите сделать доклад для конференции — сравните подходы к чему-нибудь в разных языках. Такой доклад можно прочитать на конференциях всех упомянутых языков. Устроить турне по городам, почувствовать себя звездой🥰

Однажды так и сделаю, но пока просто напишу пост, как разные языки работают с потоками. Рассмотрим Java, Python, JavaScript и Go.

Немного базы:
▫️ Если у процессора 8 ядер, в каждый момент времени выполняется максимум 8 задач
▫️ Задачи выполняются потоками операционной системы (ОС). Планировщик ОС распределяет процессорное время между потоками ОС
▫️ Процесс — это запущенная программа. У каждого процесса своя память и ресурсы. Другие процессы не имеют к ним доступа.
▫️Внутри процессов есть потоки. Каждый поток выполняет свою задачу. Потоки могут обмениваться данными через общую память процесса.

Python и JavaScript

В каждом процессе (программе) используется один поток операционной системы.

Потоки в этих языках - это способ логического разделения задач. Например, запрос 1 выполняется в потоке Т1, запрос 2 — в потоке Т2. У каждой задачи своя область видимости и локальные переменные. Один поток ОС переключается между такими "потоками", и так создаётся иллюзия одновременного выполнения.

Один экземпляр сервиса нагружает только одно ядро процессора. Чтобы задействовать 8 ядер, запускают 8 экземпляров сервиса (процессов) + балансировщик.

✅ Нет многопоточных проблем, простой код
💔 Сложная и сильно связанная инфраструктура. Самих сервисов больше. Плюс у процессов нет общей памяти, поэтому обмен данными происходит через БД и очереди

✨ Java (традиционная модель) ✨

Потоки в джаве соотносятся с потоками ОС в отношении 1 к 1. Каждый экземпляр Thread жёстко связан с одним потоком в ОС. Планировщик ОС распределяет процессорное время. Если ядер 8, в каждый момент времени параллельно работают максимум 8 задач.

✅ Простая инфраструктура
✅ Общая память между потоками, обмениваться данными проще и быстрее
✅ Шикарная библиотека java.util.concurrent
💔 Дополнительные сложности: гонки, дедлоки, проблемы с видимостью и атомарностью. Многопоточный код сложно тестировать и дебажить
💔 Число потоков и задач в работе ограничивается ОС

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

✨ Go и Java с виртуальными потоками ✨


Потоки соотносятся с потоками ОС как многие ко многим. Распределением потоков по ядрам ОС занимается сам процесс (JVM или Go runtime)

✅ Нет ограничений на количество потоков, а значит и на количество задач в работе
✅ Более простой код, не нужно экономить на потоках
💔 Многопоточные проблемы остаются

Что тут важно:

Говорят, что язык программирования не влияет на общую архитектуру. Квадратик микросервиса на схеме может быть написан на чем угодно. Это не совсем так. Синтаксис языка и правда не важен, а вот модель многопоточности напрямую влияет и на архитектуру, и на инфраструктуру.

Теорию рассказала, в следующем посте покажу конкретный пример🔥
Подробнее
]]>
https://linkbaza.com/catalog/-1001451569711 Wed, 06 Aug 2025 09:05:00 +0300
Подробнее
]]>
https://linkbaza.com/catalog/-1001451569711 Thu, 31 Jul 2025 10:02:21 +0300
LinkedHashMap: наследование и композиция

Сегодня на примере LinkedHashMap очень чётко покажу проблему наследования. Ни в одной теоретической статье не найдёте такого наглядного примера. Просто бриллиант💎

Действующие лица - классы HashSet, HashMap, LinkedHashSet и LinkedHashMap. В них много общего кода, и организовать нужные связи — задача со звёздочкой.

В JDK эту задачу решили не лучшим образом. Как раз по вине наследования.

Вернёмся в 1998 год. Я смотрела Сейлор Мун, кто-то из подписчиков даже не родился. Ещё в тот год вышла java 1.2. В пакете java.util были 2 всем знакомых класса: HashMap и HashSet.

HashSet сделан на базе HashMap примерно так:
public class HashSet implements Set {
    HashMap map;
    public HashSet() {
        map = new HashMap<>();
    }
    ...
}

Прошло 4 года. В java 1.4 добавились Linked* реализации:
▫️ LinkedHashMap стал наследником HashMap. Тут всё сложилось удачно
▫️ LinkedHashSet стал наследником HashSet. И здесь не всё гладко.

У HashSet внутри HashMap, доступа снаружи к нему нет. А внутри LinkedHashSet должен быть LinkedHashMap. Как заменить объект внутри родителя без изменения существующих методов?

Решение в итоге ужасное. В HashSet добавили такой package private конструктор:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

поле dummy нужно, чтобы этот конструктор отличался от уже существующих.

Только вдумайтесь: в родителя добавили специальный конструктор для конкретного потомка. С его деталями реализации!

Код получился бы чище, если вместо наследования от хэшсета использовать композицию:
class LinkedHashSet implements Set {
  private LinkedHashMap map;
  public LinkedHashSet() {
    this.map = new LinkedHashMap<>();
  }
  ...
}

✅ В HashSet нет лишнего
😑 Нужно скопировать кучу мелких методов типа add, size, contains

На примере LinkedHashSet чётко видна проблема наследования.

У родителя по всем заветам инкапсуляции скрыта реализация. Если наследование изначально не предусмотрено, к внутренним полям родителя нет прямого доступа. Код сложнее переиспользовать, и появляются странные конструкции.

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

Композиция и в моменте, и в перспективе гораздо удобнее. Но 30 лет назад это было не так очевидно.
Подробнее
]]>
https://linkbaza.com/catalog/-1001451569711 Tue, 29 Jul 2025 09:30:03 +0300
Подробнее
]]>