Martin Puppe

Notizen

Fundstücke 28/2015

| Comments

Griechenland

Die jetzige Zuspitzung der Krise in und um Griechenland ist nur ein Symptom viel tiefer liegendender Probleme unseres Wirtschaftssystems. Man könnte vieles zu dem Thema schreiben. Vielleicht mache ich das auch irgendwann. Hier erst mal nur Links, ohne Kommentare.

“Die Politik hat sich ins Gefängnis der Märkte begeben”

Gregor Gysi: Der Prohpet

“Volksbefragung”: EU entsetzt über neuartige Entscheidungsmethode aus Griechenland

“Wenn es ernst wird, muss man lügen”

Sonstiges

Inspeqtor and OSS Products With Mike Perham
Mike Perham hat zwei Open-Source-Projekte gestartet, von denen er mittlerweile ganz gut leben kann. Genau genommen, verdient er sein Geld mit proprietären Zusatzfeatures. In dem Artikel, den ich letzte Woche verlinkt habe, wurde dieses Modell als “Open Core” bezeichnet.

“Kein Aserbaidschaner würde je behaupten, in einer Demokratie zu leben”
Ich wusste bisher sehr wenig über Aserbaidschan. Jetzt weiß ich mehr.

The Future of UI Design? Old-School Text Messages und On conversational UIs
“Conversational UIs” sind anscheinend ein großer Trend in China. Ich bin skeptisch. Künstliche Intelligenz ist eben meistens doch nicht intelligent genug. Aber es schadet nicht über Alternativen nachzudenken. Kennt jemand Beispiele für solche “Apps”, die man hierzulande mal ausprobieren könnte?

The Lone Bellow - Button

Fundstücke 27/2015

| Comments

Statt gelegentlich Links kommentarlos über Twitter zu verteilen, will ich in Zukunft hier im Blog mehr oder weniger (eher weniger) regelmäßig auf interessante Artikel, Vorträge, Musik etc. verweisen.

The Tradeoff Fallacy (PDF, via TechCrunch)
Es wird ja gerne behauptet, dass Konsumenten gerne ihre Daten abgeben, um im Gegenzug kostenfreie Dienste zu erhalten. Diese Studie belegt, dass eine Mehrheit der Konsumenten keineswegs mit diesem Deal einverstanden ist, sondern sich einfach machtlos fühlt. Meine Meinung: Unternehmen wie Facebook sitzen dank Netzwerkeffekt auf einem Monopol, das sie ausnutzen, um die Konditionen zu diktieren. Natürlich könnte ich auf Whatsapp verzichten und einen anderen Messenger nutzen, aber da sind eben nicht die Leute, die ich erreichen will.

The Internet’s Original Sin
Der Artikel verweist auch auf einen großartigen Vortrag von Maciej Cegłowski. Werbefinanzierte Geschäftsmodelle führen notwendigerweise zu Zentralisierung und Überwachung.

A Constructive Look At TempleOS
Es wäre leicht, TempleOS als eine Kuriosität abzutun. Immerhin behauptet der Programmierer, von Gott beauftragt worden zu sein, ein Betriebssystem zu entwickeln. Aber statt billige Witze zu machen, geht der Autor ohne Vorurteile an die Sache heran. Und er stellt fest, dass TempleOS tatsächliche einige coole Ideen beinhaltet.

The Death Of The Von Neumann Architecture
Beim Lesen des Artikels musste ich direkt an Cory Doctorows Vortrag auf dem 28C3 denken. Es geht um Kontrolle und Macht. Wenn ich einen Computer kaufe, gehört er dann mir, oder dem Hersteller? Wenn ich ein Auto kaufe, gehört es dann mir oder dem Hersteller?

How to Make Money from Open Source Platforms Teil 2 Teil 3 Teil 4
Kann man mit Freier Software Geld verdienen? Vor allem Teil 3 fand ich aufschlussreich.

Artificial intelligence?
“We need to be aware of our prejudices and bigotries, and make sure that we don’t build systems that grant our prejudices the appearance of science. That’s ultimately why AI scares us: we worry that it will be as inhuman as humans all too frequently are. If we are aware of our own flaws, and can honestly admit and discuss them, we’ll be OK.” “We might use AI to hide from our responsibility for our mistakes, but they will most certainly be our mistakes.”

TIS-100 und Door Kickers
Zwei tolle Spiele! Vielleicht muss man Programmierer sein, um mit TIS-100 Spaß zu haben. Andererseit hätte ich selbst niemals gedacht, dass ich mal freiwillig Assembler programmieren würde.

9 Anti-Patterns Every Programmer Should Be Aware Of
Besonders über Punkt 3 (“Analysis Paralysis”) stolpere ich leider gelegentlich.

The World Was Never Closer To Nuclear War Than On Jan. 25, 1995

Endangered Blood: NPR Music Tiny Desk Concert

Installing Rust nightly builds into your home directory

| Comments

Currently, the easiest way to get up and running with Rust is to run the following command in your shell:

curl -s https://static.rust-lang.org/rustup.sh | sudo sh

This will install Rust into /usr/local and you usually need root permissions to do that. I had been looking for an alternative for two reasons:

  • On my Macbook, /usr/local is mostly managed by Homebrew and brew doctor complains if it finds libraries that were put there by someone else.
  • I don’t have root permissions at the university computer lab.

Fortunately, installing Rust into $HOME is relatively painless. rustup.sh lets you specify a custom prefix. The above command only has to be slightly tweaked:

curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --prefix=$HOME/.local

Once Rust has been installed, there’s still three things left to do.

  1. Put rustc etc. on your $PATH.
  2. Tell rustc where to find the Rust libraries.
  3. Tell man where to find the manual pages.

The first two points can be accomplished by adding the following to your $HOME/.bashrc or $HOME/.zshrc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if [ -d $HOME/".local/bin" ] ; then
    export PATH="$HOME/.local/bin:$PATH"
fi

rust_dyld=$HOME/.local/lib/rustlib/x86_64-apple-darwin/lib
if [ ! -d $rust_dyld ] ;
    rust_dyld=$HOME/.local/lib/rustlib/x86_64-unknown-linux-gnu/lib
fi

if [ -d $rust_dyld ] ; then
    if [ -z $DYLD_LIBRARY_PATH ] ; then
        export DYLD_LIBRARY_PATH=$rust_dyld
    else
        export DYLD_LIBRARY_PATH=$rust_dyld:$DYLD_LIBRARY_PATH
    fi
fi

unset rust_dyld

Note: Take a look at lines 5 to 8. You should check whether either of these two directories actually exists. If not, you have to modify those lines accordingly.

If you are using fish, put this into $HOME/.config/fish/config.fish instead:

1
2
3
4
5
6
7
8
9
10
11
12
if test -d $HOME/.local/bin
    set -gx PATH $HOME/.local/bin $PATH
end

set -l rust_dyld $HOME/.local/lib/rustlib/x86_64-apple-darwin/lib
if test ! -d $rust_dyld
    set rust_dyld $HOME/.local/lib/rustlib/x86_64-unknown-linux-gnu/lib
end

if test -d $rust_dyld
    set -gx DYLD_LIBRARY_PATH $rust_dyld $DYLD_LIBRARY_PATH
end

Now we need to tell man where to find the manual pages. Add the following line to $HOME/.manpath:

MANPATH_MAP /Users/martin/.local/bin    /Users/martin/.local/share/man

Finally, start a new terminal session and try the following:

rustc --version
man rustc

If you later want to upgrade to the latest nightly, just rerun rustup.sh (like above):

curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --prefix=$HOME/.local

Git-Repositories ohne Root-Rechte hosten mit Gitolite

| Comments

Über die Vorzüge von Git brauche ich an dieser Stelle nicht viele Worte verlieren. Das verteilte Versionkontrollsystem ist bei der Software-Entwicklung ein äußerst nützliches Werkzeug. Ein Server ist dabei nicht unbedingt notwendig, um es zu verwenden. Aber ein zentrales Repository, das immer erreichbar ist, hat durchaus einige Vorteile. In diesem Artikel möchte ich zeigen, wie einfach es ist, für einzelne Benutzer oder für Gruppen Repositories auf einem Server zu hosten. Und das Beste dabei ist: mehr als einen SSH-Zugang mit einfachen Benutzerrechten braucht man dazu nicht!

Im Folgenden gehen wir davon aus, dass ein SSH-Zugang mit dem Benutzernamen “benutzer” auf dem Server “server” vorhanden ist.

Einzelne Benutzer

Möchte man ganz allein an einem Repository arbeiten, so ist die Einrichtung besonders simpel. Es reicht aus, auf dem Server mit git init --bare repo.git1 ein “nacktes” Repository zu erzeugen, also ein Repository das keine Arbeitsverzeichnis enthält. Unter der Annahme, dass der vorhergehende Befehl im Verzeichnis $HOME/git des Servers ausgeführt wurde, kann auf der lokalen Maschine jetzt mittels git clone benutzer@server:git/repo.git ein Klon des Repositories erstellt werden. Mit git push und git pull Änderungen zwischen lokalem und entferntem Repository übermittelt werden.

Gruppen

Nehmen wir nun an, dass wir auch anderen Personen lesenden oder schreibenden Zugriff auf ein oder mehrere Repositories gewähren wollen. Außerdem nehmen wir an, dass wir diesen Personen nur den Zugriff auf die Repositories nicht aber auf unser komplettes Benutzerkonto gestatten wollen. Ohne die notwendigen Rechte, um eigens dafür eine Unix-Gruppe zu erstellen, scheint das Problem nur schwierig zu lösen zu sein. Doch dank gitolite kann man einen anderen Weg gehen. Neben Git muss der Server dazu nur zwei Voraussetzungen erfüllen:

  1. der Server muss SSH-Authentifizierung mittels Schlüsselpaar erlauben, und
  2. es muss ein Perl-Interpreter vorhanden sein.

Beides sollte unter den meisten Unix-Systemen gegeben sein.

Kurz zur Funktionsweise von Gitolite. SSH erlaubt die Authentifizierung mittels Schlüsselpaar. Gitolite nutzt diesen Mechanismus, um am Schlüssel zu erkennen, welcher Gitolite-Benutzer sich einloggt. Gitolite erlaubt dabei aber den Benutzern nur den Zugriff auf die Git-Repositories nicht auf den vollständigen Benutzer-Account.

Gitolite einrichten

Als erstes erstellen wir auf dem lokalen Rechner ein SSH-Schlüsselpaar. Dazu wird das Kommando ssh-keygen verwendet. Die Voreinstellung für den Dateinamen kann man mit Return bestätigen. Es ist ratsam eine gute Passphrase zu verwenden2. Den öffentlichen Teil des soeben erzeugten Schlüsselpaares kopiert man nun auf den Server. Der Name der Datei auf dem Server kann dabei frei gewählt werden, und entspricht dem Namen unter dem Gitolite den Benutzer später kennen wird (hier: “alice”).

1
scp $HOME/.ssh/id_rsa.pub benutzer@server:alice.pub

Nun loggen wir uns auf dem Server ein und stellen mittels echo $PATH sicher, dass der Ordner bin im Homeverzeichnis existiert und Teil des Suchpfads für ausführbare Dateien ist. Falls nicht:

1
2
mkdir $HOME/bin
export PATH=$HOME/bin:$PATH

Damit die Änderung von $PATH auch nach dem Schließen des Terminals oder dem Ausloggen erhalten bleibt, trägt man die zweite Zeile außerdem in die Datei $HOME/.profile oder $HOME/.bashrc ein.

Nun wird Gitolite installiert und initialisiert. Hier im Beispiel wird Gitolite in das Verzeichnis $HOME/src/gitolite installiert.

1
2
3
4
5
6
mkdir $HOME/src
cd $HOME/src
git clone git://github.com/sitaramc/gitolite
gitolite/install -ln
cd $HOME
gitolite setup -pk alice.pub

Falls vorher schon der selbe SSH-Schlüssel zur Anmeldung verwendet wurden, wird nach der Eingabe des letzten Befehls eventuell eine Warnung angezeigt. Der Grund ist, dass der Schlüssel nun doppelt in der Datei $HOME/.ssh/authorized_keys vorhanden ist. Falls dies der Fall ist, sollte der entsprechende alte Eintrag entfernt werden. ACHTUNG: Mit dem Schlüssel ist dann aber keine Anmeldung mehr mit vollem Shell-Zugriff möglich, es sei denn der folgende Schritt wird ausgeführt.

Einem Gitolite-Nutzer Shell-Zugriff erlauben

Dem Gitolite-Nutzer (hier “alice”) dem der eigentliche SSH-Zugang gehört, sollte man vollen Zugriff auf den (Unix-)Nutzeraccount gewähren. Dazu wird zunächst der Befehl echo alice > $HOME/.gitolite.shell-users ausgeführt. Dann wird an zwei Stellen die Datei $HOME/.gitoliterc bearbeitet. Die erste Stelle ist die folgende Zeile:

1
# SHELL_USERS_LIST        =>  "$ENV{HOME}/.gitolite.shell-users",

Hier wird das Zeichen # entfernt. Die zweite Stelle sieht so aus:

1
2
# give some users direct shell access
# 'Shell',

Auch hier wird in der zweiten Zeile das # entfernt. Anschließend muss noch der Befehl gitolite compile; gitolite trigger POST_COMPILE ausgeführt werden.

Administration von Gitolite

Damit ist die Einrichtung von Gitolite abgeschlossen. Nun stellt sich die Frage, wie man weitere Benuter und Repositories hinzufügt. In Gitolite wird die gesamte Administration über ein dafür bei der Initialisierung erzeugtes Repository abgewickelt. Auf dem lokalen Rechner wird dieses daher erst mal geklont:

1
git clone benutzer@server:gitolite-admin.git

In diesem Repository befinden sich zwei Unterverzeichnisse, conf und keydir. Benutzer kann man nun einfach hinzufügen, indem man deren öffentliche Schlüssel nach keydir kopiert und sie nach dem Schema name.pub benennt.

Um Repositories hinzuzufügen muss die Datei conf/gitolite.conf bearbeitet werden. Ein Beispieleintrag sähe folgendermaßen aus:

gitolite-admin/conf/gitolite.conf
1
2
3
4
repo foo
    RW+     =   alice
    R       =   bob
    RW      =   eve

In diesem Beispiel hat Alice darf lesen, schreiben und Rewinds durchführen; Bob darf nur lesen; Eve darf lesen und schreiben, aber keine Rewinds durchführen.

Nachdem Änderungen durchgeführt wurden, müssen diese committet werden und auf den Server gepusht werden. Erst dann werden sie wirksam und hier im Beispiel wird das Repository foo erstellt. Anschließend kann etwa Eve mit

1
git clone benutzer@server:foo.git

das Repository klonen. Wobei der Unix-Benuter “benutzer” für alle Gitolite-Benutzer der selbe ist.

  1. Die Endung “.git” ist dabei nicht notwendig, aber per Konvention werden so nackte Repositories kenntlich gemacht, um sie von normalen Repositories zu unterscheiden.

  2. Eine gute Methode, um sichere Passphrasen zu erzeugen, die dennoch leicht zu merken sind, ist Diceware.

BitTorrent Sync auf QNAP-NAS-Geräten

| Comments

Dropbox (Referral-Link) ist an sich eine schöne Sache. Einfach Dateien in einen Ordner werfen, und auf jedem Gerät, das man mit seinem Dropbox-Account verbindet, wird dieser Ordner synchronisiert. Das erlaubt mir beispielsweise, überall auf die Dateien zuzugreifen, die ich für die Uni brauche. Aber Dropbox hat auch Nachteile. Es ist mit einem Dollar pro Jahr und Gigabyte recht teuer. Außerdem hat Dropbox jederzeit Zugriff, auf die Daten, die man ihnen anvertraut.

Und hier kommt BitTorrent Sync ins Spiel. Das Programm erlaubt es, beliebig große Ordner zwsichen beliebig vielen Rechnern verschlüsselt über das BitTorrent-Protokoll zu synchronisieren. BitTorrent Sync ist zwar erst in der Alpha-Phase, läuft aber bereits recht stabil.

Die Synchronisierung zwischen zwei Rechnern funktioniert natürlich nur, wenn beide Rechner gleichzeitig eingeschaltet sind. Um eine mit Dropbox vergleichbare Funktionalität zu bekommen, braucht man also einen Server, der rund um die Uhr eingeschaltet ist, oder zumindest immer dann, wenn einer der anderen Rechner läuft. Dafür kommt beispielsweise ein NAS-Server in Frage, wie sie etwa von QNAP hergestellt werden.

Und damit kommen wir zum eigentlichen Inhalt dieses Artikels. Ich hatte am Pfingst-Wochenende etwas Zeit zum Experimentieren und habe BitTorrent Sync auf meinem QNAP-NAS zum Laufen bekommen. Eine Anleitung und die benötigten Skripte sind unter https://github.com/puppe/btsync-qpkg zu finden.

Unter Linux einen Proxy verwenden

| Comments

Manchmal ist man in Netzwerken unterwegs, deren Firewall erst mal jeglichen Netzwerkverkehr blockt. Um den Benutzern dennoch zu ermöglichen, zumindest auf das Web zuzugreifen, wird dann meist ein Proxy-Server zur Verfügung gestellt, der zwischen den Benutzern und dem eigentlichen Ziel der Anfragen vermittelt.

Nehmen wir einmal an, dass der Proxy-Server unter der IP-Adresse 192.168.0.42 erreichbar ist und auf Port 8080 lauscht. Dann muss in die Datei /etc/profile folgendes eingetragen werden:

1
2
3
4
5
6
export http_proxy=http://192.168.0.42:8080
export https_proxy=http://192.168.0.42:8080
export ftp_proxy=http://192.168.0.42:8080
export HTTP_PROXY=$http_proxy
export HTTPS_PROXY=$https_proxy
export FTP_PROXY=$ftp_proxy

Was aber, wenn man für manche Anfragen explizit keinen Proxy-Server verwenden will, z.B. weil man auf Dienste zugreifen will, die auf dem eigenen Rechner oder innerhalb des lokalen Netzwerkes laufen? Dazu dient die Umgebungsvariable no_proxy. Möchte man beispielsweise für Anfragen an localhost oder die IP-Adresse 192.168.0.10 keinen Proxy verwenden, so sollte der Datei /etc/profile folgende Zeile hinzugefügt werden:

1
export no_proxy=localhost,192.168.0.10

Hadoop unter Debian Squeeze installieren

| Comments

Im folgenden Artikel beschreibe ich, wie Hadoop unter Debian Squeeze im sogenannten pseudo-verteilten Modus installiert werden kann. Den Text habe ich im Rahmen meiner Tätigkeit als studentische Hilfskraft beim Leibniz-Zentrum für Psychologische Information und Dokumentation (ZPID) für ein internes Wiki verfasst. Da ich denke, dass diese Anleitung auch für andere nützlich sein könnte, veröffentliche ich sie auch hier im Blog.


Die wichtigsten Bestandteile von Hadoop sind:

  • Hadoop Distributed File System (im Folgenden HDFS)
  • Hadoop MapReduce (im Folgenden HMR)

Beide folgen einer Master/Slave-Architektur. Der Master-Dienst heißt bei HDFS NameNode, der Slave-Dienst DataNode. Bei HMR ist der sogenannte JobTracker der Master und die TaskTracker sind die Slaves. Typischerweise besteht ein Hadoop-Cluster aus mindestens drei Arten von Maschinen:

  • Ein dedizierter NameNode
  • Ein dedizierter JobTracker
  • Mehrere Maschinen, auf denen sowohl der DataNode- als auch der TaskTracker-Dienst läuft

Als Einstieg in den Betrieb eines Hadoop-Clusters, und um Programme zu testen, die das HMR-Framework verwenden, bietet es sich an, erst einmal alle Dienste auf einer einzigen Maschine laufen zu lassen. Dieser Operationsmodus wird als pseudo-verteilt bezeichnet.

Wir gehen von folgenden Voraussetzungen aus:

  • Betriebssystem: Debian 6 Squeeze (sollte aber auch für die meisten anderen Debian-basierten Distributionen funktionieren)
  • Installierte Java Runtime Environment 6 (hier das Paket openjdk-6-jre-headless)

Installation

Download-Links sind unter http://hadoop.apache.org/releases.html zu finden. Wir laden z.B. die aktuelle stabile Version für x86-Systeme und deren Signatur von einem Spiegel-Server herunter:

1
2
wget http://mirror.synyx.de/apache/hadoop/common/hadoop-1.0.4/hadoop_1.0.4-1_i386.deb
wget http://mirror.synyx.de/apache/hadoop/common/hadoop-1.0.4/hadoop_1.0.4-1_i386.deb.asc

Außerdem laden wir die Datei KEYS von einem Server der Apache Foundation und importieren die Schlüssel in den GPG-Schlüsselring. Anschließend verifizieren wir die Signatur.

1
2
3
wget http://www.eu.apache.org/dist/hadoop/common/KEYS
gpg --import KEYS
gpg --verify hadoop_1.0.4-1_i386.deb.asc

Schließlich installieren wir das Paket:

1
sudo dpkg -i hadoop_1.0.4-1_i386.deb

Bei der Installation werden automatisch die Gruppe hadoop und die Benutzer hdfs und mapred angelegt.

Konfiguration

Zunächst müssen unter /etc/hadoop die Dateien core-site.xml, hdfs-site.xml und mapred-site.xml mit folgendem Inhalt angelegt werden:

core-site.xml
1
2
3
4
5
6
<configuration>
     <property>
         <name>fs.default.name</name>
         <value>hdfs://localhost:9000</value>
     </property>
</configuration>
hdfs-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
    <property>
        <name>dfs.name.dir</name>
        <value>/var/local/lib/hadoop-dfs/name</value>
    </property>
    <property>
        <name>dfs.data.dir</name>
        <value>/var/local/lib/hadoop-dfs/data</value>
    </property>
</configuration>
mapred-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
    <property>
        <name>mapred.job.tracker</name>
        <value>localhost:9001</value>
    </property>
    <property>
        <name>mapred.system.dir</name>
        <value>/hadoop/mapred/system</value>
    </property>
    <property>
        <name>mapreduce.jobtracker.staging.root.dir</name>
        <value>/user</value>
    </property>
</configuration>

Eine Übersicht über alle Konfigurationsoptionen und deren Standardwerte findet sich unter:

Zuletzt müssen wir noch sicherstellen, dass die Umgebungsvariable JAVA_HOME korrekt gesetzt wird. Dazu bearbeiten wir die Datei /etc/hadoop/hadoop-env.sh bearbeiten. Wir ersetzen die Zeile

1
export JAVA_HOME=/usr/lib/jvm/java-6-sun

durch

1
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk

Wichtig! Damit die letzte Änderung wirksam wird, müssen wir uns aus- und wieder einloggen. Mittels echo $JAVA_HOME kann überprüft werden, ob die Variable richtig gesetzt ist.

Erstes Starten der Dienste

Wir werden zuerst das HDFS und dann HMR starten.

HDFS starten

Bevor wir HDFS starten, müssen wir die lokalen Ordner erstellen, in denen die Daten des HDFS abgelegt werden. Es handelt sich dabei, um die Ordner die zuvor in der Konfigurationsdatei hdfs-site.xml festgelegt wurden. Anschließend wirds verteilte Dateisystem formatiert. Einige der Operationen müssen als Benutzer hdfs ausgeführt werden. In diesen Fällen steht am Beginn des Befehls sudo -u hdfs.

1
2
3
4
sudo mkdir -p /var/lib/local/hadoop-dfs
sudo chown hdfs:hadoop /var/lib/local/hadoop-dfs
sudo -u hdfs mkdir /var/lib/local/hadoop-dfs/name /var/lib/local/hadoop-dfs/data
sudo -u hdfs hadoop namenode -format

Nun können wir den NameNode-Dienst und den DataNode-Dienst starten.

1
2
sudo service hadoop-namenode start
sudo service hadoop-datanode start

Nun sollte es möglich sein unter http://localhost:50070 den aktuellen Status des NameNodes und der DataNodes zu einzusehen.

Sollte irgendetwas nicht funktionieren, hilft es bei der Diagnose der Probleme einen Blick in die Log-Dateien zu werfen. Diese werden unter /var/log/hadoop abgelegt. Fehler des NameNode-Dienstes bspw. findet man unter /var/log/hadoop/hadoop--namenode-HOSTNAME.log. Wobei HOSTNAME durch den tatsächlichen Hostnamen bzw. Rechnernamen ersetzt werden muss.

HMR starten

Bevor wir die Dienste für MapReduce starten können, müssen wir im HDFS das Verzeichnis /hadoop/mapred/system anlegen und mapred als Besitzer des Verzeichnisses festlegen.

1
2
sudo -u hdfs hadoop fs -mkdir /hadoop/mapred/system
sudo -u hdfs hadoop fs -chown -R mapred:hadoop /hadoop/mapred

Mittels hadoop fs -ls /hadoop/mapred, können wir uns davon überzeugen, dass der Ordner mit den richtigen Rechten erstellt wurde.

Nun können wir die TaskTracker- und JobTracker-Dienste starten:

1
2
sudo service hadoop-jobtracker start
sudo service hadoop-tasktracker start

Hat alles geklappt, lässt sich unter http://localhost:50030 der Status des TaskTrackers und der JobTracker einsehen.

Benutzer-Verzeichnis erstellen

1
2
sudo -u hdfs hadoop fs -mkdir /user/BENUTZER
sudo -u hdfs hadoop fs -chown BENUTZER:GRUPPE /user/BENUTZER

Ein Beispiel-Programm ausführen

Unsere Hadoop-Installation testen wir mit einem Beispiel-Programm aus, das in der Hadoop-Distribution enthalten ist. Zunächst kopieren wir die Eingabedaten in das verteilte Dateisystem, dann führen wir das Programm aus, und schließlich kopieren wir die Ausgabedaten des Programms auf das lokale Dateisystem.

1
2
3
hadoop fs -put /usr/share/hadoop/templates/conf/ input
hadoop jar /usr/share/hadoop/hadoop-examples-1.0.4.jar grep input output 'dfs[a-z.]+'
hadoop fs -get output output

Mittels cat output/* können wir uns das Ergebnis ansehen.

Closures in Javascript (und Scala)

| Comments

Closures1 sind meiner Meinung nach eines der interessantesten Sprachkonstrukte von Javascript. Sie erlauben sehr elegante Lösungen, die sonst nicht möglich wären. Was das konkret bedeutet, werde ich an einem Beispiel erläutern. Zunächst möchte ich aber erklären, was Closures sind.

Was ist eine Closure?

Eine Closure beinhaltet eine Funktion und (auch nicht-lokale) Variablen, die innerhalb der Funktion referenziert werden. Closures verändern die Lebensdauer von Variablen. Schauen wir uns dazu ein Beispiel an:

1
2
3
4
5
6
7
8
var x = 0;

function foo() {
  var y = 42;
  return x + y;
}

x = x + 1;

Wir sehen hier zwei Variablen, x und y. Im Hinblick auf die Sichtbarkeit unterscheiden sich die Variablen darin, dass y nur innerhalb der Funktion foo sichtbar ist. Was die Lebensdauer betrifft, so existiert y nur während foo ausgeführt wird.

Sichtbarkeit und Lebensdauer für sich genommen sind keine schwierigen Konzepte. Javascript ist allerdings eine funktionale Sprache und da wird die Sache interessant. Javascript erlaubt es nämlich, Funktionen als Rückgabewert anderer Funktionen zu verwenden. Dazu erweitern wir das obige Beispiel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function makeFoo() {
  var x = 0;

  function foo() {
    var y = 41;
    x = x + 1;
    return x + y;
  }

  return foo;
}

var bar = makeFoo();
console.log(bar());
console.log(bar());
console.log(bar());

In der Konsole erhält man folgende Ausgabe:

1
2
3
42
43
44

Das ist auf den ersten Blick überraschend, wenn wir davon ausgehen, dass lokale Variablen nur existieren, solange die Funktion, in der sie definiert wurden, ausgeführt wird. Die Variable x hingegen bleibt weiterhin innerhalb der Funktion foo bzw. bar sichtbar und behält zudem ihren Zustand, obwohl makeFoo vollständig abgearbeitet wurde. foo wurde zusammen mit x in eine Closure gepackt.

Praktische Anwendung

Das obige Beispiel ist konstruiert und in der Praxis kaum nützlich. Closures haben aber durchaus sinnvolle Anwendungen. Stellen wir uns einmal vor, wir wollten verschiedene Objekte mit einer eindeutigen Nummer (einer ID) versehen. Dazu wollen wir eine Funktion generateId definieren, die stets eine andere Zahl zurückliefert. Der Einfachheit halber zählen wir von 0 aufwärts. Wir haben mehrere Möglichkeiten dieses Problem zu lösen.

Möglichkeit 1: Globaler Zähler

1
2
3
4
5
6
7
8
9
10
11
var counter = 0;

function generateId() {
  var id = counter;
  counter = counter + 1;
  return id;
}

var obj = {
  id: generateId()
}

Ich halte diese Lösung für nicht sehr sinnvoll. counter ist eine globale Variable. D.h. wir verschmutzen hier den globalen Namensraum mit einer Variable, die nur für eine einzige Funktion benötigt wird. Außerdem kann die Variable praktisch an jeder Stelle im Programm versehentlich geändert werden. Und es wird überhaupt nicht klar, dass counter und generateId` zusammengehören.

Möglichkeit 2: Zähler und Funktion in einem Objekt

1
2
3
4
5
6
7
8
9
10
11
12
var idGenerator = {
  counter: 0,
  generateId: function() {
    var id = this.counter;
    this.counter = this.counter + 1;
    return id;
  }
};

var obj = {
  id: idGenerator.generateId()
}

Diese Lösung ist schon etwas besser, in der Hinsicht, dass wir auf eine globale Variable counter verzichten. Auch wird der Zusammenhang von counter und generateId deutlicher. Allerdings ist diese Variante unhandlich in der Verwendung (idGenerator.generateId()). Außerdem kann auch hier counter an anderer Stelle im Programm geändert werden, obwohl die Variable einzig und allein von der Funktion generateId manipuliert werden sollte.

Möglichkeit 3: Closure

1
2
3
4
5
6
7
8
9
10
11
12
var generateId = (function() {
    var counter = 0;
    return function() {
        var id = counter;
        counter = counter + 1;
        return id;
    };
})();

var obj = {
  id: generateId()
}

Ich halte diese Lösung für die eleganteste. Der Zusammenhang von counter und generateId wird deutlich und der Zähler ist außerhalb der Funktion überhaupt nicht sichtbar.

Anhang: Das Beispiel in Scala

Closures gibt es nicht nur in Javascript, sondern in wohl jeder funktionalen Programmiersprache. Ich habe mich kürzlich im Rahmen einer Vorlesung mit Scala beschäftigen dürfen, daher hier das obige Beispiel in Scala:

1
2
3
4
5
6
7
8
val generateId = (() => {
  var counter = 0
  () => {
    var id = counter;
    counter = counter + 1;
    id
  }
}).apply()
  1. Kurz zur Terminologie: Das deutsche Wort für „function closure“ ist wohl „Funktionsabschluss“. Allerdings hat man wenig Glück, wenn man danach googlet. Mir scheint der Begriff nicht sehr gebräuchlich zu sein, also bleibe ich bei der englischen Bezeichnung.

XML Schema, Keys und Namespaces

| Comments

Bei der Erstellung von Schemas mit einem Ziel-Namensraum kann man leicht in eine kleine, facepalm-induzierende Falle tappen, die mich heute einiges an Nerven gekostet hat. Um das Problem zu erläutern sehen wir uns am besten ein Beispiel an.

Ohne Namensraum

Beispielinstanz ohne Namensraum (instance.xml) download
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="schema.xsd">

    <bar id="1"/>
    <bar id="1"/>

</foo>

Das Dokument besteht aus einem Wurzelement foo und Kindelementen bar. Die bar-Elemente haben ein Attribut id. Nun wollen wir erzwingen, dass id ein Schlüssel ist. Die Instanz wäre dann nicht valide. Das entsprechende XML-Schema muss dazu etwa so aussehen:

Schema ohne Ziel-Namensraum (schema.xsd) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Man beachte hier vor allem die Zeilen 14 bis 16, in denen festgelegt wird, dass innerhalb des foo-Elements die bar-Elemente eindeutig durch das Schlüssel-Attribut id identifizierbar sein müssen.

Mit Namensraum

Hier folgt eine Beispiel-Instanz, für die http://foo.bar als Namensraum festgelegt wurde.

Beispielinstanz mit Namensraum (instance_namespace.xml) download
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns="http://foo.bar"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://foo.bar schema_falsch.xsd">

    <bar id="1"/>
    <bar id="1"/>

</foo>

Man könnte nun meinen, dass es ausreicht, den Namensraum entsprechend auch im Schema anzugeben. Das wurde im folgenden Beispiel in Zeile 3 und 4 getan.

Falsches Schema mit Zielnamensraum (schema_falsch.xsd) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://foo.bar"
    targetNamespace="http://foo.bar" elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Das Problem ist aber, dass die Beispiel-Instanz nicht als invalide erkannt wird, sondern klaglos akzeptiert wird. Warum ist das so? Das Problem ist der XPath-Ausdruck in Zeile 17. Damit werden alle bar-Elemente selektiert und sichergestellt, dass diese eine eindeutige id haben. Unser Ziel-Namensraum ist allerdings http://foo.bar. Das heißt wir wollen nicht bar-Elemente selektieren, sondern http://foo.bar:bar-Elemente. Das richtige Schema sieht deshalb so aus:

Richtiges Schema mit Zielnamensraum (schema_richtig.xsd) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://foo.bar"
    xmlns:foobar="http://foo.bar"
    targetNamespace="http://foo.bar" elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="foobar:bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Zu beachten sind die Zeilen 4 und 18. In Zeile 4 wird definiert, dass das Kürzel foobar für den Namensraum http://foo.bar steht und in Zeile 18 wird dieses Kürzel verwendet, um den korrekten XPath-Ausdruck zu konstruieren.

resumeLater 0.1 veröffentlicht

| Comments

resumeLater ist eine Firefox-Erweiterung. Sie erlaubt es, die aktuelle Position in einem Youtube-Video zu speichern. Später kann die Wiedergabe an exakt der selben Stelle fortgesetzt werden. Das ist vor allem praktisch bei längeren Videos (etwa von Vorträgen), die man nicht an einem Stück schauen möchte. Download bei addons.mozilla.org.

In Version 0.1 wird die Video-Liste nur lokal im Browser gespeichert. Für Version 1.0 ist die Möglichkeit geplant, die Videos mit Bookmarking-Sites wie Delicious und Pinboard zu synchronisieren. Mittelfristig ist auch eine Android-App denkbar.

Außerdem vorgesehen:

  • Unterstützung weiterer Video-Seiten (Vimeo, Dailymotion etc.)
  • Deaktivieren bei Private Browsing

Quellcode und Issue-Tracker auf Github.