Стрим 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'
событие, чтобы узнать, когда пришло время написать снова.