智能sc一面
智能sc一面-2025/4/17
更多完善:真实面经
- Java 的异常分类
异常分为两类,一类Error,一类Execption。这两个类都是Throwable的子类,只有继承Throwable 的类才可以被throw或者catch
Error: 表示严重的系统问题,通常与代码无关,应用程序一般无法处理:
OutOfMemoryError
:内存不足StackOverflowError
:栈溢出VirtualMachineError
:虚拟机错误
Execption: 程序可以处理的异常,分为两大类
**检查型异常(Checked Exception):**在编译时检查,必须被显示的捕获或者声明抛出,
-
示例:
-
IOException
:I/O操作异常 -
SQLException
:数据库操作异常 -
ClassNotFoundException
:类未找到 -
FileNotFoundException
:文件未找到
非检查型异常(Unchecked Exception)
-
包括
RuntimeException
及其子类 -
不强制要求捕获或声明
-
示例:
-
NullPointerException
:空指针异常 -
ArrayIndexOutOfBoundsException
:数组越界 -
ArithmeticException
:算术异常(如除零) -
IllegalArgumentException
:非法参数 -
NumberFormatException
:数字格式错误
异常的处理的关键字
try
:尝试执行可能抛出异常的代码块catch
:捕获并处理特定类型的异常finally
:无论是否发生异常都会执行的代码块throw
:主动抛出一个异常throws
:声明方法可能抛出的异常
自定义异常,比说说自定义业务异常,新增状态码
/*** 自定义异常类*/
public class BusinessException extends RuntimeException {/*** 错误码*/private final int code;public BusinessException(int code, String message) {super(message);this.code = code;}public BusinessException(ErrorCode errorCode) {super(errorCode.getMessage());this.code = errorCode.getCode();}public BusinessException(ErrorCode errorCode, String message) {super(message);this.code = errorCode.getCode();}public int getCode() {return code;}
}
全局异常处理器
/*** 全局异常处理器*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(BusinessException.class)public BaseResponse<?> businessExceptionHandler(BusinessException e) {log.error("BusinessException", e);return ResultUtils.error(e.getCode(), e.getMessage());}@ExceptionHandler(RuntimeException.class)public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {log.error("RuntimeException", e);return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");}
}
具体使用的话,有限捕获特定的异常,比如说Sentinel的BlockExecption,包括限流异常,以及降级异常,所以就先捕获特定的异常。
2.深拷贝浅拷贝的区别和使用场景
浅拷贝 (Shallow Copy)
- 只复制对象本身和其基本类型字段
- 对于引用类型字段,只复制引用地址而不复制引用的对象
- 原始对象和拷贝对象共享引用类型的成员变量
深拷贝 (Deep Copy)
- 复制对象本身及其所有引用的对象
- 创建一个完全独立的副本
- 原始对象和拷贝对象不共享任何引用
如何实现浅拷贝 Object.clone()
class Person implements Cloneable {String name;int age;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // 浅拷贝}
}
如何实现深拷贝
递归clone,或者序列化
class Address implements Cloneable {String city;public Address(String city) { this.city = city; }@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // 浅拷贝}
}class Person implements Cloneable {String name;int age;Address address;public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}@Overrideprotected Object clone() throws CloneNotSupportedException {Person cloned = (Person) super.clone();cloned.address = (Address) address.clone(); // 深拷贝return cloned;}
}
深拷贝和浅拷贝的应用场景:
浅拷贝仅关注与复制,使用
深拷贝关注,应用隔离
3.Java 注解的原理
Java 注解(Annotation)是一种(标记)元数据,用于为代码提供额外的信息,不会直接影响代码的执行,但可以通过反射机制在运行时或编译时被读取和处理。
- 注解的定义
注解通过 @interface
关键字定义,类似于接口的定义。注解可以包含元素(类似于方法),这些元素可以有默认值。
通过元注解来规定注解使用的范围。元注解是用于定义其他注解的注解。Java 提供了以下几种元注解:
-
**@Retention**
:指定注解的保留策略。 -
RetentionPolicy.SOURCE
:注解仅在源码中保留,编译时丢弃。 -
RetentionPolicy.CLASS
:注解在编译时保留,但运行时不可见(默认策略)。 -
RetentionPolicy.RUNTIME
:注解在运行时保留,可以通过反射读取。 -
**@Target**
:指定注解可以应用的目标元素。 -
ElementType.TYPE
:类、接口、枚举。 -
ElementType.METHOD
:方法。 -
ElementType.FIELD
:字段。 -
ElementType.PARAMETER
:参数。 -
其他目标元素。
-
**@Documented**
:指定注解是否包含在 Javadoc 中。 -
**@Inherited**
:指定注解是否可以被继承。 -
**@Repeatable**
:指定注解是否可以重复使用。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {String value() default "";int count() default 0;
}
- 注解的使用
@MyAnnotation(value = "example", count = 10)
public class MyClass {@MyAnnotation(count = 5)public void myMethod() {// 方法体}
}
- 注解的处理机制
- 编译时处理
在编译阶段,通过注解处理器(Annotation Processor)读取和处理注解。注解处理器可以生成新的代码、资源文件,或者对代码进行静态检查。
- 运行时处理
在程序运行时,通过反射机制读取注解信息,并根据注解的内容执行相应的逻辑。
使用 Java 反射 API(如 Class
、Method
、Field
等)获取注解信息。
Class<?> clazz = MyClass.class;
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
if (annotation != null) {System.out.println("Value: " + annotation.value());System.out.println("Count: " + annotation.count());
}
应用场景
- 框架中的依赖注入(如 Spring 的
@Autowired
)。 - 动态代理(如 JDK 动态代理)。
元数据(Metadata)是“关于数据的数据”,即描述其他数据的信息。
4.反射如何使用
反射(Reflection)是 Java 提供的一种强大机制,允许程序在运行时动态地获取类的信息、操作类的属性和方法。这种能力使得 Java 具有动态语言的特性。
获取 Class 对象的三种方式
// 1. 通过类名.class
Class<String> stringClass = String.class;// 2. 通过对象.getClass()
String str = "Hello";
Class<?> strClass = str.getClass();// 3. 通过Class.forName() - 最常用的方式
Class<?> clazz = Class.forName("java.lang.String");
基本使用示例
// 获取类名
String className = clazz.getName(); // java.lang.String
String simpleName = clazz.getSimpleName(); // String// 获取修饰符
int modifiers = clazz.getModifiers();
Modifier.isPublic(modifiers); // 检查是否是public// 获取包信息
Package pkg = clazz.getPackage();
反射核心操作
操作构造方法
// 获取所有public构造方法
Constructor<?>[] constructors = clazz.getConstructors();// 获取指定参数的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class);// 创建实例
Object instance = constructor.newInstance("Hello World");// 获取私有构造方法
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(int.class);
privateConstructor.setAccessible(true); // 设置可访问
Object privateInstance = privateConstructor.newInstance(123);
操作方法
// 获取所有public方法(包括继承的)
Method[] methods = clazz.getMethods();// 获取类声明的所有方法(不包括继承的)
Method[] declaredMethods = clazz.getDeclaredMethods();// 获取指定方法
Method method = clazz.getMethod("substring", int.class, int.class);// 调用方法
Object result = method.invoke("Hello World", 0, 5); // 返回"Hello"// 调用私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(instance);
操作字段
// 获取所有public字段
Field[] fields = clazz.getFields();// 获取类声明的所有字段(包括private)
Field[] declaredFields = clazz.getDeclaredFields();// 获取指定字段
Field field = clazz.getField("FIELD_NAME");// 获取字段值
Object value = field.get(instance);// 设置字段值
field.set(instance, newValue);// 操作私有字段
Field privateField = clazz.getDeclaredField("privateField");
privateField.setAccessible(true);
Object privateValue = privateField.get(instance);
反射的应用,动态代理
interface Hello {void sayHello();
}class HelloImpl implements Hello {public void sayHello() {System.out.println("Hello World");}
}// 代理处理器
class ProxyHandler implements InvocationHandler {private Object target;public ProxyHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method call");Object result = method.invoke(target, args);System.out.println("After method call");return result;}
}// 使用动态代理
Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),new Class<?>[] {Hello.class},new ProxyHandler(new HelloImpl())
);
hello.sayHello();
注解处理
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {String value();
}@MyAnnotation("Test Class")
class MyClass {@MyAnnotation("Test Field")private String field;@MyAnnotation("Test Method")public void method() {}
}// 处理注解
Class<?> clazz = MyClass.class;// 获取类上的注解
MyAnnotation classAnnotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(classAnnotation.value()); // 输出: Test Class// 获取方法上的注解
Method method = clazz.getMethod("method");
MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);
System.out.println(methodAnnotation.value()); // 输出: Test Method
5.JVM 内存区的划分
1. 程序计数器**(Program Counter Register)**
- 线程私有
- 记录当前线程执行的字节码行号指示器
- 唯一不会出现OutOfMemoryError的区域
2. Java虚拟机栈(Java Virtual Machine Stacks)
- 线程私有
- 存储栈帧(Frame),每个方法调用会创建一个栈帧
- 包含局部变量表、操作数栈、动态链接、方法出口等信息
- 可能出现StackOverflowError和OutOfMemoryError
3. 本地方法栈**(Native Method Stack)**
- 线程私有
- 为本地(Native)方法服务
- 可能出现StackOverflowError和OutOfMemoryError
4. Java堆(Java Heap)
- 线程共享
- 存储对象实例和数组
- GC主要工作区域
- 可细分为新生代(Eden、Survivor0、Survivor1)和老年代
- 可能出现OutOfMemoryError
5. 方法区**(Method Area)**
- 线程共享
- 存储类信息、常量、静态变量、即时编译器编译后的代码等
- 在HotSpot VM中也被称为"永久代"(PermGen),在Java 8中被元空间(Metaspace)取代
- 可能出现OutOfMemoryError
6. 运行时常量池**(Runtime Constant Pool)**
- 方法区的一部分
- 存放编译期生成的各种字面量和符号引用
7. 直接内存**(Direct Memory)**
- 不是JVM规范定义的内存区域
- 使用Native函数库直接分配的堆外内存
- 通过存储在Java堆中的DirectByteBuffer对象引用
- 可能出现OutOfMemoryError

这些内存区域共同协作,支持Java程序的运行,不同区域有不同的生命周期和垃圾回收策略。
6.IO 介绍一下
Java I/O 流是Java中处理输入输出的核心机制,它提供了统一的接口来读写不同类型的数据源(如文件、网络连接、内存缓冲区等)。
**一、**I/O 流的基本概念
1. 流的分类
-
按方向:
-
输入流(InputStream/Reader):从数据源读取数据
-
输出流(OutputStream/Writer):向目标写入数据
-
按数据类型:
-
字节流(InputStream/OutputStream):处理二进制数据
-
字符流(Reader/Writer):处理文本数据,自动处理字符编码
核心类结构
java.io
├── InputStream (字节输入流)
│ ├── FileInputStream
│ ├── ByteArrayInputStream
│ └── FilterInputStream
│ ├── BufferedInputStream
│ ├── DataInputStream
│ └── ...
├── OutputStream (字节输出流)
│ ├── FileOutputStream
│ ├── ByteArrayOutputStream
│ └── FilterOutputStream
│ ├── BufferedOutputStream
│ ├── DataOutputStream
│ └── ...
├── Reader (字符输入流)
│ ├── InputStreamReader
│ │ └── FileReader
│ ├── BufferedReader
│ └── ...
└── Writer (字符输出流)├── OutputStreamWriter│ └── FileWriter├── BufferedWriter└── ...
二、使用示例
1. 字节流示例
文件复制(基础版)
try (InputStream in = new FileInputStream("source.jpg");
OutputStream out = new FileOutputStream("target.jpg")) {byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
使用缓冲流提高性能
try (InputStream in = new BufferedInputStream(new FileInputStream("source.jpg"));
OutputStream out = new BufferedOutputStream(new FileOutputStream("target.jpg"))) {byte[] buffer = new byte[8192]; // 更大的缓冲区
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
2. 字符流示例
文本文件读取
try (BufferedReader reader = new BufferedReader(new FileReader("text.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
文本文件写入
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("第一行");
writer.newLine(); // 换行
writer.write("第二行");
}
使用try-with-resources自动关闭资源
try (InputStream in = new FileInputStream("data.bin");
OutputStream out = new FileOutputStream("copy.bin")) {
// 自动关闭资源
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
3. 数据流(处理基本数据类型)
写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"))) {
dos.writeInt(100);
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("Hello");
}
读取基本数据类型
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) {
int i = dis.readInt();
double d = dis.readDouble();
boolean b = dis.readBoolean();
String s = dis.readUTF();System.out.println(i + ", " + d + ", " + b + ", " + s);
}
4. 对象序列化流
对象序列化(写入)
class Person implements Serializable {
private String name;
private int age;
// 构造方法、getter/setter省略
}try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
Person person = new Person("张三", 25);
oos.writeObject(person);
}
对象反序列化(读取)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
Person person = (Person) ois.readObject();
System.out.println(person.getName() + ", " + person.getAge());
}
三、最佳实践
- 始终关闭流:使用try-with-resources语句确保资源释放
- 使用缓冲流:特别是对于小量多次的I/O操作
- 处理异常:妥善处理IOException
- 选择正确的流类型:
- 文本数据:字符流(Reader/Writer)
- 二进制数据:字节流(InputStream/OutputStream)
- 注意字符编码:指定正确的字符集(如UTF-8)
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
7.NIO 介绍一下
Java NIO (New I/O) 是 Java 1.4 引入的一套新的 I/O API,提供了与传统 I/O 不同的编程模型,主要目标是提高 I/O 操作的效率,特别是在高并发场景下。
NIO 核心组件
1. Buffer**(缓冲区)**
-
数据容器,所有数据读写都通过缓冲区进行
-
核心实现:ByteBuffer(还有 CharBuffer, IntBuffer 等)
-
重要属性:
-
capacity:容量
-
position:当前位置
-
limit:读写限制
-
mark:标记位置
2. Channel**(通道)**
-
双向数据传输通道,可以同时读写
-
主要实现:
-
FileChannel:文件通道
-
SocketChannel:TCP 客户端通道
-
ServerSocketChannel:TCP 服务端通道
-
DatagramChannel:UDP 通道
3. Selector**(选择器)**
- 多路复用器,用于检查多个 Channel 的状态
- 允许单线程管理多个 Channel
- 基于操作系统提供的 I/O 多路复用机制(如 epoll, kqueue)
8.线程创建的方式
1,2,3,4
9.线程池的原理
线程的频繁的创建和销毁是损耗性能的,以一种池化的思想去管理线程,复用线程可以减少线程频繁创建销毁的虽好。核心就是线程的复用。
线程池的核心线程数,参考ThreadPoolExecutor

- corePoolSize 核心线程数目
- maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)
- keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
- unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
- workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
- threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
- handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
工作流程

1,任务在提交的时候,首先判断核心线程数是否已满,如果没有满则直接添加到工作线程执行
2,如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列
3,如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务
如果核心或临时线程执行完成任务后会检查阻塞队列中是否有需要执行的线程,如果有,则使用非核心线程执行任务
4,如果所有线程都在忙着(核心线程+临时线程),则走拒绝策略
10.线程池的拒绝策略
线程池的拒绝策略(Rejected Execution Handler)是当线程池无法接受新任务时(队列已满且线程数达到最大值)采取的处理方式。
Java提供了4种内置拒绝策略,都实现了RejectedExecutionHandler接口。
一、四种内置拒绝策略
1. AbortPolicy**(中止策略)**
默认策略
new ThreadPoolExecutor.AbortPolicy()
-
行为:直接抛出RejectedExecutionException异常
-
特点:
-
让调用者感知到任务被拒绝
-
需要调用者自己处理异常
-
适用场景:需要明确知道任务是否被拒绝的重要业务
2. CallerRunsPolicy**(调用者运行策略)**
new ThreadPoolExecutor.CallerRunsPolicy()
-
行为:将任务回退给调用者线程执行
-
特点:
-
不会丢弃任务
-
会降低新任务提交速度(因为调用者线程被占用)
-
适用场景:不允许任务丢失且可以接受性能下降的场景
3. DiscardPolicy**(丢弃策略)**
new ThreadPoolExecutor.DiscardPolicy()
-
行为:静默丢弃被拒绝的任务,不做任何处理
-
特点:
-
任务被无声无息丢弃
-
可能导致业务问题不易被发现
-
适用场景:允许丢失非关键任务的场景
4. DiscardOldestPolicy**(丢弃最老策略)**
new ThreadPoolExecutor.DiscardOldestPolicy()
-
行为:丢弃队列中最老的任务(队首任务),然后尝试重新提交当前任务
-
特点:
-
可能丢失重要但较老的任务
-
适合时效性强的任务
-
适用场景:新任务比老任务更重要的场景
二、自定义拒绝策略
实现RejectedExecutionHandler接口创建自定义策略:
public class CustomRejectionPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义处理逻辑,例如:
// 1. 记录日志
System.err.println("Task rejected: " + r.toString());// 2. 保存到数据库等待恢复
// saveToDatabase(r);// 3. 延迟后重试
try {
executor.getQueue().offer(r, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}// 使用自定义策略
executor.setRejectedExecutionHandler(new CustomRejectionPolicy());
三、各策略适用场景对比
策略 | 是否丢失任务 | 是否抛出异常 | 适用场景 |
---|---|---|---|
AbortPolicy | 是 | 是 | 需要严格保证任务不被静默丢弃的场景 |
CallerRunsPolicy | 否 | 否 | 不允许丢失但可以接受性能下降的场景 |
DiscardPolicy | 是 | 否 | 允许静默丢弃非关键任务的场景 |
DiscardOldestPolicy | 是 | 否 | 新任务比队列中老任务更重要的场景 |
11.设计模式有了解那些
12.hashmap的实现原理
13.在高并发的场景下解决hashmap的线程安全。
**一、**HashMap 线程安全问题
标准 HashMap 在多线程环境下存在以下问题:
- 数据不一致:多线程同时修改可能导致数据丢失
- 死循环:JDK7及以前版本的扩容可能导致环形链表
- 并发修改异常:迭代时修改会抛出 ConcurrentModificationException
二、解决方案
1. 使用 Collections.synchronizedMap
Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
-
原理:通过同步方法包装
-
特点:
-
简单但性能较差(全表锁)
-
适合低并发场景
2. 使用 Hashtable
Map<String, Object> map = new Hashtable<>();
-
特点:
-
早期线程安全实现
-
所有方法同步,性能较差
-
不推荐使用
3. 使用 ConcurrentHashMap**(推荐)**
JDK7 实现
- 分段锁技术:将数据分成多个段(segment),每段独立加锁
- 并发度:默认16个段,可配置
JDK8+ 实现
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
-
改进:
-
取消分段锁,改用 synchronized + CAS 锁单个桶(Node)
-
红黑树优化:链表长度>8时转为红黑树
-
更高的并发度
-
关键特性:
-
读操作完全无锁
-
写操作只锁单个桶
-
扩容时支持并发迁移
4. 其他并发Map实现
ConcurrentSkipListMap
ConcurrentNavigableMap<String, Object> map = new ConcurrentSkipListMap<>();
-
特点:
-
基于跳表实现
-
天然有序(Map.Entry)
-
适合需要排序的场景
三、性能对比
实现方式 | 读性能 | 写性能 | 适用场景 |
---|---|---|---|
HashMap | 极高 | 极高 | 单线程环境 |
Hashtable | 低 | 低 | 遗留系统(不推荐) |
Collections.synchronizedMap | 低 | 低 | 低并发,简单同步需求 |
ConcurrentHashMap | 高 | 中高 | 高并发读写 |
ConcurrentSkipListMap | 中 | 中 | 需要排序的高并发场景 |
四、最佳实践
- 首选 ConcurrentHashMap**:大多数并发场景的最佳选择**
- 合理设置初始容量:避免频繁扩容
new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel);
- 使用原子方法:避免额外的同步
- 考虑读多写少场景:可以配合 CopyOnWrite 模式
- **JDK****版本选择:****JDK8+**的实现在各方面都更优
14.springboot 的自动配置原理
@EnableAutoConfiguration是实现自动化配置的核心注解。 该注解通过@Import注解导入对应的配置选择器。
内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。 在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
可以说一下自定义的stater。
15.Mysql 的锁机制
全局锁
对整个数据库加锁,只允许事务的读操作,写操作会处于等待状态
flush tables with read lock; # 加锁
unlock tables; # 解锁,或者推出终端也会解锁
使用场景: 数据备份
如果不加锁,就会出现数据不一致问题。因为在备份的时候,有业务导致数据库在备份的过程中发生变化,导致备份后数据不一致。
如何备份操作呢?
首先进入mysql 终端加锁
flush tables with read lock; # 加锁
新开一个mysql命令行,尝试修改数据,发现修改不了,一直阻塞状态。直到释放全局锁。
然后用mysql提供的工具 mysqldump(主语不是sql语句,直接在我们的电脑终端执行即可)
C:\Users\26611>mysqldump -uroot -p studb > d:/studb.sql
Enter password: ****
缺点: 1. 对整个数据库加锁,数据库只能读,那么业务就不能写,会造成业务停滞。
2.对从库备份,使用全局锁,会导致在使用全局锁时,导致binlog日志文件不能写,会导致延迟。
使用全局锁,会影响业务写操作,那么怎么解决呢?
有些数据库引擎(Innodb)支持可重复读的事务隔离级别,在备份前开启事务会创建 Read View,备份期间业务仍可更新数据。可重复读下,即使其他事务更新,备份的 Read View 不受影响,确保了数据的一致性。使用 mysqldump 备份时,可加 –single-transaction 参数以适应支持此隔离级别的引擎,如 InnoDB。对于 MyISAM 这种不支持事务的引擎,备份时需使用全局锁。
表级锁
对表加锁,表锁,元数据锁,意向锁
表共享锁(Read Lock)的使用
use studb # 选择数据库
lock tables students read; # 对表加共享锁
unlock tables; # 解锁
共享锁,对表加共享锁,允许所有的事务可以读表,但是不能进行写操作;加锁的mysql终端,进行写操作,会报错,其他mysql终端写操作会阻塞。
排他锁(Write Lock) 的使用
use studb # 选择数据库
lock tables students write; # 对标加排他锁
unlock tables; # 解锁
排他锁,对表加排他锁,只有拿到排他锁的事务才可以对标读,写操作,其他事务会阻塞,直到释放锁(unlock tables 或者关闭加锁的终端)。
同时对表加共享锁和排他锁后,加锁的mysql终端不能对其他表操作。如下
mysql> lock tables students read;
Query OK, 0 rows affected (0.00 sec)mysql> select * from test;
ERROR 1100 (HY000): Table 'test' was not locked with LOCK TABLES
元数据锁
元数据锁(MDL)主要用于保护数据库表结构。当一个事务试图修改数据库的结构(如通过ALTER TABLE
命令添加或删除列)时,它需要先获得相应对象的元数据锁。这种锁确保了在表结构被修改的同时,不会有其他事务尝试访问或修改该表的结构,从而避免数据不一致或错误。
MDL加锁过程是系统自动控制,无序显示使用,在访问一张表的时候会自动加上。在表上有活动事务的时候(也就是加上了元数据锁),当一个事务要修改表结构的时候,如果表上加了元数据锁,那么就会进入阻塞状态。
意向锁
意向锁(Intention Lock)主要用于解决行级锁和表级锁之间的冲突,提高数据库的并发性和性能.
意向锁是一种表级别的锁,用于表明当前事务在表中行级锁的特性。它的主要目的是协调行锁和表锁之间的关系,避免锁冲突,从而提高数据库的并发性能。怎么理解呢?
在进行update 操作时会自动加上行级锁,然后其他事务对这个表加表锁,会扫描每一行检查行锁的类型,判断释放能加上表锁。但是有了意向锁之后,进行update 操作(行锁),会在表上加一个意向锁(IS和IX),其他事务在加表锁直接和意向锁比较判断是否能加锁。
- 意向共享锁(IS Lock):
- 当事务打算对某一行加共享锁时,它首先在表级加一个意向共享锁。
- 意向共享锁允许多个事务在表级别并发地读取数据,但不允许修改数据。
- 意向排他锁(IX Lock):
- 当事务打算对某一行加排他锁时,它首先在表级加一个意向排他锁。
- 意向排他锁意味着事务将在行级上加排他锁,其他事务不能在该行上加意向共享锁或意向排他锁。
意向共享锁与共享锁可以兼容,而意向排他锁与排他锁是互斥的。
行锁
- Record Lock(记录锁):单独在记录上加锁,适合如主键记录更新等场景。
举个例子,当一个事务执行了下面这条语句:
mysql > begin; mysql > select * from t_test where id = 1 for update;
就是对 t_test 表中主键 id 为 1 的这条记录加上 X 型的记录锁,这样其他事务就无法对这条记录进行修改了。
当事务执行 commit 后,事务过程中生成的锁都会被释放。
- 当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容);
- 当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)。
-
Gap Lock(间隙锁):Gap Lock 称为间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生.
-
Next-Key Lock(临键锁):Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。,锁定一个范围,并且锁定记录本身。也不能修改 id = 5 这条记录。所以,next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。
16.Mysql 如何解决锁竞争的问题
从以下几点分析
- 事务优化
- 缩短事务时间:事务越小越好,尽快提交
- 避免长事务:监控并终止执行时间过长的事务
- 合理设置隔离级别:在允许的情况下使用较低的隔离级别
- 索引优化
- 确保查询使用索引:没有索引会导致表锁升级
- 优化索引设计:减少不必要的索引,复合索引要合理
- 使用覆盖索引:避免回表操作
- Sql 语句优化
- 避免全表扫描:EXPLAIN 分析执行计划
- 减少热点更新:如计数器场景使用增量更新
- 分解大批量操作:大事务拆分为小批次
- 锁机制的选择:
- 行锁替代表锁:使用 InnoDB 引擎
- 乐观锁替代悲观锁:适合读多写少场景
UPDATE table SET col=new_val, version=version+1
WHERE id=1 AND version=old_version;
- 架构优化
- 读写分离:查询走从库
- 分库分表:减少单表竞争
- 使用缓存:Redis 缓存热点数据
17.mvcc是什么
多版本并发控制,主要是对多个事务对一条记录进行修改,同时保证数据的安全性的一种实践,mvcc的实现,依赖隐藏字段,版本链,读视图。
18.缓存使用的问题
缓存穿透,缓存雪崩,缓存击穿,数据一致性问题
19.常用的负载均衡策略
1. 静态负载均衡策略
轮询 (Round Robin)
- 原理:依次将请求分配给每个服务器,循环往复
- 优点:简单、公平
- 缺点:不考虑服务器实际负载
- 适用场景:服务器性能相近的简单场景
加权轮询 (Weighted Round Robin)
- 原理:在轮询基础上,为性能强的服务器分配更高权重,获得更多请求
- 优点:考虑了服务器性能差异
- 缺点:权重需要手动配置,不能动态调整
随机 (Random)
- 原理:随机选择一个服务器处理请求
- 优点:实现简单
- 缺点:可能造成负载不均
加权随机 (Weighted Random)
- 原理:按权重概率随机选择服务器
- 优点:考虑了服务器性能差异
- 缺点:权重需要手动配置
哈希 (Hash/一致性哈希)
- 原理:根据请求的某个特征值(如IP、URL)计算哈希值选择服务器
- 优点:相同请求总是落到同一服务器,适合有状态服务
- 缺点:服务器增减时可能影响大量请求路由
2. 动态负载均衡策略
最少连接 (Least Connections)
- 原理:选择当前连接数最少的服务器
- 优点:动态适应服务器负载
- 缺点:需要实时监控连接数
加权最少连接 (Weighted Least Connections)
- 原理:结合服务器权重和当前连接数选择
- 优点:更精确的负载分配
- 缺点:实现复杂度较高
响应时间加权 (Response Time Based)
- 原理:根据服务器历史响应时间分配请求
- 优点:优先选择响应快的服务器
- 缺点:需要持续监控响应时间
资源利用率 (CPU/Memory Based)
- 原理:根据服务器CPU、内存等资源使用率分配
- 优点:最精确反映服务器实际负载
- 缺点:监控开销大
3. 特殊场景策略
地理位置路由 (Geo-Based)
- 根据用户地理位置选择最近的服务器
会话保持 (Sticky Session)
- 同一用户的请求始终路由到同一服务器
故障转移 (Failover)
- 当首选服务器不可用时自动切换到备用服务器
负载均衡实现层次
- DNS****负载均衡:通过DNS解析分配
- 硬件负载均衡:如F5、A10等专用设备
- 软件负载均衡:如Nginx、HAProxy、LVS
- 客户端负载均衡:如Ribbon、Spring Cloud LoadBalancer
选择负载均衡策略时需要考虑服务器性能差异、请求特性、会话状态要求等因素,通常需要结合实际场景组合使用多种策略。
20.CI,CD了解吗
1. 基本概念
**CI (****持续集成,**Continuous Integration)
-
定义:开发人员频繁地将代码变更合并到共享主干(通常每天多次)
-
核心目标:通过自动化构建和测试,快速发现集成错误
-
关键实践:
-
代码提交触发自动构建
-
运行自动化测试套件
-
快速反馈构建和测试结果
CD (持续交付/持续部署)
-
持续交付 (Continuous Delivery):
-
确保代码变更可以随时安全地部署到生产环境
-
需要手动触发最后的部署步骤
-
持续部署 (Continuous Deployment):
-
自动化整个流程,代码通过测试后自动部署到生产环境
-
无需人工干预(关键变更可能仍需审批)
2. CI/CD 工作流程
代码提交 → 触发CI → 代码构建 → 单元测试 → 集成测试 →
↓ (持续交付) ↓ (持续部署)
人工审核 → 部署到生产 自动部署到生产
3. 核心组件与工具
CI 工具
- Jenkins:最流行的开源自动化服务器
- GitLab CI/CD:与GitLab深度集成
- GitHub Actions:GitHub原生CI/CD解决方案
- CircleCI:云原生CI/CD平台
- Travis CI:早期的云CI服务
CD 工具
- Argo CD:Kubernetes的声明式GitOps工具
- Spinnaker:Netflix开源的持续交付平台
- Tekton:Kubernetes原生CI/CD框架
辅助工具
- Docker:容器化应用打包
- Kubernetes:容器编排
- Terraform:基础设施即代码
- Ansible:配置管理
4. CI/CD 的优势
- 更快的发布周期:从月/周发布到日/小时级发布
- 更高质量的代码:通过自动化测试及早发现问题
- 降低风险:小批量变更比大批量变更风险低
- 提高开发效率:减少手动流程,专注开发
- 更好的可追溯性:每个变更都有完整记录
5. 实施CI/CD的关键步骤
- 版本控制:所有代码和配置纳入Git等版本控制系统
- 自动化构建:一键式构建整个系统
- 自动化测试:建立全面的测试套件(单元、集成、端到端)
- 频繁提交:小步快走,避免大规模合并冲突
- 监控与反馈:建立快速反馈机制
6. 现代CI/CD实践
- GitOps:使用Git作为唯一事实来源
- 不可变基础设施:每次部署创建全新环境
- 渐进式交付:金丝雀发布、蓝绿部署
- 混沌工程:在生产中故意引入故障测试系统韧性
CI/CD已成为现代软件开发的标配实践,它不仅仅是工具链的集成,更是一种开发文化和流程的变革,能够显著提升软件交付效率和质量。
更多完善:真实面经