1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-01 16:28:03 +08:00

Compare commits

...

11 Commits

17 changed files with 320 additions and 49 deletions

View File

@ -10,6 +10,7 @@ export const highQualityTechnicalArticles = arraySidebar([
"programmer-quickly-learn-new-technology",
"the-growth-strategy-of-the-technological-giant",
"ten-years-of-dachang-growth-road",
"meituan-three-year-summary-lesson-10",
"seven-tips-for-becoming-an-advanced-programmer",
"20-bad-habits-of-bad-programmers",
"thinking-about-technology-and-business-after-five-years-of-work",

View File

@ -622,10 +622,7 @@ public class Solution {
**题目分析:**
这道题想了半天没有思路,参考了 Alias 的答案,他的思路写的也很详细应该很容易看懂。
作者Alias
<https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106>
来源:牛客网
这道题想了半天没有思路,参考了 [Alias 的答案](https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106),他的思路写的也很详细应该很容易看懂。
【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是 1然后判断栈顶元素是不是出栈顺序的第一个元素这里是 4很显然 1≠4所以我们继续压栈直到相等以后开始出栈出栈一个元素则将出栈顺序向后移动一位直到不相等这样循环等压栈顺序遍历完成如果辅助栈还不为空说明弹出序列不是该栈的弹出顺序。

View File

@ -15,6 +15,8 @@ DNSDomain Name System域名管理系统是当用户使用浏览器访
![TCP/IP 各层协议概览](https://oss.javaguide.cn/github/javaguide/cs-basics/network/network-protocol-overview.png)
## DNS 服务器
DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务器都属于以下四个类别之一):
- 根 DNS 服务器。根 DNS 服务器提供 TLD 服务器的 IP 地址。目前世界上只有 13 组根服务器,我国境内目前仍没有根服务器。
@ -22,6 +24,8 @@ DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务
- 权威 DNS 服务器。在因特网上具有公共可访问主机的每个组织机构必须提供公共可访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。
- 本地 DNS 服务器。每个 ISP互联网服务提供商都有一个自己的本地 DNS 服务器。当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 层次结构中。严格说来,不属于 DNS 层级结构。
世界上并不是只有 13 台根服务器,这是很多人普遍的误解,网上很多文章也是这么写的。实际上,现在根服务器数量远远超过这个数量。最初确实是为 DNS 根服务器分配了 13 个 IP 地址,每个 IP 地址对应一个不同的根 DNS 服务器。然而,由于互联网的快速发展和增长,这个原始的架构变得不太适应当前的需求。为了提高 DNS 的可靠性、安全性和性能,目前这 13 个 IP 地址中的每一个都有多个服务器,截止到 2023 年底,所有根服务器之和达到了 600 多台,未来还会继续增加。
## DNS 工作流程
以下图为例,介绍 DNS 的查询解析过程。DNS 的查询解析过程分为两种模式:
@ -48,7 +52,7 @@ DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务
![](https://oss.javaguide.cn/github/javaguide/cs-basics/network/DNS-process2.png)
另外DNS 的缓存位于本地 DNS 服务器。由于全世界的根服务器甚少,只有 400 多台,分为 13 组,且顶级域的数量也在一个可数的范围内,因此本地 DNS 通常已经缓存了很多 TLD DNS 服务器,所以在实际查找过程中,无需访问根服务器。根服务器通常是被跳过的,不请求的。
另外DNS 的缓存位于本地 DNS 服务器。由于全世界的根服务器甚少,只有 600 多台,分为 13 组,且顶级域的数量也在一个可数的范围内,因此本地 DNS 通常已经缓存了很多 TLD DNS 服务器,所以在实际查找过程中,无需访问根服务器。根服务器通常是被跳过的,不请求的。这样可以提高 DNS 查询的效率和速度,减少对根服务器和 TLD 服务器的负担。
## DNS 报文格式

View File

@ -361,7 +361,7 @@ DNSDomain Name System域名管理系统是当用户使用浏览器访
目前 DNS 的设计采用的是分布式、层次数据库结构,**DNS 是应用层协议,它可以在 UDP 或 TCP 协议之上运行,端口为 53** 。
### DNS 服务器有哪些?
### DNS 服务器有哪些?根服务器有多少个?
DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务器都属于以下四个类别之一):
@ -370,10 +370,16 @@ DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务
- 权威 DNS 服务器。在因特网上具有公共可访问主机的每个组织机构必须提供公共可访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。
- 本地 DNS 服务器。每个 ISP互联网服务提供商都有一个自己的本地 DNS 服务器。当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 层次结构中。严格说来,不属于 DNS 层级结构
世界上并不是只有 13 台根服务器,这是很多人普遍的误解,网上很多文章也是这么写的。实际上,现在根服务器数量远远超过这个数量。最初确实是为 DNS 根服务器分配了 13 个 IP 地址,每个 IP 地址对应一个不同的根 DNS 服务器。然而,由于互联网的快速发展和增长,这个原始的架构变得不太适应当前的需求。为了提高 DNS 的可靠性、安全性和性能,目前这 13 个 IP 地址中的每一个都有多个服务器,截止到 2023 年底,所有根服务器之和达到了 600 多台,未来还会继续增加。
### DNS 解析的过程是什么样的?
整个过程的步骤比较多,我单独写了一篇文章详细介绍:[DNS 域名系统详解(应用层)](./dns.md) 。
### DNS 劫持了解吗?如何应对?
DNS 劫持是一种网络攻击,它通过修改 DNS 服务器的解析结果,使用户访问的域名指向错误的 IP 地址从而导致用户无法访问正常的网站或者被引导到恶意的网站。DNS 劫持有时也被称为 DNS 重定向、DNS 欺骗或 DNS 污染。DNS 劫持详细介绍可以参考:[黑客技术没你想象的那么难——DNS 劫持篇](https://cloud.tencent.com/developer/article/1197474)。
## 参考
- 《图解 HTTP》

View File

@ -63,7 +63,7 @@ HTTP/3.0 之前是基于 TCP 协议的,而 HTTP/3.0 将弃用 TCP改用 **
**运行于 TCP 协议之上的协议**
1. **HTTP 协议**超文本传输协议HTTPHyperText Transfer Protocol)是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
1. **HTTP 协议HTTP/3.0 之前)**超文本传输协议HTTPHyperText Transfer Protocol)是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
2. **HTTPS 协议**:更安全的超文本传输协议(HTTPS,Hypertext Transfer Protocol Secure),身披 SSL 外衣的 HTTP 协议
3. **FTP 协议**:文件传输协议 FTPFile Transfer Protocol是一种用于在计算机之间传输文件的协议可以屏蔽操作系统和文件存储方式。注意 ⚠FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。
4. **SMTP 协议**简单邮件传输协议SMTPSimple Mail Transfer Protocol的缩写是一种用于发送电子邮件的协议。注意 ⚠SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。
@ -74,9 +74,10 @@ HTTP/3.0 之前是基于 TCP 协议的,而 HTTP/3.0 将弃用 TCP改用 **
**运行于 UDP 协议之上的协议**
1. **DHCP 协议**:动态主机配置协议,动态配置 IP 地址
2. **DNS**域名系统DNSDomain Name System将人类可读的域名 (例如www.baidu.com) 转换为机器可读的 IP 地址 (例如220.181.38.148)。 我们可以将其理解为专为互联网设计的电话薄。实际上DNS 同时支持 UDP 和 TCP 协议。
3. ……
1. **HTTP 协议HTTP/3.0 ** HTTP/3.0 弃用 TCP改用基于 UDP 的 QUIC 协议 。
2. **DHCP 协议**:动态主机配置协议,动态配置 IP 地址
3. **DNS**域名系统DNSDomain Name System将人类可读的域名 (例如www.baidu.com) 转换为机器可读的 IP 地址 (例如220.181.38.148)。 我们可以将其理解为专为互联网设计的电话薄。实际上DNS 同时支持 UDP 和 TCP 协议。
4. ……
### TCP 三次握手和四次挥手(非常重要)

View File

@ -10,9 +10,9 @@ tag:
1. **基于数据块传输**:应用数据被分割成 TCP 认为最适合发送的数据块,再传输给网络层,数据块被称为报文段或段。
2. **对失序数据包重新排序以及去重**TCP 为了保证不发生丢包,就给每个包一个序列号,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据就可以实现数据包去重。
3. **校验和** : TCP 将保持它首部和数据的检验和。这是一个端到端的检验和目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错TCP 将丢弃这个报文段和不确认收到此报文段。
4. **超时重传** : 当发送方发送数据之后它启动一个定时器等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息ACK。如果发送端实体在合理的往返时延RTT内未收到确认消息那么对应的数据包就被假设为[已丢失](https://zh.wikipedia.org/wiki/丢包)并进行重传
4. **重传机制** : 在数据包丢失或延迟的情况下重新发送数据包直到收到对方的确认应答ACK。TCP 重传机制主要有基于计时器的重传也就是超时重传、快速重传基于接收端的反馈信息来引发重传、SACK在快速重传的基础上返回最近收到的报文段的序列号范围这样客户端就知道哪些数据包已经到达服务器了、D-SACK重复 SACK在 SACK 的基础上,额外携带信息,告知发送方有哪些数据包自己重复接收了)。关于重传机制的详细介绍,可以查看[详解 TCP 超时与重传机制](https://zhuanlan.zhihu.com/p/101702312)这篇文章
5. **流量控制** : TCP 连接的每一方都有固定大小的缓冲空间TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据能提示发送方降低发送的速率防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议TCP 利用滑动窗口实现流量控制)。
6. **拥塞控制** : 当网络拥塞时,减少数据的发送。
6. **拥塞控制** : 当网络拥塞时,减少数据的发送。TCP 在发送数据的时候,需要考虑两个因素:一是接收方的接收能力,二是网络的拥塞程度。接收方的接收能力由滑动窗口表示,表示接收方还有多少缓冲区可以用来接收数据。网络的拥塞程度由拥塞窗口表示,它是发送方根据网络状况自己维护的一个值,表示发送方认为可以在网络中传输的数据量。发送方发送数据的大小是滑动窗口和拥塞窗口的最小值,这样可以保证发送方既不会超过接收方的接收能力,也不会造成网络的过度拥塞。
## TCP 如何实现流量控制?
@ -101,11 +101,21 @@ ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。
连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
**优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。
- **优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。
- **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息中间第三条丢失3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N回退 N表示需要退回来重传已经发送过的 N 个消息。
**缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息中间第三条丢失3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N回退 N表示需要退回来重传已经发送过的 N 个消息。
## 超时重传如何实现?超时重传时间怎么确定?
## Reference
当发送方发送数据之后它启动一个定时器等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息ACK。如果发送端实体在合理的往返时延RTT内未收到确认消息那么对应的数据包就被假设为[已丢失](https://zh.wikipedia.org/wiki/丢包)并进行重传。
- RTTRound Trip Time往返时间也就是数据包从发出去到收到对应 ACK 的时间。
- RTORetransmission Time Out重传超时时间即从数据发送时刻算起超过这个时间便执行重传。
RTO 的确定是一个关键问题,因为它直接影响到 TCP 的性能和效率。如果 RTO 设置得太小,会导致不必要的重传,增加网络负担;如果 RTO 设置得太大会导致数据传输的延迟降低吞吐量。因此RTO 应该根据网络的实际状况,动态地进行调整。
RTT 的值会随着网络的波动而变化,所以 TCP 不能直接使用 RTT 作为 RTO。为了动态地调整 RTOTCP 协议采用了一些算法如加权移动平均EWMA算法Karn 算法Jacobson 算法等这些算法都是根据往返时延RTT的测量和变化来估计 RTO 的值。
## 参考
1. 《计算机网络(第 7 版)》
2. 《图解 HTTP》

View File

@ -11,7 +11,7 @@ MySQL 字符编码集中有两套 UTF-8 编码实现:**`utf8`** 和 **`utf8mb4
为什么会这样呢?这篇文章可以从源头给你解答。
## 何为字符集?
## 字符集是什么
字符是各种文字和符号的统称,包括各个国家文字、标点符号、表情、数字等等。 **字符集** 就是一系列字符的集合。字符集的种类较多,每个字符集可以表示的字符范围通常不同,就比如说有些字符集是无法表示汉字的。
@ -19,9 +19,15 @@ MySQL 字符编码集中有两套 UTF-8 编码实现:**`utf8`** 和 **`utf8mb4
我们要将这些字符和二进制的数据一一对应起来比如说字符“a”对应“01100001”反之“01100001”对应 “a”。我们将字符对应二进制数据的过程称为"**字符编码**",反之,二进制数据解析成字符的过程称为“**字符解码**”。
## 字符编码是什么?
字符编码是一种将字符集中的字符与计算机中的二进制数据相互转换的方法,可以看作是一种映射规则。也就是说,字符编码的目的是为了让计算机能够存储和传输各种文字信息。
每种字符集都有自己的字符编码规则,常用的字符集编码规则有 ASCII 编码、 GB2312 编码、GBK 编码、GB18030 编码、Big5 编码、UTF-8 编码、UTF-16 编码等。
## 有哪些常见的字符集?
常见的字符集有 ASCII、GB2312、GBK、UTF-8……。
常见的字符集有ASCII、GB2312、GB18030、GBK、Unicode……。
不同的字符集的主要区别在于:
@ -64,7 +70,7 @@ GB18030 完全兼容 GB2312 和 GBK 字符集,纳入中国国内少数民族
BIG5 主要针对的是繁体中文,收录了 13000 多个汉字。
### Unicode & UTF-8 编码
### Unicode & UTF-8
为了更加适合本国语言,诞生了很多种字符集。
@ -100,7 +106,7 @@ UTF-32 的规则最简单,不过缺陷也比较明显,对于英文字母这
## MySQL 字符集
MySQL 支持很多种字符编码的方式,比如 UTF-8、GB2312、GBK、BIG5
MySQL 支持很多种字符集的方式,比如 GB2312、GBK、BIG5、多种 Unicode 字符集UTF-8 编码、UTF-16 编码、UCS-2 编码、UTF-32 编码等等)
### 查看支持的字符集
@ -252,13 +258,13 @@ set names utf8mb4
# SET collation_connection = utf8mb4;
```
### jdbc 对连接字符集的影响
### JDBC 对连接字符集的影响
不知道你们有没有碰到过存储 emoji 表情正常,但是使用类似 Navicat 之类的软件的进行查询的时候,发现 emoji 表情变成了问号的情况。这个问题很有可能就是 jdbc 驱动引起的。
不知道你们有没有碰到过存储 emoji 表情正常,但是使用类似 Navicat 之类的软件的进行查询的时候,发现 emoji 表情变成了问号的情况。这个问题很有可能就是 JDBC 驱动引起的。
根据前面的内容,我们知道连接字符集也是会影响我们存储的数据的,而 jdbc 驱动会影响连接字符集。
根据前面的内容,我们知道连接字符集也是会影响我们存储的数据的,而 JDBC 驱动会影响连接字符集。
`mysql-connector-java` jdbc 驱动)主要通过这几个属性影响连接字符集:
`mysql-connector-java` JDBC 驱动)主要通过这几个属性影响连接字符集:
- `characterEncoding`
- `characterSetResults`

View File

@ -9,6 +9,7 @@
- [程序员如何快速学习新技术](./advanced-programmer/programmer-quickly-learn-new-technology.md)
- [程序员的技术成长战略](./advanced-programmer/the-growth-strategy-of-the-technological-giant.md)
- [十年大厂成长之路](./advanced-programmer/ten-years-of-dachang-growth-road.md)
- [美团三年,总结的 10 条血泪教训](./advanced-programmer/meituan-three-year-summary-lesson-10.md)
- [给想成长为高级别开发同学的七条建议](./advanced-programmer/seven-tips-for-becoming-an-advanced-programmer.md)
- [糟糕程序员的 20 个坏习惯](./advanced-programmer/20-bad-habits-of-bad-programmers.md)
- [工作五年之后,对技术和业务的思考](./advanced-programmer/thinking-about-technology-and-business-after-five-years-of-work.md)

View File

@ -323,6 +323,16 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8](https://docs.oracle.
[Web 实时消息推送详解](./system-design/web-real-time-message-push.md)
## 消息队列
### Kafka
[Kafka基础](./mq/kafka/kafka-basis.md)
## 分布式
### 理论&算法&协议

View File

@ -452,11 +452,21 @@ Java 中有 8 种基本数据类型,分别为:
**为什么说是几乎所有对象实例都存在于堆中呢?** 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存
⚠️ 注意:**基本数据类型存放在栈中是一个常见的误区!** 基本数据类型的成员变量如果没有被 `static` 修饰的话(不建议这么使用,应该要使用基本数据类型对应的包装类型),就存放在堆中。
⚠️ 注意:**基本数据类型存放在栈中是一个常见的误区!** 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆中。
```java
class BasicTypeVar{
private int x;
public class Test {
// 成员变量,存放在堆中
int a = 10;
// 被 static 修饰,也存放在堆中,但属于类,不属于对象
// JDK1.7 静态变量从永久代移动了 Java 堆中
static int b = 20;
public void method() {
// 局部变量,存放在栈中
int c = 30;
static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量
}
}
```

View File

@ -431,8 +431,8 @@ public boolean equals(Object anObject) {
> ⚠️ 注意:该方法在 **Oracle OpenJDK8** 中默认是 "使用线程局部状态来实现 Marsaglia's xor-shift 随机数生成", 并不是 "地址" 或者 "地址转换而来", 不同 JDK/VM 可能不同在 **Oracle OpenJDK8** 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode=4 启用第五种。参考源码:
>
> - <https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hpp1127> 行)
> - <https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cpp537> 行开始)
> - <https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hpp>1127 行)
> - <https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cpp>537 行开始)
```java
public native int hashCode();

View File

@ -546,7 +546,7 @@ public E poll() {
//上锁
lock.lock();
try {
//如果队列为空直接返回null反之出队返回元素值
//如果队列为空直接返回null反之出队返回元素值
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
@ -558,13 +558,12 @@ public E poll() {
```java
public boolean add(E e) {
//调用下方的add
return super.add(e);
}
public boolean add(E e) {
//调用offer如果失败直接抛出异常
//调用offer方法如果失败直接抛出异常
if (offer(e))
return true;
else

View File

@ -461,13 +461,13 @@ public class SynchronizedDemo2 {
🧗🏻 进阶一下:学有余力的小伙伴可以抽时间详细研究一下对象监视器 `monitor`
### JDK1.6 之后的 synchronized 底层做了哪些优化?
### JDK1.6 之后的 synchronized 底层做了哪些优化?锁升级原理了解吗?
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
在 Java 6 之后, `synchronized` 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 `synchronized` 锁的效率提升了很多JDK18 中,偏向锁已经被彻底废弃,前面已经提到过了)
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
关于这几种优化的详细信息可以查看下面这篇文章:[Java6 及以上版本对 synchronized 的优化](https://www.cnblogs.com/wuqinglong/p/9945618.html)
`synchronized` 锁升级是一个比较复杂的过程,面试也很少问到,如果你想要详细了解的话,可以看看这篇文章:[浅析 synchronized 锁升级的原理与实现](https://www.cnblogs.com/star95/p/17542850.html)
### synchronized 和 volatile 有什么区别?

View File

@ -91,33 +91,30 @@ ExecutorService threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSiz
**2、自己实现 `ThreadFactory`。**
```java
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程工厂,它设置线程名称,有利于我们定位问题。
*/
public final class NamingThreadFactory implements ThreadFactory {
private final AtomicInteger threadNum = new AtomicInteger();
private final ThreadFactory delegate;
private final String name;
/**
* 创建一个带名字的线程池生产工厂
*/
public NamingThreadFactory(ThreadFactory delegate, String name) {
this.delegate = delegate;
this.name = name; // TODO consider uniquifying this
public NamingThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(Runnable r) {
Thread t = delegate.newThread(r);
Thread t = new Thread(r);
t.setName(name + " [#" + threadNum.incrementAndGet() + "]");
return t;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

View File

@ -0,0 +1,213 @@
---
title: Kafka基础
category: 消息队列
tag:
- 消息队列
head:
- - meta
- name: keywords
content: Kafka基础
- - meta
- name: description
content: Kafka是一个分布式系统由服务器和客户端组成通过高性能的TCP网络协议进行通信。它可以部署在本地和云环境中的裸机硬件、虚拟机和容器上。
---
## 什么是Kafka
**KafKa**是一个**分布式**的基于**发布/订阅模式**的消息队列主要应用于大数据实时处理领域。Kafka由**服务端**和**客户端**组成通过高性能的TCP网络协议进行通信。它可以部署在本地和云环境中的裸机硬件、虚拟机和容器上。
+ 服务端Kafka作为一个或多个服务器集群运行其中部分服务器构成了存储层Brokers。其他服务器作为[Kafka Connect](https://kafka.apache.org/documentation/#connect)以事件流的形式持续导入和导出数据。同时Kafka集群具有高度的**可扩展性**和**容错性**:如果其中任何一台服务器出现故障,其他服务器将接管其工作,以确保其保持持续运行状态。
+ 客户端:提供接口编写分布式和微服务程序,以并行、大规模和容错的方式读取、写入和处理事件流。
## Kafka的使用背景为什么要使用Kafka
这道题可以理解为为什么要使用消息队列? (消息队列的作用?优点?)
+ **缓冲和削峰**:消息队列在应对类似双十一这样的突发高流量场景中发挥着关键的作用,它可以被视为一个非常有效的**缓冲**和**削峰**机制。考虑以下情形:当突然涌入大量订单请求时,下游的处理服务器可能不具备足够的计算资源来立即处理这些请求。直接将这些请求传递给下游服务器可能导致其超负荷运行,甚至崩溃。消息队列通过将这些突发的订单流量缓存到消息队列中,允许订单处理端按照其自身的处理能力逐一从消息队列中提取订单并进行处理。这种方式有效地平滑了流量高峰,确保了系统的稳定性。因此,消息队列在这里扮演了一个关键的角色,既能够充当缓冲,将请求暂时保存在队列中,又能够削减流量高峰,防止直接冲击到下游服务器,从而实现了系统的平稳运行。
+ **解耦和扩展性**:在项目开发中,由于需求的不确定性,消息队列充当了一个关键的接口层,通过将关键的业务流程解耦。这种解耦使得在后续业务需要扩展时,只需遵循约定并进行数据编程,就能轻松实现所需的扩展能力。
+ **异步通信**:消息队列提供了一种强大的机制,允许用户将消息放入队列中,而无需立即处理它们。这种异步处理方式可以显著提高业务处理速度,例如在需要发送短信验证码的用户注册等场景中,业务主线程可以将发送短信验证码的任务放入消息队列,然后继续处理其他业务,而无需等待短信发送完成。这种机制极大地提高了系统的效率和响应性。
+ **可恢复性**:即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
## Kafka的使用场景
+ **日志收集**:一个公司可以用 Kafka 可以收集各种服务的 log通过 Kafka 以统一接口服务的方式开放给各种 consumer。也就是在系统各个运行的位置将日志输送到一个统一的地方进行保存和处理。
+ **消息系统**:将业务进行解耦合,分成消息的生产者和消费者,实现异步通信、可恢复、解耦和缓冲与削峰。
+ **用户行为跟踪**Kafka 经常被用来记录web用户或者 app 用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到 kafka 的 Topic 中然后订阅者通过订阅这些topic来做实时的监控分析或者装载到 Hadoop、数据仓库中做离线分析和挖掘。
## Kafka架构
<img src="kafka-basis.assets/image-20230929154749134.png" alt="image-20230929154749134" style="zoom:50%;" />
+ **Producer**(生产者):生产者可以将数据发布到所选择的topic主题中。生产者负责将记录分配到topic的指定分区partition这里可以使用多个partition循环发送来实现多个server负载均衡。
+ **Consumer**(消费者): 消息消费者,从 Kafka Broker 取消息的客户端。
+ **Broker**Broker是kafka的服务节点一个Broker就是一个服务节点即Kafka服务器。一个broker可以容纳多个topic。broker可以看作事消息的代理Producers往Brokers里面指定的Topic写消息Consumers从Brokers里面拉取指定的消息然后进行业务处理broker在中间起到一个代理保存消息的中转站。
+ **Topic**(主题):可以理解为一个队列,一个 Topic 又分为一个或多个分区。
+ **Partition**分区Topic 是一个逻辑的概念,它可以细分为多个分区,每个分区只属于单个主题。 同一个主题下不同分区包含的消息是不同的分区在存储层面可以看作一个可追加的日志Log文件消息在被追加到分区日志文件的时候都会分配一个特定的偏移量Offset
+ **Offset**偏移量是消息在分区中的唯一标识Kafka 通过它来保证消息在分区内的顺序性,不过 Offset 并不跨越分区也就是说Kafka 保证的是分区有序性而不是主题有序性,即局部有序。
+ **Replication** (副本):是 Kafka 保证数据高可用的方式Kafka 同一 Partition 的数据可以在多 Broker 上存在多个副本,通常只有主副本对外提供读写服务,当主副本所在 Broker 崩溃或发生网络一场Kafka 会在 Controller 的管理下会重新选择新的 Leader 副本对外提供读写服务。
+ **Record** :实际写入 Kafka 中并可以被读取的消息记录。每个 Record 包含了 key、value 和 timestamp。
+ **Consumer Group** 消费者组CG消费者组内每个消费者负责消费不同分区的数据提高消费能力。一个分区只能由组内一个消费者消费消费者组之间互不影响。所有的消费者都属于某个消费者组即消费者组是逻辑上的一个订阅者。
## Kafka为什么要分区Kafka分区的目的
+ **提供并行处理能力**通过将消息分散到多个分区Kafka可以实现消息的并行处理。消费者可以独立地从不同的分区中读取消息从而提高整体的处理能力。
+ **提高可靠性和可伸缩性**Kafka通过复制机制实现数据的可靠性和冗余存储每个分区可以配置多个副本这些副本分布在不同的Broker节点上当一个副本不可用时可以使用其他副本来继续提供服务。同时通过增加分区的数量可以增加整个系统的处理能力、存储容量和实现负载均衡提高并发度提高效率。
## Kafka如何实现消息有序性
kafka中每一个partition中的消息在写入的时候都是有序的而且单独一个partition只能由一个消费者去消费可以在里面保证消息的顺序性但是分区之间的额消息是不保证有序的。总结就是kafka只保证了单个partition的有序性并没有保证多个partition的有序性因为如果需要保证多个partition的有序性那么整个kafka就退化成了单一队列毫无并发性可言了。
**那如果需要保证全局的有序性怎么办呢?**
1. 创建一个Topic只创建一个Partition这样就不会存在多个partition也自然是全局有序的了。
2. 生产者发送消息的时候发送到指定的partition。
## Kafka为什么这么快
kafka会把接收到的信息都写入硬盘中来保证消息的不丢失。为了优化写入速度Kafka采用了顺序写入和MMFile两个技术。
**写入数据**
1. 顺序写入:因为硬盘是机械结构,每次读写都会寻址->写入其中寻址是一个“机械动作”它是最耗时的。所以硬盘最讨厌随机I/O最喜欢顺序I/O。为了提高读写硬盘的速度Kafka就是使用顺序I/O。
2. MMFile即使是顺序写入硬盘的访问速度还是与内存速度有较大的差距。因此Kafka并不是实时写入硬盘的它还利用了操作系统的分页存储来利用内存提高I/O效率。
> Memory Mapped Files(后面简称mmap)也被翻译成 内存映射文件 在64位操作系统中一般可以表示20G的数据文件它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上操作系统在适当的时候
**读取数据**
1. 基于sendfile实现**零拷贝**,减少拷贝次数。
> 零拷贝是指计算机执行IO操作时CPU不需要将数据从一个存储区域复制到另一个存储区域从而可以减少上下文切换以及CPU的拷贝时间。它是一种`I/O`操作优化技术。(减少用户态与内核态之间的数据复制次数)
>sendfile具体流程如下
>
>1. sendfile系统调用文件数据被copy至内核缓冲区
>2. 从内核缓冲区copy至内核中socket相关的缓冲区
>3. socket相关的缓冲区copy到协议引擎
2. 批量压缩它把所有的消息都变成一个批量的文件并且进行合理的批量压缩减少网络IO损耗。Producer使用GZIP或者Snappy格式对消息几个进行压缩压缩的好处就是减少传输的数据量减轻对网络传输的压力。
**文件分段**
kafka 的队列topic被分为了多个区partition每个partition又分为多个段segment所以一个队列中的消息实际上是保存在N多个片段文件中。通过分段的方式每次文件操作都是对一个小文件的操作非常轻便同时也增加了并行处理能力。
**批量发送**
Kafka 允许进行批量发送消息先将消息缓存在内存中然后一次请求批量发送出去比如可以指定缓存的消息达到某个量的时候就发出去或者缓存了固定的时间后就发送出去如100条消息就发送或者每5秒发送一次这种策略将大大减少服务端的I/O次数。
## Kafka中的消息是否会丢失和重复消费
Kafka在**生产端**发送消息和**消费端**消费消息时都可能会**丢失**一些消息。
### Producer消息丢失
生产者在发送消息时会有一个ack机制当acks=0或者acks=1时都可能会丢失消息。
> 背景知识Producer发送消息时是直接与Broker中的Leader Partition进行交互的然后其他的副本再从Leader Partition中进行数据的同步。因此在发送消息的时候Producer只需要找到对应Topic的Leader Partition进行消息发送即可。
>
> 消息发送的流程:
>
> 1. 将消息发送到对应Topic下的Leader Partition
> 2. Leader Partition收到消息并将消息写入Page Cache定时刷盘进行持久化顺序写入磁盘
> 3. Foller Partition 拉取Leader Partition的消息并同Leader Partition的数据保持一致待消息拉取完毕后再给Leader Partition回复ack确认消息。
> 4. 待Leader与Foller 同步完数据并收到所有ISR中的Replica副本的ack后Leader Partition会给Producer回复ack确认消息。
Producer端为了提升发送效率减少I/O操作发送数据的时候是将多个请求合并成一个个RecordBatch并将其转换成为Request请求**异步**将数据发送出去或者按时间间隔方式每隔一定的时间自动发送出去因此Producer端消息丢失更多是因为消息根本没有发送到Kafka Broker端。
因此,**导致Producer端消息没有成功发送有以下原因**
1. 网络原因由于网络原因数据根本没有到达Broker端。
2. 数据原因消息太大超出Broker承受的范围导致Broker拒收消息。
**Producer消息确认机制**
Producer端配置了消息确认机制来确认消息是否生产成功使用ack确认机制。
1. asks=0:只要发送就自认为成功并不进行消息接收成功的ack确认。
1. 不能保证消息是否发送成功。
2. 生产环境完全不可用。
2. acks=1:当Leader Partition接收成功时进行ack确认确认后表示成功
1. 只要Leader Partition存活就可以保证不丢失保证了吞吐量。
2. 生产环境中如果需要保证吞吐量可以用这个。
3. acks=-1或者all所有Leader Partition和Foller PartitionISR都接收成功时进行ack确认确认后表示成功。
1. 保证消息不丢失,但是吞吐量低。
2. 生产环境要求数据不能丢失可以采用该方式。
### Broker端丢失场景
Broker接收到数据后会将数据进行持久化存储到磁盘为了提高吞吐量和性能采用的是**异步批量刷盘的策略**也就是说按照一定的消息量和时间间隔进行刷盘这一点和mysql、redis很像。首先数据会背存储到**PageCache**中,至于什么时候将 Cache 中的数据刷盘是由「**操作系统**」根据自己的策略决定或者调用 fsync 命令进行强制刷盘,如果此时 Broker 宕机 Crash 掉,且选举了一个落后 Leader Partition 很多的 Follower Partition 成为新的 Leader Partition那么落后的消息数据就会丢失。既然Broker是异步刷盘的那么数据就有可能会丢失比如刷盘之前操作系统崩了并且Kafka中没有提供**同步刷盘**机制。)
虽然Kafka 通过「**多 Partition (分区)多 Replica副本机制」**已经可以最大限度的保证数据不丢失,但是当数据已经写入 PageCache 中但是还没来得及刷写到磁盘,此时如果所在 Broker 突然宕机挂掉或者停电,极端情况还是会造成数据丢失。
### Consumer端丢失场景剖析
> Consumer通过Pull模式主动的去Kafka集群中拉消息
>
> 1. 在消息拉取的过程中,有个消费者组的概念,多个 Consumer 可以组成一个消费者组即 Consumer Group每个消费者组都有一个Group-Id。同一个 Consumer Group 中的 Consumer 可以消费同一个 Topic 下不同分区的数据,但是不会出现多个 Consumer 去消费同一个分区的数据。
> 2. 拉取到消息后进行业务逻辑处理,待处理完成后,会进行 ACK 确认,即提交 Offset 消费位移进度记录。
> 3. 最后 Offset 会被保存到 Kafka Broker 集群中的 **__consumer_offsets** 这个 Topic 中,且每个 Consumer 保存自己的 Offset 进度。
Consumer端丢失消息主要体现在**消费端offset的自动提交**如果开启了自动提交万一消费到数据还没处理完此时consumer直接宕机未处理完的数据丢失了下次也消费不到了因为offset已经提交完毕下次会从offset处开始消费新消息。这种丢失情况的解决方法是**采用消费端的手动提交**
### 消息重复消费
**生产端消息重复发送**
生产端发送一条消息但是未得到broker的ack生产端又重新发了一条消息。这个时候两条消息都被broker接收到了消费端从broker拉取消息时就会造成重复消费。
> kafka新版本已经在broker中保证了接收消息的幂等性比如2.4版本),只需在生产者加上参数 props.put(“enable.idempotence”, true) 即可默认是false不开启。
>
> 新版本解决方案是producer发送消息时加上PID和Sequence NumberPID是Producer的唯一IDSequence Number是数据的序列号。
>
> broker接收到消息的时候就会检查有没有收到过这个消息根据PID和Sequence Number
**消费端消息重复消费**
消费端拉取一部分数据消费完成之后提交offset之前挂掉了此时offset未提交当前消息就会被重复消费。
解决办法添加分布式锁在offset提交之后再删key这样就保证了同一个消息只会被消费一次。
## Kafka顺序消息
Kakfa如果需要保证消息的顺序性则需要牺牲一定的性能。具体的顺序方式就是使用单一的消费者由一个消费者消费可以保证消息消费的顺序性但是消息发送的顺序性还是无法保证因为消息发送端有重传机制如果一次性发送两条消息前一条消息发送失败引发重传就会导致消息发送乱序。此时如果需要保证发送和接收的顺序那就使用发送的ack机制确认发送成功之后再发送下一条消息并且只能有一个Partition。但是这种方式会导致kafka性能低下。
**高效的解决方式**
类似于tcp发送的方式给每一个消息添加一个序号然后消费端每次拉取全部消息拉取回来之后再排序根据排序之后的数据进行处理。
## Kafka与其它MQ之间的区别为什么选择使用Kafka
**kafka相对于rocketMQ、rabbitMQ来说与它们最大的区别就是分布式存储这也是kafka高性能的最主要原因**。使用分布式存储理念,一个主题下多个分区,同时可以被多个消费者和生产者去使用,也增加了接受消息和消费消息的能力!
## 参考
+ Kafka官方文档https://kafka.apache.org/documentation
+ Kafka 设计架构原理详细解析https://blog.csdn.net/qq_32828253/article/details/110732652
+ Kafka为什么这么快https://zhuanlan.zhihu.com/p/147054382
+ Kafka如何保证消息不丢失https://zhuanlan.zhihu.com/p/459610418
+ kafka专题kafka的消息丢失、重复消费、消息积压等线上问题汇总及优化https://blog.csdn.net/qq_45076180/article/details/111561984

View File

@ -555,8 +555,8 @@ public class GlobalExceptionHandler {
### Spring 管理事务的方式有几种?
- **编程式事务**:在代码中硬编码(推荐使用) : 通过 `TransactionTemplate`或者 `TransactionManager` 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助
- **声明式事务**:在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于`@Transactional` 的全注解方式使用最多)
- **编程式事务**:在代码中硬编码(在分布式系统中推荐使用) : 通过 `TransactionTemplate`或者 `TransactionManager` 手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小
- **声明式事务**:在 XML 配置文件中配置或者直接基于注解(单体应用或者简单业务系统推荐使用) : 实际是通过 AOP 实现(基于`@Transactional` 的全注解方式使用最多)
### Spring 事务中哪几种事务传播行为?
@ -598,13 +598,9 @@ public class GlobalExceptionHandler {
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
@ -632,9 +628,29 @@ public enum Isolation {
`Exception` 分为运行时异常 `RuntimeException` 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
`@Transactional` 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
`@Transactional` 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
`@Transactional` 注解中如果不配置`rollbackFor`属性,那么事务只会在遇到`RuntimeException`的时候才会回滚,加上 `rollbackFor=Exception.class`,可以让事务在遇到非运行时异常时也回滚。
`@Transactional` 注解默认回滚策略是只有在遇到`RuntimeException`(运行时异常) 或者 `Error` 时才会回滚事务,而不会回滚 `Checked Exception`(受检查异常)。这是因为 Spring 认为`RuntimeException`和 Error 是不可预期的错误,而受检异常是可预期的错误,可以通过业务逻辑来处理。
![](https://oss.javaguide.cn/github/javaguide/system-design/framework/spring/spring-transactional-rollbackfor.png)
如果想要修改默认的回滚策略,可以使用 `@Transactional` 注解的 `rollbackFor``noRollbackFor` 属性来指定哪些异常需要回滚,哪些异常不需要回滚。例如,如果想要让所有的异常都回滚事务,可以使用如下的注解:
```java
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
// some business logic
}
```
如果想要让某些特定的异常不回滚事务,可以使用如下的注解:
```java
@Transactional(noRollbackFor = CustomException.class)
public void someMethod() {
// some business logic
}
```
## Spring Data JPA