Browse Source

trading kline store init

main
strange 10 months ago
parent
commit
e8988cd9a2
  1. 20
      cmd/exchange/main.go
  2. 8
      cmd/trading/main.go
  3. 3
      config/config.toml
  4. 10
      docker-compose.yml
  5. 15
      go.mod
  6. 26
      go.sum
  7. 1
      internal/exchange/exchange.go
  8. 9
      internal/exchange/exchange_data_persist.go
  9. 24
      internal/exchange/exchange_service.go
  10. 67
      internal/trading/trading_service.go
  11. 5
      pkg/config/config.go
  12. 383
      pkg/mq/nats.go
  13. 9
      pkg/mq/nats_data.go
  14. 40
      pkg/mq/nats_options.go
  15. 22
      pkg/mq/nats_topic.go
  16. 63
      pkg/utils/collect/collect.go

20
cmd/exchange/main.go

@ -10,6 +10,7 @@ import (
"sig-pub/pkg/config" "sig-pub/pkg/config"
"sig-pub/pkg/grpc/discovery" "sig-pub/pkg/grpc/discovery"
"sig-pub/pkg/grpc/interceptor" "sig-pub/pkg/grpc/interceptor"
"sig-pub/pkg/mq"
"sig-pub/pkg/storage/kvrocks" "sig-pub/pkg/storage/kvrocks"
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics" vmts "sig-pub/pkg/storage/tsdb/victoria_metrics"
"sig-pub/pkg/utils/exit" "sig-pub/pkg/utils/exit"
@ -33,7 +34,7 @@ func main() {
conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml") conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml")
exchangeConf := config.MustLoadConfig(new(ExchangeConf), "config/exchange.toml") exchangeConf := config.MustLoadConfig(new(ExchangeConf), "config/exchange.toml")
// consul 配置 // consul
cc := api.DefaultConfig() cc := api.DefaultConfig()
cc.Address = conf.Consul.Address cc.Address = conf.Consul.Address
consulClient, err := api.NewClient(cc) consulClient, err := api.NewClient(cc)
@ -43,6 +44,17 @@ func main() {
dis := discovery.NewConsulDiscovery(consulClient) dis := discovery.NewConsulDiscovery(consulClient)
resolver := dis.Resolver() resolver := dis.Resolver()
// kvrocks db
kvdb := kvrocks.NewKVRocksDB(conf.Database.Kvrocks)
if err := kvdb.Ping(); err != nil {
panic(err)
}
// nats
if err = mq.InitNats(conf.Nats.Url); err != nil {
panic(err)
}
// new market grpc client // new market grpc client
marketUrl := discovery.ConsulDialUrl(pb.MarketService_ServiceDesc.ServiceName) marketUrl := discovery.ConsulDialUrl(pb.MarketService_ServiceDesc.ServiceName)
marketConn, err := grpc.NewClient(marketUrl, marketConn, err := grpc.NewClient(marketUrl,
@ -55,12 +67,6 @@ func main() {
marketClient := pb.NewMarketServiceClient(marketConn) marketClient := pb.NewMarketServiceClient(marketConn)
tradeInstanceAside := client.NewTradeInstanceAside(marketClient) tradeInstanceAside := client.NewTradeInstanceAside(marketClient)
// kvrocks db
kvdb := kvrocks.NewKVRocksDB(conf.Database.Kvrocks)
if err := kvdb.Ping(); err != nil {
panic(err)
}
// okx exchange // okx exchange
okxSubscriber := okx.NewOkxSubscriber(exchangeConf.Okx) okxSubscriber := okx.NewOkxSubscriber(exchangeConf.Okx)
if err := okxSubscriber.Init(); err != nil { if err := okxSubscriber.Init(); err != nil {

8
cmd/trading/main.go

@ -9,6 +9,7 @@ import (
"sig-pub/pkg/config" "sig-pub/pkg/config"
"sig-pub/pkg/grpc/discovery" "sig-pub/pkg/grpc/discovery"
"sig-pub/pkg/grpc/interceptor" "sig-pub/pkg/grpc/interceptor"
"sig-pub/pkg/mq"
"sig-pub/pkg/utils/exit" "sig-pub/pkg/utils/exit"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
@ -29,7 +30,7 @@ func main() {
conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml") conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml")
tradingConf := config.MustLoadConfig(new(TradingConf), "config/trading.toml") tradingConf := config.MustLoadConfig(new(TradingConf), "config/trading.toml")
// consul 配置 // consul
cc := api.DefaultConfig() cc := api.DefaultConfig()
cc.Address = conf.Consul.Address cc.Address = conf.Consul.Address
consulClient, err := api.NewClient(cc) consulClient, err := api.NewClient(cc)
@ -39,6 +40,11 @@ func main() {
dis := discovery.NewConsulDiscovery(consulClient) dis := discovery.NewConsulDiscovery(consulClient)
resolver := dis.Resolver() resolver := dis.Resolver()
// nats
if err = mq.InitNats(conf.Nats.Url); err != nil {
panic(err)
}
// new market grpc client // new market grpc client
marketClient, err := client.NewMarketClient( marketClient, err := client.NewMarketClient(
grpc.WithResolvers(resolver), grpc.WithResolvers(resolver),

3
config/config.toml

@ -20,6 +20,9 @@ password = ""
[consul] [consul]
address = "127.0.0.1:8500" address = "127.0.0.1:8500"
[nats]
url = "nats://@127.0.0.1:7222"
[database.mysql] [database.mysql]
logMode = "info" logMode = "info"
# https://gorm.io/zh_CN/docs/connecting_to_the_database.html # https://gorm.io/zh_CN/docs/connecting_to_the_database.html

10
docker-compose.yml

@ -126,3 +126,13 @@ services:
- "./fs/consul/config:/consul/config" - "./fs/consul/config:/consul/config"
# ports: # ports:
# - 8500:8500 # - 8500:8500
sig-nats:
container_name: sig-nats
image: nats:2.12-alpine
command: -js -p 7222 -m 7221 # --auth 123456
ports:
- "7221"
- "7222:7222"
volumes:
- ./fs/nats:/tmp/nats/jetstream

15
go.mod

@ -12,14 +12,16 @@ require (
github.com/fanjindong/go-cache v0.0.6 github.com/fanjindong/go-cache v0.0.6
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/go-resty/resty/v2 v2.16.5 github.com/go-resty/resty/v2 v2.16.5
github.com/gogo/protobuf v1.3.2
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/govalues/decimal v0.1.36 github.com/govalues/decimal v0.1.36
github.com/hashicorp/consul/api v1.32.1 github.com/hashicorp/consul/api v1.32.1
github.com/influxdata/influxdb-client-go/v2 v2.14.0 github.com/influxdata/influxdb-client-go/v2 v2.14.0
github.com/jhump/protoreflect/v2 v2.0.0-beta.2 github.com/jhump/protoreflect/v2 v2.0.0-beta.2
github.com/klauspost/compress v1.17.9 github.com/klauspost/compress v1.18.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mostynb/go-grpc-compression v1.2.3 github.com/mostynb/go-grpc-compression v1.2.3
github.com/nats-io/nats.go v1.47.0
github.com/redis/go-redis/v9 v9.7.3 github.com/redis/go-redis/v9 v9.7.3
github.com/spf13/cast v1.10.0 github.com/spf13/cast v1.10.0
github.com/spf13/viper v1.20.0 github.com/spf13/viper v1.20.0
@ -27,7 +29,7 @@ require (
go.etcd.io/etcd/client/v3 v3.6.1 go.etcd.io/etcd/client/v3 v3.6.1
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/net v0.38.0 golang.org/x/net v0.38.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.13.0
golang.org/x/time v0.8.0 golang.org/x/time v0.8.0
gonum.org/v1/gonum v0.16.0 gonum.org/v1/gonum v0.16.0
google.golang.org/grpc v1.71.1 google.golang.org/grpc v1.71.1
@ -57,7 +59,6 @@ require (
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
@ -86,6 +87,8 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect github.com/oapi-codegen/runtime v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect
@ -100,10 +103,10 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.15.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.24.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

26
go.sum

@ -192,8 +192,8 @@ github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPci
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@ -246,6 +246,12 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I= github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I=
github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg= github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -356,8 +362,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -379,8 +385,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -404,15 +410,15 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

1
internal/exchange/exchange.go

@ -48,6 +48,7 @@ type ExchangeTradeInstance struct {
Inst *types.TradeInstance Inst *types.TradeInstance
Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常, todo status change event publish Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常, todo status change event publish
LiveKline *types.IntervalState[types.Kline] // 实时k线数据 LiveKline *types.IntervalState[types.Kline] // 实时k线数据
LastKline *types.IntervalState[types.Kline] // 最后确认k线数据
LiveKStartTs *types.IntervalState[int64] // ws开始订阅k线标记时间戳 LiveKStartTs *types.IntervalState[int64] // ws开始订阅k线标记时间戳
HistoryMarkTs *types.IntervalState[int64] // 拉取历史k线标记时间戳 HistoryMarkTs *types.IntervalState[int64] // 拉取历史k线标记时间戳
Last decimal.Decimal // 交易产品实时价格tick更新 Last decimal.Decimal // 交易产品实时价格tick更新

9
internal/exchange/exchange_data_persist.go

@ -7,6 +7,8 @@ import (
"sig-pub/pkg/storage/kvrocks" "sig-pub/pkg/storage/kvrocks"
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics" vmts "sig-pub/pkg/storage/tsdb/victoria_metrics"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/exit"
"sig-pub/pkg/zlog"
) )
type ExchangeDataPersist struct { type ExchangeDataPersist struct {
@ -26,6 +28,13 @@ func NewExchangeDataService(
} }
func (p *ExchangeDataPersist) Init() (err error) { func (p *ExchangeDataPersist) Init() (err error) {
// victoriametrics 强制刷盘
exit.AddHook(func() {
zlog.Infof("force flush vmtsdb")
if err := p.vmtsdb.ForceFlush(); err != nil {
zlog.Infof("force flush vmtsdb error: ", err)
}
}, exit.WithOrderFront())
return return
} }

24
internal/exchange/exchange_service.go

@ -8,6 +8,7 @@ import (
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/client" "sig-pub/pkg/client"
"sig-pub/pkg/data" "sig-pub/pkg/data"
"sig-pub/pkg/mq"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sig-pub/pkg/utils/conver" "sig-pub/pkg/utils/conver"
@ -92,6 +93,7 @@ func (svc *ExchangeService) subscribeExchanges() {
exchangeInst := &ExchangeTradeInstance{ exchangeInst := &ExchangeTradeInstance{
Inst: tradeInst, Inst: tradeInst,
LiveKline: types.NewIntervalState[types.Kline](), LiveKline: types.NewIntervalState[types.Kline](),
LastKline: types.NewIntervalState[types.Kline](),
LiveKStartTs: types.NewIntervalState[int64](), LiveKStartTs: types.NewIntervalState[int64](),
HistoryMarkTs: types.NewIntervalState[int64](), HistoryMarkTs: types.NewIntervalState[int64](),
} }
@ -152,8 +154,12 @@ func (svc *ExchangeService) consumerKline(exchange *Exchange, c <-chan *types.Ch
// 标记交易产品开始订阅k线时间 // 标记交易产品开始订阅k线时间
exchangeInst.LiveKStartTs.SetIf(firstKline.Interval, firstKline.Ts, func(old int64) bool { return old == 0 }) exchangeInst.LiveKStartTs.SetIf(firstKline.Interval, firstKline.Ts, func(old int64) bool { return old == 0 })
// 记实时k线 // 记实时k线
exchangeInst.LiveKline.Set(lastKline.Interval, *lastKline) exchangeInst.LiveKline.Set(lastKline.Interval, *lastKline)
// 记录最后确认k线
if lastKline.Confirm {
exchangeInst.LastKline.Set(lastKline.Interval, *lastKline)
}
// 记录实时价格 // 记录实时价格
exchangeInst.Last = lastKline.Close exchangeInst.Last = lastKline.Close
@ -244,6 +250,15 @@ func (svc *ExchangeService) initialKlines(exchange *Exchange, insts []types.Trad
failed = append(failed, inst) failed = append(failed, inst)
} else { } else {
success = append(success, inst) success = append(success, inst)
// 发布交易产品初始化完成事件
publish := &mq.PublishExchangeTradeInstanceInited{
Exchange: inst.Exchange,
InstId: inst.InstId,
}
if err := mq.NatsPublish(mq.TopicExchangeTradeInstanceInited, publish); err != nil {
zlog.Errorf("publish trade instance inited error: %s(%s), err=%v", inst.InstId, inst.Exchange, err)
}
} }
} }
@ -568,9 +583,14 @@ func (svc *ExchangeService) HistoryKline(ctx context.Context, req *pb.ReqHistory
if len(klines) < int(count) { if len(klines) < int(count) {
rsp.Next = false rsp.Next = false
} }
lastK := klines[len(klines)-1] lastK := klines[len(klines)-1]
// vmtsdb 数据刷盘30s延迟, 使用内存数据替代第一根k线
if lastConfirmK := exchangeInst.LastKline.Get(interval); lastConfirmK.Ts == lastK.Ts {
klines[len(klines)-1] = &lastConfirmK
lastK = &lastConfirmK
}
// 降序排序 // 降序排序
if !req.Asc { if !req.Asc {
collect.Reverse(klines) collect.Reverse(klines)

67
internal/trading/trading_service.go

@ -5,9 +5,10 @@ import (
"io" "io"
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/client" "sig-pub/pkg/client"
"sig-pub/pkg/mq"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"sync"
"time" "time"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -19,7 +20,7 @@ type TradingService struct {
klineStore *KlineStore klineStore *KlineStore
subKlineLock sync.Mutex subscribeKlineIntervals []string
subKlineInsts [3][]string subKlineInsts [3][]string
subKlineStream grpc.BidiStreamingClient[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline] subKlineStream grpc.BidiStreamingClient[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]
} }
@ -28,9 +29,14 @@ func NewTradingService(
marketClientAside *client.TradeInstanceAside, marketClientAside *client.TradeInstanceAside,
exchangeClient pb.ExchangeServiceClient, exchangeClient pb.ExchangeServiceClient,
) *TradingService { ) *TradingService {
// 订阅实时k线周期列表
subscribeKlineIntervals := collect.Map2Slice(types.SupportedIntervals, func(interval types.Interval, _ types.IntervalAdder) string {
return string(interval)
})
return &TradingService{ return &TradingService{
marketClientAside: marketClientAside, marketClientAside: marketClientAside,
exchangeClient: exchangeClient, exchangeClient: exchangeClient,
subscribeKlineIntervals: subscribeKlineIntervals,
} }
} }
@ -47,15 +53,32 @@ func (svr *TradingService) Init() (err error) {
svr.subKlineInsts[exInst.Exchange] = append(svr.subKlineInsts[exInst.Exchange], exInst.InstId) svr.subKlineInsts[exInst.Exchange] = append(svr.subKlineInsts[exInst.Exchange], exInst.InstId)
} }
// 拉取已初始化完成交易产品, 初始化k线, 开始订阅k线
// 订阅交易产品初始化完成事件
mq.NatsCreateConsumer("trading", mq.StreamExchange, mq.TopicExchangeTradeInstanceInited, func() *mq.PublishExchangeTradeInstanceInited { return new(mq.PublishExchangeTradeInstanceInited) },
func(msg *mq.PublishExchangeTradeInstanceInited) (err error) {
// 初始化k线, 开始订阅k线
zlog.Infof("trade instance inited: %#v", msg)
return
})
// 订阅k线 // 订阅k线
go svr.subscribeKlines(false) go svr.subscribeStreamKlines(false)
return return
} }
func (svr *TradingService) subscribeKlines(reconnect bool) { func (svr *TradingService) subscribeKlines() {
}
func (svr *TradingService) subscribeStreamKlines(reconnect bool) {
defer func() { defer func() {
if svr.subKlineStream != nil {
svr.subKlineStream.CloseSend()
svr.subKlineStream = nil svr.subKlineStream = nil
go svr.subscribeKlines(true) }
go svr.subscribeStreamKlines(true)
}() }()
if reconnect { if reconnect {
@ -63,29 +86,25 @@ func (svr *TradingService) subscribeKlines(reconnect bool) {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
} }
// svr.subKlineLock.Lock()
// defer svr.subKlineLock.Unlock()
stream, err := svr.exchangeClient.SubscribeKline(context.Background()) stream, err := svr.exchangeClient.SubscribeKline(context.Background())
if err != nil { if err != nil {
zlog.Error("subscribeKlines reqeust error: ", err) zlog.Error("subscribeKlines reqeust error: ", err)
return return
} }
svr.subKlineStream = stream
if svr.subKlineStream != nil {
svr.subKlineStream.CloseSend()
}
// 发送订阅消息 // 发送订阅消息
exchanges := []pb.ExchangeType{pb.ExchangeType_OKX} exchanges := []pb.ExchangeType{pb.ExchangeType_OKX}
for _, exchange := range exchanges { for _, exchange := range exchanges {
instIds := svr.subKlineInsts[exchange] instIds := svr.subKlineInsts[exchange]
if len(instIds) == 0 {
continue
}
msg := &pb.ReqStreamSubscribeKline{ msg := &pb.ReqStreamSubscribeKline{
SubType: pb.SubscribeType_Subscribe, SubType: pb.SubscribeType_Subscribe,
Exchanges: []pb.ExchangeType{exchange}, Exchanges: []pb.ExchangeType{exchange},
InstIds: instIds, InstIds: instIds,
Intervals: SubscribeKlineIntervals, Intervals: svr.subscribeKlineIntervals,
OnlyConfirm: true, OnlyConfirm: true,
} }
zlog.Debugf("send stream subscribe kline msg: %#v", msg) zlog.Debugf("send stream subscribe kline msg: %#v", msg)
@ -116,23 +135,3 @@ func (svr *TradingService) subscribeKlines(reconnect bool) {
} }
} }
} }
var SubscribeKlineIntervals = []string{
// string(types.Interval1s),
string(types.Interval1m),
string(types.Interval3m),
string(types.Interval5m),
string(types.Interval15m),
string(types.Interval30m),
string(types.Interval1h),
string(types.Interval2h),
string(types.Interval4h),
string(types.Interval6h),
string(types.Interval12h),
string(types.Interval1d),
string(types.Interval1d),
string(types.Interval2d),
string(types.Interval3d),
string(types.Interval5d),
string(types.Interval1w),
}

5
pkg/config/config.go

@ -19,6 +19,7 @@ type Configuration struct {
// Consul api.Config // Consul api.Config
Consul ConsulConfig Consul ConsulConfig
Database Database Database Database
Nats NatsConfig
Tsdb TsdbConfig Tsdb TsdbConfig
} }
@ -50,6 +51,10 @@ type GateConf struct {
WsAddr string WsAddr string
} }
type NatsConfig struct {
Url string
}
// ============== database ============== // ============== database ==============
type Database struct { type Database struct {
Mysql MysqlConfig Mysql MysqlConfig

383
pkg/mq/nats.go

@ -0,0 +1,383 @@
package mq
import (
"context"
"errors"
"fmt"
"runtime/debug"
"sig-pub/pkg/zlog"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/gogo/protobuf/proto"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
)
var (
NatsConn *nats.Conn
NatsJS jetstream.JetStream
)
func InitNats(natsUrl string) (err error) {
options := nats.Options{
Url: natsUrl,
// 断线重连配置
RetryOnFailedConnect: true,
AllowReconnect: true,
MaxReconnect: -1,
ReconnectWait: time.Second * 2,
ReconnectBufSize: 10 * 1024 * 1024, // 10M
}
NatsConn, err = options.Connect()
if err != nil {
return
}
NatsJS, err = jetstream.New(NatsConn)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
err = initNatsStream(ctx)
if err != nil {
return
}
return
}
// initNatsStream 初始化 jetstream 消息流
func initNatsStream(ctx context.Context) (err error) {
for _, ss := range initialStream {
if len(ss) < 2 {
err = fmt.Errorf("initial jetstream %v config error", ss)
return
}
err = NatsCreateStream(ctx, ss[0], ss[1:]...)
if err != nil {
return
}
}
return
}
// NatsCreateStream 创建或更新jetstream消息流
func NatsCreateStream(ctx context.Context, streamName string, subjects ...string) (err error) {
// 活动消息流 https://docs.nats.io/nats-concepts/jetstream/streams#configuration
streamConfig := jetstream.StreamConfig{
Name: streamName,
Storage: jetstream.FileStorage,
Subjects: subjects, // 要绑定的topics
Replicas: 1, // 消息副本数量
MaxAge: time.Hour * 24, // 流中任何消息的最大年龄
MaxBytes: -1, // 存储的所有消息最大字节数 -1无限制
MaxMsgs: -1, // 流中存储的所有消息最大消息数
MaxMsgSize: -1, // 单个消息最大字节数
MaxConsumers: -1, // Stream 定义的最大消费者数量
NoAck: false,
Retention: jetstream.LimitsPolicy, // 声明流的保留策略
Discard: jetstream.DiscardOld, // stream消息数量达到limit处理策略
MaxMsgsPerSubject: -1, // stream保留每个主题最大消息数
Duplicates: time.Minute * 2, // 跟踪重复消息的窗口
AllowRollup: false, // 允许使用标题Nats-Rollup将流的所有内容或流中的主题替换为单个新消息
DenyDelete: true, // 不允许删除消息
DenyPurge: true, // 不允许清空消息
}
_, err = NatsJS.CreateOrUpdateStream(ctx, streamConfig)
if err != nil {
return
}
return
}
// NatsCreateConsumer 创建或更新 jetstream consumer
// consumerName 多个相同name只消费一次,多个不同name消费分别消费一次
func NatsCreateConsumer[T any](consumerName, streamName, topic string, inst func() T, handler func(T) (err error), options ...NatsConsumerOption) {
if NatsJS == nil {
panic(errors.New("nats js not connect"))
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
consumerName = strings.ReplaceAll(fmt.Sprintf("%s-%s", consumerName, topic), ".", "-")
// https://docs.nats.io/nats-concepts/jetstream/consumers#general
consumerConfig := jetstream.ConsumerConfig{
Durable: consumerName,
Name: consumerName,
FilterSubject: topic,
DeliverPolicy: jetstream.DeliverNewPolicy, // 只处理consumer创建后的新消息
AckPolicy: jetstream.AckExplicitPolicy, // 消息确认:不确认高qps
ReplayPolicy: jetstream.ReplayInstantPolicy, // 消息回放机制
MaxDeliver: -1, // 消息投递的最大次数-1表示没ack就不停投递
MaxAckPending: -1, // 多少条消息未投递成功则不再投递新消息-1不限制
}
for _, opt := range options {
opt(&consumerConfig)
}
consumer, err := NatsJS.Consumer(ctx, streamName, consumerName)
if err != nil {
consumer, err = NatsJS.CreateConsumer(ctx, streamName, consumerConfig)
if err != nil {
// consumer 配置修改
if err == jetstream.ErrConsumerExists {
consumer, err = NatsJS.UpdateConsumer(ctx, streamName, consumerConfig)
if err != nil {
// NatsJS.DeleteConsumer(ctx, streamName, consumerName)
panic(err)
}
} else {
panic(err)
}
}
}
consumer.Consume(func(msg jetstream.Msg) {
defer func() {
if r := recover(); r != nil {
zlog.Errorf("nats consumer recover error: consumer=%s, stream=%s, topic=%s, %v", consumerName, streamName, topic, r)
debug.PrintStack()
}
}()
defer msg.Ack()
var data T
if inst != nil {
data = inst()
if err := natsJsUnmarshal(data, msg); err != nil {
zlog.Errorf("unmarshal data %T error, %s, %v", data, msg.Subject(), err)
return
}
}
err = handler(data)
if err != nil {
zlog.Error(err)
debug.PrintStack()
}
})
}
func NatsCreateConsumerExtend[T any](consumerName, streamName, topic string, inst func() T, handler func(T, *jetstream.MsgMetadata) (ack bool, delay time.Duration, err error), options ...NatsConsumerOption) {
if NatsJS == nil {
panic(errors.New("nats js not connect"))
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
consumerName = strings.ReplaceAll(fmt.Sprintf("%s-%s", consumerName, topic), ".", "-")
// https://docs.nats.io/nats-concepts/jetstream/consumers#general
consumerConfig := jetstream.ConsumerConfig{
Durable: consumerName,
Name: consumerName,
FilterSubject: topic,
DeliverPolicy: jetstream.DeliverNewPolicy, // 只处理consumer创建后的新消息
AckPolicy: jetstream.AckExplicitPolicy, // 消息确认:不确认高qps
ReplayPolicy: jetstream.ReplayInstantPolicy, // 消息回放机制
MaxDeliver: -1, // 消息投递的最大次数-1表示没ack就不停投递
MaxAckPending: -1, // 多少条消息未投递成功则不再投递新消息-1不限制
}
for _, opt := range options {
opt(&consumerConfig)
}
consumer, err := NatsJS.Consumer(ctx, streamName, consumerName)
if err != nil {
consumer, err = NatsJS.CreateConsumer(ctx, streamName, consumerConfig)
if err != nil {
// consumer 配置修改
if err == jetstream.ErrConsumerExists {
consumer, err = NatsJS.UpdateConsumer(ctx, streamName, consumerConfig)
if err != nil {
// NatsJS.DeleteConsumer(ctx, streamName, consumerName)
panic(err)
}
} else {
panic(err)
}
}
}
consumer.Consume(func(msg jetstream.Msg) {
defer func() {
if r := recover(); r != nil {
zlog.Errorf("nats consumer recover error: consumer=%s, stream=%s, topic=%s, %v", consumerName, streamName, topic, r)
debug.PrintStack()
}
}()
var data T
if inst != nil {
data = inst()
if err := natsJsUnmarshal(data, msg); err != nil {
zlog.Errorf("unmarshal data %T error, %s, %v", data, msg.Subject(), err)
msg.Ack() // 解析失败直接确认,避免无限重试
return
}
}
// 获取消息元数据
metadata, err := msg.Metadata()
if err != nil {
zlog.Errorf("get message metadata error: %v", err)
msg.Ack() // 获取元数据失败直接确认
return
}
// 调用handler并处理结果
ack, delay, err := handler(data, metadata)
if err != nil {
zlog.Error(err)
debug.PrintStack()
}
// 根据返回值决定确认或重试
if ack {
msg.Ack()
} else {
if err := msg.NakWithDelay(delay); err != nil {
zlog.Errorf("nak with delay error: %v", err)
msg.Nak() // 如果设置延迟失败,直接Nak
}
}
})
}
// NatsSubscribe nats 请求响应注册
func NatsSubscribe[T, V any](subject string, inst func() T, handler func(req T) (rsp V, err error)) (s *nats.Subscription) {
if NatsConn == nil {
panic(errors.New("nats not connect"))
}
s, err := NatsConn.Subscribe(subject, func(msg *nats.Msg) {
defer func() {
if r := recover(); r != nil {
zlog.Errorf("nats subject recover error: topic=%s, %v", subject, r)
debug.PrintStack()
}
}()
defer msg.Ack()
var req T
if inst != nil {
req = inst()
if err := natsUnmarshal(req, msg); err != nil {
zlog.Errorf("unmarshal data %T error, %s", req, msg.Subject)
return
}
}
rsp, err := handler(req)
rspMsg := nats.NewMsg(subject)
if err != nil {
rspMsg.Header.Add("error", err.Error())
} else {
if err := natsMarshal(rsp, rspMsg); err != nil {
zlog.Errorf("marshal rsp data %T error, %s", rsp, msg.Subject)
return
}
}
msg.RespondMsg(rspMsg)
})
if err != nil {
panic(err)
}
return
}
// NatsRequest nats发送请求
func NatsRequest(subject string, rsp, req any, options ...NatsRequestOption) (err error) {
if NatsConn == nil {
return errors.New("nats not connect")
}
c := &NatsRequestOptions{Timeout: time.Second * 10}
for _, option := range options {
option(c)
}
reqMsg := nats.NewMsg(subject)
if err = natsMarshal(req, reqMsg); err != nil {
return
}
rspMsg, err := NatsConn.RequestMsg(reqMsg, c.Timeout)
if err != nil {
return
}
if e := rspMsg.Header.Get("error"); e != "" {
err = errors.New(e)
return
}
if err = natsUnmarshal(rsp, rspMsg); err != nil {
return
}
return
}
// NatsPublish nats 发布消息
func NatsPublish(subject string, data any) (err error) {
if NatsJS == nil {
return errors.New("nats js not connect")
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
msg := nats.NewMsg(subject)
if err = natsMarshal(data, msg); err != nil {
return
}
_, err = NatsJS.PublishMsg(ctx, msg)
if err != nil {
return
}
return
}
func natsMarshal(data any, msg *nats.Msg) (err error) {
mt := "1" // 序列化1json,2protobuf
if marshaler, ok := data.(proto.Marshaler); ok {
if bytes, err := marshaler.Marshal(); err == nil {
mt = "2"
msg.Data = bytes
}
}
if mt == "1" {
msg.Data, err = sonic.Marshal(data)
if err != nil {
return
}
}
msg.Header.Add("mt", mt)
return
}
func natsUnmarshal(data any, msg *nats.Msg) (err error) {
mt := msg.Header.Get("mt")
if mt == "2" {
unmarshaler, ok := data.(proto.Unmarshaler)
if !ok {
return fmt.Errorf("%T is not protobuf type", data)
}
return unmarshaler.Unmarshal(msg.Data)
}
// mt=1 json
err = sonic.Unmarshal(msg.Data, data)
if err != nil {
return
}
return
}
func natsJsUnmarshal(data any, msg jetstream.Msg) (err error) {
mt := msg.Headers().Get("mt")
if mt == "2" {
unmarshaler, ok := data.(proto.Unmarshaler)
if !ok {
return fmt.Errorf("%T is not protobuf type", data)
}
return unmarshaler.Unmarshal(msg.Data())
}
// mt=1 json
err = sonic.Unmarshal(msg.Data(), data)
if err != nil {
return
}
return
}

9
pkg/mq/nats_data.go

@ -0,0 +1,9 @@
package mq
import "sig-pub/api/pb"
// 交易产品初始化完成通知
type PublishExchangeTradeInstanceInited struct {
Exchange pb.ExchangeType `json:"exchange"` // 交易所类型
InstId string `json:"instId"` // 交易产品id
}

40
pkg/mq/nats_options.go

@ -0,0 +1,40 @@
package mq
import (
"time"
"github.com/nats-io/nats.go/jetstream"
)
type NatsConsumerOption func(c *jetstream.ConsumerConfig)
type NatsRequestOption func(c *NatsRequestOptions)
type NatsRequestOptions struct {
Timeout time.Duration
}
// WithConsumerNoneAck jetstream 消息不ack高qps
func WithConsumerNoneAck() NatsConsumerOption {
return func(c *jetstream.ConsumerConfig) {
c.AckPolicy = jetstream.AckNonePolicy
}
}
func WithConsumerMaxDeliver(maxDeliver int) NatsConsumerOption {
return func(c *jetstream.ConsumerConfig) {
c.MaxDeliver = maxDeliver
}
}
func WithConsumerMaxAckPending(maxAckPending int) NatsConsumerOption {
return func(c *jetstream.ConsumerConfig) {
c.MaxAckPending = maxAckPending
}
}
func WithRequestTimeout(timeout time.Duration) NatsRequestOption {
return func(c *NatsRequestOptions) {
c.Timeout = timeout
}
}

22
pkg/mq/nats_topic.go

@ -0,0 +1,22 @@
package mq
// 初始化jetStream流, {streamName, topics...}
var initialStream = [][]string{
{StreamExchange, "exchange.>"},
}
const (
// ========================== jetstraem定义 ==========================
StreamExchange = "stream-exchange" // 游戏行为jetstraem消息流
// ========================== mq主题订阅 ==========================
// ------------- 交易所 -------------
TopicExchangeTradeInstanceInited = "exchange.trade_instance_inited" // 交易产品初始化完成
// ========================== 请求响应 ==========================
// ------------- 拉去历史k线 -------------
// RequestExchangeHistoryKline = "request.exchange.history_kline"
)
// nats 空参数
var NatsNilData = map[string]any{}

63
pkg/utils/collect/collect.go

@ -69,15 +69,37 @@ func Tail[T any](slice []T, dv T) T {
return slice[len(slice)-1] return slice[len(slice)-1]
} }
func MapValues[K comparable, V any](kvs map[K]V) []V { func MapKeys[K comparable, V any](kvs map[K]V) (keys []K) {
var values []V if len(kvs) == 0 {
return
}
keys = make([]K, 0, len(kvs))
for k := range kvs {
keys = append(keys, k)
}
return
}
func MapValues[K comparable, V any](kvs map[K]V) (values []V) {
if len(kvs) == 0 { if len(kvs) == 0 {
return values return values
} }
values = make([]V, 0, len(kvs))
for _, v := range kvs { for _, v := range kvs {
values = append(values, v) values = append(values, v)
} }
return
}
func Map2Slice[K comparable, V any, T any](kvs map[K]V, fn func(k K, v V) T) (values []T) {
if len(kvs) == 0 {
return values return values
}
values = make([]T, 0, len(kvs))
for k, v := range kvs {
values = append(values, fn(k, v))
}
return
} }
func Sum[T int32 | int64 | int](nums []T) T { func Sum[T int32 | int64 | int](nums []T) T {
@ -153,17 +175,6 @@ func SliceEquals[T comparable](slice1 []T, slice2 []T) bool {
return true return true
} }
func Join(slice []string, on string) (res string) {
length := len(slice)
for i := 0; i < length; i++ {
res += slice[i]
if i < length-1 {
res += on
}
}
return
}
func CopyMap[K comparable, V any](src map[K]V) (dst map[K]V) { func CopyMap[K comparable, V any](src map[K]V) (dst map[K]V) {
dst = make(map[K]V, len(src)) dst = make(map[K]V, len(src))
for k, v := range src { for k, v := range src {
@ -172,19 +183,7 @@ func CopyMap[K comparable, V any](src map[K]V) (dst map[K]V) {
return return
} }
func CopyMap2[K comparable, K2 comparable, V2 any](src map[K]map[K2]V2) (dst map[K]map[K2]V2) { func MappingIndex[T any](slice []T) (index []int) {
dst = make(map[K]map[K2]V2, len(src))
for k, v := range src {
cv := make(map[K2]V2, len(v))
for k2, v2 := range v {
cv[k2] = v2
}
dst[k] = cv
}
return
}
func IndexMapping[T any](slice []T) (index []int) {
index = make([]int, len(slice)) index = make([]int, len(slice))
for i := range slice { for i := range slice {
index[i] = i index[i] = i
@ -193,23 +192,13 @@ func IndexMapping[T any](slice []T) (index []int) {
} }
func SortIndex[T any](slice []T, less func(i, j int) bool) (sortIndex []int) { func SortIndex[T any](slice []T, less func(i, j int) bool) (sortIndex []int) {
sortIndex = IndexMapping(slice) sortIndex = MappingIndex(slice)
sort.Slice(sortIndex, func(i, j int) bool { sort.Slice(sortIndex, func(i, j int) bool {
return less(sortIndex[i], sortIndex[j]) return less(sortIndex[i], sortIndex[j])
}) })
return return
} }
func SliceMapping[T interface{}, M interface{}](slice []T, mapping func(T) M) (m []M) {
if len(slice) == 0 {
return
}
for _, item := range slice {
m = append(m, mapping(item))
}
return
}
func Reverse[T any](slice []T) { func Reverse[T any](slice []T) {
length := len(slice) length := len(slice)
for i := range length / 2 { for i := range length / 2 {

Loading…
Cancel
Save