零拷贝
直接IO技术
磁盘–>内核缓冲区(内核空间)–>应用程序内存(用户空间)–>Socket 缓冲区(内核缓冲区)–>网络。
内存映射文件技术
磁盘–>内核缓冲区(内核缓冲区)–>Socket缓冲区(内核缓冲区)–>网络
零拷贝技术
磁盘–>内核缓冲区–>网络
Netty和Protobuff
Netty功能强大,内置了多种解码编码器,支持多种协议。
性能高,对比其他主流的NIO框架,Netty的性能最优。
Dubbo、Elasticsearch都采用了Netty,质量得到验证。
Protobuff是一种用于序列化结构化数据的免费开源跨平台数据格式³。它由Google开发,支持多种编程语言,如C++、Java、Python等。Protobuff的优点有:
- 轻量级高效,可以将数据压缩到很小的字节流,节省网络传输和存储空间。
- 可扩展,可以在不破坏兼容性的情况下添加或删除数据字段。
- 无关平台,无关语言,可以在不同的系统和环境中使用。
Hadoop使用netty
hadoop是通过Netty构建性能更高的网络IO层的原理如下:
- Netty是一个基于Java NIO的异步事件驱动的网络应用框架,它提供了统一的API,灵活的事件模型,可定制的线程模型,以及完整的SSL/TLS和StartTLS支持¹。
- Netty使用Reactor模式,将网络IO操作分配给多个EventLoop,每个EventLoop负责处理一个或多个Channel(代表Socket连接或其他IO组件)²。
- Netty使用ChannelPipeline和ChannelHandler机制,将业务逻辑和网络IO操作分离,每个Channel都有一个ChannelPipeline,其中包含多个ChannelHandler,用于处理不同的事件和数据转换²。
- Netty使用ByteBuf作为数据容器,它是一个可读写、自动扩展、引用计数、池化的字节缓冲区,它可以减少内存复制和垃圾回收开销²。
- Netty使用Bootstrap或ServerBootstrap来配置和启动客户端或服务器端的网络应用程序,它们可以设置各种参数,如端口号、线程组、Channel类型、ChannelInitializer、ChannelOption等²。
- Netty支持多种协议栈,如TCP/UDP、HTTP/HTTPS、WebSocket、Google Protocol Buffers等,也可以方便地定制和开发私有协议栈¹。
综上所述,hadoop是通过Netty构建性能更高的网络IO层的,主要是利用了Netty的异步事件驱动、Reactor模式、ChannelPipeline机制、ByteBuf优化、Bootstrap配置和协议栈支持等特点。hadoop使用Netty作为其RPC框架Avro的基础通信组件,实现了跨节点的高效通信。
java网络IO模型
同步与异步
- 异步:read/write 过程托管给操作系统来完成,完成后操作系统会通知(通过回调或者事件)应用网络 IO 程序(其中的线程)来进行后续的处理。
- 同步:read/write 过程由网络 IO 程序(其中的线程)来完成。
BIO
BIO:同步的、阻塞式 IO。在这种模型中,服务器上一个线程处理一次连接,即客户端每发起一个请求,服务端都要开启一个线程专门处理该请求。这种模型对线程量的耗费极大,且线程利用率低,难以承受请求的高并发。BIO 虽然可以使用线程池+等待队列进行优化,避免使用过多的线程,但是依然无法解决线程利用率低的问题。
NIO
NIO:同步的、非阻塞式 IO。在这种模型中,服务器上一个线程处理多个连接,即多个客户端请求都会被注册到多路复用器,多路复用器会轮训这些连接,轮训到连接上有 IO 活动就进行处理。NIO 降低了线程的需求量,提高了线程的利用率。Netty 就是基于 NIO 的。NIO 是面向缓冲区编程的,从缓冲区读取数据的时候游标在缓冲区中是可以前后移动的,这就增加了数据处理的灵活性。这和面向流的 BIO 只能顺序读取流中数据有很大的不同。
IO多路复用方法
- select:服务器有三种文件描述符:writefds,readfds和exceptdfs。select会阻塞并监视这3类文件描述符,通过遍历fdset整个数组来找到就绪的描述符fd,然后进行IO操作。
- poll:和select一样也是轮询+遍历,但是没有最大文件描述符限制。
- epoll : 使用时间通知机制来触发,epoll_create()在Linux内核中申请一个B+树结构的文件系统,然后epoll+ctl()注册fd,一旦fd就绪就会通知callback回调机制,epoll_wait轮询所有callback,完成对应IO操作。在水平触发下,只要fd有数据可读,epoll_wait都会返回它的事件。在边缘触发的模式下,它只会提示一次,直到下次有数据流入。
为什么Netty是异步的
NIO是一种非阻塞的IO模型,它可以让线程在等待IO操作完成时,去处理其他任务,而不是一直阻塞在IO操作上。这样就提高了线程的利用率和系统的吞吐量。
- Netty是一个基于NIO的网络框架,它使用Reactor模式,将网络IO操作分配给多个EventLoop,每个EventLoop负责处理一个或多个Channel(代表Socket连接或其他IO组件)。EventLoop内部维护了一个Selector,用于监听Channel上的事件,如可读、可写、连接等²。
- 当EventLoop检测到某个Channel上有事件发生时,它会调用相应的ChannelHandler来处理事件。ChannelHandler是Netty中的核心概念,它是一个接口,定义了各种回调方法,用于处理不同的事件和数据转换。用户可以自定义ChannelHandler来实现业务逻辑²。
- Netty中的所有IO操作都是异步的,也就是说,当用户调用某个IO操作时,例如write或connect,Netty会立即返回一个ChannelFuture对象,表示该操作的结果。ChannelFuture是一个接口,它提供了一些方法来检查操作是否完成、是否成功、是否取消等。用户可以通过添加Listener来监听ChannelFuture的状态变化,从而获取IO操作的结果²。
- 由于Netty是基于NIO的,所以它可以利用NIO的非阻塞特性,让EventLoop在等待某个Channel上的事件时,去处理其他Channel上的事件,而不是一直阻塞在某个Channel上。这样就实现了异步事件驱动的模型,提高了系统的性能和可扩展性²。
AIO
AIO:异步非阻塞式 IO。在这种模型中,由操作系统完成与客户端之间的 read/write,之后再由操作系统主动通知服务器线程去处理后面的工作,在这个过程中服务器线程不必同步等待 read/write 完成。由于不同的操作系统对 AIO 的支持程度不同,AIO 目前未得到广泛应用。因此本文对 AIO 不做过多描述。
使用 Java NIO 构建的 IO 程序,它的工作模式是:主动轮训 IO 事件,IO 事件发生后程序的线程主动处理 IO 工作,这种模式也叫做 Reactor 模式。使用 Java AIO 构建的 IO 程序,它的工作模式是:将 IO 事件的处理托管给操作系统,操作系统完成 IO 工作之后会通知程序的线程去处理后面的工作,这种模式也叫做 Proactor 模式。
从输入URL到显示页面的过程
- 解析URL:分析使用的传输协议和请求资源路径,同时检查非法字符
- 缓存判断:浏览器会判断所请求的资源是否再缓存里,抉择是否向服务器发起请求(强缓存,协商缓存)
- DNS解析:本地IP缓存>>本地DNS服务器>>跟域名服务器>>顶级域名服务器>>递归请求IP
- 获取MAC地址:从应用层一直将IP发送到数据链路层,通过ip地址和本机子网掩码首先判断是不是子网内的;如果不是再发送到网关,两种都使用ARP协议分别获取目的主机MAC地址和网关MAC地址
- TCP三次握手
- TLS四次握手
- 服务端返回html文件,浏览器进行页面渲染
- TCP四次挥手
OSI七层网络模型
OSI七层模型 | | | |
---|
层级 | 层 | 英文全称 | 常用协议 |
7 | 应用层 | Application Layer | HTTP,FTP,SMTP,TELNET,NNTP |
6 | 表示层 | Presentation Layer | LPP |
5 | 会话层 | Session Layer | SSL,TLS,DAP |
4 | 传输层 | Transport Layer | TCP,UDP |
3 | 网络层 | Network Layer | IP,ICMP,RIP,IGMP,OSPF,路由器它使用IP地址进行寻址和路由选择,实现源IP到目标IP的端到端的无连接数据报服务。 |
2 | 数据链路层 | Data Link Layer | 以太网,网卡,交换机 |
1 | 物理层 | Physical Layer | 光纤,集线器 |
-
应用层:为应用程序提供服务(HTTP,HTTPs,FTP,POP3,SMTP);
-
表示层: 数据格式转化,数据加密;
-
会话层:管理和协调不同主机上各种进程之间的通信,负责建立,管理和终止应用程序之间的会话;
-
传输层:为上层协议提供通信主机间的可靠和透明的数据传输服务,包括处理差错控制和流量控制等;
-
网络层:通过ip寻址来建立两个节点之间的连接,为源端的运输层送来分组,按路由算法选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。
ip:用ip地址来标识Internet主机,使用ARP协议完成IP地址和MAC地址的匹配;如果IP报文大于网络最大传输单元,就再次进行分片;
-
数据链路层:在物理层提供的服务的基础上向网络层提供服务,将网络层数据可靠的传输到相邻节点的目标网络层,包括物理地址寻址,数据帧分割,流量控制,数据检错和重发。他主要依据以太网协议,并有奇偶检验码和循环冗余校验码。
TCP和UDP区别
tcp:面向连接的网络协议(三次握手);可以提供可靠的字节流服务(确认应答,拥塞控制,流量控制,重传机制);提供全双工通信;仅支持点对点数据传输;
udp:面向数据报的传输协议,非面向连接的,不可靠的数据流传输,有单播,广播,多播功能
tcp重传
- 超时重传:发送一个数据以后开启一个计数器,如果数据包或者应答数据包丢失时且时间达到RTO(RTO略大于RTT)时重传。
- 快速重传:当收到3个相同的ACK报文时,在定时器过期之前,重传丢失的报文段。
- 带选择确认的重传:在快速重传的基础上,接收方返回最近收到报文段的序列号范围。
- 重复SACK:来帮助发送方判断是否发生了包失序,ACK丢失,包重复。
tcp报文头
- 序号:当前TCP数据段第一个字节占整个字节流的相对位置;
- 确认号:代表接收端希望接收的数据序号,为上次接收数据报的序号+1
- 数据偏移:TCP首部长度
- SYN:同步标志;ACK,确认序号;FIN:结束序号;URG:紧急序号;PSH:不等缓冲区满直接上交数据给应用层;RST:重置连接
- 窗口:发送方的窗口大小,即接收方目前运行对方发送的数据量(流量控制)
TCP拥塞控制
为了防止发送方的数据填满整个网络,通过拥塞窗口变量(cwnd)维护,其值根据网络拥塞程度动态变化。
- 慢启动:首先将拥塞窗口设置为一个最大报文段的数值,每收到一个ack,其值加一个MSS。同时用一个阈值ssthresh控制。
- 拥塞避免:当收不到ack了,也就是发送方拥堵了,就采用拥塞避免。把ssthresh设置为cwnd的一半,cwnd置1,每经过一个RTT时间cwnd加1,使得cwnd按线性规律缓慢增长。
- 快速重传:此算法发生在以为发生拥堵,但是是报文丢失的情况,就收到三个相同ack,tcp重传
- 快速恢复:当发送方连接收到3个ack,ssthresh减半,cwnd = ssthresh,然后开始启动拥塞避免。
三次握手四次握手
三次握手(可以阻止重复历史连接的初始化,如果旧SYN报文到达,服务端就会发送不对应的ack,客户端就能发现连接建立不对,就不会进入连接状态)
- 发送端主机A向接收端主机B发出SYN端,将序号设置为a,SYN置位;
- 主机B收到请求后,回应SYN+ACK,将序号设置为b,确认号设置为a+1
- 主机A收到主机B的请求后,发送ACK端,确认会话建立,将序号设置为a+1,确认号设置为b+1
四次挥手:(要有Time_Wait等待2MSL,第一是防止历史连接中的数据被后面连接中相同四元组错误接收,第二是保证被动关闭的一方可以正确关闭)
- 主机A想要终止连接,发送序号为p的段,FIN值位;
- 主机B收到主机A发送的FIN之后,发送ACK,确认号为p+1
- 主机B发送序号q,FIN值位
- 主机A收到主机B发送的FIN段,发送ACK,确认号为q+1
什么连接建立需要三次握手,而不是两次握手?
防止失效的连接请求报文段被服务端接收,从而产生错误。
PS:失效的连接请求:若客户端向服务端发送的连接请求丢失,客户端等待应答超时后就会再次发送连接请求,此时,上一个连接请求就是『失效的』。
若建立连接只需两次握手,客户端并没有太大的变化,仍然需要获得服务端的应答后才进入ESTABLISHED状态,而服务端在收到连接请求后就进入ESTABLISHED状态。此时如果网络拥塞,客户端发送的连接请求迟迟到不了服务端,客户端便超时重发请求,如果服务端正确接收并确认应答,双方便开始通信,通信结束后释放连接。此时,如果那个失效的连接请求抵达了服务端,由于只有两次握手,服务端收到请求就会进入ESTABLISHED状态,等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端连接资源。
为什么 A 要先进入TIME-WAIT状态,等待2MSL时间后才进入CLOSED状态
为了保证B能收到A的确认应答。 若A发完确认应答后直接进入CLOSED状态,那么如果该应答丢失,B等待超时后就会重新发送连接释放请求,但此时A已经关闭了,不会作出任何响应,因此B永远无法正常关闭。
HTTP
请求方法
- GET:请求指定的页面信息,并返回实体主体。
- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
- HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
- PUT:从客户端向服务器传送的数据取代指定的文档的内容。
- DELETE:请求服务器删除指定的页面。
GET与PUT的区别
- 都包含请求头请求行,post多了请求body。
- get多用来查询,请求参数放在url中,不会对服务器上的内容产生作用。post用来提交,如把账号密码放入body中。
- GET是直接添加到URL后面的,直接就可以在URL中看到内容,而POST是放在报文内部的,用户无法直接看到。
- GET提交的数据长度是有限制的,因为URL长度有限制,具体的长度限制视浏览器而定。而POST没有。
状态码
1XX- 信息型,服务器收到请求,需要请求者继续操作。
2XX- 成功型,请求成功收到,理解并处理。
3XX - 重定向,需要进一步的操作以完成请求。
4XX - 客户端错误,请求包含语法错误或无法完成请求。
5XX - 服务器错误,服务器在处理请求的过程中发生了错误。
200 OK - 客户端请求成功
301 - 资源(网页等)被永久转移到其它URL
302 - 临时跳转
400 Bad Request - 客户端请求有语法错误,不能被服务器所理解
401 Unauthorized - 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
404 - 请求资源不存在,可能是输入了错误的URL
500 - 服务器内部发生了不可预期的错误
503 Server Unavailable - 服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
缓存机制
- 强制缓存(200):如果请求的资源在本地缓存中还没有过期,就直接使用本地的副本,强制缓存过期时间由Expires和Cache-Control来控制。
- 协商缓存(304):请求从客户端发出,请求前先从本地缓存中取出该缓存的标识,然后向服务器发送验证请求,如果服务器没有更新资源,则让客户端使用本地缓存,否则返回新的资源。使用last-Modified和E-tag。(无论是否过期都要和服务器协商)
缺点
- 请求信息明文传输,容易被窃听截取。
- 数据的完整性未校验,容易被篡改
- 没有验证对方身份,存在冒充危险
HTTP 1.0,1.1,2,3
- Http1.0 引入了POST和HEAD方法,但是每次发送完信息要重新建立联系
- Http1.1引入了OPTION,PUT,DELETE,TRACE和CONNECT,并且最重要引入了持久连接,tcp连接默认不关闭。同时增加了host字段,来区分一个物理服务器上多个虚拟主机。
- Http2.0基于Https,同时引入了压缩头可以协商消除多个发送请求之间重复的部分。可以2.0不再采用纯文本格式而是二进制格式,并且有了服务器推送的新特性。因为把HTTP消息分解成独立的帧,实现了多路复用的交错发送,然后再另一端再重组。
- Http3.0:使用QUIC协议,它基于UDP传输协议,比传统TCP协议更快,也有拥塞控制,加密和双向流
HTTPS
HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL(Secure Socket Layer,安全套接字层)/TLS(Transport Layer Security,传输层安全),默认端口号是443。可以对数据进行加密传输以及身份认证,增加了中间人攻击的成本。在建立连接之前采用非对称加密,通信建立之后采用对称加密。
传输流程
- 首先客户端通过URL访问服务器建立SSL连接。
- 服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端。
- 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
- 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
- 服务器利用自己的私钥解密出会话密钥。
- 服务器利用会话密钥加密与客户端之间的通信。
缺点
- HTTPS协议多次握手,导致页面的加载时间延长近50%;
- HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;
- 申请SSL证书需要钱,功能越强大的证书费用越高。
- SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。
HTTPS和HTTP区别
- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
- HTTP 的端口号是 80,HTTPS 的端口号是 443。
- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
RPC
RPC与HTTP的区别
传输协议
RPC,可以基于TCP协议,也可以基于HTTP协议。HTTP,基于HTTP协议
传输效率
RPC,使用自定义的TCP协议,可以让请求报⽂体积更⼩,或者使⽤HTTP2协议,也可以很好的减少报⽂的体积,提⾼传输效率
HTTP,如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为⼀个RPC来使⽤的,这时标准RPC框架更多的是服务治理
性能消耗
RPC,可以基于thrift实现⾼效的⼆进制传输
HTTP,⼤部分是通过json来实现的,字节⼤⼩和序列化耗时都⽐thrift要更消耗性能
原因:thrift使用二进制格式进行数据传输,比json更高效和紧凑,但也更难阅读和调试。json使用文本格式进行数据传输,比thrift更易读和通用,但也更占用空间和性能。
负载均衡
RPC,基本都⾃带了负载均衡策略。
HTTP,需要配置Nginx,HAProxy来实现
服务治理(下游服务新增,重启,下线时如何不影响上游调用者)
RPC,能做到⾃动通知,不影响上游
HTTP,需要事先通知,修改Nginx/HAProxy配置
RPC负载均衡
RPC的负载均衡是指在RPC框架中,服务调用方如何从多个服务提供方中选择一个合适的节点来处理请求的问题。
- 随机策略是指按照一定的概率随机选择一个服务节点,可以实现简单的负载均衡,但不考虑服务节点的实际负载情况。
- 轮询策略是指按照顺序依次选择一个服务节点,可以保证每个节点都有相同的请求量,但也不考虑服务节点的实际负载情况。
- 权重策略是指根据每个服务节点的预设权重值来分配请求量,可以实现按照比例分配流量,但需要手动配置权重值,并且权重值不会随着服务节点的实际负载情况而变化。
- Hash策略是指根据请求的参数或者IP地址等信息计算一个哈希值,然后根据哈希值选择一个服务节点,可以实现相同请求总是落在同一个节点上,但也不考虑服务节点的实际负载情况。
- 除了这些基本的策略,还有一些自适应的负载均衡策略,它们可以根据服务节点的实际负载情况来动态调整权重或者选择最优的节点。这些策略需要收集和分析服务节点的指标数据,如CPU负载、内存占用率、请求耗时等,并且根据一定的算法来计算出一个分数或者权重,然后根据分数或者权重来选择合适的服务节点。这些策略可以更好地利用集群资源,提高系统性能和可用性。
SpringCloud和Dubbo的区别
生态环境不同:SpringCloud依托于Spring平台,具备更加完善的生态体系;而Dubbo一开始只是做RPC远程调用,生态相对匮乏,现在逐渐丰富起来。
调用方式:SpringCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。但调用时采用Netty的NIO方式,性能较好。
组件差异比较多,例如SpringCloud注册中心一般用Eureka,而Dubbo用的是Zookeeper。
SpringCloud:Spring公司开源的微服务框架,SpirngCloud 定位为微服务架构下的一站式解决方案。Dubbo:阿里巴巴开源的RPC框架,Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。
Euraka和Zookeeper的区别
从集群设计来看:Eureka集群各节点平等,没有主从关系,因此可能出现数据不一致情况;ZK为了满足一致性,必须包含主从关系,一主多从。集群无主时,不对外提供服务
CAP原则来看:Eureka满足AP原则,为了保证整个服务可用性,牺牲了集群数据的一致性;而Zookeeper满足CP原则,为了保证各节点数据一致性,牺牲了整个服务的可用性。
服务拉取方式来看:Eureka采用的是服务主动拉取策略,消费者按照固定频率(默认30秒)去Eureka拉取服务并缓存在本地;ZK中的消费者首次启动到ZK订阅自己需要的服务信息,并缓存在本地。然后监听服务列表变化,以后服务变更ZK会推送给消费者。
Dubbo框架
节点介绍
Provider: 暴露服务的服务提供方
Consumer: 调用远程服务的服务消费方
Registry: 服务注册与发现的注册中心
Monitor: 统计服务的调用次数和调用时间的监控中心
Container: 服务运行容器
调用关系介绍
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟8. 发送一次统计数据到监控中心。
宕机处理
注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
动态代理
根据网上搜索结果,我为您介绍一下java在RPC实现中使用的动态代理:
- 动态代理是一种设计模式,它可以在运行时动态地创建一个对象的代理,而不需要事先为每个对象编写代理类。
- 动态代理在RPC实现中的作用是生成客户端存根(client stub)和服务端存根(server stub),这两个存根负责将方法调用转换为网络通信,从而实现远程过程调用的透明性¹²。
- java提供了两种方式来实现动态代理:基于接口的JDK动态代理和基于类的CGLIB动态代理³。
- JDK动态代理是通过实现java.lang.reflect.InvocationHandler接口和使用java.lang.reflect.Proxy类来创建代理对象的,它要求被代理的对象必须实现一个或多个接口³。
- CGLIB动态代理是通过继承被代理对象的类和使用net.sf.cglib.proxy.Enhancer类来创建代理对象的,它不要求被代理的对象实现接口,但不能代理final类或方法³。