Модуль Apache mod_unique_id
| Описание: | Предоставляет переменную среды с уникальным идентификатором для каждого запроса |
|---|---|
| Статус: | Расширение |
| Идентификатор модуля: | unique_id_module |
| Исходный файл: | mod_unique_id.c |
Краткое описание
Этот модуль предоставляет для каждого запроса специальный токен, уникальность которого гарантируется для «всех» запросов при очень конкретных условиях. Уникальный идентификатор также уникален на нескольких машинах в правильно настроенном кластере. Переменной среды UNIQUE_ID присваивается идентификатор для каждого запроса. Уникальные идентификаторы полезны по разным причинам, рассмотрение которых выходит за рамки этого документа.
Теория
Для начала кратко напомним, как работает сервер Apache на машинах Unix. В настоящее время эта возможность не поддерживается в Windows NT. На машинах Unix Apache создает несколько дочерних процессов, каждый из которых обрабатывает запросы по одному. Каждый дочерний процесс может обслужить несколько запросов за время своего существования. Для целей этого обсуждения будем считать, что дочерние процессы не обмениваются данными друг с другом. Далее мы будем называть дочерние процессы процессами httpd.
Ваш веб-сайт размещен на одной или нескольких машинах, находящихся под вашим управлением; вместе мы будем называть их кластером машин. На каждой машине может работать несколько экземпляров Apache. Все они вместе образуют «вселенную», и, при соблюдении некоторых предположений, мы покажем, что в этой вселенной можно генерировать уникальные идентификаторы для каждого запроса без интенсивного обмена данными между машинами кластера.
Машины в вашем кластере должны удовлетворять следующим требованиям. (Даже если у вас всего одна машина, следует синхронизировать ее часы с помощью NTP.)
- Время на машинах синхронизировано с помощью NTP или другого сетевого протокола времени.
- Имена всех машин различаются, так что модуль может выполнить поиск IP-адреса по имени узла и получить разные IP-адреса для каждой машины в кластере.
Что касается предположений об операционной системе, будем считать, что идентификаторы процессов (pid) помещаются в 32 бита. Если операционная система использует для pid более 32 бит, исправление несложно, но его необходимо внести в код.
При этих предположениях в любой момент времени можно отличить любой процесс httpd на любой машине кластера от всех остальных процессов httpd. Для этого достаточно знать IP-адрес машины и pid процесса httpd. Процесс httpd может одновременно обрабатывать несколько запросов, если используется многопоточная MPM. Чтобы различать потоки, мы используем индекс потока, который Apache httpd применяет внутри. Таким образом, для генерации уникальных идентификаторов запросов нам нужно различать только разные моменты времени.
Для различения времени мы будем использовать метку времени Unix (число секунд с 1 января 1970 года UTC) и 16-битный счетчик. Точность метки времени составляет одну секунду, поэтому счетчик используется для представления до 65536 значений в течение одной секунды. Кортеж ( ip_addr, pid, time_stamp, counter ) позволяет перечислить 65536 запросов в секунду для каждого процесса httpd. Однако при повторном использовании pid со временем возникают проблемы, для решения которых используется счетчик.
При создании дочернего процесса httpd счетчик инициализируется значением (текущее число микросекунд, деленное на 10) по модулю 65536 (эта формула выбрана, чтобы устранить некоторые проблемы с разбросом младших битов таймеров микросекунд на отдельных системах). При генерации уникального идентификатора используется метка времени, соответствующая моменту поступления запроса на веб-сервер. Счетчик увеличивается при каждой генерации идентификатора (с возможностью переполнения).
Ядро назначает каждому процессу pid при его создании с помощью fork, и значения pid могут циклически повторяться (во многих Unix-системах они 16-битные, но в более новых системах их размер увеличен до 32 бит). Поэтому со временем один и тот же pid будет использован повторно. Однако, если повторное использование происходит не в ту же секунду, уникальность нашего кортежа не нарушается. Иными словами, мы предполагаем, что система не создает 65536 процессов за одну секунду (в некоторых Unix-системах это число может составлять всего 32768, но и такая ситуация маловероятна).
Предположим, что по какой-либо причине время повторяется. То есть часы системы сбились и снова показывают время из прошлого (или ушли далеко вперед, затем были правильно настроены и снова показывают время из будущего). В этом случае легко показать, что pid и метка времени могут повториться. Выбор начального значения счетчика призван помочь предотвратить это. Следует отметить, что для инициализации счетчика нам действительно нужно случайное число, но в большинстве систем нет подходящих готовых значений (т. е., нельзя использовать rand(), поскольку генератор нужно инициализировать, а в качестве начального значения нельзя использовать время, потому что время, по крайней мере с точностью до секунды, уже повторялось). Это не идеальная защита.
Насколько надежна эта защита? Предположим, что одна из ваших машин обслуживает не более 500 запросов в секунду (на момент написания это вполне разумный верхний предел, поскольку системы обычно не только раздают статические файлы). Для этого потребуется некоторое количество дочерних процессов, зависящее от числа одновременно подключенных клиентов. Но будем исходить из пессимистичного предположения, что один дочерний процесс способен обслуживать 500 запросов в секунду. Существует 1000 возможных начальных значений счетчика, при которых две последовательности из 500 запросов перекрываются. Поэтому, если время (с точностью до секунды) повторится, вероятность того, что этот дочерний процесс повторит значение счетчика и уникальность будет нарушена, составляет 1,5%. Это крайне пессимистичный пример; при реальных значениях вероятность еще ниже. Если в вашей системе такое все же вероятно, возможно, следует сделать счетчик 32-битным (изменив код).
Вас может беспокоить перевод часов «назад» при переходе на летнее время. Однако это не проблема, поскольку здесь используется время UTC, которое «всегда» идет вперед. Учтите, что Unix-системы на базе x86 могут требовать правильной настройки, чтобы это было верно: они должны считать, что часы материнской платы работают по UTC, и соответствующим образом корректировать время. Но даже в этом случае, если вы используете NTP, время UTC вскоре после перезагрузки будет правильным.
Переменная среды UNIQUE_ID формируется путем кодирования 144-битного кортежа (32-битный IP-адрес, 32-битный pid, 32-битная метка времени, 16-битный счетчик, 32-битный индекс потока) с помощью алфавита [A-Za-z0-9@-] способом, аналогичным кодированию MIME base64, в результате чего получается 24 символа. На самом деле алфавит MIME base64 — это [A-Za-z0-9+/], однако + и / необходимо специально кодировать в URL, что делает их менее предпочтительными. Все значения кодируются в сетевом порядке байтов, поэтому кодирование сопоставимо на архитектурах с разным порядком байтов. Фактический порядок элементов в кодировании таков: метка времени, IP-адрес, pid, счетчик. Этот порядок выбран не случайно, однако следует подчеркнуть, что приложения не должны разбирать кодирование. Приложения должны рассматривать весь закодированный UNIQUE_ID как непрозрачный токен, который можно сравнивать с другими UNIQUE_ID только на равенство.
Порядок был выбран так, чтобы в будущем можно было изменить кодирование, не опасаясь конфликтов с существующей базой данных UNIQUE_ID. В новых вариантах кодирования метка времени также должна оставаться первым элементом; в остальном можно использовать тот же алфавит и ту же разрядность. Поскольку метки времени по существу образуют возрастающую последовательность, достаточно выбрать секунду переключения, в течение которой все машины кластера перестанут обслуживать любые запросы и использовать старый формат кодирования. После этого они смогут возобновить обработку запросов и начать выдавать новые варианты кодирования.
Мы считаем это решение относительно переносимым способом устранения проблемы. Срок действия генерируемых идентификаторов практически не ограничен, поскольку при необходимости идентификаторы будущих запросов можно сделать длиннее. Между машинами кластера практически не требуется обмен данными (нужна только синхронизация по NTP, создающая небольшую нагрузку), а между процессами httpd обмен данными не требуется вовсе (обмен происходит неявно через значение pid, назначенное ядром). В некоторых строго определенных ситуациях идентификатор можно сократить, однако для этого нужны дополнительные предположения (например, 32-битный IP-адрес избыточен для любого сайта, но для него нет переносимой более короткой замены).
© 2018 The Apache Software Foundation
Licensed under the Apache License, Version 2.0.
https://httpd.apache.org/docs/2.4/en/mod/mod_unique_id.html