Go - Googles Sprache für skalierbare Systeme
-
Upload
frank-mueller -
Category
Software
-
view
87 -
download
1
Transcript of Go - Googles Sprache für skalierbare Systeme
❝ Parallelism Programming as the simultaneous
execution of (possibly related) computations.
Concurrency Programming as the composition
of independently executing processes.
Rob Pike
Höhere Komplexität in Clouds
wordpress
mediawikihaproxy-w mediawiki memcached
haproxy-b wordpress mysql
wordpressvarnish
Plattformen
mediawiki
wordpress
AWS Azure OpenStack Joyent ...
haproxy-w mediawiki memcached
haproxy-b wordpress mysql
wordpressvarnish
juju deploy juju-gui juju deploy wordpress juju deploy mysql juju expose juju-gui
Bootstrap
juju-gui wordpress mysql
juju set mysql dataset-size=50% juju set wordpress tuning=optimized
Bootstrap
juju-gui wordpress* mysql*
juju deploy memcached juju add-relation wordpress memcached juju add-unit wordpress
wordpress*
Bootstrap
juju-gui wordpress* mysql*
memcached
Einige Charms
ceph
mediawiki mongodb
cassandra
rails
mysql
wordpress
rabbitmq
haproxy
apache2
hadoopsquid
hbase
couchdb
postgresqlnfsntp
Google Go als Multi-Paradigmen-Sprache
imperativ
bedingt funktional
bedingt objektorientiert
nebenläufig
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
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 }
State - Watcher - Worker
State
API Networker
Provisioner
Uniter
Deployer
...
!
Collections Individuelle Objekte
Lebenszyklen
Client
Lauter nebenläufige Arbeit
Agent
State
Worker
Goroutine
Goroutine
Worker
Goroutine
Goroutine
Worker
Goroutine
Goroutine
Beispiel Firewaller
unitd Loop
unitd Loop
machined Loop
State Main Loop
machined Loop
unitd Loop
serviced Loop
Environment Configuration
Machines
Machine Units
Exposes
Ports
Herausforderungen nebenläufiger Anwendungen
Rechenleistung nutzen
Rennen kontrollieren
Konflikte vermeiden
Probleme abfedern
Sehr naives Parallelisieren (1)
func process(in []int) []int { resultChan := make(chan int) for _, value := range in { // One goroutine per value. go processValue(value, resultChan) }
// Collecting the results. out := make([]int, len(in)) for i := 0; i < len(in); i++ { out[i] = <-resultChan } return out }
Sehr naives Parallelisieren (2)
func processValue( inValue int, resultChan chan int) { // Time of result calculation may vary. outValue := inValue …
resultChan <- outValue }
Problem und Verbesserung
• Unterschiedliche Laufzeiten führen zu falscher Indizierung im Ergebnis
• Index in der Verarbeitung mitführen
• Ergebnis gemäß Index setzen
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 may vary. 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
Arbeitsteilung (1)
// Process all data passed by reference. func process(ds []*Data) {
var wg sync.WaitGroup for i := 0; i < len(ds); i += chunkSize {
// Process one chunk in the background. go processChunk(ds[i:i+chunkSize], wg)
} // Wait for all processing to complete. wg.Wait()
}
Arbeitsteilung (2)
// Processing a chunk of data passed by reference. func processChunk(ds []*Data, wg sync.WaitGroup) {
// Increment WaitGroup counter and signal when done. wg.Add(1) defer wg.Done() // Process each data. for _, d := range ds {
if d != nil { // Direct manipulation. d.Foo = ... ...
} }
}
Kontrolle von Goroutines
• Keine direkte Beziehung zwischen Goroutines
• Kontrolle nur über Channels
• Externe Packages helfen
• gopkg.in/tomb.v2
• github.com/tideland/goas/v2/loop
Operation in einer Schleifefunc (t *T) loop() {
defer t.tomb.Done() for {
select { case <-t.tomb.Dying():
// Cleanup ... return
case f := <-t.fooChan: if err := t.foo(f); err != nil {
t.tomb.Kill(err) }
case b := <-t.barChan: // ...
} }
}
Steuerung von außen
// Stop ends the main loop. func (t *T) Stop() error {
// Kill(nil) means no error as reason. t.tomb.Kill(nil) return t.tomb.Wait()
}
// Err retrieves the error in case the backend loop died. func (t *T) Err() error {
return t.tomb.Err() }
Prüfung des Backends
func (t *T) Foo(foo *Foo) (*Bar, error) { env := &fooEnv{foo, make(chan *Bar)} select { case t.fooChan <- env: case <-t.tomb.Dead():
return nil, errors.New("backend dead") } select { case bar := <- env.barChan:
return bar, nil case <-t.tomb.Dead():
return nil, errors.New("backend dead") }
}
Flexible Nutzung
• Nur Deklaration
• Einbettung in umfassendere Interfaces
• Implizites Duck Typing
• Anwendbar auf jede Typdefination
Verhalten definieren und kombinieren
type StorageReader interface { Get(name string) (io.ReadCloser, error) List(prefix string) ([]string, error) URL(name string) (string, error)
}
type StorageWriter interface { Put(name string, r io.Reader, length int64) error Remove(name string) error
}
type Storage interface { StorageReader StorageWriter
}
Teilnutzung möglich
// Only the writing is needed. func Write(sw StorageWriter) error {
... return sw.Put(myName, myReader, myLen)
}
// Full storage access here. func RemoveFirst(s Storage, prefix string) error {
l, err := s.List(prefix) if err != nil { return err } return s.Remove(l[0])
}
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 golang.org/x/net
• Weitere Crypto Packages (u.a. OpenPGP) in golang.org/x/crypto
Serialisierung via JSON
type Parameters map[string]interface{}
type Action struct { Tag string `json:"tag"` Receiver string `json:"receiver"` Name string `json:"name"` Parameters Parameters `json:"parameters,omitempty"` }
Marshalling
action := Action{ Tag: "FooBar", … Paramaters: Parameters{"foo": 4711, "bar": true}, }
marshalledAction, err := json.Marshal(action) if err != nil { … }
n, err := anyWriter.Write(marshalledAction)
Unmarshalling
var action Action
err := json.Unmarshal(marshalledAction, &action) if err != nil { … }
fmt.Printf("Tag: %s\n", action.Tag)