.
目录
1.feign和ribbon区别,ribbon 和nginx 的区别
2.微服务拆分原则和方法
3.NIO和BIO 的区别
4.Netty用的啥实现
1.feign和ribbon区别,ribbon 和nginx 的区别
客户端负载均衡器和服务端负载均衡器的关系。以这两个类型来分:服务器端负载均衡 Nginx, lvs ,硬件 F5 等都可以达到负载均衡的目的;客户端负载均衡 feign,ribbon ,由java语言实现。
服务器端负载均衡 Nginx
nginx 是客户端所有请求统一交给 nginx,由 nginx 进行实现负载均衡请求转发,属于服务器端负载均衡。既请求由 nginx 服务器端进行转发。
客户端负载均衡 Ribbon
Ribbon 是从 eureka 注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮询负载均衡策略。既在客户端实现负载均衡。
应用场景的区别:
Nginx 适合于服务器端实现负载均衡 比如 Tomcat ,Ribbon,Feign 适合与在微服务中 RPC 远程调用实现本地服务负载均衡,比如 Dubbo、SpringCloud 中都是采用本地负载均衡。
spring cloud的Netflix中提供了两个组件实现软负载均衡调用:ribbon和feign。
Ribbon
Ribbon 是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟 http 请求,步骤相当繁琐。
Feign
Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。Feign 是在 Ribbon 的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建 http 请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。
两者的不同是:
1.启动类使用的注解不同,Ribbon 用的是@RibbonClient,Feign 用的是@EnableFeignClients。
2.服务的指定位置不同,Ribbon 是在@RibbonClient 注解上声明,Feign 则是在定义抽象方法的接口中使用@FeignClient 声明。
3.调用方式不同,Ribbon 需要自己构建 http 请求,模拟 http 请求然后使用 RestTemplate 发送给其他服务,步骤相当繁琐。
2.微服务拆分原则和方法
1. 单一职责、高内聚低耦合、可以把如用户系统,订单系统,物流系统,支付系统,商品系统 等按照功能点划分实现。
2. 服务粒度适中、不要过度为了单一职责而完全脱离业务或者为了设计而设计。
3. 考虑团队结构、人员构成,职责划分
4. 以业务模型切入、以业务为核心展开服务的划分
5. 演进式拆分、先单体–集群–分布式
6. 避免环形依赖与双向依赖、可以把功能的功能抽离处理,形成通用的组件来供不同的子系统调用。
3.NIO和BIO 的区别
BIO
同步阻塞式IO,服务器实现模式为一个连接建立一个线程,即客户端有连接请求时,服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情,会造成不必要的线程开销,可以通过线程池机制改善
BIO原理
单线程:同步阻塞式IO在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字,在这个通信套接字上进行读写操作,此时不能接收其他客户端的连接请求,只能等待同当前连接的客户端的操作执行完成。
多线程:如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字,同时开启一个新的线程来出炉这个套接字的数据读写请求,然后又立刻继续accept等待其他客户端连接请求,即为每个客户端请求都单独创建一个线程来单独处理
NIO
同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器(Selector)上,多路复用器论询到连接有IO请求时才启动一个线程进行处理
同步阻塞式IO关键采用了事件驱动的思想来实现一个多路转换器
NIO BIO区别
NIO和BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件
NIO原理
1.建立连接:若服务端监听到客户端到连接请求,便为其建立通信套接字(java中就是通道(Channel),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部接收,依次为它们建立通信套接字
2.处理数据:若服务端监听到来自自己已经创建了通信套接字到客户端发来的数据,就会调用对应的接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理
3.同时监听:监听多个客户端的连接请求和接收数据请求的同时,还能监听自己有数据发送
AIO
AIO原理
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由OS先完成了再通知服务器应用区启动线程进行处理
与NIO不同,当进行读写操作时,只需要直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取的时,操作系统就会将可读的流传入read方法的缓冲区,并通知应用程序。对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。既可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。在JDK7中,这部分内容被称为NIO.2,主要在java.nio.channels包下增加了四个异步通道。
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
应用场景
BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中
NIO适合处理连接数目特别多,但是连接比较短小的场景,Jetty,Mina,Zookeeper等都是基于java nio实现
AIO方式使用于连接数目比较多且比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂
4.Netty用的啥实现
1. Netty简介
Netty是一个高性能、异步事件驱动的NIO框架,基于JAVA NIO提供的API实现。它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。 作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。
2. Netty线程模型
在JAVA NIO方面Selector给Reactor模式提供了基础,Netty结合Selector和Reactor模式设计了高效的线程模型。先来看下Reactor模式:
2.1 Reactor模式
Wikipedia这么解释Reactor模型:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。首先Reactor模式首先是事件驱动的,有一个或者多个并发输入源,有一个Server Handler和多个Request Handlers,这个Service Handler会同步的将输入的请求多路复用的分发给相应的Request Handler。可以如下图所示:
从结构上有点类似生产者和消费者模型,即一个或多个生产者将事件放入一个Queue中,而一个或者多个消费者主动的从这个队列中poll事件来处理;而Reactor模式则没有Queue来做缓冲,每当一个事件输入到Service Handler之后,该Service Handler会主动根据不同的Evnent类型将其分发给对应的Request Handler来处理。
2.2 Reator模式的实现
关于Java NIO 构造Reator模式,Doug lea在《Scalable IO in Java》中给了很好的阐述,这里截取PPT对Reator模式的实现进行说明
1.第一种实现模型如下:
这是最简单的Reactor单线程模型,由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会被阻塞,理论上一个线程可以独立处理所有的IO操作。这时Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分发请求到处理链中。
对于一些小容量应用场景,可以使用到单线程模型。但对于高负载,大并发的应用却不合适,主要原因如下:
- 当一个NIO线程同时处理成百上千的链路,性能上无法支撑,即使NIO线程的CPU负荷达到100%,也无法完全处理消息
- 当NIO线程负载过重后,处理速度会变慢,会导致大量客户端连接超时,超时之后往往会重发,更加重了NIO线程的负载。
- 可靠性低,一个线程意外死循环,会导致整个通信系统不可用
为了解决这些问题,出现了Reactor多线程模型。
2.Reactor多线程模型:
相比上一种模式,该模型在处理链部分采用了多线程(线程池)。
在绝大多数场景下,该模型都能满足性能需求。但是,在一些特殊的应用场景下,如服务器会对客户端的握手消息进行安全认证。这类场景下,单独的一个Acceptor线程可能会存在性能不足的问题。为了解决这些问题,产生了第三种Reactor线程模型
3.Reactor主从模型
该模型相比第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。
2.3 Netty模型
2.2中说完了Reactor的三种模型,那么Netty是哪一种呢?其实Netty的线程模型是Reactor模型的变种,那就是去掉线程池的第三种形式的变种,这也是Netty NIO的默认模式。Netty中Reactor模式的参与者主要有下面一些组件:
- Selector
- EventLoopGroup/EventLoop
- ChannelPipeline
Selector即为NIO中提供的SelectableChannel多路复用器,充当着demultiplexer的角色,这里不再赘述;下面对另外两种功能和其在Netty之Reactor模式中扮演的角色进行介绍。
3.EventLoopGroup/EventLoop
当系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗。多线程并发执行某个业务流程,业务开发者还需要时刻对线程安全保持警惕,哪些数据可能会被并发修改,如何保护?这不仅降低了开发效率,也会带来额外的性能损耗。
为了解决上述问题,Netty采用了串行化设计理念,从消息的读取、编码以及后续Handler的执行,始终都由IO线程EventLoop负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。这也解释了为什么Netty线程模型去掉了Reactor主从模型中线程池。
EventLoopGroup是一组EventLoop的抽象,EventLoopGroup提供next接口,可以总一组EventLoop里面按照一定规则获取其中一个EventLoop来处理任务,对于EventLoopGroup这里需要了解的是在Netty中,在Netty服务器编程中我们需要BossEventLoopGroup和WorkerEventLoopGroup两个EventLoopGroup来进行工作。通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程,也就是说BossEventLoopGroup的线程数参数为1。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO处理。
EventLoop的实现充当Reactor模式中的分发(Dispatcher)的角色。
4.ChannelPipeline
ChannelPipeline其实是担任着Reactor模式中的请求处理器这个角色。
ChannelPipeline的默认实现是DefaultChannelPipeline,DefaultChannelPipeline本身维护着一个用户不可见的tail和head的ChannelHandler,他们分别位于链表队列的头部和尾部。tail在更上层的部分,而head在靠近网络层的方向。在Netty中关于ChannelHandler有两个重要的接口,ChannelInBoundHandler和ChannelOutBoundHandler。inbound可以理解为网络数据从外部流向系统内部,而outbound可以理解为网络数据从系统内部流向系统外部。用户实现的ChannelHandler可以根据需要实现其中一个或多个接口,将其放入Pipeline中的链表队列中,ChannelPipeline会根据不同的IO事件类型来找到相应的Handler来处理,同时链表队列是责任链模式的一种变种,自上而下或自下而上所有满足事件关联的Handler都会对事件进行处理。
ChannelInBoundHandler对从客户端发往服务器的报文进行处理,一般用来执行半包/粘包,解码,读取数据,业务处理等;ChannelOutBoundHandler对从服务器发往客户端的报文进行处理,一般用来进行编码,发送报文到客户端。
下图是对ChannelPipeline执行过程的说明:
关于Pipeline的更多知识可参考:浅谈管道模型(Pipeline)
5.Buffer
Netty提供的经过扩展的Buffer相对NIO中的有个许多优势,作为数据存取非常重要的一块,我们来看看Netty中的Buffer有什么特点。
1.ByteBuf读写指针
- 在ByteBuffer中,读写指针都是position,而在ByteBuf中,读写指针分别为readerIndex和writerIndex,直观看上去ByteBuffer仅用了一个指针就实现了两个指针的功能,节省了变量,但是当对于ByteBuffer的读写状态切换的时候必须要调用flip方法,而当下一次写之前,必须要将Buffe中的内容读完,再调用clear方法。每次读之前调用flip,写之前调用clear,这样无疑给开发带来了繁琐的步骤,而且内容没有读完是不能写的,这样非常不灵活。相比之下我们看看ByteBuf,读的时候仅仅依赖readerIndex指针,写的时候仅仅依赖writerIndex指针,不需每次读写之前调用对应的方法,而且没有必须一次读完的限制。
2.零拷贝
- Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
- Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
- Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
3.引用计数与池化技术
- 在Netty中,每个被申请的Buffer对于Netty来说都可能是很宝贵的资源,因此为了获得对于内存的申请与回收更多的控制权,Netty自己根据引用计数法去实现了内存的管理。Netty对于Buffer的使用都是基于直接内存(DirectBuffer)实现的,大大提高I/O操作的效率,然而DirectBuffer和HeapBuffer相比之下除了I/O操作效率高之外还有一个天生的缺点,即对于DirectBuffer的申请相比HeapBuffer效率更低,因此Netty结合引用计数实现了PolledBuffer,即池化的用法,当引用计数等于0的时候,Netty将Buffer回收致池中,在下一次申请Buffer的没某个时刻会被复用。
总结
Netty其实本质上就是Reactor模式的实现,Selector作为多路复用器,EventLoop作为转发器,Pipeline作为事件处理器。但是和一般的Reactor不同的是,Netty使用串行化实现,并在Pipeline中使用了责任链模式。
Netty中的buffer相对有NIO中的buffer又做了一些优化,大大提高了性能。