余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

👌方法的形参在哪块区域?

发表于 2025-04-22 | 更新于 2025-06-22 | 分类于 笔记
字数统计 | 阅读时长

👌方法的形参在哪块区域?

题目详细答案

在Java虚拟机(JVM)中,方法的形参(即方法的参数)在方法调用时存储在栈帧(Stack Frame)中。栈帧是 JVM 栈(Java Stack)中的一个数据结构,每当一个方法被调用时,JVM 会为该方法创建一个新的栈帧。

一个栈帧主要包含以下几个部分:

  1. 局部变量表(Local Variable Array):用于存储方法的局部变量和形参。
  2. 操作数栈(Operand Stack):用于执行字节码指令时的操作数。
  3. 帧数据(Frame Data):包括方法的返回地址、动态链接、方法的调用者等。

方法的形参在方法调用时会被传递到局部变量表中。局部变量表是栈帧的一部分,用于存储方法的局部变量和形参。每个局部变量在局部变量表中的位置是通过索引来访问的,这些索引是从0开始的。

详细示例

假设有以下Java方法:

1
2
3
4
public int add(int a, int b) {
int sum = a + b;
return sum;
}

在调用add(5, 10)时,JVM 会执行以下步骤:

  1. 创建栈帧:为add方法创建一个新的栈帧。
  2. 初始化局部变量表:

a被存储在局部变量表的索引0处。

b被存储在局部变量表的索引1处。

  1. 执行方法体:

int sum = a + b;会在操作数栈上执行,然后将结果存储在局部变量表的索引2处。

  1. 返回结果:将sum的值从局部变量表中取出,并作为方法的返回值。

栈帧结构示意图

1
2
3
4
5
6
7
8
9
10
11
+--------------------+
| 局部变量表 |
|--------------------|
| 索引0: a = 5 |
| 索引1: b = 10 |
| 索引2: sum = 15 |
+--------------------+
| 操作数栈 |
+--------------------+
| 帧数据 |
+--------------------+

在这个过程中,a和b作为方法的形参被存储在局部变量表中。因此,方法的形参在JVM中存储于栈帧的局部变量表中。

👌触发FULL GC的几种情况?

发表于 2025-04-21 | 更新于 2025-06-22 | 分类于 笔记
字数统计 | 阅读时长

👌触发FULL GC的几种情况?

题目详细答案

老年代空间不足

当老年代的空间不足以容纳新的对象时,会触发 Full GC。具体情况包括:

对象晋升:在 Minor GC 过程中,如果 Survivor 区空间不足,存活对象会被晋升到老年代。如果老年代空间不足以容纳这些晋升的对象,就会触发 Full GC。

大对象分配:分配大对象(如大型数组或字符串)时,如果老年代空间不足以分配这些大对象,也会触发 Full GC。

永久代或元空间空间不足

在使用旧版 JVM(如 Java 7 及之前)时,永久代(Permanent Generation)用于存储类的元数据。如果永久代空间不足,会触发 Full GC。在 Java 8 及之后,永久代被元空间(Metaspace)取代,用于存储类的元数据。如果元空间不足,也会触发 Full GC。

调用System.gc()

调用System.gc()方法会显式请求 JVM 进行 Full GC。尽管 JVM 不保证一定会执行 Full GC,但通常情况下会触发一次 Full GC。

CMS垃圾收集器的失败

在使用 CMS 垃圾收集器时,如果并发收集过程中老年代空间不足,CMS 会触发一次 Full GC 作为后备措施。这种情况通常被称为 “promotion failure” 或 “concurrent mode failure”。

JNI 代码导致的内存不足

某些本地代码(JNI)可能会在堆内存中分配大量对象,导致堆内存不足,从而触发 Full GC。

内存碎片

如果堆内存中存在大量碎片,导致无法找到足够大的连续空间来分配新对象,也可能触发 Full GC 以尝试压缩内存和清理碎片。

👌为什么要分Eden和Survivor?

发表于 2025-04-20 | 更新于 2025-06-22 | 分类于 笔记
字数统计 | 阅读时长

👌为什么要分Eden和Survivor?

题目详细答案

在 JVM 中将新生代(Young Generation)分为 Eden 区和两个 Survivor 区(S0 和 S1)的主要原因是为了优化垃圾回收的效率和性能。这种分区策略基于对象的生命周期特点,利用复制算法来减少内存碎片和提高垃圾回收的效率。

优化垃圾回收效率

新生代的垃圾回收通常使用复制算法,这种算法的核心思想是将存活的对象从一个区域复制到另一个区域,而不是在原地进行标记和清除。复制算法的步骤如下:

对象分配:新创建的对象首先分配在 Eden 区。

Minor GC 触发:当 Eden 区填满时,会触发一次 Minor GC。

对象复制:在 Minor GC 过程中,存活的对象会从 Eden 区和当前使用的 Survivor 区(例如 S0)复制到另一个 Survivor 区(例如 S1)。复制完成后,Eden 区和当前使用的 Survivor 区将被清空。

区域交换:两个 Survivor 区在每次 GC 后交替使用。

假设有一个新生代大小为 1 GB,其中 Eden 区占 80%(800 MB),两个 Survivor 区各占 10%(100 MB)。对象首先分配在 Eden 区,当 Eden 区填满时,触发 Minor GC,将存活对象复制到一个 Survivor 区。下次 GC 时,再将存活对象从当前 Survivor 区复制到另一个 Survivor 区。

这种算法的优点是:

减少内存碎片:复制算法通过将存活对象紧密排列在一起,避免了内存碎片的问题。

提高回收速度:复制算法只需要遍历存活对象,而不需要遍历整个内存区域,这使得垃圾回收的速度更快。

优化内存分配

将新生代分为 Eden 区和两个 Survivor 区,能够更好地管理对象的生命周期:

大多数对象生命周期短:大多数新创建的对象很快就会变得不可达并被回收。Eden 区专门用于存储这些短生命周期对象,提高了内存分配和回收的效率。

幸存者对象管理:那些在一次或多次 Minor GC 后仍然存活的对象会被复制到 Survivor 区。通过在两个 Survivor 区之间复制和交换,可以有效管理这些对象的生命周期,直到它们被提升到老年代。

减少 GC 停顿时间

复制算法和分区策略有助于减少 GC 停顿时间(GC Pause Time),提高应用程序的响应速度:

Minor GC 更快速:由于新生代通常较小,并且复制算法只处理存活对象,Minor GC 的停顿时间通常较短。

老年代 GC 减少:通过有效管理新生代的对象,减少了老年代的对象数量和垃圾回收频率,从而减少了 Major GC 或 Full GC 的次数和停顿时间。

👌常见的jvm垃圾收集器有哪些?

发表于 2025-04-20 | 更新于 2025-06-22 | 分类于 笔记
字数统计 | 阅读时长

👌常见的jvm垃圾收集器有哪些?

题目详细答案

按照堆内存分代管理的思想,目前主要的垃圾收集器有:

1719153437245-6b20a86f-da22-4468-a839-248939d27401.png

Serial 和Serial Old收集器

Serial是jvm中最早一批的收集器之一,它是一款单线程收集器。在进行垃圾收集时,需要暂停所有的用户线程(Stop-The-World)

它的设计初衷是为了适应早期的硬件环境和应用场景。在那个时候,硬件配置相对较低,主要特点包括内存容量较小、CPU 单核、并发应用场景相对较少。

基于这些限制条件,Serial 系列的垃圾收集器采用了简单高效、资源消耗最少、单线程收集的设计思路

简单高效:由于硬件资源有限,垃圾回收器需要设计得简单高效,以减少系统资源的占用。Serial 系列的垃圾收集器实现简单,适用于小型应用或者简单的测试场景。

资源消耗最少:考虑到当时硬件资源有限,Serial 系列的垃圾收集器尽可能地减少了对系统资源的占用。通过使用单线程执行垃圾回收操作,避免了多线程切换的开销,从而最大程度地节约了系统资源。

单线程收集:由于早期的硬件环境和应用场景下,并发需求较低,采用单线程收集的设计方案足以满足当时的需求。单线程收集简化了垃圾回收器的实现,并降低了系统复杂性,使得垃圾回收过程更加可控和稳定。

Serial采用复制算法进行垃圾收集,Serial Old 收集器是 Serial 的老年代版本,同样是一个单线程收集器,采用标记整理法进行垃圾收集

Parallel Scavenge 和 Parallel Old收集器

与ParNew类似,也是一款用于新生代的多线程收集器。但Parallel Scavenge的目标是达到一个可控制的吞吐量,而ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间。

随着硬件资源的升级,包括内存空间的增大和 CPU 的多核化,传统的 Serial 垃圾收集器面临着性能瓶颈。由于它采用单线程执行垃圾回收操作,无法充分利用多核 CPU 的优势,导致在处理大内存空间时性能下降,垃圾回收时间变得更长。为了充分发挥多核 CPU 的优势,JVM 推出了 Parallel 收集器系列。Parallel 收集器的设计思想是利用多线程并行执行垃圾回收操作,以提高整个垃圾收集过程的并行度和性能。

Parallel 收集器的核心特点包括

多线程并行执行:Parallel 收集器利用了多核 CPU 的优势,通过多个线程同时执行垃圾回收操作,加快了垃圾收集的速度。

高吞吐量:由于并行执行垃圾收集操作,Parallel 收集器适用于吞吐量要求较高的应用场景。它能够在保证吞吐量的同时,尽可能地减少垃圾收集的停顿时间。

适用于大内存堆:随着内存空间的扩大,Parallel 收集器能够更好地应对大内存堆的情况,通过并行执行垃圾收集操作,提高了整个垃圾收集过程的效率。

相比于传统的 Serial 收集器,Parallel 收集器能更好地适应现代应用的需求,特别是大型内存堆和高吞吐量的场景。

Parallel Scavenge是一款采用复制算法进行垃圾收集的新生代收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,也是一款多线程的收集器,采用标记整理法进行垃圾收集

ParNew 收集器

ParNew 和 Parallel Scavenge 垃圾收集器都属于并行垃圾收集器。但ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间。

ParNew 垃圾收集器之所以应用更加广泛,一个重要原因是它是唯一能与 CMS(Concurrent Mark-Sweep)收集器配合使用的新生代收集器,特别适用于那些对停顿时间要求较高的应用场景。

以下是 ParNew 垃圾收集器的一些特点和与 CMS 配合的优势

与 CMS 配合:ParNew 垃圾收集器能够与 CMS 垃圾收集器配合使用,用于处理老年代的垃圾回收。在这种组合中,ParNew 负责新生代的垃圾收集,而 CMS 负责老年代的并发垃圾收集。这种分工合作可以有效地减少应用程序的停顿时间,满足对低停顿时间的需求。

并行收集:ParNew 垃圾收集器采用多线程并行收集的方式,类似于 Parallel Scavenge 收集器。它能够充分利用多核 CPU 的优势,加快垃圾收集的速度,提高整个应用程序的性能。

应对停顿时间要求高的场景:由于 ParNew 与 CMS 配合使用,可以针对那些对停顿时间要求较高的应用场景。CMS 收集器通过并发执行垃圾回收操作,尽量减少停顿时间,而 ParNew 则能够在新生代中高效地执行垃圾回收操作,进一步降低停顿时间。

ParNew采用复制算法进行垃圾收集,是一款新生代的并行收集器

CMS收集器

CMS 垃圾收集器的设计初衷是允许垃圾收集器在进行垃圾回收的同时,与应用程序的线程并发执行,不需要长时间暂停应用程序线程。它的工作原理是通过并发标记和清除的方式,先标记所有的存活对象,然后清除未被标记的对象。允许在垃圾收集过程中与应用程序并发执行,从而降低了垃圾收集的停顿时间,提高了系统的响应性和用户体验。

随着硬件技术的发展,可用内存越来越大,这为应用程序提供了更多的内存空间,从而能够创建更多的对象,减少了垃圾收集的频率。然而,随着内存空间的增大,垃圾收集的时间也相应增加,可能导致长时间的停顿,影响用户体验。在这种情况下,传统的垃圾收集器需要暂停应用程序线程进行垃圾收集,这会导致用户在执行某些操作时出现延迟甚至停顿的情况,这是无法接受的。

CMS 垃圾收集器的优势

并发标记和清除:CMS 垃圾收集器采用了并发标记和清除的方式,允许在垃圾收集过程中与应用程序并发执行。这意味着垃圾收集过程中只有一小部分时间需要暂停应用程序线程。

低停顿时间:由于并发执行的特性,CMS 垃圾收集器能够在较短的时间内完成垃圾回收操作,从而减少了应用程序的停顿时间。通常情况下,CMS 垃圾收集器能够将停顿时间控制在几百毫秒甚至更低。

CMS 垃圾收集器主要针对老年代进行垃圾回收,对于新生代则通常使用 ParNew 收集器。这种分代收集的方式能够更好地适应不同内存区域的特点和垃圾回收需求。使用了标记清除法+标记整理法

G1收集器

CMS 垃圾收集器开创了垃圾收集器的一个新时代,实现了垃圾收集和用户线程同时执行,从而达到了垃圾收集的过程不停止用户线程的目标。这种并发垃圾收集的思路为后续垃圾收集器的发展提供了重要的参考。

随着硬件资源的不断升级,可用的内存资源越来越多,这对于垃圾收集器的发展提出了新的挑战。传统的垃圾收集器采用物理分区的方式将内存分为老年代、新生代、永久代或 MetaSpace,但随着可用内存的增加,某一分代区域的大小可能会达到几十上百 GB。在这种情况下,传统的物理分区收集方式会导致垃圾扫描和清理时间变得更长,性能下降。

G1 垃圾收集器摒弃了传统的物理分区方式

将整个内存分成若干个大小不同的 Region 区域。每个 Region 在逻辑上组合成各个分代,这样做的好处是可以以 Region 为单位进行更细粒度的垃圾回收。G1 垃圾收集器在进行垃圾回收时,可以针对单个或多个 Region 进行回收,从而提高了收集效率和性能。

G1 垃圾收集器吸取了 CMS 垃圾收集器的优良思路并通过摒弃物理分区、采用 Region 分区的方式,实现了更细粒度的垃圾回收,从而提高了整个系统的性能和可用性。 G1 垃圾收集器在大内存环境下的表现更加出色,成为了现代 Java 应用中的重要选择。

Region(局部收集)

G1 垃圾收集器的最核心分区基本单位是 Region。与传统的垃圾收集器不同,G1 不再将堆内存划分为固定连续的几块区域,而是完全舍弃了物理分区,而是将堆内存拆分成大小为 1MB 到 32MB 的 Region 块。然后,以 Region 为单位自由地组合成新生代、老年代、Eden 区、Survivor 区和大对象区(Humongous Region)等。随着垃圾回收和对象分配的进行,每个 Region 也不会一直固定属于某个分代,它们可以随时扮演任何一个分代区域的内存角色。

Collect Set(智能收集)

在G1里面会维护一个Collect Set集合。这个集合记录了待回收的 Region 块的信息,包括每个 Region 块可回收的大小空间。有了这个 CSet 信息,G1 在进行垃圾收集时可以根据用户设定的可接受停顿时间来进行分析,找出在设定的时间范围内收集哪些区域最划算,然后优先收集这些区域。这样做不仅可以优先收集垃圾最多的 Region,还可以根据用户的设定来计算收集哪些 Region 可以达到用户所期望的垃圾收集时间。

通过 CSet,G1 垃圾收集器的性能得到了极大的提升,并且能够实现可预测的停顿时间要求。这使得垃圾回收过程变得更加智能化,更加适应不同的应用场景和用户需求。需要注意的是,用户设定的时间应该合理,官方建议在 100ms 到 300ms 之间,以平衡垃圾收集的效率和停顿时间的需求。

G1采用标记复制法进行垃圾收集,是一款适用于整个堆内存的并行收集器

ZGC收集器

ZGC(Z Garbage Collector)是一种低延迟的垃圾回收器,是 JDK 11 引入的一项重要特性。ZGC 的出现为 Java 应用提供了一种更加高效、可预测的垃圾回收解决方案,与传统的垃圾回收器相比,ZGC 的主要目标是实现极低的垃圾回收停顿时间,使得 Java 应用能够以更可预测的方式运行,尤其在大内存堆上表现良好。

ZGC 的优势和特点包括:

低停顿时间:ZGC 致力于将垃圾回收的停顿时间降至最低。它通过并发标记、并发清理等技术,在整个垃圾回收过程中尽量减少对应用程序的影响,从而实现了极低的垃圾回收停顿时间。这使得 Java 应用能够更加平滑地运行,减少了因垃圾回收而导致的不可预测性和性能波动。

可预测性:ZGC 的设计注重可预测性,即使在大内存堆上,也能够提供稳定的性能和可预测的垃圾回收行为。这使得开发人员能够更加信任和依赖于 Java 应用在生产环境中的稳定性和可靠性。

适用于大内存堆:ZGC 的低停顿时间特性使其特别适用于大内存堆的场景。在这种场景下,传统的垃圾回收器可能会面临长时间的停顿,影响应用的响应性和用户体验,而 ZGC 能够有效地缓解这一问题,保持较低的停顿时间,从而确保应用的流畅运行。

ZGC采用并发标记法+并发清理进行垃圾收集,是一款适用整个堆内存并行收集器。

👌Linux中断机制是什么?

发表于 2025-04-17 | 更新于 2025-06-22 | 分类于 Linux
字数统计 | 阅读时长

👌Linux中断机制是什么?

Linux中断机制是操作系统处理硬件和软件事件的一种重要方式。中断可以使操作系统及时响应各种事件,如硬件设备的输入输出操作、定时器事件等。

中断的基本概念

1.中断类型

  • 硬件中断:由硬件设备(如键盘、网卡、硬盘等)发出的信号,引起CPU中断当前执行的程序,转而执行中断处理程序。
  • 软件中断:由软件指令(如系统调用、异常等)引发的中断。

2.中断向量表

中断向量表(Interrupt Vector Table, IVT)是一个包含中断服务程序地址的表。当中断发生时,CPU通过中断向量表找到相应的中断服务程序并执行。

中断处理流程

  1. 中断发生:硬件设备或软件事件发出中断信号。
  2. 中断响应:CPU停止当前执行的程序,保存当前的上下文(如程序计数器、寄存器等),并根据中断向量表找到对应的中断服务程序。
  3. 中断处理:执行中断服务程序,处理中断事件。
  4. 中断返回:中断处理完成后,恢复保存的上下文,继续执行被中断的程序。

中断处理的关键组件

1.中断控制器

中断控制器(如PIC、APIC)负责管理和分配中断请求。它可以屏蔽不需要的中断,并确定中断的优先级。

2.中断处理程序

中断处理程序(Interrupt Service Routine, ISR)是处理特定中断事件的函数。它通常分为顶半部(Top Half)和底半部(Bottom Half):

  • 顶半部:快速响应中断,执行紧急的处理任务。
  • 底半部:延迟执行较长时间的任务,以减少顶半部的执行时间,避免阻塞其他中断。

3.中断上下文

中断上下文是指中断发生时CPU的状态,包括程序计数器、寄存器等。保存和恢复中断上下文是中断处理的关键步骤。

中断处理的实现

1.注册中断处理程序

在Linux内核中,可以使用request_irq函数注册中断处理程序:

1
2
3
#include <linux/interrupt.h>

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

2.中断处理程序示例

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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>

static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
printk(KERN_INFO "Interrupt occurred!\n");
// 中断处理逻辑
return IRQ_HANDLED;
}

static int __init my_module_init(void) {
int irq = 1; // 假设中断号为1
int result = request_irq(irq, my_interrupt_handler, IRQF_SHARED, "my_interrupt", NULL);
if (result) {
printk(KERN_ERR "Failed to request IRQ\n");
return result;
}
return 0;
}

static void __exit my_module_exit(void) {
int irq = 1; // 假设中断号为1
free_irq(irq, NULL);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple interrupt handler module");
MODULE_AUTHOR("Your Name");

3.顶半部和底半部

  • 顶半部:快速处理中断的关键部分。
  • 底半部:使用任务队列、工作队列或软中断机制延迟处理较长时间的任务。

中断的优先级和屏蔽

中断控制器可以设置中断的优先级和屏蔽某些中断,以确保重要的中断优先得到处理。APIC(Advanced Programmable Interrupt Controller)提供了更高级的中断管理功能,如多级中断优先级和分布式中断处理。

中断的嵌套和重入

中断处理程序应尽量短小精悍,以减少中断嵌套的层数。中断处理程序应避免使用可能导致阻塞的操作,如长时间的I/O操作和锁定操作。

👌Linux查看文件磁盘占用情况?

发表于 2025-04-16 | 更新于 2025-06-22 | 分类于 Linux
字数统计 | 阅读时长

👌Linux查看文件磁盘占用情况?

1.du命令

du(disk usage)命令用于估算文件和目录的磁盘使用情况。

基本用法

1
du [选项] [文件或目录]

常用选项

  • -h:以人类可读的格式显示(例如,K、M、G)。
  • -s:显示总计(不显示子目录的详细信息)。
  • -a:包括所有文件和目录。
  • -c:显示总计。
  • --max-depth=N:限制显示的目录层级深度。

示例

  • 查看当前目录下每个文件和子目录的磁盘使用情况:
1
du -h
  • 查看指定目录的总磁盘使用情况:
1
du -sh /path/to/directory
  • 查看指定目录及其子目录的磁盘使用情况,限制深度为1:
1
du -h --max-depth=1 /path/to/directory

2.df命令

df(disk free)命令用于查看文件系统的磁盘使用情况。

基本用法

1
df [选项] [文件或目录]

常用选项

  • -h:以人类可读的格式显示(例如,K、M、G)。
  • -T:显示文件系统类型。
  • -i:显示inode使用情况。

示例

  • 查看所有挂载的文件系统的磁盘使用情况:
1
df -h
  • 查看特定目录所在的文件系统的磁盘使用情况:
1
df -h /path/to/directory

3.ncdu工具

ncdu(NCurses Disk Usage)是一个基于ncurses的磁盘使用分析工具,提供了交互式界面。

安装

在Debian/Ubuntu系统上:

1
sudo apt-get install ncdu

在CentOS/RHEL系统上:

1
sudo yum install ncdu

使用

1
ncdu /path/to/directory

4.ls命令

ls命令也可以显示文件的大小,但不如du详细。

示例

  • 以人类可读的格式显示文件大小:
1
ls -lh
  • 显示目录的总大小:
1
ls -lhS

5.find命令结合du

可以使用find命令查找特定条件的文件,然后结合du命令查看它们的磁盘使用情况。

示例

  • 查找大于100MB的文件并显示其磁盘使用情况:
1
find /path/to/directory -type f -size +100M -execdu -h {} +

原文: https://www.yuque.com/jingdianjichi/xyxdsi/bnus9ng4m4oydmlu

👌Linux死锁是什么?

发表于 2025-04-16 | 更新于 2025-06-22 | 分类于 Linux
字数统计 | 阅读时长

👌Linux死锁是什么?

在Linux操作系统中,死锁(Deadlock)是指两个或多个进程在等待彼此释放资源,从而导致它们都无法继续执行的情况。这是一种常见的并发问题,尤其在多线程或多进程环境中。

死锁的四个必要条件

死锁的发生需要满足以下四个条件(也称为Coffman条件):

  1. 互斥条件(Mutual Exclusion):至少有一个资源是非共享的,即一次只能被一个进程使用。
  2. 占有并等待条件(Hold and Wait):一个进程已经持有了至少一个资源,并且正在等待获取其他被其他进程持有的资源。
  3. 不剥夺条件(No Preemption):资源不能被强制从进程中剥夺,只有持有资源的进程可以主动释放资源。
  4. 环路等待条件(Circular Wait):存在一个进程链,使得每个进程都在等待链中下一个进程所持有的资源。

死锁的检测和预防

为了处理死锁问题,可以采取以下几种策略:

1.预防死锁

预防死锁的方法包括破坏上述四个条件之一:

  • 破坏互斥条件:尽可能减少对非共享资源的使用。
  • 破坏占有并等待条件:在进程开始时一次性分配所有需要的资源,或者要求进程在请求资源前释放所有持有的资源。
  • 破坏不剥夺条件:允许操作系统强制剥夺某些资源。
  • 破坏环路等待条件:对资源进行排序,并要求进程按顺序请求资源。

2.避免死锁

避免死锁的方法主要是通过资源分配策略来确保系统永远不会进入死锁状态。常用的方法包括银行家算法(Banker’s Algorithm),它通过模拟资源分配情况来判断是否会导致死锁。

3.检测和恢复

如果无法预防或避免死锁,可以通过检测和恢复来处理死锁:

  • 死锁检测:定期检查系统中是否存在死锁。可以使用资源分配图(Resource Allocation Graph)来检测环路,从而识别死锁。
  • 死锁恢复:一旦检测到死锁,可以通过以下方式恢复:
    • 终止进程:强制终止一个或多个进程以打破死锁。
    • 资源剥夺:强制从某些进程中剥夺资源并重新分配。

👌Linux内存有问题怎么查看?

发表于 2025-04-16 | 更新于 2025-06-22 | 分类于 Linux
字数统计 | 阅读时长

👌linux 内存有问题怎么查看?

如果怀疑内存有问题,可以通过以下步骤进行检查和诊断:

1. 使用free命令查看内存使用情况

free命令可以显示系统的内存使用情况,包括总内存、已使用内存、空闲内存和缓存等信息。

1
free -h

输出示例:

1
2
3
total        used        free      shared  buff/cache   available
Mem: 7.8G 3.2G 1.5G 200M 3.1G 4.1G
Swap: 2.0G 0.0K 2.0G

2. 使用top或htop命令查看内存使用情况

top和htop命令可以实时显示系统的资源使用情况,包括内存使用情况。

  • top命令:
1
top
  • htop命令(需要安装):
1
2
sudo apt-get install htop
htop

3. 使用vmstat命令查看内存和系统性能

vmstat命令可以报告虚拟内存、进程、CPU 活动等信息。

1
vmstat 1 5

输出示例:

1
2
3
4
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1576828 72464 2192160 0 0 8 11 64 150 1 0 99 0 0
0 0 0 1576820 72464 2192160 0 0 0 0 59 144 0 0 100 0 0

4. 检查内存使用情况的日志

查看系统日志,如/var/log/syslog或/var/log/messages,看看是否有与内存相关的错误或警告。

1
2
sudo grep -i memory /var/log/syslog
sudo grep -i memory /var/log/messages

👌linux的中断机制用来干什么?

发表于 2025-04-16 | 更新于 2025-06-22 | 分类于 Linux
字数统计 | 阅读时长

👌linux的中断机制用来干什么?

Linux的中断机制用于处理各种硬件和软件事件,使操作系统能够及时响应并处理这些事件。中断机制在操作系统中起着至关重要的作用。

1.硬件设备的事件处理

硬件设备通过中断机制通知操作系统它们的状态变化或需要处理的事件。例如:

  • 键盘输入:当键盘按键被按下时,键盘控制器会发出中断信号,通知CPU读取按键值。
  • 网络数据接收:网卡接收到数据包时,会触发中断,通知操作系统处理接收到的数据。
  • 硬盘I/O完成:硬盘完成读写操作后,会发出中断信号,通知操作系统该操作已完成,可以继续处理数据。

2.定时器和时钟管理

操作系统使用定时器中断来管理系统时钟和时间片轮转。例如:

  • 系统时钟更新:定时器会定期触发中断,操作系统通过这些中断更新系统时钟。
  • 时间片轮转:在多任务操作系统中,定时器中断用于实现时间片轮转调度,确保各个进程能够公平地获得CPU时间。

3.系统调用和异常处理

软件中断(如系统调用和异常)允许用户空间程序与内核进行交互,并处理异常情况。例如:

  • 系统调用:用户程序通过触发软件中断进入内核态,执行系统调用以请求操作系统服务(如文件操作、进程管理等)。
  • 异常处理:当程序发生异常(如除零错误、非法内存访问等)时,CPU会触发异常中断,操作系统捕获并处理这些异常。

4.中断驱动的I/O操作

中断机制使得设备驱动程序可以实现高效的I/O操作。例如:

  • 非阻塞I/O:通过中断通知,设备驱动程序可以在设备准备好时处理I/O操作,而无需进程持续轮询设备状态,从而提高系统效率。

5.电源管理

中断机制在电源管理中也扮演重要角色。例如:

  • 电池状态变化:当电池电量低或充电完成时,电源管理芯片会触发中断,通知操作系统采取相应的措施(如节能模式或提示用户)。

6.多核处理器的中断分配

在多核处理器系统中,中断控制器(如APIC)可以将中断分配到不同的CPU核,以实现负载均衡和提高并行处理能力。

👌常见的jvm垃圾收集器的工作流程?

发表于 2025-04-16 | 更新于 2025-06-22 | 分类于 笔记
字数统计 | 阅读时长

👌常见的jvm垃圾收集器的工作流程?

题目详细答案

Serial

Serial 垃圾收集过程的简单之处在于其采用了单线程执行的方式,以简化实现并减少资源占用。

  1. 暂停用户线程(Stop the World):

在开始垃圾收集过程之前,Serial 垃圾收集器会暂停(停止)所有的用户线程。这是为了确保在垃圾收集过程中对象的状态不会被修改,从而保证垃圾收集的准确性。

  1. 执行垃圾收集:

一旦用户线程暂停,Serial 垃圾收集器会开启一个单线程来执行垃圾回收操作。这个线程会遍历堆中的对象,标记并清理不再使用的对象,以释放内存空间。

  1. 等待垃圾收集完成:

在垃圾收集过程中,用户线程会被暂停,直到垃圾收集完毕。这意味着用户线程无法在垃圾收集期间执行任何操作。

当垃圾收集完成后,Serial 垃圾收集器会恢复用户线程的执行。此时,垃圾已被清理,堆内存中有更多的可用空间供应用程序使用。

Parallel Scavenge 和 Parallel Old

Parallel Scavenge 和 Parallel Old 是 Parallel 收集器系列的两个组成部分,它们的工作机制相似,都是利用多线程并行执行垃圾回收操作,以提高整个垃圾收集过程的效率和吞吐量。以 Parallel Scavenge 为例来说明其工作机制:

  1. 多线程并行执行:Parallel Scavenge 收集器利用了多个线程并行执行新生代的垃圾回收操作。这意味着在进行新生代垃圾回收时,多个线程同时工作,加快了垃圾收集的速度。
  2. 暂停用户线程:与 Serial 收集器类似,Parallel Scavenge 在进行垃圾收集时会暂停用户线程,以确保垃圾回收的准确性。这一阶段通常称为“Stop the World”。
  3. 多线程并发清理:Parallel Scavenge 收集器的特点之一是在新生代垃圾收集过程中采用并发清理的方式。这意味着在暂停用户线程期间,多个线程同时清理新生代中的垃圾对象,从而更快地完成垃圾收集过程。

通过利用多个线程并行执行垃圾收集操作,Parallel Scavenge 能够充分发挥多核 CPU 的优势,提高了垃圾收集的效率。相比于 Serial 收集器,它能更快地完成垃圾回收操作,从而减少了应用程序的停顿时间,从而提高了整个应用程序的性能。

ParNew 和 Parallel Scavenge

ParNew 收集器和 Parallel Scavenge 收集器在工作流程上确实非常相似,都是并行垃圾收集器。

1、 标记阶段

在垃圾收集开始时,ParNew 收集器会暂停所有应用线程(Stop-The-World),然后开始标记所有存活的对象。标记阶段的主要任务是识别哪些对象是存活的,并将它们标记出来。

2、 复制阶段

在标记阶段完成之后,ParNew 收集器会将所有存活的对象从 Eden 区和一个 Survivor 区(例如 S0)复制到另一个 Survivor 区(例如 S1)。如果目标 Survivor 区没有足够的空间容纳所有存活对象,存活对象将被移动到老年代。

3、 清理阶段

复制阶段完成后,Eden 区和之前的 Survivor 区(例如 S0)中的所有对象都被认为是垃圾,并且这些区域的内存可以被清理和重用。

4、 应用程序恢复

在清理阶段完成之后,应用程序线程将被重新启动,继续执行。

ParNew 收集器的工作流程与 Parallel Scavenge 收集器类似,都是通过停止应用程序线程,然后利用多线程并行执行垃圾回收操作,最后恢复用户线程的执行。这种并行执行的方式能够提高垃圾收集的效率,同时在暂停用户线程期间确保垃圾收集的准确性。

CMS

CMS垃圾收集器为了尽量减少用户线程的停顿时间,采用了一种创新的策略。这一策略使得在垃圾回收过程的某些阶段,用户线程和垃圾回收线程可以共同工作,从而避免了长时间的垃圾回收导致用户线程一直处于等待状态。

整个 CMS 垃圾收集过程被划分为四个阶段,它们分别是:

  1. 初始标记:在这个阶段,CMS 垃圾收集器会对根对象进行一次快速的标记,标记出所有与根对象直接关联的存活对象。这个阶段需要暂停用户线程,因为要确保标记的准确性。
  2. 并发标记:在这个阶段,CMS 垃圾收集器会与用户线程并发执行,对整个堆进行标记。垃圾回收线程会在后台标记所有存活对象,而用户线程可以继续执行,不受影响。
  3. 重新标记:在并发标记阶段结束后,CMS 垃圾收集器会进行一次重新标记,来处理在并发标记阶段发生变化的对象。这个阶段需要暂停用户线程,以确保标记的准确性。
  4. 并发清理:在重新标记完成后,CMS 垃圾收集器会与用户线程并发执行,清理未标记的对象。垃圾回收线程会在后台清理不再使用的对象,而用户线程可以继续执行,不受影响。

通过将垃圾回收过程分为多个阶段,并在其中允许用户线程和垃圾回收线程并发执行,CMS 垃圾收集器成功地减少了用户线程的停顿时间。这种创新的并发垃圾收集策略提高了系统的响应性和用户体验,确保了应用程序的顺畅运行。

G1

G1 垃圾收集器的回收流程与 CMS 的逻辑大致相同,包括初始标记、并发标记、重新标记和筛选清除等阶段。但是,与 CMS 不同的是,G1 在最后一个阶段不会直接进行整体的清除。相反,它会根据用户设置的停顿时间进行智能的筛选和局部的回收。

  1. 初始标记:在初始标记阶段,G1 垃圾收集器会对根对象进行一次快速的标记,标记出所有与根对象直接关联的存活对象。这个阶段需要暂停用户线程,以确保标记的准确性。
  2. 并发标记:在并发标记阶段,G1 垃圾收集器会与用户线程并发执行,对整个堆进行标记。垃圾回收线程会在后台标记所有存活对象,而用户线程可以继续执行,不受影响。
  3. 重新标记:在并发标记阶段结束后,G1 垃圾收集器会进行一次重新标记,来处理在并发标记阶段发生变化的对象。这个阶段需要暂停用户线程,以确保标记的准确性。
  4. 筛选清除:在重新标记完成后,G1 垃圾收集器不会立即进行整体的清除操作。相反,它会根据用户设置的停顿时间智能地筛选出需要回收的 Region,并执行局部的回收。这样可以在尽量满足停顿时间的情况下,最大限度地回收垃圾。

通过这种智能的筛选和局部回收方式,G1 垃圾收集器能够更好地平衡垃圾回收的效率和停顿时间,从而提高系统的响应性和用户体验。

ZGC

ZGC 的垃圾回收过程几乎全部都是并发执行的,即与应用程序线程同时进行。

  1. 初始标记(Initial Mark):在初始标记阶段,ZGC 会标记出根对象以及直接与根对象关联的存活对象。这个阶段需要短暂地暂停所有应用线程,以确保标记的准确性。
  2. 并发标记(Concurrent Mark):在并发标记阶段,ZGC 与应用程序线程并发执行,标记所有存活对象。这个阶段不会暂停应用程序线程,因此垃圾回收和应用程序可以并发执行。
  3. 最终标记(Final Mark):在并发标记阶段结束后,ZGC 需要再次短暂地暂停所有应用线程,完成最终的标记工作。这个阶段主要用于标记在并发标记阶段有可能发生变化的对象。
  4. 筛选(Concurrent Sweep):在最终标记完成后,ZGC 会进行一次筛选,确定哪些对象可以被回收。这个阶段会并发地进行,不会暂停应用程序线程。
  5. 并发清除(Concurrent Cleanup):在筛选阶段完成后,ZGC 会并发地清除未被标记的对象,释放它们所占用的内存。这个阶段也不会暂停应用程序线程。

👌Mysql常用监控指标?

发表于 2025-04-11 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌Mysql常用监控指标?

1、QPS:数据库每秒处理的请求数量

1
show global status where variable_name in ('Queries', 'uptime');

QPS = (Queries2 -Queries1) / (uptime2 - uptime1)

2、TPS:数据库每秒处理的事务数量

1
show global status where variable_name in ('com_insert' , 'com_delete' , 'com_update', 'uptime');

事务数TC ≈’com_insert’ , ‘com_delete’ , ‘com_update’

TPS ≈ (TC2 -TC1) / (uptime2 - uptime1)

3、并发数:数据库实例当前并行处理的会话数量

1
show global status like 'Threads_running';

4、连接数:连接到数据库会话的数量

1
show global status like 'Threads_connected';

5、缓存命中率:查询命中缓存的比例

innodb缓冲池查询总数:

1
show global status like 'innodb_buffer_pool_read_requests';

innodb从磁盘查询数:

1
show global status like 'innodb_buffer_pool_reads';

生产中配置报警阈值:innodb_buffer_pool_read_requests /(innodb_buffer_pool_read_requests + innodb_buffer_pool_reads) > 0.95

6、可用性:数据库是否可以正常对外服务

周期性连接数据库并执行 select @@version;

7、阻塞:当前阻塞的会话数

1
2
3
4
5
6
7
8
select waiting_pid as '被阻塞线程',
waiting_query as '被阻塞SQL',
blocking_pid as '阻塞线程',
blocking_query as '阻塞SQL',
wait_age as '阻塞时间',
sql_kill_blocking_query as '建议操作'
from sys.innodb_lock_waits
where(unix_timestamp()-unix_timestamp(wait_started))>阻塞秒数

8、慢查询:慢查询情况

开启慢查询日志。my.inf

1
2
3
4
slow_query_log=on
slow_query_log_file=存放目录
long_query_time=0.1秒
log_queries_not_using_indexes=on

👌如何写好代码?

发表于 2025-04-11 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌如何写好代码?

前言

从代码的角度来说,好的代码可以提高系统的可维护性,扩展性。坏的代码会导致系统后期的维护和复杂性都变得非常高,非常混乱。全是坏代码的话,如果不重构就是,一坨屎山。如果重构的话,重构的人,就要💩上雕花。这是很难受的。下面是鸡哥觉得一个好代码要注意的点,大家理解吸收之后和面试官吹就好了。

代码整洁、可读

这个是必须的,代码工整,好理解,不要为了秀技,写一堆复杂的代码。而且代码之间的逻辑一定要清晰,不要大段大段的写,一屏幕一屏幕的写,没意义。需要划分模块,规定方法,单一职责,各司其职。

重复的代码会极大的增加后续的维护成本,也容易造成修改的遗漏。一定要对重复代码进行抽象和处理。

遵循规范

遵循项目风格的规范,不要搞特殊,每个项目历史背景不同,时间不同,风格都不一样,大家一定要注意保持一致,不要增加本来熟悉这个项目的人的理解成本。

还有就是团队内使用统一的代码格式,如缩进、空格和注释风格。

模块化设计

开闭原则和单一原则、模块化。要重点考虑。这样当一个需求发生的时候,其他人修改代码也是十分放心的。只需要关注自己修改的地方,而不需要考虑更多的逻辑。

模块间的依赖尽量少,各自搞各自的。不要强耦合。

单一职责就是一个方法,类最好只干一件事,这样的理解成本非常低。

注释

其实说过好多遍,一定要写,复杂的逻辑,注释更加易于理解。但是不要写成流水账,而是描述代码的意图。

单元测试

确保关键功能都有对应的单元测试,覆盖常见的和边界的用例,使用持续集成工具自动运行测试,确保每次代码变更都不会破坏已有功能。

持续重构

在开发过程中定期进行代码重构,改善代码结构和质量,每次重构的改动应尽量小且可控,确保系统的稳定性。

鲁棒性

鲁棒性表示系统可以在各种异常的情况下仍然可以稳定的工作,而不是出现不可预期的问题。好的代码在设计的时候就需要考虑各种异常和极端的场景应该如何处理,也就是常常说的面向失败和异常编程,多考虑失败场景如何处理。

学习和使用设计模式

学习常见的设计模式并在适当的场景下应用,可以提高代码的可扩展性和可维护性。同时不过度使用设计模式,保持设计的简单和直接。

代码评审

通过代码评审发现潜在问题,分享知识,提高团队整体的代码质量。积极接受同事的反馈,持续改进自己的编码习惯和技巧。一定要多吸取别人建议。

性能大关

在开发过程中关注代码的性能,避免不必要的性能瓶颈。使用性能分析工具检测和优化代码中的性能问题。

通过遵循这些建议,可以显著提高代码的质量,使其更易于维护、扩展和理解。

👌如何准备项目压测?

发表于 2025-04-11 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌如何准备项目压测?

一道开放题,年限高的会问到,初入茅庐的小伙伴会问。我不是开发吗。为什么问压测的问题!压测研发同样要配合很多东西。考察你的全局掌控能力。主要是要先梳理清楚几个方面。

数据库层面梳理

数据库层面我们重点关注的是高可用,慢sql,预警,连接池,读写分离。

比如库名,数据库从库是否在两个以上,并且从库不在同一个机房,是否订阅慢SQL,数据库线程池是否配置合理,是否有考虑库存容量及增长情况,数据库是否做了读写分离等等。

Redis层面

梳理集群,缓存主备是否双写,是否具备动态切换机制,是否考虑缓存容量及增长情况,是否配置缓存报警,是否有key未设置过期时间,是否存在热Key,是否存在大Key

应用层面

梳理是否为双机房部署,JVM配置标准化检查,内存占用率排查,是否设置磁盘预警规则

消息队列层面

梳理TOPIC,队列类型,是否配置消息积压报警,是否开启消息归档

外部依赖梳理

作为前台的业务系统,主要是依赖中台的外部接口来进行业务逻辑处理,那么一定要梳理外部依赖的接口,别名,外部接口负责人,是否要提升限流值,限流后触发的现象,以及外部不可用是否有降级措施等等。

梳理依赖的外部接口,当前qps,预估qps ,紧急联系人

资源梳理

容器资源作为我们的基石,我们有必要去把每个服务占用的资源情况梳理清楚,同时判断在618期间是否要进行扩容,来扛住更多的量,分析机房之间的机器数量是否均衡,应该保证1:1的情况。不要造成流量的倾斜。对于一些大的应用,比如超过500核,我们也要认真评估其是否有必要。

梳理,现有应用总容器数,促销活动总容器数,机房,分组,计划扩容数量等等

应用接口梳理

梳理功能模块,接口级别,场景描述,日常QPS(峰值),日常调用量/天,日常TP999,预估QPS,预估调用量,预估TP999,是否压测,负责人

配合测试确定范围

梳理清楚之后,就是和测试进行压测范围的圈定,确定接口和预计达到的巅峰。

压测观察

边压测的过程要边进行观察。如果有异常,或者恶劣影响,及时停止!

压测复盘

复盘压测时不通过的接口,和性能瓶颈,下来进行优化。

👌线上问题处理?

发表于 2025-04-11 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌线上问题处理方法论

方法论都是精华,希望各位鸡友们,理解其中深邃之处。

当产生线上问题的时候,如果没有一套方法处理机制,很容易就陷入慌乱,思路不清晰,会导致损失越来越大,反馈越来越多,耗时越老越长。基于经验,可以沉淀出一些操作步骤。

问题发生反馈

当收到问题发生反馈的时候,第一个思路!!!止损,止损,止损!可能工作经验少,或者没有工作经验的人体会不到。记住,不是去追究问题是什么原理发生的,问题是谁造成的,就一个目地,任何方案,快速止损,立马恢复系统。涉及金钱、损失之类的线上问题,立马向上升级,切忌闷头自己处理,不上报。老板们的资源比你多,经验多,说不定可以快速帮到你!

立马根据自己经验评估,是否非常熟悉,可快速解决,如果不可以,立马拉群,寻求其他小伙伴帮助!

在止损的过程中,尽可能保留线上的证据,比如日志、dump、异常流量情况 等等。方便后面复盘。

心态稳住,事情已经发生,我们在这个阶段要做的是解决问题,而不是思考之后会不会挨骂,等等等。心态要平和,专注到当前事件的解决上。

如果你是旁观者,或者来帮忙的,不要打扰主解决者的思路,问一些奇奇怪怪的,不了解情况的问题。

问题定位思路

收集问题产生现象,定位入口,定位日志,定位报错信息。

收集要保证尽可能的多汲取信息,不仅仅是日志,还可能是用户的表达,用户的操作行为,手机机型,网络等等。

第一反应:快速确认今天是否有上线操作影响。如果有立马切流量,或回滚。

开始观察日志分析,确认实际的报错信息。

观察报错后,依据自身经验开始排查,是否是调用量暴增,还是有明显的报错日志。抓取错误流量参数。服务器是否正常。

基于以上,联想问题点。

检查流量问题,服务调用异常问题,网络问题,服务器问题,数据库问题等等。

同时测试同学快速复现,看是否可以产生其他有价值的信息。

最终综合多信息找到异常点,定位故障。一定要不慌,保证准确的同时,迅速!

问题后复盘

评估影响级别,用户量,gmv 受损等等业务重视的指标。

基于规则,来判定,p0,p1,p2 等等各级别的事故级别。

同时认真仔细的回顾当时问题产生的时候,大家的状态和操作是否变形,找到可以优化的地方,比如有一个什么角色去并行做什么东西可以提高整体的效率等等。

复盘不是为了追责,喷人,只是为了让大家都知悉问题发生的过程,后续不再踩坑。
比如后续的报警措施啊,异常处理措施啊,优化思路啊,这次排查效率还有什么要优化的呀。沉淀思路啊。知识库沉淀,问题归档。

同时分析出问题产生的根本原因。开发阶段不行还是测试不行,还是产品流程问题,还是上线问题!

同时查看其他需求是否也有此问题。

铭记

海恩法则,也被称为海恩法则或海因里希法则,是由德国飞机涡轮机的发明者帕布斯·海恩提出的。这一法则主要应用于航空界,强调飞行安全,但也被广泛应用于企业生产管理、安全管理和人力资源管理等领域。海恩法则的核心思想是,每一起严重事故的背后,必然有29次轻微事故和300起未遂先兆以及1000起事故隐患。

慢sql优化方向

发表于 2025-04-10 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

慢sql优化方向?

详细解读

1. 优化查询语句

使用适当的索引

  • 创建索引:确保查询使用了适当的索引。对频繁出现在WHERE、JOIN、ORDER BY和GROUP BY子句中的列创建索引。
1
CREATE INDEX idx_column_name ON table_name(column_name);
  • 复合索引:对于多列查询,考虑使用复合索引(多列索引)。
1
CREATE INDEX idx_columns ON table_name(column1, column2);

避免全表扫描

  • 使用合适的过滤条件:确保WHERE子句中的条件能够有效地利用索引,避免全表扫描。
1
SELECT*FROM table_name WHERE indexed_column ='value';
  • 限制返回的行数:使用LIMIT子句限制返回的行数,减少数据库的负担。
1
SELECT*FROM table_name WHEREcondition LIMIT 10;

优化JOIN操作

  • 使用小表驱动大表:在JOIN操作中,确保小表在前,大表在后。
1
SELECT*FROM small_table ST JOIN large_table LT ON ST.id = LT.id;
  • 索引连接列:确保连接列上有索引,以加快JOIN操作。

避免不必要的复杂查询

  • 简化查询:尽量简化查询,避免使用不必要的子查询和嵌套查询。
1
2
-- 避免复杂的嵌套查询SELECT*FROM table_name WHERE id IN (SELECT id FROM another_table WHEREcondition);
-- 使用JOIN替代SELECT table_name.*FROM table_name JOIN another_table ON table_name.id = another_table.id WHERE another_table.condition;

2. 优化数据库设计

规范化与反规范化

  • 规范化:确保数据库设计符合第三范式,减少数据冗余。
  • 反规范化:在某些情况下,为了性能,可以适度反规范化,减少复杂的JOIN操作。

分区表

  • 表分区:对于非常大的表,可以使用表分区,将数据分成更小的部分,提高查询性能。
1
2
3
4
5
6
7
8
9
CREATETABLE orders (
order_id INT,
order_date DATE,
...
)PARTITIONBYRANGE (YEAR(order_date)) (
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022)
);

3. 优化服务器配置

调整 MySQL 配置参数

  • 调整缓冲池大小:对于 InnoDB 存储引擎,调整innodb_buffer_pool_size参数,使其尽量大(但不要超过物理内存的 70-80%)。
1
[mysqld]innodb_buffer_pool_size = 4G
  • 调整查询缓存:根据应用需求,调整查询缓存大小。
1
[mysqld]query_cache_size = 64M

使用合适的存储引擎

  • 选择适当的存储引擎:根据应用需求选择合适的存储引擎(如 InnoDB、MyISAM)。

4. 使用性能分析工具

使用EXPLAIN分析查询

  • 分析执行计划:使用EXPLAIN分析查询的执行计划,识别性能瓶颈。
1
EXPLAIN SELECT*FROM your_table WHERE your_condition;

使用性能模式(Performance Schema)

  • 收集性能数据:使用 Performance Schema 收集详细的性能数据,分析慢查询。
1
2
SELECT*FROM performance_schema.events_statements_summary_by_digestORDERBY SUM_TIMER_WAIT DESC
LIMIT 10;

5. 监控和调优

持续监控

  • 使用监控工具:使用 MySQL Enterprise Monitor、Percona Monitoring and Management (PMM)、New Relic、Datadog 等工具持续监控数据库性能。

定期调优

  • 定期审查查询:定期审查和优化慢查询,确保数据库性能持续提升。

👌如何监控慢sql?

发表于 2025-04-09 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌如何监控慢sql?

1. 启用慢查询日志

慢查询日志是 MySQL 内置的一种功能,用于记录执行时间超过指定阈值的 SQL 查询。

配置 MySQL 配置文件(my.cnf 或 my.ini)

在 MySQL 配置文件中添加或修改以下参数:

1
2
3
4
5
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
log_queries_not_using_indexes = 1
  • slow_query_log:启用慢查询日志。
  • slow_query_log_file:指定慢查询日志文件的路径。
  • long_query_time:定义慢查询的阈值(单位:秒)。
  • log_queries_not_using_indexes:记录未使用索引的查询。

重启 MySQL 服务

修改配置文件后,重启 MySQL 服务以使配置生效:

1
sudo systemctl restart mysql

2. 使用 MySQL 内置的性能模式(Performance Schema)

Performance Schema 是 MySQL 内置的一个工具,用于收集数据库内部的运行时统计信息。可以通过以下步骤启用和使用 Performance Schema:

启用 Performance Schema

在 MySQL 配置文件中添加或修改以下参数:

1
2
[mysqld]
performance_schema = ON

重启 MySQL 服务

修改配置文件后,重启 MySQL 服务以使配置生效:

1
sudo systemctl restart mysql

查询慢查询信息

使用以下 SQL 语句查询慢查询信息:

1
2
SELECT*FROM performance_schema.events_statements_summary_by_digestORDERBY SUM_TIMER_WAIT DESC
LIMIT 10;

3. 使用 MySQL 企业监控工具

MySQL 提供了企业版的监控工具,如 MySQL Enterprise Monitor,它可以自动收集、分析和报告慢查询信息。这个工具适合企业环境下的全面监控和管理。

4. 使用第三方监控工具

有许多第三方监控工具可以帮助你监控 MySQL 的性能,包括慢查询。这些工具通常提供更丰富的功能和更友好的界面。常见的第三方工具包括:

  • **Percona Monitoring and Management (PMM)**:一个开源的监控和管理工具,专为 MySQL 和 MongoDB 设计。
  • New Relic:一个强大的应用性能监控工具,支持 MySQL 和其他数据库。
  • Datadog:一个综合性的监控平台,支持 MySQL 和其他数据库。

5. 使用 SQL 分析工具

MySQL 提供了EXPLAIN语句,用于分析 SQL 查询的执行计划。通过分析执行计划,可以识别和优化慢查询。

使用EXPLAIN分析查询

1
EXPLAIN SELECT*FROM your_table WHERE your_condition;

EXPLAIN的输出结果包含了查询执行的详细信息,包括使用的索引、扫描的行数等。通过分析这些信息,可以找出查询的性能瓶颈并进行优化。

总结

监控慢 SQL 是一个持续的过程,需要结合多种工具和方法。以下是一个综合的监控策略:

  1. 启用慢查询日志:记录执行时间超过阈值的查询。
  2. 使用 Performance Schema:收集和分析详细的运行时统计信息。
  3. 使用企业监控工具:如 MySQL Enterprise Monitor,进行全面的监控和管理。
  4. 使用第三方监控工具:如 Percona Monitoring and Management (PMM)、New Relic、Datadog 等。
  5. 使用 SQL 分析工具:如EXPLAIN,分析和优化查询执行计划。

👌如何快速熟悉一个复杂的系统

发表于 2025-04-09 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌如何快速熟悉一个复杂的系统?

这个问题,是一道开放性的问题,很多小伙伴在面试的时候,尤其是一些高级的岗位,会被面试官问到,其实像这种问题,面试官的目的就是看你对于此是否有一定的方法论沉淀,如果平时只是一直在写crud,那么对于这种问题,一定是没有思考过的。通过此也能筛选出一些人的区分。

鸡翅老哥曾经做过好几次的大型项目的系统交接,每次交接都是对于一个系统从陌生到熟悉的过程,要进行快速掌控的过程。在这个过程中,你会遇到文档资料不全,原交接人可能直接离职了,找不到人。交接人没时间讲解业务等等等难受的问题,这个时候都是要靠自己来花时间处理。今天鸡翅老哥给大家沉淀一些方法论。

本篇文章的背景,我们就以一个背景来展开描述:一个大型的电商项目进行交接,你是其中的交接负责人,你该如何做?以及如何快速熟悉系统。

盘资源,盘权限

在进行系统交接的时候,我们一定要做好的一件事就是和原来的团队,确认当前大项目所需要的各种清单,例如代码库,数据库资源,redis资源等中间件情况,网关域名资源等等等。这个时候要建立表格,确保交接人和被交接人,一项一项的都要进行check,防止遗漏,不然交接完之后,再发现缺东西,那就直接难受了。

盘优先级

一个大型的项目是由很多子系统组成的,我见过最多的是100多个微服务应用。如果这么多的应用,全部由团队来进行熟悉,那就累到死了,一定要知道哪些是核心应用,哪些是非核心,我们可以编号,p0,p1,p2这种,这个过程和原来的团队要做好沟通,也为后续的熟悉做好基础。

盘业务

从看的见,摸得着的层面盘清之后,就可以开始盘业务,这个过程要依靠原有团队的讲解,以及沉淀的业务文档来进行学习和梳理。如果二者都没有,那么恭喜,步入地狱难度,需要自己通过体验产品,来划分出模块,盘出整个业务,这个地方也是有技巧,首先要划清用例者,用户身份,运营身份,等等各种身份,基于身份的形式,体验产品的整个流程并形成自己的认知业务文档。

盘技术栈

从这个地方开始就是熟悉系统的流程,先去熟悉技术栈,都是采用的什么语言,什么中间件,什么开源,基于什么样的形式进行交互,这个过程从核心系统开始,看pom文件,配置文件来了解其中的技术点。比如看到mq,大概就能猜到项目里面有异步的数据流交互,看到es,猜测这个应用是具备大数据量的处理和查询。

盘入口

什么叫入口呢,controller,对外提供的rpc,http能力,定时任务,这些都是入口,从入口来入手业务逻辑是最快的一种方式,所以先找项目的统一对外出口,不然直接看内部的逻辑,是会发懵的,比如从一个controller开始看起,就知道了这个项目对外提供的是什么模块,什么功能。方便我们去理解。

盘数据流

有了入口之后,其实我们已经大概知道该系统提供的能力是什么。在通过入口进行再深一层的看,就知道数据的流向是什么情况,在这个过程中,其实可以初步梳理出一个功能整体的系统交互和数据流向。这也是最耗时间的一个环节。

盘核心和易出问题的点

我们一个大项目的交接会有很多项目,如果挨个都看,不分优先级的去看,是很耗费时间和精力的,所以按照核心应用开始,以及找出平时容易出问题的点的应用,这种的收益是最大的。时间是宝贵的,我们也应该从这方面来进行熟悉。

盘细节

细节就不用说了,有了上面的这些铺垫之后,接下来要做的就是一个模块一个模块的整体的代码逻辑梳理,切忌每一行代码都看,要看核心的,简单的地方,了解数据流的大概即可。这样我们可以快速交接项目。

👌如何设计好一个接口?

发表于 2025-04-09 | 更新于 2025-06-22 | 分类于 方法论
字数统计 | 阅读时长

👌如何设计好一个接口?

这个问题是非常常见的一个问题。无论是在日常写代码的时候,还是实际面试的时候!

说实话,面试过很多人,上来一问,就是一句,没啥可以设计的,能正常使用查询即可,这种我都直接给他 pass 掉。

从设计接口的角度,主要需要遵循几个规范:

接口命名

命名的时候,别在那瞎搞,瞎起,不断增加后面人的理解成本,一定要见名知意。清晰,遵循开发规范。常见的规范可以直接参考阿里巴巴开发规范。不要觉得这些东西很麻烦,对于有经验的开发人员,大家遵守相同的规范,可以快速入手,一下子就明白,高手过招,无需多言。

接口设计的时候,遵循单一指责,不要做什么大而全的,比如下单并支付,千万别这么设计,createOrderAndPay。一定要做拆分,createOrder。payOrder。职责单一。

参数校验

好的接口,入参和出参的校验,一定要做好处理!参数前置校验做的好,在后面的核心业务代码,可以节省很多时间,核心业务代码,可以专注的处理业务逻辑。大家在开发过程中,有很多的时候,参数校验没做好,到业务代码,空指针等等问题,这些都是可以提前规避的。

对于出参的校验,做好错误码的定义。定义好具体的错误文案对应。要做友好的展示。不要提示参数错误,而是要提示具体的参数问题。

日志打印

关键日志必须打印打印打印!!!!经常有问题的时候,因为关键日志不打印,导致排查和理解的成本特别高,方法的入参和出参的日志可以通过拦截器来进行处理。

然后就是核心代码的关键日志。一定要打,比如经历了一些复杂逻辑的转换,关键的步骤打印日志。

打印日志要注意级别。error 、warn 、info 、debug。

公共包的,经历过大规模考验的 debug 即可。

普通业务就是 info。要注意,大 json 转换,加 isinfo 的判断,防止影响性能。

error 不要乱打,真错误的时候再打。蛇打七寸。

性能设计

对于核心接口设计的时候,必须有监控和异常的报警机制。tp99,qps,可用率。这些都要看到。

要考虑调用第三方接口,第三方的性能会不会影响自己,第三方失败的时候,如何进行处理。重试还是失败等等。

要不要加缓存,提升性能,要不要用多线程提升性能。要不要幂等,数据库压力等等都要考虑。

异常处理

一定要考虑接口的各种异常情况,应该产生的相关行为。要尽量预知做处理。不要产生不可控情况。相关校验做好。还有就是常见的你提供的接口的超时问题,要做好超时事件设置,自身也可以进行快速熔断返回。自身接口超时时间要小于上游接口。

注释

都给鸡哥写注释,别给鸡哥在这防御性编程,不写注释是一个非常坏的习惯!!到时候,你自己回过头可能都看不懂了。

限流/熔断

自己的接口要考虑好下下策,如果大流量来了,我们的服务是否能支撑的住,提前最好评估和限制调用方流量,防止自己什么都吃,导致服务整体直接不可用。极端情况也可以做熔断和快速失败,总之就是要保护好自己!

接口文档

写个清晰的接口文档,可以减少很多不必要的沟通和交流,一个好的接口文档,调用方的人,一看就懂,自然就不用浪费你的时间,教他传什么了。这种可以极大的减少沟通成本。

分布式锁切面(DistributeLockAspect)代码解读

发表于 2025-03-22 | 更新于 2025-06-22 | 分类于 分布式
字数统计 | 阅读时长

分布式锁切面(DistributeLockAspect)代码解读

  • 这是一个基于Spring AOP和Redisson实现的分布式锁切面,用于在方法执行前获取分布式锁,执行后释放锁。下面我将从多个维度详细解析这段代码:

一、核心功能概述

该切面主要实现以下功能:

  • 通过@DistributeLock注解标记需要加锁的方法

  • 支持动态生成锁的key(使用SpEL表达式)

  • 提供多种加锁策略(立即加锁、尝试加锁、带过期时间的锁)

  • 确保锁的最终释放(在finally块中)

二、代码结构解析

  1. 类定义与依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Aspect
    @Component
    @Order(Integer.MIN_VALUE) // 确保最先执行
    public class DistributeLockAspect {
    private RedissonClient redissonClient; // Redisson客户端

    // 构造函数注入
    public DistributeLockAspect(RedissonClient redissonClient) {
    this.redissonClient = redissonClient;
    }
    }
    @Aspect:声明这是一个切面类

    @Order(Integer.MIN_VALUE):确保这个切面在调用链中最早执行

    通过构造函数注入RedissonClient,用于操作分布式锁
  2. 核心切面方法

    1
    2
    3
    4
    5
    @Around("@annotation(cn.hollis.nft.turbo.lock.DistributeLock)")
    public Object process(ProceedingJoinPoint pjp) throws Exception
    使用@Around注解拦截所有带有@DistributeLock注解的方法

    ProceedingJoinPoint参数可以获取被拦截方法的信息

三、关键实现逻辑

  1. 锁Key的生成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    String key = distributeLock.key();
    if (DistributeLockConstant.NONE_KEY.equals(key)) {
    // 使用SpEL表达式动态生成key
    SpelExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression(distributeLock.keyExpression());
    // 设置上下文变量
    EvaluationContext context = new StandardEvaluationContext();
    Object[] args = pjp.getArgs();
    String[] parameterNames = discoverer.getParameterNames(method);
    // 绑定参数到上下文
    for (int i = 0; i < parameterNames.length; i++) {
    context.setVariable(parameterNames[i], args[i]);
    }
    key = String.valueOf(expression.getValue(context));
    }
    String scene = distributeLock.scene();
    String lockKey = scene + "#" + key; // 最终锁key

支持静态key和动态key两种方式

动态key使用SpEL表达式,可以从方法参数中取值

最终key格式为scene#key,便于分类管理

  1. 加锁逻辑
    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
    RLock rLock= redissonClient.getLock(lockKey);
    try {
    boolean lockResult = false;
    if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
    // 立即加锁逻辑
    if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
    rLock.lock(); // 永久锁
    } else {
    rLock.lock(expireTime, TimeUnit.MILLISECONDS); // 带过期时间的锁
    }
    lockResult = true;
    } else {
    // 尝试加锁逻辑
    if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
    } else {
    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
    }
    }

    if (!lockResult) {
    throw new DistributeLockException("acquire lock failed... key : " + lockKey);
    }

    // 执行目标方法
    response = pjp.proceed();
    } finally {
    // 释放锁
    if (rLock.isHeldByCurrentThread()) {
    rLock.unlock();
    }
    }

支持四种加锁模式:

  • 永久锁(无过期时间)

  • 带过期时间的锁

  • 尝试获取锁(带等待时间)

  • 尝试获取带过期时间的锁(带等待时间和过期时间)

确保锁最终会被释放(finally块)

四、设计亮点

灵活的锁Key生成:支持静态key和SpEL动态key

多种加锁策略:满足不同业务场景需求

线程安全:确保只有持有锁的线程才能释放锁

日志完备:记录加锁、释放锁的关键信息

异常处理:加锁失败抛出特定异常

五、使用示例

1
2
3
4
5
6
7
8
9
10
11
@Service
public class OrderService {
@DistributeLock(
scene = "order",
keyExpression = "#orderId", // 使用SpEL从参数获取值
expireTime = 5000
)
public void processOrder(String orderId) {
// 业务逻辑
}
}

六、改进建议

锁的可重入性:Redisson的RLock本身支持可重入,但可以增加重入计数日志

锁的自动续期:对于长时间任务,可以实现锁的自动续期

更细粒度的异常处理:区分业务异常和锁相关的异常

性能监控:添加锁获取时间、持有时间等监控指标

这个切面实现了一个完整且健壮的分布式锁解决方案,可以很好地应用于需要分布式锁的业务场景中。

什么是TCC(Try-Confirm/Cancel)模式?

发表于 2025-03-16 | 更新于 2025-06-22 | 分类于 分布式
字数统计 | 阅读时长

什么是TCC(Try-Confirm/Cancel)模式?

TCC(Try-Confirm/Cancel)模式是一种分布式事务管理模式,用于确保分布式系统中的数据一致性。它通过将事务分解为三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel),来实现分布式事务的管理和控制。

TCC模式的三个阶段

  1. 尝试(Try):
    • 尝试阶段是预留资源或锁定资源的阶段。
    • 该阶段确保所有需要的资源都可以被成功预留,以便后续的确认操作能够顺利进行。
    • 这一步不会真正提交事务,只是做预留操作。
  2. 确认(Confirm):
    • 确认阶段是实际提交事务的阶段。
    • 在这个阶段,系统会确认并提交所有的操作,确保数据的最终一致性。
    • 如果尝试阶段成功,确认阶段应该始终成功。
  3. 取消(Cancel):
    • 取消阶段是回滚事务的阶段。
    • 如果尝试阶段失败,或者确认阶段未能执行,系统会执行取消操作来释放预留的资源或回滚已经进行的操作。
    • 这一步确保系统能够恢复到尝试阶段之前的状态。

TCC模式的工作流程

  1. 尝试阶段:
    • 各个服务预留资源或锁定资源。
    • 如果所有服务的尝试操作都成功,则进入确认阶段;否则,进入取消阶段。
  2. 确认阶段:
    • 各个服务正式提交事务,完成实际操作。
    • 确认阶段的操作应当是幂等的(即多次执行结果相同)。
  3. 取消阶段:
    • 如果尝试阶段中的任何一个操作失败,或者确认阶段未能成功执行,系统会调用取消操作来回滚事务。
    • 取消阶段的操作也应当是幂等的。

TCC模式的优缺点

优点:

  1. 强一致性:通过明确的三阶段操作,确保了分布式事务的强一致性。
  2. 灵活性:可以根据业务需求灵活定义每个阶段的操作。
  3. 幂等性:由于确认和取消操作需要幂等性,确保了系统的稳定性和可靠性。

缺点:

  1. 复杂性:需要为每个操作定义对应的尝试、确认和取消操作,增加了系统设计和实现的复杂性。
  2. 资源占用:尝试阶段需要预留资源,可能会导致资源占用时间较长。
  3. 实现成本:需要确保每个阶段的操作都是幂等的,增加了开发和维护成本。
<i class="fa fa-angle-left"></i>1…456…12<i class="fa fa-angle-right"></i>

232 日志
18 分类
28 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini