券商高速高稳定性行情服务解决方案(单机qps28万/秒)

编程入门 行业动态 更新时间:2024-10-07 22:18:51

<a href=https://www.elefans.com/category/jswz/34/1758431.html style=券商高速高稳定性行情服务解决方案(单机qps28万/秒)"/>

券商高速高稳定性行情服务解决方案(单机qps28万/秒)

背景

前段时间和券商IT朋友交流,提到早盘高并发的情况下,行情系统经常卡死,用Java开发的服务端八万的并发已经扛不住了。之前也在百度做过类似的系统,所以第一反应想到的就是加机器,现在觉得这个想法还是有点幼稚了,因为时间原因,当时没有深入交流,最近刚好又有点时间,回想到那次交流,加上自己这几年的技术积累,感觉应该可以解决这个问题,利用一个周末的时间,用golang开发了一个行情服务,然后又经过几轮压测和优化,单机(32Core+64G)的requests/sec 最高能达到了28万/秒

方案设计

加机器不是一个好方案

1、机器成本

互联网公司有钱,可以短时间能准备准备大量的机器,一般中小型公司,又是自有机房,不可能短时间能凑够大量机器

2、运维成本

机器多了,尤其是达到了几十台或者几百台机器,运维成本会大幅上升,互联网公司有专业且强大的运维团队支持,开发人员不用太担心,记得当时在某互联网金融公司,仅仅一个微服务,多大30个结点的集群,每次部署得1个小时,经常要专门配备开发人员每天负责发布和部署。一个数据库用分库分表,竟然分了100个库,这种一般中小型公司,仅仅一两个运维人员,是不可能扛得住

为什么采用go而不是Java

Java出来有28多年了,刚开始的Java并不是为大并发设计的,虽然这几年java也在不断升级以追求大并发和低延迟,但是相比那种天生为并发和低延迟而设计的语言,有点差强人意,这其中主要代表就是golang,天生就是具备了大并发的基因

go语言的大并发靠就是协程,协程一般称为轻量级线程,也叫用户态线程,Java21即将推出的叫虚拟线程,其实都是一个意思,协程的好处在大并发情况下不会阻塞线程,而线程在大并发情况下会阻塞线程;而且单机线程数是有限的一般和CPU密切相关,单机协程数可以开到几十万。这里说的线程严格的说是内核态线程,因为线程是操作系统提供的,下面图是关于线程和协程的阻塞调度模型:

  • 线程调度模型(Java)

  • 协程调度模型(golang)

以上两种调度对比:

1、线程调度模型会阻塞线程,不能为其他任务服务,导致系统资源的浪费

2、协程发生了IO阻塞,go rutime会把协程状态设为休眠,然后让线程去调度其他协程,等协程IO处理完成,go runtime又会重新唤醒协程

为什么基于Java的行情系统容易卡死

Java的http服务一般都是tomcat或者jetty,当然少数用netty,而tomcat采用的也是线程池来处理请求任务,一般线程池有3个参数,核心线程数(core size),最大线程数(max size)和队列长度(queue size),并发起来了,首先如果是核心线程数满了,任务就会排队,如果任务队列满了,就会创建新线程执行任务,如果最大线程数也满了,就会拒绝新任务

一般来说多核的机器上,线程数都不建议设置太大,线程数和CPU的核数是有强相关性,32核心的机器上线程数到100就已经很大了,配置多了没有意义,假设来了10万个并发请求,前面的请求遇到耗时IO调用,线程被阻塞,最大线程数很快就被耗尽,导致后面的请求被拒绝。

单机开1000个线程算是见顶了,过多的线程会导致大量的CPU上下文切换,反而会降低性能,协程不一样,一个线程可以对应很多协程,单机协程数量最大能开到几十万,而且多个协程共享一个线程,协程的切换不会导致线程的上下文切换

关于协程还有别的选择吗?

上面提到go对协程支持的比较好,还有没有其他方案呢,答案是有,就是openresty,这个就是基于Nginx+LuaJit也是一种协程解决方案,这个方案也是为数不多的由中国人创造且在维护的开源项目,目前同花顺用的就是这种方案,京东详情页也是这个方式,只是个人觉得go的设计和生态都比较成熟,在并发领域,go就是根正苗红

整体架构设计

这个是一个粗略的架构,实际要考虑高可用和集群部署,比这个要复杂得多,针对用户的主要是行情查询服务和行情订阅服务,本文主要是针对行情查询服务,订阅服务放到下一篇文章

为什么要用gRPC

总体思路是就是利用gRPC进行行情的逐级分发,之所以选择grpc,理由是grpc是基于http2.0,采用二进制流式传输,数据压缩传输;支持订阅推送,客户端需要什么就推送什么,不用全部推送;支持多路复用,在流量网关和订阅服务器之间只要少量的连接就可以支持大量用户并发推送

如果不用gRPC,就得用netty自己写一套订阅和推送机制,但是效率肯定不如比gRPC做的好,因为要自己解决很多问题,没有必要重复造轮子,我们应该用通用的解决方案解决通用的问题

高速行情查询

混合部署

将Go行情查询服务和Redis部署在同一个机器上,这样的好处减少网络IO延迟,提升系统的并发处理能力,另外还能节省点成本,目前这个机器是32C 64G的一个机器

这里有人可能会觉得redis和go服务部署在一起机器上,是不是就没有IO了,性能会飞起来,其实不是,虽然用了回环地址,但是还是要走操作系统内核协议栈,而内核协议栈本身可能就是瓶颈,这个又涉及到另外一种超高速方案了,即用户态协议栈,这个后面会专门写文章分享,这里就不展开了

如要做高可用,完全一样的一套服务,再部署一个机器就行了,前面加一个ngnix做负载均衡

存储方案

二级缓存

考虑APP大量的请求主要是查询近期热点数据,所以近期K线放到Redis进行存储,99%的请求都会进入Redis,1分钟K线只存储2个月的,5分钟以上到60分钟存储10个月,日线以上周期全部存储,针对2个月的1分钟K线一般是10000条左右,全市场5000多个票,一共5000万条数据,全部存储到内存是7G,算上其他周期的K线30个G内存空间足够了,最多应该不会超过40G,从行情分发中心订阅实时K线,实时写入redis,每天盘后定时任务删除过期数据

如果内存足够可以多存储一点,我这里存储的时间长度参考了同花顺,和同花顺基本一致

以下是redis 的行情数据数据存储截图

采用zset数据结构,时间戳做score用于range查询,key是股票代码+周期类型,value是行情数据json串,做了点压缩
zset需要定期删除过期数据和写入实时数据

一级缓存

针对每天开盘那一刹那,有大量的请求,基本都是少量的热点数据,这类数据的时效都比较短,采用go进程的缓存方案,这里采用groupcache,LRU 缓存清理策略,确保频繁访问的数据直接从进程内的缓存直接获取结果

持久化层

ScyllaDB 是一款开源的分布式NoSQL数据库,它被设计为与 Apache Cassandra 兼容,但是提供了更高的性能和更低的延迟。ScyllaDB 使用了 C++编写的 Seastar 框架,可以更好地利用现代多核 CPU 和 I/O 系统的能力:

  1. 数据写入速度:股票行情数据通常是高频更新的,ScyllaDB 能够提供高速的数据写入能力。

  2. 数据读取性能:需要对历史数据进行分析时,读取速度同样重要。ScyllaDB 提供快速的读取性能,尤其是在配置和使用得当的情况下。

  3. 可扩展性:随着数据量的增长,你可能需要扩展数据库。ScyllaDB 的分布式架构允许你通过增加节点来水平扩展。

  4. 高可用性:股票行情系统通常不能容忍宕机,ScyllaDB 通过其分布式特性提供高可用性。

  5. 低延迟:对于股票行情系统来说,低延迟是非常关键的,特别是在进行高频交易时。ScyllaDB 在设计上就是为了低延迟而构建的。

  6. 并发处理:大量的用户可能同时查询数据,ScyllaDB 的高并发处理能力可以让它在这种情况下表现良好。

查询源码

package mainimport ("fmt""github/go-redis/redis/v8""golang/x/net/context""log""net/http""strings"
)var redisClient *redis.Client
var ctx = context.Background()func init() {redisClient = redis.NewClient(&redis.Options{Addr: "localhost:6379", // Redis addressDB:   0,               // 默认数据库PoolSize: 10000,         // 连接池大小})
}func getStockData(w http.ResponseWriter, r *http.Request) {start_time:=time.Now()start := r.URL.Query().Get("start")end:= r.URL.Query().Get("end")code := r.URL.Query().Get("code")period := r.URL.Query().Get("period")key := fmt.Sprintf("%s_%s", code, period)//start_time1:=time.Now()result, err := redisClient.ZRangeByScore(ctx, key, &redis.ZRangeBy{Min: start,Max: end,}).Result()//elapsed := time.Since(start_time1)//log.Println("redis query cost:",elapsed)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}var builder strings.Builderbuilder.WriteString("[")length := len(result)builder.Grow(length*100) // 手动扩展缓冲区大小for i := 0; i < length; i += 2 {if i>0{builder.WriteString(",")}// 每次拼接两个字符串builder.WriteString(result[i])if i+1 < length {builder.WriteString(",")builder.WriteString(result[i+1])}}builder.WriteString("]")var response=[]byte(builder.String())//log.Println(response)w.Header().Set("Content-Type", "application/json")_, err = w.Write(response)if err != nil {log.Println("Error writing response:", err)}elapsed = time.Since(start_time)log.Println("process request cost:",elapsed)
}func main() {http.HandleFunc("/getStockData", getStockData)log.Fatal(http.ListenAndServe(":8080", nil))
}

压力测试

测试方案

采用wrk进行压测 

wrk -t 100 -c 1000 -d 60s http://192.168.12.91:8080/getStockData?code=300525.SZ&period=1m&start=202308180930&end=202308181030

-t 100 代表100个线程

-c 1000代表每个线程有1000连接

-d 60s表示持续运行60秒

总体是模拟100*1000=10万个并发用户

之所以选择wrk,因为调研了其他的压测工具,包括jmeter,都无法实现模拟10万个连接数(用户数)

压测结果

Request/sec就是qps是28万,一分钟发送了将近1700万条请求,读取的数据1.74G

错误数0,平均延迟3ms,最大延迟是19ms

关键是即使压到了最大值,前端页面还能正常访问,而且还是极速返回数据,丝毫感觉不到有大并发压力测试

资源消耗

32核心CPU中用了将近用满了25核心,使用78%

64G内存用到8个G,使用率12%

压测完成之后,应用进程的内存和CP全部释放,稳定性还是很强悍

总结

  • IO是大并发的天敌,即使用了协程在有IO阻塞的情况下,qps也很难上去的。协程的好处,就是不会阻塞后续的请求,在大并发且有阻塞的情况下,前端还能正常访问
  • 并不是用了协程就一定会支持大并发,目前这个方案也是经过了好几轮优化才达到的,刚开始只有1万 qps,后来把针对代码做了调整,并发上升到了2万多,然后对redis又做了一次优化,才能达到了28万
  • 并不是Java做不了大并发,如果一定用Java来做,也是可以做的,就是难度和成本都比较高主要是3个方向:1、增加机器 ,2、降低延迟,延迟低了,并发能力也能上来; 3、Java21也已经推出虚拟线程,可以尝试一下
  • 这里redis相当于内存数据库,只是增加了一个刷新缓存的服务,测试结果也是反应了redis的处理性能,而不是hbase
  • 28万/秒的qps并不是目前配置的单机上限,因为目前代码还没有用到一级缓存,如果增加了一级缓存,用多个客户机器测试,应该还有上升空间

更多推荐

券商高速高稳定性行情服务解决方案(单机qps28万/秒)

本文发布于:2024-03-05 12:18:04,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1712276.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:券商   稳定性   单机   解决方案   行情

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!