Я перемещаю свой код веб-сокета с node.js на golang, где я выполняю большую обработку данных. Важнейшая проблема для меня - читать и обрабатывать данные как можно быстрее. Просто читая данные и сравнивая node.js с двумя golang решениями, я не могу получить golang с такой скоростью, как node.js. В нижеприведенных тестах решение golang -gobwas в среднем на 2,2 мс медленнее, чем 200k сообщений (быстрее в 22% случаев), в то время как горилла медленнее на 1,8 мс (и быстрее в 23% случаев).
Эталонный тест node.js код:
"use strict"
const WebSocket = require('ws')
var ws = new WebSocket("wss://api.hitbtc.com/api/2/ws")
ws.onopen = function(evt) {
hitbtc_marketnames().forEach( function (marketname) {
var marketid = marketname.replace('/', '')
send_args({ method: "subscribeOrderbook", params: {symbol: marketid}, id: 123}, ws)
send_args({ method: "subscribeTrades", params: {symbol: marketid}, id: 124}, ws)
})
}
ws.onerror = function(evt) {
throw('error')
}
ws.onclose = function(evt) {
throw('connection closed')
}
ws.onmessage = function(evt) {
var data = JSON.parse(evt.data)
var ts = Date.now() / 1000
if (data != undefined && data.params != undefined && data.params.timestamp != undefined) {
var delay = Date.now() - new Date(data.params.timestamp).getTime()
console.log(data.params.symbol, ";", data.params.sequence, ";", data.params.timestamp, ";", delay, '; js ;', evt.data.length)
}
}
function send_args (args, ws ) {
var msg = JSON.stringify(args)
console.log(Date.now(), ' send: '+msg)
try {
ws.send(msg)
} catch(ex) {
console.log(ex)
}
}
function hitbtc_marketnames() {
// return ['ETH/PAX']
return ['ADA/BCH','ADA/BTC','ADA/ETH','ADA/USD','BCH/EURS','BNB/BTC','BNB/ETH','BNB/USD','BSV/BTC','BSV/USD','BTC/EURS','BTC/PAX','BTC/USD','BTC/USDC','BTG/BTC','BTG/ETH','BTG/USD','DASH/BCH','DASH/BTC','DASH/EOS','DASH/ETH','DASH/EURS','DASH/USD','DOGE/BTC','DOGE/ETH','DOGE/USD','EOS/BCH','EOS/BTC','EOS/ETH','EOS/EURS','EOS/PAX','EOS/USD','ETC/BCH','ETC/BTC','ETC/ETH','ETC/USD','ETH/BTC','ETH/EURS','ETH/PAX','ETH/USD','ETH/USDC','EURS/USD','HT/BTC','HT/USD','IOTA/BTC','IOTA/ETH','IOTA/USD','LEO/USD','LINK/BCH','LINK/BTC','LINK/ETH','LINK/USD','LTC/BCH','LTC/BTC','LTC/EOS','LTC/ETH','LTC/EURS','LTC/USD','NEO/BTC','NEO/EOS','NEO/ETH','NEO/EURS','NEO/USD','OMG/BCH','OMG/BTC','OMG/ETH','OMG/USD','QTUM/BTC','QTUM/ETH','QTUM/USD','TRX/BCH','TRX/BTC','TRX/EOS','TRX/ETH','TRX/USD','USD/PAX','USDT/USD','USD/USDC','XEM/BTC','XEM/ETH','XLM/BCH','XLM/BTC','XLM/ETH','XLM/USD','XMR/BCH','XMR/BTC','XMR/EOS','XMR/ETH','XMR/EURS','XMR/USD','XRP/BCH','XRP/BTC','XRP/EOS','XRP/ETH','XRP/EURS','XRP/USDT','XTZ/BTC','XTZ/ETH','XTZ/USD','ZEC/BCH','ZEC/BTC','ZEC/EOS','ZEC/ETH','ZEC/EURS','ZEC/USD']
}
golang -полученное решение -
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"log"
"net/url"
"os"
"os/signal"
"strings"
"time"
)
func hitbtc_marketname() []string {
return []string{"ADA/BCH", "ADA/BTC", "ADA/ETH", "ADA/USD", "BCH/EURS", "BNB/BTC", "BNB/ETH", "BNB/USD", "BSV/BTC", "BSV/USD", "BTC/EURS", "BTC/PAX", "BTC/USD", "BTC/USDC", "BTG/BTC", "BTG/ETH", "BTG/USD", "DASH/BCH", "DASH/BTC", "DASH/EOS", "DASH/ETH", "DASH/EURS", "DASH/USD", "DOGE/BTC", "DOGE/ETH", "DOGE/USD", "EOS/BCH", "EOS/BTC", "EOS/ETH", "EOS/EURS", "EOS/PAX", "EOS/USD", "ETC/BCH", "ETC/BTC", "ETC/ETH", "ETC/USD", "ETH/BTC", "ETH/EURS", "ETH/PAX", "ETH/USD", "ETH/USDC", "EURS/USD", "HT/BTC", "HT/USD", "IOTA/BTC", "IOTA/ETH", "IOTA/USD", "LEO/USD", "LINK/BCH", "LINK/BTC", "LINK/ETH", "LINK/USD", "LTC/BCH", "LTC/BTC", "LTC/EOS", "LTC/ETH", "LTC/EURS", "LTC/USD", "NEO/BTC", "NEO/EOS", "NEO/ETH", "NEO/EURS", "NEO/USD", "OMG/BCH", "OMG/BTC", "OMG/ETH", "OMG/USD", "QTUM/BTC", "QTUM/ETH", "QTUM/USD", "TRX/BCH", "TRX/BTC", "TRX/EOS", "TRX/ETH", "TRX/USD", "USD/PAX", "USDT/USD", "USD/USDC", "XEM/BTC", "XEM/ETH", "XLM/BCH", "XLM/BTC", "XLM/ETH", "XLM/USD", "XMR/BCH", "XMR/BTC", "XMR/EOS", "XMR/ETH", "XMR/EURS", "XMR/USD", "XRP/BCH", "XRP/BTC", "XRP/EOS", "XRP/ETH", "XRP/EURS", "XRP/USDT", "XTZ/BTC", "XTZ/ETH", "XTZ/USD", "ZEC/BCH", "ZEC/BTC", "ZEC/EOS", "ZEC/ETH", "ZEC/EURS", "ZEC/USD"}
}
type messageReceived struct {
Jsonrpc string
Method string
Params struct {
Bid []interface{}
Ask []interface{}
Data []interface{}
Sequence int64
Symbol string
Timestamp string
}
}
func main() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "wss", Host: "api.hitbtc.com", Path: "api/2/ws"}
fmt.Println("connecting to", u.String())
conn, _, _, err := ws.DefaultDialer.Dial(context.Background(), u.String())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
var bJson []byte
for _, marketname := range hitbtc_marketname() {
m := map[string]interface{}{
"method": "subscribeOrderbook",
"params": map[string]string{
"symbol": strings.ReplaceAll(marketname, "/", ""),
},
"id": 123,
}
bJson, err = json.Marshal(m)
if err != nil {
log.Fatal(err)
}
err = wsutil.WriteClientMessage(conn, ws.OpText, bJson)
if err != nil {
log.Fatal(err)
}
m["method"] = "subscribeTrades"
m["id"] = 124
bJson, err = json.Marshal(m)
if err != nil {
log.Fatal(err)
}
err = wsutil.WriteClientMessage(conn, ws.OpText, bJson)
if err != nil {
log.Fatal(err)
}
}
go func() {
for {
var t time.Time
var data messageReceived
msg, _, err := wsutil.ReadServerData(conn)
if err != nil {
log.Fatal(err)
}
json.Unmarshal(msg, &data)
if len(data.Params.Timestamp) > 0 {
t, err = time.Parse("2006-01-02T15:04:05.000Z", data.Params.Timestamp)
if err != nil {
log.Fatal(err)
}
fmt.Println(data.Params.Symbol, ";", data.Params.Sequence, ";", data.Params.Timestamp, ";", time.Now().Sub(t).Seconds()*1000, "; gobwas ;", len(msg))
}
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-interrupt:
log.Println("interrupt")
select {
case <-time.After(time.Second):
}
return
}
}
}
golang -горилла-код
package main
import (
"flag"
"fmt"
"github.com/gorilla/websocket"
"log"
"os"
"os/signal"
"strings"
"time"
)
func hitbtc_marketname() []string {
return []string{"ADA/BCH", "ADA/BTC", "ADA/ETH", "ADA/USD", "BCH/EURS", "BNB/BTC", "BNB/ETH", "BNB/USD", "BSV/BTC", "BSV/USD", "BTC/EURS", "BTC/PAX", "BTC/USD", "BTC/USDC", "BTG/BTC", "BTG/ETH", "BTG/USD", "DASH/BCH", "DASH/BTC", "DASH/EOS", "DASH/ETH", "DASH/EURS", "DASH/USD", "DOGE/BTC", "DOGE/ETH", "DOGE/USD", "EOS/BCH", "EOS/BTC", "EOS/ETH", "EOS/EURS", "EOS/PAX", "EOS/USD", "ETC/BCH", "ETC/BTC", "ETC/ETH", "ETC/USD", "ETH/BTC", "ETH/EURS", "ETH/PAX", "ETH/USD", "ETH/USDC", "EURS/USD", "HT/BTC", "HT/USD", "IOTA/BTC", "IOTA/ETH", "IOTA/USD", "LEO/USD", "LINK/BCH", "LINK/BTC", "LINK/ETH", "LINK/USD", "LTC/BCH", "LTC/BTC", "LTC/EOS", "LTC/ETH", "LTC/EURS", "LTC/USD", "NEO/BTC", "NEO/EOS", "NEO/ETH", "NEO/EURS", "NEO/USD", "OMG/BCH", "OMG/BTC", "OMG/ETH", "OMG/USD", "QTUM/BTC", "QTUM/ETH", "QTUM/USD", "TRX/BCH", "TRX/BTC", "TRX/EOS", "TRX/ETH", "TRX/USD", "USD/PAX", "USDT/USD", "USD/USDC", "XEM/BTC", "XEM/ETH", "XLM/BCH", "XLM/BTC", "XLM/ETH", "XLM/USD", "XMR/BCH", "XMR/BTC", "XMR/EOS", "XMR/ETH", "XMR/EURS", "XMR/USD", "XRP/BCH", "XRP/BTC", "XRP/EOS", "XRP/ETH", "XRP/EURS", "XRP/USDT", "XTZ/BTC", "XTZ/ETH", "XTZ/USD", "ZEC/BCH", "ZEC/BTC", "ZEC/EOS", "ZEC/ETH", "ZEC/EURS", "ZEC/USD"}
}
type messageReceived struct {
Jsonrpc string
Method string
Params struct {
Bid []interface{}
Ask []interface{}
Data []interface{}
Sequence int64
Symbol string
Timestamp string
}
}
func main() {
flag.Parse()
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
c, _, err := websocket.DefaultDialer.Dial("wss://api.hitbtc.com/api/2/ws", nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
for _, channel := range hitbtc_marketname() {
m := map[string]interface{}{
"method": "subscribeOrderbook",
"params": map[string]string{
"symbol": strings.ReplaceAll(channel, "/", ""),
},
"id": 123,
}
err = c.WriteJSON(m)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
m["method"] = "subscribeTrades"
m["id"] = 124
err = c.WriteJSON(m)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
go func() {
defer close(done)
for {
var t time.Time
var data messageReceived
err := c.ReadJSON(&data)
if err != nil {
log.Println("error:", err)
os.Exit(0)
}
if len(data.Params.Timestamp) > 0 {
t, err = time.Parse("2006-01-02T15:04:05.000Z", data.Params.Timestamp)
if err != nil {
log.Fatal(err)
}
fmt.Println(data.Params.Symbol, ";", data.Params.Sequence, ";", data.Params.Timestamp, ";", time.Now().Sub(t).Seconds()*1000, "; gorilla")
}
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-done:
return
case <-interrupt:
log.Println("interrupt")
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
Методология: Я выводю строки каждого фрагмента на терминал. Я запускаю три решения одновременно и сравниваю время задержки сообщение за сообщением. Все работает на хорошем linux сервере, на котором установлена последняя версия Debian.
Действительно ли node.js просто быстрее , чем golang, или чего мне не хватает?