1
Elixir – Ein Werkzeug für hochgradig verteilte, fehlertertolerante Systeme
Herzlich willkommen –DevDay 2016
2
Übersicht
Geschaffen von José Valim
Erlang als erprobte Basis für höchstverfügbare Systeme
Funktionale Elemente von Elixir (Pattern matching, looping ohne loop-Konstrukte)
Protokolle und Polymorphie in der funktionalen Welt
Behaviours (Generic Server)
Fehlertolerante Systeme mit Supervision Trees
3
Motivation
4
Erlang als Basis
Wurzeln in der Telekommunikation
Joe Armstrong (Ericsson, 1987)
Nebenläufigkeit und Fehlertoleranz
Transparente Verteilung über heterogener Hardware
Hochverfügbar (Nine Nines)
Lebendig (Erlang/OTP 18.2 16. Dez 2015)
5
Wer setzt Erlang ein?
Amazon (SimpleDB, Amazon Elastic Compute Cloud (EC2))
Yahoo! (Delicious, 5 Mio User, 150 Mio Bookmarks)
Facebook (Chat backend)
WhatsApp (Messaging Server)
T-Mobile (SMS und Authentisierung)
Ericsson (Telekom)
6
Erlang-Applikationen
Ejabberd (Instant Messaging)
CouchDB, Riak, Mnesia
RabbitMQ (AMQP-Implementierung)
7
Stärken von Erlang
Leichtgewichtige Prozesse (Millionen)
Kein Shared Memory
Funktional (keine Seiteneffekte)
«Let it crash»-Philosophie (Supervision)
BEAM VM
OTP-Plattform
8
Warum Elixir?
Erlang als erprobte Basis
Übersichtlicherer, kompakterer Code
Eleganz («syntaktischer Zucker»)
Meta-Programmierung
Mix Build-Tool
9
Joe Armstrong liebt Elixir!
http://joearms.github.io/2013/05/31/a-week-with-elixir.html
«This has been my first week with Elixir, and I’m pretty excited.»
Elixir has a non-scary syntax and combines the good features of Ruby and Erlang. It’s not Erlang and it’s not Ruby and it has ideas of its own.
It’s a new language, but books are being written as the language is being developed. The first Erlang book came 7 years after Erlang was invented, and the first popular book 14 years later. 21 years is too long to wait for a decent book.
Dave [Thomas] loves Elixir, I think it’s pretty cool, I think we’re going to have fun together.
Erlang powers things like WhatsApp and crucial parts of half the world’s mobile phone networks. It’s going to be great fun to see what will happen when the technology becomes less scary and the next wave of enthusiasts joins the party.“
10
Elixir im Einsatz
Web-Framework Phoenix
Millionen aktiver Connections auf einem Server
Antwortzeiten im Mikrosekundenbereich möglich
IoT-Startups bauen auf Elixir
11
Eleganz
12
Summierungsserver in Erlang
-module(sum_server).
-behaviour(gen_server).
-export([
start/0, sum/1, init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3
]).
start() -> gen_server:start(?MODULE, [], []).
sum(Server, A, B) -> gen_server_call(Server, {sum, A, B}).
init(_) -> {ok. Undefined}.
handle_call({sum, A, B}, _From, State) -> {reply, A + B, State}).
handle_cast(_msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
13
Summierungsserver in Elixir
defmodule SumServer do
use GenServer
def start do
GenServer.start(__MODULE__, nil)
end
def sum(server, a, b) do
GenServer.call(server, {:sum, a, b})
end
def handle_call({:sum, a, b}, _from, state) do
{:reply, a + b, state}
end
end
14
Sprache
15
Funktionale Elemente von Elixir
Nicht-mutierbare Daten
Seiteneffektfreie Funktionen
Pattern Matching
Tail-Rekursion
Chaining von Funktionen
Polymorphie mit Protokollen
16
Nicht-mutierbare Daten
Kein OO-Mutieren a la
hashMap.add(key, value)
sondern
newHash = HashMap.add(oldHash, key, value)
Intern effizient durch Daten-Reuse
17
Seiteneffektfreie Funktionen
Funktionen liefern wie mathematische Funktionen bei jedem Aufruf das gleiche Ergebnis.
18
Pattern Matching
iex(1)> {name, gehalt} = {"Max", 5000}
Matchoperator =
bindet name und gehalt:
iex(1)> name
"Max"
iex(1)> gehalt
5000
19
Funktionsdefinitionen mittels Patterns
defmodule Geom do
def area({:square, r}), do: r * r
def area({:rectangle, a, b}), do: a * b
end
iex> c("geom.ex")
[Geom]
Iex> Geom.area({:rectangle, 45, 5})
225
Iex> Geom.area({:square, 45})
2025
Iex> Geom.area({:square, 45, 5})
** (FunctionClauseError) no function clause matching in Geom.area/1
geom.ex:2: Geom.area({:square, 45, 5})
20
Tail-Rekursion
defmodule M do
def fac(0), do: 1
def fac(n), do: n * fac(n - 1)
end
Letzter Aufruf in der Funktion: TailCall-Optimierung. So baut man Loops in Elixir!
21
Verkettung von Funktionen, Pipe-Operator – Beispiel von Joe Armstrong (Erlang)
capitalize_atom(X) ->
list_to_atom(binary_to_list(capitalize_binary(list_to_binary(atom_to_list(X))))).
Elixir:capitalize_atom = fn(x) ->
x
|> to_char_list
|> to_string
|> String.capitalize
|> to_char_list
|> List.to_atom
end
Wesentlich lesbarer!
22
Datenstrukturen mit structs
defmodule Fraction do
defstruct nom: nil, denom: nil
def new(n, d) do
%Fraction{nom: n, denom: d}
end
end
23
Protokolle
Definition:
defprotocol String.Chars do
def to_string(thing)
end
Verwendung:
iex(1)> String.Chars.to_string(1)
„1“
iex(2)> String.Chars.to_string(:myatom)
„myatom“
iex(3)> String.Chars.to_string(Fraction.new(1, 2))
** (Protocol.UndefinedError) protocol String.Chars not implemented
24
Implementation von Protokollen
defimpl String.Chars, for: Fraction do
def to_string(frac) do
"#{frac.nom} / #{frac.denom}"
end
end
iex> String.Chars.to_string(Fraction.new(1, 2))
"1 / 2"
iex> one_half = Fraction.new(1, 2)
iex> IO.puts(one_half)
1 / 2
:ok
25
Verteilung
26
Prozesse, Actor-Modell
pid = spawn(fn) startet einen Prozess
send(pid, message) schickt Nachricht
Empfang:
receive do
Pattern_1 -> action_1
Pattern_2 -> action_2
end
27
Server-Prozesse
defmodule SumServer do
use GenServer
def start do
GenServer.start(__MODULE__, nil)
end
def sum(server, a, b) do
GenServer.call(server, {:sum, a, b})
end
def handle_call({:sum, a, b}, _from, state) do
{:reply, a + b, state}
end
end
Interface-Funktion
(start, sum)
Callback-Funktion
(handle_call, handle_cast)
Handgestrickt mit Tail-RekursionBesser: Implementation von GenServer (OTP gen_server behaviour)
28
Nodes
Nodes sind oberhalb von Prozessen angesiedelt und können auf verschiedenen Maschinen liegen
Namen: iex --sname node1@localhost
Verbindung: Node.connect(:node1@localhost)
Kommunikation:
caller = selfNode.spawn(
:node2@localhost,fn -> send(caller, {:response, „hallo“} end
)
29
Fehlertoleranz
30
Fehlertoleranz
Elixir verfügt über try/catch/after-Konstrukt
Wird selten benötigt, verträgt sich nicht mit Tail-Rekursion
Typischerweise wird ein Prozess von einem Supervisor neu gestartet und verfügt dann wieder über einen «sauberen» Zustand
31
Trapping exits
Prozesse können mit spawn_link bidirektional verknüpft werden
Mittels Process.monitor kann ein Prozess unidirektional überwacht werden
Der Eltern-Prozess kann Exits trappen und entsprechend reagieren
Behaviour Supervisor startet überwachte Prozesse neu
32FractionServer
defmodule FractionServer do
use GenServer
def start_link(nominator) do
GenServer.start_link(FractionServer, nominator, name: :fraction_server)
end
def fraction(pid, denom) do
GenServer.call(pid, {:fraction, denom})
end
def init(_), do: {:ok, 1}
def handle_call({:fraction, denom}, _, state) do
{:reply, state / denom, state}
end
end
33FractionServer Supervisor
defmodule FractionSupervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, nil, name: :fraction_server_supervisor)
end
def init(_) do
processes = [worker(FractionServer, [1])]
supervise(processes, strategy: :one_for_one)
end
end
34Supervision in Aktion
iex(3)> FractionSupervisor.start_link
Starting fraction server
{:ok, #PID<0.72.0>}
iex(7)> pid = Process.whereis(:fraction_server)
#PID<0.73.0>
iex(8)> FractionServer.fraction(pid, 3)
0.3333333333333333
iex(9)> FractionServer.fraction(pid, 0)
Starting fraction server
iex(9)> pid = Process.whereis(:fraction_server)
#PID<0.83.0>
iex(10)> FractionServer.fraction(pid, 3)
0.3333333333333333
35
Supervisor B
Server 1Server 2
Worker 1
Worker 2
Worker 3
one_for_one
one_for_one
simple_one_for_oneone_for_all
Beispiel Supervision Tree
Supervisor CSupervisor A
Root-Supervisor
Top Related