1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
<?xml version="1.0"?>
<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
<?xml-stylesheet type="text/xsl" href="../style/manual.fr.xsl"?>
<!-- English Revision : 981084 -->
<!-- French translation : Lucien GENTIS -->
<!-- Reviewed by : Vincent Deffontaines -->
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<modulesynopsis metafile="mod_unique_id.xml.meta">
<name>mod_unique_id</name>
<description>Fournit une variable d'environnement contenant un
identifiant unique pour chaque requête</description>
<status>Extension</status>
<sourcefile>mod_unique_id.c</sourcefile>
<identifier>unique_id_module</identifier>
<summary>
<p>Ce module fournit un identifiant dont l'unicité est garantie
parmi "toutes" les requêtes sous des conditions très précises.
L'identifiant unique le sera aussi parmi plusieurs machines
appartenant à un cluster correctement configuré. L'identifiant est
affecté à la variable d'environnement <code>UNIQUE_ID</code> pour
chaque requête. Les identifiants uniques sont utiles pour diverses
raisons dont la nature se situe au delà de la portée de ce
document.</p>
</summary>
<section id="theory">
<title>Théorie</title>
<p>Tout d'abord un bref rappel de la manière dont le serveur Apache
fonctionne sous Unix (cette fonctionnalité n'étant actuellement pas
supportée sous Windows NT). Sous Unix, Apache crée plusieurs
processus enfants, ces derniers traitant les requêtes une par une.
Chaque processus enfant peut traiter plusieurs requêtes pendant sa
durée de vie. Dans le cadre de cette discussion, nous supposerons
que les différents processus enfants ne s'échangent pas de données
entre eux. Nous nous référerons aux processus enfants sous le nom de
<dfn>processus httpd</dfn>.</p>
<p>Votre site web est réparti entre une ou plusieurs machines dont
vous êtes l'administrateur, et que nous nommerons cluster de
serveurs. Chaque serveur peut exécuter plusieurs instances d'Apache.
L'ensemble de ces dernières sera considéré comme "l'Univers", et
sous certaines hypothèses, nous montrerons qu'il est possible dans
cet univers, de générer des identifiants uniques pour chaque
requête, sans pour autant nécessiter une communication importante
entre les différents serveurs du cluster.</p>
<p>Les machines de votre cluster doivent satisfaire ces conditions
(même si le cluster ne comporte qu'une machine, vous devez
synchroniser son horloge avec NTP) :</p>
<ul>
<li>Les temps des machines sont synchronisés via NTP ou tout autre
protocole de synchronisation du temps en réseau.</li>
<li>Les nom d'hôtes des machines sont tous différents, de façon à
ce que le module puisse recevoir une adresse IP différente pour
chaque machine du cluster en effectuant une recherche sur le nom
d'hôte.</li>
</ul>
<p>Au vu des caractéristiques actuelles du système d'exploitation,
nous supposerons que les pids (identifiants processus) sont codés
sur 32 bits. Si le système d'exploitation utilise plus de 32 bits
pour un pid, la correction est triviale mais doit être effectuée
dans le code.</p>
<p>Ces hypothèses posées, à un instant donné, nous pouvons
distinguer tout processus httpd sur toute machine du cluster de tous
les autres processus httpd. Pour ce faire, il suffit d'utiliser
l'adresse IP de la machine et le pid du processus httpd. Un
processus httpd peut traiter plusieurs requêtes simultanément si
vous utilisez un module MPM multi-threadé. Pour identifier les
threads, Apache httpd utilise en interne un index de threads. Ainsi,
afin de générer des identifiants uniques pour chaque requête, il
suffit d'effectuer une distinction en fonction du temps.</p>
<p>Pour déterminer le temps, nous utiliserons un repère de temps
Unix (les secondes écoulées depuis le 1er janvier 1970 UTC), et un
compteur 16 bits. La précision du repère de temps n'étant que d'une
seconde, le compteur va représenter 65536 valeurs par seconde. Le
quadruplet <em>(adresse IP, pid, repère de temps, compteur)</em> est
en mesure de distinguer 65536 requêtes par seconde par processus
httpd. Il peut cependant arriver que le même pid soit réutilisé au
cours du temps, et le compteur est là pour pallier cet
inconvénient.</p>
<p>Lorsqu'un processus enfant httpd est créé, le compteur est
initialisé avec (nombre de microsecondes actuel divisé par 10)
modulo 65536 (cette formule a été choisie pour éliminer certains
problème de variance avec les bits de poids faibles du compteur de
microsecondes sur certains systèmes). Lorsqu'un identifiant unique
est généré, le repère de temps utilisé est le moment où la requête
arrive sur le serveur web. Le compteur est incrémenté à chaque
création d'identifiant (et peut repasser à 0 lorsqu'il a atteint sa
valeur maximale).</p>
<p>Le noyau génère un pid pour chaque processus lors de sa création,
et le compteur de pid est réinitialisé à une certaine valeur
lorsqu'il a atteint sa valeur maximale (les pid sont codés sur 16
bits sous de nombreux Unixes, mais les systèmes les plus récents les
ont étendus à 32 bits). La même valeur de pid pourra donc être
réutilisée au cours du temps. Cependant, tant qu'elle n'est pas
réutilisée dans la même seconde, elle ne remet pas en cause
l'unicité de notre quadruplet. Nous supposerons donc que le système
ne créera pas plus de 65536 processus en une seconde (ce nombre peut
être de 32768 sous certains Unixes, mais même dans ce cas, on est en
général loin de cette situation).</p>
<p>Il est possible que le temps se répète pour une raison
quelconque.
Supposons par exemple que l'horloge système soit retardée et repasse
par un temps passé (ou bien, comme elle avançait, elle a été remise
à l'heure, et elle repasse par un temps futur). Dans ce cas, il peut
être facilement démontré que le couple pid/repère de temps peut être
réutilisé. Le choix de la formule d'initialisation du compteur a
été effectué dans l'intention de pallier ce problème. Notez qu'un
nombre vraiment aléatoire serait souhaitable pour initialiser le
compteur, mais il n'existe pas de tel nombre directement lisible sur
la plupart des systèmes (c'est à dire que vous ne pouvez pas
utiliser rand() car vous devez déclencher le générateur avec une
valeur unique, et vous ne pouvez pas utiliser le temps à cet effet
car celui-ci , au moins à la seconde près, s'est répété). Il ne
s'agit donc pas d'une défense parfaite.</p>
<p>Même si elle n'est pas parfaite, quel est le degré d'efficacité
de cette défense ? Supposons
qu'une de vos machines serve au plus 500 requêtes par seconde (ce
qui constitue une limite supérieure très raisonnable au moment où ce
document est écrit, car les systèmes ne se contentent en général pas
de débiter des fichiers statiques). Pour y parvenir, un certain nombre
de processus enfants sera nécessaire, qui dépendra du nombre de
clients simultanés présents. Mais soyons pessimiste et supposons
qu'un seul processus enfant soit capable de servir 500 requêtes par
secondes.
Il existe 1000 valeurs de démarrage possibles du compteur pour
lesquelles deux séquences de 500 requêtes puissent se recouvrir. Il
y a donc 1,5% de chance que le processus enfant répète une valeur de
compteur si le temps se répète (avec une résolution d'une seconde),
et l'unicité sera alors remise en cause. C'est cependant un exemple
très pessimiste, et avec les valeurs du monde réel, il y a bien
moins de chances que cela ne se produise. Si vous estimez que ceci a
tout de même quelque chances de se produire sur votre système, vous
pouvez migrer vers un compteur à 32 bits (en modifiant le code).</p>
<p>On pourrait supposer que ceci a plus de chance de se produire
lors du passage à l'heure d'hiver où l'horloge est "retardée". Cela
ne constitue cependant pas un problème car les temps pris en compte
ici sont des temps UTC, qui vont "toujours" de l'avant. Notez que
les Unixes à base de processeur x86 peuvent nécessiter une
configuration particulière pour que ceci soit vrai -- il doivent
être configurés pour assumer que l'horloge système est en UTC et
compenser de manière appropriée. Mais même dans ce cas, si vous
utilisez NTP, votre temps UTC sera correct peu après le
redémarrage.</p>
<!-- FIXME: thread_index is unsigned int, so not always 32bit.-->
<p>La variable d'environnement <code>UNIQUE_ID</code> est construite
par codage du quadruplet de 144 bits (adresse IP sur 32 bits, pid
sur 32 bits, repère de temps sur 32 bits, compteur 16 bits et index
de threads sur 32 bits) en
utilisant l'alphabet <code>[A-Za-z0-9@-]</code> d'une manière
similaire à celle du codage MIME base64, et sa valeur se présente
sous la forme d'une chaîne de 24 caractères. L'alphabet MIME base64
est en fait <code>[A-Za-z0-9+/]</code> ; cependant, les caractères
<code>+</code> et <code>/</code> nécessitent un codage particulier
dans les URLs, ce qui rend leur utilisation peu commode. Toutes les
valeurs sont codées dans l'ordre des octets d'une adresse réseau de
façon à ce
que le codage soit comparable entre des architectures où l'ordre des
octets est différent. L'ordre réel de codage est : repère de temps,
adresse IP, pid, compteur. Cet ordre de codage possède un but
précis, mais il faut souligner que les applications n'ont aucun
intérêt à entrer dans les détails de ce codage. Les applications
doivent se contenter de traiter la variable <code>UNIQUE_ID</code>
comme un symbole opaque, qui peut être comparé avec d'autres
<code>UNIQUE_ID</code>s en ne testant que leur égalité.</p>
<p>L'ordre a été choisi de façon à ce qu'il soit possible de
modifier le codage dans le futur sans avoir à se préoccuper de
conflits éventuels avec une base de données de
<code>UNIQUE_ID</code>s existante. Les nouveaux codages doivent
conserver le repère de temps comme premier élément, et pour le
reste, utiliser les même alphabet et longueur en bits. Comme les
repères de temps constituent essentiellement un séquence croissante,
il suffit que toutes les machines du cluster arrêtent de servir et
de requérir dans la même <em>seconde repère</em>, et n'utilisent
alors plus l'ancien format de codage. Ensuite, elles peuvent
reprendre le traitement des requêtes en utilisant les nouveaux
codages.</p>
<p>Nous pensons que ceci apporte une solution relativement portable
au problème. Les
identifiants générés possèdent une durée de vie pratiquement infinie
car les identifiants futurs pourront être allongés selon les
besoins. Pratiquement aucune communication n'est requise entre les
machines du cluster (seule la synchronisation NTP est requise, ce
qui représente une charge très faible), et aucune communication
entre les processus httpd n'est nécessaire (la communication est
implicite et incluse dans le pid assigné par le noyau). Dans des
situations très spécifiques, l'identifiant peut être raccourci, mais
dans ce cas, d'avantage d'informations doivent être admises (par
exemple, les 32 bits de l'adresse IP sont excessifs pour la plupart
des sites, mais il n'existe pas de valeur de remplacement portable
plus courte).</p>
</section>
</modulesynopsis>
|