(1)简单说说 Netty 的零拷贝机制?

零拷贝技术是一种计算机操作系统中用于提高数据传输效率的优化策略。在传统的数据传输过程中,需要将数据从一个缓存区拷贝到另一个缓存区,然后再传输给目标。这涉及到多次的CPU和内存之间的数据拷贝操作,会消耗CPU的时间和内存带宽。而零拷贝技术通过直接共享数据的内存地址,避免了中间的拷贝过程,从而提高了数据传输的效率。

1. 传统IO执行流程
在这里插入图片描述
2.零拷贝技术的实现
DMA(Direct Memory Access,直接内存访问)技术,绕过 CPU,直接在内存和外设之间进行数据传输。这样可以减少 CPU 的参与,提高数据传输的效率。

零拷贝技术可以利用Linux下的MMap、sendFile等手段来实现,使得数据能够直接从磁盘映射到内核缓存区,然后通过DMA传输到网卡缓存,整个过程中CPU只负责管理和调度,而无需执行实际的数据复制指令。

MMap
MMap(Memory Map)是 Linux 操作系统中提供的一种将文件映射到进程地址空间的一种机制,通过 MMap 进程可以像访问内存一样访问文件,而无需显式的复制操作。
使用 MMap 可以把 IO 执行流程优化成以下执行步骤:
在这里插入图片描述
传统的IO需要四次拷贝和四次上下文(用户态和内核态)切换,而MMap只需要三次拷贝和四次上下文切换,从而能够提升程序整体的执行效率,并且节省了程序的内存空间。

sendFile
在Linux操作系统中的sendFile()是一个系统调用函数,用于高效地将文件系统从内核空间直接传输到网络套接字(socket)上,从而实现零拷贝技术。这个函数的主要目的是减少 CPU 上下文切换以及内存复制操作,提高文件传输性能。
在这里插入图片描述

3. 哪些地方用到了零拷贝技术?

在Java中,以下几个地方使用了零拷贝技术:

  1. NIO(New I/O)通道:java.nio.channels.FileChannel 提供了 transferTo() 和 transferFrom() 方法,可以直接将数据从一个通道传输到另一个通道,例如从文件通道直接传输到 Socket 通道,整个过程无需将数据复制到用户空间缓冲区,从而实现了零拷贝。
  2. Socket Direct Buffer:在 JDK 1.4 及更高版本中,Java NIO 支持使用直接缓冲区(DirectBuffer),这类缓冲区是在系统堆外分配的,可以直接由网卡硬件进行 DMA 操作,减少数据在用户态与内核态之间复制次数 ,提高网络数据发送效率。
  3. Apache Kafka或者Netty等高性能框架:这些框架在底层实现上通常会利用 Java NIO 的上述特性来优化数据传输,如 Kafka 生产者和消费者在传输消息时会用到零拷贝技术以提升性能。

总结
使用零拷贝技术可以减少CPU拷贝,及减少了上下文的切换带来的性能开销,提高了程序的整体执行效率,它们的区别如下表所示:

CPU拷贝(次数) DMA拷贝(次数) 上下文切换(次数)
传统IO 2 2 4
MMap 1 2 4
sendFile() 1 2 2

(2)Netty 是如何解决粘包和拆包问题的?

Netty主要通过四种内置编码器来解决TCP的粘包和拆包问题:定长解码器(FixedLengthFrameDecoder)、行解码器(LineBasedFrameDecoder)、分隔符解码器(LengthFieldBasedFrameDecoder)和长度字段解码器(LengthFieldBasedFrameDecoder)。这些解码器通过固定长度、分隔符标记、或消息头铲毒字段等策略,为应用层协议定义数据包的边界,从而确保数据在传输和接收过程中能够被正确地分割和组装成独立的数据包。

解决方案原理
粘包和拆包问题根源在于TCP协议是面向流的,没有数据边界。为了解决这个问题,需要在应用层中加入数据包的边界标识,Netty提供的解码器正是实现这一功能的工具。

  1. FixedLengthFrameDecoder(定长解码器)
  • 原理: 规定每个消息包的长度是固定的。
  • 实现: 当接收到足够长度的数据时,直接按照这个固定长度进行数据分割,确保每个数据包都是一个完整的、固定大小的单元。
  1. LineBasedFrameDecoder(行编码器)
  • 原理: 以换行符 (\n) 作为消息的结束标识。
  • 实现: 接收端根据换行符来识别消息的边界,当读取到换行符时,就认为是一个完整的消息包。
  1. DelimiterBasedFrameDecoder(分隔符解码器)
  • 原理: 允许使用自定义的特定分隔符(如 END)来分割消息。
  • 实现: 接收端根据指定的分隔符来切割数据流,从而准确地界定每个消息的边界。
  1. LengthFieldBasedFrameDecoder(长度字段解码器)
  • 原理: 在消息体中加入一个长度字段,用来明确表示当前消息包的长度。
  • 实现: 接收端通过读取这个长度字段的值,知道数据包的准确大小,然后根据长度来截取完整的消息,从而解决粘包和拆包问题。
  • 配套使用: 通常与LengthFieldPrepender配合使用,LengthFieldPrepender在发送端为每个消息前添加长度字段前缀,而LengthFieldBasedFrameDecoder则在接收端解析这个长度字段并移除它。

通过使用这些内置的解码器,Netty能够根据不同的应用协议规则,有效地管理网络数据流,确保在发送端和接收端正确地处理和解析数据包,从而解决了TCP协议带来的粘包和拆包问题。

(3)Netty 如何解决 JDK NIO 中的空轮询 Bug?

Selector的空轮询BUG

若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%。

注意:是CPU 100%,非常严重的bug。

这个臭名昭著的epoll bug,是JDK NIO的BUG,官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7、JDK1.8版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。

Netty的解决办法总览:

  • 对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,这则触发了epoll死循环bug。
  • 重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。

Netty解决空轮询的4步骤:
Netty解决空轮询的4步骤,具体如下:
在这里插入图片描述
第一部分:定时阻塞select(timeMillins)

  • 先定义当前时间currentTimeNanos。
  • 接着计算出一个指向最少需要的时间timeoutMills。
  • 定时阻塞select(timeMillins)。
  • 每次对selectCnt++操作。

第二部分:有效IO事件处理逻辑

第三部分:超时处理逻辑

  • 如果查询超时,则seletCnt重置为1。

第四步: 解决空轮询 BUG

  • 一旦到达SELECTOR_AUTO_REBUILD_THRESHOLD这个阀值,就需要重建selector来解决这个问题。
  • 这个阀值默认是512。
  • 重建selector,重新注册channel通道

(4)介绍一下 Reactor 线程模型?

Reactor线程模型是一种高效的事件驱动编程模型,用于处理高并发网络I/O请求,其核心思想是利用I/O多路复用机制,通过Reactor(反应器)监听多种类型的事件(如连接、读、写事件),并将这些事件分发给相应的处理器(Handler)进行处理。该模型有单线程和多线程(主从多Reactor)两种模式,主从多Reactor模式通过将连接建立时间和读写时间分离到不同线程组来提高并发处理的能力和资源利用率。

(5)说说你知道的几种 I/O 模型

以下是几种常见的 I/O 模型:

1. 阻塞I/O(Blocking I/O)

  • 原理:在阻塞 I/O 模型中,当应用程序发起一个 I/O 操作(如读取文件或接收网络数据)时,线程会被阻塞,直到 I/O 操作完成。这意味着在等待 I/O 操作完成的过程中,线程无法执行其他任务。
  • 示例:以读取文件为例,当调用 read() 函数读取文件内容时,如果文件数据尚未准备好(例如文件还在从磁盘读取到内存的过程中),线程会一直等待,直到数据读取完成并返回给调用者,线程才能继续执行后续代码。
  • 特点
    • 简单易用:编程模型简单,开发者不需要处理复杂的异步逻辑。
    • 资源利用率低:由于线程在等待 I/O 操作时会一直阻塞,导致线程无法充分利用 CPU 资源,特别是在高并发场景下,需要创建大量线程来处理多个 I/O 请求,这会消耗大量的系统资源。

2. 非阻塞I/O(Non-blocking I/O)

  • 原理:非阻塞 I/O 模型允许应用程序发起 I/O 操作后,立即返回一个状态值,而不是一直等待 I/O 操作完成。如果 I/O 操作尚未完成,返回的状态值会表明操作未就绪;如果 I/O 操作已完成,则返回相应的数据。应用程序可以通过轮询的方式不断检查 I/O 操作的状态,直到操作完成。
  • 示例:在读取文件时,将文件描述符设置为非阻塞模式,然后调用 read() 函数。如果文件数据尚未准备好,read() 函数会立即返回一个错误码(如 EAGAIN 或 EWOULDBLOCK),表示数据未就绪。应用程序可以稍后再进行尝试,直到读取到数据为止。
  • 特点
    • 避免线程阻塞:线程不会因为等待 I/O 操作而阻塞,可以继续执行其他任务,提高了线程的利用率。
    • 轮询开销大:由于需要不断轮询检查 I/O 操作的状态,会消耗一定的 CPU 资源,特别是在 I/O 操作完成时间不确定的情况下,轮询的效率会较低。

3. I/O多路复用(I/O Multiplexing)

  • 原理:I/O 多路复用模型通过一个专门的机制(如 select、poll、epoll 等系统调用)来同时监控多个 I/O 事件(如文件描述符的可读、可写事件)。当有 I/O 事件就绪时,该机制会通知应用程序,应用程序再对就绪的 I/O 事件进行处理。
  • 示例:在一个服务器程序中,使用 select 函数同时监控多个客户端连接的文件描述符。当有客户端发送数据时,select 函数会返回,告知应用程序哪些文件描述符上有可读事件。应用程序然后可以对这些就绪的文件描述符进行 read() 操作,读取客户端发送的数据。
  • 特点
    • 高效处理多连接:可以同时监控多个 I/O 操作,适用于高并发场景,能够用较少的线程处理大量的 I/O 请求。
    • 编程复杂度较高:相比阻塞 I/O 和非阻塞 I/O,I/O 多路复用的编程模型相对复杂,需要开发者理解和掌握相关的系统调用和事件处理机制。

4. 信号驱动I/O(Singal-driven I/O)

  • 原理:信号驱动 I/O 模型允许应用程序预先注册一个信号处理函数,当 I/O 操作就绪时,操作系统会向应用程序发送一个信号(如 SIGIO)。应用程序在接收到信号后,可以在信号处理函数中执行相应的 I/O 操作。
  • 示例:在一个网络应用程序中,为套接字注册 SIGIO 信号处理函数。当有数据到达套接字时,操作系统会发送 SIGIO 信号,应用程序在信号处理函数中调用 read() 函数读取数据。
  • 特点
    • 异步通知:通过信号的方式异步通知应用程序 I/O 操作的就绪状态,避免了线程的阻塞和轮询。
    • 信号处理复杂:信号处理函数的执行环境比较特殊,可能会受到一些限制,而且信号的处理可能会与其他部分的代码产生交互,增加了编程的复杂性和出错的可能性。

5. 异步I/O(Asynchronous I/O)

  • 原理:异步 I/O 模型是真正的异步 I/O 操作方式。应用程序发起 I/O 操作后,可以立即返回并继续执行其他任务,而不需要关心 I/O 操作的完成情况。当 I/O 操作完成后,操作系统会通过回调函数或事件通知的方式将结果返回给应用程序。
  • 示例:使用异步 I/O 接口(如 Linux 下的 aio_read 函数)读取文件。应用程序调用 aio_read 函数后,函数会立即返回,应用程序可以继续执行其他代码。当文件数据读取完成后,操作系统会调用预先注册的回调函数,将读取到的数据传递给应用程序。
  • 特点
    • 高效非阻塞:应用程序在发起 I/O 操作后不会被阻塞,能够充分利用 CPU 资源,提高系统的并发处理能力。
    • 实现复杂:异步 I/O 的实现需要操作系统和应用程序的紧密配合,编程模型相对复杂,开发者需要处理回调函数或事件通知的逻辑。

不同I/O模型的比较

I/O模型 阻塞情况 并发处理能力 编程复杂度 适用场景
阻塞I/O 线程阻塞 简单应用,低并发场景
非阻塞I/O 线程不阻塞,但需轮询 需要避免线程阻塞,但I/O操作完成时间不确定的场景
I/O多路复用 线程不阻塞 高并发服务器应用,如Web服务器、聊天服务器等
信号驱动I/O 线程不阻塞 需要异步通知I/O就绪状态的场景,但编程复杂
异步I/O 线程不阻塞 高性能、高并发应用,对实时性要求较高的场景

在实际应用中,需要根据具体的需求和场景选择合适的 I/O 模型,以平衡性能、编程复杂度和资源利用率等因素。

(6)select、poll、epoll 之间有什么区别?

select,poll,epoll都是I/O多路复用机制,即能监视多个fd,一旦某fd就绪(读或写就绪),能够通知程序进行相应读写操作。 但select,poll,epoll本质都是同步I/O,因为他们都需在读写事件就绪后,自己负责进行读写,即该读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O实现会负责把数据从内核拷贝到用户空间。

select,poll需自己主动不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但它是设备就绪时,调用回调函数,把就绪fd放入就绪链表,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但select和poll在“醒着”时要遍历整个fd集合,而epoll在“醒着”的时候只需判断就绪链表是否为空,节省大量CPU时间,这就是回调机制带来的性能提升。

select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,且把current往等待队列上挂也只挂一次(在epoll_wait开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少开销。

(7)说一下 Netty 的应用场景?

Netty 作为一个高性能、异步事件驱动的网络应用程序框架,广泛应用于需要高并发、低延迟通信的场景,包括:分布式系统中的RPC框架 (如Dubbo、gRPC) 和 消息传输 (如RocketMQ),游戏服务器,实时音视频流处理,以及作为其他应用如Spring Cloud Gateway 等的底层通信组件,用于构建高性能服务器和客户端。

主要应用场景:

  • 分布式系统的远程服务调用(RPC):Netty提供高性能的通信基础,常被用于Dubbo、gRPC等RPC框架,实现分布式环境中服务间的快速、高并发调用和数据传输。
  • 消息队列:许多分布式消息队列产品,如 RocketMQ,在底层使用 Netty 实现消息的高性能、可靠传输。
  • 游戏服务器开发:Netty支持TCP/UDP及HTTP协议栈,并允许开发者自定义私有协议,非常适合构建大型网络游戏和手机游戏的服务端,以支持高并发的玩家交互。
  • 实时流数据的处理:例如,用于音视频流的实时传输和处理,以及其他需要快速传输大量实时数据的场景。
  • 网络代理与网关:Spring Cloud Gateway等API网关使用Netty构建,用于实现HTTP代理和负载均衡功能。
  • 大数据传输:在 Hadoop 等大数据基础设施中,利用 Netty 提升数据在多个计算节点之间的传输性能。
  • 其他高性能网络应用:作为 Netty 是一个灵活的基础框架,可用于开发各种需要高性能、低延迟、高吞吐量的网络应用程序,如协议服务器、客户端等。

(8)为什么不选择使用原生的 NIO 而选择使用 Netty 呢?

在网络编程领域,Java NIO (Non-blocking I/O) 提供了非阻塞I/O操作的API,使得开发高性能网络应用成为可能。然而,直接使用NIO API进行开发往往会遇到诸多挑战,这就是为什么许多开发者选择使用Netty这样的网络应用框架。本文将详细探讨原生NIO的局限性以及Netty如何解决这些问题。

原生NIO的挑战:

  1. API复杂且容易出错
  2. 需要处理复杂的线程模型
  3. 粘包/拆包问题处理困难
  4. 内存管理复杂

对于大多数网络应用开发场景,Netty相比原生NIO提供了更高级的抽象、更好的性能、更简洁的API和更完善的生态。除非有特殊需求,否则选择Netty无疑是更明智的决定。Netty不仅解决了原生NIO的痛点,还提供了许多额外的强大功能,使得开发者能够专注于业务逻辑而非底层细节。

(9)Netty 性能为什么这么高?

Netty之所以性能高,是因为它是一个高性能、异步事件驱动的NIO框架,其核心原因包括:

  • 异步非阻塞的IO模型:Netty的所有IO操作都是异步非阻塞的,它利用 Java Nio(New I/O)的非阻塞特性,通过Future-Listener机制,避免了线程的阻塞,提高了并发处理能力。
  • 高效的内存管理:Netty使用了 直接内存(Direct Buffer)来分配内存,而不是 Java堆内存,这减少了JVM垃圾回收的压力和频繁的数据复制,从而提升了性能。
  • 零拷贝(Zero Copy)技术:Netty在传输数据时,尽可能地避免了数据的复制,通过直接使用操作系统提供的功能,实现了零拷贝,大幅减少了CPU开销和数据拷贝次数。
  • 高吞吐量的线程模型:Netty采用了多线程、事件驱动的 Reactor模式(又称 Proactor模式),每个 Boss线程负责接收连接,而多个 Worker线程负责处理I/O事件,这种分工更合理,能更高效地利用资源。
  • 优化的线程同步和并发控制:Netty在处理并发时,大量使用了volatile关键字、CAS(Compare-and-Swap)操作和原子类,以提高多线程环境下的性能和安全。同时,通过读写锁的使用也提升了并发处理能力。

(10)简述 MyBatis 的插件运行原理,以及如何编写一个插件?

Mybatis的插件相当于拦截器,是使用JAVA动态代理来实现的。Mybatis对持久层的操作借助于四大组件(Executor,StatementHandler,ParameterHandler,ResultSetHandler)。在创建这四类对象时会判断是否有配置好的插件,若是有则返回被插件代理的动态代理对象。在执行这四类对象的方法时,先判断执行的方法是否是需要代理的方法,如果是则执行插件类的增强方法。在四大对象创建的时候:

  1. 在Mybatis中每个创建出来的对象并不是直接返回的,而是获取所有的拦截器interceptor.plugin(target);
  2. 在获取到所有的Interceptor(插件需要实现的接口)后;调用interceptor.plugin(target);返回target包装后的对象;
  3. 插件机制,我们可以使用插件为目标对象创建一个代理对象;aop(面向切面)我们的插件可以认为是四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行。

Mybatis插件编写思路:
Mybatis 的插件就是拦截器,目的是增强核心对象的功能,它是基于动态代理实现的:

  1. 首先要编写一个类实现Interceptor接口并实现其中的方法。
  2. 然后通过@Intercepts注解标识这是个Mybatis插件,在注解内部通过@Signature来确定具体拦截的是哪个组件的哪个类、方法。
  3. 最后将该插件配置在 Mybatis 的 sqlMapConfig.xml 配置文件中,以便Mybatis识别该插件并加入到它的InterceptorChain中。

(11)说说 MyBatis 的缓存机制?

MyBatis 提供了一级缓存和二级缓存两种机制来提升数据库查询性能并减轻数据库压力
一级缓存:作用范围是 SqlSession,默认开启,数据不共享,但会在执行 commit 或 rollback 后清空,以避免数据不一致。
二级缓存:作用范围是 Mapper 命名空间,默认关闭,需要手动配置开启。它支持多个 SqlSession 共享同一个 Mapper 命名空间下的数据,并且可以通过实现 Cache 接口的方式支持第三方缓存(如 Ehcache),但可能在分布式环境或涉及多表查询时导致数据不一致问题。

工作原理
1. 一级缓存(SqlSession 级别)

  • 在一个 SqlSession 中,会使用一个 HashMap 来存储查询结果,当使用相同的 SQL 进行查询时,会优先从一级缓存中获取数据,避免重复查询数据库。
  • 生命周期与 SqlSession 相同,当 SqlSession 关闭时,缓存也会失效。
  • 在执行 INSERT、UPDATE、DELETE 操作后,一级缓存会被清空,以防止读取到过时的数据。

2. 二级缓存(Mapper 级别)

  • 需要手动在 Mapper 文件中进行配置以启用。
  • 其生命周期与 Mapper 生命周期一致,允许同一个 Mapper 命名空间下的多个 SqlSession 共享数据。
  • 在查询时,会先经过 CachingExecutor(二级缓存的执行器)进行二级缓存的查询,若未命中,才会进入一级缓存和数据库查询。
  • 同样会在执行 INSERT、UPDATE、DELETE 操作后清空该 Mapper 命名空间下的缓存,以防止脏数据。

适用场景及注意事项

  • 一级缓存:适合单机环境,同一个 SqlSession 的多次查询,能够有效避免重复查询。
  • 二级缓存:适合读多写少的场景,同一个 Mapper 命名空间下的数据在多个会话中可以共享。
  • 脏数据:由于缓存的存在,在分布式或写操作频繁的系统中,需要格外注意可能出现脏数据问题,因为其他 SqlSession 可能获取到未更新的数据。

(12)MyBatis 中 #{} 和 ${} 的区别是什么?

#{}(预编译占位符)
#{} 是 MyBatis 推荐的参数占位方式,它会在 SQL 执行前被替换为问号 (?),然后通过 PreparedStatement 的参数设置方法来安全地设置参数值。

${}(字符串替换)
${} 是简单的字符串替换,MyBatis 会直接将参数值替换到 SQL 语句中,不做任何转义或类型处理。

主要区别:
1. 处理方式不同:

  • #{}:MyBatis 会创建 PreparedStatement 对象,使用 ? 作为占位符,然后通过 setXxx() 方法安全地设置参数
  • ${}:MyBatis 会直接将参数值替换到 SQL 语句中,形成完整的 SQL 字符串

2. 安全性差异:

  • #{}:能有效防止 SQL 注入攻击,因为参数值不会改变 SQL 语句结构
  • ${}:存在 SQL 注入风险,因为参数值直接成为 SQL 的一部分

3. 性能差异:

  • #{}:数据库可以对预编译的 SQL 进行缓存,提高重复执行效率
  • ${}:每次都需要重新编译 SQL 语句

4. 使用场景:

  • #{}:适用于绝大多数情况,特别是用户输入参数
  • ${}:适用于需要动态指定表名、列名等 SQL 关键字的情况

何时使用 ${}
虽然 ${} 有风险,但在某些场景下是必要的:

  1. 动态表名:SELECT * FROM ${tableName} WHERE id = #{id}
  2. 动态列名:SELECT ${columns} FROM users
  3. ORDER BY 子句:SELECT * FROM users ORDER BY ${columnName}

在这些情况下,应确保参数值不是来自不可信的来源,或者进行严格的验证。

(13)MyBatis 与 Hibernate 有哪些不同?

MyBatis 和 Hibernate 的主要区别在于,Hibernate 是一个全自动的 ORM 框架,而 MyBatis 是一个半自动的 SQL 映射框架。Hibernate 负责生成和管理 SQL,并自动处理对象与数据库的映射,开发更简单但灵活性较低。MyBatis 则需要开发者编写 SQL 和结果映射 (ResultMap),提供了更高的灵活性和对 SQL 的完全控制,更易于SQL优化和数据库移植性差的问题。

1. 本质与开发模式

  • Hibernate:完全的 ORM 框架,通过对象模型映射到数据库,开发者无需关心 SQL,可以更专注于业务逻辑。
  • MyBatis:半自动的框架,允许开发者编写自己的 SQL,并在 XML 或注解中进行映射,对 SQL 具有完全控制权。

2. SQL 控制与灵活性

  • Hibernate:主要依赖 HQL(Hibernate Query Language)或自动生成的 SQL,灵活性相对较差,对复杂、变态的 SQL 需求支持不如 MyBatis。
  • MyBatis:开发者直接编写 SQL,可以方便地编写复杂的 SQL 语句和进行手动优化,尤其在性能调优方面更具优势。

3. 学习曲线与开发难度

  • Hibernate:学习成本和配置复杂度相对较高,但熟练后开发效率高。
  • MyBatis:入门简单,开发上手快,但需要关注更多技术细节,尤其是在配置和管理复杂的关联对象时。

4. 缓存机制

  • Hibernate:拥有完善的二级缓存机制,支持第三方缓存,能够减少数据库查询次数,提高性能。
  • MyBatis:本身提供的缓存机制相对不佳,需要开发者自行配置,或依赖于第三方缓存方案。

5. 数据库移植性

  • Hibernate:数据库移植性较好,同一个应用程序在不同数据库上通常无需修改 SQL。
  • MyBatis:由于需要编写具体的 SQL,不同数据库可能需要编写不同的 SQL 语句,移植性较差。

选择建议

  • 选择 MyBatis:适合注重 SQL 控制权、对数据库性能有较高要求、且数据库环境相对固定的中小型项目。
  • 选择 Hibernate:适用于大型项目、需要处理复杂业务逻辑、对数据库移植性要求较高、并且希望利用完善的缓存机制的场景。

(14)什么是 MyBatis-Plus?它有什么作用?

MyBatis-Plus 是一个 MyBatis 的增强工具,它在 MyBatis 的基础上提供了许多便利的功能,旨在简化开发、提高效率,例如自动实现通用 CRUD 操作、提供强大的条件构造器、内置代码生成器和分页插件等。

MyBatis-Plus 的作用

  • 自动 CRUD 操作:通过 BaseMapper 和 ServiceImpl 接口,提供了一系列自动实现的通用增删改查方法,减少了手动编写 SQL 和映射代码的工作量。
  • 条件构造器:提供了 QueryWrapper 等工具,能够方便地构建复杂的查询条件,使数据库查询更加灵活和高效。
  • 代码生成器:内置了一套代码生成器,能够快速生成 Model(实体类)、Mapper、Service 和 Controller 等分层代码,极大地提高了开发效率。
  • 分页插件:提供了方便的分页查询功能,开发者无需手动处理分页逻辑,简化了分页操作。
  • 其他增强功能:还支持逻辑删除、自动填充、乐观锁等一些实用的功能,进一步提升了开发体验和数据操作的规范性。

MyBatis-Plus 和 MyBatis 的区别
MyBatis-Plus 是 MyBatis 的一个功能扩展和增强工具,两者之间的主要区别在于:

  • 功能范围:MyBatis 是一个轻量级的 SQL 映射框架,主要用于解决对象和 SQL 之间的映射问题,提供灵活的动态 SQL。MyBatis-Plus 在 MyBatis 的基础上增加了许多便捷的功能,如自动生成 SQL、代码生成器、条件构造器等。
  • 使用方式:MyBatis 通常需要手动编写 XML 映射文件或使用注解来定义 SQL。MyBatis-Plus 提供了更加简化的 API 和更丰富的抽象,并主要使用注解进行配置和映射,同时支持所有 MyBatis 原生的特性。
  • 代码量:使用 MyBatis-Plus 可以大幅减少需要编写的样板代码和 SQL 语句。开发者只需要编写业务逻辑代码和实体类,便可实现数据库操作,从而极大地提高了开发效率。
  • 兼容性:MyBatis-Plus 是基于 MyBatis 的,在引入 MyBatis-Plus 的同时,并不会影响现有的 MyBatis 框架,并且可以无缝集成 MyBatis 的原生特性。

(15)Dubbo 和 Spring Cloud Gateway 有什么区别?

Dubbo 是一个 RPC(远程过程调用)框架,主要用于服务之间的通信。它提供高性能的 RPC 调用、负载均衡、服务发现、服务注册、服务治理等功能。
适用于需要高性能 RPC 调用的分布式系统,常用于内部服务通信。

Spring Cloud Gateway 是一个 API 网关,用于处理外部客户端请求并将其路由到后端服务。它提供请求路由、负载均衡、协议转换、安全管理、流量控制、日志和监控等功能。
适用于微服务架构中的统一入口管理,常用于外部请求的入口层。

所以说它们不是一个层级的东西。

(16)什么是 API 网关?它有什么作用?

API 网关是一个充当客户端和后端服务之间中介的软件层,为 API 调用提供单一入口点,并处理诸如身份验证、请求路由、速率限制、监控和协议转换等横切关注点。它简化了 API 的管理,提高了安全性,改善了性能,并为开发者提供了更一致、更易用的 API 接口。

API 网关的作用

  • 单一入口点:为所有后端服务提供一个统一的访问入口,客户端不必了解每个服务的具体位置和细节。
  • 请求路由和转发:将客户端的请求路由到正确的后端服务或微服务,并可能将单个请求拆分成多个请求或将多个请求合并为一个。
  • 安全管理:集中管理身份验证、授权和访问控制,提供安全防护层,保护后端服务免受恶意攻击。
  • 流量管理:实施速率限制,防止服务过载,并可以进行负载均衡以优化 API 性能。
  • 协议转换:可以在不同的应用程序和后端服务之间转换协议,例如将 HTTP 请求转换为不同的协议。
  • 可观测性:提供 API 流量的监控、日志记录和跟踪功能,帮助开发者更好地理解和管理 API 的使用情况。
  • 请求/响应转换:对请求和响应进行转换,使数据格式更适合客户端或后端服务。
  • 缓存:缓存 API 响应,提高访问效率,并减少对后端服务的压力。
  • API 生命周期管理:支持 API 从设计、发布到运维监控的整个生命周期管理。

为什么需要 API 网关?
在微服务架构中,服务数量庞大且分散,如果客户端需要直接访问每一个微服务,将会变得非常复杂。API 网关通过提供一个集中的管理点,解决了这些复杂性,使得开发者能够更专注于业务逻辑的开发,而不是被基础设施细节所困扰。

(17)什么是 Seata?

Seata是阿里巴巴开源的一款名为“Simple Extensible Autonomous Transaction Architecture”的分布式事务解决方案,主要用于解决微服务架构下的数据一致性问题。它提供了AT、TCC、Saga、XA等多种事务模式,并通过事务协调器(TC)、事务管理器(TM)和资源管理器(RM)这三个核心组件来管理全局事务的状态和执行。

Seata 的核心组件

  • 事务协调器(Transaction Coordinator,TC):负责维护全局事务和分支事务的状态,并根据全局事务的管理指令驱动事务的提交或回滚。
  • 事务管理器(Transaction Manager,TM):定义了全局事务的范围,负责发起全局事务、提交全局事务或回滚全局事务。
  • 资源管理器(Resource Manager,RM):负责管理各个资源中的分支事务,包括向TC注册分支事务、报告分支事务状态,以及驱动分支事务的提交或回滚。

Seata 的作用

  • 解决分布式事务问题:Seata通过提供高性能、易用的分布式事务服务,解决了跨服务、跨数据库的全局事务一致性难题。
  • 支持多种事务模式:Seata支持多种事务模式,如AT、TCC、Saga和XA模式,以适应不同的业务场景需求。
    总而言之,Seata作为一款开源的分布式事务解决方案,致力于简化微服务架构中的分布式事务处理,让开发者能够更专注于业务逻辑的实现。

更多推荐