├── .gitignore ├── README.md └── notes ├── JVM ├── OOM常见原因及解决方案.md └── 线上事故总结.md ├── Java基础.md ├── Java基础 └── Java异常.md ├── Java进阶.md ├── Java进阶 ├── IO多路复用原理.md └── 零拷贝原理.md ├── MySQL ├── InnoDB底层原理.md └── 深入学习InnoDB可重复读隔离级别下如何避免幻读.md ├── Redis ├── Redis基础数据结构.md └── Redis面试题.md ├── pictures ├── .DS_Store ├── Java-T-Shaped.png ├── Java基础 │ └── Java异常类层次结构图.png ├── MySQL │ ├── MySQL-Component.png │ ├── innodb_directoryPage.png │ ├── innodb_directoryPage02.png │ └── innodb_directoryPage03.png └── dubbo │ └── dubbo.png ├── 框架 ├── SpringAOP面试.md ├── SpringBoot面试.md └── Spring面试.md ├── 计算机网络 ├── HTTP和HTTPS的深入分析.md └── 计算机网络面试题.md └── 面试 ├── Dubbo复习.md ├── JVM面试题.md ├── Java其他面试题.md ├── MySQL复习.md ├── Redis复习.md ├── SpringCloud复习.md ├── 复习.md ├── 秒杀架构实现.md └── 面经.md /.gitignore: -------------------------------------------------------------------------------- 1 | # idea 2 | .idea 3 | .DS_Store 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Java-T-Shaped](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/pictures/Java-T-Shaped.png) 2 | 3 | T-Shaped指的是T型人才。而Java-T-Shaped旨在记录广而又有深度的Java以及Java相关知识,来提升Java开发人员自身的实力,为的就是在这个内卷化严重的行业能够脱颖而出拿到高薪offer,从而走向人生巅峰,赢取白富美... 4 | 5 | > 项目持续更新中,不建议大家直接fork,欢迎大家star以及watch 6 | 7 | # 目录 8 | 9 | - Java 10 | 11 | ✅ [Java异常](http://github.com/coderbruis/Java-Accumulation/blob/master/notes/Java%E5%9F%BA%E7%A1%80/Java%E5%BC%82%E5%B8%B8.md) 12 | 零拷贝 (多种理解 Linux 内核 IO 理解 ) 13 | 14 | - 计算机网络 15 | 16 | - 操作系统 17 | 18 | - MySQL 19 | 20 | ✅ [MySQL面试题](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/%E9%9D%A2%E8%AF%95/MySQL%E5%A4%8D%E4%B9%A0.md) 21 | 22 | ✅ [InnoDB底层原理](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/MySQL/InnoDB%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86.md) 23 | 24 | - 计算机基础 25 | 26 | ✅ [计算机网络面试题](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E9%9D%A2%E8%AF%95%E9%A2%98.md) 27 | 28 | ✅ [HTTP和HTTPS的深入分析](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP%E5%92%8CHTTPS%E7%9A%84%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90.md) 29 | 30 | - Spring 31 | 32 | ✅ [Spring面试题](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/%E6%A1%86%E6%9E%B6/Spring%E9%9D%A2%E8%AF%95.md) 33 | 34 | ✅ [SpringAOP面试题](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/%E6%A1%86%E6%9E%B6/SpringAOP%E9%9D%A2%E8%AF%95.md) 35 | 36 | - SpringMVC 37 | 38 | - SpringBoot 39 | 40 | ✅ [SpringBoot面试题](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/%E6%A1%86%E6%9E%B6/SpringBoot%E9%9D%A2%E8%AF%95.md) 41 | 42 | - Mybatis 43 | 44 | - SpringCloud 45 | 46 | ✅ [SpringCloud面试题](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/%E9%9D%A2%E8%AF%95/SpringCloud%E5%A4%8D%E4%B9%A0.md) 47 | 48 | - 消息队列 49 | 50 | - Redis 51 | 52 | ✅ [Redis面试题](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/%E9%9D%A2%E8%AF%95/Redis%E5%A4%8D%E4%B9%A0.md) 53 | 54 | - Dubbo 55 | 56 | ✅ [Dubbo面试题](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/%E9%9D%A2%E8%AF%95/Dubbo%E5%A4%8D%E4%B9%A0.md) 57 | 58 | - Netty 59 | 60 | ✅ [IO多路复用底层原理](https://github.com/coderbruis/Java-T-Shaped/blob/master/notes/Java%E8%BF%9B%E9%98%B6/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E5%8E%9F%E7%90%86.md) 61 | 62 | -------------------------------------------------------------------------------- /notes/JVM/OOM常见原因及解决方案.md: -------------------------------------------------------------------------------- 1 | 当 JVM 内存严重不足时,就会抛出 java.lang.OutOfMemoryError 错误。本文总结了常见的 OOM 原因及其解决方法,如下图所示。如有遗漏或错误,欢迎补充指正。 2 | ![image](https://note.youdao.com/yws/res/19206/3F96F56F70CD47C5A1179C0790B0223C) 3 | 4 | ## 1. Java heap space 5 | 6 | 当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 java.lang.OutOfMemoryError:Javaheap space 错误(根据实际生产经验,可以对程序日志中的 OutOfMemoryError 配置关键字告警,一经发现,立即处理)。 7 | 8 | ### 原因分析 9 | 10 | Javaheap space 错误产生的常见原因可以分为以下几类: 11 | 12 | 1. 请求创建一个超大对象,通常是一个大数组。 13 | 2. 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。 14 | 3. 过度使用终结器(Finalizer),该对象没有立即被 GC。 15 | 4. 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。 16 | 17 | ### 解决方案 18 | 19 | 针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理: 20 | 21 | 1. 如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。 22 | 2. 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。 23 | 3. 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。 24 | 25 | ## 2. GC overhead limit exceeded 26 | 27 | 当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded 错误。简单地说,就是应用程序已经基本耗尽了所有可用内存, GC 也无法回收。 28 | 29 | 此类问题的原因与解决方案跟 Javaheap space 非常类似,可以参考上文。 30 | 31 | ## 3. Permgen space 32 | 33 | 该错误表示永久代(Permanent Generation)已用满,通常是因为加载的 class 数目太多或体积太大。 34 | 35 | ### 原因分析 36 | 37 | 永久代存储对象主要包括以下几类: 38 | 39 | 1. 加载/缓存到内存中的 class 定义,包括类的名称,字段,方法和字节码; 40 | 2. 常量池; 41 | 3. 对象数组/类型数组所关联的 class; 42 | 4. JIT 编译器优化后的 class 信息。 43 | 44 | PermGen 的使用量与加载到内存的 class 的数量/大小正相关。 45 | 46 | ### 解决方案 47 | 48 | 根据 Permgen space 报错的时机,可以采用不同的解决方案,如下所示: 49 | 50 | 1. 程序启动报错,修改 -XX:MaxPermSize 启动参数,调大永久代空间。 51 | 2. 应用重新部署时报错,很可能是没有应用没有重启,导致加载了多份 class 信息,只需重启 JVM 即可解决。 52 | 3. 运行时报错,应用程序可能会动态创建大量 class,而这些 class 的生命周期很短暂,但是 JVM 默认不会卸载 class,可以设置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC这两个参数允许 JVM 卸载 class。 53 | 54 | 如果上述方法无法解决,可以通过 jmap 命令 dump 内存对象 jmap-dump:format=b,file=dump.hprof ,然后利用 Eclipse MAT https://www.eclipse.org/mat 功能逐一分析开销最大的 classloader 和重复 class。 55 | 56 | ## 4. Metaspace 57 | 58 | JDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,通常是因为加载的 class 数目太多或体积太大。 59 | 60 | 此类问题的原因与解决方法跟 Permgenspace 非常类似,可以参考上文。需要特别注意的是调整 Metaspace 空间大小的启动参数为 -XX:MaxMetaspaceSize。 61 | 62 | ## 5. Unable to create new native thread 63 | 64 | 每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误。 65 | 66 | ### 原因分析 67 | 68 | JVM 向 OS 请求创建 native 线程失败,就会抛出 Unableto createnewnativethread,常见的原因包括以下几类: 69 | 70 | 1. 线程数超过操作系统最大线程数 ulimit 限制; 71 | 2. 线程数超过 kernel.pid_max(只能重启); 72 | 3. native 内存不足; 73 | 74 | 该问题发生的常见过程主要包括以下几步: 75 | 76 | 1. JVM 内部的应用程序请求创建一个新的 Java 线程; 77 | 2. JVM native 方法代理了该次请求,并向操作系统请求创建一个 native 线程; 78 | 3. 操作系统尝试创建一个新的 native 线程,并为其分配内存; 79 | 4. 如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次 native 内存分配; 80 | 5. JVM 将抛出 java.lang.OutOfMemoryError:Unableto createnewnativethread 错误。 81 | 82 | ### 解决方案 83 | 84 | 1. 升级配置,为机器提供更多的内存; 85 | 2. 降低 Java Heap Space 大小; 86 | 3. 修复应用程序的线程泄漏问题; 87 | 4. 限制线程池大小; 88 | 5. 使用 -Xss 参数减少线程栈的大小; 89 | 6. 调高 OS 层面的线程最大数:执行 ulimia-a 查看最大线程数限制,使用 ulimit-u xxx 调整最大线程数限制。 90 | 91 | ulimit -a .... 省略部分内容 ..... max user processes (-u) 16384 92 | 93 | ## 6. Out of swap space? 94 | 95 | 该错误表示所有可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报 Outof swap space? 错误。 96 | 97 | ### 原因分析 98 | 99 | 该错误出现的常见原因包括以下几类: 100 | 101 | 1. 地址空间不足; 102 | 2. 物理内存已耗光; 103 | 3. 应用程序的本地内存泄漏(native leak),例如不断申请本地内存,却不释放。 104 | 4. 执行 jmap-histo:live 命令,强制执行 Full GC;如果几次执行后内存明显下降,则基本确认为 Direct ByteBuffer 问题。 105 | 106 | ### 解决方案 107 | 108 | 根据错误原因可以采取如下解决方案: 109 | 110 | 1. 升级地址空间为 64 bit; 111 | 2. 使用 Arthas 检查是否为 Inflater/Deflater 解压缩问题,如果是,则显式调用 end 方法。 112 | 3. Direct ByteBuffer 问题可以通过启动参数 -XX:MaxDirectMemorySize 调低阈值。 113 | 4. 升级服务器配置/隔离部署,避免争用。 114 | 115 | 116 | ## 7. Kill process or sacrifice child 117 | 118 | 有一种内核作业(Kernel Job)名为 Out of Memory Killer,它会在可用内存极低的情况下“杀死”(kill)某些进程。OOM Killer 会对所有进程进行打分,然后将评分较低的进程“杀死”,具体的评分规则可以参考 Surviving the Linux OOM Killer。 119 | 120 | 不同于其他的 OOM 错误, Killprocessorsacrifice child 错误不是由 JVM 层面触发的,而是由操作系统层面触发的。 121 | 122 | ### 原因分析 123 | 124 | 默认情况下,Linux 内核允许进程申请的内存总量大于系统可用内存,通过这种“错峰复用”的方式可以更有效的利用系统资源。 125 | 126 | 然而,这种方式也会无可避免地带来一定的“超卖”风险。例如某些进程持续占用系统内存,然后导致其他进程没有可用内存。此时,系统将自动激活 OOM Killer,寻找评分低的进程,并将其“杀死”,释放内存资源。 127 | 128 | ### 解决方案 129 | 130 | 1. 升级服务器配置/隔离部署,避免争用。 131 | 2. OOM Killer 调优。 132 | 133 | 134 | ## 8. Requested array size exceeds VM limit 135 | 136 | JVM 限制了数组的最大长度,该错误表示程序请求创建的数组超过最大长度限制。 137 | 138 | JVM 在为数组分配内存前,会检查要分配的数据结构在系统中是否可寻址,通常为 Integer.MAX_VALUE-2。 139 | 140 | 此类问题比较罕见,通常需要检查代码,确认业务是否需要创建如此大的数组,是否可以拆分为多个块,分批执行。 141 | 142 | ## 9. Direct buffer memory 143 | 144 | Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory Mapped File)实现高速 IO。 145 | 146 | ### 原因分析 147 | 148 | Direct ByteBuffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 Directbuffer memory 错误。 149 | 150 | ### 解决方案 151 | 152 | 1. Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通过 Arthas 等在线诊断工具拦截该方法进行排查。 153 | 2. 检查是否直接或间接使用了 NIO,如 netty,jetty 等。 154 | 3. 通过启动参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值。 155 | 4. 检查 JVM 参数是否有 -XX:+DisableExplicitGC 选项,如果有就去掉,因为该参数会使 System.gc() 失效。 156 | 5. 检查堆外内存使用代码,确认是否存在内存泄漏;或者通过反射调用 sun.misc.Cleaner 的 clean() 方法来主动释放被 Direct ByteBuffer 持有的内存空间。 157 | 6. 内存容量确实不足,升级配置。 158 | 159 | -------------------------------------------------------------------------------- /notes/JVM/线上事故总结.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 总结每一次线上事故分析、处理流程以及效果。 4 | 5 | ## 正文 6 | 7 | ### 1. Linux服务器CPU异常 8 | 9 | Linux服务器CPU异常,某个Java服务tomcat使用率180%,查看tomcat日志发现是OOM——Java heap space,内存溢出了,一直在排查问题,没查出来。后来走流程时,发现同样的业务流程,H5没事,WEB端网站就报OOM,发现是WEB端网站某一查询如参orderId错改为了orderid,导致后端Java服务查询订单信息时,由于传入的orderId为null, 10 | 所以MyBatis将where查询语句给过滤了,导致全表查询,并将查询结果返给了List集合,由于数据量大导致堆内存不足,报OOM。排查过程为: 11 | 1. 先查看df -h查看磁盘使用情况; 12 | 2. 使用命令top -h查看Linux系统资源情况; 13 | 3. 使用jstat -gcutil [pid] 1000 10查看gc情况,发现gc没有太大的异常; 14 | 4. 排查前后端代码,重现bug场景。 15 | -------------------------------------------------------------------------------- /notes/Java基础.md: -------------------------------------------------------------------------------- 1 | # 1、Java异常 2 | 3 | ## 1.1、 Java异常分类和处理 4 | 5 | > 基础概念 6 | 7 | 如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。 8 | 9 | > 异常分类 10 | 11 | Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error 和 Exception。 12 | 13 | # 2、Java反射 14 | 15 | # 3、Java注解 16 | 17 | # 4、Java枚举 18 | 19 | # 5、Java内部类 20 | 21 | # 6、Java泛型 22 | 23 | # 7、Java拷贝 24 | 25 | # 8、Java序列化 26 | 27 | # 9、Java数据结构 28 | 29 | ## 9.1、Java的基础数据类型 30 | 31 | 在Java中,有八大基础数据类型,每个类型对应的二进制位数如下表: 32 | 33 | | 数据类型 | 字符数 | 二进制位数 34 | | ---- | ---- | ---- | 35 | | byte | 1字符 | 8位 | 36 | | char | 2字符 | 16位 | 37 | | short | 2字符 | 16位 | 38 | | int | 4字符 | 32位 | 39 | | float | 4字符 | 32位 | 40 | | long | 8字符 | 64位 | 41 | | double | 8字符 | 64位 | 42 | | boolean | ~ | ~ | 43 | 44 | 45 | oolean 只有两个值:true、false,可以使用 1 bit 来存储,但是具体大小没有明确规定。JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。JVM 支持 boolean 数组,但是是通过读写 byte 数组来实现的。 46 | 47 | 48 | # 10、Java网络编程 49 | 50 | # 11、Java多线程 51 | -------------------------------------------------------------------------------- /notes/Java基础/Java异常.md: -------------------------------------------------------------------------------- 1 | ## 1. Java异常 2 | 3 | ### 1.1 Java异常概念 4 | 5 | 如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。 6 | 7 | ### 1.2 Java异常分类 8 | 9 | 在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 10 | 11 | ![Java异常类层次结构图](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/pictures/Java%E5%9F%BA%E7%A1%80/Java%E5%BC%82%E5%B8%B8%E7%B1%BB%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E5%9B%BE.png) 12 | 13 | #### 1.2.1 Error(错误) 14 | 15 | Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。 16 | 17 | 这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。 18 | 19 | #### 1.2.2 Exception(异常) 20 | 21 | Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除 22 | 以 0 时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。 23 | 24 | #### 1.2.3 区别 25 | 26 | 注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。 27 | 28 | ### 1.3 Throwable类 29 | 30 | #### 1.3.1 Throwable 类常用方法 31 | 32 | - public string getMessage():返回异常发生时的简要描述 33 | - public string toString():返回异常发生时的详细信息 34 | - public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 35 | getMessage()返回的结果相同 36 | - public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息 37 | 38 | 未完待续.... 39 | 40 | ## 参考 41 | 42 | - [JavaGuide](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#32-%E5%BC%82%E5%B8%B8) 43 | -------------------------------------------------------------------------------- /notes/Java进阶.md: -------------------------------------------------------------------------------- 1 | # 1、Java并发 2 | 3 | # 2、Java虚拟机——JVM 4 | 5 | ## 2.1 JVM内存结构 6 | 7 | 如果没有特殊说明,都是针对的是 HotSpot 虚拟机。 8 | 9 | > Java有哪些内存结构?说一下Java的内存结构? 10 | 11 | Java内存区域可以分为运行时数据区和直接内存区。 12 | 13 | **运行时数据区包含了:方法区、堆区、Java虚拟机栈、本地方法栈。【JDK1.8】以前** 14 | 15 | ### 2.1.1 方法区 16 | 17 | 首先,方法区是线程间共享的内存区域,它的作用是用于存储已被虚拟机加载的类信息、常量、静态变量(类变量)。方法区也可以成为非堆区。对于HotSpot虚拟机来说,方法区也可以成为“永久代”,这是因为HotSpot的设计团队将GC分代收集扩展至方法区,或者说是使用永久代来实现方法区。不像堆区那样会进行频繁的GC动作,垃圾收集在方法区进行的比较少,这是因为方法区主要是针对“常量池的回收”以及“类型的卸载”。如果方法区中无法满足内存分配需求时,将抛出OutOfMemoryError异常。 18 | 19 | ### 2.1.2 堆区 20 | 21 | Java堆是Java虚拟机中所管理内存中最大的一块,Java堆是所有线程都共享的内存区域,虚拟机启动时,Java堆就会被创建。此区域的唯一目的就是存放对象实例,几乎所有对象都在这里进行内存分配(除了Object对象是存放在方法区中的)。另外由于堆内存区域会进行频繁的GC,所以也被称为GC堆。 22 | 从内存回收的角度来看,由于收集器都是基于分代收集算法,所以Java堆区可以细分为新生代和老年代,新生代也还可以细分为Eden空间、From Survivor空间以及To Survivor空间。 23 | 从内存分配的角度来看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(TheadLocal Allocation Buffer,TLAB)。 24 | Java堆内存可以处于物理上不连续的内存空间中,只要逻辑上连续即可。对于Java堆内存空间,如果没法分配到足够的空间,并且堆内存也无法进行再扩展时,将会抛出OutOfMemoryError异常。另外,在程序中设置堆内存可以通过-Xms和-Xmx来进行控制。 25 | 需要注意一点的是,java.lang.Class类的对象比较特殊,它虽然是对象,但其对象实例存放在方法区里的,这个对象将作为程序访问方法区中的这些类型数据的外部接口。 26 | 27 | ### 2.1.3 Java虚拟机栈 28 | 29 | Java虚拟机栈是线程私有的,它的生命周期和线程的生命周期相同。一个方法在执行时会创建一个对应的栈帧,栈帧存储于Java虚拟机栈中。如果方法执行完了,则栈帧就会从Java虚拟机栈中出栈。栈帧中存有“局部变量表”,“操作数栈”,“动态链接”等信息。局部变量表中存放的都是编译期可知的各种基本数据类型,包括了:boolean、byte、char、short、long、int、double、float,还有对象引用等。 30 | 在Java虚拟机规范中,对这个区域规定了两种异常状况: 31 | ① 如果线程请求栈深度大于虚拟机栈所允许的深度,将抛出StackOverFlow异常; 32 | ② 当前大部分虚拟机都是可以动态扩展内存的,如果Java虚拟机扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常。 33 | 34 | ### 2.1.4 本地方法栈 35 | 36 | 本地方法栈和Java虚拟机栈发挥的作用相似,它们之间的区别在于Java虚拟机栈为Java方法服务,而本地方法栈为虚拟机用到的Native方法服务。与Java虚拟机栈一样,本地方法栈区域也会抛出StackOverFlowError和OutOfMemoryError。 37 | 38 | ### 2.1.5 直接内存【非运行时数据区】 39 | 40 | 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。 41 | 在JDK1.4中新加入了NIO(New Input / Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,这种方式可以使用Native函数库直接分配对外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提升性能,因为避免了在Java堆和Native堆中来回复制数据。 42 | 显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。 43 | 44 | # 3、Spring、SpringMVC、MyBatis、SpringBoot四件套 45 | 46 | # 4、消息队列 47 | 48 | # 5、缓存 49 | 50 | # 6、Netty 51 | 52 | # 7、Dubbo 53 | 54 | # 8、分布式 55 | 56 | # 9、Docker 57 | 58 | # 10、K8S 59 | -------------------------------------------------------------------------------- /notes/Java进阶/IO多路复用原理.md: -------------------------------------------------------------------------------- 1 | 2 | - [1. 单线程、多线程](#1-单线程多线程) 3 | - [2. 多路复用](#2-多路复用) 4 | - [2.1 linux select 源码简单分析](#21-linux-select-源码简单分析) 5 | - [2.2 select方法的缺点](#22-select方法的缺点) 6 | - [2.3 linux poll 函数](#23-linux-poll-函数) 7 | - [2.4 linux epoll 函数](#24-linux-epoll-函数) 8 | - [2.5 一句话概括select和epoll](#25-一句话概括select和epoll) 9 | 10 | 11 | 12 | ## 1. 单线程、多线程 13 | 14 | 1. 多线程 => 上下文切换 15 | 2. 单线程 => 单线程不会存在上下文切换 16 | 17 | ## 2. 多路复用 18 | 19 | 比如一台服务器,已经在处理A请求,此时又进来了一个B请求,那么B请求会被丢失吗?答案是在处理IO,也就是去迎接B请求时,会有DMA去处理这个B请求,所以不会丢失。 20 | 21 | 在linux系统中一切都是文件,每个网络连接在linux中都是以文件描述符的形式存在,即:FD。 22 | 23 | 例如在服务器中有5个请求,A、B、C、D、E,那么我们写一个函数去处理这些请求: 24 | 25 | ``` 26 | // 去轮询io,这就是select的核心思想 27 | while (1) { 28 | // fdxs即 [A,B,C,D,E] 29 | for (fdx ind fdxs) { 30 | if (fdx 是否有数据 ) { 31 | 读fdx; 处理; 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | 这个就是通过单线程去判断FD是否有数据,然后去处理。 38 | 39 | 40 | ### 2.1 linux select 源码简单分析 41 | 42 | 1. 程序准备阶段:(准备fd_set数组) 43 | 44 | 首先创建fds,创建文件描述符。fd使用数组来实现。 45 | fd_size 是有限制的,即1024个bitmap(1024位)。 00110101010 0101010 ... 46 | 47 | 2. 程序运行阶段 48 | 49 | 在select源码中,select方法传入了这个bitmap数组。 50 | 51 | select程序运行时,会将用户态中的bitmap数组拷贝到内核态中,然后内核来判断bitmap中哪个fd有数据。【内核态比用户态运行效率更高】 52 | 53 | 如果select程序直接在用户态中对fd进行数据判断,但是用户态的判断最终还是要经过内核态来判断的。所以select方法直接将用户态的。 54 | 55 | 如果fd一直没有数据,则在内核态中,整个select函数会阻塞住。select是一个阻塞函数。 56 | 57 | 即如果fd没有数据,select函数就会被阻塞住。 58 | 59 | 3. 如果有数据来了 60 | ① 如果有数据来了,则会将bitmap中对应的fd进行置位,表示已经有数据了。(可能会置位多个fd) 61 | 62 | ② 会从阻塞的select函数返回; 63 | 64 | ③ 然后读取有数据的那个fd,然后处理。 65 | 66 | 67 | ### 2.2 select方法的缺点 68 | 69 | - fd数组(fd_set)只有1024bitmap 70 | - (fd_set)bitmap不可重用 71 | - 用户态到内核态有开销 72 | - O(n)时间复杂度的轮询 73 | 74 | 75 | ### 2.3 linux poll 函数 76 | 77 | 1. 程序准备阶段:(准备pollfd结构体) 78 | 79 | 相比于数组类型的fd_set,poll定义了一个结构体:poll_fd, 其中包含了 fd 和 events、revents。原理和select一样,都是在用户态中,poll函数将poll_fd拷贝到内核态,然后进行相应操作:判断 fd是否有数据,然后处理 80 | 81 | 2. 程序执行阶段 82 | 83 | ① 有数据进入的话,会将poll_fd结构中的revents进行置位为1,即pollin的状态,表示有数据进来,即设置为有数据,接着从poll方法返回; 84 | 85 | ② 然后poll返回后,判断当前poll_fd结构的revents是否为pollin状态,如果是的话就表示有数据进入,则进行”读数据“和处理数据操作。当然此时revents要置位为0。 86 | 87 | 说明,所以revents置位为0之后,即可继续重用该pollfd结构了。 88 | 89 | 因而,poll解决了select的缺点1和缺点2。 90 | 91 | poll还有如下缺点: 92 | 93 | ① 用户态到内核态的切换存在开销; 94 | 95 | ② O(n)时间复杂度的轮询; 96 | 97 | 98 | 99 | ### 2.4 linux epoll 函数 100 | 101 | 1. epoll_create 程序准备阶段:(准备epoll_fd) 比喻成一个白板 102 | 103 | 程序调用epoll_create创建一个eventpoll对象,也就是epoll_fd代表的对象。创建了的eventpoll会存放在fd列表中。 104 | 105 | 2. epoll_ctl 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上 106 | 107 | 程序调用epoll_ctl来监听一个socket,也就是注册一个文件描述符fd,而注册了的文件描述符fd会被维护在一棵红黑树上,红黑树的搜索、插入和删除时间复杂度为O(logn)。 108 | 109 | 3. epoll_wait 通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符 110 | 111 | 程序调用epoll_wait会监听已注册的文件描述符是否有事件返回,如果有则将有I/O准备好的描述符加入到一个链表中进行管理。 112 | 113 | 不需要轮询,时间复杂度为O(1) 114 | 解决select的1,2,3,4 115 | 116 | 因为epoll中,epoll_fd是将用户态和内核态都进行了共享的。 117 | 118 | ### 2.5 一句话概括select和epoll 119 | 120 | - select 121 | 122 | 类似于一堂考试,select的方式就是老师挨着询问(轮询)同学考卷是否已经完成。 123 | 124 | - epoll 125 | 126 | 类似于一堂考试,epoll的方式就是完成答卷的同学自己举手向老师示意,然后老师上前去收卷子。 127 | 128 | 129 | > 文中有描述不当或者理解错误的地方,请大佬们指出 130 | -------------------------------------------------------------------------------- /notes/Java进阶/零拷贝原理.md: -------------------------------------------------------------------------------- 1 | ## 拷贝是啥? 2 | 3 | 按字面意思理解,就是Ctrl+C和Ctrl+V,即复制、粘贴。 4 | 5 | ## 浅拷贝和深拷贝 6 | 7 | ## 零拷贝 8 | 9 | 大家是否有思考过,一台机器底层是如何将一个文件通过网络传输给另外一台机器的,其底层运作原理是怎样实现的? 10 | 11 | 在操作系统层面,可以描述为如下几步: 12 | 13 | 1. JVM通过OS将文件内容读取到内存中,JVM会向OS发起read系统调用,此时会从用户态切换为内核态; 14 | 2. 文件是存在硬盘中的,磁盘读取了文件的内容后,内核会通过DMA来将文件内容映射到内核地址空间的缓冲区中; 15 | 3. 此时内核缓冲区中的数据会拷贝到用户空间缓冲区,read系统调用返回,此时内核态切换为用户态; 16 | 4. 此时JVM中已经存放有文件中的数据了,就需要通过网络将数据传送到另外一台机器上; 17 | 5. JVM会向OS发起write系统调用,此时会触发上下文切换,从用户态切换为内核态; 18 | 6. OS会将用户缓冲区中的数据拷贝到内核中和目标机器有关的socket缓冲区; 19 | 7. 数据最终经由Socket,通过DMA传送到硬件(如网卡)缓冲区,从而进行下一步的网络通信,write系统调用返回,并从内核态切换为用户态; -------------------------------------------------------------------------------- /notes/MySQL/InnoDB底层原理.md: -------------------------------------------------------------------------------- 1 | 2 | - [1. InnoDB页结构](#1-innodb页结构) 3 | - [2. InnoDB行格式](#2-innodb行格式) 4 | - [3. InnoDB数据页结构](#3-innodb数据页结构) 5 | - [4. B+树索引](#4-b树索引) 6 | - [4.1 InnoDB中B+树索引原理](#41-innodb中b树索引原理) 7 | - [4.2 聚镞索引](#42-聚镞索引) 8 | - [参考](#参考) 9 | 10 | 11 | > 只有学习底层原理才能变得更强 12 | 13 | ## 1. InnoDB页结构 14 | 15 | InnoDB是用于将表中的数据存储到磁盘上的一款存储引擎。 16 | 17 | > 更大维度了解MySQL内部组成结构 18 | 19 | MySQL其实就是一款软件,可以分为客户端、服务器端以及引擎层。服务器端分为了一下几部分: 20 | 21 | > 服务端组成结构 22 | 23 | 1. 连接器 24 | 25 | 连接器用于客户端和服务端进行连接、用户权限校验以及拦截。 26 | 27 | 2. 缓存 28 | 29 | 客户端连接服务端成功后,即可去查询数据。但是MySQL中首先会去查询缓存,如果缓存中查询出了结果,则直接返回缓存的查询结果。否则往下执行。但是缓存在MySQL中比较的鸡肋,因为其缓存命中率非常低, 30 | 对于一些静态表以及读多不怎么写的表才适合缓存。在MYSQL8.0移除了缓存功能。 31 | 32 | 3. 词法分析器 33 | 34 | 词法分析器用于去分析SQL语句是否合法以及去识别SQL语句的关键字等。 35 | 36 | 4. 优化器 37 | 38 | 优化器决定了在表里面有多个索引的时候决定使用哪一个;还有在范围查找的时候是否要使用索引还是不去回表直接全表扫描更快都是优化器所做的。 39 | 40 | 5. 执行器 41 | 42 | 调用引擎结构,获取查询结果集。 43 | 44 | > 引擎层 45 | 46 | 对于MySQL引擎包含了InnoDB,MyISAM,Memory等引擎,而MySQL默认使用的是InnoDB引擎。 47 | 48 | 具体更深入的就不去了解了。下面用一张图来展示MySQL内部组成结构。 49 | 50 | ![https://github.com/coderbruis/Java-Accumulation/blob/maste/notes/pictures/MySQL/MySQL-Component.png](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/pictures/MySQL/MySQL-Component.png) 51 | 52 | 回到InnoDB页的简介,来看下InnoDB页结构以及原理。 53 | 54 | 在MySQL底层,需要通过InnoDB引擎将数据从磁盘中读取到内存中,或者是将内存中的数据持久化到磁盘中,那么在InnoDB中,规定是:** 将数据分为若干个页,然后以页为单位作为磁盘和内存交互的单位。在InnoDB中一个页单位大小一般为16KB。** 55 | 56 | 需要额外提出的地方: 57 | 58 | > 一个页一般是16KB,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出。 59 | 60 | ## 2. InnoDB行格式 61 | 62 | InnoDB将以16KB大小的页来作为内存和磁盘的交互基本单位,那么MySQL中的数据存储到磁盘中,又是以什么格式存储呢?在InnoDB中,设计者设计出了MySQL数据存储在磁盘的格式,也被称为:**行格式** 63 | 64 | 在InnoDB中有4中行格式: 65 | 66 | 1. Compact行格式 67 | 2. Redundant行格式 68 | 3. Dynamic行格式 69 | 4. Compressed行格式 70 | 71 | 每个行格式都分为两部分:记录的额外信息、记录的真实信息。 72 | 73 | > 记录的额外信息 74 | 75 | 在Compact行格式中,记录的额外信息包括了:变长字段长度列表、NULL值列表、记录头信息等信息,然后其他行格式也是差不多的,有些许区别,可通过查资料来确认。 76 | 77 | > 记录的真实信息 78 | 79 | 这个部分就是存储MySQL数据的真实的地方了。 80 | 81 | ## 3. InnoDB数据页结构 82 | 83 | 都知道了InnoDB中是以页作为内存和磁盘交互的基本单位,一个页的大小一般为16KB。在InnoDB中为了不同的目的而设计了许多不同类型的页,下面简单列出了几项: 84 | 85 | - 存放表头空间头部信息的页 86 | - 存放Insert Buffer信息的页 87 | - 存放INODE信息的页 88 | - 存放undo日志信息的页 89 | 90 | 我们详细介绍下存放数据的页,官方称作为:索引(INDEX)页。 91 | 92 | > 在InnoDB数据页结构中,被划分为了7个部分 93 | 94 | 详细看[MySQL 是怎样运行的:从根儿上理解 MySQL](https://juejin.im/book/6844733769996304392/section/6844733770046636046) 95 | 96 | 最重要的就是: 97 | - 用户记录(User Records) 98 | - 页面目录(Page Directory) 99 | - 页面头部(File Header) 100 | - 空闲空间(Free Space) 101 | 102 | 通常,MySQL真实数据是存储在用户记录部分的。但是在一开始生成页的时候,是不存在用户记录部分的,每当插入一条记录时,都会从Free Space分配部分空间给User Records,待Free Space空间分配完了,也就意味着 103 | 当前页使用完了,数据以及填充满了,如果还有新的记录插入的话,就需要去申请新的页了。 104 | 105 | 106 | > 在InnoDB页中的用户记录部分 107 | 108 | 在用户记录部分中,当存有用户数据记录时,都会规定两个伪记录:最小记录(Infimum)和最大记录(supremum),而其他记录都存在最大记录和最小记录中间,通过一个next_record来连接起来的一个:单链表。 109 | 110 | 不管怎么对页中的记录做CRUD,InnoDB都会维护一条记录的单链表,链表中的各个节点是按照主键值小到大的顺序连接起来。注意是从主键开始从大到小!!! 111 | 112 | 总结一下:InnoDB用户记录中,每个记录的头信息都会有一个next_record属性来使得页中所有记录串联成一个单链表。 113 | 114 | > InnoDB的页目录(Page Directory) 115 | 116 | 在InnoDB中,会把页中的记录划分为若干个组,每个组中最后一条记录的偏移量作为一个槽,然后把这个槽存放在页目录中。因此InnoDB在页中通过查询页目录来查询数据是非常快的,具体可以分为两步: 117 | 1. 通过二分法确定该记录所在的槽 118 | 2. 通过记录的next_record属性遍历该槽所在的组中的各个记录 119 | 120 | 需要额外注意的!! 页目录是为主键列创建的!!非主键列是不存在页目录的!! 121 | 122 | > InnoDB的页面头部(File Header) 123 | 124 | 在InnoDB中,每个页面头部都会存放上一页和下一页的编号用于连接上一个数据页(索引页)和下一个数据页(索引页)。 125 | 126 | 127 | 对于InnoDB数据页,总结一下: 128 | 129 | 在InnoDB中,各个数据页可以组成一个双向链表,每个数据页中的记录头部有一个next_record来根据主键值从小到大的顺序组成一个单向链表。每个数据页都会为存储到它边上的记录生成一个"页目录",然后通过主键查找某条记录的时候, 130 | 可以在页目录中使用二分法快速定位到对应的槽,然后再遍历槽对应分组中的记录即可快速找到指定的记录。 131 | 132 | ## 4. B+树索引 133 | 134 | 学过MySQL的开发者都知道,根据索引来查找数据查询效率会非常高,查询速度也会非常快。下面来看看没有走索引的条件,底层是个什么原理。 135 | 136 | 首先对于查询可以分为两种类型:以主键为搜索条件、以其他列作为搜索条件 137 | 138 | > 以主键作为搜索条件 139 | 140 | 对于以主键来搜索条件,在单数据也中,首先是可以在页目录中通过二分查找快速定位到对应的槽,然后遍历槽中分组中的用户记录即可快速查找到记录。 141 | 142 | > 以其他列作为搜索条件 143 | 144 | 对于其他列就没那么幸运了,由于非主键列是没有页目录这一项的,所以就无法通过二分法来快速定位到具体的槽。只能从页中的最小记录开始遍历页中的单链表来查询每条记录。 145 | 146 | 然而!!MySQL中用户记录是非常多的,真实线上的数据页也不可能为一个,而是非常多的数据页,所以对于按主键查找或者是非主键查找数据,都无法绕开要先确定数据在哪个页记录上。在多数据页查找的过程可以分为如下几步: 147 | 148 | 1. 在多个数据页中确定记录所在的页 149 | 2. 根据是主键还是非主键的条件来查找对应的记录 150 | 151 | 在没有索引的情况下,无论条件是根据主键列还是非主键列来查找数据,都需要从头开始遍历由双向链表连接的数据页,然后在进入数据页中去具体的记录(如果是主键列,则可以对通过二分法来确定记录在哪个槽范围,然后再到槽中去遍历记录组的记录),然而非主键列就更惨了,上层遍历了双向链表的数据页,下层还有继续去遍历单链表连接的记录!!!实在是苦逼!! 152 | 153 | 可想而知,没有索引时,在数据量巨大的情况下,查找效率是多么的低下。 154 | 155 | ### 4.1 InnoDB中B+树索引原理 156 | 157 | > 因此在InnoDB中,提供了索引用于快速查询数据。那么在InnoDB底层中索引是如何运行的呢?什么是索引呢? 158 | 159 | 在InnoDB中,会将B+树叶子节点中一个页记录链表头对应的最小主键值记录在一个目录项中,一条**目录项**包含了最小主键值以及该主键值对应的页数,而在B+树叶子节点中会存有多个数据页,并且是作为双向链表连接起来的数据页。 160 | 161 | 当目录项一多,则InnoDB会为其分配一个用于存储目录项的**页**,在前面已经说到,在InnoDB中页是MySQL内存和磁盘交互的基本单位,且页分为了很多类型,而在InnoDB中通过record_type来区分页类型。 162 | 163 | - 0:普通的用户记录 164 | - 1:目录项记录 165 | - 2:最小记录 166 | - 3:最大记录 167 | 168 | 所以用于存储数据页的页record_type类型为0,而用于存储目录项的页类型为1。这点需要大家记住! 169 | 170 | 先看下图目录项和叶子节点的数据页的关系: 171 | 172 | ![innodb_directoryPage](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/pictures/MySQL/innodb_directoryPage.png) 173 | 174 | 而将多个目录项存放在一个页中时,B+树结构如下图所示: 175 | 176 | ![innodb_directoryPage](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/pictures/MySQL/innodb_directoryPage02.png) 177 | 178 | 并且,如果存放由目录项记录的页也变得过多时,可以再为存有目录项的页们建一个**目录**, 这个目录其实和目录项一样存放由两个关键数据: 179 | 180 | - 存放着目录项页中最小的主键值 181 | - 对应的目录项页的页数 182 | 183 | 说起来有点抽象,看下下图就能理解了: 184 | 185 | ![innodb_directoryPage](https://github.com/coderbruis/Java-Accumulation/blob/master/notes/pictures/MySQL/innodb_directoryPage03.png) 186 | 187 | > 小结:就是这样,在B+树叶子节点中存放着众多的数据页,而叶子节点中数据页与数据页之间是通过双向链表来连通的。数据页中的用户真实记录之间是通过单链表来关联起来的。 188 | > InnoDB为了加快数据查询而建立了一个存储目录项的页,而又继续为存储目录项的页建立了又一个目录项页,依次建立起了InnoDB的索引机制!而B+树就是这样一个层数低,但又节点以及叶子节点 189 | > 众多的一棵树。 190 | 191 | ### 4.2 聚镞索引 192 | 193 | 对于聚镞索引有下列两个特点: 194 | 195 | 1. 在页和记录中,是通过记录的主键值来进行排序的,这包括三个方面的含义: 196 | - 页内的记录是按照主键的大小顺序排成了一个单向链表 197 | - 各个存放用户记录的页是根据用户记录中的主键值大小排序成一个双向链表 198 | - 存放目录项记录的页分为了不同层此,在同一层次中不同目录项页之间也是根据目录项中的主键值大小顺序排列成的一个双向链表 199 | 2. B+树的叶子节点存储的是完整的用户记录 200 | 所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。 201 | 202 | 我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。这种聚簇索引并不需要我们在MySQL语句中显式的使用INDEX语句去创建(后边会介绍索引相关的语句),InnoDB存储引擎会自动的为我们创建聚簇索引。另外有趣的一点是,在InnoDB存储引擎中,聚簇索引就是数据的存储方式(所有的用户记录都存储在了叶子节点),也就是所谓的索引即数据,数据即索引。 203 | 204 | ### 4.3 如何正确使用索引呢? 205 | 206 | 深入InnoDB索引底层原理后,也要浅出总结下B+树索引适用于哪些情况: 207 | 208 | 1. 全值匹配:与索引中的所有列进行匹配,也就是条件字段与联合索引的字段个数与顺序相同; 209 | 2. 匹配最左前缀:只使用联合索引的前几个字段; 210 | 3. 匹配列前缀:比如like 'xx%'可以走索引; 211 | 4. 匹配范围值:范围查询,比如>,like等; 212 | 5. 匹配某一列并范围匹配另外一列:精确查找+范围查找; 213 | 6. 只访问索引查询:索引覆盖,select的字段为主键; 214 | 215 | > 那哪些情况是不走索引的呢? 216 | 217 | 那么SQL语句什么情况下是不走索引的呢? 218 | 219 | 1. SQL语句中包含了or的不走索引; 220 | 2. SQL语句中模糊查询like使用了前缀'%xx'不走索引; 221 | 3. SQL语句中包含了运算或者函数; 222 | 4. SQL语句中列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引; 223 | 224 | ### 4.4 浅谈InnoDB的二级索引(辅助索引)以及联合索引等概念 225 | 226 | 在聚镞索引中,叶子节点是存储这用户记录的数据页,用户记录包含了主键,还有其他列值,但是数据页内是按照主键大小顺序排列成一个单链表。在InnoDB中,如果需要将其他非主键列定义为索引,则会定义成辅助索引。 227 | 228 | 辅助索引定义如下: 229 | 230 | 1. 辅助索引结构和聚镞索引类似,都是包含着目录项页以及数据页,其中叶子节点就是数据页; 231 | 2. 相比于聚镞索引,辅助索引数据页中的用户记录只存储两个值,一个是指定作为索引的列值,另外一个是主键值,数据页中数据是按照这个指定的列值来排列而成的单向链表,同样的数据页之间也是按照数据列值的顺序排列成一个双向链表; 232 | 3. 对于查询走辅助索引的情况,是需要回表,这个回表指的是虽然查询走的是辅助索引,但是查询到指定辅助索引数据页记录中的主键值,然后再按照这个主键值去走一遍聚镞索引; 233 | 234 | 对于辅助索引的回表操作,是需要消耗性能的,所以查询走辅助索引最终还是要走聚镞索引的。 235 | 236 | > 联合索引 237 | 238 | 对于联合索引,比如主键是:a,其他列:b、c 239 | 240 | 此时需要建立联合索引:b、c 241 | 242 | 其实对于联合索引,和辅助索引底层是相似的,即联合索引数据页中存储的是a、b、c散列数据。 243 | 244 | ### 4.5 InnoDB中的页分裂 245 | 246 | 对于InnoDB底层中,一个页大小为16KB,一个页总有用完的时候,当一个页中内存使用完时,即会发生页分裂操作,即将一个页中装不完的用户数据分到另外一个新开的数据页中。 247 | 248 | 而对于页分裂操作,也是需要消耗性能的,所以在InnoDB的聚镞索引中,建议让主键拥有AUTO_INCREMENT属性。 249 | 250 | ## 参考 251 | 252 | 强烈推荐下面的掘金小测,MySQL底层讲解的非常透彻!!! 253 | 254 | - [MySQL是怎样运行的:从根儿上理解 MySQL](https://juejin.im/book/6844733769996304392/section/6844733770046636046) 255 | - 《MySQL技术内幕-InnoDB存储引擎》 256 | 257 | 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /notes/MySQL/深入学习InnoDB可重复读隔离级别下如何避免幻读.md: -------------------------------------------------------------------------------- 1 | 2 | - [一、InnoDB可重复读隔离级别下如何避免幻读](#一innodb可重复读隔离级别下如何避免幻读) 3 | - [二、RR级别下的InnoDB的(快照读)非阻塞读是如何实现的?](#二rr级别下的innodb的快照读非阻塞读是如何实现的) 4 | - [三、InnoDB如何在RR隔离界别下避免幻读——next-key锁](#三innodb如何在rr隔离界别下避免幻读next-key锁) 5 | - [3.1 行锁](#31-行锁) 6 | - [3.2 Gap锁](#32-gap锁) 7 | - [四、总结](#四总结) 8 | - [4.1 InnoDB在RR隔离级别下是如何实现幻读问题的解决的呢?](#41-innodb在rr隔离级别下是如何实现幻读问题的解决的呢) 9 | - [4.2 InnoDB中非阻塞读(快照读)底层是怎么实现的?](#42-innodb中非阻塞读快照读底层是怎么实现的) 10 | 11 | 12 | ## 一、InnoDB可重复读隔离级别下如何避免幻读 13 | 14 | 在理解什么是幻读之前,先了解下脏读、幻读、不可重复读在实操场景中的现象。 15 | 16 | **脏读**:指的就是一个事务读取到了另一个事务还未提交的数据,当该事物将数据回滚,则读取到的就是脏数据。 17 | **脏读造成的结果**:事务拿着脏的数据(还未提交的数据,如果回滚了)去执行业务操作,会影响业务。 18 | **脏读解决方案**:将数据库事务隔离级别改为RC,所以事务只能读取到其他事务已经提交的数据。 19 | 20 | 21 | **不可重复读**:指的是对于两个事务A、B,A事务进行查询操作,B事务进行更新操作,更新了数据,并且提交了B事务。此时A事务再次来查询该记录,会发现和之前查询的结果不一样了,以A事务的角度来说,A事务什么都没操作(也不知道其他事务是否有操作)就发生了数据改变,即两次读是不重复的,这就代表了不可重复读。 22 | **不可重复读造成的结果**:同一事务中两次读取的结果不一样。 23 | **不可重复读解决方案**:解决方案就是将事务隔离级别由RC提升为RR。如果将事务隔离级别提升为了RR,则不管B事务如何更新数据,A事务中读取的数据都是相同的,另外完全不用担心在A事务中进行操作,会造成数据不一致的问题,因为在A事务中如果进行了数据的修改操作,会使用的B事务更新之后的数据来进行修改操作。 24 | 25 | 26 | **幻读**:(幻读的复现需要将事务隔离级别降低为RC,因为在InnoDB中RR已经解决了幻读现象)同样的对于两个事务A、B。现在数据库表中有3条记录,A事务将要将更新所有的记录,于此同时B事务新增了一条记录,并提交了事务。之后A事务执行了update操作,会发现成功操作4条记录,从A的角度来说,我什么都没做,怎么就变成了4条记录了呢?这就是幻读现象。【幻读侧重于对于记录的增加以及删除现象,而不可重复读侧重于记录本身的数据】 27 | **幻读造成的结果**:同一事务中记录条数不同,产生幻觉。 28 | **幻读解决方案**:InnoDB的伪MVCC机制。 29 | 30 | 31 | **表象**:快照读(非阻塞读)--伪MVCC 32 | **内在**:next-key锁(行锁+GAP锁也就是间隙锁) 33 | 34 | 什么是当前读呢?可以简单理解为,加了锁的增删改查就是当前读。 35 | 36 | - 当前读(查询):select...lock in share mode,select...for update 37 | 38 | - 当前读(增、删、改):update、insert、delete 39 | 40 | 不管上的是X锁(排他锁)还是S锁(共享锁)都为当前读。当前读是啥意思呢?意思是当前操作的是最新记录,其他的并发事务不能修改当前记录,对当前记录加锁。其中,select...lock in share mode是使用的S锁,select...for update、update、insert、delete都是使用的X锁。 41 | 42 | 共享锁和排他锁的区别: 43 | 44 | - 共享锁(s):又称读锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。 45 | - 排他锁(X):又称写锁。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。 46 | 47 | 使用下图理解当前读在MySQL内部运行机制 48 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114052693.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 49 | 50 | 51 | 而快照读是什么呢?快照读就是不加锁的非阻塞读,也就是select操作。不过这里的快照读是基于非Serializable事务隔离界别下的,因为在Serializable隔离级别下,快照度会退化为当前读。这里的快照读就是MVCC的实现机制。 52 | 53 | 这里由于篇幅原因,没有贴出实验过程,这里做简单总结: 54 | 55 | 1. 在RC隔离界别下,当前读和快照读读取的都是同一版本。 56 | 2. 在RR隔离界别下,当前读读到的是最新版本数据,而快照度可能读取到的是历史版本数据。 57 | 58 | 那么在RR隔离级别下,什么时候可以读到最新版本数据呢?如果在进行增、删、改完成之后,再去查询快照读,则此时读取到的是最新版本的数据。如果是在增、删、改之前进行了快照读,在增、删、改之后继续快照读,则读到的就是旧版本数据。 59 | 60 | **总结:快照读取决于一开始快照读的时间。** 61 | 62 | ## 二、RR级别下的InnoDB的(快照读)非阻塞读是如何实现的? 63 | 底层实现离不开数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段,除此之外还需要undo日志,以及read view。 64 | 65 | 原理实现就是下列几个关键内容: 66 | 67 | - 数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID 68 | - undo日志 69 | - read view机制 70 | 71 | 说起DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID,那就要先知道MySQL一条记录是由记录的额外信息部分和记录的真实数据两部分组成。记录的额外记录部分存有变长字段长度列表、NULL值列表等,而记录的真实数据部分又由真实数据以及DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID这三个隐藏列组成。 72 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114157496.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 73 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114218704.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 74 | 75 | 比如现在有一个记录Field1、Field2、Field3数据分别为11、12、13,现在事务要修改该记录,将Field2修改为32。则这条记录首先会加载X锁,首先undo log中会拷贝一条修改前的记录,并赋值DB_ROW_ID。此时被X锁锁住的记录的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID分别进行赋值,并且DB_ROLL_PTR的记录会指向undo log中的DB_ROW_ID的值。 76 | 77 | 如果此时又有一个事务对该记录进行了修改,则undo log日志中又会增加一条日志。 78 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114246913.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 79 | 80 | 这样就是快照读版本的实现了。 81 | 82 | ## 三、InnoDB如何在RR隔离界别下避免幻读——next-key锁 83 | 其实,真正实现RR隔离级别下的幻读现象,是由next-key锁解决的。next-key锁又分为了(行锁 + gap锁) 84 | 85 | ### 3.1 行锁 86 | 87 | 行锁就是Record Lock,就是对单个行记录加的锁。X锁和S锁就是行锁。 88 | 89 | ### 3.2 Gap锁 90 | Gap就是索引树中,插入新数据的间隙。间隙锁即锁定一个记录的范围,但是不锁定记录本身。间隙锁是为了避免同一事务的两次当前读出现幻读的情况。需要注意的是,Gap锁在RU、RC隔离级别下时不存在的,在RR、Serializable隔离级别下都只支持Gap锁。这就是为什么RU、RC隔离级别下无法避免幻读,RR、Serializable能够避免幻读的原因。 91 | 92 | 下面讨论的都是在RR隔离级别下出现Gap锁的场景。 93 | 94 | 1. 在RR隔离级别下,无论删、改、查,当前读若用到主键索引或者唯一键索引,会使用Gap锁吗? 95 | 答:如果where条件全部命中,则不会用Gap锁,只会加记录锁。 96 | 97 | 怎么去理解where条件全部命中,不用加Gap锁只需要加记录锁就行了呢?这是因为比如A事务需要修改操作所有记录,此时B事务使用主键索引id进行where条件查询、删除操作,此时只需要锁住where命中的id记录即可,那么就能防止事务A出现幻读现象。 98 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114311395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 99 | 100 | 如图,tb中name为主键索引,id为唯一索引。某个事务使用delete from tb where id = 9进行删除操作,首先where条件全部命中,所以先会为id为9的这个记录的唯一索引加上行锁,然后会为name为d的主键索引(聚镞索引)加上排他锁。这是为了防止其他事务对where name = d进行操作,导致数据不一致的情况。 101 | 102 | 2. 在RR隔离级别下,无论删、改、查,当前读若用到主键索引或者唯一键索引,且如果where条件部分命中或者全不命中,则会加Gap锁。对于这种情况,就包含了范围查询以及精确查询非全部命中的情况。 103 | 例子1:比如现在事务A要删除一条不存在的id为7的记录,此时事务B要新增一条id为8的记录,会发现事务B一直处于等待中,这是因为精准查询全部都不命中,会对该记录范围加Gap锁。 104 | 105 | 例子2:【tb_student中存在id为5,6,9的学生】比如在事务A中使用语句select * from tb_student where id in (5,7,9) lock in share mode;使用当前读(共享锁)来查询学生信息。在另外一个事务B中去进行新增id为6,7,8的学生,发现事务一直在等待中。这里是因为where id in (5,7,9)部分命中,所以会为(5,9]加Gap锁,锁的范围为左开右闭。因此事务B新增id为7,8的记录会被Gap锁锁住,这就是精准查询不全部命中的情况。 106 | 107 | 3. Gap锁会用在非唯一索引或者不走索引的当前读中 108 | **非唯一索引** 109 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114422831.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 110 | 111 | 比如图中某一事务A执行delete from tb1 where id = 9,因为id是非唯一索引,如果没有加Gap锁,在事务B新增一条id为9的记录时,A事务执行完delete语句后,就会发现成功删除3条记录,出现了幻觉,所以给id为9的记录加上Gap锁来防止幻读的发生。 112 | 至于Gap锁的范围,如上为:(-∞,2], (2, 6], (6, 9], (9, 11], (11, 15], (15, +∞)中的 (6, 9], (9, 11] 113 | 114 | **不走索引** 115 | 116 | 对于不走索引的情况,InnoDB会为所有的Gap加锁,相当于锁表。 117 | 118 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191025114453347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 119 | 120 | ## 四、总结 121 | ### 4.1 InnoDB在RR隔离级别下是如何实现幻读问题的解决的呢? 122 | 1. 表象:快照读(非阻塞读),伪MVCC 123 | 2. 底层:next-key(行锁+Gap锁) 124 | **a.** 在RU、RC隔离级别下不存在Gap锁,所以在RU、RC隔离级别下无法解决幻读;在RR、Serializable隔离级别下都实现了Gap锁,所以解决了幻读现象。 125 | **b.** 在RR隔离级别下,如果删、改、查语句的where条件走的是主键索引或者唯一索引 126 | **i.** where条件全部命中,则给该记录加上记录锁。 127 | **ii.** where条件不全部命中,则给该记录周围加上Gap锁。 128 | **iii.** 加上记录锁或者是Gap锁都是为了防止RR隔离级别下发生幻读现象。 129 | **c.** 在RR隔离级别下,如果删、改、查语句的where条件没有走索引或者是非唯一索引或非主键索引 130 | 在当前读where条件如果没有走非唯一索引或者没有走索引,则会使用Gap锁锁住当前记录的Gap,防止幻读的发生 131 | 132 | ### 4.2 InnoDB中非阻塞读(快照读)底层是怎么实现的? 133 | 1. 记录中存储的隐藏列DB_TRX_ID、DB_ROW_ID、DB_ROLL_ID 134 | 2. undo日志根据上述隐藏列来进行记录数据回滚(版本回滚) 135 | 3. review机制 136 | 137 | 参考文献: 138 | 139 | - 《剑指Java面试-Offer直通车》 140 | - 《掘金小册——MySQL是怎样运行的》 141 | -------------------------------------------------------------------------------- /notes/Redis/Redis基础数据结构.md: -------------------------------------------------------------------------------- 1 | 在Redis中,有五中基础数据结构,包括了: 2 | 3 | - string(字符串) 4 | - list(列表) 5 | - hash(字典) 6 | - set(集合) 7 | - zset(有序集合) 8 | 9 | ## 1. 字符串String 10 | 11 | Redis中字符串是一种"动态字符串",意味着使用者可以修改,字符串在Redis底层中有点类似于Java中的ArrayList,用一个字符数组来存储数据。在Redis底层C语言中,对于字符串这个中数据结构,定义了一个"Simple Dynamic String"。 12 | 13 | ### 1.1 常用操作命令 14 | 15 | 在Redis中,String数据结构的常用命令为:SET、GET。 16 | 17 | 值可以是任何类型的字符串,也包括了二进制数据。 18 | 19 | 常用操作列在下列: 20 | 21 | - set 22 | 23 | 设置一个key对应的value值 24 | 25 | - get 26 | 27 | 获取一个key对应的value值 28 | 29 | - exists 30 | 31 | 判断一个key是否存在 32 | 33 | - expire 34 | 35 | 设置一个键过期,通常用法为:expire key_name second 36 | 37 | - del 38 | 39 | 删除一个key 40 | 41 | - mset 42 | 43 | 批量设置 44 | 45 | - mget 46 | 47 | 批量获取 48 | 49 | - setnx 50 | 51 | 判断如果key不存在,则设置set成功,否则设置key失败。成功返回1,失败返回0。 52 | 53 | 54 | ## 2. 列表list 55 | 56 | 在Redis中,列表list相当于Java中的LinkedList链表结构。因为是链表结构,意味着list的插入和删除操作都非常快,时间复杂度为O(1),但是查询数据则非常慢,需要遍历整个链表结构,时间复杂度为O(n)。 57 | 58 | ### 2.1 列表的常用操作 59 | 60 | - lpush和rpush 61 | 62 | lpus表示的是从list的左边添加一个新的元素;rpush表示从list的右边添加一个新的元素; 63 | 64 | - lrange 65 | 66 | lrange表示从list的左边一定范围取出元素;rrange表示从list的右边获得一定范围的元素; 67 | 68 | - lindex 69 | 70 | lindex命令表示从list中取出指定下标的元素, 即相当于从Java链表操作中的get()方法; 71 | 72 | 73 | ## 3. 字典Hash 74 | 75 | 在Redis中,Redis的hash相当于Java中的HashMap,内部实现也差不多类似,就是通过"数组+链表"的链地址法来解决"哈希冲突"。不过实际上,Redis的哈希底层是包含两个哈希table的。 76 | 77 | ### 3.1 字典的常用操作 78 | 79 | - hset 80 | 81 | 像字典中添加元素; 82 | 83 | - hget 84 | 85 | 从字典中获取元素; 86 | 87 | - hgetall 88 | 89 | 从字典中获取所有元素; 90 | 91 | - hmset 92 | 93 | 向字典中批量设置元素; 94 | 95 | 96 | ## 4. 集合Set(无序) 97 | 98 | Redis 的集合相当于 Java 语言中的 HashSet,它内部的键值对是无序、唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。 99 | 100 | ## 5. 有序列表(SortedSet) 101 | 102 | ## 6. HyperLogLog 103 | 104 | HyperLogLog是在Redis中用来计算基数统计的一种数据结构,在输入元素的数量或者体积非常非常大时,通过HyperLogLog可以以非常小的内存来存储计算结果。 105 | 106 | # 7.(布隆过滤器)Bloom Filter 107 | 108 | 布隆过滤器用于检索一个元素是否在一个集合中。优点就是空间效率和时间效率都远远地超过一般的算法,缺点就是有一定的误识别率和删除困难。 109 | 110 | -------------------------------------------------------------------------------- /notes/Redis/Redis面试题.md: -------------------------------------------------------------------------------- 1 | ### 1. Redis为什么这么快? 2 | 3 | ### 2. Redis中有哪些数据结构?你了解哪些数据结构? 4 | 5 | ### 3. Redis中除了缓存外,还有哪些应用场景? 6 | 7 | ### 4. Redis中的缓存雪崩、穿透、击穿都是什么?该如何解决? 8 | 9 | #### 4.1 雪崩效应有哪些?和缓存雪崩有啥区别? 10 | 11 | 雪崩效应常见场景 12 | 13 | - 硬件故障:如服务器宕机,机房断电,光纤被挖断等。 14 | - 流量激增:如异常流量,重试加大流量等。 15 | - 缓存穿透:一般发生在应用重启,所有缓存失效时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端服务,造成服务提供者超负荷运行,引起服务不可用。 16 | - 程序BUG:如程序逻辑导致内存泄漏,JVM长时间FullGC等。 17 | - 同步等待:服务间采用同步调用模式,同步等待造成的资源耗尽。 18 | 19 | 雪崩效应应对策略 20 | 21 | 针对造成雪崩效应的不同场景,可以使用不同的应对策略,没有一种通用所有场景的策略,参考如下: 22 | 23 | - 硬件故障:多机房容灾、异地多活等。 24 | - 流量激增:服务自动扩容、流量控制(限流、关闭重试)等。 25 | - 缓存穿透:缓存预加载、缓存异步加载等。 26 | - 程序BUG:修改程序bug、及时释放资源等。 27 | - 同步等待:资源隔离、MQ解耦、不可用服务调用快速失败等。资源隔离通常指不同服务调用采用不同的线程池;不可用服务调用快速失败一般通过熔断器模式结合超时机制实现。 28 | 29 | 综上所述,如果一个应用不能对来自依赖的故障进行隔离,那该应用本身就处在被拖垮的风险中。 因此,为了构建稳定、可靠的分布式系统,我们的服务应当具有自我保护能力,当依赖服务不可用时,当前服务启动自我保护功能,从而避免发生雪崩效应。本文将重点介绍使用Hystrix解决同步等待的雪崩问题。 30 | 31 | ### 5. 如何保证缓存与数据库数据一致性? 32 | 33 | ### 6. 在实时性要求比较高的缓存如何保证和数据库中数据的一致性? 34 | 35 | ### 7. Redis中的过期机制有哪几种?底层是如何实现的? 36 | 37 | ### 8. Redis的部署方式都有哪些方式? 38 | 39 | ### 9. Redis的cluster和Setinel实现原理是什么? 40 | 41 | ### 10. Redis中的跳表原理是怎样的? 42 | 43 | ### 11. Redis的主从同步机制是如何实现的? 44 | 45 | ### 12. Redis中持久化方式有哪些?该如何使用? 46 | 47 | ### 13. Redis是如何实现分布式锁的? 48 | 49 | -------------------------------------------------------------------------------- /notes/pictures/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/.DS_Store -------------------------------------------------------------------------------- /notes/pictures/Java-T-Shaped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/Java-T-Shaped.png -------------------------------------------------------------------------------- /notes/pictures/Java基础/Java异常类层次结构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/Java基础/Java异常类层次结构图.png -------------------------------------------------------------------------------- /notes/pictures/MySQL/MySQL-Component.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/MySQL/MySQL-Component.png -------------------------------------------------------------------------------- /notes/pictures/MySQL/innodb_directoryPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/MySQL/innodb_directoryPage.png -------------------------------------------------------------------------------- /notes/pictures/MySQL/innodb_directoryPage02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/MySQL/innodb_directoryPage02.png -------------------------------------------------------------------------------- /notes/pictures/MySQL/innodb_directoryPage03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/MySQL/innodb_directoryPage03.png -------------------------------------------------------------------------------- /notes/pictures/dubbo/dubbo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbruis/Java-T/1863ca4148e2eef6e71fd0f97efab25d21942606/notes/pictures/dubbo/dubbo.png -------------------------------------------------------------------------------- /notes/框架/SpringAOP面试.md: -------------------------------------------------------------------------------- 1 | 2 | # 1. 基础概念 3 | 4 | ## 1.1 描述一下SpringAOP 5 | 6 | Spring AOP(Aspect Oriented Programming,面向切面编程)是OOPs(面向对象编程)的补充,它也提供了模块化。在面向对象编程中,关键的单元是对象,AOP的关键单元是切面,或者说关注点(可以简单地理解为你程序中的独立模块)。一些切面可能有集中的代码,但是有些可能被分散或者混杂在一起,例如日志或者事务。这些分散的切面被称为横切关注点。一个横切关注点是一个可以影响到整个应用的关注点,而且应该被尽量地集中到代码的一个地方,例如事务管理、权限、日志、安全等。 7 | AOP让你可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。这让代码在当下和将来都变得易于维护。如果你是使用XML来使用切面的话,要添加或删除关注点,你不用重新编译完整的源代码,而仅仅需要修改配置文件就可以了。 8 | Spring AOP通过以下两种方式来使用。但是最广泛使用的方式是Spring AspectJ 注解风格(Spring AspectJ Annotation Style) 9 | 10 | - 使用AspectJ 注解风格 11 | - 使用Spring XML 配置风格 12 | 13 | ## 1.2 在Spring AOP中关注点和横切关注点有什么不同 14 | 15 | 关注点是我们想在应用的模块中实现的行为。关注点可以被定义为:我们想实现以解决特定业务问题的方法。比如,在所有电子商务应用中,不同的关注点(或者模块)可能是库存管理、航运管理、用户管理等。 16 | 横切关注点是贯穿整个应用程序的关注点。像日志、安全和数据转换,它们在应用的每一个模块都是必须的,所以他们是一种横切关注点。 17 | 18 | ## 1.3 AOP有哪些可用的实现 19 | 20 | 基于Java的主要AOP实现有: 21 | 22 | 1. AspectJ 23 | 2. Spring AOP 24 | 3. JBoss AOP 25 | 26 | ## 1.4 Spring中有哪些不同的通知类型 27 | 28 | 通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型: 29 | 30 | 1. 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before 注解使用这个Advice。 31 | 2. 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning 关注使用它。 32 | 3. 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing 注解来使用。 33 | 4. 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After 注解使用。 34 | 5. 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around 注解使用。 35 | 36 | ## 1.5 Spring AOP 代理是什么? 37 | 38 | 代理是使用非常广泛的设计模式。简单来说,代理是一个看其他像另一个对象的对象,但它添加了一些特殊的功能。 39 | Spring AOP是基于代理实现的。AOP 代理是一个由 AOP 框架创建的用于在运行时实现切面协议的对象。 40 | Spring AOP默认为 AOP 代理使用标准的 JDK 动态代理。这使得任何接口(或者接口的集合)可以被代理。Spring AOP 也可以使用 CGLIB 代理。这对代理类而不是接口是必须的。 41 | 如果业务对象没有实现任何接口那么默认使用CGLIB。 42 | 43 | 总结: 44 | 45 | - JDK动态代理适合于接口形式的AOP 46 | - CGLIB代理适合于没有任何接口的 47 | - 每个Bean都会被JDK或者Cglib代理。取决于是否有接口。 48 | 49 | 50 | ## 1.6 连接点(Joint Point)和切入点(Point cut)是什么? 51 | 52 | 连接点是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。举例来说,所有定义在你的 EmpoyeeManager 接口中的方法都可以被认为是一个连接点,如果你在这些方法上使用横切关注点的话。 53 | 切入点(切入点)是一个匹配连接点的断言或者表达式。Advice 与切入点表达式相关联,并在切入点匹配的任何连接点处运行(比如,表达式 execution(* EmployeeManager.getEmployeeById(...)) 可以匹配 EmployeeManager 接口的 getEmployeeById() )。由切入点表达式匹配的连接点的概念是 AOP 的核心。Spring 默认使用 AspectJ 切入点表达式语言。 54 | 55 | ## 1.7 什么是织入(weaving)? 56 | 57 | Spring AOP 框架仅支持有限的几个 AspectJ 切入点的类型,它允许将切面运用到在 IoC 容器中声明的 bean 上。如果你想使用额外的切入点类型或者将切面应用到在 Spring IoC 容器外部创建的类,那么你必须在你的 Spring 程序中使用 AspectJ 框架,并且使用它的织入特性。 58 | 织入是将切面与外部的应用类型或者类连接起来以创建通知对象(adviced object)的过程。这可以在编译时(比如使用 AspectJ 编译器)、加载时或者运行时完成。Spring AOP 跟其他纯 Java AOP 框架一样,只在运行时执行织入。在协议上,AspectJ 框架支持编译时和加载时织入。 59 | AspectJ 编译时织入是通过一个叫做 ajc 特殊的 AspectJ 编译器完成的。它可以将切面织入到你的 Java 源码文件中,然后输出织入后的二进制 class 文件。它也可以将切面织入你的编译后的 class 文件或者 Jar 文件。这个过程叫做后编译时织入(post-compile-time weaving)。在 Spring IoC 容器中声明你的类之前,你可以为它们运行编译时和后编译时织入。Spring 完全没有被包含到织入的过程中。更多关于编译时和后编译时织入的信息,请查阅 AspectJ 文档。 60 | AspectJ 加载时织入(load-time weaving, LTW)在目标类被类加载器加载到JVM时触发。对于一个被织入的对象,需要一个特殊的类加载器来增强目标类的字节码。AspectJ 和 Spring 都提供了加载时织入器以为类加载添加加载时织入的能力。你只需要简单的配置就可以打开这个加载时织入器。 61 | 62 | ## 1.8 什么是动态代理?什么是静态代理?二者有什么区别? 63 | 64 | 对于动态代理和静态代理总结为如下几点(网上搜的,还没具体去研究) 65 | 66 | 1. 代理分为静态代理和动态代理两种。 67 | 2. 静态代理,代理类需要自己编写代码写成。 68 | 3. 动态代理,代理类通过 Proxy.newInstance() 方法生成。 69 | 4. 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。 70 | 5. 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。 71 | 6. 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。 72 | 7. 代理模式本质上的目的是为了增强现有代码的功能。 73 | 74 | # 2. 底层原理 75 | 76 | ## 2.1 77 | -------------------------------------------------------------------------------- /notes/框架/SpringBoot面试.md: -------------------------------------------------------------------------------- 1 | ## 1. SpringBoot系统初始化器解析 2 | 3 | 系统初始化器名称为:ApplicationContextInitializer 4 | 5 | 它其实就是Spring容器刷新之前执行的一个回调函数。 6 | 7 | > 作用:向SpringBoot容器中注册属性 8 | 9 | > 使用方式: 10 | 11 | 1. 实现ApplicationContextInitializer 12 | 2. @Order值 13 | 3. application.properties中定义的优先于其他方式 14 | 15 | ## 2. SpringFactoriesLoader 16 | 17 | 作用: 18 | 1. 框架内部使用的通用工厂加载机制 19 | 2. 从classpath下多个jar包特定的位置读取特定文件扩展类,并初始化该类 20 | 3. 文件内容必须是K/V形式的,即properties类型 21 | 4. 读取的是classpath路径下的spring.factories 22 | 23 | ## 3. 监听器模式 24 | 25 | 监听器发送顺序: 26 | 27 | 1. 框架启动 28 | 2. 发送starting事件,表明启动框架 29 | 3. 发送environmentPrepared事件,这个时间表示环境属性准备完毕 30 | 4. 发送contextInitialized时间,表示容器已经初始化 31 | 5. 发送started事件,表示SpringBoot已经加载完bean了 32 | 6. 发送ready事件,表示ApplicationRunner和CommandRunner运行完后的事件 33 | 7. 启动完毕 34 | 35 | 监听器原理: 36 | 1. 框架启动 37 | 2. 调用SimpleApplicationEventMulticaster#getApplicationListeners方法获取监听器 38 | 3. 遍历监听器然后调用supportsEvent是否是需要监听的事件 39 | 4. 调用onApplicationEvent调用监听事件的逻辑 40 | 41 | ## 4. 动手搭建starter 42 | 43 | 首先先介绍下SpringBoot的starter 44 | 1. starter是一个可插拔的插件 45 | 2. 与jar包类似,不过和jar包单纯导入class不同,starter还可以实现自动配置 46 | 3. starter能大幅度提升开发效率 47 | 48 | > 新建SpringBoot的starter步骤 49 | 50 | 1. 新建一个SpringBoot类 51 | 2. 引入spring-boot-autoconfigure 52 | 3. 编写属性源以及自动配置类(@ConditionOnXXX) 53 | 4. 在spring.factories中添加自动配置类的实现 54 | 5. maven打包进本地maven仓库 55 | 56 | > 使用自定义SpringBoot的starter步骤 57 | 58 | 1. pom.xml中引入新建的starter 59 | 2. 属性properties文件中设置属性开关,决定是否引入starter 60 | 3. 可以直接用了 61 | 62 | > SpringBoot starter原理解析 63 | 64 | starter自动配置类导入: 65 | 1. 启动类上的@SpringBootApplication 66 | 2. 引入AutoConfigurationImportSelector 67 | 3. ConfigurationClassParser中处理 68 | 4. 获取spring.factories中EnableAutoConfiguration实现 69 | 70 | starter自动配置类: 71 | 1. @ConditionalOnProperty 72 | 2. OnPropertyCondition 73 | 3. getMatchOutCome 74 | 4. 遍历注解属性集判断environment中是否含有并且值是一直的 75 | 5. 返回对比结果 -------------------------------------------------------------------------------- /notes/框架/Spring面试.md: -------------------------------------------------------------------------------- 1 | ## 1. Spring框架中用到了哪些设计模式? 2 | 3 | 1. 工厂模式 4 | 5 | 在Spring中,BeanFactory就是工厂模式的实现,Spring通过BeanFactory来创建Bean。 6 | 7 | 2. 单例模式 8 | 9 | 在Spring中默认bean均为单例的。 10 | 11 | 3. 适配器模式 12 | 13 | 在Spring的AOP中,通知(Advice)就是通过适配器模式来实现的。AdvisorAdapter。 14 | 15 | 4. 包装器模式 16 | 17 | 5. 代理模式 18 | 19 | 在Spring的AOP中,JDKDynamicAopProxy和Cglib2AopProxy都使用到了代理模式。 20 | 21 | 6. 观察者模式 22 | 23 | 在Spring中的监听器,就是使用到了观察者模式,例如ApplicationListener。 24 | 25 | 7. 策略模式 26 | 27 | 8. 模板方法模式 28 | 29 | 在Spring中的JDBCTemplate就是用到了模板方法模式。 30 | 31 | ## 2. 使用Spring框架有什么好处? 32 | 33 | - 轻量:Spring是轻量级的,基本的版本大于2MB 34 | - 控制反转:Spring的核心思想,通过控制反转来实现松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们 35 | - 面向切面编程(AOP):Spring支持面向切面编程,并且把应用业务逻辑和系统服务逻辑分开,实现自己的逻辑增强 36 | - SpringIOC容器:Spring提供了SpringIOC容器,用于管理应用中对象的生命周期和配置 37 | - MVC框架:Spring提供了WEB的MVC框架 38 | - 事务管理:Spring提供了一个持续的事务管理接口,可以扩展到上至本地事务,下至全局事务(JTA) 39 | - 异常处理:Spring提供了方便的API把具体的技术相关的异常转化为统一的unchecked异常 40 | 41 | ## 3. SpringIOC核心容器模块 42 | 43 | 在Spring中,IOC容器是整个框架的基础,而BeanFactory是任何以Spring为基础的应用的核心,提供了创建bean,管理bean,获取bean的核心API。Spring应用建立在IOC容器之上。 44 | 45 | BeanFactory是工厂模式的一个实现,它给Spring提供了控制反转功能,用来把应用的配置和依赖从代码中分离出来。BeanFactory最常见的实现类是:XMLBeanFactory。 46 | 47 | > ApplicationContext 48 | 49 | ApplicationContext和BeanFactory都是接口,都是用于加载Bean的,但是ApplicationContext相比于BeanFactory,它在BeanFactory的基础工鞥之上还提供了更多扩展功能,而ApplicationContext更适用于企业级开发。 50 | 51 | ## 4. 一个Spring Bean的定义包含什么?如何给Spring提供配置元数据? 52 | 53 | 一个Spring Bean(BeanDefinition)包含了容器必知的所有"配置元数据"。在Spring中有三种方式给Spring容器提供配置元数据: 54 | 55 | 1. XML配置方式 56 | 2. 基于注解的配置方式 57 | 3. 基于Java配置 58 | 59 | ## 5. Spring支持的几种Bean的作用域 60 | 61 | Spring框架支持以下五种bean的作用域: 62 | 63 | - singleton:单例,bean在每个Spring IOC容器中只有一个实例 64 | - prototype: 一个bean的定义可以有多个实例 65 | - request:每次http请求都会创建一个bean,该作用域仅支持在WEB应用环境 66 | - session:在一个HTTP session中,一个bean定义对应一个实例,同样的该作用域仅支持在WEB应用环境 67 | - global-session:在一个全局的HTTP session中,一个bean定义对应一个实例,同样的该作用域仅支持在WEB应用环境 68 | 69 | ## 6. Spring中单例bean是线程安全的嘛?说一下Spring中Bean的生命周期 70 | 71 | Spring框架中的单例bean不是线程安全的。 72 | 73 | 74 | 1. Spring容器从XML、注解等方式获取bean的定义 75 | 2. 实例bean 76 | 3. Spring容器会根据bean的定义通过setter方法来填充所有的属性 77 | 4. 如果bean实现了BeanNameAware接口,Spring传递bean的ID到setBeanName方法 78 | 5. 如果bean实现了BeanFactoryAware接口,Spring传递BeanFactory到setBeanFactory方法 79 | 6. 如果bean有任何相关联的BeanPostProcessor,则Spring会在postProcessBeforeInitialization()方法内调用他们 80 | 7. 如果bean实现了Initialization了,调用它的afterPropertySet方法;如果bean声明了初始化方法 init-method,则会调用init-method指定的初始化方法 81 | 8. 如果bean有任何相关联的BeanPostProcessor,则Spring会在postProcessAfterInitialization()方法内调用他们【注意BeanPostProcessor中会定义postProcessBeforeInitialization和postProcessAfterInitialization】 82 | 9. 如果bean实现了 DisposableBean,它将调用destroy()方法 83 | 84 | 85 | ## 7. 可以在Spring中注入一个null和一个空字符串吗? 86 | 87 | 可以的 88 | 89 | ## 8. Spring是如何解决循环依赖的? 90 | 91 | 在Spring中,单例Bean是会出现循环依赖问题,而原型(Prototype)是不支持循环依赖的。 92 | 93 | 那么在单例Bean场景中,Spring是如何解决循环依赖的? 94 | 95 | 在Spring中是通过内部维护的三个Map,也就是晚上通常说的"三级缓存"(虽然官方文档中是没有这种说法的) 96 | 在Spring中的DefaultSingletonBeanRegistry类中,会包含了三个Map: 97 | 98 | - singletonObjects 99 | 100 | 俗称的"单例池""容器",缓存创建完成生成bean的地方 101 | 102 | - singletonFactories 103 | 104 | 映射创建Bean的原始工厂 105 | 106 | - earlySingletonObjects 107 | 108 | 早期的Bean,在这个Map中存的不是bean,而是instance实例 109 | 110 | 对于singletonFactories和earlySingletonObjects是用来赋值创建bean的,创建bean完成之后就清理掉各自map中的元素。 111 | 112 | 在深入源码步骤之前,需要提前知道一点就是,Spring创建一个bean是需要经过两步: 113 | 1. bean对象的实例 114 | 2. bean对象属性的实例 115 | 116 | 整个过程需要涉及到下面四步方法的依次调用: 117 | 1. getSingleton 118 | 2. doCreateBean 119 | 3. populateBean 120 | 4. addSingleton 121 | 122 | 例如A类中依赖了B,B类中依赖了A。 123 | 124 | 首先A调用getSingleton(),当来到doCreateBean()时,会设置属性earlySingletonExpose为true,然后将A的bean工厂添加到singletonFactories中,然后调用populateBean()时发现A中依赖了B,则又会 125 | 调用B类的getSingleton(),同样的调用doCreateBean()时会设置earlySingletonExpose为true,然后将B的bean工厂添加到singletonFactories中,然后调用populateBean()进行属性填充,此时发现B类中依赖 126 | 了A类,所以又回去调用A类的getSingleton(),此时发现在isSingletonCurrentlyInCreation结合中包含了A类,表示A类正在被创建,所以回去earlySingletonObjects中获取"半成品A",然后删除singletonFactories中的 127 | A的bean工厂。当B拿到半成品"A"时,则从populateBean()返回,最后调用addSingleton()添加到singletonObjects中完成B的bean创建。而A方法也会从populateBean()中返回,拿到了B,则然后调用addSingleton()方法,将 128 | bean添加到singletonObjects。 -------------------------------------------------------------------------------- /notes/计算机网络/HTTP和HTTPS的深入分析.md: -------------------------------------------------------------------------------- 1 | > 前言 2 | 3 | 前言 讲 HTTPS 之前,我们先来回顾一下 HTTP 协议。HTTP 是一种超文本传输协议,它是无状态的、简单快速的、基于 TCP 的可靠传输协议。 4 | 5 | 既然 HTTP 协议这么好,那怎么有冒出来了一个 HTTPS 呢?主要是因为 HTTP 是明文传输的,这就造成了很大的安全隐患。在网络传输过程中,只要数据包被人劫持,那你就相当于赤身全裸的暴露在他人面前,毫无半点隐私可言。想象一下,如果你连了一个不可信的 WIFI,正好有使用了某个支付软件进行了支付操作,那么你的密码可能就到别人手里去了,后果可想而知。 6 | 7 | 网络环境的就是这样,给你带来便利的同时,也到处充满了挑战与风险。对于小白用户,你不能期望他有多高的网络安全意识,产品应该通过技术手段,让自己变得更安全,从源头来控制风险。这就诞生了 HTTPS 协议。 8 | 9 | > HTTPS 10 | 11 | 简单理解,HTTP 不就是因为明文传输,所以造成了安全隐患。那让数据传输以加密的方式进行,不就消除了该隐患。 12 | 13 | 从网络的七层模型来看,原先的四层 TCP 到七层 HTTP 之间是明文传输,在这之间加一个负责数据加解密的传输层(SSL/TLS),保证在网络传输的过程中数据是加密的,而 HTTP 接收到的依然是明文数据,不会有任何影响。 14 | ![image](https://note.youdao.com/yws/api/personal/file/B8F8A772354D4447992DAEC573ED881A?method=download&shareKey=600a1f4e14562482fe331d6a0c155677) 15 | 16 | SSL是Netscape开发的专门用户保护Web通讯的,目前版本为3.0。最新版本的TLS 1.0是IETF(工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。两者差别极小,可以理解为SSL 3.1,它是写入了RFC的。 17 | 18 | > HTTP和HTTPS对比 19 | 20 | HTTP 三大风险: 21 | 22 | 1. 窃听风险(eavesdropping):第三方可以获知通信内容。 23 | 2. 篡改风险(tampering):第三方可以修改通信内容。 24 | 3. 冒充风险(pretending):第三方可以冒充他人身份参与通信。 25 | 26 | HTTPS 解决方案(HTTPS,超文本传输安全协议) 27 | 28 | 1. 所有信息都是加密传播,第三方无法窃听。 29 | 2. 具有校验机制,一旦被篡改,通信双方会立刻发现。 30 | 3. 配备身份证书,防止身份被冒充。 31 | 32 | > 实现 33 | 34 | 通过上面的介绍,对 HTTPS 有了大致的了解。可本质问题还没说,HTTPS 是如何做到数据的加密传输?这儿不打算搬出复杂的算法公式,还是以最通俗的话述来讲讲我的理解。 35 | 36 | 首先,先来了解下加密算法的方式,常用的有两种:对称加密与非对称加密。 37 | - 对称加密:即通信双方通过相同的密钥进行信息的加解密。加解密速度快,但是安全性较差,如果其中一方泄露了密钥,那加密过程就会被人破解。 38 | - 非对称加密:相比对称加密,它一般有公钥和私钥。公钥负责信息加密,私钥负责信息解密。两把密钥分别由发送双发各自保管,加解密过程需两把密钥共同完成。安全性更高,但同时计算量也比对称加密要大很多。 39 | 40 | 对于网络通信过程,在安全的前提下,还是需要保证响应速度。如何每次都进行非对称计算,通信过程势必会受影响。所以,人们希望的安全传输,最终肯定是以对称加密的方式进行。如图: 41 | ![image](https://note.youdao.com/yws/api/personal/file/3C60894D8F48451E8B0214E748A0CD0B?method=download&shareKey=25cf528d39bb031d4438585bc8ac1dc1) 42 | 首先 Session Key 是如何得到的,并且在 Session Key 之前的传输都是明文的。Session Key 通过网络传输肯定不安全,所以它一定各自加密生成的。因此在这之前,双方还要互通,确认加密的算法,并且各自添加随机数,提高安全性。如图: 43 | ![image](https://note.youdao.com/yws/api/personal/file/00AD32DEA53D4B618635F0A4B6C80B0A?method=download&shareKey=6233acee6f54fbbcfe74be895b24765f) 44 | 这样双方确认了使用的 SSL/TLS 版本,以及生成 Session Key 的加密算法。并且双方拥有了2个随机数(客户端、服务端各自生成一个)。可是如果按照目前的随机参数,加上加密算法生成 Session Key 呢,还是存在风险,加密算法是有限固定的,而且随机数都是明文传输的,有被截获的可能。 45 | 46 | 让加密算法变得不可预测,可能性不大。那么能否再传输一个加密的随机数,这个随机数就算被截获,也无法破译。这就用到了非对称加密算法,我们在步骤二中,把公钥传输给客户端。这样客户端的第三个随机数用公钥加密,只有拥有私钥的服务端才能破译第三个参数,这样生成的 Session 就是安全的了。如图: 47 | ![image](https://note.youdao.com/yws/api/personal/file/5FE43208800141C4B4F15745C02A127B?method=download&shareKey=107a4d4841f0248a1971f501d34af6c2) 48 | 不容易啊,看似完成了。现在生成的 Session Key 是不可预测的(就算被截获也无所谓),我们可以放心的进行私密通信了。真的是这样吗?我们似乎对服务端给的公钥十分信任,如果你目前通信的本身就不可信,或者被中间人劫持,由它发送伪造的公钥给你,那后果可想而知,这就是传说中的“中间人攻击”。 49 | 50 | 所以,我们得包装一下公钥,让它变得安全。这就引出了数字证书的概念,数字证书由受信任的证书机构颁发,证书包含证书文件与证书私钥文件。私钥文件由服务端自己保存,证书文件发送给请求的客户端,文件包含了域名信息、公钥以及相应的校验码。客户可以根据得到的证书文件,校验证书是否可信。如图: 51 | ![image](https://note.youdao.com/yws/api/personal/file/A26EAD8A7B564F5F96221A26BB33B47F?method=download&shareKey=785ad083e11f682ead278dbb8f45261e) 52 | 这下算简单讲完了整个的通信过程,首先在 TCP 三次握手后,进行非对称算法的加密(校验证书),将得到的参数进行加密生成会话所需的 Session Key,在之后的数据传输中,通过 Session Key 进行对称密码传输,会话信息在私密的通道下完成。 53 | 54 | 当然,实际的过程远比这来的复杂,这中间包括了复杂的算法以及细节内容,有兴趣的同学,可查阅相关的资料进行了解。 -------------------------------------------------------------------------------- /notes/计算机网络/计算机网络面试题.md: -------------------------------------------------------------------------------- 1 | ## 1. HTTP和HTTPS的区别 2 | 3 | 1. HTTP默认端口为80,而HTTPS的默认端口为443 4 | 2. HTTPS是在HTTP基础上加上了SSL协议,Secure Sockets Layer 安全套接字协议 5 | 3. HTTP是未经安全加密的协议,容易受攻击者的信息劫持、数据窃听以及数据伪造 6 | 4. HTTP是无状态协议的,即浏览器对事务的处理是没有记忆能力的,HTTP通过Cookie来解决无状态的问题 7 | 5. HTTPS有两种加密算法:对称加密和非对称加密两种算法,HTTPS在生产中是运用的对称加密和非对称加密的混合加密模式来工作的 8 | 9 | ## 2. TCP和UDP 10 | 11 | | TCP | UDP | 12 | | ---- | ---- | 13 | | TCP是面向连接的协议 | UDP是面向无连接的协议 | 14 | | TCP是需要建立连接后才能传输数据 | UDP不需要建立连接即可传输大量数据 | 15 | | TCP每个数据包中都是有特定顺序进行排列的 | UDP数据包没有固定顺序,所有数据包都是独立的 | 16 | | TCP传输的速度比较慢 | UDP传输速度快 | 17 | | TCP头部有20个字节 | UDP头部字需要8个字节即可 | 18 | | TCP是重量级的,发送任何数据之前都需要建立三次握手 | UDP是轻量级的 | 19 | | TCP有发送确认 | UDP没有发送确认 | 20 | | TCP是可靠的传输协议 | UDP是不可靠的传输协议,不保证数据能传输到目的地 | 21 | 22 | ## 3. TCP的三次握手和四次挥手 23 | 24 | TCP三次握手流程: 25 | 26 | 1. 客户端发送一个SYN报文给服务器端并且携带和一个seq Num为a,客户端状态变为SYN_SENT;服务器端接受到SYN报文后,此时服务端状态为Listen; 27 | 2. 服务器端接着又发送一个SYN ACK报文给客户端,这个报文中携带了ack Num为a+1的字段以及seq Num为b的字段,发送后服务器端这边的状态就变为了SYN_RCVD; 28 | 3. 当客户端再次发送ACK报文给服务器端时,也会携带一个ack Num为b+1,此时客户端状态变为了ESTABLISHED,服务器端也变为了ESTABLISHED; 29 | 30 | TCP三次握手阶段,客户端状态流转情况如下: 31 | CLOSE ——> SYN_SENT ——> ESTABLISHED 32 | 33 | TCP三次握手阶段,服务器端状态流转情况如下: 34 | CLOSE ——> SYN_RCVD ——> ESTABLISHED 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /notes/面试/Dubbo复习.md: -------------------------------------------------------------------------------- 1 | ## Dubbo SPI 2 | 3 | ### JDK的SPI定义以及使用分为四步 4 | 1. 定义一个接口及接口方法 5 | 2. 编写该接口的一个实现类 6 | 3. 在META-INF/services目录下,创建一个以接口全路径命名的文件 7 | 4. 文件内容为具体实现类的全路径名,如果有多个则以逗号分隔开 8 | 5. 在代码中通过java.util.ServiceLoader来加载扩展点具体的实现类 9 | 10 | ### Dubbo SPI使用的是策略模式,为的是”对扩展开放,对修改封闭“(开闭原则);让接口和实现解耦,为Dubbo框架的扩展性奠定了基础 11 | 12 | ### JDK SPI的缺点 13 | 1. JDK SPI会一次性加载、实例化扩展点所有实现,并且扩展点实现的初始化会非常耗时,如果扩展点没有用到,则会非常浪费资源 14 | 2. 不支持IOC和AOP,即没有和Spring集成 15 | 3. 加载过程中如果报异常了,则会被吞掉,则无法进一步排查异常 16 | 17 | ### Dubbo SPI优化的地方 18 | 1. Dubbo SPI只是加载扩展点,并不会立即全部初始化,并且会根据扩展点类型的不同而缓存到内存中。、 19 | 2. Dubbo自己实现了IOC和AOP,让一个扩展可以通过setter直接注入到其他扩展点中 20 | 3. Dubbo SPI在扩展加载失败的时候会先抛出真实异常并打印日志 21 | 22 | ### Dubbo如何自己实现了IOC和AOP? 23 | 24 | Dubbo通过支持包装扩展类,然后把通用功能的抽象逻辑放到包装类中,然后在核心逻辑的前后插入自己的逻辑进行代码增强,从而实现了扩展点的AOP功能。 25 | 26 | 而Dubb中,通过T injectExtension(T instanc)这个方法来实现的IOC功能。 27 | 28 | ### Dubbo中扩展点有哪些分类以及缓存 29 | Dubbo的SPI可以分为两种缓存: 30 | 1. Class缓存 31 | 32 | Dubbo SPI获取扩展类时,会先从缓存中读取。如果缓存中不存在,则加 33 | 载配置文件,根据配置把Class缓存到内存中,并不会直接全部初始化。 34 | 35 | 2. 实例缓存 36 | 37 | 基于性能考虑,Dubbo框架中不仅缓存Class,也会缓存Class实例化后的 38 | 对象。每次获取的时候,会先从缓存中读取,如果缓存中读不到,则重新加载并缓存 39 | 起来。这也是为什么Dubbo SPI相对Java SPI性能上有优势的原因,因为Dubbo SPI 40 | 缓存的Class并不会全部实例化,而是按需实例化并缓存,因此性能更好。 41 | 42 | 43 | 这两种缓存中又可以分为普通扩展类、包装扩展类(Wrapper类)、自适应扩展类(Adaptive类) 44 | 45 | - 普通扩展类 46 | 47 | 最基础的,配置在SPI配置文件中的扩展类实现。 48 | 49 | - 包装扩展类 50 | 51 | 这种Wrapper类没有具体的实现,只是做了通用逻辑的抽象,并且需要 52 | 在构造方法中传入一个具体的扩展接口的实现。属于Dubbo的自动包装特性。 53 | 54 | - 自适应扩展类 55 | 56 | 一个扩展接口会有多种实现类,具体使用哪个实现类可以不写死在配 57 | 置或代码中,在运行时,通过传入URL中的某些参数动态来确定。这属于扩展点的自适应特性。 58 | 59 | ### 扩展点的特性 60 | 61 | 从Dubbo官方文档中可以知道,扩展类一共包含四种特性:自动包装、自动加载、自适应 62 | 和自动激活。 63 | 64 | ### 扩展点注解 65 | 66 | > @SPI 67 | 68 | @SPI注解可以使用在类、接口和枚举类上,Dubbo框架中都是使用在接口上。它的主要作用就是标记这个接口是一个Dubbo SPI接口,即是一个扩展点,可以有多个不同的内置或用户 69 | 定义的实现。运行时需要通过配置找到具体的实现类。 70 | 71 | Dubbo中很多地方通过getExtension (Class type. String name)来获取扩展点接口的 72 | 具体实现,此时会对传入的Class做校验,判断是否是接口,以及是否有@SPI注解,两者缺一 73 | 不可。 74 | 75 | > @Adaptive 76 | 77 | @Adaptive注解可以标记在类、接口、枚举类和方法上,但是在整个Dubbo框架中,只有 78 | 几个地方使用在类级别上,如AdaptiveExtensionFactory和AdaptiveCompiler,其余都标注在 79 | 方法上。 80 | 81 | 如果标注在接口的方法上,即方法级别注解,则可以通过参数动态获得实现类,这一 82 | 点已经在自适应特性上说明。方法级别注解在第一次getExtension时,会自动生成 83 | 和编译一个动态的Adaptive类,从而达到动态实现类的效果。 84 | 85 | > @Activate 86 | 87 | @Activate可以标记在类、接口、枚举类和方法上。主要使用在有多个扩展点实现、需要根 88 | 据不同条件被激活的场景中,如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。 89 | ©Activate可传入的参数很多。 90 | 91 | ### ExtensionLoader工作原理 92 | 93 | ExtensionLoader是整个扩展机制的主要逻辑类,在这个类里面卖现了配置的加载、扩展类 94 | 缓存、自适应对象生成等所有工作。 95 | 96 | ExtensionLoader 的逻辑入口可以分为 getExtension、getAdaptiveExtension、 97 | getActivateExtension三个,分别是获取普通扩展类、获取自适应扩展类、获取自动激活的扩 98 | 展类。总体逻辑都是从调用这三个方法开始的,每个方法可能会有不同的重载的方法,根据不 99 | 同的传入参数进行调整。 100 | 101 | 三个入口中,getActivateExtension对getExtension 的依赖比较getAdaptiveExtension 102 | 则相对独立。 103 | 104 | ## Dubbo启停原理解析 105 | 106 | - Dubbo配置解析原理 107 | - Dubbo服务暴露原理 108 | - Dubbo服务消费原理 109 | - Dubbo优雅停机解析 110 | 111 | ### Dubbo配置解析 112 | 113 | 目前Dubbo框架提供了3中配置方式:XML配置、注解、属性文件(properties和yml)。 114 | 115 | 对于Dubbo的配置,不管是注解还是XML配置、properties配置都是需要对象来承载配置内容的。 116 | 117 | > 基于XML配置原理解析 118 | 119 | 对于XML配置原理解析,在之前学习Spring源码时,就已经有来了解到,一般都是通过XXNamespaceHandler来进行处理的,而在Dubbo中是通过DubboNamespaceHandler来完成。 120 | 121 | ``` 122 | public class DubboNamespaceHandler extends NamespaceHandlerSupport implements ConfigurableSourceBeanMetadataElement { 123 | 124 | static { 125 | Version.checkDuplicate(DubboNamespaceHandler.class); 126 | } 127 | 128 | @Override 129 | public void init() { 130 | registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); 131 | registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true)); 132 | registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true)); 133 | registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true)); 134 | registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true)); 135 | registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true)); 136 | registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true)); 137 | registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true)); 138 | registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true)); 139 | registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true)); 140 | registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true)); 141 | registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); 142 | registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); 143 | registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); 144 | } 145 | } 146 | ``` 147 | 148 | DubboNamespaceHandler主要把不同的标签关联至U解析实现类中o registerBeanDefinitionParser方法约定了在Dubbo框架中遇到标签application> module和registry等都会委托给 149 | DubboBeanDefinitionParser处理。需要注意的是,在新版本中重写了注解实现,主要解决了以前实现的很多缺陷(比如无法处理AOP等)。 150 | 151 | 来看下DubboBeanDefinitionParser类的parse方法核心逻辑,部分内容省略,只贴出核心逻辑: 152 | ``` 153 | // 初始化 RootBeanDefinition 154 | RootBeanDefinition beanDefinition = new RootBeanDefinition(); 155 | beanDefinition.setBeanClass(beanClass); 156 | beanDefinition.setLazyInit(false); 157 | // 获取beanId 158 | String id = resolveAttribute(element, "id", parserContext); 159 | // 如果没有beanId则将beanName设置为beanId,确保Spring容器没有重复的Bean定义 160 | if (StringUtils.isEmpty(id) && required) { 161 | // 一次尝试获取XML配置标签name和interface作为Bean唯一id 162 | String generatedBeanName = resolveAttribute(element, "name", parserContext); 163 | if (StringUtils.isEmpty(generatedBeanName)) { 164 | // 如果协议标签没有指定name,则使用默认name:dubbo 165 | if (ProtocolConfig.class.equals(beanClass)) { 166 | generatedBeanName = "dubbo"; 167 | } else { 168 | generatedBeanName = resolveAttribute(element, "interface", parserContext); 169 | } 170 | } 171 | // 如果beanName也为空,则用beanClass作为beanId 172 | if (StringUtils.isEmpty(generatedBeanName)) { 173 | generatedBeanName = beanClass.getName(); 174 | } 175 | id = generatedBeanName; 176 | int counter = 2; 177 | while (parserContext.getRegistry().containsBeanDefinition(id)) { 178 | id = generatedBeanName + (counter++); 179 | } 180 | } 181 | // Step3 将获取到的Bean注册到Spring 182 | if (StringUtils.isNotEmpty(id)) { 183 | // BeanId 出现重复则抛异常 184 | if (parserContext.getRegistry().containsBeanDefinition(id)) { 185 | throw new IllegalStateException("Duplicate spring bean id " + id); 186 | } 187 | // 将xml转换为的Bean注册到Spring的parserContext,后续属性通过adPropertyValue来增添 188 | parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); 189 | beanDefinition.getPropertyValues().addPropertyValue("id", id); 190 | } 191 | ``` 192 | 193 | 小结:首先DubboBeanDefinition#Parse方法前半部分逻辑就是负责把标签解析成对应的BeanDefinition定义,并注册到Spring上下文中,同时保证了Spring容器中相同的id的Bean不会被覆盖。 194 | 195 | ``` 196 | if (ProtocolConfig.class.equals(beanClass)) { 197 | // 如果dubbo标签中配置了protocol协议,则添加protocol属性 198 | for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { 199 | BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); 200 | PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol"); 201 | if (property != null) { 202 | Object value = property.getValue(); 203 | if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) { 204 | definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id)); 205 | } 206 | } 207 | } 208 | } else if (ServiceBean.class.equals(beanClass)) { 209 | // 如果 配置了class属性,那么为具体class配置的类注册Bean,并注入ref属性。 210 | String className = resolveAttribute(element, "class", parserContext); 211 | if (StringUtils.isNotEmpty(className)) { 212 | RootBeanDefinition classDefinition = new RootBeanDefinition(); 213 | // 通过类反射工具获取className的实例 214 | classDefinition.setBeanClass(ReflectUtils.forName(className)); 215 | classDefinition.setLazyInit(false); 216 | // parseProperties主要是解析标签中的name、class、ref属性并通过key-value键值对取出来,放到BeanDefinition中 217 | // 因此ServiceBean就会包含了用户配置的属性值 218 | parseProperties(element.getChildNodes(), classDefinition, parserContext); 219 | beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl")); 220 | } 221 | } else if (ProviderConfig.class.equals(beanClass)) { 222 | parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition); 223 | } else if (ConsumerConfig.class.equals(beanClass)) { 224 | parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition); 225 | } 226 | ``` 227 | 228 | 接下来对于标签的解析,通过了parseNested方法来处理,即解析嵌套标签。因为provider和consumer标签可能会在内部嵌套代码,即、 229 | 即内部可能会嵌套标签。,解析内部的service并生成Bean的时候,会把外层provider实例对象注入service,这种设计方式允许内部标签直接获取外部标签属性。 230 | 231 | 232 | 那么标签的attribute是如何提取的呢?对于attribute属性,主要分为两种场景: 233 | - 查找配置对象的get、set和is前缀方法,如果标签属性名和方法名称相同,则通过反 234 | 射调用存储标签对应值。 235 | - 如果没有和get、set和is前缀方法匹配,则当作parameters参数存储,parameters 236 | 是一个Map对象。 237 | 238 | 小结:以上两种场景的值最终都会存储到Dubbo框架的URL中,唯一区别就是get、set和is前缀方法当作普通属性存储,parameters是用Map字段存储的 239 | 240 | > 基于注解配置原理解析 241 | 242 | Dubbo重启开源后,对Dubbo的注解进行了重写,重写后解决了一下几个问题: 243 | 244 | - 注解支持不充分,需要XML配置 245 | - @ServiceBean不支持SpringAOP 246 | - @Reference不支持字段继承性 247 | 248 | 注解处理逻辑包含3部分内容 249 | 1. 如果用户使用了配置文件,则框架按需生成对应bean 250 | 2. 将所有使用Dubbo的@Service的class提升为bean存入SpringIOC中 251 | 3. 为使用@Reference注解的字段或方法注入代理对象 252 | 253 | 另外,看下Dubbo注解机制 254 | 1. @EnableDubbo激活注解 255 | 2. 通过DubboConfigConfigurationSelector来支持配置文件读取配置 256 | 3. 通过ServiceAnnotationBeanPostProcessor来提升@Service注解的服务为Spring bean 257 | 4. 通过ReferenceAnnotationBeanPostProcessor来注入@Reference引用 258 | 259 | ``` 260 | @EnableDubboConfig 261 | @DubboComponentScan 262 | public @interface EnableDubbo { 263 | ... 264 | } 265 | ``` 266 | 267 | ``` 268 | @Import(DubboConfigConfigurationRegistrar.class) 269 | public @interface EnableDubboConfig { 270 | ... 271 | } 272 | ``` 273 | 274 | ``` 275 | @Import(DubboComponentScanRegistrar.class) 276 | public @interface DubboComponentScan { 277 | ... 278 | } 279 | ``` 280 | 281 | 1. 由于DubboConfigConfigurationRegistrar实现了ImportBeanDefinitionRegistrar,所以会实现registerBeanDefinition()方法。 282 | 2. 在registerBeanDefinition()方法中,会注册一个DubboConfigConfiguration的一个注解,该注解中会有一个@EnableDubboConfigBindings注解 283 | 3. 然后将EnableDubboConfigBiding注解修饰的Bean注册到Spring容器中。 284 | 4. EnableDubboConfigBinding的作用是进行属性绑定,Dubbo会根据用户配置属性自动填充这些承载的对象。 285 | 5. 随后,DubboConfigConfigurationRegistrar的registerBeanDefinition方法后面会继续注册各种BeanPostProcessor,包括了ReferenceAnnotationBeanPostProcessor等。 286 | 287 | > Dubbo服务注解扫描和注册 288 | 289 | 在Dubbo中,通过ServiceAnnotationBeanPostProcessor来进行服务注解扫描和注册, 该类的父类ServiceClassPostProcessor实现了服务注解扫描和注册的核心逻辑。 290 | 1. Dubbo框架首先会提取用户配置的扫描包名称,因为包名可能使用${...}占位符,因此框架会调用Spring的占位符解析做进一步解码 291 | 2. 开始真正的注解扫描,委托Spring对所有符合包名的.class文件做字节码分析,最终通过AnnotationTypeFilte(Service.class)配置扫描@Service注解作为过滤条件 292 | 3. 然后通过findServiceBeanDefinitionHolders来对扫描的服务创建 BeanDefinitionHolder,用于生成ServiceBean的RootBeanDefinition,用于Spring启动后的服务暴露 293 | 294 | > Dubbo消费者注入 295 | 296 | 在Dubbo中,通过ReferenceAnnotationBeanPostProcessor来实现消费者注解注入,该类核心逻辑包含以下几步: 297 | 1. 查找Bean中所有通过@Reference修饰的字段或方法 298 | 2. 调用InjectionMetadata的inject来对字段、方法进行反射绑定 299 | 300 | 因为处理器 ReferenceAnnotationBeanPostProcessor 实现了 InstantiationAwareBeanPostProcessor接口,所以在Spring的Bean中初始化前会触发postProcessPropertyValues方法,该方法允许我们做进一步处理,比如增加属性和属性值修改等。 301 | 302 | ### 服务暴露 303 | 304 | 不管在服务暴露还是服务消费场景下,Dubbo框架都会根据优先级对配置信息做聚合处理,目前默认覆盖策略主要遵循以下几点规则: 305 | 1. -D 传递给 JVM 参数优先级最高,比如-Ddubbo. protocol.port=20880 306 | 2. 代码或XML配置优先级次高,比如Spring中XML文件指定 307 | 3. 配置文件优先级最低,比如 dubbo.properties 文件指定 dubbo.protocol.port=20880o一般推荐使用dubbo.properties作为默认值,只有XML没有配置时,dubbo.properties配置项才会生效,通常用于共享公共配置,比如应用名等 308 | 309 | #### 远程服务暴露机制 310 | 311 | 在详细探讨服务暴露细节之前,我们先看一下整体RPC的暴露原理: 312 | 1. 服务转换成Invoker 313 | - ServiceConfig => ref 314 | - ProxyFactory => Javassist、JDK动态代理 315 | - Invoker => AbstractProxyInvoker 316 | 2. Invoker转化成Exporter 317 | - Protocol => Dubbo、injvm等 318 | - Exporter 319 | 320 | 在整体上看,Dubbo框架做服务暴露分为两大部分,第一步将持有的服务实例通过代理转换成Invoker,第二步会把Invoker通过具体的协议(比如Dubbo 转换成Exporter,框架做了 321 | 这层抽象也大大方便了功能扩展。 322 | 323 | 这里的Invoker可以简单理解成一个真实的服务对象实例,是Dubbo框架实体域,所有模型都会向它靠拢,可向它发起invoke调用。它可能是一个本地的实现,也可能是一个远程的实现,还可能是一个集群实现 324 | 325 | 首先,RPC暴露和服务暴露有啥区别?是同一个内容吗? 326 | 327 | 接下来我们深入探讨内部框架处理的细节,框架真正进行服务暴露的入口点在ServiceConfig#doExport中,无论XML还是注解,都会转换成ServiceBean,它继承自 328 | ServiceConfig,在服务暴露前,会按照-D、XML、Properties覆盖属性。 329 | 330 | Dubbo支持多注册中心同时写,如果配置了服务同时注册多个注册中心,则会在ServiceConfig#doExportUrls中依次暴露。 331 | 332 | ``` 333 | private void doExportUrls() { 334 | ServiceRepository repository = ApplicationModel.getServiceRepository(); 335 | ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass()); 336 | repository.registerProvider( 337 | getUniqueServiceName(), 338 | ref, 339 | serviceDescriptor, 340 | this, 341 | serviceMetadata 342 | ); 343 | // 加载注册中心地址 344 | List registryURLs = ConfigValidationUtils.loadRegistries(this, true); 345 | 346 | // 获取协议配置 347 | for (ProtocolConfig protocolConfig : protocols) { 348 | String pathKey = URL.buildKey(getContextPath(protocolConfig) 349 | .map(p -> p + "/" + path) 350 | .orElse(path), group, version); 351 | 352 | repository.registerService(pathKey, interfaceClass); 353 | 354 | serviceMetadata.setServiceKey(pathKey); 355 | // 如果服务指定暴露多个协议(Dubbo、REST),则依次暴露服务 356 | doExportUrlsFor1Protocol(protocolConfig, registryURLs); 357 | } 358 | } 359 | ``` 360 | 361 | 真实服务暴露逻辑是在doExportUrlsFor1Protocol方法中实现的。 362 | 363 | 364 | 总结下doExportUrlsFor1Protocol逻辑 365 | 1. 通过反射获取配置信息对象如application、module、protocolCOnfig到map中,用于后序构造URL 366 | 2. 然后根据map来新建URL对象 367 | 3. 如果没有配置任何信息,则调用exportLocal进行本地JVM服务暴露4. 如果配置了监控地址如dubbo-admin,则服务调用信息会上报最后用服。 368 | 5. 通过动态代理的方式创建Invoker对象,在服务端生成的是AbstractProxyInvoker实例对象,所有真实的方法调用都会委托给代理, 369 | 然后代理转发给服务ref调用 370 | 6. 通过动态代理转化成Invoker,registryURL存储的就是注册中心地址,然后使用export作为key追加服务元数据信息 371 | 6. 服务暴露后向注册中心注册服务信息 372 | 7. 如果之前判断没有注册中心,则直接暴露服务,不经过注册中心。因为这里暴露的URL信息是以具体RPC协议开头的,并不是以注册中心协议开头的 373 | 374 | 下面看下有注册中心服务的暴露和无注册中心服务的暴露URL: 375 | 376 | - registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol=zookeeper&export=dubbo://ip:port/xxx? 377 | - dubbo://ip:host/xxx.Service?timeout=1000& 378 | 379 | protocol实例会自动根据服务暴露URL自动做适配,有注册中心场景会取出具体协议,比如zookeeper协议,首先会创建注册中心实例,然后取出export对于的具体服务URL, 380 | URL对应的协议(默认为Dubbo)进行服务暴露,当服务暴露成功后把服务数据注册到ZooKeeper。 381 | 382 | 如果没有注册中心,则在⑦中会自动判断URL对应的协议(Dubbo)并直接暴露服务,从而没有经过注册中心。 383 | 384 | 在将服务实例ref转化成Invoker之后,如果有注册中心,则会通过RegistryProtocol#export进行更细粒度的控制,比如先进行服务暴露在向注册中心进行注册服务元数据。 385 | 386 | 在注册中心在做服务暴露时,会做以下几件事情: 387 | 1. 委托具体协议(Dubbo)进行服务暴露,创建NettyServer监听端口和保存服务实例 388 | 2. 创建注册中心对象,与注册中心创建TCP连接 389 | 3. 注册服务元数据到注册中心 390 | 4. 订阅configurators节点,监听服务动态属性变更事件 391 | 5. 服务销毁收尾工作,比如关闭端口、反注册服务信息等 392 | 393 | ## Dubbo的RPC实现 394 | 395 | 还没写,先引用一下优秀博文 [https://www.cnblogs.com/markcd/p/9075060.html](https://www.cnblogs.com/markcd/p/9075060.html) 396 | 397 | 什么是PRC呢?RPC就是"远程过程调用",通俗点说就是一台计算机的方法调用另外一台计算机的方法。 398 | 399 | > Dubbo RPC有什么优点? 400 | 401 | 优点如下: 402 | 1. 【面向接口代理的高性能RPC调用】提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节 403 | 2. 【服务自动注册与发现】支持多种注册中心服务,服务实例上下线实时感知 404 | 3. 【运行期流量调度】内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等策略 405 | 4. 【智能负载均衡】内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量 406 | 5. 【高度可扩展能力】遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点,平等对待内置实现与第三方实现 407 | 6. 【可视化的服务治理与运维】提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数 408 | 409 | ## Dubbo支持哪些协议?默认推荐使用哪个?为什么 410 | 411 | 在Dubbo中,默认是:dubbo、rmi、hessian、http、webservice、thrift、memcached、redis、rest9中协议,默认推荐的是dubbo协议。 412 | 413 | Dubbo2.0协议采用了单一长连接和NIO异步通讯,Hessian2/dubbo二进制序列化,适合于小数据量大并发的服务调用,以及服务消费者机器大于服务提供者机器数的情况下。Dubbo主要用于服务间RPC通信,数据量小 414 | 并发大的场景非常合适。 415 | 416 | ## Dubbo有几种负载均衡策略?使用哪些场景? 417 | 418 | | 负载均衡策略 | 说明 | 419 | | ---- | ---- | 420 | | Random LoadBalance | 随机,按权重设置随机概率(默认) | 421 | | RoundRobin LoadBalance | 轮询,按公约后的权重设置轮询比率 | 422 | | LeastActive LoadBalance | 最少活跃调用数,相同活跃数的随机 | 423 | | ConsistentHash LoadBalance | 一致性Hash,相同参数的请求总是发到统一提供者 | 424 | 425 | > 一致性Hash是什么原理? 426 | 427 | [通俗易懂的一致性Hash算法](https://www.bilibili.com/video/BV1Hs411j73w?from=search&seid=12295226113612837705) 428 | 429 | 对于普通的hash算法,比如三台缓存服务器a、b、c,图片需要缓存到这三台缓存服务器上,缓存策略就是通过hash(图片名称) % 机器数量 得出的值去存放,公式如下: 430 | ``` 431 | hash(图片名称)% 机器数量 432 | ``` 433 | 而当有大量请求需要查询图片时,则请求是打在缓存中的,同样通过上面计算公式去计算到那台服务器上去取。 434 | 435 | 但此时又加了一台新的缓存服务器d,此时机器数为4,而通过上面计算公式计算得出的值会和原先三台机器时计算得出的结果不一致,会导致大量缓存失效,从而导致大量请求全打到服务器上,极端情况下会让服务器雪崩。 436 | 437 | 此时就需要一个一致性hash算法来避免这种情况的发生。 438 | 439 | 一致性hash就是去了2^32次方来替换上面公司的机器数量,让取余操作形成了一个闭环,也就是取余值是在:0 ~ (2^32-1)之间。 440 | 441 | 同样的拿上面的例子说明,a、b、c三台缓存服务器会在0 ~ (2^32-1)这个闭环上三个不同的位置,如果此时aa.jpg图片计算出了值,在换上判断到最近的一个服务器为a,则存进a服务器中,同理其他的其他图片 442 | bb.jpg、cc.jgp分别存在了b、c服务器上。此时又新加入一台服务器d,会在a、b、c之后的位置,则如果其他请求落在了d服务器,则只会在d这块区域缓存失效,所以一致性hash相比于普通的hash算法,可以避免 443 | 大量的缓存失效。 444 | 445 | 但是,一致性hash还是有不足的地方,即: 446 | 447 | 一致性hash算法容易造成哈希偏斜,也就是a、b、c三台服务器挤在一块了,从而导致缓存不均匀,严重点就造成系统崩溃 448 | 449 | 对于这种情况,可以通过增加多台服务器,而为每个服务器增加虚拟节点。例如为a服务器增加a1,a2,a3,a4等虚拟节点。 450 | 451 | 所以对于缓存图片的读取,只需要先访问虚拟节点从而找到真实节点,最后在去真实节点获取到缓存的图片。 452 | 453 | > Nginx的负载均衡和Dubbo的负载均衡有什么不同? 454 | 455 | Nginx的负载均衡是在http请求层面;Dubbo的负载均衡是在服务层面; 456 | 457 | -------------------------------------------------------------------------------- /notes/面试/JVM面试题.md: -------------------------------------------------------------------------------- 1 | ### 1. JVM类加载的详细过程? 2 | 3 | ### 2. JVM是如何加载Java类的? 4 | 5 | ### 3. Java类加载器包括⼏种?它们之间的⽗⼦关系是怎么样的?双亲委派机制是什么意思?有什么好处? 6 | 7 | 启动Bootstrap类加载、扩展Extension类加载、系统System类加载。 8 | 9 | 父子关系如下: 10 | 11 | 启动类加载器 ,由C++ 实现,没有父类; 12 | 扩展类加载器,由Java语言实现,父类加载器为null; 13 | 系统类加载器,由Java语言实现,父类加载器为扩展类加载器; 14 | 自定义类加载器,父类加载器肯定为AppClassLoader。 15 | 双亲委派机制:类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载。 优势避免Java核心API篡改。 16 | 17 | ### 4. 自定义类加载器怎么实现? 18 | 19 | 【扩展】其中哪个方法走双亲委派模型,哪个不走,不走的话怎么加载类(实现findclass方法,一般用defineclass加载外部类),如何才能不走双亲委派。(重写loadclass方法) 20 | 21 | ### 5. 类加载器的双亲委派模型的作用,能重复加载某个类吗? 22 | 23 | ### 6. JVM内存模型是什么? 24 | 25 | ### 7. JVM中为什么推荐要将-Xmx和-Xms设置大小一样? 26 | 27 | ### 8. Perm Space中保存什么数据?会引起OutOfMemory吗? 28 | 29 | 加载class文件。 30 | 31 | 会引起,出现异常可以设置 -XX:PermSize 的大小。JDK 1.8后,字符串常量不存放在永久带,而是在堆内存中,JDK8以后没有永久代概念,而是用元空间替代,元空间不存在虚拟机中,二是使用本地内存。 32 | 33 | ### 9. JDK 1.8之后Perm Space有哪些变动? MetaSpace大小默认是无限的么? 还是你们会通过什么式来指定? 34 | 35 | JDK 1.8后用元空间替代了 Perm Space;字符串常量存放到堆内存中。 36 | 37 | MetaSpace大小默认没有限制,一般根据系统内存的大小。JVM会动态改变此值。 38 | 39 | 1. -XX:MetaspaceSize:分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark)。此值为估计值,MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。 40 | 2. -XX:MaxMetaspaceSize:分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。 41 | 42 | ### 10. 了解逃逸分析算法嘛?说一下原理与作用呢? 43 | 44 | ### 11. 垃圾回收机制是什么?回收步骤有哪些? 45 | 46 | 一个对象被垃圾回收的过程: 47 | 48 | 1. 对象没有与GC Roots的引用链连接; 49 | 2. 被第一次标记为可回收状态; 50 | 3. 进行一次筛选; 51 | 4. 判断是否有必要执行finalize()方法; 52 | 5. 有必要的话,对象会被放置在F-Queue队列中; 53 | 6. 虚拟机的Finalizer线程会调用对象的finalize()方法; 54 | 7. 对象在finalize()方法里通过将this对象与类变量或者对象的成员变量进行了关联,也就与引用链上的对象进行了关联; 55 | 8. 自救成功,然后从F-Queue中被移除出去;如果被判断为没有必要执行finalize()方法; 56 | 9. 在GC对F-Queue进行第二次小规模标记的时候,被标记为可回收状态; 57 | 10. 下次垃圾回收,就会回收掉这个对象了 58 | 59 | 60 | 61 | ### 12. 项目中如何查看垃圾回收? 62 | 63 | ### 13. jvm关于gc的命令,知道多少? 64 | 65 | ### 14. 详细说一下CMS的作用,原理以及回收过程,会停顿(STW)几次?为什么会停顿? 66 | 67 | ### 15. 详细说一下G1的作用,原理以及回收过程? 68 | 69 | ### 16. 如何利用Unsafe API绕开JVM的控制? 70 | 71 | ### 17. 频繁YOUNGGC怎么查问题? 72 | 73 | -------------------------------------------------------------------------------- /notes/面试/Java其他面试题.md: -------------------------------------------------------------------------------- 1 | ### 1. 泛型与PECS 2 | 3 | [泛型与PECS](https://blog.csdn.net/qq_30496695/article/details/83032189) -------------------------------------------------------------------------------- /notes/面试/MySQL复习.md: -------------------------------------------------------------------------------- 1 | 2 | 本文主要是总结MySQL中的知识点,具体知识点不做具体展开,知识点详细内容以连接的形式展开。 3 | 4 | ## 1. InnoDB存储引擎的特点?相比于其他的存储引擎有什么作用? 5 | 6 | ✅ 回答 7 | 8 | - InnoDB会将数据分为若干个页,以页作为磁盘和内存之间交互的基本单位。InnoDB中一个页单位为16KB。InnoDB会将16KB的数据从磁盘读取到内存中,或者是以16KB的数据从内存刷新到磁盘中 9 | - InnoDB的行格式是指记录存储在磁盘中的格式,包括了:Compact、Redundant、Dynamic和Compressed四种格式 10 | - InnoDB支持事务机制,默认会为每一条SQL语句封装成事务,然后自动提交,所以默认情况会影响运行速度,最好将多条SQL语言放在begin和commit之间组成一个事务来执行 11 | - InnoDB不支持全文索引 12 | - InnoDB支持表、行(默认)级锁 13 | - InnoDB的4大特性:插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(ahi),预读(read ahead) 14 | 15 | ## 2. InnoDB和MyISAM引擎对比 16 | 17 | ✅ 回答 18 | 19 | - 【最大的区别】InnoDB支持事务,MyISAM不支持事务 20 | - InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MyISAM会失败 21 | - InnoDB是聚镞索引????而MyISAM是非聚镞索引 22 | 23 | 底层使用的是B+树作为索引结构,数据文件和(主键)索引绑定在一起的,InnoDB中是默认会为一个没有主键的表添加上主键的,因为主键索引效率高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此主键不应过大,因为主键过大,其他索引也会变大。 24 | 25 | MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。 26 | 27 | 也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。 28 | 29 | - Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引领域的查询效率上MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了 30 | - MyISAM表格可以被压缩后进行查询操作 31 | - InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁 32 | 33 | InnoDB的行锁是实现在索引上的,而不是锁在物理行记录上。潜台词是,如果访问没有命中索引,也无法使用行锁,将要退化为表锁。 34 | ``` 35 | 例如: 36 | 37 | t_user(uid, uname, age, sex) innodb; 38 | 39 | uid PK 40 | 无其他索引 41 | update t_user set age=10 where uid=1; 命中索引,行锁。 42 | 43 | update t_user set age=10 where uid != 1; 未命中索引,表锁。 44 | 45 | update t_user set age=10 where name='chackca'; 无索引,表锁。 46 | ``` 47 | - InnoDB表必须有主键(用户没有指定的话会自己找或生产一个主键),而Myisam可以没有 48 | 49 | > InnoDB和MyISAM如何选择 50 | 51 | 1. 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM; 52 | 2. 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。 53 | 3. 系统奔溃后,MyISAM恢复起来更困难,能否接受; 54 | 4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。 55 | 56 | ## 3. 在MySQL的SQL语句中,哪些是走索引,哪些是不走索引的? 57 | 58 | 1. SQL语句中包含了or的不走索引; 59 | 2. like查询语句中 % 前缀不走索引,后缀要走索引; 60 | 3. 没有查询条件,或者查询条件没有建立索引的不走索引; 61 | 4. 62 | -------------------------------------------------------------------------------- /notes/面试/Redis复习.md: -------------------------------------------------------------------------------- 1 | 2 | - [基础](#基础) 3 | - [1. Redis的基础数据结构](#1-redis的基础数据结构) 4 | - [1.1 扩展(详细知识)](#11-扩展详细知识) 5 | - [2. 如果有大量的key需要设置同一时间过期,一般需要注意些什么?](#2-如果有大量的key需要设置同一时间过期一般需要注意些什么) 6 | - [3. Redis分布式锁如何实现?有哪些使用场景?](#3-redis分布式锁如何实现有哪些使用场景) 7 | - [3.1 流行的分布式锁实现方案有哪些?有什么区别以及适用场景有哪些不同?](#3.1-流行的分布式锁实现方案有哪些?有什么区别以及适用场景有哪些不同) 8 | - [4. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?](#4-假如redis里面有1亿个key其中有10w个key是以某个固定的已知的前缀开头的如何将它们全部找出来) 9 | - [5. 使用过Redis做异步队列么,你是怎么用的?](#5-使用过redis做异步队列么你是怎么用的) 10 | - [6. Redis是怎么持久化的?服务主从数据怎么交互的?](#6-redis是怎么持久化的服务主从数据怎么交互的) 11 | - [6.1 Redis的两种持久化各自优缺点是什么?](#6.1-Redis的两种持久化各自优缺点是什么) 12 | - [6.2 生产线上这两种持久化方式是如何运行的?或者说是二者该如何选择?](#6.1-生产线上这两种持久化方式是如何运行的?或者说是二者该如何选择) 13 | - [7. Redis为什么会这么快?](#7-Redis为什么会这么快) 14 | - [8. Pipeline有什么好处,为什么要用pipeline?](#8-pipeline有什么好处为什么要用pipeline) 15 | - [9. Redis的同步机制了解么?](#9-redis的同步机制了解么) 16 | - [10. 是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?](#10-是否使用过redis集群集群的高可用怎么保证集群的原理是什么) 17 | - [11. Redis中主从之间数据是怎么同步的?](#11-Redis中主从之间数据是怎么同步的) 18 | - [12. Redis的内存淘汰机制?](#12-Redis的内存淘汰机制) 19 | - [13. 并发地操作Redis中的数据会有什么问题?如何解决?](#13-并发地操作Redis中的数据会有什么问题?如何解决) 20 | - [14. Redis和Memcached的区别?为什么使用Redis?](#14-Redis和Memcached的区别?为什么使用Redis) 21 | - [15. Redis线程模型了解么?介绍一下](#15-Redis线程模型了解么?介绍一下) 22 | - [16. 在集群模式下,Redis的Key是如何寻址的?分布式寻址都有哪些算法?了解一致性Hash算法吗?](#16-在集群模式下,Redis的Key是如何寻址的?分布式寻址都有哪些算法?了解一致性Hash算法吗) 23 | - [17. Redis常见性能问题和解决方案](#17-Redis常见性能问题和解决方案) 24 | - [18. MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?](#18-MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据) 25 | - [19. 在什么样的场景下可以充分的利用Redis的特性,大大提高Redis的效率?](#19-在什么样的场景下可以充分的利用Redis的特性,大大提高Redis的效率) 26 | - [20. 如何保证缓存和数据库数据的一致性?](#20-如何保证缓存和数据库数据的一致性) 27 | 28 | 29 | 30 | ## 1. Redis的基础数据结构 31 | 32 | Redis的基础数据结构包括了:String、Hash、List、Set、SortedSet,这是最基本的数据结构了。但如果是Redis的中高级用户,还需要添加上下面几种数据结构: 33 | 34 | - HyperLogLog 35 | - Geo 36 | - Pub/Sub 37 | 38 | 亮点!加分!可以继续说出下面的数据结构: 39 | 40 | - Redis Module 41 | - BloomFilter 42 | - RedisSearch 43 | - Redis-ML 44 | 45 | 46 | ### 1.1 扩展(详细知识) 47 | 48 | 代办... 49 | 50 | ## 2. 如果有大量的key需要设置同一时间过期,一般需要注意些什么? 51 | 52 | 如果大量key的过期时间设置过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象,而如果严重的话会出现缓存雪崩现象。一般需要在时间上加一个 53 | 随机值,使得过期时间分散一些。 54 | 55 | 电商首页经常会使用定时任务刷新缓存,可能大量的数据失效时间都十分集中,如果失效时间一样,又刚好在失效的时间点大量用户涌入,就有可能造成缓存雪崩。 56 | 57 | > 在机票白屏销售系统中,首页会缓存大量的热点航线搜索结果,如果此时热点航线缓存同时失效,而又刚好有大量用户涌入,则会造成缓存雪崩。 58 | 59 | ## 3. Redis分布式锁如何实现?有哪些使用场景? 60 | 61 | > 先拿setnx来争抢锁,抢到锁后再用expire给锁加一个过期时间防止锁忘记了释放。 62 | 63 | 由于这两不操作不是原子性的,即在setnx之后,expire之前发生了进程中断或者是redis程序重启维护了,拿回怎样解决呢? 64 | 65 | > 我记得set指令有非常复杂的参数,这个应该可以同时把setnx和expire合成一条指令来用,保证了原子性 66 | 67 | 扩展下setnx、expire、set这三个指令的详细知识 68 | 69 | ### 3.1 流行的分布式锁实现方案有哪些?有什么区别以及适用场景有哪些不同? 70 | 71 | ## 4. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来? 72 | 73 | 可以使用keys指令扫出指定模式的key列表。当然keys指令用到线上,是会出现严重的问题的! 74 | 75 | > 如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? 76 | 77 | 这个时候你要回答Redis关键的一个特性:Redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。 78 | 79 | 不过,增量式迭代命令也不是没有缺点的:举个例子, 使用 SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于 SCAN 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。 80 | 81 | 82 | ## 5. 使用过Redis做异步队列么,你是怎么用的? 83 | 84 | 一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。 85 | 86 | > 【追问】如果对方追问可不可以不用sleep呢? 87 | 88 | list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。 89 | 90 | > 【追问】如果对方接着追问能不能生产一次消费多次呢? 91 | 92 | 使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。 93 | 94 | > 【追问】如果对方继续追问 pub/su b有什么缺点? 95 | 96 | 在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等。 97 | 98 | > 【追问】如果对方究极TM追问Redis如何实现延时队列? 99 | 100 | 使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。 101 | 102 | ## 6. Redis是怎么持久化的?服务主从数据怎么交互的? 103 | 104 | RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。 105 | 106 | 这里很好理解,把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好了,服务器重启的时候先把表的数据全部搞进去,但是他可能不完整,你再回放一下日志,数据不就完整了嘛。不过Redis本身的机制是 AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件城后,Redis启动成功;AOF/RDB文件存在错误时,Redis启动失败并打印错误信息 107 | 108 | > 【追问】对方追问那如果突然机器掉电会怎样? 109 | 110 | 取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。 111 | 112 | > 【追问】对方追问RDB的原理是什么? 113 | 114 | 你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。 115 | 116 | 注:回答这个问题的时候,如果你还能说出AOF和RDB的优缺点,我觉得我是面试官在这个问题上我会给你点赞,两者其实区别还是很大的,而且涉及到Redis集群的数据同步问题等等。想了解的伙伴也可以留言,我会专门写一篇来介绍的。 117 | 118 | ## 7. Redis为什么会这么快? 119 | 120 | ## 8. Pipeline有什么好处,为什么要用pipeline? 121 | 122 | 可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。 123 | 124 | ## 9. Redis的同步机制了解么? 125 | 126 | Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。 127 | 128 | ## 10. 是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么? 129 | 130 | 131 | Redis Sentinal 着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 132 | 133 | Redis Cluster 着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。 134 | 135 | ## 11. Redis中主从之间数据是怎么同步的? 136 | 137 | ## 12. Redis的内存淘汰机制? 138 | 139 | ## 13. 并发地操作Redis中的数据会有什么问题?如何解决? 140 | 141 | ## 14. Redis和Memcached的区别?为什么使用Redis? 142 | 143 | ## 15. Redis线程模型了解么?介绍一下 144 | 145 | ## 16. 在集群模式下,Redis 的 Key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 Hash 算法吗? 146 | 147 | ## 17. Redis常见性能问题和解决方案 148 | 149 | ## 18. MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据? 150 | 151 | ## 19. 在什么样的场景下可以充分的利用Redis的特性,大大提高Redis的效率? 152 | 153 | ## 20. 如何保证缓存和数据库数据的一致性? 154 | -------------------------------------------------------------------------------- /notes/面试/SpringCloud复习.md: -------------------------------------------------------------------------------- 1 | ## 1. 什么是SpringCloud? 2 | 3 | SpringCloud是一系列框架的集合,它通过SpringBoot开发的便利性巧妙地简化了分布式系统的搭建、开发,如服务发现与注册、配置中心、负载均衡、断路器以及数据监控等都可以通过SpringBoot来一键启动和部署。 4 | 5 | ## 2. SpringCloud的核心组件有哪些? 6 | 7 | - Eureka:服务注册于发现 8 | - Feign:基于动态代理机制,根据注解和选择的机器,拼接请求url地址,发起请求 9 | - Ribbon:实现负载均衡 10 | - Hystrix:提供线程池,让不同的服务走不同的线程,实现不同服务间调用的隔离,避免了服务雪崩的问题 11 | - Zuul:网关管理,由Zuul网关转发请求给对应的服务 12 | 13 | ## 3. SpringCloud和Dubbo 14 | 15 | SpringCloud和Dubbo都是现在主流的微服务架构 16 | 17 | ### 3.1 注册中心的区别 18 | 19 | 首先,SpringCloud注册中心是Eureka,主要维护的是AP,即可用性和分区容错性;而Dubbo维护的是CP,即一致性和分区容错性;在一个分布式系统中,有一个CAP定理,即C(一致性,Consistency)、A(可用性,Availability)、P(分区容错性,Partition tolerance),但是这三者只能同时实现两点,不可能三者兼顾。而在分布式系统中,P是必须保证的,所以对于一个分布式系统应用来说,要么就是CP、要么就是AP。 20 | 21 | > 分区容错性 22 | 23 | 分区容错性约束了一个分布式系统需要具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。 24 | 25 | > 一致性 26 | 27 | 在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。在一直性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。 28 | 29 | > 可用性 30 | 31 | 可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 32 | 33 | > eureka 34 | 35 | 在SpringCloud中,使用eureka来作为注册中心。eureka分为了eureka-server和eureka-client。 36 | 37 | ``` 38 | 每一个微服务中都有eureka client,用于服务的注册于发现 39 | (服务的注册:把自己注册到eureka server) 40 | (服务的发现:从eureka server获取自己需要的服务列表) 41 | 42 | 每一个微服务启动的时候,都需要去eureka server注册 43 | 44 | 当A服务需要调用B服务时,需要从eureka服务端获取B服务的服务列表,然后把列表缓存到本地,然后根据ribbon的客户端负载均衡规则,从服务列表中取到一个B服务,然后去调用此B服务 45 | 当A服务下次再此调用B服务时,如果发现本地已经存储了B的服务列表,就不需要再从eureka服务端获取B服务列表,直接根据ribbon的客户端负载均衡规则,从服务列表中取到一个B服务,然后去调用B服务 46 | 47 | 微服务,默认每30秒,就会从eureka服务端获取一次最新的服务列表 48 | 49 | 如果某台微服务down机,或者添加了几台机器, 50 | 此时eureka server会通知订阅他的客户端,并让客户端更新服务列表, 51 | 而且还会通知其他eureka server更新此信息 52 | 53 | 心跳检测,微服务每30秒向eureka server发送心跳, 54 | eureka server若90s之内都没有收到某个客户端的心跳,则认为此服务出了问题, 55 | 会从注册的服务列表中将其删除,并通知订阅它的客户端更新服务列表, 56 | 而且还会通知其他eureka server更新此信息 57 | ``` 58 | 59 | > zookeeper 60 | 61 | ``` 62 | zookeeper也可以作为注册中心,用于服务治理(zookeeper还有其他用途,例如:分布式事务锁等) 63 | 每启动一个微服务,就会去zk中注册一个临时子节点, 64 | 例如:5台订单服务,4台商品服务 65 | (5台订单服务在zk中的订单目录下创建的5个临时节点) 66 | (4台商品服务在zk中的商品目录下创建的4个临时接点) 67 | 68 | 每当有一个服务down机,由于是临时接点,此节点会立即被删除,并通知订阅该服务的微服务更新服务列表 69 | (zk上有watch,每当有节点更新,都会通知订阅该服务的微服务更新服务列表) 70 | 71 | 每当有一个新的微服务注册进来,就会在对应的目录下创建临时子节点,并通知订阅该服务的微服务更新服务列表 72 | (zk上有watch,每当有节点更新,都会通知订阅该服务的微服务更新服务列表) 73 | 74 | 每个微服务30s向zk获取新的服务列表 75 | ``` 76 | 77 | > zookeeper和eureka的区别 78 | 79 | eureka基于AP 80 | 81 | zookeeper基于CP 82 | 83 | 由于作为注册中心可用性的需求要高于一致性,所以eureka貌似要比zookeeper更合理一些 84 | 85 | 86 | 1. ZooKeeper保证的是CP,Eureka保证的是AP 87 | 88 | ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的 89 | Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的 90 | 自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务 91 | Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用) 92 | 当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性) 93 | Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪 94 | 95 | 2. ZooKeeper有Leader和Follower角色,Eureka各个节点平等 96 | 97 | 3. ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题 98 | 99 | 4. Eureka本质上是一个工程,而ZooKeeper只是一个进程 100 | 101 | 102 | 103 | ## 4. Dubbo的RPC和SpringCloud的REST 104 | 105 | 106 | ## 5. SpringCloud相比于Dubbo有哪些优势?有哪些缺点?二者的优缺点? 107 | 108 | > SpringCloud的优点 109 | 110 | - 每一个服务足够内聚,代码容易理解 111 | - 开发效率提高,一个服务只做一件事 112 | - 微服务能够被小团队单独开发 113 | - 微服务是松耦合的,是有功能意义的服务 114 | - 可以用不同的语言开发,面向接口编程 115 | - 易于与第三方集成 116 | - 微服务只是业务逻辑的代码,不会和HTML,CSS或者其他界面组合 117 | 118 | 开发中,两种开发模式 119 | 前后端分离 120 | 全栈工程师 121 | 122 | - 可以灵活搭配,连接公共库/连接独立库 123 | 124 | > SpringCloud的缺点 125 | 126 | - 分布式系统的复杂性 127 | - 多服务运维难度,随着服务的增加,运维的压力也在增大 128 | - 系统部署依赖 129 | - 服务间通信成本 130 | - 数据一致性 131 | - 系统集成测试 132 | - 性能监控 133 | 134 | ## 6. 135 | -------------------------------------------------------------------------------- /notes/面试/复习.md: -------------------------------------------------------------------------------- 1 | ## Java并发 2 | 3 | ### 1. LinkedBlockingQueue 4 | 5 | 1. 底层使用的是链表+ReentrantLock+Condition来实现的 6 | 2. 在LinkedBlockingQueue中包含了两个Condition对象,一个是notFull,另一个是notEmpty;如果LinkedBlockingQueue里对象是线程的话,notFull阻塞的是put操作的线程,如果是notEmpty的话,则阻塞的是take操作的线程; 7 | 3. 对于LinkedblockingQueue, put操作底层原理是先用可重入锁锁住,所以是线程安全的。锁住之后还用一个AtomicInteger类型的count来进行队列元素计数操作,如果put操作时判断count等于队列容量时,则notFull.await阻塞当前put操作的线程;如果其他其他线程从notFull中唤醒了此线程,并且此线程获得了CPU执行权后,则从notFull.await返回,执行入队操作,并且调用AtomicInteger的自增操作,并且释放掉可重入锁。接着判断AtomicInteger.getAndIncrement()结果的返回值c (方法开头设的初始值为-1),如果成功则返回0,则表示队列中已经存入了一个新的元素(i++ 先获取i的值,然后自增),可以唤醒notEmpty中等待的take线程。 8 | 4. 对于LinkedBlockingQueue,take操作也是使用的可重入锁来锁住核心逻辑,然后使用一个AtomicInteger来进行来进行计数(整个阻塞队列共用)。一个线层进来后先拿到可重入锁,然后判断count是否为0,为0的话则调用notEmpty.await()阻塞住当前线程;其他线程唤醒了notEmpty中阻塞的take线程后,并且获取到了CPU执行权后,则从notEmpty的await()方法返回,然后调用出队方法,随后调用count.getAndDecrement()方法,返回结果给c,随后在finally块中释放锁。最后再判断c==capacity,如果为true则表示已经有线程从队列中获取了元素,则去唤醒notFull阻塞队列中阻塞的线程,可以进行put操作了。 9 | 5. LinkedBlockignQueue常见操作有:add, put, take, offer, peek等 10 | 11 | ### 2. SynchronousQueue 12 | 13 | 1. 底层使用的是堆栈和队列数据结构 14 | 2. SynchronousQueue不存储数据,其本身没有容量大小,即如果放入一个数据到SynchronousQueue中,方法调用是不能立即返回的,必须等到其他线程把数据消费调才能返回 15 | 3. 先进后出的堆栈和先进先出的队里都是有SynchronousQueue内部的一个Transfer内部类实现的,它们都共同调用了transfer方法 16 | 4. 如果没有特别指出,SynchronousQueue默认使用的是堆栈作为底层数据结构实现,因为堆栈的效率比队列的效率更高 17 | 5. 对于堆栈来说,其定义的SNode节点中有一个SNode match作为属性判断,如果SynchronousQueue中put了一个元素进来,则match会有值,此时take操作的线程就会判断到match中有数据,则可以执行take操作 18 | 6. SynchronousQueue的阻塞操作是有SNode中的Thread的线程阻塞方法来实现的 19 | 7. take和put这两个方法共用transfer,transfer中会判断是否有数据传进来,如果有则表示的是put操作,否则是take操作 20 | 8. SynchronousQueue底层阻塞实际用的是LockSupport.park操作,而更深的底层就是使用的是UNSAFE.park 21 | 22 | ### 3. DelayQueue 23 | 24 | 不同于阻塞队列,延迟队列指的是延迟一段时间后再执行队列操作。 25 | 26 | 1. DelayQueue是非常有意思的队列,底层使用了优先队列进行排序和超时阻塞实现了延迟队列 27 | 2. PriorityQueue使用的是堆排序(小根堆) 28 | 3. 超时阻塞使用的是锁的等待能力,这里的锁仍然是ReentrantLock可重入锁 29 | 4. PriorityQueue对于优先级的比较是通过delay值来进行比较的,delay值表示的是过期时间 30 | 31 | ### 4. ArraysBlockingQueue 32 | 33 | 按字面意思来说,ArraysBlockingQueue指的是数组阻塞队列 34 | 35 | 1. ArraysBlockingQueue是有界的阻塞队列,数组容量一旦被创建,后序则无法修改,这一点和普通的数组结构的类不一样,是不能进行动态扩容的 36 | 2. 队列中元素是有顺序的,先入先出进行排序,从队尾插入数据,从对头拿出数据 37 | 3. 队列满时,往队列中put数据会被阻塞;队列为空时,从队列中take数据会被阻塞 38 | 4. 队列中用的锁也是ReentrantLock,进行put和take操作时,就通过ReentrantLock来锁住 39 | 5. 对于put和take操作的阻塞,是由Condition来存储的;put阻塞于notFull的Condition中,take阻塞于notEmpty的Condition中。 40 | 41 | ### 5. Lock接口 42 | 43 | 1. 锁是用来控制多个线程访问共享资源的一种方式,一般而言锁是放置多个线程同时访问共享资源(但是特殊的锁除外,如多写锁可以并发的访问共享资源)。 44 | 2. Lock接口的出现,是为了解决synchronize关键字实现的锁的缺陷,因为synchronize不能显示调用加锁和释放锁。Lock接口的出现,提供了锁获取、锁释放、可中断获取锁以及超时获取锁等多种synchronize不具备的特性。 45 | 3. 响应中断的意思是,例如一个线程获得了某个共享资源的执行权,即获得了锁,在执行到一半时被其他线程调用了interrupt方法,或者其他中断操作,导致当前线程被中断,会抛出一个InterruptedException异常来响应中断。 46 | 4. Lock接口的tryLock方法会去非阻塞获取锁,如果获取成功则返回true,否则返回false;tryLock方法还可以设置超时时间,而此时该方法调用线程有三种状态:① 超时时间内获取锁;② 超时,获取锁失败,返回false;③ 超时时间被中断,返回InterruptException; 47 | 5. Lock接口还定义了Condition.newCondition()方法,用于获取阻塞队列;当获线程获取了锁后,可以调用Condition对象的wait()方法阻塞自己,然后释放当前线程的锁。 48 | 49 | ### 6. AbstractQueuedSynchronizer(同步器) 50 | 51 | AQS是JUC里的核心,是构建锁和其他同步组件的核心框架。 52 | 53 | 1. AQS里有一个int的成员变量表示同步状态:state,它是有volatile修饰的。 54 | 2. AQS里通过一个内置的同步队列来维持线程排队获取资源的工作,即获取state状态。 55 | 3. 其他同步器或者锁需要继承AQS来管理同步状态,因而实现自己的同步或者锁逻辑,而同步或者锁逻辑的实现则避免不了要修改同步状态,而AQS提供了setState()和getState()、compareAndSetState()来进行操作。 56 | 4. 同步器是实现锁的关键;同步器是面向锁开发者的;锁而是面向使用者的。开发则需要通过同步器实现锁的语义,而使用着则不关心锁底层逻辑。同步器就像锁匠,需要在底层实现锁的机制;使用者就想购买锁的用户,只要锁和解锁的功能。 57 | 5. AQS的核心功能一,独占式获取同步状态tryAquire(int args)。 58 | 6. AQS的核心功能二,独占式释放同步状态tryRelease(int args)。 59 | 7. AQS的核心功能三,共享式获取同步状态tryAcquireShared(int args)。 60 | 8. AQS的核心功能四,共享式释放同步状态tryReleaseShared(int args)。 61 | 9. AQS的同步队列通过管理内部的一个同步队列(FIFO的双向队列)来完成对同步状态的管理,该同步队列的实现逻辑为如下过程 62 | - ① 当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列中(尾部),同时阻塞当前线程。 63 | - ② 当同步状态释放时,会把队列首节点中的线程唤醒,使其再次尝试获取同步状态。 64 | - ③ 同步状态中的节点内保存着的是获取同步状态失败的线程(被阻塞等待),等待状态,前驱结点和后继节点。 65 | - ④ 节点的等待状态waitStatus包含如下:CANCELLED(waitStatus值为1 ,由于同步队列中等待的线程因为等待超时或者是被中断,需要从同步队列中取消等待);SIGNAL(waitStatus值为-1,后继节点的线程处于等待状态,而当前线程如果释放了同步状态或者是被取消了,将会通知后继节点,使后继节点的线程得以运行)。CONDITION(waitStatus值为-2,节点在阻塞队列中,节点线程阻塞在Condition阻塞队列中,当其他获取了锁的线程调用Condition.signal()方法后,该节点将会从阻塞队列中转移到同步队列中,加入到同步状态的获取中);PROPAGATE(waitStatus值为-3,表示下一次共享同步状态的获取将会无条件地传播下去);INITIAL(值为0,初始状态) 66 | - ⑤ 由于在获取锁时,是有多个线程一起竞争同步状态state的,但只能有一个线程能获取到同步状态,而其他线程则会获取失败,因而会被构造成同步队列的节点,一起被加入到同步队列的尾部,此时需要保证添加到同步队列尾部的线程安全性,因此AQS提供了一个compareAndSetTail()方法来实现。 67 | - ⑥ 同步队列首节点是获取同步状态成功的节点 68 | 69 | ### 7. 线程池 70 | 71 | 四种拒绝策略: 72 | 73 | 1. AbortPolicy 74 | 75 | 默认的拒绝策略,直接抛出:RejectedExecutionException异常。 76 | 77 | 2. CallerRunsPolicy 78 | 79 | 将任务返还给调用者线程执行 80 | 81 | 3. DiscardPolicy 82 | 83 | 直接跑起无法处理的任务,不处理也不报错(渣男模式)。 84 | 85 | 4. DiscardOldestPolicy 86 | 87 | 抛弃阻塞队列中排队时间最长的任务,然后把当前任务添加到队列中尝试再次提交任务。 88 | 89 | > Future、FutureTask、Callable、Runnable 90 | 91 | FutureTask继承至RunableFuture,而RunnableFuture又分别继承Runnable和Future,所以FutureTask既是Runnable又是Future。 92 | 93 | FutureTask构造方法可以将Runnable封装成Callable,通过Executor.callable(Runnable)方法来调用,其内部是通过RunnableAdapter适配器来实现的将 94 | Runnable转化为Callable对象。 95 | 96 | ### 8. 线程 97 | 98 | #### JMM 99 | 100 | 什么是JMM(Java内存模型)?Java内存模型就是描述了程序中各种变量(共享变量)的访问规则,以及在JVM中将共享变量存储到内存中以及从内存中读取(共享变量)的底层细节。由于在计算机中,CPU的处理速度比内存的处理速度远高的多,所以此时需要引入一个折中的方案来平衡二者,即引入了高速缓存来过度。 101 | 102 | > 基于高速缓存的存储与交互很好地解决了处理器和内存的速度矛盾,但是却引入了新的问题,即缓存一致性问题。 103 | 104 | 在计算机中,存在CPU => 高速缓存 => 缓存一致性协议 => 主内存 以及 主内存 => 缓存一致性协议 => 高速缓存 => CPU。理解起来就是变量从内存中读取然后在CPU中交互,需要经过缓存一致性协议以及高速缓存。需要注意,现在计算机都是多CPU的,所以多个CPU需要获取变量,都需要从主内存中获取。 105 | 106 | 所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。 107 | 108 | 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。 109 | 110 | > 线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。 111 | 112 | 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。 113 | 114 | 正是因为这样的机制,才导致了可见性问题的存在,那我们就讨论下可见性的解决方案。 115 | 116 | > 为啥加锁可以解决可见性问题呢? 117 | 118 | 因为某一个线程进入synchronized代码块前后,线程会获得锁,清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁。 119 | 120 | 而获取不到锁的线程会阻塞等待,所以变量的值肯定一直都是最新的。 121 | 122 | > Volatile修饰共享变量 123 | 124 | 每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,如果他操作了数据并且写会了,他其他已经读取的线程的变量副本就会失效了,需要都数据进行操作又要再次去主内存中读取了。 125 | 126 | volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。 127 | 128 | 是不是看着加一个关键字很简单,但实际上他在背后含辛茹苦默默付出了不少,我从计算机层面的缓存一致性协议解释一下这些名词的意义。 129 | 130 | 之前我们说过当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致,举例说明变量在多个CPU之间的共享。 131 | 132 | 如果真的发生这种情况,那同步回到主内存时以谁的缓存数据为准呢? 133 | 134 | 为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol等。 135 | 136 | > MESI(缓存一致性协议) 137 | 138 | 当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。 139 | 140 | > 总线嗅探 141 | 142 | 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。 143 | 144 | 什么是总线呢? 145 | 146 | 总线是计算机各种功能部件之间,传送信息的公共通信干线。 147 | 148 | 但是,要注意总线风暴!!! 149 | 150 | 由于Volatile的MESI缓存一致性协议,需要不断的从主内存嗅探和cas不断循环,无效交互会导致总线带宽达到峰值。 151 | 152 | 所以不要大量使用Volatile,至于什么时候去使用Volatile什么时候使用锁,根据场景区分。 153 | 154 | > 指令重排序 155 | 156 | 什么是重排序?为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。 157 | 158 | .... 太多了,参考:[https://mp.weixin.qq.com/s/Oa3tcfAFO9IgsbE22C5TEg](https://mp.weixin.qq.com/s/Oa3tcfAFO9IgsbE22C5TEg) 159 | 160 | 161 | ### 9. 红黑树 162 | 163 | 详情请看博客[https://www.cnblogs.com/hello-shf/p/11364565.html](https://www.cnblogs.com/hello-shf/p/11364565.html) 164 | 165 | 166 | 1. 借助2-3树来理解红黑树。一棵2-3树是一颗绝对平衡的二叉树,2-3树有2节点的也有3节点类型的。对于2-3树的插入操作,需要通过融合、左旋或者右旋以及分裂来维护绝对平衡性。 167 | 2. 红黑树定义 168 | - 每个节点要么是红色,要么是黑色 169 | - 红黑树根节点是黑色的 170 | - 每个叶子节点(红黑树中叶子节点指的是最后一个没有子节点的节点)是黑色的 171 | - 如果一个节点是红色的,那么它的孩子都是黑色的 172 | - 从任意一个节点到叶子节点经过的黑色节点都是一样的 173 | 174 | ### 10. 数据库 175 | 176 | #### 密集索引和稀疏索引 177 | 178 | > InnoDB密集索引 179 | 180 | - 若一个主键被定义,该主键则作为密集索引 181 | - 若没有主键被定义,该表的第一个唯一非空索引则作为密集索引 182 | - 若不满足以上条件,innodb内部会生成一个隐藏主键(密集索引) 183 | - 非主键索引存储相关键位和其对应的主键值,包含两次查找 184 | 185 | InnoDB使用的是密集索引;而MyISAM使用的是非聚集索引 186 | 187 | #### 如何定位并优化慢查询sql 188 | 189 | 具体步骤? 190 | 191 | 1. 根据慢查询日志定位慢查询sql 192 | 193 | 通过show variables like '%quer%' 来看下 slow_query_log是否开启,以及slow_query_file看下慢日志存储地址,最后还要看下long_query_time指定超过多少秒后就被定义为慢查询。通常会将long_query_time设置为1秒。 194 | 195 | 通过show status like '%show_queries%'; 来查看慢查询数量。 196 | 197 | 通过set global slow_query_log = on;来设置开启慢查询; 198 | 通过set global long_query_time = 1;来设置慢查询超时时间,配置此配置之后需要重新连接MySQL实例; 199 | 最好去MySQL配置库里的配置。 200 | 201 | 1. 根据慢查询日志定位慢查询sql 202 | 2. 使用explain等工具分析sql 203 | 3. 修改sql或者尽量让sql走索引 204 | 205 | ### 11 ThreadLocal 206 | 207 | 作用:用于存储线程本地的副本变量,为了达到线程隔离的目的;另外用于确保线程安全; 208 | 209 | > ThreadLocal的使用场景 210 | 211 | 1. 线程隔离。由于用户访问应用程序是通过线程来实现的,所以不同的用户访问应用程序会通过不同的线程来访问,而可以通过ThreadLocal来确保同一个线程内访问获得的用户信息是相同的,同时也不会影响其他线程的用户信息。所以ThreadLocal可以确保线程隔离性。 212 | 2. 线程资源一致性。对于这个场景,在JDBC中也使用到了。在JDBC中会通过ThreadLocal来确保线程资源的一致性。当用户通过客户端去访问应用程序时,会从连接池中分配一个Connection连接用于进行数据库交互,该线程会拿着这个Connection去访问搜索服务、订单服务、商品服务等等,这里就需要确保一次请求调用链的Connection是相同的,这里就是线程资源的一致性问题。如果Connection都不相同,则就没法控制事务了。除了JDBC中的应用,在SqlSessionManager中也同样使用了ThreadLocal,对于SqlSession的存储,都是通过ThreadLocal来实现的。 213 | 3. 线程安全。线程安全性就是确保在Thread中能有一份自己的本地变量,为了确保安全性,则通过ThreadLocal来确保的。相比于直接加Synchronize、Lock加锁,ThreadLocal的效率更高。 214 | 215 | 216 | > ThreadLocal是如何保证线程隔离的? 217 | 218 | 首先得知道,在ThreadLocal中存在一个ThreadLocalMap,这个map是用于ThreadLocal的自定义HashMap,其内部定义了一个Entry类。在ThreadLocal中还存在一个Entry数组,用来存储这个类的实例对象。类似于HashMap,ThreadLocalMap同样拥有初始值,拥有扩容机制。 219 | 220 | 说这么多,其实真正的原因是:每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别人没办法拿到,从而实现了隔离。 221 | 222 | 223 | > ThreadLocal中的WeakReference? 224 | 225 | 对于ThreadLocalMap中的内部类:Entry,继承了WeakReference,这表示的是一个弱引用,即在进行GC时,无论内存是否够用都会把WeakReference引用指向的对象给回收掉。 226 | 227 | 那为什么要将key设置成弱引用呢? 228 | 229 | key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。 230 | 231 | > Netty中的解决方案 232 | 233 | 补充一点:ThreadLocal的不足,我觉得可以通过看看netty的fastThreadLocal来弥补,大家有兴趣可以康康。 234 | 235 | 236 | ## 偏向锁、轻量级锁、自旋锁、重量级锁 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /notes/面试/秒杀架构实现.md: -------------------------------------------------------------------------------- 1 | ## 1. 1核2G的服务器最大线程数应该设置为多少? 2 | 3 | 答,1核2G的服务器最大线程数应该在100左右;4核8G的服务器最大线程数在800左右(经过实际压测得出的结果)。 4 | 5 | 需要注意这里的最大线程树和"线程池的最大线程数不同",对于IO密集型服务器,线程池最好开启2*CPU核数个线程(最大线程);对于CPU密集型服务器,则线程池最好开启N*CPU核心个数(最大线程池)。 6 | 7 | ## 2. 对于秒杀场景,我们关心的是读(访问查询商品)和写(下单商品)这两不操作, 8 | 9 | ## 3. 在大型的应用集群中若对redis访问过度依赖,是否会产生应用服务对redis访问的带宽产生瓶颈,若产生瓶颈,如何解决这样的问题? 10 | 11 | 针对读请求导致的性能瓶颈,只需要在数据写入过程中复制一份,数据就变成两份,数据读能力就扩展了一倍。 12 | 写请求,redis是key-value存储结构,通过对写请求key做sharding,分散到不同的master实例上,解决写的瓶颈问题 13 | 14 | ## 4. redis系统中的热key和大key 该如何处理? 15 | 16 | > 大key 17 | 18 | 大key会影响到服务的性能,比如value非常大的或者集合元素非常多的,那么如何发现呢? 19 | 20 | 在redis中,对于大可以,可以通过scan来扫描, 21 | 22 | 不过最新版本redis是有删除优化的功能,会自动删除大key。 23 | 24 | > 热key 25 | 26 | 对于热key,可以通过: 27 | 1. 建立二级缓存; 28 | 2. 热key备份,然后分摊到多态redis中分摊压力; 29 | 30 | ## 5. 未优化前,交易性能瓶颈在哪? 31 | 32 | 和MySQL直接的IO过多,包括了查商品、查用户、下订单、减库存等等操作,会出现库存行锁问题。 33 | 34 | 问题就在于同步操作库存,会有库存行锁问题出现。所以需要引入异步化操作,即队列。对于扣减库存操作需要通过消息队列来实现 35 | 异步扣减。 36 | 37 | 在这个过程中,是先发送扣减库存的一个消息,然后再扣减redis缓存中的一个库存数量,而这都是需要保证事务一致性的。 38 | 39 | ## 6. 秒杀下单接口会被脚本不停的刷,该如何处理?秒杀下单和下单前的验证逻辑冗余了,该如何解决? 40 | 41 | 秒杀验证逻辑复杂,对交易系统产生无关联负载。 42 | 43 | 对此,需要一个秒杀令牌机制来限制下单接口被刷。 44 | 45 | - 秒杀接口需要依靠令牌才能进入 46 | - 秒杀的令牌由秒杀活动木块负责生成 47 | - 秒杀活动模块对秒杀令牌生产全权负责 48 | - 秒杀下单前需要先获得秒杀令牌 49 | 50 | ## 7. 什么是秒杀大闸原理? 51 | 52 | - 依靠秒杀令牌的授权原理定制化发牌逻辑,做到大闸功能 53 | - 根据秒杀商品初始库存办法对于数量令牌,控制大闸浏览 54 | - 用户风控策略前置到秒杀令牌发放中 55 | - 库存售罄判断前置到秒杀令牌发放中 56 | 57 | ## 8. 队列泄洪原理 58 | 59 | - 排队有些时候比并发更加高效(例如redis单线程模型,innodb mutex key等) 60 | - 依靠排队去限制并发流量 61 | - 依靠排队和下游拥塞窗口程度调整队列释放流量 62 | - 为了避免A端服务并发大,但是B端对接服务并发小的情况,对请求进行限制(保护下游服务) 63 | 64 | 不过需要注意的是,对于设置线程池为20最大线程数,表示的是最大并发请求为20,但不表示TPS为20。 65 | 并发和TPS是两个维度。 66 | 67 | ## 9. CompletableFuture 68 | 69 | 1. 底层实现了UNSAFE,调用了UNSAFE相关的CAS操作,除了get和join方法,都是无锁操作 70 | 2. CompletableFuture提升了开发人员的异步编程能力,方便开发异步编程代码 71 | 3. CompletableFuture底层大致的原理就是通过提供的线程池来运行指定任务,然后通过设置回调方法来获取线程执行结果,而main或者请求线程可以继续往下执行, 72 | 而不用像FutureTask这类api需要不断轮询判断任务是否已经执行完成了。 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /notes/面试/面经.md: -------------------------------------------------------------------------------- 1 | ## 东信北邮面经 2 | 3 | (Java一年开发经验) 4 | 5 | 记一年Java开发经验面经,公司为东信北邮成都研发中心,直接上面经: 6 | 7 | 一面1h40min: 8 | 9 | 1. 线程池核心参数有哪些? 10 | 2. 线程池线程数该怎么分配?依据是什么? 11 | 3. Future、FutureTask、Runnable、Callable有什么区别? 12 | 4. 你们线上处理数据的服务有多少台?分配了多少内存? 13 | 5. 你们系统中下单的时候为什么要用线程池?可以用其他的实现方案吗? 14 | 6. 搜索时延这么高,该如何进行优化?如何提高响应速度?如何优化以提升用户体验度? 15 | 7. 规定给出的并发量外,如果有额外的流量访问进来了,如何做熔断处理? 16 | 8. Redis基础数据有哪些? 17 | 9. Redis获取所有key用什么命令?线上使用会有什么后果? 18 | 10. Redis集群了解吗?cluster和sentinel知道吗? 19 | 11. Redis为什么这么快? 20 | 12. Synchronize,底层是如何实现的?和ReentrantLock相比有什么区别? 21 | 13. JDK中偏向锁、自旋锁、轻量级锁、重量级锁的区别? 22 | 14. JDK锁自旋的自旋阈值了解吗?如何调整自旋次数? 23 | 15. 了解GC吗?说一下GC算法有哪些?垃圾收集器有哪些? 24 | 16. 使用标记整理算法有什么弊端? 25 | 17. 你简历上说有JVM内存调优经验,你是怎么进行的JVM内存调优? 26 | 18. 说一下双亲委派模型?如何破坏双亲委派模型?知道Tomcat中是如何违背双亲委派模型的吗? 27 | 19. MySQL中索引触发条件有哪些?不触发索引的条件有哪些? 28 | 20. 了解MySQL的主从同步机制吗? 29 | 21. InnoDB有哪些特点?和MyISAM有什么区别? 30 | 22. B+树底层实现原理?时间复杂度是多少? 31 | 23. 一条记录插入MySQL中,InnoDB底层是如何执行的? 32 | 24. 链表查询数据的时间复杂度是多少?插入和删除呢?插入一条数据是怎么个过程? 33 | 25. 说一下快排思路,了解双指针(双路)快排吗?说一下双路快排的思路? 34 | 26. 了解RabbitMQ吗?说一下底层? 35 | 27. 了解Vue吗?用过Vue吗? 36 | 28. 四级过了没? 37 | 38 | 39 | ## 同程艺龙面经 40 | 41 | (Java一年开发经验) 42 | 43 | 一面1h: 44 | 45 | 1. 请求响应断连如何解决?请求下单之后,网络断开了,我们这边请求没接收到下单的结果怎么办? 46 | 2. 重复出票怎么处理?重复下单怎么处理? 47 | 3. 分布式锁如何实现的?redis分布式锁和zk分布式锁有什么区别? 48 | 4. redis分布式锁实现的话,setnx和expire命令,如果A线程将expire操作操作到了B线程了,也就是expire了另一个线程的资源,这种怎么避免? 49 | 5. 线程池是什么? 50 | 6. Future是什么?有什么作用? 51 | 7. 线程池核心数是20个,最大线程数是40个,讲一下任务进入线程池的原理逻辑? 52 | 8. 线程池阻塞队列有哪几种? 53 | 9. 优先队列了解吗?优先队列底层实现机制? 54 | 10. 你说下LinkedBlockingQueue底层阻塞的原理是什么? 55 | 11. 你说一下ReentrantLock和Synchronize的区别? 56 | 12. ReentrantLock和读写锁的区别? 57 | 12. AQS里的setState()是什么时候调用? 58 | 13. 讲一下锁的升级策略? 59 | 14. HTTP和HTTPS的区别? 60 | 15. 对称加密和非对称加密有什么区别?HTTPS是用到了那种加密方式? 61 | 16. 看你简历说是熟悉JVM,那JVM有哪几种GC算法? 62 | 17. 新生代中为什么会有From Survice和To Survice区? 63 | 18. 使用CMS垃圾收集器时,遇到大对象比较多导致频繁GC,该如何解决这种情况? 64 | 19. GCRoot是什么?哪些对象是GCRoot? 65 | 20. MySQL中有哪些索引类型? 66 | 21. B+树和B树有什么区别? 67 | 22. 联合索引什么情况下会失效? 68 | 23. 最左匹配原则是什么? 69 | 24. MySQL有哪几种隔离级别? 70 | 25. 幻读是什么?RR隔离级别能解决幻读吗?那RR隔离级别是怎么解决幻读的? 71 | 26. 你知道间隙锁吗?间隙锁的作用是什么?是怎么解决幻读的? 72 | 27. SpringAOP了解吗?有哪几种实现方式? 73 | 28. CGLIB什么情况下会失效? 74 | 29. 为什么要同时重写equals和hashcode? 75 | 76 | 二面50min: 77 | 78 | 1. 你在项目中学习到了哪些?或者是遇到了哪些困难?怎么解决的? 79 | 2. 如何保证你的调用链路的稳定性?如何保证服务调用链路请求的幂等性? 80 | 3. mq消息的幂等性如何保证? 81 | 4. mq如何保证顺序消费? 82 | 5. 线程池创建有哪几种方式? 83 | 6. 线程池核心参数有哪些? 84 | 7. 线程池中拒绝策略有几种?分别说一下各自的作用? 85 | 8. Redis都用到了哪些场景?除了缓存还有其他场景吗? 86 | 9. 缓存雪崩、穿透、击穿是什么?如何解决? 87 | 10. 对于穿透解决方案给key对应的value对写为null、未知错误、稍后重试时,如何解决当数据库真的有对应数据时,缓存数据没更新导致正式用户请求访问不到缓存? 88 | 11. 你刚刚是讲到了布隆过滤器吗?讲一下布隆过滤器的作用以及底层原理? 89 | 12. 如何保证缓存和数据库数据一致性? 90 | 13. 对实时性要求比较高的缓存如何保证和数据库数据一致性? 91 | 14. 在Redis中常用的数据结构有哪些? 92 | 15. Redis中Hash结构的扩容机制是什么?那你可以讲讲HashMap的扩容机制吗? 93 | 16. Redis中的过期机制有哪几种?底层都是如何实现的? 94 | 17. Redis的部署有哪些方式? 95 | 18. Redis的cluster和Setinel实现原理是什么? 96 | 19. 数据库的查询优化、排查慢sql以及sql优化是怎么进行的?详细点说明下 97 | 20. InnoDB中哈希索引实现机制是什么? 98 | 21. MySQL中唯一索引和聚镞索引相比,性能如何? 99 | 22. 除了常用排序算法,你还会用到什么算法?或者了解什么算法? 100 | 23. Redis中跳表的原理了解吗? 101 | 24. 项目中用过自定义SpringBoot starter吗?starter是如何实现的? 102 | 25. 有没有做过或了解过大批量数据迁移,类似于大批量MySQL数据迁移到ES中的操作? 103 | 26. 分布式事务了解吗?你们项目中都用到了哪些分布式事务?都有哪些优缺点? 104 | 27. 说一下JVM内存模型有哪些?说一下JVM的内存区域? 105 | 28. 在Java中,有哪些包是直接定义在对外内存中的? 106 | 29. 线上做过哪些性能排查以及性能优化的操作? 107 | 30. 了解Java中的happen-before原则吗? 108 | 31. 除了用锁、volatile之外,如何保证一个线程的变量能被另外一个线程的变量给实时读取到? 109 | 110 | 111 | --------------------------------------------------------------------------------