Netty相关网络IO面试题(20题)

写在前面:
本文2万多字,阅读需要花费一点时间, 主要介绍网络编程部分知识,包括Netty 和 IO 网络等知识点;
仅供复习使用!

1. 网络四元组

四元组,简单理解就是在 TCP 协议中,去确定一个客户端连接的组成要素,它包括源IP 地址、目标 IP 地址、源端口号、目标端口号。
正常情况下,我们对于网络通信的认识可能是这样(如图)。
服务端通过 ServerSocket 建立一个对指定端口号的监听,比如 8080。 客户端通过目标 ip 和端口就可以和服务端建立一个连接,然后进行数据传输。
image.png
但是我们知道的是,一个 Server 端可以接收多个客户端的连接,比如像这种情况(如图)。
那,当多个客户端连接到服务端的时候,服务端需要去识别每一个连接
image.png
并且(如图),TCP 是全双工协议,也就是说数据允许在连接的两个方向上同时传输,因此这里的客户端,如果是反向通信,它又变成了服务端
image.png
所以基于这两个原因,就引入了四元组的设计,也就是说,当一个客户端和服务端建立一个 TCP 连接的时候,通过源 IP 地址、目标 IP 地址、源端口号、目标端口号来确定一个唯一的 TCP 连接。因为服务器的 IP 和端口是不变的,只要客户端的 IP 和端口彼此不同就 OK 了。
比如像这种情况(如图),同一个客户端主机上有三个连接连到 Server 端,那么这个时候源 IP 相同,源端口号不同。此时建立的四元组就是(10.23.15.3,59461 ,192.168.8.135,8080)
其中,源端口号是每次建立连接的时候系统自动分配的。
image.png
:::info
“终局思维”来看待自己的职业规划
:::

2. 对 Netty 的认识

好的,我用三点来简单的介绍下 Netty

  1.  第一:Netty 是一个 基于 NIO 模型的高性能网络通信框架,其实可以认为它是对 NIO 网络模。型的封装,提供了简单易用的 API,我们可以利用这些封装好的API 快速开发自己的网络程序。
  2.  第二:Netty 在 NIO 的基础上做了很多优化,比如零拷贝机制、高性能无锁队列、内存池等,因此性能会比 NIO 更高。
  3.  第三:Netty 可以支持多种通信协议,如 Http、WebSocket 等,并且针对数据通信的拆包黏包问题,Netty 内置了拆包策略。

为什么要用 Netty

Nety 相比于直接使用 JDK 自带的 NIO 相关的 API 来说更加易用

  1. 统一的 API,支持多种传输类型,如阻塞、非阻塞,以及 epoll、poll 等模型。
  2. 我们可以使用非常少的代码来实现,多线程 Reactor 模型以及主从多线程 Reactor模型
  3. 自带编解码器解决 TCP 粘包/拆包问题。
  4. 自带各种协议栈。
  5. 比直接使用 Java 库中的 NIO API 有更高的吞吐量、更低的延迟、更低的资源消耗和更少的内存复制。
  6. 安全性不错,有完整的 SSL/TLS 以及 StartTLS 支持。
  7. 社区活跃成熟稳定,经历了大型项目的使用和考验,而且很多开源项目都使用到了Netty, 比如我们经常接触的 Dubbo、RocketMQ ZK 等等。

Netty 可以做什么事情

核心点还是在于解决服务器如何承载更多的用户同时访问
传统的 BIO 模型,由于阻塞的特性,使得在高并发场景中,很难获得更高的吞吐量。
而后来基于 NIO 的多路复用模型虽然在阻塞方面进行了优化,但是它的 API 使用比较复杂,对于初学者来说使用不是很友好。而 Netty 是基于 NIO 的封装,提供了成熟且简单易用的 API,降低了使用成本和学习成本。
本质上来说,Netty 和 NIO 所扮演的角色是相同的,都是为提升服务端的吞吐量,让用户获得更好的产品体验。
另外,Netty 这个中间件经过很多年验证,在目前主流中间件如 Zookeeper、Dubbo、RocketMQ 中都有应用。

3. Netty 核心组件和作用

高手:Netty 由三层结构构成:网络通信层、事件调度器与服务编排层
在网络通信层有三个核心组件:Bootstrap、ServerBootStrap、Channel
 Bootstrap 负责客户端启动并用来链接远程 netty server
 ServerBootStrap 负责服务端监听,用来监听指定端口,
 Channel 是负责网络通信的载体
事件调度器有两个核心组件:EventLoopGroup 与 EventLoop
 EventLoopGroup 本质上是一个线程池,主要负责接收 I/O 请求,并分配线程执行处理请求。
 EventLoop。相当于线程池中的线程
在服务编排层有三个核心组件 ChannelPipeline、ChannelHandler、ChannelHandlerContext
 ChannelPipeline 负责将多个 Channelhandler 链接在一起
 ChannelHandler 针对 IO 数据的处理器,数据接收后,通过指定的 Handler 进行处理。
 ChannelHandlerContext 用来保存 ChannelHandler 的上下文信息

Netty 有几种线程模型吧?

Netty 提供了三种 Reactor 模型的支持
 单线程单 Reactor 模型
 多线程单 Reactor 模型
 多线程多 Reactor 模型

对于这三种线程 Reactor 模型的理解

Reactor 模型有三个重要的组件:

  1. Reactor :将 I/O 事件发派给对应的 Handler
  2. Acceptor :处理客户端连接请求
  3. Handlers :执行非阻塞读/写
    这是最基本的单 Reactor 单线程模型
    image.png
    其中 Reactor 线程,负责多路分离套接字,有新连接到来触发 connect 事件之后,交
    由 Acceptor 进行处理,有 IO 读写事件之后交给 hanlder 处理。
    Acceptor 主要任务就是构建 handler ,在获取到和 client 相关的 SocketChannel 之
    后 ,绑定到相应的 hanlder 上,对应的 SocketChannel 有读写事件之后,基于 racotor
    分发,hanlder 就可以处理了(所有的 IO 事件都绑定到 selector 上,有 Reactor 分发)
    多线程单 Reactor 模型
    单线程 Reactor 这种实现方式有存在着缺点,从实例代码中可以看出,handler 的执行
    是串行的,如果其中一个 handler 处理线程阻塞将导致其他的业务处理阻塞。由于
    handler 和 reactor 在同一个线程中的执行,这也将导致无法接收新的请求。
    为了解决这种问题,有人提出使用多线程的方式来处理业务,也就是在业务处理的地方
    加入线程池异步处理,将 reactor 和 handler 在不同的线程来执行,这就是多线程单
    Reactor 模型
    image.png
    多线程多 Reactor 模型
    在多线程单 Reactor 模型中,所有的 I/O 操作是由一个 Reactor 来完成,而 Reactor
    运行在单个线程中,它需要处理包括 Accept()/read()/write/connect 操作,对于小容
    量的场景,影响不大。但是对于高负载、大并发或大数据量的应用场景时,容易成为瓶
    颈,主要原因如下:
     一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU
    负荷达到 100%,也无法满足海量消息的读取和发送;
     当 NIO 线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超
    时之后往往会进行重发,这更加重了 NIO 线程的负载,最终会导致大量消息积压
    和处理超时,成为系统的性能瓶颈;
    所以,我们还可以更进一步优化,引入多 Reactor 多线程模式
    image.png
    Main Reactor 负责接收客户端的连接请求,然后把接收到的请求传递给
    SubReactor(其中 subReactor 可以有多个),具体的业务 IO 处理由 SubReactor
    完成。
     Acceptor,请求接收者,在实践时其职责类似服务器,并不真正负责连接请求的
    建立,而只将其请求委托 Main Reactor 线程池来实现,起到一个转发的作用。
     Main Reactor,主 Reactor 线程组,主要负责连接事件,并将 IO 读写请求转发
    到 SubReactor 线程池。
     Sub Reactor,Main Reactor 通常监听客户端连接后会将通道的读写转发到 Sub
    Reactor 线程池中一个线程(负载均衡),负责数据的读写。在 NIO 中 通常注册通
    道的读(OP_READ)、写事件(OP_WRITE)

3. 服务网格

服务网格这个概念出来很久了,从 2017 年被提出来,到 2018 年正式爆发,很多云厂
商和互联网企业都在纷纷向服务网格靠拢。像蚂蚁集团、美团、百度、网易等一线互联
网公司,都有服务网格的落地应用。
在我看来呢,服务网格是微服务架构的更进一步升级,它的核心目的是实现网络通信与
业务逻辑的分离,使得开发人员更加专注在业务的实现上。

服务网格,也就是 Service Mesh,它是专门用来处理服务通讯的基础设施层。它的主
要功能是处理服务之间的通信,并且负责实现请求的可靠性传递。
Service Mesh,我们通常把他称为第三代微服务架构,既然是第三代,那么意味着他
是在原来的微服务架构下做的升级。
为了更好的说明 Service Mesh,那我就不得不说一下微服务架构部分的东西。
首先,当我们把一个电商系统以微服务化架构进行拆分后,会的到这样的一个架构(如
图),其中包括 Webserver、payment、inventory 等等。

    这些微服务应用,会被部署到 Docker 容器、或者 Kubernetes 集群。由于每
个服务的业务逻辑是独立的,比如 payment 会实现支付的业务逻辑、order 实现订单
的处理、Webserver 实现客户端请求的响应等

    所以,服务之间必须要相互通信,才能实现功能的完整性。比如用户把一个商
品加入购物车,请求会进入到 Webserver,然后转发到 shopping cart 进行处理,并
存到数据库

image.png
image.png
image.png
而在这个过程中,每个服务之间必须要知道对方的通信地址,并且当有新的节点加入进
来的时候,还需要对这些通信地址进行动态维护。所以,在第一代微服务架构中,每个
微服务除了要实现业务逻辑以外,还需要解决上下游寻址、通讯、以及容错等问题。
(如图)于是,在第二代微服务架构下,引入了服务注册中心来实现服务之间的寻址,
并且服务之间的容错机制、负载均衡也逐步形成了独立的服务框架,比如主流的 Spring
Cloud、或者 Spring Cloud Alibaba。
image.png
在第二代微服务架构中,负责业务开发的小伙伴不仅仅需要关注业务逻辑,还需要花大
量精力去处理微服务中的一些基础性配置工作,虽然 Spring Cloud 已经尽可能去完成
了这些事情,但对于开发人员来说,学习 Spring Cloud,以及针对 Spring Cloud 的
配置和维护,仍然存在较大的挑战。另外呢,也增加了整个微服务的复杂性。
实际上,在我看来,“微服务中所有的这些服务注册、容错、重试、安全等工作,都是
为了保证服务之间通信的可靠性”。
于是,就有了第三代微服务架构,Service Mesh。
(如图)原本模块化到微服务框架里的微服务基础能力,被进一步的从一个 SDK 中演
进成了一个独立的代理进程-SideCar
SideCar 的主要职责就是负责各个微服务之间的通信,承载了原本第二代微服务架构中
的服务发现、调用容错、服务治理等功能。使得微服务基础能力和业务逻辑迭代彻底解耦
image.png
之所以我们称 Service Mesh 为服务网格,是因为在大规模微服务架构中,每个服务的
通信都是由 SideCar 来代理的,各个服务之间的通信拓扑图,看起来就像一个网格形状
Istio 是目前主流的 Service Mesh 开源框架

4. IO 和 NIO 有什么区别

首先,I/O ,指的是 IO 流, 它可以实现数据从磁盘中的读取以及写入。
实际上,除了磁盘以外,内存、网络都可以作为 I/O 流的数据来源和目的地。
在 Java 里面,提供了字符流和字节流两种方式来实现数据流的操作。
其次,当程序是面向网络进行数据的 IO 操作的时候,Java 里面提供了 Socket 的方式来实现。
通过这种方式可以实现数据的网络传输。
(如图)基于 Socket 的 IO 通信,它是属于阻塞式 IO,也就是说,在连接以及 IO 事
件未就绪的情况下,当前的连接会处于阻塞等待的状态。
image.png
如果一旦某个连接处于阻塞状态,那么后续的连接都得等待。所以服务端能够处理的连接数量非常有限。
NIO,是 JDK1.4 里面新增的一种 NEW IO 机制,相比于传统的 IO,NIO 在效率上做了很大的优化,并且新增了几个核心组件。
Channel、Buffer、Selectors。
(如图)另外,还提供了非阻塞的特性,所以,对于网络 IO 来说,NIO 通常也称为No-Block IO,非阻塞 IO。
也就是说,通过 NIO 进行网络数据传输的时候,如果连接未就绪或者 IO 事件未就绪的情况下,服务端不会阻塞当前连接,而是继续去轮询后续的连接来处理。
所以在 NIO 里面,服务端能够并行处理的链接数量更多。
image.png
因此,总的来说,IO 和 NIO 的区别,站在网络 IO 的视角来说,前者是阻塞 IO,后者
是非阻塞 IO

5. TCP 协议为什么要设计三次握手

关于这个问题,我会从下面 3 个方面来回答。

  1. TCP 协议,是一种可靠的,基于字节流的,面向连接的传输层协议。
     可靠性体现在 TCP 协议通信双方的数据传输是稳定的,即便是在网络不好的情
    况下,TCP 都能够保证数据传输到目标端,而这个可靠性是基于数据包确认机
    制来实现的。
     TCP 通信双方的数据传输是通过字节流来实现传输的
     面向连接,是说数据传输之前,必须要建立一个连接,然后基于这个连接进行
    数据传输
  2. (如图)因为 TCP 是面向连接的协议,所以在进行数据通信之前,需要建立一个
    可靠的连接,TCP 采用了三次握手的方式来实现连接的建立。
    所谓的三次握手,就是通信双方一共需要发送三次请求,才能确保这个连接的建立。
     客户端向服务端发送连接请求并携带同步序列号 SYN。
     服务端收到请求后,发送 SYN 和 ACK, 这里的 SYN 表示服务端的同步序列
    号,ACK 表示对前面收到请求的一个确认,表示告诉客户端,我收到了你的请求。
     客户端收到服务端的请求后,再次发送 ACK,这个 ACK 是针对服务端连接的
    一个确认,表示告诉服务端,我收到了你的请求。
    image.png
  3. 之所以 TCP 要设计三次握手,我认为有三个方面的原因:
     TCP 是可靠性通信协议,所以 TCP 协议的通信双方都必须要维护一个序列号,
    去标记已经发送出去的数据包,哪些是已经被对方签收的。而三次握手就是通
    信双方相互告知序列号的起始值,为了确保这个序列号被收到,所以双方都需
    要有一个确认的操作。
     TCP 协议需要在一个不可靠的网络环境下实现可靠的数据传输,意味着通信双
    方必须要通过某种手段来实现一个可靠的数据传输通道,而三次通信是建立这
    样一个通道的最小值。当然还可以四次、五次,只是没必要浪费这个资源。
     防止历史的重复连接初始化造成的混乱问题,比如说在网络比较差的情况下,
    客户端连续多次发送建立连接的请求,假设只有两次握手,那么服务端只能选
    择接受或者拒绝这个连接请求,但是服务端不知道这次请求是不是之前因为网
    络堵塞而过期的请求,也就是说服务端不知道当前客户端的连接是有效还是无
    效。

6. Cookie 和 Session 的区别

我先解释一下 Cookie,它是客户端浏览器用来保存服务端数据的一种机制。
当通过浏览器进行网页访问的时候,服务器可以把某一些状态数据以 key-value 的方式
写入到 Cookie 里面存储到客户端浏览器。
然后客户端下一次再访问服务器的时候,就可以携带这些状态数据发送到服务器端,服
务端
可以根据 Cookie 里面携带的内容来识别使用者。
Session 表示一个会话,它是属于服务器端的容器对象,默认情况下,针对每一个浏览
器的请求。
Servlet 容器都会分配一个 Session。
Session 本质上是一个 ConcurrentHashMap,可以存储当前会话产生的一些状态数
据。
我们都知道,Http 协议本身是一个无状态协议,也就是服务器并不知道客户端发送过
来的多次请求是属于同一个用户。
所以 Session 是用来弥补 Http 无状态的不足,简单来说,服务器端可以利用 session
来存储客户端在同一个会话里面的多次
请求记录。
基于服务端的 session 存储机制,再结合客户端的 Cookie 机制,就可以实现有状态的
Http 协议。
(如图)具体的工作原理是:
客户端第一次访问服务端的时候,服务端会针对这次请求创建一个会话,并生成一个唯
一的 sessionId 来标注这个会话。
然后服务端把这个 sessionid 写入到客户端浏览器的 cookie 里面,用来实现客户端状
态的保存。
在后续的请求里面,每次都会携带 sessionid,服务器端就可以根据这个 sessionid 来
识别当前的会话状态。
image.png
所以,总的来看,Cookie 是客户端的存储机制,Session 是服务端的存储机制。
这两者结合使用,来实现会话状态的存储,以上就是我对这个问题的理解!

7. Netty 中 Reactor 模式的理解

Reactor 其实是在 NIO 多路复用的基础上提出的一个高性能 IO 设计模式。
它的核心思想是把响应 IO 事件和业务处理进行分离,通过一个或者多个线程来处理 IO
事件。
然后把就绪的事件分发给业务线程进行异步处理。
Reactor 模型有三个重要的组件:
 Reactor :把 I/O 事件分发给对应的 Handler
 Acceptor :处理客户端连接请求
 Handlers :执行非阻塞读/写,也就是针对收到的消息进行业务处理。
在 Reactor 的这种设计中,有三种模型分别是
 单线程 Reactor 模型。
 多线程 Reactor 模型。
 主从多 Reactor 多线程模型。
(如图),单线程 Reactor 模型,就是由同一个线程来负责处理 IO 事件以及业务逻辑。
这种方式的缺点在于 handler 的执行过程是串行,如果有任何一个 handler 处理线程
阻塞,就会影响整个服务的吞吐量。
image.png
所以,就有了多线程 Reactor 模型(如图)。
也就是把处理 IO 就绪事件的线程和处理 Handler 业务逻辑的线程进行分离,
每个 Handler 由一个独立线程来处理,
在这种设计下,即便是存在 Handler 线程阻塞问题,也不会对 IO 线程造成影响。
在多线程 Reactor 模型下,所有的 IO 操作都是由一个 Reactor 来完成的,而且 Reactor
运行在单个线程里面。
对于并发较高的场景下,Reactor 就成为了性能瓶颈,所以在这个基础上做了更进一步
优化。
提出了多 Reactor 多线程模型(如图),这种模式也叫 Master-Workers 模式。
它把原本单个 Reactor 拆分成了 Main Reactor 和多个 SubReactor,
Main Reactor 负责接收客户端的链接,然后把接收到的连接请求随机分配到多个
subReactor 里面。
SubReactor 负责 IO 事件的处理。
image.png

8. IO 的多路复用机制

IO 多路复用机制,核心思想是让单个线程去监视多个连接,一旦某个连接就绪,也就
是触发了读/写事件。
就通知应用程序,去获取这个就绪的连接进行读写操作。
也就是在应用程序里面可以使用单个线程同时处理多个客户端连接,
在对系统资源消耗较少的情况下提升服务端的链接处理数量。
在 IO 多路复用机制的实现原理中,
客户端请求到服务端后,此时客户端在传输数据过程中(如图),
为了避免 Server 端在 read 客户端数据过程中阻塞,服务端会把该请求注册到 Selector
复路器上,
服务端此时不需要等待,只需要启动一个线程,
通过 selector.select()阻塞轮询复路器上就绪的 channel 即可,
也就是说,如果某个客户端连接数据传输完成,
那么 select()方法会返回就绪的 channel,然后执行相关的处理就可以了。
image.png
常见的 IO 多路复用机制的实现方式有: select 、poll、epoll。
这些都是 Linux 系统提供的 IO 复用机制的实现,其中 select 和 poll 是基于轮询的方式
去获取就绪连接。
而 epoll 是基于事件驱动的方式获取就绪连接。从性能的角度来看,基于事件驱动的方
式要优于轮询的方式。

9. select 和 epoll 的区别

select 和 epoll 都是 I/O 多路复用的机制,它们可以让一个进程监听多个文件描述符的 IO 事件或者连接事件,
只要其中任意一个或多个文件描述符就绪,就会触发阻塞唤醒,使得应用程序可以直接
进行数据的读取或者写入。
它们的区别主要有几个方面:

  1. select 是基于轮询的机制,它需要遍历整个监听集合,直到找到就绪的文件描述符。
    而 epoll 是基于事件通知的机制,它只需要遍历当前就绪的文件描述符集合,大大减少了遍历的次数和开销。
  2. select 的监听集合大小有限,一般受操作系统的限制,而 epoll 没有这个限制,可以监听大量的文件描述符。
  3. 在处理大量文件描述符时,select 的性能随着监听集合的增大而逐渐下降,而epoll 的性能则能够保持稳定。
  4. 在多线程环境下,select 需要将监听集合传递给每个线程,而 epoll 可以在一个线程中处理多个文件描述符,避免了线程间的切换和数据复制等开销。

从对比中不难发现,epoll 相比于 select 在性能和扩展性方面都有很大的优势,所以通常在企业级应用中使用较多的还是 epoll。
但是在一些特定的场景下,select 也有它的优势,例如在处理少量文件描述符或需要跨平台支持的情况下,select 是一个更好的选择。

epoll和poll的区别

  1. select模型,使⽤的是数组来存储Socket连接⽂件描述符,容量是固定的,需要通过轮询来判断是否发⽣了IO事件
  2. poll模型,使⽤的是链表来存储Socket连接⽂件描述符,容量是不固定的,同样需要通过轮询来判断是否发⽣了IO事件
  3. epoll模型,epoll和poll是完全不同的,epoll是⼀种事件通知模型,当发⽣了IO事件时,应⽤程序才进⾏IO操作,不需要像poll模型那样主动去轮询

10. 拆包和粘包?怎么解决?

拆包和粘包是在网络编程中比较常见的现象。
因为 TCP 协议底层是面向流的传输,所以数据在传输的过程中会被分割成一个个的数
据包(如图)
接收端在接收数据时需要重新组装数据包,但是 TCP 协议不保证数据包与应用层的数
据交互一一对应,
所以就可能会造成数据不完整的问题
image.png
拆包指的是把一个完整的数据包拆分成多个小包进行发送,
而接收端可能无法一次性接收到所有小包,导致接收到的数据不完整。
粘包指的是把多个数据包粘合在一起一次性发送,
而接收端可能无法正确区分每个数据包,导致接收到的数据出现错位或混乱。
拆包和粘包现象是 TCP 协议的数据传输机制导致的,所以要解决这个问题,
就是需要让服务端知道如何判断一个数据包的完整性,因此可以采用以下几种方法:
 在数据包中添加特殊字符或特殊标记,表示一个数据包的开始和结尾
 通过自定义消息协议,并在协议头中保存数据包的长度信息,接收方可以根据这个
长度来解析数据包来保证消息的完整性
 基于定长消息,也就是发送端的消息长度是固定的,服务端按照固定长度来解析

Netty 线程池默认大小为 CPU 核数的 2 倍

1、分析原因 ()
我们都知道使用多线程的本质是为了提升程序的性能,总体来说有两个最核心的指标,一个延迟,一个吞
吐量。延迟指的是发出请求到收到响应的时间,吞吐量指的是 。这两个指标之间有一定的关联,因为同等
条件下延迟越短吞吐量越大,但由于它们是不同的维度,一个是时间,一个是空间,并不能相互转换。
因此,提升性能最主要的目的就是要降低延迟,提高吞吐量

2、如何衡量性能指标
具体来说,要降低延时,就是要提高 CPU 的处理能力。而提高吞吐量,就是要提高 IO 读写效率。那么具
体如何衡量系统性能,我从以下两个方面来分析:
我们可以将程序分为是 I/O 密集型任务和 CPU 密集型任务。
那么第 1 种情况,对于 CPU 密集型任务而言,理论上“线程的数量 = CPU 核数”就是合适的。但是,在
实际应用中的线程数量一般会设置为“CPU 核数 + 1”。因为线程有可能因为内存页失效或其他原因导致
阻塞,多设置一个线程可以保证 CPU 的利用率。
第 2 种情况,而对于 I/O 密集型任务而言,我们假设 CPU 计算和 I/O 操作的耗时比是 1:1,那么 2 个线
程是最合适的。如果 CPU 计算和 I/O 操作的耗时比是 1:2,也就是说 3 个线程是合适的,这样 CPU 和 I/O
设备的利用率都可以达到 100%。根据这个推测,我们可以得到这样一个公式
最佳线程数 = 1 +(IO 耗时/CPU 耗时)
image.png
如果是多核 CPU 只需要等比扩大
*最佳线程数 = CPU 核数 (1 + R)
而 Netty 的默认线程池个数,就是假设了 I/O 耗时和 CPU 耗时的占比是 1:1,实际上 Netty 有一个参数叫
ioRatio,默认为 50,它表示在一个轮事件循环中,单个 I/O 线程执行 I/O 事件和执行异步任务的耗时占
比为 1:1。相当于 R = 1,代入上面的公式,就可以得出 Netty 默认设置的线程池大小自然就是
默认线程池大小 = CPU 核数 * (1 + 1)
也就 2 倍 CPU 核数大小。而且 Netty 的应用场景主要是 I/O 密集型任务,所以,Netty 这样设计是有科
学性的。

3、总结与使用建议
我们可以提前压测,根据压测结果来进行微调。一般情况下,保证生产环境为压测环境的 75%即可。如果
修改 Netty 的线程池大小,也一定要考虑 ioRatio 这个参数是否需要调整,因为 2 倍 CPU 核数的大小是
假设的 I/O 耗时和 CPU 耗时为 1:1,调整线程大小之后,性能效果也不一定符合期望值。
在大部分场景下,没有必要太过于关注线程池大小怎么配置,I/O 密集型任务使用 Netty 默认配置就可以
了。因为,提高吞吐量也不能只简单的只依赖线程池,还可以通过缓存、微服务拆分,优化业务逻辑、优
化算法等方式来协作解决。
image.png

11. Netty 的零拷贝

Netty 的零拷贝主要包含三个方面:

  • Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
  • Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。
  • Netty 的文件传输采用了 transferTo 方法,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。

12. 哪几种序列化协议

序列化(编码)是将对象序列化为二进制形式(字节数组),主要用于网络传输、数据持久化等;而反序列化(解码)则是将从网络、磁盘等读取的字节数组还原成原始对象,主要用于网络传输对象的解码,以便完成远程调用。影响序列化性能的关键因素:序列化后的码流大小(网络带宽的占用)、序列化的性能(CPU资源占用);是否支持跨语言(异构系统的对接和开发语言切换)。

  • Java默认提供的序列化:无法跨语言、序列化后的码流太大、序列化的性能差XML,优点:人机可读性好,可指定元素或特性的名称。缺点:序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息;只能序列化公共属性和字段;不能序列化方法;文件庞大,文件格式复杂,传输占带宽。适用场景:当做配置文件存储数据,实时数据转换。
  • JSON,是一种轻量级的数据交换格式,优点:兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好、与XML相比,其协议比较简单,解析速度比较快。缺点:数据的描述性比XML差、不适合性能要求为ms级别的情况、额外空间开销比较大。适用场景(可替代XML):跨防火墙访问、可调式性要求高、基于Web browser的Ajax请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务。
  • protostuff 基于protobuf协议,但不需要配置proto文件,直接导包即可Jboss marshaling 可以直接序列化java类, 无须实java.io.Serializable接口Message pack 一个高效的二进制序列化格式
  • Hessian 采用二进制协议的轻量级remoting onhttp工具
  • kryo 基于protobuf协议,只支持java,需要注册(Registration),然后序列化(Output),反序列化(Input)

13. NIOEventLoopGroup源码?

NioEventLoopGroup(其实是MultithreadEventExecutorGroup) 内部维护一
个类型为 EventExecutor children [], 默认大小是处理器核数 * 2, 这样就构成了
一个线程池,初始化EventExecutor时NioEventLoopGroup重载newChild方
法,所以children元素的实际类型为NioEventLoop。
线程启动时调用SingleThreadEventExecutor的构造方法,执行NioEventLoop
类的run方法,首先会调用hasTasks()方法判断当前taskQueue是否有元素。如
果taskQueue中有元素,执行 selectNow() 方法,最终执行
selector.selectNow(),该方法会立即返回。如果taskQueue没有元素,执行
select(oldWakenUp) 方法
select ( oldWakenUp) 方法解决了 Nio 中的 bug,selectCnt 用来记录
selector.select方法的执行次数和标识是否执行过selector.selectNow(),若触
发了epoll的空轮询bug,则会反复执行selector.select(timeoutMillis),变量
selectCnt 会逐渐变大,当selectCnt 达到阈值(默认512),则执行
rebuildSelector方法,进行selector重建,解决cpu占用100%的bug。
rebuildSelector方法先通过openSelector方法创建一个新的selector。然后将
old selector的selectionKey执行cancel。最后将old selector的channel重新注
册到新的selector中。rebuild后,需要重新执行方法selectNow,检查是否有
已ready的selectionKey。
接下来调用processSelectedKeys 方法(处理I/O任务),当selectedKeys !=
null时,调用processSelectedKeysOptimized方法,迭代 selectedKeys 获取
就绪的 IO 事件的selectkey存放在数组selectedKeys中, 然后为每个事件都调用
processSelectedKey 来处理它,processSelectedKey 中分别处理OP_READ;
OP_WRITE;OP_CONNECT事件。
最后调用runAllTasks方法(非IO任务),该方法首先会调用
fetchFromScheduledTaskQueue方法,把scheduledTaskQueue中已经超过
延迟执行时间的任务移到taskQueue中等待被执行,然后依次从taskQueue中
取任务执行,每执行64个任务,进行耗时检查,如果已执行时间超过预先设定
的执行时间,则停止执行非IO任务,避免非IO任务太多,影响IO任务的执行。
每个NioEventLoop对应一个线程和一个Selector,NioServerSocketChannel
会主动注册到某一个NioEventLoop的Selector上,NioEventLoop负责事件轮
询。
Outbound 事件都是请求事件, 发起者是 Channel,处理者是 unsafe,通过
Outbound 事件进行通知,传播方向是 tail到head。Inbound 事件发起者是
unsafe,事件的处理者是 Channel, 是通知事件,传播方向是从头到尾。
内存管理机制,首先会预申请一大块内存Arena,Arena由许多Chunk组成,而
每个Chunk默认由2048个page组成。Chunk通过AVL树的形式组织Page,每
个叶子节点表示一个Page,而中间节点表示内存区域,节点自己记录它在整个
Arena中的偏移地址。当区域被分配出去后,中间节点上的标记位会被标记,这
样就表示这个中间节点以下的所有节点都已被分配了。大于8k的内存分配在
poolChunkList中,而PoolSubpage用于分配小于8k的内存,它会把一个page
分割成多段,进行内存分配。
ByteBuf的特点:支持自动扩容(4M),保证put方法不会抛出异常、通过内置
的复合缓冲类型,实现零拷贝(zero-copy);不需要调用flip()来切换读/写模
式,读取和写入索引分开;方法链;引用计数基于AtomicIntegerFieldUpdater
用于内存回收;PooledByteBuf采用二叉树来实现一个内存池,集中管理内存的
分配和释放,不用每次使用都新建一个缓冲区对象。UnpooledHeapByteBuf每
次都会新建一个缓冲区对象。

14. CSRF攻击?如何防⽌?

CSRF: Cross Site Requst Forgery 跨站请求伪造,⼀个正常的请求会将合法⽤户的session id保存到
浏览器的cookie。这时候,如果⽤户在浏览器中打来另⼀个tab⻚, 那这个tab⻚也是可以获得浏览器的
cookie。⿊客就可以利⽤这个cookie信息进⾏攻击。

攻击过程:

  1. 某银⾏⽹站A可以以GET请求的⽅式发起转账操作。 www.xxx.com/transfor.do?
    accountNum=100&money=1000 accountNum表示⽬标账户。这个请求肯定是需要登录才可以正
    常访问的。
  2. 攻击者在某个论坛或者⽹站上,上传⼀个图⽚,链接地址是 www.xxx.com/transfer.do?
    accountNum=888&money=10000 其中这个accountNum就是攻击者⾃⼰的银⾏账户。
  3. 如果有⼀个⽤户,登录了银⾏⽹站,然后⼜打开浏览器的另⼀个tab⻚,点击了这个图⽚。这时,银
    ⾏就会受理到⼀个带了正确cookie的请求,就会完成转账。⽤户的钱就被盗了。

CSRF防⽌⽅式:

  1. 尽量使⽤POST请求,限制GET请求。POST请求可以带请求体,攻击者就不容易伪造出请求。
  2. 将cookie设置为HttpOnly : respose.setHeader(“Set-Cookie”,“cookiename=cookievalue;HttpOnly”)。
  3. 增加token;
  4. 在请求中放⼊⼀个攻击者⽆法伪造的信息,并且该信息不存在于cookie当中。这也是Spring Security框架中采⽤的防范⽅式。

15. 什么是OAuth2.0协议?有哪⼏种认证⽅式

OAuth2.0是⼀个开放标准,允许⽤户授权第三⽅应⽤程序访问他们存储在另外的服务提供者上的信息,
⽽不需要将⽤户名和密码提供给第三⽅应⽤或分享他们数据的所有内容。
OAuth2.0协议的认证流程,简单理解,就是允许我们将之前的授权和认证过程交给⼀个独⽴的第三⽅进⾏担保。

OAuth2.0协议有四种认证⽅式:

  1. 授权码模式
  2. 简化模式
  3. 密码模式
  4. 客户端模式

:::info
OAuth2.0的使⽤场景通常称为联合登录, ⼀处注册,多处使⽤
SSO Single Sign On 单点登录。** ⼀处登录,多处同时登录 (SSO的实现关键是将Session信息集中存储)**
:::

单点登录这块怎么实现

单点登录的英文名叫做:Single Sign On(简称SSO),只需要登录一次,就可以访
问所有信任的应用系统

解决系统之间Session不共享问题有一下几种方案:
Tomcat集群Session全局复制(最多支持5台tomcat,不推荐使用)
JWT(常见)
Oauth2
CAS
自己实现(redis+token

  以JWT为例
I. 用户访问其他系统,会在网关判断token是否有效
II. 如果token无效则会返回401(认证失败)前端跳转到登录页面
III. 用户发送登录请求,返回浏览器一个token,浏览器把token保存到cookie
IV. 再去访问其他服务的时候,都需要携带token,由网关统一验证后路由到目
标服务  

image.png

权限认证如何实现

后台的管理系统,更注重权限控制,最常见的就是RBAC模型来指导实现权限
RBAC(Role-Based Access Control)基于角色的访问控制
3个基础部分组成:用户、角色、权限
具体实现
5用户角色中间表、角色权限中间表)
7张表(用户表、角色表、权限表、菜单表、用户角色中间表、角色权限中间表、权限菜单中间表)
image.png
常见的权限框架有:

  • Apache shiro
  • Spring security(推荐)

上传数据的安全性

对称加密
文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥
image.png
数据发信方将明文和加密密钥一起经过特殊的加密算法处理后,使其变成复杂的加密密文发送出去,
收信方收到密文后,若想解读出原文,则需要使用加密时用的密钥以及相同加密算法的逆算法对密文进行解密,才能使其回复成可读明文。
在对称加密算法中,使用的密钥只有一个,收发双方都使用这个密钥,这就需要解密方事先知道加密密钥。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点: 没有非对称加密安全.
用途: 一般用于保存用户手机号、身份证等敏感但能解密的信息。
常见的对称加密算法有: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256

非对称加密
两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密
image.png
解释: 同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客
户端.
加密与解密:
私钥加密,持有公钥才可以解密
公钥加密,持有私钥才可解密
签名:
私钥签名, 持有公钥进行验证是否被篡改过.
优点: 非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据
进行加密。
用途: 一般用于签名和认证。私钥服务器保存, 用来加密, 公钥客户拿着用于对
于令牌或者签名的解密或者校验使用.
常见的非对称加密算法有: RSA、DSA(数字签名用)、ECC(移动设备用)、RS256
:::info
使用非对称加密(或对称加密),给前端一个公钥让他把数据加密后传到后台,后台解密后处理数据
传输的数据很大建议使用对称加密,不过不能保存敏感信息
传输的数据较小,要求安全性高,建议采用非对称加密
:::

16. 如何设计⼀个开放授权平台

开放授权平台也可以按照认证和授权两个⽅向来梳理。
1. 认证: 就可以按照OAuth2.0协议来规划认证的过程。
2. 授权
a. ⾸先需要待接⼊的第三⽅应⽤在开放授权平台进⾏注册,注册需要提供⼏个必要的信息 clintID,
消息推送地址,密钥(⼀对公私钥,私钥由授权平台⾃⼰保存,公钥分发给第三⽅应⽤)。
b. 然后,第三⽅应⽤引导客户发起请求时,采⽤公钥进⾏参数加密,授权开放平台使⽤对应的私钥解密。
c. 接下来:授权开放平台同步响应第三⽅应⽤的只是消息是否处理成功的结果。⽽真正的业务数
据由授权开放平台异步推动给第三⽅应⽤预留的推送地址。

17. TCP的三次握⼿和四次挥⼿

TCP协议是7层⽹络协议中的传输层协议,负责数据的可靠传输。

在建⽴TCP连接时,需要通过三次握⼿来建⽴,过程是:

  1. 客户端向服务端发送⼀个SYN
  2. 服务端接收到SYN后,给客户端发送⼀个SYN_ACK
  3. 客户端接收到SYN_ACK后,再给服务端发送⼀个ACK

在断开TCP连接时,需要通过四次挥⼿来断开,过程是:

  1. 客户端向服务端发送FIN
  2. 服务端接收FIN后,向客户端发送ACK,表示我接收到了断开连接的请求,客户端你可以不发数据
    了,不过服务端这边可能还有数据正在处理
  3. 服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接
  4. 客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了

18. 浏览器发出⼀个请求到收到响应经历了哪些步骤?

  1. 浏览器解析⽤户输⼊的URL,⽣成⼀个HTTP格式的请求
  2. 先根据URL域名从本地hosts⽂件查找是否有映射IP,如果没有就将域名发送给电脑所配置的DNS进
    ⾏域名解析,得到IP地址
  3. 浏览器通过操作系统将请求通过四层⽹络协议发送出去
  4. 途中可能会经过各种路由器、交换机,最终到达服务器
  5. 服务器收到请求后,根据请求所指定的端⼝,将请求传递给绑定了该端⼝的应⽤程序,⽐如8080被tomcat占⽤
  6. tomcat接收到请求数据后,按照http协议的格式进⾏解析,解析得到所要访问的servlet
  7. 然后servlet来处理这个请求,如果是SpringMVC中的DispatcherServlet,那么则会找到对应的Controller中的⽅法,并执⾏该⽅法得到结果
  8. Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过⽹络发送给浏览器所在的服务器
  9. 浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

19. 跨域请求,怎么解决

跨域是指浏览器在发起⽹络请求时,会检查该请求所对应的协议、域名、端⼝和当前⽹⻚是否⼀致,如
果不⼀致则浏览器会进⾏限制,⽐如在www.baidu.com的某个⽹⻚中,如果使⽤ajax去访问
www.jd.com是不⾏的,但是如果是img、iframe、script等标签的src属性去访问则是可以的,之所以浏
览器要做这层限制,是为了⽤户信息安全。但是如果开发者想要绕过这层限制也是可以的:

  1. response添加header,⽐如resp.setHeader(“Access-Control-Allow-Origin”, “*”);表示可以访问
    所有⽹站,不受是否同源的限制
  2. jsonp的⽅式,该技术底层就是基于script标签来实现的,因为script标签是可以跨域的
  3. 后台⾃⼰控制,先访问同域名下的接⼝,然后在接⼝中再去使⽤HTTPClient等⼯具去调⽤⽬标接⼝
  4. ⽹关,和第三种⽅式类似,都是交给后台服务来进⾏跨域访问

20. 零拷⻉

零拷⻉指的是,应⽤程序在需要把内核中的⼀块区域数据转移到另外⼀块内核区域去时,不需要经过先
复制到⽤户空间,再转移到⽬标内核区域去了,⽽直接实现转移。image.pngimage.png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值