Java Portal | Программирование
13K subscribers
1.14K photos
94 videos
36 files
1.03K links
Присоединяйтесь к нашему каналу и погрузитесь в мир для Java-разработчика

Связь: @devmangx

РКН: https://clck.ru/3H4WUg
Download Telegram
Почему Redis однопоточный (и почему из-за этого он быстрый)

У тебя сервер на 32 ядра, а Redis грузит только одно.

На вид неэффективно. Но при этом Redis остается одной из самых быстрых in-memory баз.
Почему так?

Основной bottleneck обычно не CPU

Для типичных операций Redis вроде GET, SET, INCR CPU почти не тратится.

В реальных условиях упираешься в другое:

- пропускная способность сети (ответы клиентам)
- пропускная способность памяти (RAM <--> CPU cache)
- системные вызовы, TCP, epoll и другая возня ядра

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

Почему мультипоточность сделала бы Redis медленнее?

Если бы несколько потоков трогали одну область данных, понадобились бы:

Локи
Чтобы безопасно читать или писать ключи. Ожидание блокировок добавляет задержки.

Context Switching
Переключение между 32 потоками сжигает ресурсы и ломает кэш-локальность.

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


Это создает "cache thrashing" и ест производительность.

Во многих сценариях эти накладные расходы больше, чем сама работа Redis-команды.

Секрет - однопоточный event loop


Redis работает на неблокирующем event loop (epoll/kqueue).
Он умеет держать тысячи соединений и обрабатывать только те сокеты, у которых есть данные.

Плюсы:

- без блокировок
- предсказуемые задержки
- строгий порядок операций
- минимальный overhead

Работая в памяти, один поток способен обработать сотни тысяч или даже миллионы команд в секунду.

Но небольшая поправка = Redis не полностью однопоточный

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

Остальное вынесено в фоновые потоки.

✔️Главный поток

Выполняет команды и трогает dataset.

✔️BIO-потоки

Для долгих задач:

lazy delete (UNLINK)
синхронизация AOF
фоновые задачи модулей

✔️I/O threads (с версии Redis 6+)

Для чтения запросов и отправки ответов по сети.
При этом сами данные не трогаются, значит = без локов.
Так Redis получает выгоду от распараллеливания, не ломая простоту и предсказуемость.

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

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👀4👍32🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Хочешь избежать проблем, когда Cloudflare падает?

Или когда La Liga блокирует твои страницы?

Разработчик сделал open-source CLI, чтобы можно было легко отключать этот сервис в проектах за пару секунд.

$ npx disable-cloudflare@latest


👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3
В Java такое видел почти каждый:

var user = new User("Jordy", "Colombia", 28, true, "admin");

Код компилируется, но никто не помнит, что означает каждый параметр (если только IDE не подсказывает).

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

Классический конструктор начинает убивать читаемость.

От этого и спасает Builder:

var user = User.builder()
.name("Jordy")
.country("Colombia")
.age(28)
.active(true)
.role("admin")
.build();


Теперь код читается как нормальное предложение.

Не нужно запоминать порядок, угадывать значения и зависеть от IDE. Назначение каждого поля видно сразу.

Причем сам JDK в новых API давно использует этот подход, пример с HttpRequest:

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("url"))
.GET()
.build();


Смысл в том, что когда у объекта много параметров, простой конструктор перестает быть понятным и превращается в препятствие.

Builder это не просто "красивый паттерн".

Это инструмент, который делает код понятным, а создание объектов безопасным и предсказуемым.

Создание объекта не должно выглядеть как угадывание параметров.

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍21🔥63💊1
Java хотела добавить лямбды, но при этом не поломать JVM и не переписывать двадцатилетние API.

Решение? Сделать вид, что в Java есть функции первого класса, используя то, что уже было: интерфейсы с единственным абстрактным методом.

Так появились функциональные интерфейсы.

@FunctionalInterface
interface Operation {
int apply(int x, int y);
}

Operation add = (a, b) -> a + b;

System.out.println(add.apply(3, 4)); // 7


Кажется, что ты передаешь функцию... но нет.

Компилятор создаёт экземпляр Operation.

В Java нет "свободных" функций, как в JavaScript или Kotlin.

Сейчас это используется даже со старыми классами языка.

Например, Runnable существует ещё с Java 1.0 и содержит всего один метод run.

Когда в Java 8 завезли лямбды, стало можно писать так:

new Thread(() -> System.out.println("Hello from a thread")).start();


И старый конструктор Thread(Runnable r) продолжил работать без изменения ни одной строки оригинального API.

Именно это позволило не сломать экосистему и сохранить стабильность, за которую Java так ценят.

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
12👍3🔥2
Путаешь Dockerfile и Docker Compose?

Тогда вот короткое пояснение.

Они дополняют друг друга, но выполняют разные задачи в контейнерной инфраструктуре.

Dockerfile используется для создания и сборки Docker-образов.

Docker Compose используется для запуска контейнеров как части многоконтейнерной среды или с заданными параметрами выполнения.

Примечание. Начиная с версии 1.28.6, Docker Compose по умолчанию ищет compose.yaml или compose.yml, но сохраняет обратную совместимость с docker-compose.yaml/yml. Если в проекте есть оба варианта, приоритет у compose.yaml.

Прикрепил простую визуализацию, чтобы было легче разобраться.

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥43
В начале карьеры я удалил 5GB лог-файл на продовом сервере, который уже почти забился.

Я запустил df -h, ожидая, что использование диска упадёт. Не упало.

Всё ещё показывало 100% занято.

Ни ошибок, ни предупреждений. Просто те же цифры, будто я вообще ничего не удалял.

И вот тогда я понял, что удаление файла не всегда сразу освобождает место.

В Linux то, что мы называем "файлом", на самом деле состоит из двух частей: имени файла (по сути указателя) и inode (где лежат данные и метаданные). Когда ты удаляешь имя файла, ты просто убираешь указатель. Но inode с данными остаётся на диске, пока какой-то процесс держит файл открытым.

В моём случае веб-сервер всё ещё писал в тот самый лог. И хотя я удалил имя файла, процесс продолжал держать открытый файловый дескриптор. Inode оставался живым — его не видно обычными командами, но место он продолжал занимать.

Место освободилось только после перезапуска веб-сервера, когда все дескрипторы закрылись.

Поэтому нужны разные команды, чтобы увидеть реальную картину:

Проверить использование файловой системы:

df -h


Посмотреть реальные размеры директорий:

du -sh /var/log/*


Найти удалённые файлы, которые всё ещё держатся процессами:

lsof +L1


du показывает, что в директориях реально занимает место, а df сколько занято на уровне файловой системы.

Если они не совпадают, почти всегда причина = удалённые файлы, которые всё ещё открыты процессами.

По этой же причине нормальный log rotation не просто удаляет файлы. Такие инструменты, как logrotate, сначала переименовывают файл и отправляют сигнал процессу, чтобы он корректно закрыл старый дескриптор и открыл новый.

Три важных вывода:

Имя файла это всего лишь указатель на inode

Удаление происходит только тогда, когда inode больше никем не используется

При разборе проблем с диском всегда проверяй и df, и du


Мелочь, но понимание этого может спасти от очень странных и неприятных инцидентов в проде.

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍206🔥4😁1
Есть забавная вещь, которая происходит со многими Java-разработчиками. Oни пишут современный код с мышлением старой Java.

И, если честно, их сложно за это винить.

Java менялась куда быстрее, чем комьюнити успевало к этим изменениям адаптироваться.

Годами Java ассоциировалась с огромными классами, кучей геттеров и сеттеров, громоздкими фабриками, бесконечными try/catch блоками. Со стороны казалось, что язык боится любого изменения.

Java была про структуры и правила.

Это был язык принципа = делай явно или не делай вообще.

Но потом подъехали серьезные обновления: лямбды, records, pattern matching, sealed-классы, streams, более выразительный switch, выведение типов, более декларативные API, и стиль, который стал ближе к функциональному.

У всего этого есть плюсы и минусы, понятно.

И внезапно Java перестала ощущаться языком начала двухтысячных и стала языком под 2025 год (ну или под 2020, кому как). Хотя многие до сих пор пишут так, будто на календаре 2010.

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

И главный момент тут в том, что современная Java не заменяет классическую = они существуют параллельно. Хотя со временем, конечно, и "новая Java" тоже станет предметом споров и хейта, как старая.

Язык изменился, но команды, компании, легаси-фреймворки и технические решения десятилетней давности никуда не делись. Они тянут в обратную сторону и фактически продолжают поддерживать устаревшее представление о Java.

Java сегодня это умение жить сразу в двух эпохах.

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

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

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
9
Spring Boot: Spring Data JPA имеет встроенную поддержку пагинации через Pageable.

Лучше использовать пагинацию в репозиториях вместо того, чтобы вытягивать все данные разом.

Вместо такого репозитория:

public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findAll();
}


Лучше так:

public interface BookRepository extends JpaRepository<Book, Long> {
Page<Book> findAll(Pageable pageable);
}


И дальше в сервисном слое:

public Page<Book> getBooks(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return bookRepository.findAll(pageable);
}


👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
Java 21. Виртуальные потоки + структурированная конкуренция в 15 строках:

Запускай 100000+ конкурентных задач почти без оверхеда с виртуальными потоками.

Структурированная конкуренция делает async-код читаемым как синхронный, без утечек и callback-адского.
В реальных проектах даёт 10-кратный рост пропускной способности при гораздо более простом коде.

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍84😁1
Всегда есть шанс, что один и тот же запрос прилетит в ваш API или сервер несколько раз.

Даже если вы отключаете кнопку после первого клика, вероятность уменьшается, но не исчезает.

Это решается идемпотентностью.

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

Например, в платежных системах это обязательное свойство.

Это разница между стабильной системой и системой, которая может уронить бизнес.

Представим эндпоинт /payment/charge.

Если пользователь дважды жмёт кнопку оплатить, приложение не должно интерпретировать это как две транзакции.

По сути решение простое:

клиент отправляет idempotency-key, а сервер гарантирует, что операция будет выполнена только один раз.
Но правильная реализация важна.

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

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

Без идемпотентности API ведет себя непредсказуемо.

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

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍84
Совет по Spring Boot

RestTestClient для тестирования API

RestTestClient это единый инструмент для тестирования REST API, который можно использовать вместо WebTestClient, TestRestTemplate (удалён) или MockMVC.

1. Определи объект RestTestClient в тесте
2. Привяжи его к конкретному компоненту. Это может быть controller, MockMVC, server или Spring context
3. Потом используй RestTestClient в тесте

@SpringBootTest
public class PersonControllerWithHeadersTests {

private WebApplicationContext context;
private RestTestClient restTestClient;

@BeforeEach
public void setup(WebApplicationContext context) {
restTestClient = RestTestClient
.bindToApplicationContext(context)
.build();
}

@Test
void addPersonTest() {
restTestClient.post()
.uri("/persons")
.body(Instancio.create(Person.class))
.exchange()
.expectStatus().is2xxSuccessful()
.expectBody(Person.class)
.value(p -> assertNotNull(p.getId()));
}
}


👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
⚡️Чёрная пятница на Stepik: забери -25% на курс по Linux!

Внутри 20+ модулей: от установки Linux и работы с файлами до сетей, прав, дисков, процессов, автоматизации на Bash и многого другого. Всё сразу закрепляется на практике (200+ заданий с автопроверкой).

Материал подаётся понятным языком, шаг за шагом, на реальных примерах и с наглядными схемами.

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

Есть бесплатные демо-уроки для ознакомления. В ближайшие 48ч курс доступен со скидкой 20% по промокоду «BLACKFRIDAY25»: открыть курс на Stepik
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥1
Java-подсказка: начиная с Java 11, если нужно повторить строку n раз, можно использовать метод repeat(n) у String.

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3🔥3😁1
Если у тебя начинают получаться сложные вложенные подзапросы, лучше перейти на CTE , чтобы сделать запросы читабельнее.

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

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
Java-команды часто ищут прорывы не там.

Spring + JVM получает больше профита от Kotlin, чем от попыток перезабрести фреймворк.

Kotlin прокачивает базу.

А именно база решает, движется ли команда быстро или нет

1. Код становится проще.

Меньше шаблонного мусора.
Более безопасные дефолты.
Четче выраженные намерения.
Команда делает те же фичи быстрее и с меньшим количеством багов.

2. Работа с конкурентностью становится чище.

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

3. Миграция с низкими рисками.

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

Так выглядит распространенный сценарий в успешных командах:

Один инженер пробует -> остальные замечают разницу -> adoption летит вверх.

4. Бизнес тоже ощущает разницу.

Меньше дефектов.
Быстрее онбординг.
Выше скорость разработки.
Лучше асинхронное поведение.
И все это без замены стека.

Kotlin не переписывает ваш проект.
Он исправляет мелочи, которые тормозят бекенд-команду годами.

Если ты уже на Spring + JVM, это самый выгодный апгрейд, который можно выкатить хоть завтра 🍌

👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣5🔥3💊2🤔1