Стрим highWaterMark недоразумение

После прочтения некоторого кода на Github кажется, что я неправильно понял, как highWaterMark концепция работает.

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

1) Пока highWaterMark предел не достигнут, поток может буферизовать и записывать данные.

2) Если highWaterMark предел достигнут, поток больше не может буферизоваться, поэтому метод #write возвращает false, чтобы вы знали, что то, что вы пытались записать, не будет записано (никогда).

3) Как только поток испускает drain событие, это означает, что буфер был очищен, и вы можете снова написать, откуда вы получили "отклонено".

На мой взгляд, это было ясно и просто, но похоже, что это не совсем верно (на шаге 2), действительно ли данные, которые вы пытаетесь записать, действительно "отклонены", когда метод #write возвращает false? Или это буфер (или что-то еще)?

Извините за основной вопрос, но я должен быть уверен!

3 ответа

Решение

2) Если достигнут предел highWaterMark, поток больше не может буферизоваться, поэтому метод #write возвращает false, чтобы вы знали, что то, что вы пытались записать, не будет записано (никогда).

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

Ваш вопрос адресован в writable.write(chunk[, encoding][, callback]) документы:

Это возвращаемое значение является строго рекомендательным. Вы МОЖЕТЕ продолжать писать, даже если он вернется false, Однако записи будут буферизироваться в памяти, поэтому лучше не делать это чрезмерно. Вместо этого подождите 'drain' событие, прежде чем писать больше данных.

действительно ли данные, которые вы пытаетесь записать, "отклонены", когда метод #write возвращает false? Или это буфер (или что-то еще)?

Данные буферизуются. Однако чрезмерные призывы к write() без возможности опустошения буфера приведет к высокому использованию памяти, низкой производительности сборщика мусора и даже может вызвать сбой Node.js с Allocation failed - JavaScript heap out of memory ошибка. Смотрите этот связанный вопрос:

Узел: fs write() не записывает внутри цикла. Почему бы и нет?


Для справки, вот некоторые важные подробности оhighWaterMarkи противодавление от текущей документации (v8.4.0):

writable.write()

Возвращаемое значение true если внутренний буфер меньше, чем highWaterMark настроен, когда поток был создан после принятия chunk, Если false дальнейшие попытки записи данных в поток должны прекратиться до 'drain' событие испускается.

Пока поток не сливается, звонки write() будет буфер chunk, и вернуться false, После того, как все буферизованные фрагменты слиты (приняты для доставки операционной системой), 'drain' событие будет выпущено. Рекомендуется один раз write() возвращается falseбольше никаких кусков не будет написано до 'drain' событие испускается. Во время звонка write() в потоке, который не является сливом, Node.js будет буферизовать все записанные фрагменты до тех пор, пока не произойдет максимальное использование памяти, после чего он будет прерван безоговорочно. Даже до того, как он прервется, высокое использование памяти вызовет низкую производительность сборщика мусора и высокую RSS (которая обычно не высвобождается обратно в систему, даже если память больше не требуется).

Противодавление в потоках

В любом случае, когда буфер данных превысил highWaterMark или очередь записи в данный момент занята, .write() вернусь false,

Когда false значение возвращается, система противодавления срабатывает. Это приостановит входящий Readable поток от отправки любых данных и ждать, пока потребитель не будет готов снова. Как только буфер данных очищен, .drain() событие будет отправлено и возобновит поток входящих данных.

После завершения очереди обратное давление позволит снова отправлять данные. Используемое пространство в памяти освободится и подготовится к следующему пакету данных.

               +-------------------+         +=================+
               |  Writable Stream  +--------->  .write(chunk)  |
               +-------------------+         +=======+=========+
                                                     |
                                  +------------------v---------+
   +-> if (!chunk)                |    Is this chunk too big?  |
   |     emit .end();             |    Is the queue busy?      |
   +-> else                       +-------+----------------+---+
   |     emit .write();                   |                |
   ^                                   +--v---+        +---v---+
   ^-----------------------------------<  No  |        |  Yes  |
                                       +------+        +---v---+
                                                           |
           emit .pause();          +=================+     |
           ^-----------------------+  return false;  <-----+---+
                                   +=================+         |
                                                               |
when queue is empty     +============+                         |
^-----------------------<  Buffering |                         |
|                       |============|                         |
+> emit .drain();       |  ^Buffer^  |                         |
+> emit .resume();      +------------+                         |
                        |  ^Buffer^  |                         |
                        +------------+   add chunk to queue    |
                        |            <---^---------------------<
                        +============+

Любые данные, которые вы write в поток в итоге будет записан, даже если вызов возвращен false (и хранится в памяти до тех пор).

highWaterMark опция дает вам некоторый контроль над количеством используемой "буферной памяти". Как только вы написали больше указанной суммы, write вернусь false чтобы дать вам возможность прекратить писать. Однако вам не нужно: если вы не остановитесь, никакие данные не будут отброшены, вы просто в конечном итоге израсходуете больше памяти (перезапись данных приведет к дублированию). И, как вы упоминаете, вы можете слушать 'drain' событие, чтобы узнать, когда пришло время написать снова.

Другие вопросы по тегам