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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
|
\section{Containerisierung und Modularisierung}
Um eine optimale Skalierbarkeit zu erreichen wird die Anwendung in einzelne
Module aufgeteilt und in einzelne Container verpackt. Dadurch ist es einfach
möglich die Anwendung auf mehreren Rechnern gleichzeitig laufen zu lassen und
entsprechende Interaktionen zwischen den Container zu definieren. Durch die
containerisierung mithilfe von Docker\footnote{\url{https://www.docker.com/}}
ist es ebenfalls möglich die Container auf einem beliebegem Betriebssystem zu
starten ohne von bestimmten bibliotheksveriosnen abhängig zu sein da diese in
den Container bereits integriert sind.
\subsection{Modularisierung des Generators}
Um den Generator zu modularisieren muss erst definiert werden was für
potentielle Module existieren und wie diese miteinander interagieren.
\par Insgesamt generiert der Generator zufällige Werte in einem gegebenen
Intervall, testet mithilfe des NFW-profils ob diese Sterne existieren oder
nicht und schreibt die Sterne anschließend in eine Datenbank. Es sind sofort
ein paar Module ersichtlich: ein Modul welches die Zufälligen Koordinaten
generiert, ein Modul welches den Wert aus dem NFW-Profil berechnet und ein
Modul welches die Daten in die Datenbank schreibt.
\begin{figure}[ht!]
\centering
\begin{forest}
for tree={draw, grow=0}
[DB
[generator
[traefik
[NFW]
[\( \dots \)]
[NFW]
]
]
]
\end{forest}
\label{fig:generator_setup}
\end{figure}
\subsubsection{Generator Modul}
Das Generator Modul generiert zufällige Koordinaten in einem definiertem
Intervall und sendet diese an einen NFW Container. Damit nicht ein Container
unter der last der ein kommenden Antworten leidet wird der
Reverse-Proxy\footnote{\url{https://de.wikipedia.org/wiki/Reverse_Proxy}}
Traefik\footnote{\url{https://traefik.io/}} verwendet. Dieser routet die
Anfragen an weitere Container weiter wodurch einer optimale Lastverteilung und
Skalierbarkeit gegeben ist.
\subsubsection{NFW Modul}
Das NFW-modul erhält einen Wert und berechnet den entsprechenden NFW Wert.
Dadurch das er durch Traefik angesteuert wird kann falls die Anzahl der
Anfragen zu hoch wird einfach ein identische Container gestartet werden.
Traefik erkennt diesen Container automatisch und kann diesen beim Routen der
Anfragen entsprechend nutzen.
\subsubsection{DB Modul}
Um die Daten zurück in die Datenbank zu schrieben wird das DB-Modul welches
unter \ref{subsubsec:db_modul} genau beschrieben wird verwendet.
\subsection{Modularisierung des Simulators}
Der Simulator simuliert die Sterne aus der Datenbank indem er Stern für Stern
die Kraft die auf einen Stern wirkt berechnet, die neue Position des Sternes
ausrechnet und anschließend den ``neuen'' Stern zurück in die Datenbank
schriebt.
\begin{figure}[ht!]
\centering
\begin{forest}
for tree={draw, grow=0}
[DB
[DB-actions
[manager
[Simulator]
[\( \dots \)]
[Simulator]
]
]
]
\end{forest}
\label{fig:simulator_setup}
\end{figure}
\subsubsection{Manager}
Um die Simulations Container optimal skalieren zu können, wird statt den
Simulations Container aktiv Sterne zu geben darauf gewartet, dass ein
Simulations Container einen Stern anfragt. Der Manager fragt im Vorhinein die
Datenbank an um eine Liste an Stern-IDs zu bekommen auf die die Kraft berechnet
werden müssen. Diese Liste an Stern-IDs wird in einen Channel geschrieben,
welcher die Sterne einzeln ausgeben kann. Sobald der Channel leer ist,
entnimmt der Manager der Datenbank die nächsten Stern-IDs.
\subsubsection{Simulator}
Der Simulator Container entnimmt dem Manager Container einen Stern und
berechnet die Kraft die auf ihn wirkt indem er den in der Datenbank
gespeicherten Baum in Kombination des Barnes-Hut Algorithmus nutzt. Nachdem die
Kraft berechnet wurde kann die Neue Position des Sternes berechnet werden und
wieder in die Datenbank eingefügt werden.
\subsubsection{DB Modul} \label{subsubsec:db_modul}
Der Datenbank Container interagiert mit der Datenbank und stellt verschiedene
Methoden zur Verfügung um z.B. Sterne in die Datenbank einzufügen, Daten aus
der Datenbank zu erhalten und den Massen Mittelpunkt aller inneren Knoten zu
berechnen.
\par Das Modul stellt ebenfalls funktionen zur verfügung welche sterne die in
die Funktion gegeben werden in die Datenbank einfügen und lesen können.
\subsection{Sonstige Container}
\subsubsection{Viewer}
Um sich das Endergebnis anschauen zu können, müssen die Daten aus der Datenbank
in ein entsprechendes Format gebracht werden damit sie betrachtet werden
können. Dazu nutzt der Viewer-Container die Daten aus der Datenbank und
generiert daraus entsprechend Bilder, Videos oder Vektorgraphiken. Die
generierten Bilder sind meist in einer sehr hohen Auflösung von
\(15360\)x\(15360\)px ausgegeben. Problematisch wird hierbei die Datei-Größe:
Ein solch großes Bild ist schnell mehrere Hundert Megabytes groß. Um das
Problem zu lösen können die resultierenden Bildern anstatt als Rastergrafik als
Vektorgraphik exportiert werden. Dadurch kann die Größe der Datei um ein
mehrfaches reduziert werden und es treten keine Effekte wie Unschärfe auf, da
die Grafik lokal gerendert wird.
\subsubsection{Controller}
Der Controller steuert den gesamt Zustand, er bestimmt also was getan werden
muss, z.B. wieviele Sterne generiert werden, wo sich die einzelnen Container
befinden und wie die Last auf den Container ist.
\subsubsection{Monitoring}
Um einen Überblick über die Gesamtsituation zu bekommen ist es nicht hilfreich
sich auf allen Servern anzumelden und dort nachzugucken wie die Auslastung
gerade ist. Um dies an einer Stelle zu ``monitoren`` verwende ich die ``time
series database`` Prometheus\footnote{\url{https://prometheus.io/}} als backend
für das Monitoring System Grafana\footnote{\url{https://grafana.com/}}.
\par Die einzelnen Simulations-container senden alle paar Sekunden die Anzahl
der Sterne die sie bereits simuliert haben an einen Manager-Container. Dieser
stellt Prometheus wiederum die gesammelten Daten zur Verfügung. Prometheus
sammelt die Daten alle paar Sekunden ein und speichert diese um anschließend
einen Verlauf in der Form eines Graphen o.ä. darzustellen. Um alle Server zu
monitoren kann Grafana auf mehrere Prometheus Instanzen zugreifen und
entsprechende Graphen generieren. Somit ist es möglich mit geringem Aufwand
alle laufenden Dienste auf einen Blick zu überwachen. Der Gesamte Prozess ist
in Abbilding \ref{fig:monitoring_setup} dargestellt.
\begin{figure}[ht!]
\centering
\begin{forest}
for tree={draw, grow=0}
[Grafana
[Prometheus
[manager, label=Nuremberg
[Simulator]
[\( \dots \)]
[Simulator]
]
[manager, label=Helsinki
[Simulator]
[\( \dots \)]
[Simulator]
]
]
[Prometheus
[manage, label=Falkenstein
[Simulator]
[\( \dots \)]
[Simulator]
]
[manager, label=Amsterdam
[Simulator]
[\( \dots \)]
[Simulator]
]
]
]
\end{forest}
\caption{Das Monitoren von mehreren Containern}
\label{fig:monitoring_setup}
\end{figure}
\subsection{Datenbank Skalierung}
\begin{figure*}[ht]
\centering
\begin{forest}
[, s sep+=5mm, draw, circle
[A,tikz={\node[draw,fit=()(!1)(!l), label=below:Server 1] {};}, draw, circle
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
]
[B,tikz={\node[draw,fit=()(!1)(!l), label=below:Server 2] {};}, draw, circle
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
]
[C,tikz={\node[draw,fit=()(!1)(!l), label=below:Server 3] {};}, draw, circle
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
]
[D,tikz={\node[draw,fit=()(!1)(!l), label=below:Server 4] {};}, draw, circle
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
[\(\dots\)]
]
]
\end{forest}
\caption{Die Teilbäume \(A, B, C\) und \(D\) werden auf verschiedenen Servern gespeichert und entsprechend angesprochen.}
\label{fig:tree_sharding}
\end{figure*}
Ein Flaschenhals der bei der Skalierung entsteht ist die Anbindung an die
Datenbank: Desto mehr Simulations-Container mit der Datenbank interagieren,
desto höher wird die Auslastung der Datenbank. Um dieses Problem zu lösen
bietet es sich an die Datenbank in mehrere teile aufzuspalten. Ein weiters
essenzielles Problem das bei der Verteilung der Simulations rechen Knoten in
verschiedene Rechenzentren entsteht ist, dass die Bandbreite zur Datenbank
sinkt und die Latenz steigt. Dieses Problem wird durch sogennantes
``Sharding'' (vertikale bzw. horizontalte
Fragmentierung\footnote{\url{https://de.wikipedia.org/wiki/Denormalisierung\#Fragmentierung}})
gelöst.
\subsubsection{Sharding (Vertikale bzw. Horizontale Fragmentierung)}
Ab einer gewissen Größe kann eine Galaxie nicht mehr in einer Datenbank
gespeichert werden. Diese muss demnach auf mehrere Rechner aufgeteilt werden.
Da die Datenbank einerseits die einzelnen Sterne Speicher und Bäume welche die
Sterne referenziert bietet es sich hier an diese beiden Bestandteile der
Datenbank in einzelne Datenbanken auszulagern.
\paragraph{Sterne (Horizontale Fragmentierung)} ~\\
Möchte man eine Liste an Sternen auf mehrere Datenbanken aufspalten wird die
Liste entsprechend aufgeteilt und auf die Datenbanken verteilt. Wird nun ein
bestimmter Stern gesucht wird die Anfrage über einen reverse-proxy geleitet
welcher die Anfrage an die entsprechende Datenbank weiterleitet.
\par Es ist somit möglich die Liste an Sternen auf mehrere Datenbanken
aufzuteilen und somit die Last von einem System auf mehrere zu verteilen.
\paragraph{Bäume (Vertikale Fragmentierung)} ~\\
Die Aufteilung der Datenbank in der die Bäume gespeichert werden gestaltet sich
ähnlich. Statt alle Bäume in einer Datenbank zu speichern, werden die
entsprechenden Teilbäume ab einer bestimmten Tiefe in verschiedene Datenbanken
verteilt. Die Interaktion mit der Datenbank verändert sich nur minimal. Statt
bei einer Anfrage an die Wurzel eines Baumes die entsprechenden node\_ids der
Kinder zu bekommen, erhält man die Adresse der Datenbank in der der Teilbaum
gespeichert wird. Dies ist in Abbildung \ref{fig:tree_sharding} visuell dargestellt.
\subsubsection{Caching}
Ein weiters Problem das mit der Nutzung eines verteilten Systems entsteht ist
die Bandbreite zwischen den Simulatoren und der Datenbank und die entsprechende
Latenz. Der Durchschnite mehrerer Messungen zwischen verschiedenen Servern ist
in Abbildung \ref{fig:bandwidth_latency} dargestellt.
\begin{figure}
\centering
\begin{tabular} {l | l | l | l}
Server 1 & Server 1 & Bandbreite & Latenz \\
(Standort) & (Standort) & (Mb/s) & (ms) \\ \hline\hline
Nuremberg & Helsinki & 450 & 23 \\ \hline
Nuremberg & Nuremberg & 2000 & \\ \hline
localhost & localhost & 55000 & 0.07 \\ \hline
\end{tabular}
\caption{Messungen der Anbindungen zwischen verschiedenen Servern. Die Messung
Nuremberg \( \leftrightarrow \) Nuremberg bezieht sich auf zwei Server im
gleichen Rechenzentrum und die Messung localhost \( \leftrightarrow \)
localhost auf die selbe Maschine}
\label{fig:bandwidth_latency}
\end{figure}
\par Es wird deutlich, dass falls der Server auf der die Datenbank läuft sich
physisch sehr weit von den Servern auf denen sich die Simulation-container
befinden steht, die Bandbreite zu Problemen führt. Es bietet sich also an die
Daten die viel von den Simulations-containern genutzt werden physisch näher an
die Simulations-container zu bringen um so Probleme die durch die niedrige
Bandbreite und hohe Latenz entstehen zu minimieren. Dies ist in Abbildung
\ref{fig:local_caching} zu sehen: Es existiert eine Haupt-Datenbank welche alle
Zeitschritte speichert und mit den lokalen Datenbanken kommuniziert. Diese
Speichern den jeweiligen Zeitschritt, den die Simulations-Container benötigen
um die Kraft Berechnung durchzuführen. Sobald der Lokale Cache leer ist wird
der Nächste Zeitschritt von der Datenbank in den Cache kopiert und die
Simulations-Container können mit der Arbeit fortfahren.
\begin{figure}[ht]
\centering
\resizebox{\linewidth}{!}{%
\tikzset{concept/.append style={fill={none}}}
\begin{tikzpicture}
\path[mindmap,concept color=black,text=black, level 1/.append style={level distance=4.5cm,sibling angle=120},]
node[concept] {Haupt Datenbank}
[clockwise from=0]
child[draw, concept color=black] {
node[concept] {Nuremberg \\ Cache}
[clockwise from=60]
child { node[concept] {Simulator 1} }
child { node[concept] {Simulator \dots} }
child { node[concept] {Simulator \( n \)} }
}
child[draw, concept color=black] {
node[concept] {Helsinki \\ Cache}
[clockwise from=-60]
child { node[concept] {Simulator 1} }
child { node[concept] {Simulator \dots} }
child { node[concept] {Simulator \( n \)} }
}
child[draw, concept color=black] {
node[concept] {Falkenstein \\ Cache}
[clockwise from=-180]
child { node[concept] {Simulator 1} }
child { node[concept] {Simulator \dots} }
child { node[concept] {Simulator \( n \)} }
};
\end{tikzpicture}
}
\caption{Aufspaltung der Datenbank und Nutzung von lokalen Caches}
\label{fig:local_caching}
\end{figure}
Der Server-Hoster hetzner bietet eine simple und schnelle Art caching einfach
aufzubauen. Es ist möglich ein virtueles Volumen zu erzueugen auf das mehrere
Rechner in einem Rechenzentrum zugreifen können. Das Virtuelle Volume wird in
die entsprechenden Server als volume eingehangen und ist somit für alle
nutzbar. Damit lässt sich an den jeweiligen Standorten einfach ein cache
implementieren.
|