很久没有写技术相关的内容了。最近在写一个TCPSocket服务器,用于与硬件交互,由于是自定义协议,所以涉及到了一些字节流处理。
在同事的推荐下使用了Vert.x,以下是一些我的理解和学习笔记。
Vertx实例 和 Verticle
Vertx实例包括EventBus、Shared Data、Global Session等。
一个Vertx app有两类线程:Eventloop线程、Worker线程。Worker线程用于执行一部分同步代码,Eventloop用于执行verticles。
Verticle可以看作是一类线程,一般启动多个Verticle实例来充分发挥多核性能。同一类Verticles可以绑定到同一个端口,参考官方文档 Scaling - sharing TCP servers。
一个Verticle类可以启动多个实例,N个Verticle被分配到M个eventloop线程上,eventloop线程是Vertx内部自己分配的系统级线程。
异步编程
Vertx的特点,和Nodejs相似。比起Netty这样的框架,需要处理多线程之间的问题,考虑加锁、线程安全问题。由于是单线程Actor模型,基于事件的模型,写的代码不是一个顺序的方式,有一定的上手成本。如果有做过Nodejs开发,那么就非常好理解。和Nodejs一样通过回调或者Promise、Future等方式来书写代码。
Verticles 拆分
既可以按照业务逻辑拆分,也可以按照功能拆分。我的做法是启动了N个TCP Server Verticle(如果只启动一个实例,那么无法充分发挥多核CPU的性能),用于处理Socket连接。启动了Y个线程用于操作数据库/文件的Verticle实例,因为不是所有socket连接都会涉及到相关操作。这需要具体根据业务场景而定。比如在Nodejs中,写代码的时候都是只考虑单线程的逻辑,但是部署的时候可以多进程部署,通过master进程与child进程的通信,来充分利用多核。
很明显,当然也并不是线程数越多越好,默认情况下vertx会启动(CPU数*2)个eventloop线程,如果线程过多反而导致上下文切换的开销不小。
执行同步代码
大部分的第三方库都有官方提供的异步实现,比如Redis、MySQL客户端等。没有的可以通过vertx提供的vertx.executeBlocking方法来将同步代码包起来执行,它会自动使用Worker线程去处理。Worker线程通常情况下是不需要手动去启动、处理的。
Verticles 通信
- Eventbus,一个vertx实例只有一个eventbus(类似EventEmitter)
- Shared Data API(异步锁、Map、计数器)
TCP 相关
TCP协议已经解决了可靠性和传输有序性的问题,写的时候需要考虑到TCP的数据包是流的形式,比如多个TCP包发过来的时候服务器接受到的可能是一个,可能是多个一起,总之需要在应用层自己处理边界问题。
常见的分割的方式比如\r\n,定长,协议头写入后续包的长度,参考官方文档 Record parser,以及参考Vertx + Protobuf二进制协议解析来实现自定义协议的解析。
Buffer
Vertx实现的Buffer类会自动扩容,处理数据也非常的方便。
BTW,一个实用的API:setRegisterWriteHandler,在将应用切分为多个Verticle后,需要在其他的verticle中向socket中写数据时,使用这个API非常方便。
参考资料
https://vertx.io/docs/vertx-core/java/
https://www.zhihu.com/question/308972356