Почему Centrifugo?

Centrifugo v2

package main

import "unsafe"
import "compress/flate"

func main() {
	var w flate.Writer
	println(unsafe.Sizeof(w))
}

Рефакторинг протокола 

JSON-streaming

{"id": 1, "method": "subscribe", "params": {"channel": "ch1"}}
{"id": 2, "method": "subscribe", "params": {"channel": "ch2"}}

Экономим на syscall'ах

net.Buffers

func (c *Conn) write(bufs ...[]byte) error {
    for _, buf := range bufs {
        if len(buf) > 0 {
	    if _, err := c.conn.Write(buf); err != nil {
	        return err
	    }
	}
    }
    return nil
}
import "net"

func (c *Conn) write(bufs ...[]byte) error {
    b := net.Buffers(bufs)
    _, err := b.WriteTo(c.conn)
    return err
}

Объединяем несколько сообщений в одно на уровне приложения

BenchmarkMerge-4     20000    75249 ns/op    69170 B/op    63 allocs/op
BenchmarkNoMerge-4   10000   193548 ns/op    63568 B/op    213 allocs/op

На каждой итерации бенчмарка добавляем 100 сообщений в очередь и отправляем в соединение, объединяя сообщения и нет

Protobuf

0a 0a 32 08 08 07 10 00 18 1a 20 00 0a 08 22 06 08 02 10 19 18 03 
0a 09 22 07 08 02 10 a2 03 18 20 0a 09 22 07 08 02 10 8d 02 ...
JSON: {"type":"ping","time":123456789} = 32 bytes

Protobuf: 0A 05 08 95 9A EF 3A = 7 bytes

Protobuf

JSON: {"method":"ping","time":123456789} = 34 bytes

Protobuf: 0A 05 08 95 9A EF 3A = 7 bytes

Protobuf

BenchmarkJsonMarshal-8            500000    2980 ns/op    1223 B/op   9 allocs/op
BenchmarkJsonUnmarshal-8          500000    3120 ns/op    463 B/op    7 allocs/op


BenchmarkProtobufMarshal-8        2000000    901 ns/op    200 B/op    7 allocs/op
BenchmarkProtobufUnmarshal-8      2000000    692 ns/op    192 B/op   10 allocs/op

Gogoprotobuf

BenchmarkJsonMarshal-8            500000    2980 ns/op    1223 B/op   9 allocs/op
BenchmarkJsonUnmarshal-8          500000    3120 ns/op    463 B/op    7 allocs/op


BenchmarkProtobufMarshal-8        2000000    901 ns/op    200 B/op    7 allocs/op
BenchmarkProtobufUnmarshal-8      2000000    692 ns/op    192 B/op   10 allocs/op


BenchmarkGogoprotobufMarshal-8    10000000   152 ns/op    64 B/op     1 allocs/op
BenchmarkGogoprotobufUnmarshal-8  10000000   221 ns/op    96 B/op     3 allocs/op

GRPC

Bidirectional streaming

service Centrifuge {
    rpc Communicate(stream Command) returns (stream Reply) {}
}

message Command {
    uint32 id = 1;
    MethodType method = 2;
    bytes params = 3;
}

message Reply {
    uint32 id = 1;
    Error error = 2;
    bytes result = 3;
}

Prometheus

dep

dep

Goreleaser

Организация кода – все было разбито на небольшие пакеты

Библиотека Centrifuge

Зачем?

Организация кода - все в одном пакете :)

Логирование

// LogEntry represents log entry.
type LogEntry struct {
	Level   LogLevel
	Message string
	Fields  map[string]interface{}
}

Аутентификация

func authMiddleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
        credentials := &centrifuge.Credentials{
	    UserID: "42",
	    Exp:    time.Now().Unix() + 600,
	    Info:   []byte(`{"name": "Alexander"}`),
	}
	ctx = context.WithValue(
            ctx, 
            centrifuge.CredentialsContextKey,
            credentials,
        )
	r = r.WithContext(ctx)
	h.ServeHTTP(w, r)
    })
}
enum MethodType {
    CONNECT = 0;
    REFRESH = 1;
    SUBSCRIBE = 2;
    UNSUBSCRIBE = 3;
    PUBLISH = 4;
    PRESENCE = 5;
    PRESENCE_STATS = 6;
    HISTORY = 7;
    PING = 8;
    RPC = 9;
    MESSAGE = 10;
}

Команды протокола

Что хочется добавить в будущем

XHR 2

Redis STREAMs

> XADD mystream * payload "hello"
1506871964177.0


> XRANGE mystream 1506871964175.0 +
1) 1) 1506871964177.0
   2) 1) "payload"
      2) "hello"



> XREAD BLOCK 5000 STREAMS mystream $

Show me the code!

Спасибо за внимание, присоединяйтесь!

Centrifugo 2

By Emelin Alexander

Centrifugo 2

  • 1,399