- Digicomp

Werbung
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
Handgestrickt mit Tail-Rekursion
Besser: Implementation von GenServer (OTP gen_server behaviour)
defmodule SumServer do
use GenServer
def start do
GenServer.start(__MODULE__, nil)
end
 Interface-Funktion
 (start, sum)
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
 Callback-Funktion
 (handle_call, handle_cast)
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 = self
Node.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
FractionServer
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
32
FractionServer 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
33
Supervision 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
34
35
Beispiel Supervision Tree
Root-Supervisor
Supervisor A
one_for_one
one_for_all
Supervisor C
one_for_one
Supervisor B
Worker 1
Server 1
Server 2
Worker 2
Worker 3
simple_one_for_one
Herunterladen