В этой статье представлен принцип работы ввода-вывода операционной системы, проектирование ввода-вывода Java, базовое использование, распространенные методы и реализации высокопроизводительного ввода-вывода в проектах с открытым исходным кодом, а также подробное понимание способа высокопроизводительного ввода-вывода.
Базовые концепты
Прежде чем представить принципы ввода-вывода, давайте рассмотрим несколько основных понятий:
- (1) Операционная система и ядро
Операционная система: системное программное обеспечение, управляющее аппаратными и программными ресурсами компьютера.ядро: основное программное обеспечение операционной системы, отвечающее за управление системными процессами, памятью, драйверами устройств, файлами и сетевыми системами и т. д., а также предоставление приложениям услуг безопасного доступа к компьютерному оборудованию.
- 2 Пространство ядра и пространство пользователя
Для того, чтобы пользовательские процессы не могли напрямую работать с ядром и обеспечить безопасность ядра, операционная система делит адресное пространство памяти на две части:Ядро-пространство, используемый программой ядраПользовательское пространство, для использования пользовательскими процессами В целях безопасности пространство ядра и пространство пользователя изолированы, даже если пользовательская программа выйдет из строя, ядро не пострадает.
- 3 поток данных
Данные в компьютере основаны на передаче сигналов высокого и низкого напряжения во времени.Эти сигналы данных являются непрерывными и имеют фиксированное направление передачи, подобно потоку воды в водопроводе, поэтому концепция абстрактного потока данных ( поток ввода-вывода):Относится к упорядоченному набору байтов с началом и концом.,
Абстрактная роль потока данных:Реализовать развязку программной логики от базового оборудования., путем введения потока данных в качестве уровня абстракции между программой и аппаратным устройством, программирования для общего интерфейса ввода и вывода потока данных, а не конкретных характеристик оборудования, программа и базовое оборудование могут быть независимо и гибко заменены и расширены
Как работает ввод-вывод
1 дисковый ввод-вывод
Типичный диск ввода-вывода для чтения и записи работает следующим образом:
tips:DMA: Полное название Direct Memory Access, представляющее собой механизм, позволяющий периферийным устройствам (аппаратным подсистемам) напрямую обращаться к основной памяти системы. Основываясь на методе доступа DMA, передача данных между основной памятью системы и аппаратным устройством может сохранить все планирование ЦП.
Стоит отметить, что:
- Операции чтения и записи реализованы на основе системных вызовов
- Операции чтения и записи проходят через пользовательский буфер, буфер ядра, и процесс приложения не может напрямую управлять диском.
- Когда процесс приложения читает, он должен блокироваться до тех пор, пока данные не будут прочитаны.
2 сетевых ввода/вывода
Вот введение в самую классическую модель блокирующего ввода-вывода:
tips:recvfrom, функция получения данных через сокет
Стоит отметить, что:
- Операции чтения и записи сетевого ввода-вывода проходят через пользовательские буферы, буферы Sokcet.
- Поток сервера блокируется от вызова recvfrom до тех пор, пока он не вернется с готовой дейтаграммой.После успешного завершения recvfrom поток начинает обрабатывать дейтаграмму.
Дизайн ввода/вывода Java
1 классификация ввода/вывода
Поток данных воплощается и реализуется на языке Java.Как правило, поток данных Java связан со следующими моментами:
-
(1) Направление потокаизвне в программу, называемуювходной поток; из программы наружу, называетсявыходной поток
-
(2) Единица данных потокаПрограмма использует байты как минимальную единицу данных для чтения и записи, называемуюбайтовый поток, с символами в качестве минимальной единицы данных для чтения и записи, называемойпоток символов
-
(3) Функциональные роли потоков
Поток чтения/записи данных с/на определенное устройство ввода-вывода (например, диск, сеть) или объект хранения (например, массив памяти), называемыйузел потока; Подключите и инкапсулируйте существующий поток и реализуйте функцию чтения/записи данных через инкапсулированный поток, который называетсяпоток процесса(или фильтровать поток);
2 интерфейс ввода/вывода
В пакете java.io есть куча классов операций ввода-вывода, их легко понять, когда вы новичок, на самом деле, есть правила, если внимательно их соблюдать: Эти классы операций ввода-вывода находятся вНа основе наследования 4 базовых абстрактных потоков, либо потока узла, либо потока обработки.
2.1 Четыре основных абстрактных потока
Пакет java.io содержит все классы, необходимые для потокового ввода-вывода.В пакете java.io есть четыре основных абстрактных потока, которые имеют дело с потоками байтов и потоками символов соответственно:
- InputStream
- OutputStream
- Reader
- Writer
2.2 Узловой поток
Имя класса ввода/вывода потока узла состоит из типа потока узла + типа абстрактного потока.Обычные типы узлов:
- файл
- Конвейерный конвейер связи между потоками внутри процесса
- ByteArray/CharArray (массив байтов/массив символов)
- StringBuffer/String (буфер строки/строка)
Создание потока узлов обычно передается в источнике данных в конструкторе, например:
FileReader reader = new FileReader(new File("file.txt"));
FileWriter writer = new FileWriter(new File("file.txt"));
2.3 Потоки обработки
Имя класса для обработки потокового ввода-вывода состоит из функции, инкапсулирующей существующий поток, и абстрактного типа потока.
- буфер: Предоставляет функцию буферизации для данных, считываемых и записываемых потоком узла, и данные могут считываться и записываться пакетами на основе буфера для повышения эффективности. Распространенными являются BufferedInputStream, BufferedOutputStream.
- Преобразование потока байтов в поток символов: Реализовано InputStreamReader, OutputStreamWriter
- Преобразование потока байтов в данные базового типа: Здесь основные типы данных, такие как int, long, short, реализованы DataInputStream и DataOutputStream.
- Преобразование потока байтов в экземпляр объекта: используется для реализации сериализации объектов, реализуемой ObjectInputStream и ObjectOutputStream.
Шаблон адаптера/декоратора применяется к потоку обработки, который преобразует/расширяет существующий поток.Создание потока обработки обычно передается в существующем потоке узла или потоке обработки в конструкторе:
FileOutputStream fileOutputStream = new FileOutputStream("file.txt");
// 扩展提供缓冲写
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
// 扩展提供提供基本数据类型写
DataOutputStream out = new DataOutputStream(bufferedOutputStream);
3 Java NIO
3.1 Проблемы со стандартным вводом-выводом
Java NIO (новый ввод-вывод) — это API-интерфейс ввода-вывода (начиная с Java 1.4), который может заменить стандартный API-интерфейс ввода-вывода Java. Java NIO обеспечивает другой способ работы с вводом-выводом по сравнению со стандартным вводом-выводом. для решения стандартного ввода-вывода имеет следующие проблемы:
- (1) Несколько копий данных
Стандартная обработка ввода-вывода, чтобы завершить полное чтение и запись данных, по крайней мере, необходимо прочитать из базового оборудования в пространство ядра, затем прочитать в пользовательский файл, записать из пользовательского пространства в пространство ядра, а затем записать в базовое оборудование
Кроме того, когда нижний уровень выполняет системные вызовы ввода-вывода с помощью таких функций, как запись и чтение, ему необходимо передать данные в буфер, где находятся данные.начальный адрес и длинаИз-за существования JVM GC позиция объекта в куче часто перемещается, и параметр адреса, передаваемый в системную функцию после перемещения, не является реальным адресом буфера.
Это может привести к ошибкам чтения и записи.Чтобы решить вышеуказанные проблемы, при использовании стандартного ввода-вывода для выполнения системных вызовов будет вызвано дополнительное копирование данных: копирование данных из кучи JVM в непрерывную пространственную память за пределами куча (память вне кучи)
Таким образом, всего создается 6 копий данных, а эффективность выполнения низкая.
- (2) Блокировка операций
В традиционной обработке сетевого ввода-вывода такие операции, как запрос на установление соединения (подключение), чтение данных сетевого ввода-вывода (чтение) и отправка данных (отправка), блокируют поток.
// 等待连接
Socket socket = serverSocket.accept();
// 连接已建立,读取请求消息
StringBuilder req = new StringBuilder();
byte[] recvByteBuf = new byte[1024];
int len;
while ((len = socket.getInputStream().read(recvByteBuf)) != -1) {
req.append(new String(recvByteBuf, 0, len, StandardCharsets.UTF_8));
}
// 写入返回消息
socket.getOutputStream().write(("server response msg".getBytes()));
socket.shutdownOutput();
Возьмем приведенную выше серверную программу в качестве примера, когда соединение запроса установлено, сообщение запроса прочитано и сервер вызывает метод чтения, данные клиента могут быть не готовы (например, данные клиента все еще записываются или передается), и поток должен заблокировать метод чтения и дождаться готовности данных
Чтобы реализовать одновременный ответ сервера, каждое соединение должно обрабатываться независимым потоком.Когда количество одновременных запросов велико, для поддержания соединения слишком велики накладные расходы на память и переключение потоков.
3.2 Buffer
Три основных компонента ядра Java NIO: Buffer (буфер), Channel (канал), Selector.
Buffer предоставляет байтовые буферы, обычно используемые для операций ввода/вывода.Обычные буферы включают ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer, соответствующие базовым типам данных: byte, char, double, float, int, long, short, ниже в основном используется наиболее часто используемый ByteBuffer в качестве примера.Нижний уровень буфера поддерживает кучу Java (HeapByteBuffer) или память вне кучи (DirectByteBuffer).
память вне кучиЭто относится к памяти, соответствующей памяти кучи, а объекты памяти выделяются в память вне кучи JVM.Эта память напрямую управляется операционной системой (а не виртуальной машиной. По сравнению с памятью в куче, преимущества использования памяти вне кучи в операциях ввода-вывода в:
- Нет необходимости восстанавливать линию JVM GC, уменьшая использование ресурсов потока GC.
- Когда система ввода-вывода вызывает, непосредственно управляйте памятью вне кучи, что может сохранить копию памяти вне кучи и памяти в куче.
Выделение и освобождение базовой памяти вне кучи ByteBuffer основано на функциях malloc и free.Внешний метод allocateDirect может применяться для выделения памяти вне кучи и возврата объекта DirectByteBuffer, наследующего класс ByteBuffer:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
Восстановление памяти вне кучи основано на переменной-члене класса Cleaner класса DirectByteBuffer, который предоставляет чистый метод, который можно использовать для активной утилизации.Большая часть памяти вне кучи в Netty определяет наличие Cleaner путем записи и активно вызывает метод clean для восстановления; Кроме того, когда объект DirectByteBuffer является сборщиком мусора, соответствующая память вне кучи также освобождается.
tips: для параметра JVM не рекомендуется устанавливать -XX:+DisableExplicitGC, поскольку некоторые фреймворки, использующие Java NIO (например, Netty), будут активно вызывать System.gc() при ненормальном исчерпании памяти, запускать полный сборщик мусора и перерабатывать Объект DirectByteBuffer как рециркулируемая куча Механизм последней гарантии для внешней памяти.Установка этого параметра приведет к тому, что в этом случае внешняя память не будет очищаться.
Память вне кучи основана на переменной-члене класса DirectByteBuffer базового класса ByteBuffer: объект Cleaner, объект Cleaner будет выполнять unsafe.freeMemory(address) в соответствующее время, тем самым освобождая эту память вне кучи.
Под буфером можно понимать набор основных типов данных, массив с непрерывными адресами хранения, поддерживает операции чтения и записи, соответствует режиму чтения и записи и сохраняет текущее состояние положения этих данных через несколько переменных: емкость, положение, предел:
- вместимость Общая длина массива буферов
- position положение следующего элемента данных для работы
- позиция следующего нерабочего элемента в массиве лимитных буферов: limit
3.3 Channel
Концепцию канала можно сравнить с объектами потока ввода-вывода.Операции ввода-вывода в NIO в основном основаны на канале: Данные, считанные из канала: создайте буфер, затем запросите у канала чтение данных. Запись данных из канала: создать буфер, заполнить данные, запросить канал для записи данных
Каналы и потоки очень похожи со следующими отличиями:
- Каналы можно читать и записывать, в то время как стандартные потоки ввода-вывода являются однонаправленными.
- Канал можно читать и записывать асинхронно.Стандартные потоки ввода-вывода требуют, чтобы потоки блокировались и ждали завершения операций чтения и записи.
- Канал всегда основан на буфере Чтение и запись буфера
Реализация наиболее важных каналов в Java NIO:
- FileChannel: используется для чтения и записи данных файлов.Метод, предоставляемый FileChannel, может уменьшить количество копий данных чтения и записи файлов, которые будут представлены позже.
- DatagramChannel: чтение и запись данных для UDP
- SocketChannel: используется для чтения и записи данных TCP, представляя клиентское соединение.
- ServerSocketChannel: слушайте запросы TCP-подключения, каждый запрос создает SocketChannel, обычно используемый для сервера.
Основываясь на стандартном вводе-выводе, нашим первым шагом может быть получение входного потока следующим образом, чтение данных с диска в программу по байтам, а затем переход к следующему шагу.В программировании NIO нам нужно получить канал сначала. , а потом читать и писать
FileInputStream fileInputStream = new FileInputStream("test.txt");
FileChannel channel = fileInputStream.channel();
tips: FileChannel может работать только в режиме блокировки, а ввод-вывод асинхронной обработки файлов был добавлен в JDK 1.7 java.nio.channels.AsynchronousFileChannel.
// server socket channel:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 9091));
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
int readBytes = socketChannel.read(buffer);
if (readBytes > 0) {
// 从写数据到buffer翻转为从buffer读数据
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
System.out.println("server 收到:" + body);
}
}
3.4 Selector
Селектор (selector), который является одним из основных компонентов Java NIO, используется для проверки того, доступно ли состояние одного или нескольких каналов NIO для чтения и записи. Реализуйте один поток для управления несколькими каналами, то есть вы можете управлять несколькими сетевыми подключениями.
Ядро Selector основано на функции мультиплексирования ввода-вывода, предоставляемой операционной системой. Один поток может одновременно отслеживать несколько дескрипторов соединения. программа для выполнения соответствующих операций чтения и записи. Существуют различные реализации, такие как выбор, опрос, epoll и т. д.
Основной принцип работы Java NIO Selector заключается в следующем:
- (1) Инициализировать объект Selector, серверный объект ServerSocketChannel
- (2) Зарегистрируйте событие принятия сокета ServerSocketChannel с помощью Selector.
- (3) Поток заблокирован в selector.select(), когда клиент запрашивает сервер, поток выходит из блокировки
- (4) Получить все события готовности на основе селектора.В это время событие принятия сокета получается первым, а событие события готовности для чтения данных клиента SocketChannel регистрируется с помощью селектора.
- (5) Поток снова блокируется в selector.select(), когда данные о клиентском соединении готовы, доступны для чтения.
- (6) Прочитайте данные запроса клиента на основе ByteBuffer, затем запишите данные ответа и закройте канал.
Пример следующий, полный исполняемый код выложен на github (GitHub.com/Это ты/Это...):
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9091));
// 配置通道为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 注册服务端的socket-accept事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// selector.select()会一直阻塞,直到有channel相关操作就绪
selector.select();
// SelectionKey关联的channel都有就绪事件
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 服务端socket-accept
if (key.isAcceptable()) {
// 获取客户端连接的channel
SocketChannel clientSocketChannel = serverSocketChannel.accept();
// 设置为非阻塞模式
clientSocketChannel.configureBlocking(false);
// 注册监听该客户端channel可读事件,并为channel关联新分配的buffer
clientSocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
}
// channel可读
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
int bytesRead;
StringBuilder reqMsg = new StringBuilder();
while ((bytesRead = socketChannel.read(buf)) > 0) {
// 从buf写模式切换为读模式
buf.flip();
int bufRemain = buf.remaining();
byte[] bytes = new byte[bufRemain];
buf.get(bytes, 0, bytesRead);
// 这里当数据包大于byteBuffer长度,有可能有粘包/拆包问题
reqMsg.append(new String(bytes, StandardCharsets.UTF_8));
buf.clear();
}
System.out.println("服务端收到报文:" + reqMsg.toString());
if (bytesRead == -1) {
byte[] bytes = "[这是服务回的报文的报文]".getBytes(StandardCharsets.UTF_8);
int length;
for (int offset = 0; offset < bytes.length; offset += length) {
length = Math.min(buf.capacity(), bytes.length - offset);
buf.clear();
buf.put(bytes, offset, length);
buf.flip();
socketChannel.write(buf);
}
socketChannel.close();
}
}
// Selector不会自己从已selectedKeys中移除SelectionKey实例
// 必须在处理完通道时自己移除 下次该channel变成就绪时,Selector会再次将其放入selectedKeys中
keyIterator.remove();
}
}
tips: Java NIO реализует высокопроизводительный сетевой ввод-вывод на основе Selector. Он громоздкий в использовании и неудобный в использовании. Как правило, в отрасли используется оптимизация пакетов на основе Java NIO и расширяется инфраструктура Netty с богатыми функциями для достижения элегантной реализации.
Оптимизация высокопроизводительного ввода/вывода
Далее представлена оптимизация высокопроизводительного ввода-вывода в сочетании с популярными в отрасли проектами с открытым исходным кодом.
1 нулевая копия
Технология нулевого копирования используется для сокращения или даже полного исключения ненужных копий процессора при чтении и записи данных, уменьшения использования пропускной способности памяти и повышения эффективности выполнения.Существует несколько различных принципов реализации нулевого копирования.Обычные проекты с открытым исходным кодом описаны ниже. -копировать реализацию
1.1 Нулевая копия Кафки
Kafka предоставляется на основе ядра Linux 2.1, а улучшенная функция отправки файла в ядре 2.4 + DMA Gather Copy, предоставляемая аппаратным обеспечением, обеспечивает нулевое копирование и передачу файлов через сокеты.
Функция завершает передачу файла через системный вызов, уменьшая переключение режима исходного режима чтения/записи. При этом копия данных уменьшается, а подробный процесс отправки файла выглядит следующим образом:
Основной процесс выглядит следующим образом:
- (1) Пользовательский процесс инициирует системный вызов sendfile
- (2) Ядро копирует данные файла с диска в буфер ядра на основе копирования DMA.
- (3) Ядро копирует информацию описания файла (дескриптор файла, длину данных) из буфера ядра в буфер сокета.
- (4) Ядро копирует данные буфера ядра на сетевую карту на основе информации об описании файла в буфере сокета и функции Gather Copy, предоставляемой оборудованием DMA.
- (5) Системный вызов sendfile пользовательского процесса завершается и возвращается
По сравнению с традиционным методом ввода-вывода, метод sendfile + DMA Gather Copy обеспечивает нулевое копирование, количество копий данных уменьшается с 4 до 2, количество системных вызовов уменьшается с 2 до 1, а количество пользовательских процессов переключение контекста изменено с 4 раз DMA Copy на 2 раза, что значительно повышает эффективность обработки
Нижний уровень Kafka основан на transferTo FileChannel в пакете java.nio:
public abstract long transferTo(long position, long count, WritableByteChannel target)
TransferTo отправляет файл, связанный с FileChannel, в указанный канал.Когда потребитель потребляет данные, сервер Kafka отправляет данные сообщения в файле в SocketChannel на основе FileChannel
1.2 Нулевая копия RocketMQ
RocketMQ реализует нулевое копирование на основе mmap + write: mmap() может сопоставлять адрес буфера в ядре с буфером в пользовательском пространстве для реализации совместного использования данных, устраняя необходимость копировать данные из буфера ядра в пользовательский буфер.
tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
Основной процесс mmap + write для достижения нулевого копирования выглядит следующим образом:
- (1) Пользовательский процесс инициирует системный вызов mmap к ядру.
- (2) Выполнить сопоставление адресов памяти между буфером чтения пространства ядра пользовательского процесса и буфером пользовательского пространства.
- (3) Ядро копирует данные файла с диска в буфер ядра на основе копирования DMA.
- (4) Системный вызов mmap пользовательского процесса завершается и возвращается
- (5) Пользовательский процесс инициирует системный вызов записи в ядро.
- (6) Ядро копирует данные из буфера ядра в буфер сокета на основе копирования ЦП.
- (7) Ядро копирует данные из буфера сокета на сетевую карту на основе копирования DMA.
- (8) Системный вызов записи пользовательского процесса завершается и возвращается
Логика хранения и загрузки сообщений в RocketMQ на основе mmap написана в org.apache.rocketmq.store.MappedFile, внутренняя реализация основана на java.nio.MappedByteBuffer, предоставленном nio, а метод map на основе FileChannel получает mmap буфер:
// 初始化
this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
При запросе сообщения CommitLog на основе смещения pos mappedByteBuffer запрашивается размер данных:
public SelectMappedBufferResult selectMappedBuffer(int pos, int size) {
int readPosition = getReadPosition();
// ...各种安全校验
// 返回mappedByteBuffer视图
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(pos);
ByteBuffer byteBufferNew = byteBuffer.slice();
byteBufferNew.limit(size);
return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
}
советы: механизм transientStorePoolEnableЧасть памяти Java NIO mmap не является резидентной памятью и может быть заменена памятью подкачки (виртуальной памятью).Чтобы повысить производительность отправки сообщений, RocketMQ вводит механизм блокировки памяти, который сопоставляет файл CommitLog, который необходимо недавно работал в памяти и обеспечивает функцию блокировки памяти, которая гарантирует, что эти файлы всегда существуют в памяти. Параметр управления этого механизма — transientStorePoolEnable
Поэтому есть 2 способа сохранить данные MappedFile в кисть CommitLog:
- 1 Включите transientStorePoolEnable: запись байтового буфера памяти (writeBuffer) -> фиксация (фиксация) из байтового буфера памяти (writeBuffer) в файловый канал (fileChannel) -> файловый канал (fileChannel) -> сброс на диск
- 2 TransientStorePoolEnable не включен: запись байтового буфера сопоставленного файла (mappedByteBuffer) -> байтовый буфер сопоставленного файла (mappedByteBuffer) -> сброс на диск
RocketMQ реализует нулевое копирование на основе mmap+write, что подходит для сохранения данных и передачи небольших файлов, таких как сообщения бизнес-уровня. Kafka основан на методе отправки файла с нулевым копированием, который подходит для сохранения данных и передачи больших блоков файлов с высокой пропускной способностью, таких как сообщения системного журнала.
tips:Индексный файл Kafka использует метод mmap + write, а сеть отправки файлов данных использует метод sendfile.
1.3 Нулевая копия Netty
В Netty есть два типа нулевого копирования:
- 1 На основе нулевого копирования, реализованного операционной системой, нижний уровень основан на методе TransferTo класса FileChannel.
- 2 На основе оптимизации операций на уровне Java объект кэша массива (ByteBuf) упаковывается и оптимизируется. Путем создания представления данных для данных ByteBuf он поддерживает слияние и сегментацию объектов ByteBuf. Когда нижний уровень сохраняет только одно хранилище данных, это уменьшает ненужные копии.
2 Мультиплексирование
После оптимизации инкапсуляции функций Java NIO в Netty реализация кода мультиплексирования ввода-вывода стала намного элегантнее:
// 创建mainReactor
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
// 创建工作线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
final ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
// 组装NioEventLoopGroup
.group(boosGroup, workerGroup)
// 设置channel类型为NIO类型
.channel(NioServerSocketChannel.class)
// 设置连接配置参数
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
// 配置入站、出站事件handler
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
// 配置入站、出站事件channel
ch.pipeline().addLast(...);
ch.pipeline().addLast(...);
}
});
// 绑定端口
int port = 8080;
serverBootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
} else {
System.err.println("端口[" + port + "]绑定失败!");
}
});
3 Кэш страницы (PageCache)
Кэш страниц (Page Cache) — это кеш файлов операционной системы, который используется для сокращения операций ввода-вывода на диске.В страницах содержимое представляет собой физический блок на диске, и кеш страниц может помочь.Скорость последовательного чтения и записи файлов программой практически близка к скорости чтения и записи памяти., основная причина в том, что ОС использует механизм PageCache для оптимизации производительности операций чтения и записи:
Политика чтения кэша страниц: Когда процесс инициирует операцию чтения (например, процесс инициирует системный вызов read()), он сначала проверяет, есть ли требуемые данные в кэше страниц:
- Если это так, откажитесь от доступа к диску и читайте прямо из кеша страниц.
- Если нет, ядро планирует операцию блочного ввода-вывода для чтения данных с диска, чтения следующих нескольких страниц (не менее одной страницы, обычно трех страниц), а затем помещения данных в страницу в кэше.
Политика записи кэша страниц: Когда процесс инициирует системный вызов записи для записи данных в файл, они сначала записываются в кэш страниц, а затем метод возвращается. На данный момент данные еще не сохранены в файл, Linux только помечает эту страницу данных в кэше страниц как «грязную» и добавляет ее в список грязных страниц.
Затем поток обратной записи флешера периодически записывает страницы из связанного списка грязных страниц на диск, чтобы данные на диске согласовывались с памятью, и, наконец, очищает флаг «грязных». Грязные страницы записываются обратно на диск в следующих трех случаях:
- Свободная память ниже определенного порога
- Грязные страницы находятся в памяти выше определенного порога
- Когда пользовательский процесс вызывает системные вызовы sync() и fsync()
В RocketMQ логическая очередь потребления ConsumeQueue хранит меньше данных и читается последовательно. Благодаря эффекту предварительного чтения механизма кэширования страниц производительность чтения файлов Consume Queue почти близка к памяти чтения, даже когда сообщения накапливаются. влияют на производительность.Предусмотрены две стратегии сброса сообщений:
- Синхронная очистка: после того, как сообщение фактически сохраняется на диске, сторона брокера RocketMQ фактически возвращает успешный ответ ACK стороне производителя.
- Асинхронная очистка может в полной мере использовать преимущества PageCache операционной системы.Пока сообщение записывается в PageCache, отправителю может быть возвращен успешный ACK. Сброс сообщений осуществляется путем отправки фонового асинхронного потока, что снижает задержку чтения и записи и повышает производительность и пропускную способность MQ.
Kafka также использует кеш страниц для достижения высокой производительности при чтении и записи сообщений, которые не будут здесь подробно описываться.
Ссылаться на
"Глубокое понимание ядра Linux - Даниэль П.Бовет"
Наклейки Netty по обучению грамотности в работе с памятью вне кучи Java - Jiangnan Baiyi
Java НИО? Хватит это читать! - Чжу Сяоси
Процесс хранения сообщений RocketMQ — Чжао Кун
Статья для понимания архитектуры модели Netty - caison
Более интересно, добро пожаловать, чтобы обратить внимание на общедоступную архитектуру распределенной системы