Elixir Ein Werkzeug für hochgradig verteilte ... · Erlang als erprobte Basis für...

Preview:

Citation preview

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

Recommended