I/O多路复用

I/O多路复用的含义

IO多路复用(I/O Multiplexing)是指在一个线程或进程中,通过某种机制同时处理多个I/O操作的技术。它通常用于高并发的网络编程中,尤其是在处理大量客户端请求时,能够有效提高系统的性能和响应速度。

在传统的阻塞I/O模型中,操作系统为每个I/O请求分配一个线程或进程,每个线程都会等待I/O操作完成,这样在高并发的情况下就会造成大量的资源浪费。而IO多路复用通过使单个线程能够同时管理多个I/O操作,避免了创建大量线程,从而提高了系统的效率。

常见的IO多路复用技术:

select:
select是最早的IO多路复用机制,允许一个线程监视多个文件描述符(通常是网络连接),并在其中任何一个文件描述符就绪时通知应用程序进行处理。
限制:每次调用时最多只能监视1024个文件描述符(操作系统对select的限制),而且效率较低。

poll:
poll与select类似,但是它不受文件描述符数量的限制,可以支持更多的文件描述符。
但是,poll依然存在遍历所有文件描述符的性能瓶颈,特别是在处理大量连接时,性能较低。

epoll(Linux特有):
epoll是Linux下的一种高效IO多路复用机制,它通过事件驱动的方式,只关心那些发生了事件的文件描述符,避免了遍历所有文件描述符的开销。
支持水平触发(Level-Triggered, LT)和边缘触发(Edge-Triggered, ET)两种模式,能够有效提高高并发情况下的性能。

kqueue(BSD和macOS特有):
kqueue是BSD系统上的IO多路复用机制,功能与epoll类似,也支持高效的事件通知机制。

应用场景

  1. 高并发网络编程(Redis,Nginx,数据库连接池等等);
  2. 大量并发I/O操作的场景,如处理多个客户端请求、文件传输等;

和HTTP协议多路复用的区别

概念:
I/O多路复用是操作系统的技术,允许在一个线程或进程中同时处理多个 I/O 操作(通常是网络连接)。
HTTP/2多路复用 是一种协议机制,允许多个 HTTP 请求和响应在同一个 TCP 连接上并行传输,减少了因多次建立连接而带来的开销。

目的:
I/O多路复用主要目的是提高高并发场景下的资源利用效率,减少线程开销。
HTTP/2多路复用主要目的是减少 HTTP 协议中多个请求/响应之间的延迟,提高带宽利用率。

工作原理:
I/O多路复用指的是操作系统提供的机制,允许一个线程或进程同时监视多个 I/O 操作(通常是文件描述符、网络套接字等),操作系统提供了 select、poll、epoll 等系统调用,允许一个线程同时等待多个文件描述符上的事件(例如,网络数据是否可读、是否可写)。当其中某个文件描述符上的事件发生时,操作系统会通知应用程序,应用程序随后处理该事件。
HTTP/2 的多路复用是指在一个 TCP 连接上同时并行地发送多个请求和响应。不同于 HTTP/1.x 中的每个请求和响应必须各自占用一个独立的 TCP 连接,HTTP/2 的多路复用允许多个 HTTP 请求和响应在同一个连接上交替进行,不需要等待前一个请求完成后才能开始下一个请求。
HTTP/2 使用 二进制分帧(binary framing) 的方式,将请求和响应分解为多个小的帧(frame),然后将这些帧通过一个连接交错发送。每个请求和响应都会被分配一个唯一的流标识符(stream identifier),可以在同一个连接中并发地发送多个流,而不需要等待其他流的完成。
这种机制是通过使用一个 流(stream)来标识不同的请求和响应,每个流包含多个 帧(frame),这些帧可以在网络上交替传输。因为 HTTP/2 只使用一个 TCP 连接,所以可以有效避免 HTTP/1.x 中“头阻塞”问题(head-of-line blocking)。

I/O 多路复用与 Go channel

Go 的 channel 在底层也利用了 I/O 多路复用 的概念,尤其是在实现高并发、协程间通信时。虽然 Go 的 channel 并不是直接操作文件描述符或网络连接,但 Go 的 调度器(Goroutine Scheduler)channel 的实现 使用了类似于 I/O 多路复用的技术来高效地处理大量并发的任务。

Go 中的 channel 实现并不直接使用操作系统的 I/O 多路复用机制(如 epollselect),但它与这些技术在概念上非常相似,具体来说:

  • select 语句:Go 的 select 语句允许一个 Goroutine 在多个 channel 上进行阻塞操作,直到某个 channel 就绪。这种机制类似于操作系统中的 I/O 多路复用,允许程序在多个 I/O 操作之间进行选择,直到其中一个操作完成。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    select {
    case msg1 := <-ch1:
    // 处理 ch1 的消息
    case msg2 := <-ch2:
    // 处理 ch2 的消息
    case ch3 <- 3:
    // 向 ch3 发送数据
    default:
    // 没有任何 case 就绪时执行
    }

    这里的 select 语句会阻塞当前 Goroutine,直到其中一个 channel 的操作(发送或接收)完成。它会在多个 channel 上等待,类似于操作系统如何在多个文件描述符上等待事件。

  • 调度器(Scheduler):Go 的调度器(Goroutine Scheduler)也使用了类似于 I/O 多路复用的事件驱动机制来处理成千上万的 Goroutines。在高并发场景下,Go 调度器能够在单个线程上调度数以千计的 Goroutines,这得益于 Go 对 Goroutine 的高效管理和调度。每个 Goroutine 的状态变化(如从阻塞到就绪)都会触发调度器的处理,这类似于 I/O 多路复用在操作系统中对事件的监听和分发。

Go channel 底层实现

Go 的 channel 是由运行时(runtime)实现的,它与底层的调度器和事件循环机制密切相关。虽然 channel 的实现并不直接操作文件描述符,但它使用了类似的技术来避免大量的阻塞和等待。具体而言,Go 会将阻塞的 Goroutines 放入 等待队列,并通过事件通知机制(即调度器)唤醒相应的 Goroutine。

当你在多个 channel 上使用 select 语句时,Go 调度器通过类似于 I/O 多路复用的机制等待 channel 上的事件发生(例如,某个 channel 变为可读或可写),然后调度相应的 Goroutine 执行。这种处理方式类似于 I/O 多路复用在网络编程中的应用。

虽然 Go 的 channel 实现本身并不直接依赖于操作系统级别的 I/O 多路复用(如 epollselect 等),但 Go 的调度器和 channel 的实现使用了类似的事件驱动模型。在多个 channel 上等待事件时,Go 的调度器会高效地管理多个 Goroutines,并且避免了传统的阻塞模型所带来的性能问题。因此,Go 中的 channelselect 语句在并发控制上使用了与 I/O 多路复用相似的思想,以实现高效的协程调度和通信。

Linux服务器网络问题排查命令

测试与目标主机连通性

1
ping URL

测试目标主机指定端口是否开放

1
telnet URL port

追踪本机到目标主机网络的路径以及途径各网络节点的延迟

1
traceroute URL

可以查看网络传输数据包传输的实际路径

域名解析查询

1
nslookup URL

用于诊断域名解析是否正确或确定域名解析指向了哪台服务器

查看本机路由表

1
2
3
ip route
ifconfig
netstat -r

Linux服务器性能指标过载排查思路

当 Linux 服务器 的 CPU、内存 或 网络 出现过载时,通常意味着系统正在面临资源瓶颈或异常行为。为了排查这个问题,需要通过一系列的系统工具和方法来诊断和识别瓶颈的根本原因。以下是一些常见的排查步骤:

  1. 检查 CPU 使用情况

查看整体 CPU 使用情况:
• 使用 top 或 htop 命令查看当前系统的 CPU 使用率。

1
top

htop 提供了更加友好的界面,可以使用它来动态地查看 CPU 负载。
• %us:用户空间的 CPU 使用比例
• %sy:内核空间的 CPU 使用比例
• %id:空闲 CPU 时间
• %wa:等待 I/O 的 CPU 时间
• %st:虚拟机偷取的 CPU 时间

查看每个进程的 CPU 使用情况:
• 使用 top 或 ps 命令查看当前占用 CPU 的进程。

1
top -o %CPU

或者使用 ps 列出占用 CPU 的进程:

1
ps aux --sort=-%cpu | head -n 10

检查是否有进程陷入 无响应状态:
• 如果进程处于 D(uninterruptible sleep) 状态,表示它在等待 I/O 操作完成,可能导致 CPU 被阻塞。

1
ps aux | grep ' D '

检查系统负载(load average):
• 使用 uptime 或 top 命令查看系统负载:

1
uptime

负载均衡值(load average)显示的是在过去 1 分钟、5 分钟和 15 分钟内的平均进程数。高负载可能意味着系统 CPU 资源紧张。

  1. 检查内存使用情况

使用 free 命令查看内存使用:
• free -m 可以查看系统内存的使用情况(单位是 MB)。

1
free -m
•	Mem:总内存、已用内存、剩余内存。
•	Swap:交换分区的使用情况。高 swap 使用可能意味着物理内存不足,导致系统频繁交换数据到磁盘。

检查进程的内存占用情况:
• 使用 top 或 ps 命令查看占用内存最多的进程。

1
top -o %MEM

或者使用 ps 查看内存使用最多的进程:

1
ps aux --sort=-%mem | head -n 10

检查是否有 内存泄漏:
• 如果系统的可用内存持续下降,可以使用 smem(或者 pmap)查看各个进程的内存使用情况,排查是否有内存泄漏。

1
smem -r

使用 vmstat 查看虚拟内存情况:
• vmstat 提供了系统的内存、交换区、I/O、进程、CPU 等信息。

1
vmstat 1
•	si 和 so 分别表示交换内存的输入和输出。高交换活动通常意味着物理内存不足。

检查内存是否有 OOM(Out of Memory) 错误:
• 如果系统没有足够的内存,它可能会启动 OOM Killer 来终止一些进程以释放内存。可以通过查看 /var/log/messages 或 /var/log/syslog 日志文件来确认是否发生了 OOM 错误。

1
cat /var/log/syslog | grep -i "out of memory"
  1. 检查网络使用情况

使用 netstat 或 ss 查看网络连接:
• netstat 或 ss 可以查看当前的网络连接及其状态。

1
netstat -tulnp

或者:

1
ss -tuln
•	通过 -t 查看 TCP 连接,-u 查看 UDP 连接,-l 查看正在监听的端口,-n 显示数字格式。

检查网络带宽使用情况:
• 使用 iftop 或 nload 监控网络流量。

1
iftop

或者:

1
nload

这些工具可以帮助你实时查看每个连接的流量情况,找出是否存在异常的流量高峰。

检查 TCP 状态:
• 使用 ss 查看 TCP 连接的状态,查找是否存在大量的 TIME_WAIT 或 CLOSE_WAIT 状态的连接,可能意味着有大量的关闭连接或连接未正常关闭。

1
ss -s

你可以检查 TIME_WAIT 和 CLOSE_WAIT 状态,如果这些连接积压过多,可能导致网络性能下降。

网络丢包或延迟:
• 使用 ping 测试到其他主机的连通性,检查是否有丢包现象:

1
ping -c 10 <destination_ip>

你可以查看是否存在高延迟或丢包,丢包可能指示网络硬件或带宽问题。

  1. 检查磁盘 I/O 使用情况

使用 iostat 查看磁盘 I/O 情况:
• 使用 iostat 检查磁盘的读取/写入情况。

1
iostat -x 1
•	%util:磁盘利用率,如果值接近 100%,表示磁盘繁忙。
•	await:磁盘操作的平均等待时间,较高的 await 值可能表示磁盘性能瓶颈。

使用 dstat 或 iotop 监控磁盘 I/O:
• dstat 或 iotop 可以显示实时的磁盘 I/O 使用情况,帮助你定位是否有某些进程正在占用大量磁盘 I/O。

1
dstat -cdlmn

或者使用 iotop:

1
sudo iotop
  1. 分析日志文件
    • 查看 /var/log/syslog、/var/log/messages、/var/log/dmesg 等系统日志,可能会有有关硬件故障、磁盘问题或其他异常行为的警告或错误信息。

    1
    2
    cat /var/log/syslog | grep -i "error"
    cat /var/log/messages | grep -i "error"
  2. 其他工具
    • sar(System Activity Reporter):可以查看过去的系统性能数据,帮助你发现历史上的问题。

    1
    2
    sar -u 1 3  # 查看 CPU 使用情况
    sar -r 1 3 # 查看内存使用情况

    • dmesg:检查内核日志,查看是否有硬件故障或内存错误。

1
dmesg | grep -i error

总结

当 Linux 服务器出现 CPU、内存或网络过载时,需要结合多种工具来诊断问题。通过 top/htop 查看 CPU 和进程状态,使用 free/iostat 监控内存和磁盘使用情况,利用 iftop/ss 排查网络瓶颈,以及通过日志文件了解系统错误和警告。系统性能问题往往是多因素的,定位问题时需要逐步排查。

GPU工作原理

GPU和CPU的区别

CPU架构设计
CPU
CPU核心参数
CPU
GPU架构设计
GPU
GPU核心参数
GPU
在CPU的架构中包含多级缓存,各种复杂操作指令集,CPU的架构设计是为了支持复杂的逻辑运算,每个核心都是完整通用的处理器单元,且其工作模式实际上是串行执行任务的,GPU的架构从设计之初就是专门用于处理图像和视频等图形数据,具有大量的流处理器和着色器,且其工作模式是并行执行任务的,从规格也可以看出除了核心数外同期的GPU比CPU有更高的时钟频率和内存带宽。
总结下来GPU和CPU的主要区别在以下几点:

  1. 并行计算能力:
    GPU:GPU设计为大规模的并行计算,具有数千个小型处理核心,这些核心能够同时执行大量的计算任务。深度学习中的许多计算(如矩阵乘法、卷积运算等)都可以高度并行化。GPU能够同时处理多个数据流(例如,多个神经元的计算),从而大大提高了训练效率。
    CPU:CPU一般只有少数几个(通常为4到16个)强大的核心,适合处理顺序执行的任务。CPU适合执行需要复杂决策和高频任务的应用,但在深度学习中的大规模矩阵计算、卷积等任务上,其并行能力远逊色于GPU。
  2. 内存带宽:
    GPU:GPU通常配备更宽的内存带宽,允许其快速访问和处理大规模的数据集。例如,现代GPU如NVIDIA的A100、V100、H100等,具有高达1TB/s的内存带宽。这使得GPU在处理大规模数据集时能够更快地读取和写入数据,从而提高了训练速度。
    CPU:虽然CPU的内存带宽也在不断提高,但通常远低于GPU。CPU的内存带宽有限,这使得它在处理深度学习中的大规模数据时,可能成为瓶颈,尤其是对于需要大量数据交换的神经网络模型(如大规模图像、视频处理等)。

适合GPU计算的场景

  1. 图形渲染和计算机视觉
    图形渲染:GPU最初的设计目的是加速图形渲染,特别是3D图形。GPU在计算机图形学中通过执行大量并行的图形计算(如光照、阴影、纹理映射等)来加速渲染过程。
    应用:视频游戏、虚拟现实(VR)、增强现实(AR)、3D建模和动画制作。
    计算机视觉:GPU可以加速图像处理、目标检测、图像分割、面部识别、自动驾驶汽车中的视觉系统等任务。
    应用:安防监控、医学影像分析、自动驾驶、工业视觉检测。
  2. 大规模矩阵计算
    GPU:深度学习中的核心任务(如反向传播中的梯度计算和前向传播中的矩阵乘法)是矩阵计算密集型的。GPU特别适合这类任务,因为它能通过并行计算同时处理矩阵中的多个元素。NVIDIA的GPU通过CUDA等库进一步优化了这些运算,使得在训练深度学习模型时,GPU能够极大提升计算效率。
    CPU:虽然CPU也能进行矩阵计算,但由于缺乏大规模的并行处理能力,CPU在执行大规模矩阵计算时远远不如GPU高效。
  3. 自然语言处理(NLP)
    加速NLP任务:GPU在处理大规模文本数据、训练和推理自然语言处理模型(如Transformer、BERT、GPT等)时表现出色。NLP模型通常需要大量的计算资源来处理文本序列,GPU能够显著加速这些计算。
    应用:机器翻译、语音识别、聊天机器人、情感分析、文本生成。
  4. 医学影像处理
    CT、MRI图像处理:医学影像处理中常常需要进行大规模的数据分析和图像处理,GPU能够加速图像重建、分割、识别等任务。
    自动诊断:Nvidia的GPU通过CUDA可以加速基于深度学习的自动化诊断系统,如利用卷积神经网络(CNN)进行肿瘤检测、器官分割等任务。

深度学习和神经网络

深度学习神经网络是紧密相关的概念,但它们并不完全相同。以下是它们之间的关系及区别:

1. 神经网络(Neural Networks)

神经网络是模仿生物神经系统的计算模型,尤其是大脑神经元之间的连接结构。在人工智能领域,神经网络被用来进行数据处理和模式识别。

基本概念:

  • 神经网络(Neural Network):由一组互相连接的节点(即神经元)组成,这些神经元通过权重相连。神经网络可以进行监督学习和非监督学习,用于完成分类、回归等任务。
  • 层次结构:神经网络通常包含输入层、隐藏层和输出层。每一层包含若干神经元(节点),每个神经元接受前一层的输出,并通过激活函数处理后传递给下一层。
  • 激活函数:通常使用如ReLU、Sigmoid、Tanh等激活函数来增加网络的非线性能力。

传统神经网络(浅层神经网络):

  • 神经网络最初并不深,通常只有一层或两层隐藏层(即浅层神经网络)。这些浅层网络对于一些简单的任务(如线性可分问题)效果良好。
  • 不足:浅层神经网络无法有效地处理复杂、高维的数据和任务(如图像识别、自然语言处理等),并且存在梯度消失和梯度爆炸的问题。

2. 深度学习

深度学习是神经网络的一种拓展,它指的是具有多个隐藏层(即深层结构)的神经网络模型。深度学习的“深度”通常指的是网络中有多个隐藏层,而这些层次使得模型能够自动学习和提取数据的高级特征。

基本概念:

  • 深度神经网络:深度学习中的神经网络通常包含很多隐藏层,这些层使得模型能够逐步提取数据的高层次特征。
  • 多层学习:每一层可以从前一层的输出中学习到更复杂的特征。比如,在图像识别中,浅层可以学习边缘和简单形状,中间层可以学习纹理和局部结构,深层可以学习复杂的对象或场景。
  • 反向传播算法:深度学习模型通常使用反向传播(backpropagation)和梯度下降等优化方法来训练网络,使得模型能够逐渐减少损失函数的值,找到最佳的权重参数。

主要特点:

  • 深度结构:深度学习网络有多个隐藏层,通常包含上百或上千个神经元。
  • 自我特征提取:深度学习能够自动从原始数据中提取特征,而无需人工手动设计特征。这使得深度学习在处理复杂任务(如语音识别、自然语言处理、图像识别等)时表现优越。
  • 大数据和计算资源:深度学习通常需要大量的训练数据和强大的计算能力(如GPU)来训练深度神经网络。

3. 深度学习和神经网络的关系

  • 深度学习是神经网络的一个子集:深度学习本质上是通过深层神经网络(DNN)来解决更复杂的任务。它是神经网络的一种扩展或进阶版本,强调使用深度(多层)的结构来提取数据中的高级特征。
  • 神经网络可以是浅层的:而深度学习网络通常都是深层的神经网络。浅层神经网络和深度学习之间的主要区别在于网络的深度(即隐藏层的数量)。
  • 深度学习模型的优势:深度学习能够从大量的复杂数据中学习到有用的表示(特征),并且在很多任务上超越了传统的浅层神经网络,尤其是在图像、语音、文本等领域。

4. 神经网络的种类与深度学习的相关性

深度学习使用的神经网络架构通常比传统的神经网络更加复杂和深层。常见的深度学习网络架构包括:

  • 卷积神经网络(CNN):专门用于处理图像数据,通过卷积层提取图像中的局部特征,广泛应用于图像分类、目标检测等任务。
  • 循环神经网络(RNN):适用于处理序列数据,能够处理时间依赖性较强的问题,如语音识别、语言建模等。
  • 长短时记忆网络(LSTM):RNN的一种变种,能够更好地捕捉长时间序列中的依赖关系。
  • 变换器网络(Transformer):主要用于自然语言处理(NLP)任务,特别是语言模型(如BERT、GPT)。它基于自注意力机制,可以并行化训练并处理长序列数据。

5. 深度学习的核心优势

  • 自动特征学习:深度学习能够自动从原始数据中学习出特征,而不需要手动设计特征。这对于复杂的任务(如图像分类、语音识别等)非常有利。
  • 高维数据处理:深度神经网络能有效处理高维、复杂的数据,如图像、音频和文本等。
  • 性能提升:随着数据量和计算能力的增加,深度学习在许多任务上都表现出了比传统机器学习方法更优的性能。

6. 总结

  • 神经网络是深度学习的基础,指的是由多个神经元连接组成的计算模型。传统神经网络通常是“浅层”的,只有少量的隐藏层。
  • 深度学习是利用深层神经网络(即具有多个隐藏层的神经网络)来自动学习数据的高级特征。深度学习通过多个层次的非线性映射,将复杂的输入数据转化为有用的表示,在许多任务中取得了突破性进展。

简而言之,深度学习是基于神经网络的一种进阶应用,它通过深层次结构和大规模数据的处理,解决了更复杂、更抽象的问题。

链表常见简单算法

链表

1
2
3
4
type LinkNode struct{
Val int
Next *LinkNode
}

反转链表

1
2
3
4
5
6
7
8
9
10
11
func reverseLinkList(head *LinkNode)*LinkNode{
curr:=head
prev:=nil
for curr.Next!=nil{
next:=curr.Next
curr.Next=prev
prev=curr
curr=next
}
return prev
}

输出链表倒数第K个节点值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func findKthFromEnd(head *LinkNode,k int)int{
curr:=head
count:=0
goal:=head
for curr.Next!=nil{
curr=curr.Next
if count<k{
count++
}else{
goal=goal.Next
}
}
if count<k{
retrun nil
}else{
return goal.Val
}
}

判断链表是否有环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 判断链表是否有环
func hasCycle(head *ListNode) bool {
if head == nil || head.Next == nil {
return false
}
slow, fast := head, head.Next
for slow != fast {
if fast == nil || fast.Next == nil {
return false
}
slow = slow.Next
fast = fast.Next.Next
}
return true
}

两数相加 leetcode.2

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
// 定义sum用于计算链表当前位的值,carry为链表下一位的进位值
var sum,carry int
// 定义头尾两个链表指针,头指针用于记录返回链表头节点
// 尾指针用于在链表尾部插入新节点
var head,tail *ListNode
for l1 != nil || l2 != nil {
var(
val1 int
val2 int
)
newNode:=new(ListNode)
if l1!=nil{
val1 = l1.Val
l1 = l1.Next
}
if l2!=nil{
val2 = l2.Val
l2 = l2.Next
}
sum = val1 + val2 + carry
sum, carry = sum%10, sum/10
newNode.Val = sum
// 记录头节点
if head == nil{
head = newNode
tail = newNode
}else{
tail.Next = newNode
tail = newNode
}
}
if carry==1{
tail.Next = &ListNode{Val:1}
}
return head
}

滑动窗口算法及应用场景

滑动窗口基本思想

滑动窗口主要应用在链表,数组等一维数据结构中,所谓窗口在算法中实际使用双指针来实现,主要要确定滑动指针的边界。

滑动窗口算法实例

  1. 获取链表倒数第K个节点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    func getKthFromEnd(head *LinkNode,k int)*LinkNode{
    slow:=head
    fast:=head
    for i:=0;i<k&&fast!=nil;i++{
    fast = fast.Next
    }
    if fast==nil{
    return nil
    }
    for fast!=nil{
    slow = slow.Next
    fast.Next = fast.Next
    }
    return slow
    }
  2. 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    func lengthOfLongestSubstring(s string) int {
    n := len(s)
    maxLength := 0
    lastIndex := make([]int, 128)

    start := 0
    for fast := 0; fast < n; fast++ {
    currentChar := s[fast]
    if lastIndex[currentChar] > start {
    start = lastIndex[currentChar]
    }
    if fast-start+1 > maxLength {
    maxLength = fast - start + 1
    }
    lastIndex[currentChar] = fast + 1
    }

    return maxLength
    }
  3. leetcode [209] 长度最小的子数组
    给定一个含有 n 个正整数的数组和一个正整数 slow 。
    找出该数组中满足其总和大于等于 slow 的长度最小的 子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func minSubArrayLen(target int, nums []int) int {
minLen := len(nums)+1
left := 0 // 窗口的左边界
currentSum := 0 // 当前窗口的和

for right := 0; right < len(nums); right++ {
currentSum += nums[right] // 将右边界的元素加入到当前窗口的和中
// 当当前窗口的和大于等于 target 时,尝试缩小窗口
for currentSum >= target {
minLen = min(minLen, right-left+1) // 更新最小长度
currentSum -= nums[left] // 从窗口的左边界减去元素
left++ // 移动左边界
}
}

// 如果没有找到符合条件的子数组,返回0
if minLen == len(nums)+1 {
return 0
}
return minLen
}

滑动窗口应用场景

滑动窗口算法是一种非常实用的算法技术,它在处理数组或字符串等序列数据时特别有用。以下是一些滑动窗口算法的应用场景:

  1. 寻找子数组/子串的最小/最大和

    • 给定一个数组和一个整数k,找出所有长度为k的子数组的最大和或最小和。
    • 给定一个数组和一个目标和,找出具有最大和的连续子数组。
  2. 寻找满足条件的子数组

    • 如前所述,寻找满足总和大于等于某个特定值的最小长度子数组。
    • 寻找数组中所有等于目标和的子数组。
  3. 字符串处理

    • 找出字符串中不包含重复字符的最长子串。
    • 找出字符串中满足特定条件的最短子串,例如包含所有特定字符的最短子串。
  4. 数据流问题

    • 在处理数据流时,滑动窗口可以用来维护一个固定大小的数据窗口,以便于实时计算窗口内数据的统计信息。
  5. 时间序列分析

    • 在金融领域,滑动窗口可以用来分析特定时间段内的股票价格变动。
  6. 滑动窗口协议

    • 在计算机网络中,滑动窗口协议用于控制数据传输,确保接收方不会因为数据量过大而处理不过来。
  7. 图像处理

    • 在图像处理中,滑动窗口可以用来识别图像中的特定模式或特征。
  8. 机器学习特征提取

    • 在机器学习中,滑动窗口可以用来从时间序列数据中提取特征。
  9. 游戏开发

    • 在游戏开发中,滑动窗口可以用来检测玩家在一定时间内的行为模式。
  10. 实时监控系统

    • 在实时监控系统中,滑动窗口可以用来检测异常行为,例如在短时间内的频繁登录尝试。

滑动窗口算法因其简单和高效而在这些场景中被广泛使用。通过维护一个动态的窗口,算法可以在不遍历整个数据集的情况下,快速找到满足特定条件的数据子集。

记录一次context cancel报错问题

问题描述

生产环境数据库日志有大量报错内容为context canceled的记录。

排查步骤

  1. 根据报错日志,定位到报错context canceled发生在集中在ORM提交事务之前的DDL方法;
    报错处业务场景:
    服务为B/S架构,数据同步页面后端服务使用golang-Goframe框架开发,数据库为PostgreSQL,服务业务逻辑为首次进入数据同步页面即执行数据同步(客户刷新页面并不会触发数据同步,只会显示历史数据),期间批量并发调用数据查询API并将返参更新到数据库中,因执行更新操作之前需要删除数据库原有数据,故整个删除更新操作开启了事务。
    1
    2
    3
    4
    tx,err:=g.DB().Begin(ctx)
    if err!=nil{}
    tx.DB().Model("table").Delete("key = ?", key)
    _, err := tx.DB().Model("table").Data(data).Batch(10).Insert()
  2. 回溯代码发现Begin(ctx)传入的context是请求handle使用的context,分别开始排查断开handle的原因
    1. 通过接口测试工具排查接口耗时是否大于接口超时限制;
    2. 通过数据库最大连接数设置,观察活跃连接数排查服务是否存在连接池泄露问题;
    3. 假设是用户主动刷新页面导致连接被关闭;
  3. 经测试,在接口返回之前模拟用户刷新操作可复现问题;
  4. Begin(ctx)的入参ctx改为通过Background()返回的根上下文,保证程序同步动作不因请求连接断开而结束;
  5. 为减少接口响应时间,还将事务拆分成多个小事务,也尝试使用乐观锁而不是用事务来执行Update操作;

限流的实现方式

限流目的

限流是为保护自身系统和下游系统不被高并发流量冲垮,导致系统雪崩等问题。
在保证系统可用的情况下尽可能多增加进入的请求,其余的请求在排队等待,或者返回友好提示,保证里面进入系统的用户可以正常使用,防止系统雪崩。

令牌桶

令牌桶
指定令牌桶的容量并以固定速率往令牌桶放置令牌,客户端请求时,首先查询令牌桶内的令牌数,若大于0则取出一个令牌,服务端正常处理客户端请求,否则服务端拒绝服务。
此外,如果令牌放置的速度比客户端请求的速度要快,令牌桶的令牌会一直增加直到令牌占满整个令牌桶。

漏桶

漏桶
指定漏桶容量,同时漏桶会以一定速率放行客户端请求,当桶内请求数量累计数量大于桶容量时,请求被拒绝。

令牌桶和漏桶的区别

令牌桶
1. 令牌桶令牌数量是按照指定速率增加的;
2. 令牌桶的容量是固定的,但放行(处理请求)的速度不是固定的,理论上支持的最大并发量就是桶内的容量;
3. 系统的负载能力应该与令牌桶的容量以及放置令牌的速率相关;
漏桶
1. 客户端请求以任意速率进入漏桶;
2. 漏桶的容量是固定的,放行(处理请求)的速率也是固定的;
3. 系统的负载能力应该与漏桶的放行速率相关;
总之,令牌桶在限制请求平均处理速率的基础上还允许突发的流量增加,而漏桶有更强的削峰能力,可以强制限制请求处理的速率,但漏桶出口的速度固定,因为计算性能固定,不能灵活的应对服务动态扩容的场景

计数器算法

计数器算法是在一段时间间隔内(时间窗/时间区间),处理请求的最大数量固定,超过部分不做处理。
例如:限制接口A在单位时间T内的访问次数不能超过X个。

  1. 在每个单位时间T开始时,设置一个计数器counter,每有一个请求,counter就加1,如果counter的值小于X则放行请求,否则拒绝服务;
  2. 每个单位时间T结束时,重置counter。

计数器算法存在的临界问题
临界问题
如果在单位时间T的结束之前的一小段时间和下一个单位时间T开始之后的一小段时间内出现突发流量,理论上系统最大会承载2X个请求,此时流量速率是可能远大于限制的;

在浏览器输入域名并回车会发生哪些事情

从TCP/IP网络模型角度理解

当你在浏览器中输入域名并按下回车键时,会触发一系列的网络活动,涉及到多个TCP/IP网络模型中的协议和组件。以下是从浏览器输入域名到访问网页的大致流程,以及涉及的主要网络协议:

  1. DNS解析(域名系统协议 - DNS):
    • 当你在浏览器中输入一个域名时,首先需要将域名解析为IP地址。这通常通过DNS查询完成。
    • 浏览器会检查本地的DNS缓存,如果没有找到对应的IP地址,它会向配置的DNS服务器发送查询请求。
    • DNS服务器会递归查询,直到找到对应的IP地址,并将结果返回给浏览器。
  2. ARP协议(地址解析协议 - ARP):
    • 如果目标服务器与本地设备在同一局域网内,浏览器可能会使用ARP来将目标IP地址解析为MAC地址。
  3. IP协议(网际协议 - IP):
    • IP协议负责将数据封装成IP数据包,并在网络中传输。它会处理数据包从源到目的地的传输。
  4. ICMP协议(互联网控制消息协议 - ICMP):
    • 如果在传输过程中遇到问题,如目的地不可达,ICMP协议可以用来发送错误消息。
  5. TCP协议(传输控制协议 - TCP):
    • 一旦DNS解析出IP地址,浏览器会使用TCP协议建立到目标服务器的连接。这涉及到三次握手过程:
    • 客户端发送SYN(同步序列编号)包到服务器。
    • 服务器响应SYN-ACK(同步和确认)包。
    • 客户端发送ACK(确认)包,完成握手。
  6. HTTP/HTTPS协议(超文本传输协议/安全超文本传输协议):
    • TCP连接建立后,浏览器会发送HTTP(或HTTPS,如果是加密连接)请求到服务器,请求获取网页内容。
    • 如果是HTTPS连接,还会涉及到SSL/TLS协议来建立安全连接,包括证书验证和加密通信。
  7. FTP/SFTP协议(文件传输协议/安全文件传输协议):
    • 如果请求涉及下载文件,可能会使用FTP或SFTP协议。
  8. SMTP/POP3/IMAP协议(简单邮件传输协议/邮局协议/互联网消息访问协议):
    • 如果请求涉及发送或接收电子邮件,可能会使用SMTP、POP3或IMAP协议。
  9. TCP/IP数据传输:
    • 一旦HTTP请求被发送,服务器会处理请求并返回相应的HTML页面、图片、CSS文件、JavaScript文件等。
    • 这些响应通过TCP连接传输回浏览器。
  10. 浏览器渲染:
    • 浏览器接收到数据后,会解析HTML、CSS和JavaScript,并将网页渲染显示给用户。

TCP/IP网络模型通常被分为四层:应用层、传输层、网络层和数据链路层。以下是你提到的协议分别属于网络模型的哪一层:

  1. 应用层:
    • DNS(域名系统协议):负责将域名转换为IP地址。
    • HTTP/HTTPS(超文本传输协议/安全超文本传输协议):用于客户端和服务器之间的请求和响应消息传输。
    • FTP/SFTP(文件传输协议/安全文件传输协议):用于文件的传输。
    • SMTP/POP3/IMAP(简单邮件传输协议/邮局协议/互联网消息访问协议):用于电子邮件的发送和接收。
  2. 传输层:
    • TCP(传输控制协议):提供可靠的、面向连接的数据传输服务。
    • UDP(用户数据报协议):提供不可靠的、无连接的数据传输服务(虽然UDP没有被提及,但它也是传输层的一个重要协议)。
  3. 网络层:
    • IP(网际协议):负责数据包的寻址和路由。
    • ICMP(互联网控制消息协议):用于发送错误消息和网络查询。
  4. 数据链路层:
    • ARP(地址解析协议):将IP地址解析为MAC地址,用于同一局域网内的设备通信。
    数据链路层还涉及到其他一些协议和机制,如以太网(Ethernet)用于局域网通信,以及各种网络接口卡(NIC)驱动程序等。

从云服务分布式部署架构理解

从浏览器输入域名并回车后的过程会涉及到多个网络协议和组件,具体如下:

  1. DNS解析:
    • 首先,浏览器会进行DNS解析,将域名转换为IP地址。如果域名对应的服务是分布式部署的,DNS解析可能返回一个负载均衡器的IP地址,或者根据地理位置或其他策略返回不同区域服务器的IP地址。
  2. 负载均衡:
    • 四层负载均衡:在IP+端口层面进行请求分发,根据IP地址和端口号将流量导向后端的具体服务器。
    • 七层负载均衡:在应用层进行请求分发,可以根据URL、主机名等应用层信息将请求路由到不同的处理服务器。
    • 负载均衡器可以根据不同的策略来分配请求,如轮询、最少连接、源IP散列或加权负载均衡。
  3. 网络协议:
    • HTTP/HTTPS:用于浏览器与服务器之间的请求和响应消息传输。
    • TCP/IP:负责数据包的寻址和路由。
    • UDP:在需要低延迟或高吞吐量的场景中使用,如实时应用、视频流或DNS查询。
  4. 服务发现与注册:
    • 在分布式系统中,服务实例需要在服务注册中心注册自己,以便负载均衡器或其他服务能够发现并调用它们。常见的服务注册中心包括ETCD、Consul或Zookeeper。
  5. 服务网关:
    • 服务网关负责路由、过滤和负载均衡,提供统一的服务入口。例如,Spring Cloud Gateway可以作为服务网关。
  6. 熔断器:
    • 熔断器如Hystrix或Resilience4j,提供故障时的自动切断机制和降级服务,防止系统雪崩。
  7. 配置管理:
    • 分布式配置中心如Spring Cloud Config或Apollo,用于统一管理配置信息,支持动态更新配置。
  8. 消息队列:
    • 消息队列如RabbitMQ、Kafka等,用于异步通信和消息传递。
  9. 缓存和数据库:
    • 分布式缓存如Redis、Memcached,以及分布式数据库如MySQL集群、Cassandra、HBase等,用于存储和查询数据。
    通过这些组件和协议的协同工作,分布式系统能够处理大量的并发请求,提供高可用性和可扩展性。

从HTTP数据报文解析的角度理解

客户端发起HTTP请求,包含以下主要部分:
HTTP(HyperText Transfer Protocol) 是用于客户端和服务器之间传输超文本数据的协议,广泛用于万维网(Web)中。HTTP 是一种 应用层协议,它依赖于底层的 传输层协议(如 TCP 或 TLS/SSL)来实现数据传输。

HTTP 协议的基本结构
HTTP 协议是一个 请求-响应协议,它由 客户端(通常是浏览器)发起请求,服务器接收到请求后返回响应。它是无状态的协议,意味着每个请求都是独立的,服务器不维护客户端的状态。

  1. HTTP 请求

HTTP 请求由客户端(如浏览器)发起,包含以下主要部分:

请求行(Request Line)

请求行由以下三部分组成:
• HTTP 方法:指定请求类型(如 GET、POST、PUT、DELETE 等)。
• 请求 URL:指定服务器上的资源路径。
• HTTP 版本:指明客户端支持的 HTTP 协议版本,通常是 HTTP/1.1 或 HTTP/2。

例如:

GET /index.html HTTP/1.1

请求头(Request Headers)

请求头包含客户端的一些信息,如浏览器类型、支持的语言、请求的主机、请求的时间等。常见的请求头包括:
• Host:指定请求的主机和端口号(例如:Host: www.example.com)。
• User-Agent:客户端应用程序的详细信息(例如浏览器类型)。
• Accept:告诉服务器客户端支持哪些媒体类型(如 text/html、application/json)。
• Content-Type:请求体的类型(通常用于 POST 请求)。
• Authorization:用于传输认证信息(如用户名和密码)。

请求体(Request Body)

请求体主要用于包含客户端向服务器发送的数据,通常在 POST 或 PUT 请求中使用。GET 请求一般没有请求体。

  1. HTTP 响应

HTTP 响应由服务器发出,包含以下主要部分:

响应行(Response Line)

响应行由以下三部分组成:
• HTTP 版本:服务器使用的 HTTP 协议版本(例如 HTTP/1.1)。
• 状态码:表示请求处理的结果(如 200 OK、404 Not Found)。
• 状态码描述:对状态码的描述(如 OK、Not Found)。

例如:

HTTP/1.1 200 OK

响应头(Response Headers)

响应头包含服务器返回的一些元数据,如:
• Content-Type:返回的数据类型(如 text/html、application/json)。
• Content-Length:响应体的长度。
• Set-Cookie:服务器发送的 Cookie 数据,用于会话管理。
• Cache-Control:缓存控制指令(如 no-cache、max-age)。
• Date:响应生成的日期和时间。

响应体(Response Body)

响应体包含实际的数据内容,通常是客户端请求的资源(如 HTML 页面、图片、JSON 数据等)。

  1. HTTP 方法

HTTP 支持多种请求方法,常见的包括:
• GET:用于从服务器获取数据。是最常见的 HTTP 请求方法。请求中不会携带请求体。
• POST:用于向服务器发送数据。通常用于表单提交、文件上传等场景。
• PUT:用于更新服务器上的资源。
• DELETE:用于删除服务器上的资源。
• HEAD:类似于 GET,但服务器只返回响应头,不返回响应体。
• OPTIONS:用于获取服务器支持的 HTTP 方法。
• PATCH:用于部分更新资源。

  1. HTTP 状态码
    HTTP 状态码表示服务器对请求的响应状态。常见的 HTTP 状态码可以分为五个类别:

1xx(信息性状态码)
• 100 Continue:客户端可以继续发送请求的其余部分。
• 101 Switching Protocols:服务器根据客户端的请求切换协议。

2xx(成功状态码)
• 200 OK:请求成功,通常返回请求的资源。
• 201 Created:请求成功并创建了新的资源。
• 204 No Content:请求成功,但没有返回内容。

3xx(重定向状态码)
• 301 Moved Permanently:请求的资源已永久移动到新位置。
• 302 Found:请求的资源临时移动到新位置。
• 304 Not Modified:资源未被修改,客户端可以使用缓存的副本。

4xx(客户端错误状态码)
• 400 Bad Request:请求无效或缺少必需的参数。
• 401 Unauthorized:请求要求身份验证。
• 403 Forbidden:服务器理解请求但拒绝执行。
• 404 Not Found:请求的资源不存在。

5xx(服务器错误状态码)
• 500 Internal Server Error:服务器内部错误,导致无法完成请求。
• 502 Bad Gateway:作为网关或代理的服务器收到了无效响应。
• 503 Service Unavailable:服务器当前无法处理请求,通常是由于超载或维护。

  1. HTTP 的连接管理

长连接(Keep-Alive)

HTTP/1.0 默认每个请求都建立一次新连接,服务器响应后立即关闭连接。为了优化性能,HTTP/1.1 引入了 持久连接(即长连接),在同一 TCP 连接上处理多个请求,避免每次请求都建立新的连接,减少延迟。

管道化(Pipelining)

HTTP/1.1 支持请求管道化(pipelining),即客户端可以在前一个请求尚未收到响应时,继续发送多个请求。这在减少延迟方面有一定优势,但并非所有服务器都支持。

  1. HTTP/2 和 HTTP/3
    • HTTP/2:相较于 HTTP/1.1,HTTP/2 引入了二进制帧、多路复用、头部压缩等新特性,使得请求和响应更高效,解决了 HTTP/1.1 中存在的一些性能瓶颈。HTTP/2 采用 二进制协议(而非文本协议)来提高传输效率。
    • HTTP/3:基于 QUIC 协议的 HTTP/3 进一步优化了性能,采用 UDP 作为传输层协议,提供更低的延迟和更快的连接建立速度。HTTP/3 的核心优势是减少了握手时间,提高了数据传输的稳定性和效率。

  2. 安全性
    • HTTPS:HTTP Secure(HTTPS)是通过 TLS/SSL 加密的 HTTP 协议,提供数据加密、身份验证和数据完整性,防止中间人攻击和窃听。通过加密的连接,HTTPS 可以确保用户和服务器之间的通信是安全的。

总结

HTTP 协议是 Web 数据传输的基础,具有简单、灵活和易于实现的特点。虽然 HTTP/1.1 已经被广泛使用,现代 Web 中很多应用已经开始采用更高效的 HTTP/2 或 HTTP/3 协议,以提高性能。通过对 HTTP 协议的理解,我们能够更好地设计和优化 Web 应用。

MySQL常见八股文

事务的四个特性

一般来说,衡量事务必须满足四个特性:ACID,即 原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)

持久性(Durability):事务处理结束后,对数据的修改就是永久的,会持久化到硬盘上,即便系统故障也不会丢失。

MySQL事务的隔离级别

MySQL数据库支持四种事务隔离级别,这些级别定义了在并发环境下事务如何相互隔离,以避免不同类型的事务并发问题,如脏读、不可重复读和幻读。以下是四种事务隔离级别及其描述:

  1. READ UNCOMMITTED(读未提交):
    • 在这个级别下,事务可以读取到其他事务未提交的数据。这意味着可能会读到脏数据,因为其他事务可能最终会回滚。
    • 脏读:可以读取到其他事务未提交的数据。
    • 不可重复读:可能。
    • 幻读:可能。

  2. READ COMMITTED(读已提交):
    • 事务只能读取到其他事务已经提交的数据。这是大多数数据库系统的默认隔离级别。
    • 脏读:不可能。
    • 不可重复读:可能。
    • 幻读:可能。

  3. REPEATABLE READ(可重复读):
    • 事务在整个过程中可以多次读取到相同的数据集。这是MySQL的默认隔离级别(对于InnoDB存储引擎)。
    • 脏读:不可能。
    • 不可重复读:不可能。
    • 幻读:可能(在MySQL中,REPEATABLE READ隔离级别实际上防止了幻读,因为它使用Next-Key Locks)。

  4. SERIALIZABLE(串行化):
    • 这是最高的隔离级别,事务会完全隔离,事务会依次顺序执行,模拟了事务串行执行的场景。
    • 脏读:不可能。
    • 不可重复读:不可能。
    • 幻读:不可能。

在MySQL中,可以通过以下SQL语句查看和设置事务隔离级别:

1
2
3
4
5
6
7
8
9
10
11
-- 查看当前会话的事务隔离级别:
SELECT @@tx_isolation;

-- 查看全局事务隔离级别:
SELECT @@global.tx_isolation;

-- 设置当前会话的事务隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL [隔离级别];

-- 设置全局事务隔离级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL [隔离级别];

其中[隔离级别]可以是READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ或SERIALIZABLE中的一个。需要注意的是,更改全局事务隔离级别可能需要具有相应的权限,并且在某些情况下,更改可能不会立即生效,因为MySQL可能需要重启才能应用新的全局设置。

不可重复读和幻读对事务隔离性的影响

在数据库事务中,不可重复读(No-repeatable Read)和幻读(Phantom Read)是两种不同的并发问题,它们对事务的隔离性有不同的影响。
不可重复读(Non-repeatable read)是数据库事务中的一个并发问题,指的是在一个事务内,多次读取同一数据集合时,由于其他事务的干扰,读取到的结果不一致。
对事务隔离性的影响:
• 防止不可重复读:在可重复读隔离级别下,事务可以保证在整个事务期间对数据的读取是一致的,即使其他事务对数据进行了修改。
• 锁机制:为了实现可重复读,数据库通常会使用行级锁或间隙锁(Gap Locks)来防止其他事务修改或插入那些被当前事务读取的数据行。
幻读(Phantom Read)
幻读是指在一个事务内,第一次查询某条记录时不存在,但在后续的查询中却出现了,就像“幻影”一样。这通常是因为其他事务插入了新的记录,而这些记录符合当前事务的查询条件。
对事务隔离性的影响:
• 影响数据一致性:幻读可能会导致事务在逻辑上的数据不一致,尤其是在涉及范围查询和计数的场景中。
• 防止幻读的机制:为了防止幻读,数据库需要使用更复杂的锁机制,如Next-Key Locks(行锁和间隙锁的组合),这样可以锁定一个范围,防止其他事务在这个范围内插入新的记录。
对事务隔离性的影响总结
• 可重复读:提高了事务的隔离性,使得事务在执行期间能够看到一致的数据视图,但可能会因为锁的使用而降低并发性。
• 幻读:降低了事务的隔离性,因为它允许其他事务在当前事务执行期间插入新的记录,这可能会导致当前事务的逻辑错误。
在实际应用中,不同的数据库管理系统对于可重复读和幻读的处理方式可能有所不同。例如,MySQL的InnoDB存储引擎在可重复读隔离级别下通过Next-Key Locks机制来防止幻读,而其他数据库可能需要更高的隔离级别(如串行化)来防止幻读。选择合适的隔离级别需要在数据一致性和系统并发性之间做出权衡。

MySQL的存储引擎有哪些

MySQL支持多种存储引擎,每种存储引擎都有其独特的特点和适用场景。以下是一些常用的MySQL存储引擎及其使用场景:

  1. InnoDB存储引擎:
    • 特点:支持事务处理、行级锁定和外键约束,适合需要高并发和数据一致性的应用。
    • 适用场景:电子商务网站、金融系统等需要事务支持的场景。
    • 优点:灾难恢复性好、支持事务、使用行级锁、支持外键关联、支持热备份。
    • 缺点:占用的数据空间相对较大。
  2. MyISAM存储引擎:
    • 特点:插入和查询速度快,但不支持事务处理和外键约束,适合读密集型应用。
    • 适用场景:内容管理系统、日志系统等读多写少的场景。
    • 优点:读取速度快,不占用大量内存与存储资源。
    • 缺点:更新机制浪费内存空间,需要依靠OPTIMIZE TABLE来恢复,主机宕机后,MyISAM表易损坏,灾难恢复性不佳。
  3. Memory存储引擎:
    • 特点:所有数据存储在内存中,读写速度快,但不支持事务处理和外键约束,数据在数据库重启后会丢失,适合临时数据存储。
    • 适用场景:会话信息、临时表等需要快速访问的场景。
    • 优点:提供内存表,显著提高访问数据的速度。
    • 缺点:服务器重启后数据会丢失,复制维护时需要小心。
  4. Archive存储引擎:
    • 特点:提供高压缩存储,适用于存储历史数据或日志数据,只支持最基本的插入和查询功能。
    • 适用场景:适用于存储历史数据或日志数据的归档。
  5. CSV存储引擎:
    • 特点:将数据存储在逗号分隔值的文本文件中,易于导入导出,不支持索引。
    • 适用场景:数据导入导出,需要将数据存储在可读性强、易编辑的文件中的场景。
  6. Federated存储引擎:
    • 特点:允许MySQL服务器访问远程MySQL服务器上的表,类似于分布式数据库。
    • 适用场景:分布式数据库系统,需要跨多个MySQL服务器访问数据的场景。
    每种存储引擎都有其特定的优势和限制,选择时应根据具体的业务需求和场景来决定。

MySQL索引的存储结构为什么是B+树而不是B树

B树和B+树都是平衡多路查找树,用于索引和存储数据,但它们在数据结构和一些实现细节上有所不同。以下是B树和B+树的主要区别:

  1. 节点结构:
    • B树:节点中既包含数据,也包含索引键。每个节点可以包含多个数据项和索引键,数据项和索引键是交替存储的。
    • B+树:节点中只包含索引键,不包含数据。数据存储在叶子节点,且叶子节点之间通过指针连接,形成一个链表。
  2. 数据存储位置:
    • B树:数据可以存储在树的任何节点中,包括非叶子节点。
    • B+树:数据只存储在叶子节点中,非叶子节点仅用于索引。
  3. 查询效率:
    • B树:查询可能在非叶子节点结束,因为数据可能存储在非叶子节点。
    • B+树:查询必须到达叶子节点,因为所有数据都存储在叶子节点。
  4. 树的高度:
    • B树:由于数据分布在所有节点中,树的高度相对较高。
    • B+树:由于非叶子节点不存储数据,只存储索引键,树的高度相对较低。
  5. 范围查询:
    • B树:进行范围查询时,需要在叶子节点中进行中序遍历。
    • B+树:进行范围查询时,可以直接在叶子节点的链表上进行,效率更高。
  6. 磁盘I/O:
    • B树:由于树的高度较高,磁盘I/O次数相对较多。
    • B+树:由于树的高度较低,磁盘I/O次数相对较少。
  7. 空间利用率:
    • B树:节点中存储数据和索引键,空间利用率相对较低。
    • B+树:节点中只存储索引键,空间利用率相对较高,可以存储更多的索引键。
  8. 写操作:
    • B树:写操作时,可能需要对非叶子节点进行分裂,操作较为复杂。
    • B+树:写操作时,只需要对叶子节点进行分裂,操作相对简单。
    MySQL选择B+树作为索引结构而不是B树,主要有以下几个原因:
  9. 查询性能更优:
    • B+树的所有数据都存储在叶子节点中,查询时只需要遍历一次叶子节点即可得到查询结果,而B树的数据既可能存储在非叶子节点中,也可能存储在叶子节点中。B树在查询时需要遍历非叶子节点和叶子节点,效率相对较低。
  10. 磁盘I/O次数更少:
    • B+树的非叶子节点不存储数据,只存储索引键,因此可以存储更多的索引键,减少了树的高度,使得磁盘I/O次数减少,查询更快。
  11. 更适于范围查询:
    • B+树的叶子节点通过指针相互连接,形成一个有序链表,方便顺序访问和范围查询。在B树中进行范围查询时,需要对B树进行中序遍历,而B+树的范围查询只需要对链表进行遍历即可。
  12. 查询效率更稳定:
    • B树的查询时间复杂度在1到树高之间,分别对应记录在根节点和叶节点,而B+树的查询复杂度则稳定为树高,因为所有数据都在叶节点。
  13. 空间利用率更高:
    • 由于B+树的非叶子节点不存储数据,只存储索引键,因此在相同磁盘空间下,B+树可以存储更多的索引键,提高了空间利用率。
  14. 更适合外部存储:
    • B+树由于内节点无data域,每个节点能索引的范围更大更精确,使得B+树单次磁盘IO的信息量大于B树,从而减少了查询时需要的磁盘IO次数。
    综上所述,B+树在查询性能、磁盘I/O次数、范围查询以及查询效率稳定性方面相较于B树都有显著优势,这些特性使得B+树更适合作为MySQL数据库的索引结构。