diff --git a/JVM.md b/JVM.md index b06a1df..83b22b2 100644 --- a/JVM.md +++ b/JVM.md @@ -4,6 +4,10 @@ [TOC] + + +https://mp.weixin.qq.com/s?__biz=MzI4NjI1OTI4Nw==&mid=2247489183&idx=1&sn=02ab3551c473bd2c8429862e3689a94b&chksm=ebdef7a7dca97eb17194c3d935c86ade240d3d96bbeaf036233a712832fb94af07adeafa098b&mpshare=1&scene=23&srcid=0812OQ78gD47QJEguFGixUVa&sharer_sharetime=1628761112690&sharer_shareid=0f9991a2eb945ab493c13ed9bfb8bf4b#rd + # JVM ## JVM常量池 diff --git a/Middleware.md b/Middleware.md index 9849d65..0b45b2d 100644 --- a/Middleware.md +++ b/Middleware.md @@ -2775,6 +2775,434 @@ Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。Apache ![ApacheDubbo](images/Middleware/ApacheDubbo.jpg) +## RPC + +Remote Procedure Call Protocol 既 **远程过程调用**,一种能让我们**像调用本地服务一样调用远程服务**,可以让调用者对网络通信这些细节**无感知**,比如服务消费方在执行 helloWorldService.sayHello("sowhat") 时,实质上调用的是远端的服务。这种方式其实就是**RPC**,**RPC**思想在各大互联网公司中被广泛使用,如阿里巴巴的**dubbo**、当当的**Dubbox** 、Facebook 的 **thrift**、Google 的**grpc**、Twitter的**finagle**等。 + + + +## 框架设计 + +### Dubbo 简介 + +Dubbo 是阿里巴巴研发开源工具,主要分为2.6.x 跟 2.7.x 版本。是一款分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案,可以和Spring 框架无缝集成,它提供了6大核心能力: + +- 面向接口代理的高性能RPC调用 +- 智能容错和负载均衡 +- 服务自动注册和发现 +- 高度可扩展能力 +- 运行期流量调度 +- 可视化的服务治理与运维 + + + +**调用过程**: + +- 服务提供者 **Provider** 启动然后向 **Registry** 注册自己所能提供的服务。 +- 服务消费者 Consumer 向**Registry**订阅所需服务,**Consumer** 解析**Registry**提供的元信息,从服务中通过负载均衡选择 **Provider**调用。 +- 服务提供方 **Provider** 元数据变更的话**Registry**会把变更推送给**Consumer**,以此保证**Consumer**获得最新可用信息。 + + + +**注意点**: + +- **Provider** 跟 **Consumer** 在内存中记录调用次数跟时间,定时发送统计数据到**Monitor**,发送的时候是**短**连接。 +- **Monitor** 跟 **Registry** 是可选的,可直接在配置文件中写好,**Provider** 跟 **Consumer**进行直连。 +- **Monitor** 跟 **Registry** 挂了也没事, **Consumer** 本地缓存了 **Provider** 信息。 +- **Consumer** 直接调用 **Provider** 不会经过 **Registry**。**Provider**、**Consumer**这俩到 **Registry**之间是长连接。 + + + +### Dubbo框架分层 + +![Dubbo框架分层](images/Middleware/Dubbo框架分层.jpg) + +如上图,总的而言 Dubbo 分为三层。 + +- **Busines**层:由用户自己来提供接口和实现还有一些配置信息。 +- **RPC**层:真正的RPC调用的核心层,封装整个RPC的调用过程、负载均衡、集群容错、代理。 +- **Remoting**层:对网络传输协议和数据转换的封装。 + + + +如果每一层再细分下去,一共有十层: + +1. 接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现。 +2. 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心初始化配置。 +3. 服务代理层(Proxy):服务接口透明代理,Provider跟Consumer都生成代理类,使得服务接口透明,代理层实现服务调用跟结果返回。 +4. 服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心。 +5. 路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBlancce。 +6. 监控层(Monitor):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor 和 MonitorService。 +7. 远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker 和 Exporter。 +8. 信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer。 +9. 网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec。 +10. 数据序列化层(Serialize):可复用的一些工具,扩展接口为 Serialization、ObjectInput、ObjectOutput 和 ThreadPool。 + + + +他们之间的调用关系直接看下面官网图即可: + +![Dubbo调用关系](images/Middleware/Dubbo调用关系.jpg) + + + +## SPI机制 + +**Dubbo** 采用 **微内核设计** + **SPI** 扩展技术来搭好核心框架,同时满足用户定制化需求。这里重点说下**SPI**。 + +### SPI 含义 + +**SPI** 全称为 **Service Provider Interface**,是一种**服务发现机制**。它约定在**ClassPath**路径下的**META-INF/services**文件夹查找文件,自动加载文件里所定义的类。 + + + +### Java SPI缺点 + +- 不能按需加载,Java SPI在加载扩展点的时候,会一次性加载所有可用的扩展点,很多是不需要的,会浪费系统资源。 +- 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。 +- 不支持AOP与依赖注入,JAVA SPI可能会丢失加载扩展点异常信息,导致追踪问题很困难。 + + + +### Dubbo SPI + +JDK自带的不好用Dubbo 就自己实现了一个 SPI,该SPI **可以通过名字实例化指定的实现类,并且实现了 IOC 、AOP 与 自适应扩展 SPI** 。 + +```properties +key = com.sowhat.value +``` + +Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。 + +- META-INF/services/ :该目录下 SPI 配置文件是为了用来兼容 Java SPI +- META-INF/dubbo/ :该目录存放用户自定义的 SPI 配置文件 +- META-INF/dubbo/internal/ :该目录存 Dubbo 内部使用的 SPI 配置文件 + + + +### Dubbo SPI源码追踪 + +**ExtensionLoader.getExtension** 方法的整个思路是 查找缓存是否存在,不存在则读取SPI文件,通过反射创建类,然后设置依赖注入这些东西,有包装类就包装下,执行流程如下图所示: + +![DubboSPI源码追踪](images/Middleware/DubboSPI源码追踪.png) + +### injectExtension IOC + +查找 set 方法,根据参数找到依赖对象则注入。 + + + +### WrapperClass AOP + +包装类,Dubbo帮你自动包装,只需某个扩展类的构造函数只有一个参数,并且是扩展接口类型,就会被判定为包装类。 + + + +### Activate + +Active 有三个属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL参数中出现才会被激活,order 表示实现类的顺序。 + + + +### Adaptive自适应扩展 + +**需求**:根据配置来进行 SPI 扩展的加载后不想在启动的时候让扩展被加载,想根据请求时候的参数来动态选择对应的扩展。**实现**:Dubbo用代理机制实现了自适应扩展,为用户想扩展的接口 通过JDK 或者 Javassist 编译生成一个代理类,然后通过反射创建实例。实例会根据本来方法的请求参数得知需要的扩展类,然后通过 ExtensionLoader.getExtensionLoader(type.class).getExtension(name)来获取真正的实例来调用,看个官网样例。 + +```java +public interface WheelMaker { + Wheel makeWheel(URL url); +} +// WheelMaker 接口的自适应实现类 +public class AdaptiveWheelMaker implements WheelMaker { + public Wheel makeWheel(URL url) { + if (url == null) { + throw new IllegalArgumentException("url == null"); + } + // 1. 调用 url 的 getXXX 方法获取参数值 + String wheelMakerName = url.getParameter("Wheel.maker"); + if (wheelMakerName == null) { + throw new IllegalArgumentException("wheelMakerName == null"); + } + // 2. 调用 ExtensionLoader 的 getExtensionLoader 获取加载器 + // 3. 调用 ExtensionLoader 的 getExtension 根据从url获取的参数作为类名称加载实现类 + WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName); + // 4. 调用实现类的具体方法实现调用。 + return wheelMaker.makeWheel(URL url); + } +} +``` + +查看Adaptive注解源码可知该注解可用在**类**或**方法**上,Adaptive 注解在类上或者方法上有不同的实现逻辑。 + + + +#### Adaptive注解在类上 + +Adaptive 注解在类上时,Dubbo 不会为该类生成代理类,Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加载逻辑由人工编码完成,这不是我们关注的重点。 + + + +#### Adaptive注解在方法上 + +Adaptive注解在方法上时,Dubbo则会为该方法生成代理逻辑,表示拓展的加载逻辑需由框架自动生成,实现机制如下: + +- 加载标注有 @Adaptive 注解的接口,如果不存在,则不支持 Adaptive 机制 +- 为目标接口按照一定的模板生成子类代码,并且编译生成的代码,然后通过反射生成该类的对象 +- 结合生成的对象实例,通过传入的URL对象,获取指定key的配置,然后加载该key对应的类对象,最终将调用委托给该类对象进行 + +```java +@SPI("apple") +public interface FruitGranter { + Fruit grant(); + @Adaptive + String watering(URL url); +} +--- +// 苹果种植者 +public class AppleGranter implements FruitGranter { + @Override + public Fruit grant() { + return new Apple(); + } + @Override + public String watering(URL url) { + System.out.println("watering apple"); + return "watering finished"; + } +} +--- +// 香蕉种植者 +public class BananaGranter implements FruitGranter { + @Override + public Fruit grant() { + return new Banana(); + } + @Override + public String watering(URL url) { + System.out.println("watering banana"); + return "watering success"; + } +} +``` + +调用方法实现: + +```java +public class ExtensionLoaderTest { + @Test + public void testGetExtensionLoader() { + // 首先创建一个模拟用的URL对象 + URL url = URL.valueOf("dubbo://192.168.0.1:1412?fruit.granter=apple"); + // 通过ExtensionLoader获取一个FruitGranter对象 + FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class) + .getAdaptiveExtension(); + // 使用该FruitGranter调用其"自适应标注的"方法,获取调用结果 + String result = granter.watering(url); + System.out.println(result); + } +} +``` + +通过如上方式生成一个内部类。 + + + +## 服务暴露流程 + +### 服务暴露总览 + +**Dubbo**框架是以**URL**为总线的模式,运行过程中所有的状态数据信息都可以通过**URL**来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过**URL**的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的**Key**从**URL**的参数列表中获取。**URL** 具体的参数如下: + +- protocol:指的是 dubbo 中的各种协议,如:dubbo thrift http +- username/password:用户名/密码 +- host/port:主机/端口 +- path:接口的名称 +- parameters:参数键值对 + +```properties +protocol://username:password@host:port/path?k=v +``` + +服务暴露从代码流程看分为三部分: + +- 检查配置,最终组装成 **URL**。 +- 暴露服务到到本地服务跟远程服务。 +- 服务注册至注册中心。 + + + +服务暴露从对象构建转换看分为两步: + +- 将服务封装成**Invoker**。 +- 将**Invoker**通过协议转换为**Exporter**。 + + + +### 服务暴露源码追踪 + +- 容器启动,Spring IOC刷新完毕后调用 onApplicationEvent 开启服务暴露,ServiceBean +- export 跟 doExport 来进行拼接构建URL,为屏蔽调用的细节,统一暴露出一个可执行体,通过ProxyFactory 获取到 invoker +- 调用具体 Protocol 将把包装后的 invoker 转换成 exporter,此处用到了SPI +- 然后启动服务器server,监听端口,使用NettyServer创建监听服务器 +- 通过 RegistryProtocol 将URL注册到注册中心,使得consumer可获得provider信息 + + + +## 服务引用流程 + +Dubbo中一个可执行体就是一个invoker,所以 provider 跟 consumer 都要向 invoker 靠拢。通过上面demo可知为了无感调用远程接口,底层需要有个代理类包装 invoker。 + + + +**服务的引入时机有两种** + +- 饿汉式 + + 通过实现 Spring 的 InitializingBean 接口中的 afterPropertiesSet 方法,容器通过调用 **ReferenceBean**的 afterPropertiesSet 方法时引入服务。 + +- 懒汉式(默认) + + 懒汉式是只有当服务被注入到其他类中时启动引入流程。 + + + +**服务引用的三种方式** + +- **本地引入**:服务暴露时本地暴露,避免网络调用开销 +- **直接连接引入远程服务**:不启动注册中心,直接写死远程**Provider**地址 进行直连 +- **通过注册中心引入远程服务**:通过注册中心抉择如何进行负载均衡调用远程服务 + + + +**服务引用流程** + +- 检查配置构建map ,map 构建 URL ,通过URL上的协议利用自适应扩展机制调用对应的 protocol.refer 得到相应的 invoker ,此处 +- 想注册中心注册自己,然后订阅注册中心相关信息,得到provider的 ip 等信息,再通过共享的netty客户端进行连接。 +- 当有多个 URL 时,先遍历构建出 invoker 然后再由 **StaticDirectory** 封装一下,然后通过 cluster 进行合并,只暴露出一个 invoker 。 +- 然后再构建代理,封装 invoker 返回服务引用,之后 Comsumer 调用的就是这个代理类。 + + + +**调用方式** + +- oneway:不关心请求是否发送成功。 +- Async异步调用:Dubbo天然异步,客户端调用请求后将返回的 ResponseFuture 存到上下文中,用户可随时调用 future.get 获取结果。异步调用通过唯一**ID** 标识此次请求。 +- Sync同步调用:在 Dubbo 源码中就调用了 future.get,用户感觉方法被阻塞了,必须等结果后才返回。 + + + +## 调用整体流程 + +**调用之前你可能需要考虑这些事** + +- consumer 跟 provider 约定好通讯协议,dubbo支持多种协议,比如dubbo、rmi、hessian、http、webservice等。默认走dubbo协议,连接属于**单一长连接**,**NIO异步通信**。适用传输数据量很小(单次请求在100kb以内),但是并发量很高。 +- 约定序列化模式,大致分为两大类,一种是字符型(XML或json 人可看懂 但传输效率低),一种是二进制流(数据紧凑,机器友好)。默认使用 hessian2作为序列化协议。 +- consumer 调用 provider 时提供对应接口、方法名、参数类型、参数值、版本号。 +- provider列表对外提供服务涉及到负载均衡选择一个provider提供服务。 +- consumer 跟 provider 定时向monitor 发送信息。 + + + +**调用大致流程** + +- 客户端发起请求来调用接口,接口调用生成的代理类。代理类生成RpcInvocation 然后调用invoke方法。 +- ClusterInvoker获得注册中心中服务列表,通过负载均衡给出一个可用的invoker。 +- 序列化跟反序列化网络传输数据。通过NettyServer调用网络服务。 +- 服务端业务线程池接受解析数据,从exportMap找到invoker进行invoke。 +- 调用真正的Impl得到结果然后返回。 + + + +**调用方式** + +- oneway:不关心请求是否发送成功,消耗最小。 +- sync同步调用:在 Dubbo 源码中就调用了 future.get,用户感觉方法被阻塞了,必须等结果后才返回。 +- Async 异步调用:Dubbo天然异步,客户端调用请求后将返回的 ResponseFuture 存到上下文中,用户可以随时调用future.get获取结果。异步调用通过**唯一ID**标识此次请求。 + + + +## 集群容错负载均衡 + +Dubbo 引入了**Cluster**、**Directory**、**Router**、**LoadBalance**、**Invoker**模块来保证Dubbo系统的稳健性,关系如下图: + +![Dubbo集群容错负载均衡](images/Middleware/Dubbo集群容错负载均衡.jpg) + +- 服务发现时会将多个多个远程调用放入**Directory**,然后通过**Cluster**封装成一个**Invoker**,该**invoker**提供容错功能 +- 消费者代用的时候从**Directory**中通过负载均衡获得一个可用**invoker**,最后发起调用 +- 你可以认为**Dubbo**中的**Cluster**对上面进行了大的封装,自带各种鲁棒性功能 + + + +### 集群容错 + +集群容错是在消费者端通过**Cluster**子类实现的,Cluster接口有10个实现类,每个**Cluster**实现类都会创建一个对应的**ClusterInvoker**对象。核心思想是**让用户选择性调用这个Cluster中间层,屏蔽后面具体实现细节**。 + +| Cluster | Cluster Invoker | 作用 | +| :--------------- | :---------------------- | :---------------------------- | +| FailoverCluster | FailoverClusterInvoker | 失败自动切换功能,**默认** | +| FailfastCluster | FailfastClusterInvoker | 一次调用,失败异常 | +| FailsafeCluster | FailsafeClusterInvoker | 调用出错则日志记录 | +| FailbackCluster | FailbackClusterInvoker | 失败返空,定时重试2次 | +| ForkingCluster | ForkingClusterInvoker | 一个任务并发调用,一个OK则OK | +| BroadcastCluster | BroadcastClusterInvoker | 逐个调用invoker,全可用才可用 | +| AvailableCluster | AvailableClusterInvoker | 哪个能用就用那个 | +| MergeableCluster | MergeableClusterInvoker | 按组合并返回结果 | + + + +### 智能容错之负载均衡 + +Dubbo中一般有4种负载均衡策略。 + +- **RandomLoadBalance**:加权随机,它的算法思想简单。假设有一组服务器 servers = [A, B, C],对应权重为 weights = [5, 3, 2],权重总和为10。现把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。**默认实现**。 +- **LeastActiveLoadBalance**:最少活跃数负载均衡,选择现在活跃调用数最少的提供者进行调用,活跃的调用数少说明它现在很轻松,而且活跃数都是从 0 加起来的,来一个请求活跃数+1,一个请求处理完成活跃数-1,所以活跃数少也能变相的体现处理的快。 +- **RoundRobinLoadBalance**:加权轮询负载均衡,比如现在有两台服务器 A、B,轮询的调用顺序就是 A、B、A、B,如果加了权重,A 比B 的权重是2:1,那现在的调用顺序就是 A、A、B、A、A、B。 +- **ConsistentHashLoadBalance**:一致性 Hash 负载均衡,将服务器的 IP 等信息生成一个 hash 值,将hash 值投射到圆环上作为一个节点,然后当 key 来查找的时候顺时针查找第一个大于等于这个 key 的 hash 值的节点。一般而言还会引入虚拟节点,使得数据更加的分散,避免数据倾斜压垮某个节点。如下图 Dubbo 默认搞了 160 个虚拟节点。 + +![ConsistentHashLoadBalance](images/Middleware/ConsistentHashLoadBalance.png) + + + +### 智能容错之服务目录 + +关于 服务目录Directory 你可以理解为是相同服务Invoker的集合,核心是RegistryDirectory类。具有三个功能。 + +- 从注册中心获得invoker列表 +- 监控着注册中心invoker的变化,invoker的上下线 +- 刷新invokers列表到服务目录 + + + +### 智能容错之服务路由 + +服务路由其实就是路由规则,它规定了服务消费者可以调用哪些服务提供者。条件路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则: + +```properties +host = 10.20.153.14 => host = 10.20.153.12 +``` + +该条规则表示 IP 为 10.20.153.14 的服务消费者只可调用 IP 为 10.20.153.12 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下: + +```properties +[服务消费者匹配条件] => [服务提供者匹配条件] +``` + +如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。 + + + +## 设计RPC + +一个RPC框架大致需要以下功能: + +1. 服务的注册与发现 +2. 用动态代理 +3. 负载均衡(LoadBalance) +4. 通信协议 +5. 序列化与反序列化 +6. 网络通信(Netty) +7. Monitor + # Nacos diff --git a/OS.md b/OS.md index 82ea90c..78d4db5 100644 --- a/OS.md +++ b/OS.md @@ -33,7 +33,10 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc ### 阻塞I/O -当用户程序执行 `read` ,线程会被阻塞,一直等到内核数据准备好,并把数据从内核缓冲区拷贝到应用程序的缓冲区中,当拷贝过程完成,`read` 才会返回。阻塞等待的是 **内核数据准备好** 和 **数据从内核态拷贝到用户态** 两个过程。过程如下图: +阻塞IO情况下,当用户调用`read`后,用户线程会被阻塞,等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后`read`才会返回。阻塞分两个阶段: + +- **等待CPU把数据从磁盘读到内核缓冲区** +- **等待CPU把数据从内核缓冲区拷贝到用户缓冲区** ![阻塞IO](images/OS/阻塞IO.png) @@ -41,7 +44,9 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc ### 非阻塞I/O -非阻塞的 `read` 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区,`read` 调用才可以获取到结果。过程如下图: +非阻塞的 `read` 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,询问数据是否准备好,当数据没有准备好时,内核立即返回EWOULDBLOCK错误。直到数据准备好后,内核将数据拷贝到应用程序缓冲区,`read` 请求才获取到结果。 + +**注意**:这里最后一次 `read` 调用获取数据的过程,是一个**同步的过程**,是需要等待的过程。这里的同步指的是**内核态的数据拷贝到用户程序的缓存区这个过程**。 ![非阻塞IO](images/OS/非阻塞IO.png) @@ -49,6 +54,14 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc +### I/O多路复用 + +非阻塞情况下无可用数据时,应用程序每次轮询内核看数据是否准备好了也耗费CPU,能否不让它轮询,当内核缓冲区数据准备好了,以事件通知当机制告知应用进程数据准备好了呢?应用进程在没有收到数据准备好的事件通知信号时可以忙写其他的工作。此时**IO多路复用**就派上用场了。像**select、poll、epoll** 都是I/O多路复用的具体的实现。 + +![IO-多路复用](images/OS/IO-多路复用.png) + + + ### 同步I/O 无论 `read` 和 `send` 是 `阻塞I/O`,还是 `非阻塞I/O` 都是同步调用。因为在 `read` 调用时,内核将数据从内核空间拷贝到用户空间的过程都是需要等待的,即这个过程是同步的,如果内核实现的拷贝效率不高,`read` 调用就会在这个同步过程中等待比较长的时间。 @@ -239,8 +252,6 @@ Proactor 模式的示意图如下: ## select/poll/epoll -select/poll/epoll对比: - ![select、poll、epoll对比](images/OS/select、poll、epoll对比.png) **注意**:**遍历**相当于查看所有的位置,**回调**相当于查看对应的位置。 @@ -251,22 +262,19 @@ select/poll/epoll对比: ![select工作流程](images/OS/select工作流程.jpg) -POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理 +`select` 本质上是通过设置或者检查存放 `fd` 标志位的数据结构来进行下一步处理。 **缺点** -- 单个进程可监视的fd数量被限制,即能监听端口的数量有限,数值存在如下文件里:`cat /proc/sys/fs/file-max` -- 对socket是线性扫描,即采用轮询的方法,效率较低 -- select采取了内存拷贝方法来实现内核将FD消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大 +- **单个进程可监视的`fd`数量被限制**。能监听端口的数量有限,数值存在文件:`cat /proc/sys/fs/file-max` +- **需要维护一个用来存放大量fd的数据结构**。这样会使得用户空间和内核空间在传递该结构时复制开销大 +- **对fd进行扫描时是线性扫描**。`fd`剧增后,`IO`效率较低,因为每次调用都对`fd`进行线性扫描遍历,所以随着`fd`的增加会造成遍历速度慢的性能问题 +- **`select()`函数的超时参数在返回时也是未定义的**。考虑到可移植性,每次在超时之后在下一次进入到`select`之前都需要重新设置超时参数 +**优点** - -select是第一版IO复用,提出后暴漏了很多问题。 - -- select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的 -- select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但不会告诉是那个sock上有数据,只能自己遍历查找 -- select 只能监视1024个链接 -- select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用,要收回,这个select 不支持的 +- **`select()`的可移植性更好**。在某些`Unix`系统上不支持`poll()` +- **`select()`对于超时值提供了更好的精度:微秒**。而`poll`是毫秒 @@ -274,21 +282,18 @@ select是第一版IO复用,提出后暴漏了很多问题。 ![poll工作流程](images/OS/poll工作流程.jpg) -本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态 - -- 其没有最大连接数的限制,原因是它是基于链表来存储的 -- 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义 -- poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd -- 边缘触发:只通知一次,epoll用的就是边缘触发 - +poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。 +**缺点** -poll 修复了 select 的很多问题: +- 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义 +- 与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符 -- poll 去掉了1024个链接的限制 -- poll 从设计上来说不再修改传入数组 +**优点** -但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。 +- poll() 不要求开发者计算最大文件描述符加一的大小 +- poll() 在应付大数目的文件描述符的时候速度更快,相比于select +- 它没有最大连接数的限制,原因是它是基于链表来存储的 @@ -296,41 +301,48 @@ poll 修复了 select 的很多问题: ![epoll工作流程](images/OS/epoll工作流程.jpg) -在Linux2.6内核中提出的select和poll的增强版本 +epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。 + -- 支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次 -- 使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知 **优点** -- 没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口) -- 效率提升:非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关 -- 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销 -- 文件映射内存直接通过地址空间访问,效率更高,把文件映射到内存中 +- **支持一个进程打开大数目的socket描述符(FD)** + + select最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是1024/2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 +- **IO效率不随FD数目增加而线性下降** + 传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在Linux内核。 -epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如: +- **使用mmap加速内核与用户空间的消息传递** -- epoll 现在是线程安全的 -- epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了 -- epoll 内核态管理了各种IO文件描述符, 以前用户态发送所有文件描述符到内核态,然后内核态负责筛选返回可用数组,现在epoll模式下所有文件描述符在内核态有存,查询时不用传文件描述符进去了 + 这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap同一块内存实现的。 ## BIO(同步阻塞I/O) +每个客户端的Socket连接请求,服务端都会对应有个处理线程与之对应,对于没有分配到处理线程的连接就会被阻塞或者拒绝。相当于是`一个连接一个线程`。 + 用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。 ![同步阻塞IO](images/OS/同步阻塞IO.png) **特点:**I/O执行的两个阶段进程都是阻塞的。 +- 使用一个独立的线程维护一个socket连接,随着连接数量的增多,对虚拟机造成一定压力 +- 使用流来读取数据,流是阻塞的,当没有可读/可写数据时,线程等待,会造成资源的浪费 + + + **优点** - 能够及时的返回数据,无延迟 - 程序简单,进程挂起基本不会消耗CPU时间 + + **缺点** - I/O等待对性能影响较大 @@ -340,6 +352,18 @@ epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll ## NIO(同步非阻塞I/O) +服务器端保存一个Socket连接列表,然后对这个列表进行轮询: + +- 如果发现某个Socket端口上有数据可读时说明读就绪,则调用该Socket连接的相应读操作 +- 如果发现某个Socket端口上有数据可写时说明写就绪,则调用该Socket连接的相应写操作 +- 如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口 + +这样能充分利用服务器资源,效率得到了很大提高,在进行I/O操作请求时候再用个线程去处理,是`一个请求一个线程`。Java中使用Selector、Channel、Buffer来实现上述效果。 + +- `Selector`:Selector允许单线程处理多个Channel。如果应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。要使用Selector,得向Selector注册Channel,然后调用他的select方法,这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子入有新连接接进来,数据接收等。 +- `Channel`:基本上所有的IO在NIO中都从一个Channel开始。Channel有点像流,数据可以从channel**读**到buffer,也可以从buffer**写**到channel。 +- `Buffer`:缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松的使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变换情况,Channel提供从文件,网络读取数据的渠道,但是读取或者写入的数据都必须经由Buffer。 + 用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求过程中,虽然用户线程每次发起IO请求后可以立即返回,但为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。 ![同步非阻塞IO](images/OS/同步非阻塞IO.png) diff --git a/images/Middleware/ConsistentHashLoadBalance.png b/images/Middleware/ConsistentHashLoadBalance.png new file mode 100644 index 0000000..0b47622 Binary files /dev/null and b/images/Middleware/ConsistentHashLoadBalance.png differ diff --git a/images/Middleware/DubboSPI源码追踪.png b/images/Middleware/DubboSPI源码追踪.png new file mode 100644 index 0000000..785beb8 Binary files /dev/null and b/images/Middleware/DubboSPI源码追踪.png differ diff --git a/images/Middleware/Dubbo框架分层.jpg b/images/Middleware/Dubbo框架分层.jpg new file mode 100644 index 0000000..17757af Binary files /dev/null and b/images/Middleware/Dubbo框架分层.jpg differ diff --git a/images/Middleware/Dubbo调用关系.jpg b/images/Middleware/Dubbo调用关系.jpg new file mode 100644 index 0000000..94942a3 Binary files /dev/null and b/images/Middleware/Dubbo调用关系.jpg differ diff --git a/images/Middleware/Dubbo集群容错负载均衡.jpg b/images/Middleware/Dubbo集群容错负载均衡.jpg new file mode 100644 index 0000000..6d7d519 Binary files /dev/null and b/images/Middleware/Dubbo集群容错负载均衡.jpg differ diff --git a/images/OS/IO-多路复用.png b/images/OS/IO-多路复用.png new file mode 100644 index 0000000..8efb49d Binary files /dev/null and b/images/OS/IO-多路复用.png differ diff --git a/images/OS/异步IO.png b/images/OS/异步IO.png index 62ad175..1d1a4cd 100644 Binary files a/images/OS/异步IO.png and b/images/OS/异步IO.png differ diff --git a/images/OS/阻塞IO.png b/images/OS/阻塞IO.png index acd9ab1..dc90054 100644 Binary files a/images/OS/阻塞IO.png and b/images/OS/阻塞IO.png differ diff --git a/images/OS/非阻塞IO.png b/images/OS/非阻塞IO.png index 02949df..08dea29 100644 Binary files a/images/OS/非阻塞IO.png and b/images/OS/非阻塞IO.png differ diff --git a/support/IO.drawio b/support/IO.drawio index cc2fe48..bc4096a 100644 --- a/support/IO.drawio +++ b/support/IO.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file