я увлечен своим интересом к разработке программного обеспечения, в частности, загадкой эргономического создания программных систем, которые решают самый широкий набор проблем, делая как можно меньше компромиссов. Мне также нравится думать о себе как о разработчике систем, что, по определению Эндрю Келли, означает разработчик, заинтересованный в полном понимании систем, с которыми они работают. В этом блоге я делюсь с вами своими идеями о решении следующей проблемы: создание надежного и исполнительного приложения для полного стека . Довольно сложная задача, не так ли? В блоге я сосредотачиваюсь на части «Performant Web Server» - вот где я чувствую, что могу предложить свежую перспективу, так как остальное либо хорошо проточено, либо мне нечего добавить.
Основное предостережение - будет нет образцов кода , я на самом деле не проверил это. Да, это серьезный недостаток, но на самом деле это займет много времени, чего у меня нет, и между публикацией некорректного блога и не публикуя его вообще, я застрял с первым. Вы были предупреждены.
]
и какие части мы собираем наше приложение?
]с нашими инструментами, принятыми, давайте начнем!
]Zig не имеет поддержки языкового уровня для Coroutines :( и Coroutines - это то, с чем каждый операционный веб -сервер построен. Итак, нет смысла пытаться?
]держаться, включить, давайте сначала поместим шляпу нашего системного программиста. Кораки - это не серебряная пуля, ничего нет. Каковы фактические преимущества и недостатки?
]общеизвестно, что Coroutines (потоки пользователей) более легкие и быстрее. Но каким образом именно? (Ответы здесь в основном спекуляции, возьмите с собой соль и протестируйте ее самостоятельно)
]
время выполнения GO, например, мультиплексирует Goroutines на потоки ОС. Темы делятся таблицей страниц, а также другие ресурсы, принадлежащие процессу. Если мы введем изоляцию и сродство процессора к миксу - потоки будут постоянно работать на своих соответствующих ядрах процессоров, все структуры данных ОС останутся в памяти, и не нужно быть изменено, пользовательский планировщик будет выделять время ЦП на горутины с точностью, поскольку он использует кооперативную модель многозаданную модель. Возможна ли конкуренция?
]] победы достигаются путем отсрочки абстракции потока на уровне ОС и замены его на Goroutine. Но ничего не потеряно в переводе?
]Я утверждаю, что абстракция «истинного» на уровне ОС для независимой единицы выполнения - это даже не поток - это на самом деле процесс ОС. На самом деле, различие здесь не так очевидно - все, что различает потоки и процессы, - это различные значения PID и TID. Что касается дескрипторов файлов, то виртуальная память, обработчики сигналов, отслеживаемые ресурсы - независимо от того, отдельные ли они для ребенка указаны в аргументах в Syscall "Clone". Таким образом, я буду использовать термин «процесс», чтобы означать поток выполнения, который владеет собственными системными ресурсами - в первую очередь время ЦП, память, открытые дескрипторы файлов.
теперь почему это важно? Каждая единица исполнения имеет свои собственные требования для системных ресурсов. Каждая сложная задача может быть разбита на единицы, где каждый из них может сделать свой собственный, предсказуемый, запросить ресурсы - память и время процессора. И чем дальше вверх по дереву подзадачи, вы идете, к более общей задаче - график системных ресурсов образует кривую колокола с длинными хвостами. И вы обязаны убедиться, что хвосты не переполняют ограничение системных ресурсов. Но как это делается, и что произойдет, если этот предел на самом деле переполнен?
Если мы используем модель одного процесса и много критиков для независимых задач, когда один из них перевернут предел памяти - поскольку использование памяти отслеживается на уровне процесса, весь процесс убит. Это в лучшем случае - если вы используете CGROUPS (что автоматически относится к стручкам в Kubernetes, которые имеют CGROUP на POD) - вся CGROUP убит. Создание надежной системы необходимо учитывать. А как насчет времени процессора? Если наша услуга получит множество запросов на вычислительные задачи одновременно, он не отвечает. Затем сроки, отмены, повторные перезапуск следуют.
]] единственный реалистичный способ справиться с этими сценариями для большинства основных программных стеков - оставлять «жир» в системе - некоторые неиспользованные ресурсы для хвоста кривой колокола - и ограничивая количество параллельных запросов - которые, опять же, приводят к неиспользованным ресурсам. И даже с этим мы будем время от времени убить или не отвечать, в том числе для «невинных» запросов, которые оказываются в том же процессе, что и выброс. Этот компромисс приемлем для многих и достаточно хорошо обслуживает программные системы на практике. Но можем ли мы сделать лучше?
]Поскольку использование ресурсов отслеживается для для процесса, в идеале мы появились бы на новом процессе для каждой небольшой, предсказуемой единицы выполнения. Затем мы установили Ulimit для процессора и памяти - и мы готовы к работе! У Ulimit есть мягкие и жесткие ограничения, что позволит процессу изящно прекращаться после удара по мягкому пределу, и если это не происходит, возможно, из -за ошибки - насильственно прекращается при ударе по жесткому пределу. К сожалению, появление новых процессов на Linux - это медленный, нерестив новый процесс на запрос не поддерживается для многих веб -структур, а также для других систем, таких как височные. Кроме того, переключение процессов дороже - что смягчается закреплением коровы и процессора, но все же не идеально. К сожалению, давние процессы-это неизбежная реальность.
, чем дальше мы переходим от чистой абстракции недолговечных процессов, тем больше работы на уровне ОС нам необходимо позаботиться о себе. Но есть также преимущества, которые необходимо получить, например, использование io_uring для партии ввода -ввода между многими потоками выполнения. На самом деле, если большая задача состоит из подзадач - действительно ли мы заботимся об их отдельном использовании ресурсов? Только для профилирования. Но если для большой задачи мы могли бы управлять (отрезать) хвосты кривой Resource Bell, это было бы достаточно хорошо. Таким образом, мы могли бы породить столько процессов, сколько и запросов, которые мы хотим обработать одновременно, чтобы они были долгоживущими и просто привыкли к Ulimit для каждого нового запроса. Таким образом, когда запрос переполняет свои ограничения ресурсов, он получает сигнал ОС и способен грациозно прекратить, не влияя на другие запросы. Или, если высокое использование ресурсов является преднамеренным, мы могли бы сказать клиенту платить за более высокую квоту ресурсов. Звучит довольно хорошо для меня.
], но производительность все равно будет пострадать, по сравнению с подходом Coroutine-Per-Request. Во -первых, копирование вокруг таблицы памяти процесса стоит дорого. Поскольку таблица содержит ссылки на страницы памяти, мы могли бы использовать огромные страницы, таким образом ограничивая размер данных, которые будут скопированы. Это может быть непосредственно возможно только с языками низкого уровня, такими как Zig. Кроме того, многозадачность уровня ОС является превентивным и не кооперативным, что всегда будет менее эффективным. Или это?
]Существует Syscall sched_yield, которая позволяет потоке отказываться от процессора, когда он завершит свою часть работы. Кажется довольно кооперативным. Может ли также быть способ запросить время времени заданного размера? На самом деле, есть - с политикой планирования rade_deadline. Это политика в режиме реального времени, что означает, что для запрошенного срез времени процессора поток работает непрерывно. Но если ломтик переполнен - нажимает преодоление, и ваша нить заменена и деприоритизирована. И если срез не подходит - поток может вызовать ched_yield, чтобы сигнализировать о ранней отделке, что позволяет запускать другие потоки. Это похоже на лучшее из обоих миров - кооперативная и предварительная модель.
]
ограничение - это тот факт, что поток read_deadline не может раскошелиться. Это оставляет нас с двумя моделями для параллелистики - либо процесс за запрос, который устанавливает крайний срок для себя, и запускает цикл событий для эффективного IO, либо процесс, который из начала порождает поток для каждой микро -задачи, каждый из которых устанавливает свой собственный крайний срок и использует очередь для связи друг с другом. Первый более прямой, но требует цикла событий в пользовательском пространстве, последний больше использует ядро.
]обе стратегии достигают того же конца, что и модель Coroutine - , сотрудничая с ядром, можно выполнить задачи применения с минимальными перерывами .
]это все для высокопроизводительной, низкой задержки, низкоуровневой стороны вещей, где сияет Zig. Но когда дело доходит до фактического бизнеса приложения, гибкость гораздо более ценна, чем задержка. Если процесс включает в себя реальных людей, подписавшихся на документы - задержка компьютера незначительна. Кроме того, несмотря на страдания в производительности, объектно -ориентированные языки дают разработчику лучшие примитивы для моделирования домена бизнеса. И в самом дальнем конце этого, такие системы, как Flowable и Camunda, позволяют управленческому и операционному персоналу программировать бизнес -логику с большей гибкостью и более низким барьером въезда. Такие языки, как Zig, не помогут с этим, и только стоят на вашем пути.
]
Python, с другой стороны, является одним из самых динамичных языков, которые есть. Занятия, объекты - все они являются словари под капотом, и их можно манипулировать во время выполнения, как вам нравится. Это имеет штраф за производительность, но делает моделирование бизнеса с помощью классов и объектов, а также множеством умных трюков практичным. Зиг - это противоположность этому - в Zig намеренно мало умных уловок, что дает вам максимальный контроль. Можем ли мы объединить их силы, заставляя их взаимодействовать?
Действительно, мы можем, благодаря тому, что оба поддерживают C abi. Мы можем заставить интерпретатор Python работать из процесса Zig, а не в качестве отдельного процесса, снижая накладные расходы в стоимости выполнения и код клея. Это также позволяет нам использовать пользовательские распределители Zig в Python - устанавливая арену для обработки отдельного запроса, тем самым уменьшая, если не устранять накладные расходы коллектора мусора, и установить крышку памяти. Основным ограничением станут нерестящие нити выполнения CPYTHON для сбора мусора и IO, но я не нашел никаких доказательств того, что это так. Мы могли бы зацепить Python в пользовательскую петлю события в Zig, с отслеживанием памяти на штуку, используя поле «контекст» в AbstractMemoryLoop. Возможности безграничны.
]мы обсудили достоинства параллелизма, параллелизма и различных форм интеграции с ядром ОС. В исследовании не хватает тестов и кода, которые, я надеюсь, компенсируют качество предлагаемых идей. Вы пробовали что -нибудь подобное? Что вы думаете? Обратная связь приветствуется :)
]Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3