Указатели на внешние классы
— В чём разница между методами getDeclaredField()
и getField()
рефлексии?
Невозможно наследовать свойства родительского класса и приватные свойства одновременно.
— Как загрузить пользовательский класс java.lang.System
с помощью рефлексии?
— Почему переменная в лямбда-выражении должна быть final?
— Понимание прерывания потоков в Java.
— Пользовательская сериализация в Java.
— Динамические прокси в Java:
* Анализ исходного кода динамического прокси JDK.
JDK использует необходимый для генерации прокси-класса `Class`, который наследуется от `Proxy` и реализует наш интерфейс. Экземпляр прокси предоставляет функцию сохранения пользовательской логики `InvocationHandler`.
Все методы в прокси-классе (включая `toString()`) реализуются через метод `invoke` нашего пользовательского `InvocationHandler`, поэтому один и тот же метод прокси-класса будет иметь одинаковую логику.
* Динамический прокси CGLib.
Перехват вызовов методов в перехватчике осуществляется через `proxy.invokeSuper(obj, args);`, что эквивалентно вызову метода суперкласса напрямую из подкласса.
— Проверяемые и непроверяемые исключения.
— Ограничения идеи полной ответственности загрузчика классов.
Вызывающий объект будет использовать только свой собственный `ClassLoader` для загрузки других классов.
— Анализ основных принципов работы ссылок Java.
— Принцип работы PhantomReference
и Cleaner
.
— Можно ли ссылаться на метод с одним параметром с помощью BiConsumer
?
— Как понять выполнение вредоносного кода AccessController.doPrivileged()
?
— Обход песочницы Java.
— Причины задержки при переключении между пользовательским режимом и режимом ядра.
— Диаграмма состояний потока Java.
— Реализация synchronized
.
Синхронизация используется для решения проблем управления ресурсами в параллельной среде. Она может снизить потребление ресурсов и сделать ресурсы управляемыми. В параллельной среде система не может точно знать, сколько задач нужно выполнить и сколько ресурсов требуется выделить в любой момент времени. Это приводит к следующим проблемам:
1. Частые запросы на создание и уничтожение планировщика приводят к дополнительным затратам.
2. Отсутствие средств сдерживания при бесконечном запросе ресурсов.
3. Система не может эффективно управлять распределением внутренних ресурсов.
Процесс: если количество задач в очереди меньше corePoolSize
, то задача упаковывается в Worker и выполняется напрямую. В противном случае задача ставится в очередь и ожидает выполнения. Если очередь задач заполнена, добавляется новый Worker. Когда достигается максимальный размер пула потоков, вступает в силу стратегия отклонения.
— Завершение работы пула потоков.
После вызова shutdown()
состояние пула потоков устанавливается как SHUTDOWN
, и все незанятые потоки прерываются. Однако это не относится к потокам, выполняющим задачи.
Состояние SHUTDOWN
означает, что новые задачи не будут добавляться в пул потоков, а ранее выполнявшиеся задачи завершат своё выполнение. После завершения выполнения задачи поток блокируется в ожидании получения новой задачи, и пул потоков не может перейти из состояния SHUTDOWN
в состояние завершения.
Поэтому в дизайне пула потоков Doug Lea добавил tryTerminated()
во всех местах, где пул потоков может завершиться, чтобы попытаться завершить работу пула. В этом методе проверяется, перешёл ли пул потоков в состояние завершения, нет ли ожидающих выполнения задач, но есть ли ещё потоки. Если да, то один из незанятых потоков пробуждается. Когда этот поток возобновляет выполнение и достигает точки выхода, он продолжает распространять прерывание.
— Разбор UncaughtExceptionHandler
.
— Разбор weakCompareAndSet()
.
weakCompareAndSet()
не создаёт никаких гарантий «случилось до», то есть не добавляет барьеры памяти перед и после операций с volatile полями. Поэтому невозможно гарантировать правильность чтения и записи данных в другие переменные, кроме целевой переменной операции weakCompareAndSet
(целевая переменная обязательно является volatile переменной).
— Модель выполнения CompletableFuture
.
Методы с суффиксом Async делегируют задачу по умолчанию или указанному пулу потоков. Методы без суффикса Async могут использовать предыдущий метод или основной поток, в зависимости от ситуации.
volatile
— Внутренняя реализация volatile
.
Реализуется через Ringbus + MESI протокол, известный как Cache Locking.
MESI примерно означает, что несколько ядер CPU соединены вместе через ringbus. Каждое ядро поддерживает собственное состояние кэша. Если одни и те же данные кэшируются на нескольких ядрах, их состояние будет S (shared). Как только одно ядро изменяет эти данные (состояние становится M), другие ядра мгновенно узнают об этом через ringbus, переводят свои кэши в состояние I (Invalid) и считывают данные из кэша, помеченного как M. Одновременно данные атомарно записываются обратно в основную память. Наконец, состояние кэша снова становится S.
— Интерпретация исходного кода AbstractQueuedSynchronizer
.
Согласно BUG в более ранней версии JDK, когда блокировка освобождается одновременно с использованием doReleaseShared(), поток 1 освобождает блокировку и устанавливает head в 0, ожидая пробуждения потока 1 для попытки получить блокировку. PROPAGATE 状态
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//为了保证传播,当上一个释放线程唤醒等待线程后,等待线程判断是否要唤醒后继节点时有判断依据,在当前 head.ws = 0 的时候,我得设置 ws = PROPAGATE。
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//挺关键的,循环内,我要么对原head完成状态变更,要么我继续循环
if (h == head) // loop if head changed
break;
}
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//此时,释放线程1释放了一个锁,当前线程尝试获取成功后,没有锁了(以 semaphore 为例),r = 0
int r = tryAcquireShared(arg);
//此时,释放线程2又释放了一个锁,但以前的代码因为 head 的 ws = 0,不再继续调用 unparkSuccessor。现在有了 PROPAGATE 状态,对于共享锁释放,如果 ws = 0,会设置 head 的 ws 为 PROPAGATE。
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
} finally {
if (interrupted)
selfInterrupt();
}
}
//设置 head 并传播
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
//此时,虽然 propagate 也就是尝试获取锁的返回值 r = 0,因为在尝试获取锁和现在判断是否要传播之间,的确有释放线程2释放了锁,所以要继续判断。h!=null并且,h.waitStatus = PROPAGATE,证明的确需要唤醒后继线程。如果此时,释放线程2还未完成之前 head 的 ws 的状态改变,释放线程2会检测到 head 指针已经指向了新的节点,释放线程2后重新循环,确保会调用 unparkSuccessor() 或者是在 head 更新之前,完成PROPAGATE状态的变更
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
Самосинхронизирующийся замок имеет определённые недостатки, такие как несправедливость, голодание потоков и потребление ресурсов для синхронизации блокировки, поэтому появился очередь блокировки для управления несколькими самосинхронизирующимися замками.
Узлы в очереди получают блокировку после того, как они становятся головой, устанавливая head для указания на этот узел, одновременно устанавливая thread, pred и next равными null, делая его похожим на виртуальный головной узел с сохранением только указателя на следующий узел. Замена объекта-заголовка, можно сразу перейти в синхронизированный блок.
Поток 2
хочет заблокировать объект a1, обнаруживает, что a1 склонен к потоку 1
, вызывает отмену блокировки, при этом блокировка a1 становится облегчённой.
Поток 2
в течение времени BiasedLockingDecayTime (по умолчанию 25 секунд) склоняет блокировку класса A к количеству блокировок, равному порогу BiasedLockingBulkRebiasThreshold, вызывает массовую переадресацию, увеличивает значение эпохи на единицу, что эквивалентно тому, что все объекты класса А теряют свою склонность и становятся несклонными, а затем перенаправляются. В этот момент, если поток блокирует объект a, объект будет склоняться к этому потоку.
Поток x
(или любой другой поток) блокирует уже склонный к другому потоку объект ax, вызывая отмену склонности, и когда количество отмен склонностей достигает порога BiasedLockingBulkRevokeThreshold, происходит массовая отмена склонностей, новый созданный объект класса A markword изначально не имеет склонности и не может быть склонён.
Устранение атомарных операций синхронизации с помощью склонной блокировки и массовой переадресации
Читать статью о склонной блокировке.
Гипотеза облегчённой блокировки заключается в том, что в реальных программах большинство блокировок не конкурируют за ресурсы.
Гипотеза склонной блокировки состоит в том, что большинство мониторов не только не конкурируют, но и имеют только один поток, который входит и выходит из них в течение их жизненного цикла.
Массовая переадресация предполагает, что когда склонность к отмене достигает определённого порога, склонность текущего объекта больше не приносит пользы, и необходимо выбрать новую склонность. Массовая отмена склонности предполагает, что порог достигнут, и класс действительно конкурирует, поэтому склонная блокировка больше не подходит.
Процесс обновления склонной блокировки:
Lock Record
.При получении склонной блокировки пустой
Lock Record
помещается в текущий стек потоков, и CAS не требуется для заполненияdisplaced markword
.
[Объектные вычисления hashcode могут привести к расширению склонной блокировки]
Когда объект находится в состоянии склонной блокировки и ему необходимо вычислить свой хэш-код идентичности, его склонная блокировка будет отменена, и блокировка расширится до облегчённой блокировки.
[Семантика volatile в Java]
[Анализ склонной блокировки, облегчённой блокировки и тяжёлой блокировки в Java]
[Мониторы деградируют]
Существует несколько глобальных мониторов, которые изначально отсутствуют и создаются по мере необходимости.
[Деградация блокировки в Java]
Это предложение по оптимизации показывает, что в настоящее время тяжёлые блокировки monitor
объектов могут быть удалены во время STW, и удаляются только те monitor
объекты, к которым обращается только VM Thread
.
Таким образом, тяжёлые блокировки могут деградировать до облегчённых блокировок.
[Обеспечение видимости readHolds в ReentrantReadWriteLock]
[Принцип LockSupport.park()]
Если состояние прерывания истинно, park не сможет блокировать.
[Архитектура системы ввода-вывода Java от принципа до применения]
[Блокировка и асинхронность с синхронностью]
✨[Концепция понимания]: Синхронность и асинхронность фокусируются на сотрудничестве между коммуникационными сторонами, синхронность означает, что для текущей бизнес-операции обе стороны должны работать последовательно, асинхронность позволяет им работать параллельно. Блокировка и неблокирование фокусируются на состоянии вызывающего абонента во время ожидания бизнеса, блокируется ли он или занят ожиданием.
На разных уровнях эти концепции проявляются по-разному.
— На уровне межпроцессного взаимодействия синхронность означает блокировку, асинхронность означает неблокировку. — На уровне ввода-вывода Linux синхронность может блокировать или не блокировать, асинхронность всегда не блокирует. — В сетевой связи основное внимание уделяется синхронной блокировке и асинхронной неблокировке. Синхронный означает, что отправитель отправляет запрос после отправки и ожидает результата, прежде чем отправить следующий запрос. Асинхронный означает, что отправителю не нужно ждать результата перед отправкой следующего запроса.
В целом, асинхронное программирование — это стиль управления потоком программы, имитированный языком программирования и API вызовами. Программа может имитировать асинхронные вызовы на синхронных API (например, многопоточность), или она может скрывать асинхронные интерфейсы нижнего уровня.
[Графическое объяснение инструкций байт-кода Java]
[Обзор оптимизации компилятора и оптимизации времени выполнения в Java]
[Оптимизация безопасных точек STW с помощью рукопожатий потока]
Операции отмены склонности блокировки также могут выполняться без перехода программы в безопасную точку.
[Использование параметра запуска javaagent]
[Быстрое и медленное распределение при создании объектов в JVM]
При создании объекта JVM процесс выглядит следующим образом:
[Псевдосовместное использование при кэшировании строк (False Sharing)]
[Понимание TLAB]
[Понимание PLAB]
Потоки находятся в буферах распределения выживших и старых поколений.
[OopMap]
Каждый метод может иметь несколько oopMaps, то есть код метода делится на несколько сегментов в соответствии с safepoint, и каждый сегмент кода имеет свой собственный oopMap, область действия которого ограничена этим сегментом кода.
[CMS] Процесс сбора мусора
CMS GC журнал анализа
CMS сборщик мусора (Garbage Collector) использует журнал для анализа работы сборщика. Журнал содержит информацию о том, какие объекты были удалены из памяти и какие остались.
Concurrent-preclean и concurrent-abortable-preclean — это два этапа процесса очистки памяти. Этап concurrent-preclean используется для очистки «грязных» объектов, которые были помечены как подлежащие удалению. Этап concurrent-abortable-preclean используется для планирования процесса повторной маркировки объектов.
Во время Young GC (Young Generation Garbage Collection) таблица карточек используется для поиска ссылок на объекты в старом поколении. Таблица карточек записывает информацию о том, какие объекты являются «живыми», а какие — «мёртвыми».
Этап preclean очищает «грязные» карточки, а CMS GC поток сканирует область памяти, соответствующую этим карточкам, обновляет устаревшую информацию об объектах и удаляет метки с карточек.
Concurrent-abortable-preclean определяет момент начала процесса повторной маркировки и предотвращает непрерывное приостановление работы приложения (Stop-The-World).
Процесс сбора мусора CMS
Таблица карточек также используется для отслеживания ссылок на объекты старого поколения. Она нужна для поддержки Young GC и CMS. Во время каждого цикла Young GC таблица карточек сбрасывается и повторно сканируется, что удовлетворяет требованиям Young GC, но нарушает работу CMS, так как информация, необходимая CMS, может быть потеряна.
Чтобы избежать потери информации, вне таблицы карточек была создана дополнительная таблица bitmap, называемая mod-union table. Во время работы CMS concurrent marking каждый раз, когда происходит Young GC и таблица карточек должна быть сброшена, соответствующий бит в таблице mod-union обновляется.
Таким образом, во время процесса CMS remark таблица карточек и таблица mod-union содержат всю необходимую информацию об изменениях ссылок в процессе параллельной маркировки.
Почему CMS использует алгоритм «маркировка-очистка»
На последнем этапе очистки, если выполняется сжатие кучи, расположение объектов может измениться, и необходимо временно остановить мутатор.
По сравнению с G1, последний этап очистки CMS не может выбрать определённые области, и время приостановки может быть слишком долгим, поэтому CMS выбирает алгоритм «маркировка-очистка».
Алгоритм маркировки
С точки зрения алгоритма маркировки, CMS использует заголовки объектов для записи того, что объект был помечен, в то время как G1 использует внешнюю битовую карту для этой цели.
Заголовки объектов не совместимы с записью при копировании.
G1
Параметры оптимизации G1
Принцип работы алгоритма сбора мусора G1
Обзор алгоритма сбора мусора G1
Понимание концепции G1
Глубокое понимание журнала GC G1
Различия между SATB и Incremental Update алгоритмами
SATB предполагает, что все живые объекты остаются живыми во время параллельной маркировки, поэтому, когда белая ссылка разрывается с объектом, эта ссылка помещается в стек обхода кучи, делая объект серым.
Incremental Update помечает чёрный объект как серый, когда он ссылается на белый объект, поэтому после завершения параллельной маркировки требуется повторное сканирование корневого набора и перемаркировка серых объектов как чёрных.
Объяснение двух Bitmap в регионе
PrevTAMS и nextTAMS в регионе используются для записи новых выделенных объектов во время параллельной маркировки и сохранения снимка.
Связь между G1-Card Table и RSet
RSet — это структура, которая указывает, кто на кого ссылается.
Она похожа на хеш-таблицу, где ключ — это другой регион, а значение — массив указателей на другие регионы. Во время сборки мусора RSet используется для определения регионов, которые будут использоваться в качестве корней GCRoots. 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0
// Объектная копия, выполнение CSet разделённого на блоки объекта переноса и CSet раздела пространства восстановления [Объектная копия (мс): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
// После выполнения задачи, если очередь задач пуста, рабочий поток инициирует запрос на завершение. [Завершение (мс): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 0.7] [Попытки завершения: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
[GC Worker Другое (мс): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] [GC Worker Всего (мс): Min: 0.1, Avg: 0.4, Max: 0.6, Diff: 0.6, Sum: 1.8] [GC Worker Конец (мс): Min: 3378.7, Avg: 3378.7, Max: 3378.8, Diff: 0.1]
//После объектной копии ссылки изменяются. Здесь, в коде, ссылки на эвакуированный новый объект исправляются. [Исправление корневых ссылок кода: 0.0 мс]
//Удаление nmethod, который больше не ссылается на определённый region, из записи корневого кода RSet. [Очистка корневых ссылок кода: 0.0 мс]
//В любом цикле сбора выполняется сканирование PRT (таблицы для каждого региона), которая записывает код корней в CSet и RSet. Во время сканирования в глобальной таблице карточек делается отметка, чтобы предотвратить повторное сканирование. В конце цикла сбора отметки в глобальной таблице карточек удаляются. [Очистить CT: 0.1 мс] [Другое: 0.7 мс]
//Используется для сбора молодого поколения после параллельной маркировки и в смешанном сборе. В этих процессах сбора, поскольку есть добавление кандидатов старого поколения в раздел, часто необходимо определить диапазон следующего сбора; но в чистом сборе молодого поколения все разделы сбора будут собраны без выбора. [Выбор CSet: 0.0 мс] [Обработка Ref: 0.5 мс] [Регистрация запроса: 0.0 мс] [Перекраска карточек: 0.1 мс] [Огромные регистры: 0.0 мс] [Восстановление огромных: 0.1 мс] [Освобождение CSet: 0.1 мс]
Эден: 304.0M(304.0M)->0.0B(304.0M) Выжившие: 2048.0K->2048.0K Куча: 304.5M(512.0M)->529.0K(512.0M)
[Время: пользователь=0.01 системное=0.00, реальное=0.00 сек]
```
Смешанный сбор журнала GC (процесс и YGC полностью совпадают, только диапазон отличается)
```java
29,268: [Пауза сбора (G1 Evacuation Pause) (смешанная), 0,0059011 сек]
[Параллельное время: 5,6 мс, рабочие GC: 4]
... ...
[Исправление корневых ссылок кода: 0,0 мс]
[Очистка корневых ссылок кода: 0,0 мс]
[Очистить CT: 0,1 мс]
[Другое: 0,3 мс]
... ...
[Эден: 14,0M(14,0M)->0,0B(156,0M) Выжившие: 10,0M->4096,0K Куча: 165,9M(512,0M)->148,7M(512,0M)]
[Время: пользователь = 0,02 системное = 0,01, реальное = 0,00 сек]
```
Цикл параллельной маркировки
```java
//Эта строка журнала является первым этапом глобальной параллельной маркировки, то есть инициализацией маркировки, которая происходит вместе с YGC, а 857M->617M после YGC представляет изменение памяти кучи до и после, 0,0112237 представляет время задержки YGC.
[Пауза сбора (G1 Evacuation Pause) (молодое поколение) (начальная маркировка) 857M->617M(1024M), 0,0112237 сек]
//Начало параллельного сканирования корневого региона. Сканируемый раздел выжившего также называется корневым разделом (Root Region).
[Начало сканирования параллельного корневого региона]
//Завершение параллельного сканирования корневого раздела и подведение итогов времени задержки этого этапа.
[Конец сканирования параллельного корневого раздела, 0,0000525 сек]
[Начать параллельную маркировку]
[Завершить параллельную маркировку, 0,0083864 сек]
//Окончательная маркировка завершает работу, оставшуюся после этапа параллельной маркировки, включая обработку буфера SATB, и подводит итоги времени задержки этого этапа.
[Отметить, 0,0038066 сек]
//Этап очистки будет выполнять вычисления на основе всей информации о маркировке региона, чтобы определить информацию о выживших объектах каждого региона, и отсортировать регионы в соответствии с эффективностью сбора GC.
[Очистка 680M->680M(1024M), 0,0006165 сек]
``` **Для структуры данных, которая легко меняется, в таблице данных требуется лишь небольшая часть записей.**
Индекс условия (ICP) — это метод оптимизации запросов в базах данных, который позволяет перенести фильтрацию условий из кода приложения на уровень базы данных.
ICP (Index Condition Pushdown) — индексная подстановка. Для столбцов, которые покрываются вспомогательным индексом, условие where предварительно фильтруется перед обращением к таблице. Это называется «индексной подстановкой».
Mysql Intent Lock — блокировка намерения. Блокировка намерения — это табличная блокировка, которая автоматически запрашивается при запросе блокировки строки. Она обеспечивает сосуществование блокировок строк и таблиц.
Разница между in и exist. In будет кэшировать данные в выражении, работать с ними в памяти, но перебирать всю таблицу B. Exist не будет кэшировать данные, но для каждой записи в A не нужно перебирать всю таблицу B. In-подзапрос можно оптимизировать до полусоединения. Например, при материализации таблицы соединение может быть автоматически оптимизировано как левое или правое внешнее соединение. Кроме того, его всегда можно преобразовать в оператор exist.
Change buffer — буфер изменений. В памяти change buffer занимает часть пула буферов. На диске change buffer является частью системного табличного пространства и хранит изменения во вспомогательных индексах после закрытия сервера базы данных.
LSN (Log Sequence Number) — номер последовательности журнала. Максимальный LSN в системе > в журнале redo > на грязной странице > в контрольной точке.
Почему разделение на главный и подчинённый серверы повышает производительность при одинаковой нагрузке на чтение и запись?
Как автоинкрементный идентификатор приводит к несогласованности главного и подчинённого серверов?
Горизонтальное разделение сегментов и расширение. Масштабирование по горизонтали, предварительное двойное написание.
Разделение на сегменты и таблицы: решение проблем с транзакциями (https://mp.weixin.qq.com/s/lgxP0mzwwrV05aUmYb7hJA).
Innodb — это механизм хранения данных в MySQL, который обеспечивает высокую надёжность, целостность и производительность данных.
Физическая структура независимого табличного пространства. Табличное пространство состоит из групп, каждая группа содержит 256 областей, а сегмент представляет собой логическую концепцию, состоящую из 64 страниц. Каждый индекс имеет два сегмента: листовой и нелистовой. Сегменты связаны списком, описанным структурой INODE Entry, которая содержит начальный узел группы и расположение страниц в сегменте, содержащих фрагменты. Структура INODE Entry для первой области каждой группы хранит информацию о том, какие из 256 страниц области свободны, в виде битовой карты. Два сегмента каждого индекса имеют указатели на свои корневые страницы в заголовке сегмента Page Header. Кроме того, местоположение корневых страниц всех индексов хранится в Data Directory Header первой области системного табличного пространства. Таким образом, можно найти корневую страницу индекса в словаре данных, затем получить структуру INODE Entry двух сегментов индекса и, наконец, получить все области или пустые страницы в этих сегментах.
Преобразование in-подзапроса в полусоединение.
Преимущество преобразования подзапросов в полусоединения или операторы exist заключается в том, что это может помочь подзапросам использовать индексы.
Процесс восстановления InnoDB.
Механизм восстановления после сбоя. Когда MySQL перезапускается после сбоя, он сначала использует журнал повторов для восстановления целостности базы данных, а затем решает, следует ли повторить или откатить транзакции на основе состояния binlog. Если транзакция сама содержит commit, она повторяется, иначе происходит откат.
Выполнение оператора обновления MySQL.
Решает ли MVCC проблему фантомного чтения?
Согласованность между базой данных и кешем при двойном написании.
Стратегии кеширования. Проблема горячих точек в кеше может быть решена с помощью многоуровневого кеша. При параллельном чтении первый поток успешно получает блокировку и отвечает за обновление основного кеша, последующие потоки возвращают данные из вторичного кеша. При одновременной записи используется распределённая блокировка для обновления вторичного кеша и удаления основного кеша после завершения обновления.
Подробное объяснение внутренней структуры данных Redis.
Принцип репликации при записи в Redis.
Процесс репликации главного и ведомого узлов Redis. При полной репликации во время bgsave данные сохраняются в RDB, и в это время клиентские команды помещаются в client-output-buffer-limit slave 256MB 64MB 60. При частичной репликации, когда мастер и ведомое устройство временно теряют связь, мастер сохраняет команды в repl-backlog-buffer, а ведомое устройство получает их после восстановления соединения. Бин-определение (BeanDefinition), только класс, который был просканирован с помощью @ComponentScan, становится BeanDefinition, остальные временно существуют в configClass.
registerBeanDefinitionForImportedConfigurationClass (configClass);
loadBeanDefinitionsForBeanMethod(beanMethod);
loadBeanDefinitionsFromImportedResources (configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass. getImportBeanDefinitionRegistrars());
В процессе loadBeanDefinitions, если обнаружен новый beanDefinition, он добавляется в candidates и происходит повторный анализ.
@Async разбор исходного кода:
@EnableAsync
вводит AsyncConfigurationSelector.class
, затем ProxyAsyncConfiguration.class
и, наконец, bean AsyncAnnotationBeanPostProcessor
.
AsyncAnnotationBeanPostProcessor
генерирует и содержит аспект AsyncAnnotationAdvisor
.
Когда точка расширения postProcessAfterInitialization()
вызывается, проверяется следующее:
Процесс создания бинов разбор исходного кода:
В createBean есть следующие точки расширения:
Выполняется предварительное обработчик бина InstantiationAwareBeanPostProcessor
, метод postProcessBeforeInstantiation()
.
Происходит создание бина.
Метод postProcessMergedBeanDefinition()
MergedBeanDefinitionPostProcessor
.
postProcessMergedBeanDefinition()
, который анализирует и инициализирует аннотации (@PostConstruct) и уничтожает аннотации (@PreDestory) и кэширует их в lifecycleMetadataCache. После выполнения родительского класса он сам отвечает за кэширование аннотаций @Resource.Выполняется последующий обработчик бина InstantiationAwareBeanPostProcessor
, метод postProcessAfterInstantiation()
.
Перед применением значений свойств выполняется обработчик InstantiationAwareBeanPostProcessor
, метод postProcessPropertyValues()
.
Применяется applyPropertyValues()
, анализируются значения свойств, включая ссылки на бины, которые ещё не загружены.
Вызывается предварительный обработчик бина BeanPostProcessor
, метод postProcessBeforeInitialization()
,
@PostConstruct
.Происходит инициализация бина, если это InitializingBean, вызывается afterPropertiesSet()
.
Вызывается пользовательский метод init-method.
Вызывается последующий обработчик бина BeanPostProcessor
, метод postProcessAfterInitialization()
,
Вызывается метод requiresDestruction
DestructionAwareBeanPostProcessor
, чтобы определить, нужно ли регистрировать логику уничтожения бина.
prepareContext()
метод:
AbstractApplicationContext
метод refresh()
разбор исходного кода:
...
Процесс создания прокси и цепочки вызовов аспектов:
Во время создания бина в точках расширения AbstractAutoProxyCreator.getEarlyBeanReference()
или AbstractAutoProxyCreator.postProcessAfterInitialization()
обнаруживаются бины типа Advisor.class
и бины с аннотациями @Aspect
.
Бины аспектов упаковываются в Advisors, а методы аспектов сортируются в фиксированном порядке.
После выбора подходящих аспектов создаётся прокси текущего бина.
Например, JdkDynamicAopProxy используется как InvocationHandler, и когда его метод invoke() вызывается, он преобразует упорядоченный список аспектов в цепочку перехватчиков MethodInterceptor, и каждый раз, когда вызывается метод прокси-класса, применяется аспект.
Порядок выполнения аспектов:
Для внутренних аспектов Advisor порядок выполнения соответствует Around -> Before -> Around -> After -> AfterReturning -> AfterThrowing
. Этот порядок уже установлен при создании прокси для бина.
Циклические зависимости:
Циклическая зависимость возникает, когда объекты ссылаются друг на друга, образуя цикл. Циклические зависимости могут возникать из-за конструкторов, инъекций и использования prototype. Режимы внедрения свойств, внедрение свойств в режиме одиночки или внедрение методов — три вида.
Spring может решить проблему циклической зависимости при внедрении свойств в режиме одиночки. Решение заключается в том, чтобы создать экземпляр bean после инициализации и перед его использованием, а затем использовать трёхступенчатое кэширование для обеспечения правильности ссылок.
Spring решает проблему циклических зависимостей: почему используется трёхступенчатое, а не двухступенчатое кэширование.
getEarlyBeanReference()
предоставляет SmartInstantiationAwareBeanPostProcessor
возможность раскрыть себя до инициализации bean. Это необходимо, потому что когда BeanPostProcessor
окончательно оборачивает bean в прокси-класс, исходный класс уже недоступен.
@Async вызывает ошибку циклической зависимости: принцип.
На примере этой циклической зависимости показано, как возникает ошибка:
c
и помещает его в трёхступенчатый кэш singletonFactories
.d
и помещает его в трёхступенчатый кэш singletonFactories
.AbstractAutoProxyCreator
генерирует прокси-объект класса c
(номер один). Прокси-объекты создаются с помощью вставки аспектов, и дополнительные прокси не создаются. Прокси-объект класса с
(номер один) присваивается d
.d
, и есть аспект. Создаётся прокси-объект класса d
. Затем, когда применяется @Async
, @Async
post processor проверяет, является ли переданный объект прокси-классом, и если да, то просто добавляет к нему обработку, не создавая новый прокси-класс. Наконец, инициализация d
завершается, и он становится прокси-объектом класса d
. У d
нет ранней ссылки, поэтому проверка циклической зависимости не требуется.c
была раскрыта, требуется проверка циклической зависимости. Обнаруживается, что d
зависит от c
, который находится в alreadyCreated
, что означает, что он был создан ранее и получил раннюю ссылку на c
(номер один), поэтому одиночка c
имеет разные версии в разных ссылках, вызывая ошибку.@Lazy устраняет циклическую зависимость.
Отображение отношений @RequestMapping.
Обработка отображения отношений запускается методом afterPropertiesSet()
в RequestMappingHandlerMapping
. Метод isHandler()
определяет, будет ли bean сканироваться, основываясь на том, отмечен ли он аннотацией @Controller или @RequestMapping.
AbstractAdvisorAutoProxyCreator определяет, следует ли проксировать текущий bean.
В Spring при внедрении зависимостей, когда создаются прокси-классы и когда это обычные bean?
Целевой источник Spring (TargetSource).
Прокси-класс JdkDynamicAopProxy содержит информацию, связанную с целевым объектом, в AdvisedSupport. Когда начинается выполнение цепочки вызовов AOP, метод targetSource.getTarget()
используется для получения реального целевого объекта target. Этот механизм делает вызовы методов гибкими и позволяет расширять их для реализации множества функций, таких как пул целевых объектов и горячая замена (замена целевого объекта во время выполнения).
Интерфейс адаптивного класса T$Adaptive.
В соответствии с параметрами в URL, адаптивные классы SPI загружаются во время выполнения.
Существует три типа реализации классов SPI: адаптивные, обёрточные и обычные. При использовании ExtensionLoader для получения расширения (name) фреймворк автоматически оборачивает обёрточный класс вокруг обычного класса расширения. Например, ProtocolFilterWrapper может создавать цепочку вызовов invoker, и при экспорте() он может вызывать свои расширенные методы.
Класс-обёртка интерфейса.
Можно вызвать методы реализации интерфейса через сгенерированный класс-обёртку, что аналогично отражению.
ReferenceAnnotationBeanPostProcessor наследует AnnotationInjectedBeanPostProcessor для завершения внедрения свойств Reference.
Когда текущий ReferenceBean находится в процессе заполнения Bean, вызывается метод postProcessPropertyValues() в AnnotationInjectedBeanPostProcessor, который наследуется от InstantiationAwareBeanPostProcessorAdapter. Здесь происходит внедрение InjectionMetadata, которое строит ReferenceBean, инициализирует ReferenceBean и вызывает invoker = refprotocol.refer(interfaceClass, urls.get(0));
, чтобы получить invoker. Затем (T) proxyFactory.getProxy(invoker)
присваивает значение свойству ref в ReferenceBean. Таким образом, когда пользователь вызывает метод свойства, аннотированного @Reference, запрос будет направлен к прокси-классу Invoker, сгенерированному протоколом. Invoker, который фактически выполняет вызов через Invoker. Различные протоколы имеют разные основные функции, которые связаны с коммуникацией клиента.
ServiceAnnotationBeanPostProcessor наследует BeanDefinitionRegistryPostProcessor для завершения сканирования классов, аннотированных @Service (указывающих на com.alibaba.dubbo.config.annotation.Service), и для внедрения соответствующих определений Bean ServiceBean в контейнер Spring. Когда bean создаётся, он внедряет свойства в ServiceBean (например, свойство ref).
Фильтр Dubbo: ContextFilter.
ContextFilter используется для передачи вложений в Invocation и RpcContext LOCAL/SERVER_LOCAL.
Локальные корни и локальное маскирование.
Процесс инициализации Dubbo. ДуббоАвтоКонфигурейшн$МножественныйДуббоКонфигКонфигурейшн — это аннотация @EnableDubboConfig, которая импортирует DubboConfigConfigurationRegistrar.class (ссылка: https://www.dazhuanlan.com/2020/02/09/5e3f67a09f421/). Это приводит к импорту DubboConfigConfiguration.Single.class.
Аннотация @EnableDubboConfigBindings в этих классах импортирует DubboConfigBindingsRegistrar.class для обработки значения аннотации @EnableDubboConfigBindings (конфигурация класса элементов массива). В процессе обработки для каждого класса конфигурации dubbo создаются (пустые) BeanDefinition и соответствующий com.alibaba.dubbo.config.spring.beans.factory.annotation.DubboConfigBindingBeanPostProcessor#0. Этот DubboConfigBindingBeanPostProcessor внутренне содержит имя соответствующего beanDefinition.
ServiceAnnotationBeanPostProcessor, который является BeanDefinitionRegistryPostProcessor, находит все классы с аннотацией @Service и регистрирует их как ServiceBean типа beanDefinition, где записывается ссылка на реальный класс поставщика услуг @Service.
Когда экземпляр bean создаётся, метод postProcessBeforeInitialization() DubboConfigBindingBeanPostProcessor сопоставляется с соответствующим именем bean, а затем выполняет соответствующее присвоение значений каждому классу конфигурации Dubbo AbstractConfig в соответствии с префиксом свойства и свойством application.properties.
Во время populateBean() метод applyPropertyValues() AbstractAutowireCapableBeanFactory преобразует свойства serviceBean (например, ref) из RuntimeReference в реальный прокси-класс. Во время разрешения через BeanDefinitionValueResolver.resolveValueIfNecessary()->resolveReference(Object argName, RuntimeBeanReference ref)->bean = this.beanFactory.getBean(refName) завершается разрешение. Затем ServiceBean действует как InitializingBean и при выполнении метода afterPropertiesSet() присваивает значения другим свойствам, таким как provider, protocol, module и т. д., находя соответствующие bean в beanFactory.
После того как все свойства установлены, можно экспортировать сервис.
Процесс сборки Dubbo Reference
В процессе создания экземпляра bean перед populateBean() выполняется applyMergedBeanDefinitionPostProcessors(), который применяет метод postProcessMergedBeanDefinition() каждого bean к каждому MergedBeanDefinitionPostProcessor. В этом методе ReferenceAnnotationBeanPostProcessor выполняет findInjectionMetadata() и вызывает метод postProcessPropertyValues() InstantiationAwareBeanPostProcessor. AnnotationInjectedBeanPostProcessor в этом методе выполняет findInjectionMetadata(), чтобы найти каждый класс с атрибутом @Reference, и упаковывает его как dubbo AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata и кэширует его.
При populateBean() AnnotationInjectedBeanPostProcessor (фактически ReferenceAnnotationBeanPostProcessor с типом аннотации @Reference) вызывается как InstantiationAwareBeanPostProcessor и вызывается его метод postProcessPropertyValues(). Этот метод получает необходимые атрибуты для внедрения из кэша текущего bean и выполняет метод inject() AnnotatedFieldElement, чтобы получить InjectedObject() -> ReferenceAnnotationBeanPostProcessor.doGetInjectedBean() -> buildReferenceBeanIfAbsent(referencedBeanName|ReferenceBean имя, reference|аннотация, injectedType|тип атрибута, getClassLoader()). Затем ReferenceBean устанавливается в конфигурацию dubbo в классе beanFactory. Наконец, этот ReferenceBean генерирует InvocationHandler, и если это удалённый сервис, он инициализируется, устанавливает атрибут ref ReferenceBean, а затем создаёт jdk динамический прокси и успешно внедряется.
Инициализация удалённого сервиса
Вызывается ReferenceConfig.get(), который выполняет инициализацию, создавая соответствующий Invoker в зависимости от протокола, а затем устанавливая Proxy в свойство ref текущего объекта. DubboInvoker содержит базовый коммуникационный клиент.
DubboProtocol refer() для DubboInvoker для создания ExchangeClient
AllChannelHandler
(handler, url).Итак, ChannelHandler — самый внешний слой MultiMessageHandler, самый внутренний слой — requestHandler, который непосредственно обрабатывает данные канала. Отправка и получение данных происходит от внешнего слоя к внутреннему.
Личное мнение: Kafka использует время для создания временных колёс, что снижает сложность добавления задач до O(1). Также временные колёса разделены на сегменты, что ускоряет добавление элементов в очередь задержки delayQueue.
Каждое временное колесо имеет своё собственное временное колесо более высокого уровня.
Каждый элемент timeTask имеет собственное абсолютное время, которое помещается в соответствующий сегмент в зависимости от абсолютного времени. Это означает, что добавление элемента timeTask в временное колесо не зависит от текущего времени currentTime. Затем обновляется время истечения срока действия элемента, помещённого в сегмент (также на основе абсолютного времени элемента).
В очереди задержки delayQueue нет концепции сегментов, только сегменты с абсолютным временем истечения срока действия. Если сегмент предыдущего временного колеса был пройден, а время истечения срока действия какого-либо элемента в этом сегменте изменилось, то элемент необходимо добавить снова.
Время currentTime обновляется только при выходе элемента из очереди задержки. В противном случае оно остаётся неизменным. Это не вызывает проблем, поскольку элементы в очереди задержки имеют абсолютное время истечения срока действия.
currentTime фактически служит только для привязки нескольких временных колёс друг к другу, чтобы обеспечить согласованность между временными колёсами разных уровней, иначе элемент timeTask может быть выполнен при добавлении во временное колесо верхнего уровня. Другая его функция — выполнение задачи, о которой говорится ниже.
Для самого нижнего временного колеса точность currentTime не имеет значения, хотя сейчас она уже не важна. Перед выполнением задачи currentTime будет обновлено естественным образом.
Кстати, выполнение элемента timeTask осуществляется путём повторного добавления элемента (это также позволяет временному колесу деградировать). После того как элемент в очереди задержки достигнет своего времени истечения срока действия, сначала обновляется currentTime, затем добавляется элемент для выполнения.
После создания сообщения NetworkClient клиент отправляет сообщение в соответствующий раздел RecordAccumulator. Несколько сообщений накапливаются в сборщике и объединяются в одну запись. Когда первый пакет записей в разделе заполнен, начинается отправка пакета. При отправке записи из разных разделов группируются по узлам сервера, образуя ClientRequest, и отправляются соответствующим узлам.
Запрос на отправку и ответ на приём от клиента осуществляются через Selector. Когда Selector получает доступное для чтения или записи событие, KafkaChannel передаёт логику обработки через транспортный уровень SocketChannel для фактической отправки.
KafkaChannel содержит Send и NetworkReceive. Send и NetworkReceive используют буферы для хранения данных в канале. Send содержит один буфер, NetworkReceive содержит два буфера: обычный буфер и буфер размера Size. При создании экземпляра выделяется 4 байта для буфера размера. После заполнения буфера размера NetworkReceive считывает размер обычного буфера, выделяет память и начинает получать данные.
Что касается обслуживания потребителей, существует два важных понятия: групповой координатор и групповой лидер:
Групповой координатор — это специальный брокер. Потребители отправляют heartbeats групповому координатору, чтобы сообщить о своём состоянии здоровья при опросе сообщений и фиксации потребления сообщений.
Групповой лидер — это потребитель, первым присоединившийся к группе потребителей. Групповой координатор назначает потребителям список, которым управляет групповой лидер. Групповой лидер отвечает за назначение и переназначение разделов.
Если потребитель выходит из строя или сеть не работает, групповой координатор не получает сердцебиение в течение длительного времени и считает потребителя отключённым, уведомляя группового лидера о необходимости перераспределения разделов, после чего групповой лидер уведомляет группового координатора о результатах перераспределения, а групповой координатор уведомляет потребителей о новых отношениях разделов.
Поток вызывающего абонента consumer caller thread узнает, было ли инициировано повторное распределение, только когда poll() выполняется. Поток heartbeat thread устанавливает флаг только тогда, когда получает ошибку heartbeat error code, и этот флаг проверяется потоком caller thread только при выполнении poll().
Процесс повторного распределения:
Первый этап (FIND_COORDINATOR):
Потребитель находит GroupCoordinator соответствующего потребительского пула на брокере с наименьшей нагрузкой и устанавливает соединение. Запрос FindCoordinatorRequest отправляется на поиск узла.
Второй этап (JOIN GROUP):
Потребитель отправляет запрос JoinGroupRequest на GroupCoordinator. Поле protocol_metadata в запросе получается методом subscription() интерфейса PartitionAssignor.
Если это исходный потребитель, повторно присоединяющийся к потребительскому пулу, перед отправкой запроса будет запущен метод ConsumerRebalanceListener onPartitionsRevoked().
После получения запроса GroupCoordinator выбирает лидера для потребительского пула и стратегию распределения разделов для потребительского пула.
Затем GroupCoordinator возвращает ответ JoinGroupResponse. Однако поле members содержит данные только лидера.
Третий этап (SYNC GROUP):
Лидер потребительского пула распределяет разделы в соответствии со стратегией, полученной от GroupCoordinator, и отправляет запрос SyncGroupRequest на GroupCoordinator. Все потребители отправляют этот запрос, и GroupCoordinator сохраняет план распределения и метаданные потребительского пула в теме _consumer_offsets, а затем отправляет ответ каждому потребителю.
Когда потребитель получает план распределения, он вызывает метод onAssignment() интерфейса PartitionAssignor, а затем вызывает метод OnPartitionAssigned() интерфейса ConsumerRebalanceListener. Затем запускается задача сердцебиения, и потребитель периодически отправляет данные на сервер. GroupCoordinator отправляет HeartbeatRequest, чтобы определить, находятся ли они в сети.
Четвёртый этап (HEARTBEAT)
Начинается потребление перед тем, как потребитель запрашивает последнее смещение потребления через OffsetFetchRequest и продолжает потреблять с этого места. Потребитель поддерживает свою подчинённость группе потребителей и владение разделами, отправляя сердцебиение GroupCoordinator.
Как ZooKeeper гарантирует, что транзакции выполняются по порядку?
Обеспечение согласованности процесса выборов в ZooKeeper.
О проблемах ZooKeeper [1], [2]?
Когда клиентское соединение с узлом прерывается, клиент будет пытаться повторно подключиться после тайм-аута, и транзакция не завершится неудачей, но в конечном итоге успешно.
Неустойчивые решения для ZooKeeper.
Поведение после разделения ZooKeeper.
Обработка переполнения zxid.
Процесс синхронизации данных после сбоя выборов в ZooKeeper.
Процесс обработки sessionMovedException в ZooKeeper.
Быстрый выбор лидера (fast leader election).
Протокол ZAB состоит из четырёх этапов: выборы, обнаружение, синхронизация и широковещательная передача. FLE состоит из трёх этапов: выборы, восстановление и широковещательная передача.
Новый выбранный лидер будет фиксировать предложения предыдущего лидера перед началом широковещательной передачи сообщений. Если предложение p1 было отправлено, но не зафиксировано, лидер выходит из строя, начинается новый раунд выборов, и если новый лидер по какой-либо причине не имеет P1, то P1 возвращается с ошибкой. Если у нового лидера есть P1, P1 будет успешным.
Выборы лидера после завершения состоят из двух этапов: активация лидера и активное сообщение. После выборов лидеру необходимо начать широковещательную передачу сообщений, которая требует двух шагов. Во-первых, лидер активируется. Для активации лидера необходимы два условия: он должен иметь максимальный zxid и быть гарантированным последователем назначенной задачи. Первое условие является жёстким, но второе условие может быть выполнено с высокой вероятностью, и после того, как лидер станет кандидатом, будет проведена вторая проверка активации лидера.
Анализ архитектуры Tomcat.
Исходный код анализа процесса обработки запросов Tomcat.
Reactor — это модель, управляемая событиями, используемая для обслуживания одного или нескольких одновременных клиентских запросов. Она отделяет приём запросов от обработки событий, повышая способность системы обрабатывать параллелизм. Java NIO Reactor основан на многопользовательской технологии ядра системы.
Разница между SocketChannel и ServerSocketChannel.
Анализ исходного кода запуска Netty.
Дизайн таймера времени Netty на основе HashedWheelTimer.
Ошибка JDK Epoll пустого опроса.
Управление памятью в основном направлено на повышение коэффициента использования памяти. Основной идеей простой реализации пула объектов является создание слоя пула памяти поверх кучи JVM и выделение пространства из пула с помощью метода allocate и возврат пространства в пул с помощью метода release. Создание и уничтожение объектов вызывают методы allocate и release большое количество раз, поэтому пулу памяти необходимо решить проблему восстановления фрагментированного пространства после частого запроса и освобождения пространства. На основе этой потребности существуют две стратегии оптимизации распределения памяти: партнёрская система и система slab.
Партнёрская система использует полностью двоичное дерево для управления областями памяти, где левый и правый узлы являются партнёрами друг друга, а каждый узел представляет собой блок памяти. При распределении памяти большие блоки памяти непрерывно делятся пополам, пока не будет найден требуемый минимальный фрагмент памяти. Освобождение памяти проверяет, свободны ли партнёры (левый и правый узлы) освобождаемого фрагмента памяти, и объединяет их в более крупный блок памяти, если они свободны. Например, ChunkList управляет памятью.
Система slab в основном решает проблему фрагментации памяти, разделяя большие блоки памяти на равные фрагменты памяти, образуя набор фрагментов памяти одинакового размера. В соответствии с размером требуемого пространства памяти, память выделяется в виде небольших блоков памяти или их целых кратных, а при освобождении памяти фрагменты памяти возвращаются набору фрагментов памяти. Например, TinySubPage и SmallSubPage.
Метод select() селектора блокируется. PollWrapper отвечает за сохранение текущего зарегистрированного FD на селекторе. Когда вызывается метод select() селектора, адрес памяти pollWrapper передаётся ядру, которое отвечает за опрос pollWrapperFD. Как только событие готово, ядро передаёт событие готовности FD обратно в пользовательское пространство, и поток, заблокированный в методе select(), пробуждается. Возьмём, к примеру, WindowsSelectorImpl, который при создании экземпляра также создаёт экземпляр канала wakeupPipe, который содержит два источника wakeupSourceFd и приёмника wakeupSinkFd. Два файла описания (https://www.yuque.com/myboy-ipf95/kgi7au/kh8vee) (процесс может определить через таблицу дескрипторов файлов, какие входные и выходные потоки уже открыты). Система добавляет wakeupSourceFd в pollWrapper как первый FD, чтобы система могла контролировать его. Когда Select требуется активировать wakeup(), он записывает один байт данных в wakeupSinkFd, который немедленно отправляется на исходный конец wakeupPipe. После этого данные поступают в Source, и Select возвращает результат.
EpollSelectorImpl: принцип реализации (https://zhuanlan.zhihu.com/p/159346800)
NioEventLoop: анализ исходного кода (https://blog.csdn.net/TheLudlows/article/details/82961193)
Обработчики inBound и outBound (https://blog.csdn.net/weixin_41947378/article/details/108059942)
Методы ChannelHandler предоставляют конкретную бизнес-логику, а ChannelHandlerContext отвечает за выполнение цепочки вызовов. ChannelHandlerAdaptor наследует от ChannelHandler, и его методы по умолчанию вызывают методы ChannelHandlerContext.
Входящие события запускаются удалёнными данными, а исходящие события — пользовательским кодом.
Разница между ctx.channel().writeAndFlush() и ctx.writeAndFlush() (https://blog.csdn.net/chehec2010/article/details/90643436) (https://blog.csdn.net/qq_34827263/article/details/100729528) (https://www.cnblogs.com/qdhxhz/p/10234908.html)
Для AbstractNioByteChannel и AbstractNioMessageChannel эти два класса в основном реализуют чтение и запись фреймворка. Их реализация примерно одинакова: AbstractNioByteChannel читает и записывает байтовые массивы, а AbstractNioMessageChannel преобразует байтовый массив при чтении в структурированный объект и сериализует структурированный объект в байтовый массив при записи.
AbstractNioByteChannel определяет три абстрактных метода для реализации реальных операций чтения и записи: doReadBytes, doWriteBytes и doWriteFileRegion.
У AbstractNioMessageChannel есть два абстрактных метода для реализации чтения и записи структурированных данных: doReadMessages и doWriteMessage.
Netty: серверная среда выполнения
При инициализации NioServerSocketChannel он устанавливает свой pipeline. ServerBootstrapAcceptor помещается на предпоследнее место, после чего в bossGroup выбирается NioEventLoop для регистрации. Затем начинается прослушивание.
Каждый объект NioEventLoop создаётся при создании экземпляра NioEventLoopGroup. При создании openSelector() создаёт селектор для каждого NioEventLoop.
Когда соответствующий NioEventLoop получает сигнал SelectionKey.OP_READ или SelectionKey.OP_ACCEPT, метод read() NioMessageUnsafe генерирует соответствующий NioSocketChannel. Затем инициируется конвейер, и когда он достигает ServerBootstrapAcceptor, он назначает childHandler (то есть ChannelInitializer) для нового NioSocketChannel и регистрирует его в workerGroup одного из объектов NioEventLoop, где NioEventLoop обрабатывает канал во время выполнения.
Асинхронная последовательная неблокирующая обработка:
Sentinel: анализ исходного кода (https://mp.weixin.qq.com/s/C6ba2NwWrwjeq617Z4Rd2w)
Основные понятия: — Node представляет различные типы статистических узлов в Sentinel. — defaultnode: каждый ресурс в цепочке вызовов имеет соответствующий узел этого типа. — clusterNode: все одинаковые ресурсы в цепочке совместно используют узел этого типа. — EntranceNode: соответствует каждой цепочке вызовов. — StatisticNode: разные источники ресурсов имеют соответствующий узел.
Каждый ресурс имеет цепочку слотов.
Linux: модель ввода-вывода
Краткое описание модели Linux IO (https://mp.weixin.qq.com/s/3C7Iv1jof8jitOPL_4c_bQ) Подробное описание модели Linux IO (https://www.jianshu.com/p/486b0965c296) Многопользовательский доступ: select, poll, epoll (https://www.wemeng.top/2019/08/22/%E8%81%8A%E8%81%8AIO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E4%B9%8Bselect%E3%80%81poll%E3%80%81epoll%E8%AF%A6%E8%A7%A3/) (https://wenchao.ren/2019/07/Select%E3%80%81Epoll%E3%80%81KQueue%E5%8C%BA%E5%88%AB/)
Технология нулевого копирования в Linux (https://mp.weixin.qq.com/s/0SHaQBgMJ4MlKjX6m08EpQ)
Способы управления передачей информации между устройствами ввода-вывода и основной памятью в модели ввода-вывода Linux включают опрос программ, прерывания и DMA.
Три основных подхода к технологии нулевого копирования в Linux:
Пример чтения данных с диска и записи их в сетевую карту:
Прямой ввод-вывод пользователя: ядро выполняет только необходимую настройку. Хотя существует переключение контекста пользовательского пространства ядра, оно снижает накладные расходы ЦП на копирование.
mmap() + write: часть области пользовательской буферизации отображается в область буферизации ядра, уменьшая однократное копирование ЦП из ядра в пользовательскую память.
sendfile: до вызова команды sendfile данные считывались и записывались с помощью команд read() и write(), что приводило к четырём переключениям контекста и избыточному копированию в пользовательское пространство. Команда sendfile отправляет данные непосредственно в сокетную память, полностью скрывая процесс передачи данных от пользователя. Однако проблема заключается в том, что пользователь не может изменять данные во время процесса.
Sendfile + DMA gather copy: данные, считанные с диска в буфер ядра, должны быть переданы в буфер сокета, а затем скопированы в сетевую карту с помощью DMA. Теперь, благодаря аппаратной поддержке, команда sendfile копирует только информацию о файле, такую как дескрипторы файлов и длину данных, в буфер сокетов. Данные отправляются непосредственно на сетевую карту при передаче, что дополнительно уменьшает однократное копирование процессора.
Splice: sendfile подходит только для копирования данных из файла в сокет, в то время как системный вызов splice реализует нулевую копию данных между двумя файловыми дескрипторами.
Копирование при записи: когда несколько процессов совместно используют буфер ядра, это снижает системные издержки.
Общий буфер: используя пул буферов, он может быть одновременно отображён в пространство пользователя и пространство ядра, устраняя необходимость в копировании.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )