分布式服务框架基础服务
通信框架
长连接还是短连接
相比于短连接,长连接更节省资源:如果每发送一条消息就要创建链路、发起握手认证、关闭链路释放资源,会损耗大量的系统资源。长连接只在首次创建时或者链路断连重连才创建链路,链路创建成功之后服务提供者和消费者会通过业务消息和心跳维系链路,实现多消息复用同一个链路节省资源。
远程通信是常态,调用时延时关键指标:服务化之后,本地API调用变成了远程服务调用,大量本地方法演化成跨进程通信,网络时延成为关键指标之一。相比于一次简单的服务调用,链路的重建通常耗时更多,这就会导致链路层的时延消耗远远大于服务调用本身的损耗。
BIO还是NIO
BIO的通信模型如下:通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接的请求后,为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数成1:1的正比关系,由于线程是Java虚拟机非常宝贵的系统资源,当线程数膨胀后,系统的性能将急速下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出,创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
NIO的通信模型如下:采用多路复用技术,一个多路复用器Selector可以同时轮询多个Channel,由于JDK采用了epoll()代替了传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需一个线程负责Selector的轮询当需要同时处理多个客户端接入请求时,可以利用多线程或者IO多路复用技术进行处理。
各种IO性能对比
可靠性设计
链路有效性检测:目前流行和通用做法是心跳检测。可以从三个层面来实现:TCP层面的心跳检测,即TCP的Keep-Alive机制,它的作用域是整个TCP协议栈。协议层的心跳检测,主要存在于长连接协议中,例如SMPP协议。应用层的心跳检测,主要由各业务通过约定方式时给对方发送心跳消息的实现。
断连重连检测:客户端检测到链路中断后,等INTERVAL时间,发起重连操作,如果重连失败,间隔周期INTERVAL后再次发起连接,直到重连成功。
消息缓存重发:并非所有场景都需要
资源优雅释放:Java的优雅停机通过注册JDK的ShutdownHook来实现,当系统接收到退出指令后,首先标记系统处于退出状态,不再接收新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。
性能设计
从网络传输方式(BIO还是NIO),序列化性能,线程模型考虑,我们应该采用异步非阻塞通信,高效I/O线程模型,高性能的序列化框架。
序列化与反序列化
序列化包括文本类(XML/JSON)和二进制类(PB/Thrift)2种,序列化和反序列化在使用上要独立灵活,易扩展,在功能设计上要具备前后兼容,跨语言支持,性能考虑码流大小,速度,资源占用等。
常见的序列化有fastjson,MessagePack(跨语言),ProtocolBuffer(跨语言),Thrift(跨语言), Avro(跨语言),Xstream,hessian2(java),java自带序列化,Kryo(java)等。
例如dubbo提供7种序列化,如DubboSerialization(自研究,不建议生产使用),Hessian2Serialization(默认使用),JavaSerialization(原生),CompactedJavaSerialization(压缩java序列化,在原生java序列化基础上,实现了自己定义的类描写叙述符写入和读取。写Object类型的类描写叙述符仅仅写入类名称,而不是类的完整信息,这样有非常多Object类型的情况下能够降低序列化后的size),FstSerialization,KryoSerialization, FastJsonSerialization。
协议栈
不同的服务在性能上使用不同协议进行传输,比如对接异构第三方服务时,通常会选择HTTP/Restful等公有协议。
协议栈承载了业务内部各模块之间的消息交互和服务调用,它的主要功能包括:
定义了私有协议的通信模型和消息定义;
支持服务提供者和消费者之间采用点对点长连接通信;
基于Java NIO通信框架,提供高性能的异步通信能力;
提供可扩展的编解码框架,支持多种序列化格式;
握手和安全认证机制;
链路的高可用性。
服务提供者和消费者之间采用单链路,长连接通信,链路创建流程如下:
- 客户端发送握手请求,携带节点ID等认证信息;
- 服务端校验:节点ID有效性,重复登录,ip地址黑白名单等,通过后,返回握手应答信息;
- 链路建立后,客户端发送业务消息;
- 客户端服务端心跳维持链路;
- 服务端退出时,关闭连接,客户端感知连接关闭,关闭客户端连接。
Dubbo支持多种协议,包括Dubbo协议,injvm协议,hessian协议,http协议,rest协议,redis协议,rmi协议,thrift协议,webservice协议,memcached协议。
服务间参数传递
服务消费者和提供者之间进行通信时,除了接口定义的请求参数,往往还需要携带一些额外参数,例如消息提供者的IP地址,消息调用链的跟踪ID,这些参数不能通过业务接口来进行传递,需要底层的分布式服务框架支持这种惨呼传递方式。大体分为2种:内部传参和外部传参;内部传参指的是服务提供者或者消费者接口内部业务上下文的传递,外部传参指的是服务消费者和提供者之间的参数传递。
Dubbo的服务提供方使用RpcContext.getContext() .getAttachments(),获取参数;服务器消费方使用
RpcContext.getContext().setAttachment(),传递参数。
服务注册
对于服务提供者,它需要发布服务;对于服务消费者,它最关心如何获取它所需要的服务。如何有效的管理服务订阅和发布是一个分布式服务框架需要解决的一个问题。最常见的是使用Zookeeper作为服务注册中心。
服务发布和引用
服务提供者通过配置,注解,API调用等方式,把本地接口发布成远程服务;服务消费者通过对等的方式引用远程服务提供者,实现服务的发布和引用。有4种方式:XML配置,属性配置,API配置, 注解配置。
服务优先级调度
为了保证高优先级的服务能够正常运行,保障服务SLA,需要降低一些非核心服务的调度频次,释放部分资源占用,保障系统的整体运行平稳。有4种实现方式:基于线程调度器的优先级调度策略;基于优先级队列的优先级策略;基于加权配置的优先级调度策略;基于服务迁入迁出的优先级调度策略。