Slide 1

Slide 1 text

ݎ࿚ͳTCPαʔόΛ࡞ΔͨΊʹ ʙkatsubushiͷ஌ݟ͔Βʙ 2019-06-22 Kamakura.go #5 @fujiwara

Slide 2

Slide 2 text

@fujiwara SRE(૯຿෦) github.com/fujiwara sfujiwara.hatenablog.com WEB+DB Press vol.111 SREνʔϜ࿈ࡌ Perl Hackers HubͰAWS X-Rayͷ࿩Λॻ ͖·ͨ͠

Slide 3

Slide 3 text

katsubushi - ෼ࢄϢχʔΫID࠾൪ػ • ϢχʔΫͳ࣌ܥྻॱͷInt64 IDΛൃߦ͢Δ • ෼ࢄ؀ڥ(ෳ਺୆)ͰඃΒͣಈ͘ • Memcached ProtocolΛ࣮૷͍ͯ͠Δ • Go੡ͷಠ࣮ࣗ૷ϛυϧ΢ΣΞ github.com/kayac/go-katsubushi

Slide 4

Slide 4 text

ݎ࿚ͳαʔόʁ ݎ࿚ is ... • ϦιʔεϦʔΫ͠ͳ͍ • མͪͳ͍ • (ϓϩμΫτͷੑ্࣭) ID͕ܾͯ͠ඃΒͳ͍ • →γϟʔσΟϯά͞ΕͨผDBʹಉҰID͕ൃߦ͞ΕͨΒࢮ

Slide 5

Slide 5 text

memcached protocol ͷ࠾༻ཧ༝ • ܰྔ • HTTPͷΑ͏ʹ༨ܭͳϔομ͕ͳ͍ • Int64 Λ1ݸฦ͢ͷʹϔομ͕Կඦbyteͱ͔ͪΐͬͱ… • ΫϥΠΞϯτ͕ߴ଎ • ౰࣌ओʹ࢖༻͍ͯͨ͠Perlʹ͸Cache::Memcached::Fastͱ ͍͏ߴ଎ͳ࣮૷ ID͸େྔʹൃߦ͢Δ͜ͱ͕͋ΔͷͰ͍ܰͷ͕Α͍

Slide 6

Slide 6 text

HTTPͳΒͱ΋͔͘memcached protocolΛॲཧ͢Δαʔό͸ طଘͷϑϨʔϜϫʔΫ͕ͳ͍ ॻ͚͹͍͍ΑͶɺ؆୯ͩ͠

Slide 7

Slide 7 text

Go Ͱ TCP αʔό net package Ͱ؆୯ʹͰ͖Δ Listen, Acceptͯ͠ConnΛgoroutineͰॲཧ͢Δ͚ͩ ln, err := net.Listen("tcp", ":8080") if err != nil { // handle error } for { conn, err := ln.Accept() if err != nil { // handle error } go handleConnection(conn) }

Slide 8

Slide 8 text

Go Ͱ TCP Echo αʔόɺ௒؆୯ func handleConnection(conn net.Conn) { defer conn.Close() // ൈ͚Δͱ͖ʹ੾அ b := bufio.NewReader(conn) for { line, err := b.ReadString('\n') // 1ߦಡΉ if err != nil { return } _, err = io.WriteString(conn, line) // Φ΢Ϝฦ͠ if err != nil { return } } }

Slide 9

Slide 9 text

͔͠͠ݱ࣮͸໘౗

Slide 10

Slide 10 text

ϦιʔεϦʔΫ͠ͳ͍

Slide 11

Slide 11 text

ແ௨৴λΠϜΞ΢τ • ܨ͗ͬͺͳ͠Ͱ͍ͳ͘ͳΔΫϥΠΞϯτରࡦ • ΫϥΠΞϯτ͕TCPΛ͖Ε͍ʹ੾அ͢Δͱ͸ݶΒͳ͍ • αʔόଆͰ੾Βͳ͍ͱίωΫγϣϯ(goroutine)͕ϦʔΫ͢Δ

Slide 12

Slide 12 text

net/Conn.SetDeadline ίωΫγϣϯʹࢦఆͨ࣌͠ࠁ·Ͱ௨৴͕ͳ͔ͬͨΒ ΤϥʔʹͳΔΑ͏ઃఆ͢Δ ௨৴ཱ֬௚ޙɺಡΈॻ͖Ͱ͖ͨλΠϛϯάͰ࠶౓ઃఆ → ແ௨৴λΠϜΞ΢τ͕࣮૷Ͱ͖Δ type Conn interface { SetDeadline(t time.Time) error }

Slide 13

Slide 13 text

for { conn, err := ln.Accept() if err != nil { // handle error } conn.SetDeadline(time.Now().Add(idleTimeout)) go handleConnection(conn) } func handleConnection(conn net.Conn) { defer conn.Close() buf := bufio.NewReader(conn) for { line, _ := buf.ReadString('\n') // 1ߦಡΉ conn.SetDeadline(time.Now().Add(idleTimeout)) // ^λΠϜΞ΢τԆ௕

Slide 14

Slide 14 text

ϓϩηεऴྃ࣌ʹ੾அ αʔό΋ͪΌΜͱ੾அ͠ͳ͍ͱߦّ͕ѱ͍ΑͶ? func (app *App) handleConn(ctx context.Context, conn net.Conn) { defer conn.Close() // ͜ͷίωΫγϣϯͷॲཧΛࣗൃతʹ੾Δͱ͖ go func() { <-ctx.Done() // ϓϩηε͕γάφϧΛड͚Δͱ͜ͷctx͕Done conn.Close() // ͪΌΜͱ੾Δʂ }() // ...

Slide 15

Slide 15 text

͜͜Ͱ໰୊Ͱ͢ ͜ͷίʔυͰ͸ͳʹ͔͕ϦʔΫ͠·͢ func (app *App) handleConn(ctx context.Context, conn net.Conn) { defer conn.Close() // ͜ͷίωΫγϣϯͷॲཧΛࣗൃతʹ੾Δͱ͖ go func() { <-ctx.Done() // ϓϩηε͕γάφϧΛड͚Δͱ͜ͷctx͕Done conn.Close() // ͪΌΜͱ੾Δʂ }() // ...

Slide 16

Slide 16 text

goroutineϦʔΫ handleConn͕returnͯ͠΋goroutine͕ࢮͳͳ͍ func (app *App) handleConn(ctx context.Context, conn net.Conn) { defer conn.Close() // ͜ͷίωΫγϣϯͷॲཧΛࣗൃతʹ੾Δͱ͖ go func() { <-ctx.Done() // ͜Ε͕ݺ͹ΕΔ(=ϓϩηεऴྃ)·Ͱ conn.Close() // goroutine ͕ੜ͖࢒Δ }() // ...

Slide 17

Slide 17 text

मਖ਼ํ๏1 ࢠ context Λ࡞ͬͯ defer cancel() ࣗൃతऴྃ࣌ɺ਌ऴྃ࣌ͲͪΒͰ΋ίωΫγϣϯ੾அͱ goroutineऴ͕ྃͰ͖ΔͷͰϦʔΫ͠ͳ͍ func (app *App) handleConn(ctx context.Context, conn net.Conn) { ctx2, cancel := context.WithCancel(ctx) defer cancel() go func() { <-ctx2.Done() conn.Close() }() // ... 1 Fix goroutine leak. #28 https://github.com/kayac/go-katsubushi/pull/28/files

Slide 18

Slide 18 text

མͪͳ͍

Slide 19

Slide 19 text

ʮ͜ͷ katsubushi ಈ͍ͯΔ͔ͳ?ʯ $ curl localhost:11212 VALUE / 0 18 591644124189298688 VALUE HTTP/1.1 0 18 591644124189298689 END ERROR ERROR ERROR ʮID͸ฦ͖͚ͬͯͨͲͳʹ͜Ε??ʯ ʮ͋ɺcurlͰୟ͍ͯͨɻɻɻʯ

Slide 20

Slide 20 text

Կ͕ى͖͍͔ͯͨ curl ͸ katsubushi ʹ HTTP ͰϦΫΤετͨ͠(౰વ) $ curl -v 127.0.0.1:11212 > GET / HTTP/1.1 > Host: 127.0.0.1:11212 > User-Agent: curl/7.65.1 > Accept: */* > (ۭߦ) HTTPͷϦΫΤετ͸ memcached protocol Ͱ΋ղऍͰ͖Δʂ memcached protocol Ͱ஋ͷऔಘ: GET key1 key2 HTTP Ͱ GET ϦΫΤετͷ1ߦ໨: GET / HTTP/1.1

Slide 21

Slide 21 text

ͭ·Γ͜͏͍͏Ϩεϙϯε͕ฦΔ VALUE / 0 18 # / ͱ͍͏keyͷ஋(flag=0, 18byte) 591644124189298688 VALUE HTTP/1.1 0 18 # HTTP/1.1 ͱ͍͏keyͷ஋(flag=0, 18byte) 591644124189298689 END # HTTPͷ1ߦ໨ʹ͸Ϩεϙϯε͕ฦͤͨΑʂ ERROR # Host: ͱ͔஌Βͳ͍! ERROR # User-Agnet: ͱ͔஌Βͳ͍!! ERROR # Accept: ͱ͔஌Βͳ͍!!!

Slide 22

Slide 22 text

Ұํͦͷ͜Ζ HTTP Λ৯΂ͨ katsubushi ͸ panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x134807f] goroutine 20 [running]: github.com/kayac/go-katsubushi.(*App).handleConn(0xc00012a000, 0x14a3de0, 0xc0000ac038) /Users/fujiwara/src/github.com/kayac/go-katsubushi/app.go:160 +0x2cf created by github.com/kayac/go-katsubushi.(*App).Listen /Users/fujiwara/src/github.com/kayac/go-katsubushi/app.go:131 +0x2ed ʮ͠ɺࢮΜͰΔ…ʯ

Slide 23

Slide 23 text

Կ͕ى͖͍͔ͯͨ ಡΈऔͬͨϦΫΤετߦ([]byte)ΛίϚϯυʹ࣮ͯ͠ߦ HTTPϔομ͓ΘΓͷۭߦΛಡΜͩͱ͜ΖͰ… cmd, err := app.BytesToCmd(scanner.Bytes()) if err != nil { return } err = cmd.Execute(app, conn) // ͜͜Ͱࢮ

Slide 24

Slide 24 text

൜ਓ func (app *App) BytesToCmd(data []byte) (cmd MemdCmd, err error) { if len(data) == 0 { // ۭߦͳͷͰ 0 byte return nil, nil // ← ͍ͭ͜ } foo, err := Foo() ͱ͍͏ΠϯλʔϑΣʔεΛΈͨ ී௨ͷGopher͕ߟ͑Δ͜ͱ ʮerr != nil ͳΒ foo == nil ͔΋͔ͩΒؾΛ͚ͭΑ͏ʯ ʮerr == nil ͳΒ foo != nil ͩΑͶʂʯ

Slide 25

Slide 25 text

nil, nil Λฦ͢ͷ͕ѱ͍ ॻ͍ͨͷ͸Ͳͬͪ΋ࣗ෼͚ͩͲ݀ʹམͪͨ ௨ৗmemcached protocolͰϦΫΤετʹۭߦ͸དྷͳ͍ ຊ൪ӡ༻தͷϓϩηε͕ଈࢮͯ͠ͼͬ͘Γͨ͠ katsubushiΫϥΠΞϯτϥΠϒϥϦ͸failoverΛ࣮૷͍ͯͨ͠ daemontools Ͱ͙͢ʹ࠶ىಈ͞Εͨ ͷͰக໋ইʹ͸ͳΒͳ͔ͬͨ γεςϜશମͰݎ࿚ʹ͍͖ͯ͠·͠ΐ͏…

Slide 26

Slide 26 text

ରԠ2 func (app *App) BytesToCmd(data []byte) (cmd MemdCmd, err error) { if len(data) == 0 { - return nil, nil + return nil, errors.New("No command") ஌Βͳ͍ίϚϯυ͕དྷͨΒଈ࠲ʹ઀ଓΛ੾ͬͨ΄͏͕͍͍͔΋ 2 Never die #8 https://github.com/kayac/go-katsubushi/pull/8

Slide 27

Slide 27 text

༨ஊ1 ࣮͸ curl ͸ telnet Ͱ͖·͢ $ curl telnet://127.0.0.1:11212 GET id VALUE id 0 18 591655476635111424 END quit

Slide 28

Slide 28 text

༨ஊ2 ʮ1ߦ໨Λղऍͯ͠key͕ / HTTP/1.[01] ͳΒHTTPɻɻɻʯ ʮͭ·Γಉ͡ϙʔτͰ memcached ͱ HTTP ྆ରԠ αʔό͕ॻ͚ΔͷͰ͸!??ʯ ్த·Ͱॻ͍ͯਖ਼ؾʹ໭ͬͯ຅ʹ

Slide 29

Slide 29 text

(ϓϩμΫτͷੑ্࣭) ID͕ܾͯ͠ඃΒͳ͍

Slide 30

Slide 30 text

katsubushi ID ͸࣌ࠁϕʔε +-+-----------------------------------------+----------+------------+ | | Timestamp | WorkerID | Sequence | +-+-----------------------------------------+----------+------------+ |0|00001001001001010100110010111011011100100|0000000001|000000000000| +-+-----------------------------------------+----------+------------+ | | 78,560,982,756ms since epoch | 1| 0| +-+-----------------------------------------+----------+------------+ = 2017-06-28T06:29:42.756Z ࣌ࠁ͕໭Δͱಉ͡ID͕ൃߦ͞ΕΔՄೳੑ͕͋Δ

Slide 31

Slide 31 text

monotonic time https://golang.org/doc/go1.9#monotonic-time Go 1.9ͰOS࣌ࠁ͕໭ͬͯ΋ time.Now() ͸໭Βͳ͍ monotonic time͕࣮૷͞Εͨ 2017−01−01ͷӞඵൃੜ࣌ Cloudflare͕࣌ࠁͷר͖໭ΓͰো֐3 ࣌ࠁ͕໭ͬͨ݁Ռ math/rand.Int63n ʹෛͷ஋͕౉Γpanic 3 How and why the leap second affected Cloudflare DNS https://blog.cloudflare.com/how-and-why-the-leap-second-affected- cloudflare-dns/

Slide 32

Slide 32 text

monotonic time Ҏલͷ katsubushi ts := g.timestamp() // for rewind of server clock if ts < g.lastTimestamp { return 0, errors.New("system clock was rollbacked") } // ུ g.lastTimestamp = ts ࣌ࠁ͕ר͖໭ͬͨΒΤϥʔʹͯ͠๷ޚ ID͕ඃΔ͙Β͍ͳΒൃߦͰ͖ͳ͍ํ͕Ϛγ

Slide 33

Slide 33 text

monotonic time ରԠ4 katsubushi ID ͷ࣌ࠁ෦෼͸ 2015-01-01T00:00:00 UTC Λىݯͱͨ͠ܦաඵ਺ var Epoch = time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) d := time.Now().Sub(Epoch) ͜ͷΑ͏ʹɺ೔࣌ࢦఆͰ࡞ͬͨ஋͔Βܭࢉ͢Δͱmonotonicʹ ͳΒͳ͍ 4 use Monotonic time #21 https://github.com/kayac/go-katsubushi/pull/21

Slide 34

Slide 34 text

monotonic time ରԠ now := time.Now() startedAt := now offset := now.Sub(Epoch) // ىಈ࣌ͷ࣌ࠁͱepochͷࠩΛܭࢉ͓ͯ͘͠ // ... d := time.Now().Sub(startedAt) + offset time.Now() Ͱ࡞ͬͨ஋ಉ࢜Λൺֱͯ͠offsetΛௐ੔͢Ε͹ ר͖໭Βͳ͍

Slide 35

Slide 35 text

࣌ࠁͷଞʹ΋ඃΔՄೳੑ͕͋Δ෦෼͕… +-+-----------------------------------------+----------+------------+ | | Timestamp | WorkerID | Sequence | +-+-----------------------------------------+----------+------------+ |0|00001001001001010100110010111011011100100|0000000001|000000000000| +-+-----------------------------------------+----------+------------+ | | 78,560,982,756ms since epoch | 1| 0| +-+-----------------------------------------+----------+------------+ = 2017-06-28T06:29:42.756Z WorkerID = ಉ࣌ʹಈ࡞͍ͯ͠ΔϓϩηεؒͰҰҙͷID

Slide 36

Slide 36 text

RedisΛ࢖ͬͯҰҙੑΛ୲อ͢Δ Ҏલͷൃදʹ͋ΔͷͰͲ͏ͧ https://speakerdeck.com/fujiwara3/katsubushi?slide=79

Slide 37

Slide 37 text

؂ࢹ΋େࣄ • github.com/fukata/golang-stats-api-handler • GoͷϝτϦΫεΛऔΔ • mackerel-plugin-gostats ΋͋ΔΑ • net/http/pprof ͰϓϩϑΝΠϧऔಘͷޱΛ։͚Δ

Slide 38

Slide 38 text

·ͱΊ • GoͰTCPαʔόΛ࡞Δͷ͸؆୯ • Ͱ΋࣮ӡ༻Ͱݎ࿚ͳ΋ͷΛ࡞Δʹ͸͍Ζ͍Ζ͋Δ • Ұݟ؆୯ʹݟ͑Δ΋ͷͰ΋ɺࣗલ࣮૷͸প΁ͷୈҰา