Netty 是一个 NIO client-server(客户端服务器)框架,使用 Netty 能够快速开发网络应用,例如服务器和客户 端协议。 Netty 提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。 Netty 的内部实现时很复杂的,可是 Netty 提供了简单易用的 api 从网络处理代码中解耦业务逻辑。 Netty 是彻底基 于 NIO 实现的,因此整个 Netty 都是异步的。
简单点说就是Netty提供了一个简单,间接的方法来操做网络之间的通信。linux
互联网行业:随着网站规模的不断扩大,系统并发访问量也愈来愈高,传统基于 Tomcat 等 Web 容器的垂直架构已经没法知足需求,须要拆分应用进行服务化,以提升开发和维护效率。从组网状况看,垂直的架构拆分以后,系统采用分布式部署,各个节点之间 须要远程服务调用,高性能的 RPC 框架必不可少,Netty 做为异步高性能的通讯框架,每每做为基础通讯组件被这些 RPC 框架使用。
典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通讯,Dubbo 协议默认使用 Netty 做为基础通讯组件,用于实现各进程节点之间的内部通讯。它的架构图以下:
spring
其中,服务提供者和服务消费者之间,服务提供者、服务消费者和性能统计节点之间使用 Netty 进行异步/同步通讯。
除了 Dubbo 以外,淘宝的消息中间件 RocketMQ 的消息生产者和消息消费者之间,也采用 Netty 进行高性能、异步通讯。数据库
除了阿里系和淘宝系以外,不少其它的大型互联网公司或者电商内部也已经大量使用 Netty 构建高性能、分布式的网络服务器。
游戏行业:不管是手游服务端、仍是大型的网络游戏,Java 语言获得了愈来愈普遍的应用。Netty 做为高性能的基础通讯组件,它自己提供了 TCP/UDP 和 HTTP 协议栈,很是方便定制和开发私有协议栈。帐号登录服务器、地图服务器之间能够方便的经过 Netty 进行高性能的通讯,架构示意图以下:apache
大数据领域:经典的 Hadoop 的高性能通讯和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨节点通讯,它的 Netty Service 基于 Netty 框架二次封装实现。
大数据计算每每采用多个计算节点和一个/N个汇总节点进行分布式部署,各节点之间存在海量的数据交换。因为 Netty 的综合性能是目前各个成熟 NIO 框架中最高的,所以,每每会被选中用做大数据各节点间的通讯。
企业软件:企业和 IT 集成须要 ESB,Netty 对多协议支持、私有协议定制的简洁性和高性能是 ESB RPC 框架的首选通讯组件。事实上,不少企业总线厂商会选择 Netty 做为基础通讯组件,用于企业的 IT 集成。
通讯行业:Netty 的异步高性能、高可靠性和高成熟度的优势,使它在通讯行业获得了大量的应用。编程
首先咱们看下传统基于同步阻塞 IO(BIO)的线程模型图:后端
由上图咱们能够看出,传统的同步阻塞 IO 通讯存在以下几个问题:
线程模型存在致命缺陷:一链接一线程的模型致使服务端没法承受大量客户端的并发链接;
性能差:频繁的线程上下文切换致使 CPU 利用效率不高;api
可靠性差:因为全部的 IO 操做都是同步的,因此业务线程只要进行 IO 操做,也会存在被同步阻塞的风险,这会致使系统的可靠性差,依赖外部组件的处理能力和网络的状况。浏览器
采用非阻塞 IO(NIO)以后,同步阻塞 IO 的三个缺陷都将迎刃而解:
Nio 采用 Reactor 模式,一个 Reactor 线程聚合一个多路复用器 Selector,它能够同时注册、监听和轮询成百上千个 Channel,一个 IO 线程能够同时并发处理N个客户端链接,线程模型优化为1:N(N < 进程可用的最大句柄数)或者 M : N (M一般为 CPU 核数 + 1, N < 进程可用的最大句柄数);缓存
因为 IO 线程总数有限,不会存在频繁的 IO 线程之间上下文切换和竞争,CPU 利用率高;
全部的 IO 操做都是异步的,即便业务线程直接进行 IO 操做,也不会被同步阻塞,系统再也不依赖外部的网络环境和外部应用程序的处理性能。安全
因为切换到 NIO 编程以后能够为系统带来巨大的可靠性、性能提高,因此,目前采用 NIO 进行通讯已经逐渐成为主流。
咱们经过 JDK NIO 服务端和客户端的工做时序图来回答下这个问题:
即使抛开代码和 NIO 类库复杂性不谈,一个高性能、高可靠性的 NIO 服务端开发和维护成本都是很是高的,开发者须要具备丰富的 NIO 编程经验和网络维护经验,不少时候甚至须要经过抓包来定位问题。也许开发出一套 NIO 程序须要 1 个月,可是它的稳定极可能须要 1 年甚至更长的时间,这也就是为何我不建议直接使用 JDK NIO 类库进行通讯开发的一个重要缘由。
下面再一块儿看下 JDK NIO 客户端的通讯时序图:它一样很是复杂。
基于IO的经典同步堵塞模型:
经典的IO模型也就是传统的服务器端同步阻塞I/O处理(也就是BIO,Blocking I/O)的经典编程模型,当咱们每获得一个新的链接时,就会开启一个线程来处理这个链接的任务。之因此使用多线程,主要缘由在于socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个链接在处理I/O的时候,系统是阻塞的,若是是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可让CPU去处理更多的事情。
所以这个模型最本质的问题在于,严重依赖于线程。但线程是很”贵”的资源,主要表如今:
线程的建立和销毁成本很高,在Linux这样的操做系统中,线程本质上就是一个进程。建立和销毁都是重量级的系统函数。
线程自己占用较大内存,像Java的线程栈,通常至少分配512K~1M的空间,若是系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
线程的切换成本是很高的。操做系统发生线程切换的时候,须要保留线程的上下文,而后执行系统调用。若是线程数太高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现每每是系统load偏高、CPU sy使用率特别高(超过20%以上),致使系统几乎陷入不可用的状态。
NIO是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,并且已经被愈来愈多地应用到大型应用服务器,成为解决高并发与大量链接、I/O处理问题的有效方式。
不使用NIO的缘由:
为何选择Netty
与Mina相比有什么优点?
一、都是Trustin Lee的做品,Netty更晚;
二、Mina将内核和一些特性的联系过于紧密,使得用户在不须要这些特性的时候没法脱离,相比下性能会有所降低,Netty解决了这个设计问题;
三、Netty的文档更清晰,不少Mina的特性在Netty里都有;
四、Netty更新周期更短,新版本的发布比较快;
五、它们的架构差异不大,Mina靠apache生存,而Netty靠jboss,和jboss的结合度很是高,Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi);
六、Netty比Mina使用起来更简单,Netty里你能够自定义的处理upstream events 或/和 downstream events,可使用decoder和encoder来解码和编码发送内容;
七、Netty和Mina在处理UDP时有一些不一样,Netty将UDP无链接的特性暴露出来;而Mina对UDP进行了高级层次的抽象,能够把UDP当成”面向链接”的协议,而要Netty作到这一点比较困难。
在此我向你们推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
一、BIO(同步阻塞IO)
使用ServerSocket绑定IP地址和监听端口,客户端发起链接,经过三次握手创建链接,用socket来进行通讯,经过输入输出流的方式来进行同步阻塞的通讯
每次客户端发起链接请求,都会启动一个线程
线程数量:客户端并发访问数为1:1,因为线程是JAVA虚拟机中很是宝贵的资源,一旦线程数急剧增长,系统性能会急剧降低,致使线程栈溢出,建立新的线程失败,并最终致使宕机
因此在JDK1.4以前,人们想到了一种方法,即PIO方式
二、PIO(伪异步阻塞IO)
使用线程池来处理客户端的请求
客户端个数:线程池最大线程数=M:N,其中M远大于N
在read和write的时候,仍是IO阻塞的,只是把每一个线程交由线程池来控制管理
三、NIO(异步阻塞IO)
用NIO方式处理IO
使用多路复用器Selector来轮询每一个通道Channel,当通道中有事件时就通知处理,不会阻塞
使用至关复杂
四、AIO(真正的异步非阻塞IO)
NIO2.0引入了新的异步通道的概念,不须要使用多路复用器(Selector)对注册通道进行轮询便可实现异步读写,从而简化了NIO编程模型
一、构建事件处理池
二、使用引导程序关联事件处理池、通道、事件处理器
三、绑定端口服务
四、等待操做完成
五、关闭事件处理池
几种IO的功能和特性对比
按照书上的例子码了一遍:
服务端:
服务端处理器:
客户端:
客户端处理器:
Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,做为一个异步NIO框架,Netty的全部IO操做都是异步非阻塞的,经过Future-Listener机制,用户能够方便的主动获取或者经过通知机制得到IO操做结果。
做为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通讯行业等得到了普遍的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是数一数二的,它已经获得成百上千的商用项目验证,例如Hadoop的RPC框架avro使用Netty做为底层通讯框架;不少其余业界主流的RPC框架,也使用Netty来构建高性能的异步通讯能力。
经过对Netty的分析,咱们将它的优势总结以下:
1.API使用简单,开发门槛低;
2.功能强大,预置了多种编解码功能,支持多种主流协议;
3.定制能力强,能够经过ChannelHandler对通讯框架进行灵活地扩展;
4.性能高,经过与其余业界主流的NIO框架对比,Netty的综合性能最优;
5.成熟、稳定,Netty修复了已经发现的全部JDK NIO BUG,业务开发人员不须要再为NIO的BUG而烦恼;
6.社区活跃,版本迭代周期短,发现的BUG能够被及时修复,同时,更多的新功能会加入;
经历了大规模的商业应用考验,质量获得验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业获得成功商用,证实了它已经彻底可以知足不一样行业的商业应用了。
在此我向你们推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
Netty 采用了比较典型的三层网络架构进行设计,逻辑架构图以下所示:
第一层:Reactor 通讯调度层,它由一系列辅助类完成,包括 Reactor 线程 NioEventLoop 以及其父类、NioSocketChannel/NioServerSocketChannel 以及其父 类、ByteBuffer 以及由其衍生出来的各类 Buffer、Unsafe 以及其衍生出的各类内 部类等。该层的主要职责就是监听网络的读写和链接操做,负责将网络层的数据 读取到内存缓冲区中,而后触发各类网络事件,例如链接建立、链接激活、读事 件、写事件等等,将这些事件触发到 PipeLine 中,由 PipeLine 充当的职责链来 进行后续的处理。
第二层:职责链 PipeLine,它负责事件在职责链中的有序传播,同时负责动态的 编排职责链,职责链能够选择监听和处理本身关心的事件,它能够拦截处理和向 后/向前传播事件,不一样的应用的 Handler 节点的功能也不一样,一般状况下,每每 会开发编解码 Hanlder 用于消息的编解码,它能够将外部的协议消息转换成内部 的 POJO 对象,这样上层业务侧只须要关心处理业务逻辑便可,不须要感知底层 的协议差别和线程模型差别,实现了架构层面的分层隔离。
第三层:业务逻辑处理层,能够分为两类:
1.纯粹的业务逻辑 处理,例如订单处理。
2.应用层协议管理,例如HTTP协议、FTP协议等。
接下来,我从影响通讯性能的三个方面(I/O模型、线程调度模型、序列化方式)来谈谈Netty的架构。
传统同步阻塞I/O模式以下图所示:
它的弊端有不少:
1.性能问题:一链接一线程模型致使服务端的并发接入数和系统吞吐量受到极大限制;
2.可靠性问题:因为I/O操做采用同步阻塞模式,当网络拥塞或者通讯对端处理缓慢会致使I/O线程被挂住,阻塞时间没法预测;
3.可维护性问题:I/O线程数没法有效控制、资源没法有效共享(多线程并发问题),系统可维护性差;
几种I/O模型的功能和特性对比:
Netty的I/O模型基于非阻塞I/O实现,底层依赖的是JDK NIO框架的Selector。
Selector提供选择已经就绪的任务的能力。简单来说,Selector会不断地轮询注册在其上的Channel,若是某个Channel上面有新的TCP链接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,而后经过SelectionKey能够获取就绪Channel的集合,进行后续的I/O操做。
一个多路复用器Selector能够同时轮询多个Channel,因为JDK1.5_update10版本(+)使用了epoll()代替传统的select实现,因此它并无最大链接句柄1024/2048的限制。这也就意味着只须要一个线程负责Selector的轮询,就能够接入成千上万的客户端,这确实是个很是巨大的技术进步。
使用非阻塞I/O模型以后,Netty解决了传统同步阻塞I/O带来的性能、吞吐量和可靠性问题。
经常使用的Reactor线程模型有三种,分别以下:
1.Reactor单线程模型:Reactor单线程模型,指的是全部的I/O操做都在同一个NIO线程上面完成。对于一些小容量应用场景,可使用单线程模型。
2.Reactor多线程模型:Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理I/O操做。主要用于高并发、大业务量场景。
3.主从Reactor多线程模型:主从Reactor线程模型的特色是服务端用于接收客户端链接的再也不是个1个单独的NIO线程,而是一个独立的NIO线程池。利用主从NIO线程模型,能够解决1个服务端监听线程没法有效处理全部客户端链接的性能不足问题。
事实上,Netty的线程模型并不是固定不变,经过在启动辅助类中建立不一样的EventLoopGroup实例并经过适当的参数配置,就能够支持上述三种Reactor线程模型。
在大多数场景下,并行多线程处理能够提高系统的并发性能。可是,若是对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会致使性能的降低。为了尽量的避免锁竞争带来的性能损耗,能够经过串行化设计,即消息的处理尽量在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。
为了尽量提高性能,Netty采用了串行无锁化设计,在I/O线程内部进行串行操做,避免多线程竞争致使的性能降低。表面上看,串行化设计彷佛CPU利用率不高,并发程度不够。可是,经过调整NIO线程池的线程参数,能够同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工做线程模型性能更优。
序列化方式
影响序列化性能的关键因素总结以下:
1.序列化后的码流大小(网络带宽占用)
2.序列化&反序列化的性能(CPU资源占用)
3.并发调用的性能表现:稳定性、线性增加、偶现的时延毛刺等
对Java序列化和二进制编码分别进行性能测试,编码100万次,测试结果代表:Java序列化的性能只有二进制编码的6.17%左右。
Netty默认提供了对Google Protobuf的支持,经过扩展Netty的编解码接口,用户能够实现其它的高性能序列化框架,例如Thrift的压缩二进制编解码框架。
不一样的应用场景对序列化框架的需求也不一样,对于高性能应用场景Netty默认提供了Google的Protobuf二进制序列化框架,若是用户对其它二进制序列化框架有需求,也能够基于Netty提供的编解码框架扩展实现。
Netty架构剖析之可靠性
Netty面临的可靠性挑战:
1.做为RPC框架的基础网络通讯框架,一旦故障将致使没法进行远程服务(接口)调用。
2.做为应用层协议的基础通讯框架,一旦故障将致使应用协议栈没法正常工做。
3.网络环境复杂(例如手游或者推送服务的GSM/3G/WIFI网络),故障不可避免,业务却不能中断。
从应用场景看,Netty是基础的通讯框架,一旦出现Bug,轻则须要重启应用,重则可能致使整个业务中断。它的可靠性会影响整个业务集群的数据通讯和交换,在当今以分布式为主的软件架构体系中,通讯中断就意味着整个业务中断,分布式架构下对通讯的可靠性要求很是高。
从运行环境看,Netty会面临恶劣的网络环境,这就要求它自身的可靠性要足够好,平台可以解决的可靠性问题须要由Netty自身来解决,不然会致使上层用户关注过多的底层故障,这将下降Netty的易用性,同时增长用户的开发和运维成本。
Netty的可靠性是如此重要,它的任何故障均可能会致使业务中断,蒙受巨大的经济损失。所以,Netty在版本的迭代中不断加入新的可靠性特性来知足用户日益增加的高可靠和健壮性需求。
Netty提供的心跳检测机制分为三种:
1.读空闲,链路持续时间t没有读取到任何消息;
2.写空闲,链路持续时间t没有发送任何消息;
3.读写空闲,链路持续时间t没有接收或者发送任何消息。
当网络发生单通、链接被防火墙Hang住、长时间GC或者通讯线程发生非预期异常时,会致使链路不可用且不易被及时发现。特别是异常发生在凌晨业务低谷期间,当早晨业务高峰期到来时,因为链路不可用会致使瞬间的大批量业务失败或者超时,这将对系统的可靠性产生重大的威胁。
从技术层面看,要解决链路的可靠性问题,必须周期性的对链路进行有效性检测。目前最流行和通用的作法就是心跳检测。
心跳检测机制分为三个层面:
1.TCP层面的心跳检测,即TCP的Keep-Alive机制,它的做用域是整个TCP协议栈;
2.协议层的心跳检测,主要存在于长链接协议中。例如SMPP协议;
3.应用层的心跳检测,它主要由各业务产品经过约定方式定时给对方发送心跳消息实现。
心跳检测的目的就是确认当前链路可用,对方活着而且可以正常接收和发送消息。作为高可靠的NIO框架,Netty也提供了基于链路空闲的心跳检测机制:
1.读空闲,链路持续时间t没有读取到任何消息;
2.写空闲,链路持续时间t没有发送任何消息;
3.读写空闲,链路持续时间t没有接收或者发送任何消息。
流量整形(Traffic Shaping)是一种主动调整流量输出速率的措施。Netty的流量整形有两个做用:
1.防止因为上下游网元性能不均衡致使下游网元被压垮,业务流程中断;
2.防止因为通讯模块接收消息过快,后端业务线程处理不及时致使的“撑死”问题。
流量整形的原理示意图以下:
流量整形(Traffic Shaping)是一种主动调整流量输出速率的措施。一个典型应用是基于下游网络结点的TP指标来控制本地流量的输出。流量整形与流量监管的主要区别在于,流量整形对流量监管中须要丢弃的报文进行缓存——一般是将它们放入缓冲区或队列内,也称流量整形(Traffic Shaping,简称TS)。当令牌桶有足够的令牌时,再均匀的向外发送这些被缓存的报文。流量整形与流量监管的另外一区别是,整形可能会增长延迟,而监管几乎不引入额外的延迟。
Netty支持两种流量整形模式:
1.全局流量整形:全局流量整形的做用范围是进程级的,不管你建立了多少个Channel,它的做用域针对全部的Channel。用户能够经过参数设置:报文的接收速率、报文的发送速率、整形周期。
2.链路级流量整形:单链路流量整形与全局流量整形的最大区别就是它以单个链路为做用域,能够对不一样的链路设置不一样的整形策略。
在此我向你们推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
Netty的优雅停机三部曲:
1.再也不接收新消息
2.退出前的预处理操做
3.资源的释放操做
Java的优雅停机一般经过注册JDK的ShutdownHook来实现,当系统接收到退出指令后,首先标记系统处于退出状态,再也不接收新的消息,而后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。
一般优雅退出须要有超时控制机制,例如30S,若是到达超时时间仍然没有完成退出前的资源回收等操做,则由停机脚本直接调用kill -9 pid,强制退出。
在实际项目中,Netty做为高性能的异步NIO通讯框架,每每用做基础通讯框架负责各类协议的接入、解析和调度等,例如在RPC和分布式服务框架中,每每会使用Netty做为内部私有协议的基础通讯框架。 当应用进程优雅退出时,做为通讯框架的Netty也须要优雅退出,主要缘由以下:
尽快的释放NIO线程、句柄等资源;
若是使用flush作批量消息发送,须要将积攒在发送队列中的待发送消息发送完成;
正在write或者read的消息,须要继续处理;
设置在NioEventLoop线程调度器中的定时任务,须要执行或者清理。
Netty架构剖析之安全性
Netty面临的安全挑战:
对第三方开放
做为应用层协议的基础通讯框架
安全威胁场景分析:
对第三方开放的通讯框架:若是使用Netty作RPC框架或者私有协议栈,RPC框架面向非授信的第三方开放,例如将内部的一些能力经过服务对外开放出去,此时就须要进行安全认证,若是开放的是公网IP,对于安全性要求很是高的一些服务,例如在线支付、订购等,须要经过SSL/TLS进行通讯。
应用层协议的安全性。做为高性能、异步事件驱动的NIO框架,Netty很是适合构建上层的应用层协议。因为绝大多数应用层协议都是公有的,这意味着底层的Netty须要向上层提供通讯层的安全传输功能。
SSL/TLS
Netty安全传输特性:
1.支持SSL V2和V3
2.支持TLS
3.支持SSL单向认证、双向认证和第三方CA认证。
SSL单向认证流程图以下:
Netty经过SslHandler提供了对SSL的支持,它支持的SSL协议类型包括:SSL V二、SSL V3和TLS。
单向认证:单向认证,即客户端只验证服务端的合法性,服务端不验证客户端。
双向认证:与单向认证不一样的是服务端也须要对客户端进行安全认证。这就意味着客户端的自签名证书也须要导入到服务端的数字证书仓库中。
CA认证:基于自签名的SSL双向认证,只要客户端或者服务端修改了密钥和证书,就须要从新进行签名和证书交换,这种调试和维护工做量是很是大的。所以,在实际的商用系统中每每会使用第三方CA证书颁发机构进行签名和验证。咱们的浏览器就保存了几个经常使用的CA_ROOT。每次链接到网站时只要这个网站的证书是通过这些CA_ROOT签名过的。就能够经过验证了。
可扩展的安全特性
经过Netty的扩展特性,能够自定义安全策略:
1.IP地址黑名单机制
2.接入认证
3.敏感信息加密或者过滤机制
IP地址黑名单是比较经常使用的弱安全保护策略,它的特色就是服务端在与客户端通讯的过程当中,对客户端的IP地址进行校验,若是发现对方IP在黑名单列表中,则拒绝与其通讯,关闭链路。
接入认证策略很是多,一般是较强的安全认证策略,例如基于用户名+密码的认证,认证内容每每采用加密的方式,例如Base64+AES等。
Netty架构剖析之扩展性
经过Netty的扩展特性,能够自定义安全策略:
1.线程模型可扩展
2.序列化方式可扩展
3.上层协议栈可扩展
4.提供大量的网络事件切面,方便用户功能扩展
Netty的架构可扩展性设计理念以下:
5.判断扩展点,事先预留相关扩展接口,给用户二次定制和扩展使用;
6.主要功能点都基于接口编程,方便用户定制和扩展。