前言:
行情推送主要是为了解决交易所的产生的实时数据,兼容了前端、移动端和量化程序。为了解决行情推送的高性能和高可用,着实做了很多的调优,优化涵盖了架构、中间件和golang的机制优化。线上的推送集群可稳定支撑近35w的客户端。
该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=6250
内容:
行情推送采用golang语言开发的,为啥用golang,简单实用,高并发。
推送系统从架构上来说,主要分了推送网关和推送业务服务,网关主要是为了解决鉴权和协议编码,业务端主要是维护订阅关系及缓存逻辑。
关于分享的ppt放到github了,有兴趣的可以看看。 https://github.com/rfyiamcool/share_ppt/blob/master/push_cluster.pdf
github对于大文件的访问,时常有些抽风导致浏览失败,可直接下载 http://xiaorui.cc/static/push_cluster.pdf
截图:
下面是部分截图.
























检索:
// xiaorui.cc
分布式⾏行行情推送系统
rfyiamcool
xiaorui.cc
github.com/rfyiamcool
1
架构介绍
2
性能优化
3
排坑记
4
总结
1
架构设计
推送集群架构
组件介绍
push-router
服务发现注册及调度
push-gateway
鉴权及协议转换
⽀持websocket,
grpc, grpc-web,
kcp
push-server
业务逻辑, 维护订阅关系及缓存
push-control
集群管理
nats-bus
消息总线
技术选型
golang
mq
proto
grpc
json
envoy -> grpc-web
protobuf
websocket
pb over tcp
pb over kcp
nats-stream
cache
redis
database
scylla
技术选型
grpc
内部量化
grpc-web
web前端
websocket
供外部量化api
移动端
pb over tcp
pb over kcp …
grpc
优点
protobuf
HTTP2
⽀持各类语⾔
基于http2兼容好
⽀持bidi全双⼯通信模式
TLS
TCP
protobuf
⾼性能序列化
压缩
服务注册发现
Client
push-router
register/hb/stats
register
discovery
push-gateway
push-gateway
discovery
push-server
push-server
grpc-web in fe
chrome
unary
server side stream
sub/unsub/getHistory
push-gateway
push-gateway
push-server
push-server
😅 不⽀持grpc bidi模式
多级缓存
push-server
初始化缓存数据
push-server
expire 24h in cache
query timestamp in cache
>15 d
< 15d in redis
< 15 d
> 15d in scylla
redis
redis
redis
scylla
scylla
scylla
scylla
2
性能优化
推送优化
当某个topic的订阅者超过⼀个量级
并发触发会更快
协程池有效减少栈扩充消耗
切chunk可减少协程池调度
推送优化
kline订阅关系变更
⽤户的订阅和撤销订阅,以及上下线
发布订阅消息时需遍历订阅客户端
kline是双层嵌套的map, 频繁变更带来锁竞争
kline map
ticker_btc:usdt
client1
client2
…
ticker_btc:eth
client1
client3
嵌套map改成分段为1024个map结构
锁粒度尽量降低到topic级别
grpc连接池
为什么要使⽤连接池?
push-gateway
stream复⽤产⽣了锁竞争
benchmark
1 client, 100 goroutine, 8w qps
x 10
grpc bidi
x 10
1 client, 300 goroutine, 5w qps
10 client, 300 goroutine, 15w qps
50 client, 300 goroutine, 30w qps
push-server
push-server
push-server
grpc连接池
减少系统调⽤
msg-1
msg-1
msg-2
msg-3
msg-2
msg-3
sender
socket.read/write
msg-4
msg-5
msg-1
msg-2
msg-3
⼼跳定时器优化
升级golang版本到1.10.3以上
runtime改进为p个timer定时器
严重的锁竞争
业务上允许低时间精度
实现⾃定义时间轮
锁分散到每个槽位
使⽤map存储定时任务
损失精度来减少锁竞争
定时器优化
Ticker
按照刻度扫描
最大程度减少
锁冲突 !!!
回调队列
callBack
caller
struct timerEntry {
map
cas
}
callBack
caller
缓存优化
cache
Rwlock
优化为分段锁 !
cache
cache
cache
Rwlock
Rwlock
Rwlock
⼴播惊群
push-server-1
push-server-2
msg1;msg2
share-topic
msg1;msg2
producer
push-server-1
msg1;msg2
nasts-bus
msg1
push-topic1
订单只需推给相关买卖家
push-server被频繁唤醒
push-server-2
msg2
push-topic1
msg1;msg2
producer
加⼤吞吐
event wait
goroutine
goroutine
Chan
pipeline
goroutine
goroutine
event wait
goroutine
Chan
goroutine
goroutine
并发单请求合并成pipeline访问redis
减少系统调⽤开销
pipeline
⽇志引起的问题
logger
logger
golang线程数增多
宿主机disk io有时飙⾼, 引起写⽇志阻塞
继⽽造成runtime sysmon检测
由于syscall长时间阻塞, 启⽤新线程绑定P
线程数不会减少
由于disk io阻塞业务协程, 造成时延升⾼
writer
pipeline flush
golang in docker
golang默认P的数量为取cpu core
P
docker内cpuinfo为宿主机配置
P
P
P
P数的增多会增加runtime消耗
P
P
P
根据docker的cpu-quota来动态配置P
P
P
P
cpu 64 core
http://github.com/uber-go/automaxprocs
panic: send on closed channel
现状
每个client会有读写协程及chan
问题
当⽤户关闭退出时, 如何清理回收 ?
⽅法
不主动关闭channel
关闭context通知
解绑 topic-> client对应关系及删除
⾼可⽤性
if push-router crash ?
多个router由envoy-ingress负载均衡
if push-gateway crash?
push-router会得知健康状态
客户端从push-router获取可⽤的gateway
if push-server crash ?
gateway从router选择最优push-server
下发⾏情订阅请求
通过上次的ack id下发⽤户订单订阅
内核优化
开启bbr拥塞控制算法
国内不明显
外国效果明显
各类优化
必须注意锁竞争的问题
使⽤sync.pool缓存频繁的堆对象
bytes.Buffer复⽤
减少系统调⽤
优化defer的调⽤
通过pipeline提⾼各端的效率
协程池
控制并发
消除⽑刺
减少more stack
3
排坑记
⼤坑
grpc-web
不⽀持bidi mode
需要envoy做协议转换
goroutine per connnect模式造成协程过多
kcp的表现并不美好
⼩坑
runtime gFree & allgs
runtime开销
过多的退出协程
过多的休眠协程
gc, sysmon, deadlock check …
netpoll vs raw epoll
go
go
conn.read
conn.read
go
go
go
…
conn.read
netpoll in golang runtime
go
raw epoll
conn.write
Q&A
