一、又是划水的一天
虽然时不时划水,总算是坚持下来,希望以后每天输出一篇,一个知识点或者几句话的收获也记录下。掘金没有文章归档,更像是日志形式,正好记录自己每天的收获。
二、BIO和线程池
现在的高性能网络模型总是和 Netty、IO多路复用、NIO、Reactor、事件驱动等,也很少有讲BIO的,那BIO到底实践起来行不行?
行,在 Netty 没有普及的时代、在没有业务上的秒杀等超高并发场景或者在接口业务处理耗时低的场景,BIO通信模型编写简单,可靠。
三、案例
这里是实践过的一个案例,都说亿级流量系统,其实今天讲的使用 BIO 和线程池的网络通信模型也可以说是每天亿级流量系统,具体业务我就不描述了,是一个消息系统。很多亿级流量系统指的都是每天,其实分摊到一天 86400 秒,每秒的流量不高。考虑到业务都集中在白天,那么一天算8小时,最有大约在 150 TPS,结合负载均衡设备,到单机的流量很小了。
1、业务上的处理
只是描述一下现状,很多地方合不合理都值得商榷。这里想插一段,有时候去一家新公司,有的同学可能会抱怨这系统设计这么垃圾、这个基础设施都没有、这代码写的太烂了我怎么再改。罗马不是一天建成的,现有的系统和代码也支撑了这个公司业务发展到了这个规模,很多不合理的地方,只是用更高级别或者更远的业务场景来看现在,小米加步枪照样也把仗打了,等到了更好的武器和人员配置。大部分公司招人除了应届生,整体都是希望招聘到能力在团队中位数之上的人,招聘不单单是凑人数,也是在提升团队整体实例。也是希望新来的同学带来更先进的东西。互联网业务发展太快了,很多公司从用户量一万到千万可能也就两三年,技术上都可能来不及反应,不可能要求一万流量的时候 k8s、分布式数据库、微服务、监控全部都完善了。
扯远了回来,亿级流量,首先根据业务拆分了两套处理环境,两个负载均衡、每台后面大约挂了10几台服务器。
2、通信
使用 Socket BIO 通信,应用层协议自定义,传输报文可以是二进制、JSON、XML,阻塞式通信加50 * CUP核数线程模型, 伪代码如下:
Socket socket = serverSocket.accept();
Thread thread = new XxxThread(socket);
excuter.submit(thread);
为什么可以使用BIO,一个是因为业务流量平稳,没有高峰期;第二个重要原因是业务处理速度块,大约只需要10ms,为什么这么块,因为不涉及到微服务和数据库,耗时的IO操作都是读写Redis。消息系统,业务系统来的数据只要格式对,理论上不存在发不出去的场景,发不出去也是极特殊情况,业届消息触达率(比如 APP 推送)也不可能 100%。
省略很多业务逻辑后,大致上是:
- socket建立 -> 线程池处理 -> 消息校验 -> redis -> 响应
- 消费redis -> 发送
可以大致算一下10ms一个业务处理,1s就是 100TPS,线程池也有几百,也不是相当耗费CPU的操作,也就是并发能力100问题不大,单机上千TPS能够扛住。十几台大约上万TPS,Redis集群模式 16主32从,应付流量平稳的业务场景,每天发送上亿消息已经稳定运行了好几年。
3、细节
系统的很多细节没有提,觉着老旧的设计,以后也不太会用到,但是思路还是挺不错的,列举下细节。
-
短连接,客户端发送一下消息之后,就关闭链接,虽然浪费时间,但避免没数据占用链接,而且 socket 链接建立能耗时多久,几毫米可以接收,客户端代码也很简单,不用去写链接线程池也不用去维护长链接。
-
消息体很大一次发不完怎么办,应用层协议有总的消息体大小,比如 1MB,每次通信不能操作 128KB,超过了要分片发送。怎么做到分片的数据合起来?一切都在协议和服务端设计里,协议里有字段标明消息唯一身份标示、消息顺序、消息分片总数,服务端在接收到分片消息后,使用JVM内存把消息缓存起来(Map),每次的分片消息都会检查是否完整了,只有消息唯一身份标示这条消息整体完成了,才会处理业务逻辑,否者一直缓存着。
时间太紧张了,以后多画画图