Почему 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 := ¢rifuge.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