Skalierbare Anwendungen
mit Google Go
Frank Müller

Oldenburg
Baujahr 1965
Entwickler
Fachautor
!

frank@mweb.de
@themue
Moderne
Mehrkernsysteme

sind wie die

Vereinigung von Flüssen
Ihre Energie gilt es

effizient zu nutzen
Hier bietet sich

	 	 	 	 	 	 	 Google Go an
imperativ

bedingt objektorientiert

Google Go als multikulturelle Sprache

bedingt funktional

nebenläufig
❝
It’s better to have a permanent
income than to be fascinating.
–Oscar Wilde
Nebenläufigkeit in Go

•

Leichtgewichtige Goroutines im Thread
Pool


• Sehr große Anzahl gleichzeitig möglich

• Kommunikation und Synchronisation über
Channels


•

Vielfältige Kontrolle durch select
Statement
Beispiel: Goroutines als Server
Client

Client

Client

Server
Beispiel: Lastverteilung
Worker

Client

Master

Client

Worker

Worker
Netze von Goroutines
A
C

D

B
E

F

G
E1

E2

E3
Ein Wimmeln wie

im Bienenstock
Kapselung in Typen
package service
!

type Service struct {
	 thisChan	 chan *This
	 thatChan	 chan *That
	 foo	 	 	 bool
	
	 bar	 	 	 int
	 baz	 	 	 string
}
Konstruktoren sind Funktionen
func NewService(...) *Service {
	 s := &Service{
	 	 thisChan: make(chan *This),
	 	 thatChan: make(chan *That, 10),
	 	 …,
	 }
!

	
	

// Start of the backend loop as goroutine.
go s.loop()

!

	 return s
}
Endlosschleifen und select
func (s *Service) loop() {
	 ticker := time.NewTicker(time.Second)
	 for {
	 	 select {
	 	 case this := <-s.thisChan:
	 	 	 s.doThis(this)
	 	 case that := <-s.thatChan:
	 	 	 s.doThat(that)
	 	 case <-ticker.C:
	 	 	 s.doTick()
	 	 }
	 }
}
Methoden als externe Schnittstellen

func (s *Service) DoThis(data string) int {
	 respChan := make(chan int)
	 this := &This{data, respChan}
!

	 s.thisChan <- this
	 return <-respChan
}
Goroutines können
eigene Wege

gehen …
… oder gemeinsame
Rechenleistung nutzen

Konflikte vermeiden

Herausforderungen nebenläufiger Anwendungen

Rennen kontrollieren

Probleme abfedern
Sehr naive Parallelisierung (1)
func process(in []int) []int {
	 resultChan := make(chan int)
	 for _, value := range in {
	 	 // One goroutine per value.
	 	 go processValue(value, resultChan)
	 }
	 out := make([]int, 0)
	 for i := 0; i < len(in); i++ {
	 	 out = append(out, <-resultChan)
	 }
	 return out
}
Sehr naive Parallelisierung (2)

func processValue(
	 inValue int, resultChan chan int) {
	 // Time of result calculation depends
	 // on value.
	 outValue := inValue …
!

	 resultChan <- outValue
}
Verbesserung

•
• Output mit richtiger Größe initialisieren

Ergebnis gemäß Index setzen
•
Index in der Verarbeitung mitführen
Verbesserung (1)
func process(in []int) []int {
	 resultChan := make(chan *result)
	 for index, value := range in {
	 	 // One goroutine per value.
	 	 go processValue(index, value, resultChan)
	 }
	 out := make([]int, len(in))
	 for i := 0; i < len(in); i++ {
	 	 result <- resultChan
	 	 out[result.index] = result.value
	 }
	 return out
}
Verbesserung (2)
type result struct {
	 index int
	 value int
}
!

func processValue(
	 index, inValue int, resultChan chan *result) {
	 // Time of result calculation depends
	 // on value.
	 outValue := inValue …
!

	 // Send index with result.
	 resultChan <- &result{index, outValue}
}
Isolierung von Zugriffen

• Loops serialisieren Zugriffe

• Synchronisation über Channels oder
Package sync


• Referenzen nur an eine Goroutine senden

Aufteilung von Arrays und Slices
•
Unabhängigkeit von Goroutines

• Keine Beziehung zwischen Goroutines

Kontrolle nur über Channels

•
• Externe Packages helfen

git.tideland.biz/goas/loop

•
• launchpad.net/tomb
Schleifenfunktion
func (t *MyType) backendLoop(l loop.Loop) error {
	 for {
	 	 select {
	 	 case <-l.ShallStop():
	 	 	 return nil
	 	 case foo := <-t.fooChan:
	 	 	 if err := t.processFoo(foo); err != nil {
	 	 	 	 return err
	 	 	 }
	 	 case …:
	 	 	 …
	 	 }
	 }
}
Kontrolle der Schleifenfunktion
// Start of the goroutine in constructor.
t.loop = loop.Go(t.backendLoop)
!

// Tell loop to stop.
err := t.loop.Stop()
!

// Kill loop with passed error and wait.
t.loop.Kill(myError)
err := t.loop.Wait()
!

// Retrieve status and possible error.
status, err := t.loop.Error()
Deadlocks vermeiden (Timeout)

func (t *MyType) Foo(foo *Foo) error {
	 select {
	 case t.fooChan <- foo:
	 case <-time.After(timeout):
	 	 return errors.New("timed out")
	 }
	 return nil
}
Deadlocks vermeiden (Loop)

func (t *MyType) Foo(foo *Foo) error {
	 select {
	 case t.fooChan <- foo:
	 case <-t.loop.IsStopping():
	 	 return errors.New("not running anymore")
	 }
	 return nil
}
Vernetzung
Hilfreiche Packages

• net, …/http für IP und Web

html/template, mime/… für Inhalte

•
• encoding/gob, …/json, …/xml für die
Serialisierung


• compress/… zur Komprimierung

• crypto/… zur Verschlüsselung
Externe Packages von Google

• WebSocket, SPDY, Dict und mehr in
code.google.com/p/go.net


• Weitere Crypto Packages (u.a. OpenPGP)
in code.google.com/p/go.crypto
Serialisierung via JSON
type OrderItem struct {
	 Id	 	 	 	 int		 	 `json:"ArticleNo"`
	 Quantity		 int
}
!

type Order struct {
	 Name		 	 string
	 Street	 	 string
	 City	 	 	 string
	 Remarks	 string	 `json:",omitempty"`
	 Items		 	 []OrderItem
}
Marshalling
order := Order{
	 Name: 	 "Foo Bar",
	 …
	 Items:	 []OrderItem{{4711, 1}, {4712, 5}, …},
}
!

marshalledOrder, err := json.Marshal(order)
if err != nil {
	 …
}
!

n, err := anyWriter.Write(marshalledOrder)
Unmarshalling

var order Order
!

err := json.Unmarshal(marshalledOrder, &order)
if err != nil {
	 …
}
!

fmt.Printf("Name: %sn", order.Name)
Netz und Crypto

WebSockets

Fallbeispiel einer API

Reflection für Dispatching

Serialisierung via JSON
❝
Zitat hier eingeben.
Bildquellen –Christian Bauer
123RF

iStockphoto

eigene Quellen

Skalierbare Anwendungen mit Google Go