- TCP/IP 模型(四层):应用层、传输层、网络层、数据链路层
- OSI 模型 (七层):应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
- 应用层:操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括:Telnet、FTP、HTTP、SNMP 等
- 表示层:将应用处理的信息转换为适合网络传输的格式,或将来自下一层的数据转换为上层能够处理的格式。数据的表示、安全、压缩
- 会话层:负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理
- 传输层:管理两个节点之间的数据传输。负责可靠传输(确保数据被可靠地传送到目标地址)
- 网络层:负责地址管理与路由选择, 在这一层,数据的单位称为数据包(packet)(路由器)
- 数据链路层:负责互连设备之间传送和识别数据帧(交换机)
- 物理层:以"0"、 "1"代表电压的高低,灯光的闪灭,在这一层,数据的单位称为比特(bit),(中继器、集线器、还有我们通常说的双绞线也工作在物理层)
- Socket:为了区别不同的应用程序进程和连接,计算机操作系统为应用程序与 TCP/IP 协议交互提供了称为套接字(Socket)的接口,区分不同应用程序进程间的网络通信和连接
协议类型:TCP、UDP、HTTP、HTTPS、HTTP2
- Application(应用层)-->TCP-->IP-->Ethernet(以太网)
- 互联网由一整套协议构成。TCP 只是其中的一层(传输层)
- TCP 是以太网协议和 IP 协议的上层协议,也是应用层协议的下层协议
- 最底层的以太网协议(Ethernet)规定了电子信号如何组成数据包(packet),解决了子网内部的点对点通信,也就是解决了局域网的点对点通信
- 但是,以太网协议不能解决多个局域网如何互通,这由 IP 协议解决
- IP 协议只是一个地址协议,并不保证数据包的完整。如果路由器丢包(比如缓存满了,新进来的数据包就会丢失),就需要发现丢了哪一个包,以及如何重新发送这个包。这就要依靠 TCP 协议
- TCP 协议的作用是,保证数据通信的完整性和可靠性,防止丢包
- 以太网数据包(packet)的大小是固定的,最初是 1518 字节,后来增加到 1522 字节。其中, 1500 字节是负载(payload),22 字节是头信息(head)
- IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要 20 字节,所以 IP 数据包的负载最多为 1480 字节
- TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要 20 字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为 1400 字节左右
- SEQ(Sequence Number):TCP 协议会为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包
- ACK(Acknowledgement):TCP 协议规定,只有 ACK=1 时有效,也规定连接建立后所有发送的报文的 ACK 必须为 1
- SYN(Synchronization): 在连接建立时用来同步序号。当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使 SYN=1 和 ACK=1. 因此, SYN 置 1 就表示这是一个连接请求或连接接受报文
- FIN (Finish):用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接
介绍图片:https://pic2.zhimg.com/80/v2-1587e91f14923eae0fa7d846cb10df99_1440w.jpg
所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送 3 个包。
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手
- 第一次握手(SYN=1, seq=x):
客户端发送一个 TCP 的 SYN 标志位置1的包,
指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。
发送完毕后,客户端进入 SYN_SEND 状态
2,第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。
服务器端选择自己 ISN 序列号,放到 Seq 域里,
同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。
发送完毕后,服务器端进入 SYN_RCVD 状态
- 第三次握手(ACK=1,ACKnum=y+1)
客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,
并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1
发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束
介绍图片:https://pic2.zhimg.com/80/v2-6e5f05603526c1d259b300f20b8857a5_1440w.jpg
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作
- 第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入 FIN_WAIT_1 状态
- 第二次挥手(ACK=1,ACKnum=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接
- 第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK
- 第四次挥手(ACK=1,ACKnum=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态
主要特点:
- UDP 是无连接的,即发送数据之前不需要建立连接(发送数据结束时也没有连接可释放),减少了开销和发送数据之前的时延
- UDP 使用尽最大努力交付,即不保证可靠交付,主机不需要维持复杂的连接状态表
- UDP 是面向报文的,发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界
- UDP 没有拥塞控制,网络出现的拥塞不会使源主机的发送速率降低
- UDP 支持一对一、一对多、多对一和多对多的交互通信
- UDP 的首部开销小,只有 8 个字节,比 TCP 的 20 个字节的首部要短
- HTTP 是一个属于应用层的面向对象的协议
- 基于 TCP/IP 协议的应用层协议。它不涉及数据包(packet)传输,主要规定了客户端和服务器之间的通信格式,默认使用 80 端口
- HTTP(Hyper Text Transfer Protocol(超文本传输协议)) 的最早版本是 1991 年发布的 0.9 版。该版本极其简单,只有一个命令 GET
- 用于客户端向服务器请求(request)网页
- 协议规定,服务器只能回应 HTML 格式的字符串,不能回应别的格式
- 服务器发送完毕,就关闭 TCP 连接
1996 年 5 月,HTTP/1.0 版本发布,内容大大增加
新增功能:
- 内容格式:请求格式任何格式的内容都可以发送。这使得互联网不仅可以传输文字,还能传输图像、视频、二进制文件
- 请求方法:除了 GET 命令,还引入了 POST 命令和 HEAD 命令
- 请求和响应格式:HTTP 请求和回应的格式也变了。除了数据部分,每次通信都必须包括头信息(HTTP header),用来描述一些元数据
- Content-Type:关于字符的编码,1.0 版规定,头信息必须是 ASCII 码,后面的数据可以是任何格式。因此,服务器回应的时候,必须告诉客户端,数据是什么格式,这就是 Content-Type 字段的作用
- Content-Encoding:由于发送的数据可以是任何格式,因此可以把数据压缩后再发送。Content-Encoding 字段说明数据的压缩方法
- 其他的新增功能还包括状态码(status code)、多字符集支持、多部分发送(multi-part type)、权限(authorization)、缓存(cache)、内容编码(content encoding)等
缺点:
- 每个 TCP 连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接(可以通过添加 Connection: keep-alive 的 Header 来优化)
1997 年 1 月,HTTP/1.1 版本发布
新增功能:
- 持久连接:引入了持久连接(persistent connection),即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive,对于同一个域名,大多数浏览器允许同时建立 6 个持久连接
- 管道机制:引入了管道机制(pipelining),即在同一个 TCP 连接里面,客户端可以同时发送多个请求。这样就进一步改进了 HTTP 协议的效率
- Content-Length:新增 Content-Length 的 Header(因为一个 TCP 连接现在可以传送多个 Response)
- 分块传输编码:采用"流模式"(stream)取代"缓存模式"(buffer),只要请求或回应的头信息有 Transfer-Encoding 字段,就表明回应将由数量未定的数据块组成
- 请求方法:新增了许多动词方法:PUT、PATCH、HEAD、 OPTIONS、DELETE
- 自定义 Host:客户端请求的头信息新增了 Host 字段,用来指定服务器的域名
缺点:
- 虽然 1.1 版允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为"队头堵塞"
2009 年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1 效率不高的问题
这个协议在 Chrome 浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承
2015 年,HTTP/2 发布。它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是 HTTP/3
新增功能:
- 二进制协议:HTTP/1.1 版的头信息肯定是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧,二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧
- 多路复用(Multiplexing):HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"
- 数据流:因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream)。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流 ID,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流,ID 一律为奇数,服务器发出的,ID 为偶数。数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM 帧),取消这个数据流。1.1 版取消数据流的唯一方法,就是关闭 TCP 连接。这就是说,HTTP/2 可以取消某一次请求,同时保证 TCP 连接还打开着,可以被其他请求使用。客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应
- 头信息压缩:HTTP/2 对这一点做了优化,引入了头信息压缩机制(header compression)。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了
- 服务器推送:HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push)
HTTPS 为使用 SSL/TLS 的 HTTP 通信
作用:
- 所有信息都是加密传播,第三方无法窃听
- 具有校验机制,一旦被篡改,通信双方会立刻发现
- 配备身份证书,防止身份被冒充
发展史:
1994年,NetScape公司设计了SSL协议(Secure Sockets Layer)的1.0版,但是未发布。
1995年,NetScape公司发布SSL 2.0版,很快发现有严重漏洞。
1996年,SSL 3.0版问世,得到大规模应用。
1999年,互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。
2006年和2008年,TLS进行了两次升级,分别为TLS 1.1版和TLS 1.2版。最新的变动是2011年TLS 1.2的修订版。
目前,应用最广泛的是 TLS 1.0,接下来是 SSL 3.0。但是,主流浏览器都已经实现了 TLS 1.2 的支持
TLS 1.0 通常被标示为 SSL 3.1,TLS 1.1 为 SSL 3.2,TLS 1.2 为 SSL 3.3
基本的运行过程:
- 客户端发出请求(ClientHello):告知服务器客户端支持的协议版本、加密方法、压缩方法
- 服务器回应(SeverHello):确认使用的加密通信协议版本,确认使用的加密方法,比如 RSA 公钥加密,返回服务器证书
- 客户端回应:向服务端发送一个随机数。该随机数用服务器公钥加密,然后发送编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送,再发送客户端握手结束通知,表示客户端的握手阶段已经结束
- 服务器的最后回应:向客户端发送编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送,再发送服务器握手结束通知,表示服务器的握手阶段已经结束
- 客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用"会话密钥"加密内容
https://tool.oschina.net/commons?type=5
- GET 请求的数据会附在 URL 之后(部分浏览器会限制 URL 长度,IE 为 2KB)
- Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求
CDN 的全称是 Content Delivery Network,即内容分发网络
基本思路: 实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上
DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出 IP 地址
正向代理代理的对象是客户端,反向代理代理的对象是服务端
例如:
- 由于防火墙的原因,我们并不能直接访问谷歌,那么我们可以借助 VPN 来实现,这就是一个简单的正向代理的例子
- 当我们在外网访问百度的时候,其实会进行一个转发,代理到百度的不同服务器去,这就是所谓的反向代理
- LVS,全称 Linux Virtual Server,是国人章文嵩发起的一个开源项目
- LVS 是一个工作在网络的第四层(传输层)、具有强大性能的反向代理服务器,支持反向代理、负载均衡等功能
- LVS 采用的是同步请求转发的策略,LVS 接收到请求之后,立即 redirect 到一个后端服务器,由客户端直接和后端服务器建立连接
- 缺点:
- LVS 没有故障转移机制,一旦后端接受到请求的服务器出了问题,那么这次请求就失败
- Nginx 是由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru 站点(俄文:Рамблер)开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日
- Nginx 是一款轻量级的 Web 服务器、反向代理服务器,由于它的内存占用少,启动极快,高并发能力强,在互联网项目中广泛应用
- Nginx 工作在网络的第七层(应用层),支持静态服务器、反向代理、负载均衡、虚拟主机、故障转移等功能
- Nginx 采用的是异步转发策略,Nginx 接收到请求之后,在保持客户端连接的同时,发起一个相同内容的新请求到后端,等后端返回结果后,再由 Nginx 返回给客户端
- 缺点:
- 所有的请求和响应流量都会经过 Nginx,当作为后端的服务器规模庞大时,Nginx 的网络带宽就成了一个巨大的瓶颈
- 使用 LVS 时,仅请求流量经过 LVS 的网络,响应流量由后端服务器的网络返回
- 因此,如果在 LVS 的后端在添加一层 Nginx(多个),每个 Nginx 后端再配置几台应用服务器,那么结合两者的优势,既能避免 Nginx 的流量集中瓶颈,又能避免 LVS 不支持故障转移的问题
注入式(Inject)攻击是一类非常常见的攻击方式,其基本特征是程序允许攻击者将不可信的动态内容注入到程序中,并将其执行,这就可能完全改变最初预计的执行过程,产生恶意效果
常见注入攻击手段:
- SQL 注入:select _ from user where name = “input_usr_name”,input_user_name=“ or “”=”,得到的 SQL 为 select _ from user where name = "" or ""="",类似场景可以利用注入的不同 SQL 语句,进行各种不同目的的攻击
- 操作系统命令注入:Java 语言提供了类似 Runtime.exec(…) 的 API,可以用来执行特定命令,所以可以类似 SQL 注入一样注入系统命令
- XML 注入:Java 核心类库提供了全面的 XML 处理、转换等各种 API,而 XML 自身是可以包含动态内容的,例如 XPATH,如果使用不当,可能导致访问恶意内容
- LDAP 注入、XSS(Cross-site Scripting)注入等
全称是 Distributed Denial of Service,翻译成中文就是分布式拒绝服务。一般来说是指攻击者利用“肉鸡”对目标网站在较短的时间内发起大量请求,大规模消耗目标网站的主机资源,让它无法正常服务。在线游戏、互联网金融等领域是 DDoS 攻击的高发行业
拖库就是指黑客通过各种社工手段、技术手段(对网站进行扫描,寻找漏洞,类如 SQL 注入、文件上传漏洞等)将数据库中敏感信息非法获取,一般这些敏感信息包括用户的账号信息如用户名、密码;身份信息如真实姓名、证件号码;通讯信息如电子邮箱、电话、住址等
在拖库后,取得大量的用户数据之后,黑客会通过一系列的技术手段和黑色产业链将有价值的用户数据变现,这通常也被称作“洗库”
撞库是黑客通过收集互联网已泄露的用户和密码信息,生成对应的字典表,尝试批量登陆其他网站后,得到一系列可以登录的用户。很多用户在不同网站使用的是相同的帐号密码,因此黑客可以通过获取用户在 A 网站的账户从而尝试登录 B 网址,这就可以理解为撞库攻击
- 第一层:service 层,接口层,给服务提供者和消费者来实现的
- 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
- 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo * 都会给你生成代理,代理之间进行网络通信
- 第四层:registry 层,服务注册层,负责服务的注册与发现
- 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
- 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
- 第七层:protocal 层,远程调用层,封装 rpc 调用
- 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
- 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
- 第十层:serialize 层,数据序列化层
- 第一步:provider 向注册中心去注册
- 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
- 第三步:consumer 调用 provider
- 第四步:consumer 和 provider 都异步通知监控中心
- 使用 Servlet 容器运行(Tomcat、Jetty 等)
- 自建 Main 方法类来运行
- 使用 Dubbo 框架提供的 Main 方法类来运行
- xml 配置:<dubbo:reference ... init="true" />
- dubbo.properties 属性配置:Dubbo 将自动加载 classpath 根目录下的 dubbo.properties,可以通过 JVM 启动参数 -Ddubbo.properties.file=xxx.properties 改变缺省配置位置
- API 配置:代码动态设置
- 注解配置
同步等待逻辑:
- DubboInvoker 中调用 ExchangeChannel,ExchangeChannel 通过 Netty 发起请求,生成 DefaultFeature
- DefaultFuture 的 get 方法中采用自旋锁+可重入锁实现超时和获取异步返回的结果的功能
异步获取结果的逻辑:
- DefaultFuture 中维护一个全局的静态 Map,存储请求 Id 和 DefaultFuture 的映射关系
- HeaderExchangeHandler 作为 Netty 的 response-handler,接收 response 后存储到全局的静态 Map 中
- 再根据请求 Id 获取到 DefaultFeature,将结果同步到该对象中
- Dubbo 协议:基于 hessian 作为序列化协议,传输数据量小(每次请求在 100kb 以内),但是并发量很高
- rmi 协议:走 Java 二进制序列化,多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用
- hessian 协议:走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用
- http 协议:走表单序列化
- webservice:走 SOAP 文本序列化
- dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议。但是 hessian 是其默认的序列化协议
- RandomLoadBalance:默认策略,加权随机策略,随机调用实现负载均衡,可以对 provider 不同实例设置权重,权重越大分配流量越高
- RoundRobinLoadBalance:加权轮询策略,将请求轮流分配给每台服务器,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高,可以对 provider 不同实例设置权重,权重越大分配流量越高
- LeastActiveLoadBalance:最小活跃数负载均衡策略。活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者
- ConsistentHashLoadBalance:一致性 Hash 策略,同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量
- Failover Cluster 模式:默认策略,失败自动切换,自动重试其他机器,可以配置重试次数
- Failfast Cluster 模式:一次调用失败就立即失败
- Failsafe Cluster 模式:出现异常时忽略掉
- Failback Cluster 模式:失败了后台自动记录请求,然后定时重发
- Forking Cluster 模式:并行调用多个 provider,只要一个成功就立即返回,可以设置最大并行数
- Broadcast Cluster 模式:逐个调用所有的 provider,任何一个 provider 出错则报错
- service provider interface,单接口多实现模型,方便扩展
- 默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略
- 调用链路图
- 服务访问压力以及时长统计
- 服务分层
- 调用链路失败监控和报警
- 服务可用性监控(接口调用成功率)
- 服务鉴权
- 一个服务挂了,调用该服务的在失败次数达到一定量之后,需要降级,走备用逻辑
- RpcContext 中维护了 InternalThreadLocal,InternalThreadLocal 基于 InternalThreadLocalMap 实现,InternalThreadLocalMap 内部基于 TreadLocal 实现
- RpcContext 会逐级专递,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如 A 调用 B,B 再调用 C,则 B 机器上,在 B 调用 C 之前,RpcContext 记录的是 A 调用 B 的信息,在 B 调用 C 之后,RpcContext 记录的是 B 调用 C
- 带来的问题:
- 如果 consumer-A 异步调用 provider-B,而 provider-B 本身又调用了 provider-C。当 provider-B 调用 provider-C 时,会变成异步
- 问题原因:是否异步调用取决于 RpcContext 中 async 的值,其次才是服务本身的配置。当 A 调用 B 时,会把 async=true 传给 B 的 RpcContext;B 调用 C 时,虽然服务本身 async=false,但 RpcContext 中 async=true,自然也就成了异步调用
- 解决方案:别让 async 参数应用到 provider 端。或者修改 ContextFilter 源码,重写 RpcContext 时删除 async 参数
- 超时重试
- 默认 timeout=1000ms,retry=2,如果网络超时,会导致自动重试
- 解决方案:Provider 端实现幂等,或者客户端不配置重试,改为对失败的请求补偿的方式
- 服务重名
- dubbo 的负载均衡策略,无法区分重名的服务
- 异步调用问题
- 如果 consumer-A 异步调用 provider-B,而 provider-B 本身又调用了 provider-C。当 provider-B 调用 provider-C 时,会变成异步,但是取结果是同步,导致返回 B 给 A 的是 null
- 解决方案:
- 别让 async 参数应用到 provider 端
- 修改 ContextFilter 源码,重写 RpcContext 时删除 async 参数
- 自定义 com.alibaba.dubbo.rpc.Filter 实现,在 Filter 中清除掉 RpcContext 中的 async 参数
- Dubbo 返回 Float 或者 Double 类型参数精度丢失
- 由于 dubbo 在进行序列化的时候默认的协议是 hession2 协议,而使用 hession2 协议会将十进制转换为二进制,在转换的过程中就会遇到精度丢失的问题
- 解决方案:不使用这两种浮点类型,使用 BigDemical 或者其他类型来表示
Netty 是一个异步事件驱动的 NIO 网络应用程序框架
- Bootstrap:配置并启动服务的类
- NioEventLoopGroup:
- 多个 EventLoop 组成的事件循环处理组,每个 EventLoop 中都有一个死循环,负责接收/处理请求
- bossGroup:接收并分发 client 的请求,一般分配一个线程即可
- workerGroup:执行 client 的请求,可自定义分配线程池
- Buffer:
- 缓冲区,在数据传输时,在内存里开辟的一块临时保存数据的区域,是一种化同步为异步的机制,可以解决数据传输的速率不对等以及不稳定的问题
- 主流 Buffer:
- ChannelBuffer:重写的 ByteBuffer
- DynamicChannelBuffer:长度会根据内容的长度来扩充的 Buffer,在写入前做容量检查,容量不够时,新建一个容量 x2 的 buffer
- CompositeChannelBuffer:由多个 ChannelBuffer 组合而成,CompositeChannelBuffer 在读写时并不会开辟新的内存并直接复制所有 ChannelBuffer 内容,而是直接保存了所有 ChannelBuffer 的引用,并在子 ChannelBuffer 里进行读写,从而实现了零拷贝
- ByteBufferBackedChannelBuffer:ByteBufferBackedChannelBuffer 封装了 NIO ByteBuffer 的类,用于实现堆外内存的 Buffer(使用 NIO 的 DirectByteBuffer)
- Channel:
- Channel 是通讯的载体
- ChannelHandler:负责 Channel 中的逻辑处理
- ChannelPipeline:
- 介绍地址
- ChannelHandler 的容器
- 每个 Channel 中都包含一个 ChannelPipeline
- 每个 ChannelPipeline 中都维护了一个双向链表,用于顺序存储所有 ChannelHandler
- 当对 Channel 进行操作时,会产生一个 ChannelEvent,并发送到 ChannelPipeline。ChannelPipeline 会遍历 ChannelHandler 链表进行处理
- 每个 ChannelHandler 处理之后,可能会产生新的 ChannelEvent,并流转到下一个 ChannelHandler
- 每个 ChannelHandler 接收到一个 ChannelEvent 并处理结束后,如果需要继续处理,那么它需要调用 ChannelPipeline.sendUp(Down)stream 新发起一个事件。如果它不再发起事件,那么处理就到此结束,即使它后面仍然有 Handler 没有执行
- 这个机制可以保证最大的灵活性,对 Handler 的先后顺序也有了更严格的要求
- ChannelPipeline 包含两条线路:Upstream 和 Downstream。Upstream 对应上行,接收到的消息、被动的状态改变,都属于 Upstream。Downstream 则对应下行,发送的消息、主动的状态改变,都属于 Downstream。ChannelPipeline 接口包含了两个重要的方法:sendUpstream(ChannelEvent e)和 sendDownstream(ChannelEvent e),就分别对应了 Upstream 和 Downstream
Netty 的零拷贝体现在三个方面:
- Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝
- Netty 的 CompositeChannelBuffer 是由多个 ChannelBuffer 组合而成的,可以看做一个整体进行读写。CompositeChannelBuffer 在读写时并不会开辟新的内存并直接复制所有 ChannelBuffer 内容,而是直接保存了所有 ChannelBuffer 的引用,并在子 ChannelBuffer 里进行读写,从而实现了零拷贝
- Netty 文件传输类 DefaultFileRegion 通过 transferTo()方法可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题
介绍地址
- Typesafe 开源,基于 JAVA/Scala 语言开发,基于 Actor 并发模型实现,是一款高性能、高容错性的分布式&并行应用框架
- 提供了并行、并发、异步非阻塞、高容错性、持久化、轻量级等分布式场景下的重要能力
- Spark2.0 以前的的 RPC 是通过 Akka 的类库实现的
- 主要结构
- Actor
- Actor 的基础就是消息传递,一个 Actor 可以认为是一个基本的计算单元,它能接收消息并基于其执行运算,它也可以发送消息给其他 Actor。Actors 之间相互隔离,它们之间并不共享内存
- Actor 是由状态(State)、行为(Behavior)和邮箱(MailBox,可以认为是一个消息队列)三部分组成:
- 状态:Actor 中的状态指 Actor 对象的变量信息,状态由 Actor 自己管理,避免了并发环境下的锁和内存原子性等问题
- 行为:Actor 中的计算逻辑,通过 Actor 接收到的消息来改变 Actor 的状态
- 邮箱:邮箱是 Actor 和 Actor 之间的通信桥梁,邮箱内部通过 FIFO(先入先出)消息队列来存储发送方 Actor 消息,接受方 Actor 从邮箱队列中获取消息
- ActorSystem
- ActorSystem 可以看做是 Actor 的系统工厂或管理者。主要有以下功能:
- 管理调度服务
- 配置相关参数
- 日志功能
- ActorSystem 可以看做是 Actor 的系统工厂或管理者。主要有以下功能:
- ActorRef
- ActorRef 可以看做是 Actor 的引用,是一个 Actor 的不可变,可序列化的句柄(handle),它可能不在本地或同一个 ActorSystem 中
- ActorRef 最重要功能是支持向它所代表的 Actor 发送消息
- Dispatcher
- Akka MessageDispatcher 驱动 Akka actor 运行(tick),也可以说是这个机器的引擎
- 所有的 MessageDispatcher 都实现了 ExecutionContext trait, 这意味着它们可以用来执行任何代码
- 主要类型:
- Dispatcher:无共享性限制,为每一个 Actor 创建一个 MailBox,底层使用 java 线程池实现,可自定义线程池
- PinnedDispatcher:无共享性限制,为每一个 Actor 创建一个 MailBox,底层使用 akka.dispatch.ThreadPoolExecutorConfigurator
- BalancingDispatcher:仅对同一类型的 Actor 共享,为所有的 Actor 创建一个共用的 MailBox,底层使用 fork-join-executor 线程池
- Mailbox
- 保存发往某 Actor 的消息. 通常每个 Actor 拥有自己的邮箱, 但是如果是使用 BalancingDispatcher 使用同一个 BalancingDispatcher 的所有 Actor 共享同一个邮箱实例
- Actor
- 主要流程:
- ProducerActor 创建一个 ActorSystem(系统的控制中心)
- 通过 ActorSystem 来创建了一个 ActorRef,并将消息发送到 ActorRef
- ActorRef 将消息传递到 Dispatcher 中
- Dispatcher 将消息投递到全部目标 Actor 的 Mailbox 中
- 随后 Dispatcher 将 Mailbox 扔给一个线程去执行
- MailBox 将消息出队并最终将其委托给真实的 Teacher Actor 的接收方法去处理
- Apache Mina 是一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架。它通过 Java nio 技术基于 TCP/IP 和 UDP/IP 协议提供了抽象的、事件驱动的、异步的 API
- 核心组件:
- I/O Service,具体提供服务的组件
- I/O Filter Chain,过滤器链,用于支持各种切面服务
- I/O Handler,用于处理用户的业务逻辑