diff --git a/docs/assets/css/extra.css b/docs/assets/css/extra.css index 01297d16..e01607cb 100644 --- a/docs/assets/css/extra.css +++ b/docs/assets/css/extra.css @@ -46,6 +46,8 @@ /* 图片的样式 */ img { + margin: 0 auto; + display: block; border-radius: 10px; position: relative; transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out; @@ -210,6 +212,5 @@ blockquote { code:not(.ex):not(.md-code__content) { color: #b88b22; - font-weight: bold; font-family: "Longjf Nerd Font"; } diff --git "a/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/SpringMVC\347\232\204\345\267\245\344\275\234\345\216\237\347\220\206.png" "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/SpringMVC\347\232\204\345\267\245\344\275\234\345\216\237\347\220\206.png" new file mode 100644 index 00000000..22f10c1b Binary files /dev/null and "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/SpringMVC\347\232\204\345\267\245\344\275\234\345\216\237\347\220\206.png" differ diff --git "a/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/\344\270\211\346\254\241\346\217\241\346\211\213.png" "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/\344\270\211\346\254\241\346\217\241\346\211\213.png" new file mode 100644 index 00000000..c31a33cd Binary files /dev/null and "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/\344\270\211\346\254\241\346\217\241\346\211\213.png" differ diff --git "a/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/\345\233\233\346\254\241\346\214\245\346\211\213.png" "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/\345\233\233\346\254\241\346\214\245\346\211\213.png" new file mode 100644 index 00000000..623783b0 Binary files /dev/null and "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/img/\345\233\233\346\254\241\346\214\245\346\211\213.png" differ diff --git "a/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/\351\201\207\345\210\260\347\232\204\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206.md" "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/\351\201\207\345\210\260\347\232\204\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206.md" index 1740689d..c7049474 100644 --- "a/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/\351\201\207\345\210\260\347\232\204\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206.md" +++ "b/docs/\345\274\200\345\247\213\351\230\205\350\257\273/\351\235\242\350\257\225\351\242\230/\351\201\207\345\210\260\347\232\204\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206.md" @@ -120,52 +120,47 @@ InnoDB存储引擎: ## 13.SpringBoot启动流程 -**第一部分**:SpringApplication初始化模块,配置一些基本的环境变量,资源,监听器,构造器; +### 第一部分: SpringApplication 初始化模块 +配置一些基本的环境变量、资源、监听器、构造器; -**第二部分**:实现了应用具体的启动方案,包括流程的监听模块,加载配置环境模块以及创建上下文环境模块 +### 第二部分: 实现应用具体的启动方案 +包括流程的监听模块、加载配置环境模块以及创建上下文环境模块; -**第三部分**:自动化配置模块,这个模块是实现SpringBoot的自动配置 +### 第三部分: 自动化配置模块 +这个模块是实现SpringBoot的自动配置。 -**启动**: +#### 启动过程: -每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类有一个@SpringBootApplication注解,它是一个组合注解; +每个SpringBoot程序都有一个主入口,即main方法。在这个方法里调用了`SpringApplication.run()`以启动整个SpringBoot程序。该方法所在的类有一个`@SpringBootApplication`注解,它是一个组合注解: -!!! note "" - 1️⃣ `@EnableAutoConfiguration`:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置 - - 2️⃣ `@SpringBootConfiguration`(内部为@Configuration): 标注这个类是一个配置类; - - 3️⃣ `@ComponentScan`:组件扫描,可自动发现和装配Bean,自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中 - -**SpringBoot启动类** - - 首先进入run方法 - - run方法中去创建了一个SpringApplication实例, - - \- 使用SpringFactoriesLoader在应用的META-INFO/spring.facories中查找并加载所有可用的ApplicationContextInitializer。 - - \- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。 - - 在该构造方法内,我们可以发现其调用了一个初始化的initialize方法 - -该方法中实现了如下几个关键步骤: - -1.创建了应用的监听器SpringApplicationRunListeners并开始监听 +!!! Note "" + 1. `@EnableAutoConfiguration`: SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置; + 2. `@SpringBootConfiguration` (内部为`@Configuration`): 标注这个类是一个配置类; + 3. `@ComponentScan`: 组件扫描,可自动发现和装配Bean,自动扫描并加载符合条件的组件(如`@Component`和`@Repository`等),并将这些bean定义加载到IoC容器中。 -2.加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment +#### SpringBoot启动类 -3.配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners) +首先,进入`run`方法: -4.创建run方法的返回对象:ConfigurableApplicationContext(应用配置上下文) +!!! Note "" + - 创建了一个`SpringApplication`实例; + - 使用`SpringFactoriesLoader`在应用的`META-INF/spring.factories`中查找并加载所有可用的`ApplicationContextInitializer`; + - 使用`SpringFactoriesLoader`在应用的类路径中查找并加载所有可用的`ApplicationListener`; + - 在构造方法内,调用了一个初始化的方法`initialize`。 -5.回到run方法内,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联 +该方法中实现了以下关键步骤: -6.接下来的refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。 +!!! Note "" + 1. 创建了应用的监听器`SpringApplicationRunListeners`并开始监听; + 2. 加载SpringBoot配置环境(`ConfigurableEnvironment`),如果是通过web容器发布,则会加载`StandardEnvironment`; + 3. 将配置环境(`Environment`)加入到监听器对象(`SpringApplicationRunListeners`)中; + 4. 创建`run`方法的返回对象:`ConfigurableApplicationContext`(应用配置上下文); + 5. 回到`run`方法内,`prepareContext`方法将`listeners`、`environment`、`applicationArguments`、`banner`等重要组件与上下文对象关联; + 6. 接下来的`refreshContext(context)`方法是实现`sprin-boot-starter-*`(如mybatis、redis等)自动化配置的关键,包括`sprin.factories`的加载,bean的实例化等核心工作。 -refresh方法 +#### refresh方法 -配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文。回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成 +配置结束后,SpringBoot进行了一些基本的收尾工作,返回了应用环境上下文。回顾整个流程,SpringBoot的启动主要包括创建 **配置环境**(`environment`)、**事件监听**(`listeners`)、**应用上下文**(`applicationContext`),并在容器中开始 **实例化所需的Bean**。至此,通过SpringBoot启动的程序已经构造完成。 ----- @@ -206,44 +201,51 @@ CREATE TABLE 表名( ## 15.threadlocal变量及作用 -

ThreadLocal对外提供的方法只有三个 get()、set(T)、remove()

+#### 介绍 +ThreadLocal是为了线程隔离,它使得每个线程都有自己独立的变量副本,从而避免了多线程环境下的数据竞争和线程安全问题. + +- `get()`: 获取当前线程的线程局部变量值。 +- `set(T value)`: 设置当前线程的线程局部变量值。 +- `remove()`: 移除当前线程的线程局部变量值,防止内存泄漏。 +- `initialValue()`: 返回一个初始值,这是一个受保护的方法,可以在子类中重写以提供初始值。 + +!!! Example "内存泄漏的原因" + 用ThreadLocalMap是来存entry,因为key为弱引用,value为强引用,会存在内存泄漏问题:ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成弱引用了。这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。 -

-ThreadLocal是为了线程隔离,为了变量只能被固定的线程使用,用ThreadLocalMap是来存entry,因为key为弱引用,value为强引用,会存在内存泄漏问题:ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成弱引用了。这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。 -

+ 由于Thread中包含变量ThreadLocalMap,因此 ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。 -

-由于Thread中包含变量ThreadLocalMap,因此 ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。 -

+ ​但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。 -

-​但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。 -

+ 因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。 -

-因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。 -

+#### 使用场景 -!!!Info "内存泄漏解决方案:" +- **线程安全的对象使用**:在多线程环境下,我们可能需要每个线程使用自己独立的对象实例,例如 `SimpleDateFormat`。`SimpleDateFormat` 不是线程安全的,使用 ThreadLocal 可以为每个线程提供一个独立的 `SimpleDateFormat` 实例。 +- **数据库连接管理**:在基于线程的环境中(如 Web 应用),可以使用 ThreadLocal 来管理数据库连接,为每个线程提供独立的连接实例,以避免连接共享带来的线程安全问题。 +- **日志记录**:在日志记录中,ThreadLocal 可以用来存储当前线程的上下文信息,如用户 ID 或事务 ID,以便在日志输出时提供更多的调试信息。 +- **用户会话管理**:在 Web 应用中,每个用户的会话数据可以使用 ThreadLocal 存储,从而确保同一用户的多个请求在同一个线程中处理时能够访问到正确的会话数据。 - 1️⃣每次使用完ThreadLocal都调用它的 remove() 方法清除数据
- 2️⃣将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。 +!!! Warning "注意事项" + 1️⃣**内存泄漏**:如果线程不再需要使用该变量,但忘记调用 remove() 方法来清理,那么由于 ThreadLocalMap 中的 Entry 的 key 是对 Thread 的弱引用,所以 Thread 被回收后,Entry 的 key 会被置为 null,但 value 不会被回收,从而导致内存泄漏。因此,使用完 ThreadLocal 后,最好调用 remove() 方法来清理。 + !!! Success "解决方案" + ①每次使用完ThreadLocal都调用它的 remove() 方法清除数据
+ ②将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。 + 2️⃣**线程池中的使用**:在线程池中,线程可能会被复用。如果线程之前设置过 ThreadLocal 变量,但在使用后没有清理,那么下一个任务可能会读取到上一个任务设置的值。因此,在线程池中使用 ThreadLocal 时需要特别小心。
+ 3️⃣**性能开销**:虽然 ThreadLocal 可以简化多线程编程,但是频繁的创建和销毁线程局部变量也会带来一定的性能开销。 ------ ## 16.spring框架bean对象的生命周期 -> ``` -> 1.实例化对象 -> 2.初始化操作 (一般对对象的属性赋值) -> 3.用户使用对象(调用其中的方法) -> 4.对象销毁 (一般都是释放资源) -> ``` -> +1. **Bean 的创建**:当 Spring 容器启动时,根据配置文件中的定义创建 Bean 的实例。 +2. **依赖注入**:为新创建的 Bean 注入其所需的属性(即依赖关系)。 +3. **初始化**:执行任何必要的初始化工作,比如设置资源或者建立数据库连接等。 +4. **运行时服务**:Bean 处于活动状态,提供服务。 +5. **销毁**:容器决定销毁 Bean 时执行必要的清理工作。 ## 17.SpringBoot框架中yml和properties文件哪个优先加载 -yml->ymal->properties +yml->ymal->properties
由里向外加载,所以最外层的最后被加载,会覆盖里层的属性 ## 18.怎么理解多线程,你的项目中哪里用到了多线程 @@ -288,18 +290,21 @@ R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊 3. **哈希表结构**:结合数组结构和链表结构的优点,从而实现了查询和修改效率高,插入和删除效率也高的一种数据结构 -!!! Note "常见的HashMap就是这样的一种数据结构" +!!! Note "常见的HashMap的数据结构" 1️⃣HashMap底层是一个`Entry[ ]`数组,当存放数据时,会根据hash算法来计算数据的存放位置 2️⃣算法:`hash(key)%n` , n就是数组的长度,其实也就是集合的容量 3️⃣当计算的位置没有数据的时候,会直接存放数据 - 4️⃣当计算的位置,有数据时,会发生 **hash冲突/hash碰撞** ,解决的办法就是采用链表的结构,在对应的数据位置存放链表的头节点,对于这个链表来说,每次新加的节点会从头部位置开始加入,也就是说,数组中的永远是新节点. + 4️⃣当计算的位置,有数据时,会发生 **hash冲突/hash碰撞** , + + !!! Warning "" + 解决的办法就是采用链表的结构,在对应的数据位置存放链表的头节点,对于这个链表来说,每次新加的节点会从头部位置开始加入,也就是说,数组中的永远是新节点. #### ②HashMap扩容 -1、**加载因子** +1️⃣、**加载因子** ```java static final float DEFAULT_LOAD_FACTOR = 0.75f; @@ -311,12 +316,12 @@ static final float DEFAULT_LOAD_FACTOR = 0.75f; 但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。 -2、**为何随机增删、查询效率都很高的原因是?** +2️⃣、**为何随机增删、查询效率都很高的原因是?** **原因**: 增删是在链表上完成的,而查询只需扫描部分,则效率高。(存在链表退化问题) HashMap集合的key,会先后调用两个方法,hashCode and equals方法,这两个方法都需要重写 -**JDK1.8后引入了红黑树**:当hash表的单一链表长度**超过 8 个**的时候,链表结构就会转为红黑树结构。好处就是避免在最极端的情况下链表变得很长很长,在查询的时候,效率会非常慢。红黑树是一种近似平衡的二叉查找树,其主要的优点就是“平衡“,即**左右子树高度几乎一致**,以此来防止树退化为链表 +**JDK1.8后引入了红黑树**:当hash表的单一链表长度 **超过 8 个** 的时候,链表结构就会转为红黑树结构。好处就是避免在最极端的情况下链表变得很长很长,在查询的时候,效率会非常慢。红黑树是一种近似平衡的二叉查找树,其主要的优点就是“平衡“,即 **左右子树高度几乎一致** ,以此来防止树退化为链表 #### ③HashMap中的put()和get()的实现原理: @@ -354,9 +359,11 @@ HashMap集合的key,会先后调用两个方法,hashCode and equals方法, 前两种方式由于全局锁的问题,存在很严重的性能问题。针对HashTable会锁整个hash表的问题,ConcurrentHashMap提出了分段锁的解决方案。 ---------------------------------------------------------------JDK1.7------------------------------------------------------------ +-----------------------------------***JDK1.7***------------------------------ -> **1.分段锁**的思想就是:**锁的时候不锁整个hash表,而是只锁一部分**。如何实现呢?这就用到了ConcurrentHashMap中最关键的**Segment**。ConcurrentHashMap中维护着一个Segment数组,每个Segment可以看做是一个HashMap。而Segment本身继承了ReentrantLock,它本身就是一个锁。在Segment中通过HashEntry数组来维护其内部的hash表。每个HashEntry就代表了map中的一个K-V,用HashEntry可以组成一个链表结构,通过next字段引用到其下一个元素。 +**1.分段锁** 的思想就是:**锁的时候不锁整个hash表,而是只锁一部分**。 + +如何实现呢?这就用到了ConcurrentHashMap中最关键的 **Segment** 。ConcurrentHashMap中维护着一个Segment数组,每个Segment可以看做是一个HashMap。而Segment本身继承了ReentrantLock,它本身就是一个锁。在Segment中通过HashEntry数组来维护其内部的hash表。每个HashEntry就代表了map中的一个K-V,用HashEntry可以组成一个链表结构,通过next字段引用到其下一个元素。 ![整体结构](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/4/6/169f29dca9416c8f~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75.awebp) @@ -368,7 +375,7 @@ HashMap集合的key,会先后调用两个方法,hashCode and equals方法, HashMap的线程安全问题大部分出在扩容(rehash)的过程中。**ConcurrentHashMap的扩容只针对每个segment中的HashEntry数组进行扩容**。由上述put的源码可知,ConcurrentHashMap在rehash的时候是有锁的,所以在rehash的过程中,其他线程无法对segment的hash表做操作,这就保证了线程安全。 -************************************************************JDK1.8***************************************************** +----------------------------------**JDK1.8**------------------------------- https://juejin.cn/post/6844903813892014087 @@ -422,27 +429,27 @@ ThreadPoolExecutor executor = new ThreadPoolExecutor( #### jdk中提供了四种工作队列(这个也很重要,实际开发也需要很关注): -> ​ **1️⃣`ArrayBlockingQueue`**:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。 -> -> ​ **2️⃣`LinkedBlockingQueue`**:基于链表的无界阻塞队列(其实最大容量为Integer.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。 -> -> ​ **3️⃣`SynchronousQueue`**:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。 -> -> ​ **4️⃣`PriorityBlockingQueue`**:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。 -> +!!! Info "" + **1️⃣`ArrayBlockingQueue`**:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。 -#### **handler 拒绝策略** + **2️⃣`LinkedBlockingQueue`**:基于链表的无界阻塞队列(其实最大容量为Integer.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。 -​ 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略: + **3️⃣`SynchronousQueue`**:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。 -> ​ **1️⃣CallerRunsPolicy**:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池shutdown,则直接抛弃任务 -> -> ​ **2️⃣AbortPolicy**:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。 -> -> ​ **3️⃣DiscardPolicy**:该策略下,直接丢弃任务,什么都不做。 -> -> ​ **4️⃣DiscardOldestPolicy**:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列 -> + **4️⃣`PriorityBlockingQueue`**:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。 + +#### handler 拒绝策略 + +​当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略: + +!!! Info "" + **1️⃣CallerRunsPolicy**:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池shutdown,则直接抛弃任务 + + **2️⃣AbortPolicy**:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。 + + **3️⃣DiscardPolicy**:该策略下,直接丢弃任务,什么都不做。 + + **4️⃣DiscardOldestPolicy**:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列 ----- @@ -450,74 +457,53 @@ ThreadPoolExecutor executor = new ThreadPoolExecutor( #### **负载均衡策略:** -> 1. **轮询策略**:根据配置文件顺序依次访问服务器--默认策略 -> 2. **权重策略**:根据设定的权重大小挑选那台服务器有限访问,使用**weight**关键字定义权重 -> 3. **IPHASH策略**:每个请求**按访问 ip 的 hash 结果分配**,这样每个访客固定访问一个后端服务器,可以解决 session 的问题。需要用户与服务器绑定,则使用该策略 -> 4. **least_conn**:把请求转发给连接数较少的后端服务器。 -> +1. **轮询策略**:根据配置文件顺序依次访问服务器--默认策略 +2. **权重策略**:根据设定的权重大小挑选那台服务器有限访问,使用**weight**关键字定义权重 +3. **IPHASH策略**:每个请求**按访问 ip 的 hash 结果分配**,这样每个访客固定访问一个后端服务器,可以解决 session 的问题。需要用户与服务器绑定,则使用该策略 +4. **least_conn**:把请求转发给连接数较少的后端服务器。 IPHASH优先级比较高,会覆盖权重策略 IPHASH算法原理:TODO ## 25.mybatis框架有几级缓存,默认开启几级?(按场景分析) -#### 二级缓存配置(默认开启一级) +#### 二级缓存配置(默认只开启一级) -> -> - ​ **[eviction]()**:缓存回收策略(默认是LRU) -> -> ​ **LRU**-最近最少使用的:移除最长时间不被使用的对象 -> ​ **FIFO**-先进先出:按对象进入缓存的顺序移除它们 -> ​ **SOFT**-软引用:移除基于垃圾回收器状态和软引用的对象 -> ​ **WEAK**-弱引用:更积极的移除基于垃圾回收器状态和弱引用的对象 -> -> - ​ **[flushInterval]()**:**刷新间隔**,单位毫秒,缓存多少时间清空一次,默认情况是不设置,也就是没有 -> -> ​ 间隔,缓存仅仅调用语句时刷新 -> -> - ​ **[size]()**:**引用数目**:正整数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出 -> -> - ​ **[readOnly]()**:只读,true/false -> -> ​ **true**:**只读缓存**,会给所有调用者返回缓存对象的相同实例,因此这次对象不可以被修改,这提 -> -> ​ 供了很重要的性能优势,mybatis会认为所有从缓存中获取数据的操作都是只读操作,mybatis为 -> -> ​ 了加快获取速度,直接就将缓存中的引用交给用户,不安全,速度快 -> -> ​ **false**:**读写缓存**,会返回缓存对象的拷贝(通过序列化),性能稍差,但是安全,默认是 -> -> ​ false,mybatis会认为获取的数据可能会被修改,就利用序列化&反序列化拷贝一份新的数据 -> -> - ​ **[type]()**:指定自定义缓存的全类名,实现Cache接口即可 - -### Mybatis一级缓存 +- ​**eviction**:缓存回收策略(默认是LRU) + + 1. **LRU**-最近最少使用的:移除最长时间不被使用的对象 + 2. **FIFO**-先进先出:按对象进入缓存的顺序移除它们 + 3. **SOFT**-软引用:移除基于垃圾回收器状态和软引用的对象 + 4. **WEAK**-弱引用:更积极的移除基于垃圾回收器状态和弱引用的对象 + +- ​**flushInterval**:**刷新间隔**,单位毫秒,缓存多少时间清空一次,默认情况是不设置,也就是没有间隔,缓存仅仅调用语句时刷新。 +- ​**size**:**引用数目**:正整数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出。 +- ​**readOnly**:只读,true/false + 1. **true**:**只读缓存**,会给所有调用者返回缓存对象的相同实例,因此这次对象不可以被修改,这提供了很重要的性能优势,mybatis会认为所有从缓存中获取数据的操作都是只读操作,mybatis为了加快获取速度,直接就将缓存中的引用交给用户,不安全,速度快 + 2. **false**:**读写缓存**,会返回缓存对象的拷贝(通过序列化),性能稍差,但是安全,默认是false,mybatis会认为获取的数据可能会被修改,就利用序列化&反序列化拷贝一份新的数据。 + +- ​**type**:指定自定义缓存的全类名,实现Cache接口即可。 ### ①MyBatis一级缓存概述 -> MyBatis 的一级缓存是 `SqlSession` 级别的,通过同一个 SqlSession 对象查询的数据会被缓存,下次再查询相同的数据时,就会从缓存中直接获取,不会从数据库重新访问; 一般我们说到 MyBatis 的一级缓存时,都是针对查询操作而言的; +MyBatis 的一级缓存是 `SqlSession` 级别的,通过同一个 `SqlSession` 对象查询的数据会被缓存,下次再查询相同的数据时, +就会从缓存中直接获取,不会从数据库重新访问; 一般我们说到 MyBatis 的一级缓存时,都是针对查询操作而言的; ### ②MyBatis 的一级缓存是默认开启的。 -**`缓存失效`**的四种情况: - -> 1.不同的 SqlSession 对象对应不同的一级缓存,即使查询相同的数据,也要重新访问数据库; -> -> 2.同一个 SqlSession 对象,但是查询的条件不同; -> -> 3.同一个 SqlSession 对象两次查询期间执行了任何的“增删改”操作,无论这些“增删改”操作是否影响到了缓存的数据; -> -> 4.同一个 SqlSession 对象两次查询期间手动清空了缓存(调用了 SqlSession 对象的 clearCache() 方法)。 +!!! Note "缓存失效的四种情况" + 1. 不同的 `SqlSession` 对象对应不同的一级缓存,即使查询相同的数据,也要重新访问数据库; + 2. 同一个 `SqlSession` 对象,但是查询的条件不同; + 3. 同一个 `SqlSession` 对象两次查询期间执行了任何的“增删改”操作,无论这些“增删改”操作是否影响到了缓存的数据; + 4. 同一个 `SqlSession` 对象两次查询期间手动清空了缓存(调用了 `SqlSession` 对象的 clearCache() 方法)。 ### ③几种特殊情况 -> 1.如果在同一个 SqlSession 对象两次查询同一数据期间,我们使用另一个 SqlSession 对象修改了这个数据,那么这两次查询返回的结果依旧是相同的(说明 SqlSession 对象还是从一级缓存中获取了数据),即使数据已经发生了变化; -> -> 2.同理第一种情况,如果在同一个 SqlSession 对象两次查询同一数据期间,我们使用 Navicat、Mysql Workbench 等数据库管理工具修改了这一数据,* 那么这两次查询返回的结果依旧是相同的,即使数据已经发生了变化; -> -> 3.如果在同一个 SqlSession 对象两次查询同一数据期间,我们使用该对象“增删改”了与该数据无关的其它数据,并没有进行任何涉及该数据的操作,* 数据也没有发生变化,那么 MyBatis 的一级缓存依旧会失效,这延伸自 2 中的第 3 种情况; -> -> 4.如果在同一个 SqlSession 对象两次查询同一数据期间,我们又多次查询了其它数据(期间没有进行任何的“增删改”操作),那么数据库中的该数据没有发生变化,MyBatis 也会顺利从一级缓存中获取到该数据。 +!!! Warning "" + 1. 如果在同一个 `SqlSession` 对象两次查询同一数据期间,我们使用另一个 `SqlSession` 对象修改了这个数据,那么这两次查询返回的结果依旧是相同的(说明 `SqlSession` 对象还是从一级缓存中获取了数据),即使数据已经发生了变化; + 2. 同理第一种情况,如果在同一个 `SqlSession` 对象两次查询同一数据期间,我们使用 Navicat、Mysql Workbench 等数据库管理工具修改了这一数据,那么这两次查询返回的结果依旧是相同的,即使数据已经发生了变化; + 3. 如果在同一个 `SqlSession` 对象两次查询同一数据期间,我们使用该对象“增删改”了与该数据无关的其它数据,并没有进行任何涉及该数据的操作, 数据也没有发生变化,那么 MyBatis 的一级缓存依旧会失效,这延伸自 2 中的第 3 种情况; + 4. 如果在同一个 `SqlSession` 对象两次查询同一数据期间,我们又多次查询了其它数据(期间没有进行任何的“增删改”操作),那么数据库中的该数据没有发生变化,MyBatis 也会顺利从一级缓存中获取到该数据。 ------ @@ -525,27 +511,21 @@ IPHASH优先级比较高,会覆盖权重策略 ### ①二级缓存开启条件 -> 1.在核心配置文件中,设置全局配置属性`cacheEnabled="true"`,默认为true,不需要设置 -> -> 2.映射文件中必须设置 **``** 标签 -> -> 3.二级缓存必须在SqlSession提交关闭或提交后才会生效,范围是SqlSessionFactory -> -> 4.查询的数据所转换的实体类类型必须实现序列化接口 +1. 在核心配置文件中,设置全局配置属性`cacheEnabled="true"`,默认为true,不需要设置 +2. 映射文件中必须设置 **``** 标签 +3. 二级缓存必须在`SqlSession`提交关闭或提交后才会生效,范围是`SqlSessionFactory` +4. 查询的数据所转换的实体类类型必须实现序列化接口 ### ②二级缓存失效情况 -> **两次查询之间执行任意增删改,使一级和二级缓存同时失效** +**两次查询之间执行任意增删改,使一级和二级缓存同时失效** ### ③mybatis缓存查询的顺序 -> 1.先查询二级缓存,因为二级缓存中可能会有其他程序已经查询出来的数据,可以直接哪来用 -> -> 2.如果二级缓存没有命中,在查询一级缓存 -> -> 3.如果一级缓存也没有命中,查询数据库 -> -> 4.SQLSession关闭后,一级缓存中的数据会写入二级缓存 +1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查询出来的数据,可以直接哪来用 +2. 如果二级缓存没有命中,在查询一级缓存 +3. 如果一级缓存也没有命中,查询数据库 +4. `SQLSession`关闭后,一级缓存中的数据会写入二级缓存 ------ @@ -553,29 +533,25 @@ IPHASH优先级比较高,会覆盖权重策略 #### 1.继承父类不同 -Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类;但**二者都实现了Map接口。** +Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类;但 **二者都实现了Map接口** 。 #### 2.线程的安全性 -1.**HashTable是同步**(方法中使用了Synchronized)的;**而HashMap是未同步**(方法中缺省Synchronized)的。 - -2.**Hashtable 线程安全**,因为它每个方法中都加入了Synchronize,在多线程并发的环境下,可以直接使用Hashtable,不需自己在加同步; - -**HashMap线程不安全**,因为HashMap底层是一个Entry数组,当发生hashmap冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。 +1. **HashTable是同步**(方法中使用了Synchronized)的;**而HashMap是未同步**(方法中缺省Synchronized)的。 +2. **Hashtable 线程安全**,因为它每个方法中都加入了Synchronize,在多线程并发的环境下,可以直接使用Hashtable,不需自己在加同步; +3. **HashMap线程不安全**,因为HashMap底层是一个Entry数组,当发生hashmap冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。 #### 3.是否有contains方法 -1.HashTable有一个contains(Object value)方法,功能和containsValue方法(Object value)功能一样。 - -2.HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey。 +1. HashTable有一个contains(Object value)方法,功能和containsValue方法(Object value)功能一样。 +2. HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey。 #### 4.可否允许有null值 key、value都是对象,但是不能拥有重复key值,value值可以重复出现。 -**1.Hashtable中,key和value都不允许出现null值。** - -**2.HashMap允许null值(key和value都可以),因为在HashMap中null可以作为健,而它对应的值可以有多个null。** +1. **Hashtable中,key和value都不允许出现null值。** +2. **HashMap允许null值(key和value都可以),因为在HashMap中null可以作为健,而它对应的值可以有多个null。** #### 5.遍历方式内部实现不同 @@ -619,7 +595,8 @@ static int indexFor(int h, int length) { ## 27.BeanFactory和FactoryBean的区别 -区别:**BeanFactory是个Factory**,也就是IOC容器或对象工厂,**FactoryBean是个Bean**。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似 +!!! Note "区别" + **BeanFactory是个Factory**,也就是IOC容器或对象工厂,**FactoryBean是个Bean**。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。 ## 28.Java线程通信方式 @@ -629,13 +606,13 @@ static int indexFor(int h, int length) { **③、wait/notify机制** -**④**、**管道通信**就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信 +**④、管道通信** 就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信 ## 29.Spring依赖注入方式 ## 30.springMVC执行流程 -![MVC执行流程](https://github.com/971230/Interview/blob/master/img/SpringMVC%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86.png?raw=true) +![MVC执行流程](./img/SpringMVC的工作原理.png) ## 31.如何保证线程安全 @@ -645,78 +622,71 @@ static int indexFor(int h, int length) { MySQL 有多种存储引擎,每种存储引擎有各自的优缺点,可以择优选择使用 -```txt -MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE -``` +!!! Note "有哪些引擎" + MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE -> 1. **事务支持:** InnoDB 提供了对事务的完整支持,包括 ACID(原子性、一致性、隔离性、持久性)属性。这使得 InnoDB 适用于需要高度数据一致性和可靠性的应用程序,如金融系统和在线交易系统。 -> 2. **行级锁定:** InnoDB 使用行级锁定来进行并发控制,而不是表级锁定。这意味着多个事务可以同时操作同一表的不同行,提高了并发性和性能。行级锁定还减小了死锁的风险,相对于表级锁定更灵活。 -> 3. **外键约束:** InnoDB 支持外键,这是关系型数据库中重要的特性之一。外键用于维护表与表之间的关系完整性,确保引用完整性和一致性。 -> 4. **崩溃恢复:** InnoDB 提供了崩溃恢复机制,通过事务日志(transaction log)和重做日志(redo log)来确保数据库在发生故障或崩溃时可以恢复到一致的状态。 -> 5. **自动增长列:** InnoDB 支持自动增长列,使得在插入数据时不必手动指定主键的值。这简化了开发人员的工作,并提高了应用程序的易用性。 -> 6. **热备份:** InnoDB 支持热备份(Hot Backup),可以在数据库运行的同时进行备份,而不需要停止数据库服务。这有助于提高数据库的可用性和灵活性。 -> 7. **MVCC(多版本并发控制):** InnoDB 使用 MVCC 来支持并发控制,允许读取操作不被写入操作阻塞。每个事务在执行时都会创建一个快照,从而允许并发读取,而不会造成读取脏数据。 -> 8. **自适应哈希索引和全文本索引:** InnoDB 引擎支持自适应哈希索引,这有助于提高查询性能。此外,它还支持全文本索引,用于进行全文搜索操作。 +1. **事务支持:** InnoDB 提供了对事务的完整支持,包括 ACID(原子性、一致性、隔离性、持久性)属性。这使得 InnoDB 适用于需要高度数据一致性和可靠性的应用程序,如金融系统和在线交易系统。 +2. **行级锁定:** InnoDB 使用行级锁定来进行并发控制,而不是表级锁定。这意味着多个事务可以同时操作同一表的不同行,提高了并发性和性能。行级锁定还减小了死锁的风险,相对于表级锁定更灵活。 +3. **外键约束:** InnoDB 支持外键,这是关系型数据库中重要的特性之一。外键用于维护表与表之间的关系完整性,确保引用完整性和一致性。 +4. **崩溃恢复:** InnoDB 提供了崩溃恢复机制,通过事务日志(transaction log)和重做日志(redo log)来确保数据库在发生故障或崩溃时可以恢复到一致的状态。 +5. **自动增长列:** InnoDB 支持自动增长列,使得在插入数据时不必手动指定主键的值。这简化了开发人员的工作,并提高了应用程序的易用性。 +6. **热备份:** InnoDB 支持热备份(Hot Backup),可以在数据库运行的同时进行备份,而不需要停止数据库服务。这有助于提高数据库的可用性和灵活性。 +7. **MVCC(多版本并发控制):** InnoDB 使用 MVCC 来支持并发控制,允许读取操作不被写入操作阻塞。每个事务在执行时都会创建一个快照,从而允许并发读取,而不会造成读取脏数据。 +8. **自适应哈希索引和全文本索引:** InnoDB 引擎支持自适应哈希索引,这有助于提高查询性能。此外,它还支持全文本索引,用于进行全文搜索操作。 ## 34.spring事务机制 ## 35.数据库事务隔离 -**`脏读`**:所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。也就是说,**当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据**。 +**`脏读`**:所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。也就是说,**当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据**。 -**`不可重复读`**:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。也就是说,**当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配**,也就照应了不可重复读的语义。 +**`不可重复读`**:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。也就是说,**当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配**,也就照应了不可重复读的语义。 -**`幻读`**:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。也就是说,**当前事务读第一次取到的数据比后来读取到数据条目不一致**。 +**`幻读`**:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。也就是说,**当前事务读第一次取到的数据比后来读取到数据条目不一致**。 ## 36.什么是偏向锁,锁的升级过程 -# TCP/IP三次握手四次挥手 +## TCP/IP三次握手四次挥手 -## 三次握手 +### 三次握手 假设发送端为客户端,接收端为服务端。开始时客户端和服务端的状态都是CLOSED。 -![](https://i0.hdslb.com/bfs/article/50b797c94e2d2faf154e012a1256274658b27555.png@819w_437h_progressive.webp) + -> **第一次握手**:客户端向服务端发起建立连接请求,客户端会随机生成一个起始序列号x,客户端向服务端发送的字段中包含标志位SYN=1,序列号seq=x。第一次握手前客户端的状态为CLOSE,第一次握手后客户端的状态为SYN-SENT。此时服务端的状态为LISTEN。 -> -> **第二次握手**:服务端在收到客户端发来的报文后,会随机生成一个服务端的起始序列号y,然后给客户端回复一段报文,其中包括标志位SYN=1,ACK=1,序列号seq=y,确认号ack=x+1。第二次握手前服务端的状态为LISTEN,第二次握手后服务端的状态为SYN-RCVD,此时客户端的状态为SYN-SENT。(其中SYN=1表示要和客户端建立一个连接,ACK=1表示确认序号有效) -> -> **第三次握手**:客户端收到服务端发来的报文后,会再向服务端发送报文,其中包含标志位ACK=1,序列号seq=x+1,确认号ack=y+1。第三次握手前客户端的状态为SYN-SENT,第三次握手后客户端和服务端的状态都为ESTABLISHED。 -> -> **此时连接建立完成。** +!!! Note "" + - **第一次握手**:客户端向服务端发起建立连接请求,客户端会随机生成一个起始序列号x,客户端向服务端发送的字段中包含标志位SYN=1,序列号seq=x。第一次握手前客户端的状态为CLOSE,第一次握手后客户端的状态为SYN-SENT。此时服务端的状态为LISTEN。 + + - **第二次握手**:服务端在收到客户端发来的报文后,会随机生成一个服务端的起始序列号y,然后给客户端回复一段报文,其中包括标志位SYN=1,ACK=1,序列号seq=y,确认号ack=x+1。第二次握手前服务端的状态为LISTEN,第二次握手后服务端的状态为SYN-RCVD,此时客户端的状态为SYN-SENT。(其中SYN=1表示要和客户端建立一个连接,ACK=1表示确认序号有效) + + - **第三次握手**:客户端收到服务端发来的报文后,会再向服务端发送报文,其中包含标志位ACK=1,序列号seq=x+1,确认号ack=y+1。第三次握手前客户端的状态为SYN-SENT,第三次握手后客户端和服务端的状态都为ESTABLISHED。 + + - **此时连接建立完成**。 ### 两次握手可以吗? -第三次握手主要为了防止已失效的连接请求报文段突然又传输到了服务端,导致产生问题。 +第三次握手主要为了防止已失效的连接请求报文段突然又传输到了服务端,导致产生问题。 - 比如客户端A发出连接请求,可能因为网络阻塞原因,A没有收到确认报文,于是A再重传一次连接请求。 - - 连接成功,等待数据传输完毕后,就释放了连接。 - - 然后A发出的第一个连接请求等到连接释放以后的某个时间才到达服务端B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段。 - - 如果不采用三次握手,只要B发出确认,就建立新的连接了,此时A不会响应B的确认且不发送数据,则B一直等待A发送数据,浪费资源。 -## 四次挥手 +### 四次挥手 -![](https://i0.hdslb.com/bfs/article/f36b71e83aaca198728443532c6ef86a714d8a43.png@819w_587h_progressive.webp) + -> 1. A的应用进程先向其TCP发出连接释放报文段(FIN=1,seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。 -> -> 2. B收到连接释放报文段后即发出确认报文段(ACK=1,ack=u+1,seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。 -> -> 3. A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。 -> -> 4. B发送完数据,就会发出连接释放报文段(FIN=1,ACK=1,seq=w,ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。 -> -> 5. A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL(最大报文段生存时间)后,A才进入CLOSED状态。B收到A发出的确认报文段后关闭连接,若没收到A发出的确认报文段,B就会重传连接释放报文段。 +1. A的应用进程先向其TCP发出连接释放报文段(FIN=1,seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。 +2. B收到连接释放报文段后即发出确认报文段(ACK=1,ack=u+1,seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。 +3. A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。 +4. B发送完数据,就会发出连接释放报文段(FIN=1,ACK=1,seq=w,ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认。 +5. A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL(最大报文段生存时间)后,A才进入CLOSED状态。B收到A发出的确认报文段后关闭连接,若没收到A发出的确认报文段,B就会重传连接释放报文段。 ### 第四次挥手为什么要等待2MSL? -> **保证A发送的最后一个ACK报文段能够到达B**。这个ACK报文段有可能丢失,B收不到这个确认报文,就会超时重传连接释放报文段,然后A可以在2MSL时间内收到这个重传的连接释放报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的连接释放报文段,所以不会再发送一次确认报文段,B就无法正常进入到CLOSED状态。 -> -> **防止已失效的连接请求报文段出现在本连接中**。A在发送完最后一个ACK报文段后,再经过2MSL,就可以使这个连接所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段。 +**保证A发送的最后一个ACK报文段能够到达B**。这个ACK报文段有可能丢失,B收不到这个确认报文,就会超时重传连接释放报文段,然后A可以在2MSL时间内收到这个重传的连接释放报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的连接释放报文段,所以不会再发送一次确认报文段,B就无法正常进入到CLOSED状态。 + +**防止已失效的连接请求报文段出现在本连接中**。A在发送完最后一个ACK报文段后,再经过2MSL,就可以使这个连接所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段。 ### 为什么是四次挥手? @@ -736,16 +706,56 @@ B+树: 当Redis和MySQL之间出现消息不一致的情况时,可以考虑以下几种解决方案: -> 1. **数据同步机制**:可以通过定时任务或者触发器等方式,**定期将MySQL中的数据同步到Redis中**,确保数据的一致性。这种方式适用于数据量较小且对实时性要求不高的场景。(订阅binlog) -> 2. **双写模式**:在写入MySQL数据时,同时写入Redis中,确保数据的一致性。这种方式可以在应用层面实现,但需要考虑并发写入的问题,如使用分布式锁来保证数据的一致性。 -> 3. **异步队列**:将MySQL的变更操作写入消息队列,然后由消费者异步处理并更新Redis中的数据。这种方式可以降低对MySQL的写入压力,并提高系统的可扩展性和容错性。 -> 4. **读写分离**:将读操作从Redis中获取数据,写操作则直接操作MySQL,通过将读写分离可以减少对Redis和MySQL之间的数据一致性要求。但需要注意同步问题,即写入MySQL后需要及时更新Redis中的数据。 -> 5. **使用分布式事务**:如果业务场景中需要保证强一致性,可以考虑使用分布式事务来确保Redis和MySQL的数据一致性。可以使用分布式事务框架,如Seata、TCC等来实现。 -> 6. **采用延迟双删策略**:删除redis ↣ 更新数据库 ↣ 延时500毫秒 ↣ 删除redis -> 7. **消息队列方式** -> +### 1️⃣只读缓存 +在这种模式下,更新数据时先更新数据库,然后删除缓存中的对应条目。这样,当缓存被删除后,下次读取该数据时会触发缓存缺失,从而从数据库中重新加载数据。 + +!!! Note "" + **优点:** 简单易实现。减少了缓存与数据库之间的一致性问题。 + + **缺点:** 如果删除缓存失败,可能会导致数据不一致。可能会导致缓存穿透问题,即缓存中没有的数据却不断地被请求,导致每次都去数据库中查询。 + +!!! Info "解决策略" + **延时双删**:删除redis ↣ 更新数据库 ↣ 延时500毫秒 ↣ 删除redis。这可以确保数据库操作已经完成并持久化,从而减少数据不一致的风险。 + + **消息队列+异步重试**:使用消息队列来异步更新缓存,可以减少对用户请求的影响。当数据库更新后,发送一条更新缓存的消息到消息队列,由另一个进程监听消息队列并更新缓存。 -选择合适的解决方案需要根据具体的业务场景和需求来确定,考虑到数据量、实时性要求、系统复杂度等因素。 +### 2️⃣读写缓存 +在这种模式下,更新数据时同时更新数据库和缓存。这种方法要求更新操作具有原子性,以确保数据库和缓存同时被更新或同时失败。 + +!!! Note "" + **优点:** 数据一致性更好。 + + **缺点:** 实现复杂度较高。如果缓存更新失败,可能会导致数据不一致。 + +!!! Info "解决策略" + **同步直写**:使用事务来保证缓存和数据库更新的原子性。 + + **分布式锁**:在更新数据时,使用分布式锁来确保同一时间只有一个线程可以更新数据库和缓存。 + +### 3️⃣基于消息队列的异步更新 +这种方法通过消息队列来异步更新缓存,可以减少对用户请求的影响。当数据库更新后,发送一条更新缓存的消息到消息队列,由另一个进程监听消息队列并更新缓存。 + +!!! Note "" + **优点:** 可以减少对用户请求的影响。可以通过重试机制来保证数据的一致性。 + + **缺点:** 需要确保消息不会因为网络等原因丢失。高并发情况下需要处理消息积压的问题。 + +### 4️⃣订阅 MySQL 的 Binlog +利用 MySQL 的 Binlog(二进制日志)来异步同步数据。这种方法可以减少数据不一致的时间窗口,但仍然存在极小的延迟。 + +!!! Note "" + **优点:** 可以减少数据不一致的时间窗口。实现较为复杂,需要额外的维护成本。 + + **缺点:** 实现较为复杂,需要额外的维护成本。可能存在一定的延迟。 + +!!! Example "简短一点好记" + 1. **数据同步机制**:可以通过定时任务或者触发器等方式,**定期将MySQL中的数据同步到Redis中**,确保数据的一致性。这种方式适用于数据量较小且对实时性要求不高的场景。(订阅binlog) + 2. **双写模式**:在写入MySQL数据时,同时写入Redis中,确保数据的一致性。这种方式可以在应用层面实现,但需要考虑并发写入的问题,如使用分布式锁来保证数据的一致性。 + 3. **异步队列**:将MySQL的变更操作写入消息队列,然后由消费者异步处理并更新Redis中的数据。这种方式可以降低对MySQL的写入压力,并提高系统的可扩展性和容错性。 + 4. **读写分离**:将读操作从Redis中获取数据,写操作则直接操作MySQL,通过将读写分离可以减少对Redis和MySQL之间的数据一致性要求。但需要注意同步问题,即写入MySQL后需要及时更新Redis中的数据。 + 5. **使用分布式事务**:如果业务场景中需要保证强一致性,可以考虑使用分布式事务来确保Redis和MySQL的数据一致性。可以使用分布式事务框架,如Seata、TCC等来实现。 + 6. **采用延迟双删策略**:删除redis ↣ 更新数据库 ↣ 延时500毫秒 ↣ 删除redis + 7. **消息队列方式** ## 41.MySQL一二级缓存,和缓存实现 @@ -763,21 +773,23 @@ https://www.begtut.com/mysql/mysql-tutorial.html ## 46.乐观锁和悲观锁的区别 -- > **悲观锁**:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此**操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据**。**适合写操作比较多的场景,写可以保证写操作时数据正确** +**悲观锁**:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此**操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据**。**适合写操作比较多的场景,写可以保证写操作时数据正确** -假定会发生并发冲突,屏蔽一切可违反数据完整性的操作,同一时刻只能有一个线程执行写操作 +!!! Note "" + 假定会发生并发冲突,屏蔽一切可违反数据完整性的操作,同一时刻只能有一个线程执行写操作 -悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。 + 悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。 -例如:synchronized,Lock,WriteReadLock + 例如:synchronized,Lock,WriteReadLock -- > **乐观锁**:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此**乐观锁操作数据不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作**。**适合读操作比较多的场景,不加锁的特点可以使其读操作的性能大幅提升** +**乐观锁**:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此**乐观锁操作数据不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作**。**适合读操作比较多的场景,不加锁的特点可以使其读操作的性能大幅提升** -假设不发生冲突,只在提交操作时检查是否违反数据完整性,多个线程可以并发执行写操作,但是只能有一个线程执行写操作成功。 +!!! Note "" + 假设不发生冲突,只在提交操作时检查是否违反数据完整性,多个线程可以并发执行写操作,但是只能有一个线程执行写操作成功。 -乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。 + 乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。 -例如:Java中的CAS算法(依赖硬件CPU)、AtomicInteger + 例如:Java中的CAS算法(依赖硬件CPU)、AtomicInteger ------ @@ -789,9 +801,9 @@ https://www.begtut.com/mysql/mysql-tutorial.html #### ①、保证了不同线程对共享变量进行操作时的可见性(尤其是多核和多CPU场景下),即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 -> (1)当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去; -> -> (2)这个写会操作会导致其他线程中的volatile变量缓存无效。 +!!! Note "" + (1)当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
+ (2)这个写会操作会导致其他线程中的volatile变量缓存无效。 #### ②、禁止进行指令重排序,可以保证步骤的执行顺序是一定的,即有序性。(例如count++底层会有三个步骤)JVM底层执行时会对指令进行重排序的优化。 @@ -813,76 +825,64 @@ volatile可以保证线程可见性且提供了一定的有序性,但是无法 在JVM底层volatile是采用"内存屏障"来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,**加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏)**,内存屏障会提供3个功能; -> * (1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; -> * (2)它会强制将对缓存的修改操作立即写入主存; -> * (3)如果是写操作,它会导致其他CPU中对应的缓存行无效。 +!!! Note "" + (1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
+ (2)它会强制将对缓存的修改操作立即写入主存;
+ (3)如果是写操作,它会导致其他CPU中对应的缓存行无效。 ------ ## 48.*SQL语句优化(选择几条) -遇到性能慢的sql语句,不要一上来就想着等价改写,先通过索引进行优化,合理的索引能解决90%的性能问题。如果索引都解决不了的情况下,才去尝试使用等价改写来进行优化sql,一般来说等价改写能解决剩下5%的问题。如果连等价改写都解决不了剩下的5%的性能问题话,就要尝试改业务,或者改数据库技术栈来解决问题了,这种通常来说成本会非常高。 - -> 1、Where 子句中:**where 表之间的连接必须写在其他 Where 条件之前**,那些可以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后 -> -> 2、**用 `EXISTS` 替代 `IN`、用 `NOT EXISTS` 替代 `NOT IN`** -> -> 3、**避免在索引列上进行计算或使用函数,因为这将导致索引不被使用** -> -> 4、**避免在索引列上使用 `IS NULL` 和 `IS NOT NULL`** -> -> 5、对查询进行优化,应尽量避免全表扫描,首先应**考虑在 where 及 order by 涉及的列上建立索引** -> -> 6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃 使用索引而进行全表扫描 -> -> 7、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描 -> -> 8、**使用`连接(JOIN)`替代子查询**:子查询的性能较差,可以尝试使用连接(JOIN)来替代子查询,以提高查询性能。 -> -> 9、**使用LIMIT限制结果集大小**:在查询时使用`LIMIT`来限制结果集的大小,避免返回过多的数据。 -> -> 10、**避免使用`SELECT*`** -> -> 11、**避免过多的连接和嵌套查询**:过多的连接和嵌套查询会导致查询性能下降,可以考虑优化查询逻辑,减少连接和嵌套查询的使用。 -> -> 12、**隐式类型转换造成不使用索引** -> -> 13、**尽量避免使用 `or`,会导致数据库引擎放弃索引进行全表扫描** -> -> 14、**尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。** -> -> ```sql -> SELECT ??? FROM 表名 WHERE username LIKE '%陈%' -> ``` -> -> 优化方式:尽量在字段后面使用模糊查询。如下: -> -> ```sql -> SELECT ??? FROM 表名 WHERE username LIKE '陈%' -> ``` +遇到性能慢的sql语句,不要一上来就想着等价改写,先通过索引进行优化,合理的索引能解决90%的性能问题。如果索引都解决不了的情况下,才去尝试使用等价改写来进行优化sql,一般来说等价改写能解决剩下5%的问题。如果连等价改写都解决不了剩下的5%的性能问题话,就要尝试改业务,或者改数据库技术栈来解决问题了,这种通常来说成本会非常高。 + +!!! Danger "常考重要" + 1、Where 子句中:**where 表之间的连接必须写在其他 Where 条件之前**,那些可以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后
+ 2、**用 `EXISTS` 替代 `IN`、用 `NOT EXISTS` 替代 `NOT IN`**
+ 3、**避免在索引列上进行计算或使用函数,因为这将导致索引不被使用**
+ 4、**避免在索引列上使用 `IS NULL` 和 `IS NOT NULL`**
+ 5、对查询进行优化,应尽量避免全表扫描,首先应**考虑在 where 及 order by 涉及的列上建立索引**
+ 6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃 使用索引而进行全表扫描
+ 7、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
+ 8、**使用`连接(JOIN)`替代子查询**:子查询的性能较差,可以尝试使用连接(JOIN)来替代子查询,以提高查询性能。
+ 9、**使用LIMIT限制结果集大小**:在查询时使用`LIMIT`来限制结果集的大小,避免返回过多的数据。
+ 10、**避免使用`SELECT*`**
+ 11、**避免过多的连接和嵌套查询**:过多的连接和嵌套查询会导致查询性能下降,可以考虑优化查询逻辑,减少连接和嵌套查询的使用。
+ 12、**隐式类型转换造成不使用索引**
+ 13、**尽量避免使用 `or`,会导致数据库引擎放弃索引进行全表扫描**
+ 14、**尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。**
+ ```sql + SELECT ??? FROM 表名 WHERE username LIKE '%陈%' + ``` + 优化方式:尽量在字段后面使用模糊查询。如下:
+ ```sql + SELECT ??? FROM 表名 WHERE username LIKE '陈%' + ``` + +[相关链接🔗收藏!](https://juejin.cn/post/7379816515552641050) ### Mysql Explain使用分析 -![ ](https://pic1.zhimg.com/v2-a4cb15fb2e3984a4ed007e44e3a080c8_r.jpg) - -- 1️⃣【id】:查询的标识符,表示查询的执行顺序。 -- 2️⃣【select_type】:查询类型,如 `SIMPLE`(简单查询),`PRIMARY`(主查询),`UNION`(联合查询的一部分),`SUBQUERY`(子查询)。 -- 3️⃣【table】:查询涉及的表 -- 4️⃣【type】:连接类型,表示MySQL如何查找行。常见类型按效率从高到低排列为: -- - `system`:表只有一行(常见于系统表)。 - - `const`:表最多有一个匹配行(索引为主键或唯一索引)。 - - `eq_ref`:对于每个来自前一个表的行,表中最多有一个匹配行。 - - `ref`:对于每个来自前一个表的行,表中可能有多个匹配行。 - - `range`:使用索引查找给定范围的行。 - - `index`:全表扫描索引。 - - `ALL`:全表扫描。 -- 5️⃣【possible_keys】:查询中可能使用的索引。 -- 6️⃣【key】:实际使用的索引。 -- 7️⃣【key_len】:使用的索引键长度。 -- 8️⃣【ref】:使用的列或常量,与索引比较。 -- 9️⃣【rows】:MySQL 估计的要读取的行数。 -- 🔟【filtered】:经过表条件过滤后的行百分比。 -- ⓫【Extra】:额外的信息,如 `Using index`(覆盖索引),`Using where`(使用 WHERE 子句过滤),`Using filesort`(文件排序),`Using temporary`(使用临时表)。 +![](https://pic1.zhimg.com/v2-a4cb15fb2e3984a4ed007e44e3a080c8_r.jpg) + +- ①【id】:查询的标识符,表示查询的执行顺序。 +- ②【select_type】:查询类型,如 `SIMPLE`(简单查询),`PRIMARY`(主查询),`UNION`(联合查询的一部分),`SUBQUERY`(子查询)。 +- ③【table】:查询涉及的表 +- ④【type】:连接类型,表示MySQL如何查找行。常见类型按效率从高到低排列为: + - `system`:表只有一行(常见于系统表)。 + - `const`:表最多有一个匹配行(索引为主键或唯一索引)。 + - `eq_ref`:对于每个来自前一个表的行,表中最多有一个匹配行。 + - `ref`:对于每个来自前一个表的行,表中可能有多个匹配行。 + - `range`:使用索引查找给定范围的行。 + - `index`:全表扫描索引。 + - `ALL`:全表扫描。 +- ⑤【possible_keys】:查询中可能使用的索引。 +- ⑥【key】:实际使用的索引。 +- ⑦【key_len】:使用的索引键长度。 +- ⑧【ref】:使用的列或常量,与索引比较。 +- ⑨【rows】:MySQL 估计的要读取的行数。 +- ⑩【filtered】:经过表条件过滤后的行百分比。 +- ⑪【Extra】:额外的信息,如 `Using index`(覆盖索引),`Using where`(使用 WHERE 子句过滤),`Using filesort`(文件排序),`Using temporary`(使用临时表)。 ------ @@ -968,7 +968,7 @@ Kafka的重复消费问题是指消费者在处理消息时,由于一些原因 ------ -## **55.Kafka消息乱序怎么办** +## 55.Kafka消息乱序怎么办 当Kafka消息乱序时,可以采取以下几种方法来解决: