diff --git a/.gitignore b/.gitignore index 4f63cd8..e706d04 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +*.DS_Store diff --git "a/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.md" "b/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.md" new file mode 100644 index 0000000..b22b4b4 --- /dev/null +++ "b/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.md" @@ -0,0 +1,594 @@ +# JVM 实用参数(基于 Java 6 的 HotSpot JVM) + +## JVM 类型以及编译器模式 + +### -server & -client + +- 有两种类型的 HotSpot JVM,一般我们开发默认安装都是 server 端。类比起来,server 端更像是 JDK,client 更像是 JRE,client 端占用更少的内存和启动时间,但是不像服务端的 VM 会为堆空间提供更大的空间和并行的垃圾收集器 +- java -server + + - 指定虚拟机为服务端,一般我们安装默认就是这个,也推荐使用服务端 VM + - JVM功效学:在 JVM 启动的时候根据可用的硬件和操作系统来自动的选择JVM的类型。 + +- java -client + + - 在一个 32 位的系统上,HotSpot JDK 可以运行服务端 VM,但是 32 位的 JRE 只能运行客户端 VM。 + +### -version and -showversion + +- 查看安装的 Java 版本和 JVM 类型 + + - mixed mode,混合模式,HotSpot 默认的运行模式 + + - JVM在运行时可以动态的把字节码编译为本地代码。 + + - class data sharing,类数据共享 + + - 一种在只读缓存(在 jsa 文件中,Java Shared Archive)中存储 JRE 的系统类,被所有 Java 进程的类加载器用来当做共享资源。类数据共享(Class data sharing)可能在经常从 jar 文档中读所有的类数据的情况下显示出性能优势。 + +- java -version + + - 参数在打印完上述信息后立即终止 JVM + +- java -showversion + + - 输出相同的信息,但是 -showversion 紧接着会处理并执行 Java 程序。 + +### -Xint & -Xcomp & -Xmixed + +- java -Xint + + - 解释模式(interpreted mode)下,-Xint 标记会强制 JVM 执行所有的字节码,这会降低运行速度,通常低 10 倍或更多。 + +- java -Xcomp + + - 与 -Xint 正好相反,JVM 在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。但是 -xcomp 没有让 JVM 启用 JIT 编译器的全部功能,所以很多应用在使用 -Xcomp 也会有一些性能上的损失。 + +- java -Xmixed + + - 混合模式 -Xmixed,最新版本的 HotSpot 的默认模式就是混合模式,运行时间长的应用,建议使用 JVM 的默认设置,让 JIT 编译器充分发挥其动态潜力,JIT 编译器是组成 JVM 最重要的组件之一,也正是因为 JVM 在这方面的进展才让 Java 不再那么慢。 + +## 参数分类和即时(JIT)编译器诊断 + +### 参数分类 + +- 标准参数 + + - 功能和输出的参数都是很稳定的,在将来的 JVM 版本中也基本不会改变。可以用 java -help 检索出所有标准参数。 + +- X 参数 + + - 非标准化的参数,意思就是在将来的版本中可能会改变。所有的这类参数都以 -X 开始,可以用 java -X 来检索,但是不能保证所有参数都可以被检索出来。 + +- XX 参数,「-XX:」 + + - XX参数,也是不标准的,X 参数的功能是十分稳定的,而 XX 参数是属于仍在实验当中的。XX 参数不应该在不了解的情况下使用。 + - 语法 + + - 对于布尔类型的参数,有「+」或「-」,然后才设置 JVM 选项的实际名称。例如,-XX:+ 用于激活 选项,而 -XX:- 用于注销选项。 + - 对于需要非布尔值的参数,如 String 或者 Integer,先写参数的名称,后面加上「=」,最后赋值。例如, -XX:= 赋值 。 + +### JIT 编译方面的一些 XX 参数 + +- java -XX:+PrintCompilation + + - 输出一些关于从字节码转化成本地代码的编译过程。 + +- java -XX:+CITime + + - 可以在 JVM 关闭时得到各种编译的统计信息。 + +- java -XX:+UnlockExperimentalVMOptions + + - 当设置一个特定的 JVM 参数时,JVM 会在输出「Unrecognized VM option」后终止。如果发生了这种情况,应该首先检查是否输错了参数。然而,如果参数输入是正确的,并且 JVM 并不识别,或许就需要设置 -XX:+UnlockExperimentalVMOptions 来解锁参数。 + +- -XX:+LogCompilation + + - 把扩展的编译输出写到「hotspot.log」文件中,但是需要使用 -XX:+UnlockExperimentalVMOptions 来解锁。 + +- -XX:+PrintOptoAssembly + + - 把编译器线程生成的本地代码被输出并写到「hotspot.log」文件中,使用这个参数要求运行的服务端 VM 是 debug 版本。 + +## 打印所有XX参数及值 + +### java -XX:+PrintFlagsFinal + +- 表格的每一行包括五列,来表示一个 XX 参数。第一列表示参数的数据类型,第二列是名称,第四列为值,第五列是参数的类别。第三列「=」表示第四列是参数的默认值,而「:=」表明了参数被用户或者 JVM 赋值了。 + +### java -XX:+PrintFlagsInitial + +- 查看所有参数的默认值 + +### 查询已经被赋值「:=」的参数 + +- java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Java类名称 | grep ":" + +### java -XX:+PrintCommandLineFlags + +- 让 JVM 打印出那些已经被用户或者 JVM 设置过的详细的 XX 参数的名称和值。建议 –XX:+PrintCommandLineFlags 这个参数应该总是设置在JVM启动的配置项里。 + +## 内存调优 + +### 堆内存划分 + +- 新生代(young generation):存储着新分配的和较年轻的对象 +- 老年代(old generation):存储着长寿的对象 +- 永久代(permanent generation):存储着那些需要伴随整个 JVM 生命周期的对象。比如,已加载的对象的类定义或者 String 对象内部缓存 +- G1 垃圾回收器:模糊了新生代和老年代之间的区别 + +### -Xms and -Xmx (or: -XX:InitialHeapSize & -XX:MaxHeapSize) + +- -Xms + + - 初始堆内存 + +- -Xmx + + - 最大堆内存 + +### -XX:+HeapDumpOnOutOfMemoryError & -XX:HeapDumpPath + +- 分析堆内存快照(Heap Dump)是一个很好的定位内存溢出问题手段。堆内存快照会默认保存在 JVM 的启动目录下名为 java_pid.hprof 的文件里( 就是 JVM 进程的进程号) +- -XX:+HeapDumpOnOutOfMemoryError + + - 让 JVM 在发生内存溢出时自动的生成堆内存快照。 + +- -XX:HeapDumpPath= + + - 改变默认的堆内存快照生成路径,可以相对路径也可以是绝对路径 + +### -XX:OnOutOfMemoryError + +- 接受一串指令和它们的参数,当发生内存溢出的时候,可以执行一些指令来达到清理或通知的监控行为。 +- 示例:当内存溢出错误发生的时候,我们会将堆内存快照写到 /tmp/heapdump.hprof 文件并且在 JVM 的运行目录执行脚本 cleanup.sh + + java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" 项目、工程名称 + +### -XX:PermSize & -XX:MaxPermSize + +- 永久代在堆内存中是一块独立的区域,它包含了所有 JVM 加载的类的对象表示。 +- -XX:MaxPermSize + + - 设置永久代大小的最大值 + +- -XX:PermSize + + - 设置永久代初始大小 + +### -XX:InitialCodeCacheSize & -XX:ReservedCodeCacheSize + +- 代码缓存:存储已编译方法生成的本地代码。 + + 如果代码缓存被占满,JVM 会打印出一条警告消息,并切换到 interpreted-only 模式(解释模式):JIT 编译器被停用,字节码将不再会被编译成机器码。 + +- 字节值 + +### -XX:+UseCodeCacheFlushing + +- 当代码缓存被填满时让 JVM 放弃一些编译代码,通过使用 -XX:+UseCodeCacheFlushing 这个参数,可以避免当代码缓存被填满的时候 JVM 切换到 interpreted-only 模式(解释模式)。 + +## 额外杂乱未分类总结 + +### 调整最小堆容量个最大堆容量 + +- -Xms256M + + - ms 全称 memory start,代表最小堆容量 + +- -Xmx256M + + - mx 全称 memory max,代表最大堆容量 + +- -X 表示它是 JVM 运行参数 +- 线上环境中,一般都是将 JVM 的 Xms 和 Xmx 设置成一样的大小,避免在 GC 后调整堆大小时带来的额外压力 + +### 配置计数器的值到达某个阈值的时候,对象从新生代晋升至老年代 + +- -XX:MaxTenuringThreshold +- 默认设置为 15,如果配置为 1,则直接晋升至老年代 + +### 让 JVM 遇到 OOM 异常时能输出堆内信息 + +- -XX:HeapDumpOnOutOfMemoryError + +### 设置永久代空间 + +- -XX:MaxPermSize=1280m + +### 启用 G1 垃圾收集器 + +- -XX:UseG1GC + +### 收集器相关参数 + +- -XX:Survivorratio +- -XX:PretenureSizeThreshold +- -XX:HandlePromotionFailure + +### 默认指定 ParNew 收集器 + +- -XX:+UseConcMarkSweepGC +- -XX:+UseParNewGC + +### ParNew 收集器相关 JVM 参数 + +- 默认指定 ParNew 收集器 + + - -XX:+UseConcMarkSweepGC + - -XX:+UseParNewGC + +- 限制垃圾收集的线程数 + + - -XX:ParallelGCThreads + +### 设置新生代空间的大小 + +- -Xmn + +### 设置 Eden 与 Survivor 的比例 + +- -XX:SurvivorRatio + +### Parallel Scavenge 收集器相关 JVM 参数 + +- 控制最大垃圾收集停顿时间 + + - -XX:MaxGCPauseMillis + +- 直接设置吞吐量大小 + + - -XX:GCTimeRatio + +- 自动设置新生代栈帧参数,GC 自适应调节策略 + + - -XX:+UseAdaptiveSizePolicy + - 开启这个开关就不需要手工设置新生代的大小(-Xmn)、Eden 与 Survivor 的比例、晋升老年代对象的年龄 + +### 查看 GC 日志 + +- -XX:PrintGCDetails + +### 在 Serial 和 ParNew GC 回收期中,晋升年龄阈值通过参数「MaxTenuringThreshold」设定 + +### Java GC 日志可以通过 +PrintGCDetails开启 + +### CMS 为了避免这个阶段没有等到 Minor GC 而陷入无限等待,提供了参数「CMSMaxAbortablePrecleanTime」,默认为 5s,含义是如果可中断的预清理执行超过5s,不管发没发生 Minor GC,都会中止此阶段,进入重新标记阶段。 + +### CMS 提供 CMSScavengeBeforeRemark 参数,用来保证重新标记阶段之前强制进行一次 Minor GC。 + +### 在启动时观察加载了哪个 Jar 包中的哪个类 + +- -XX:_TraceClassLoading + +## 新生代垃圾回收 + +对象一般出生在 Eden 区,年轻代 GC 过程中,对象在 2 个幸存区之间移动,如果对象存活到适当的年龄,会被移动到老年代。当对象在老年代死亡时,就需要更高级别的 GC,更重量级的 GC 算法。 + +### 把堆划分为新生代和老年代的好处 + +- 简化了新对象的分配(只在新生代分配内存) +- 可以更有效的清除不再需要的对象 +- 新生代和老年代使用不同的 GC 算法 + + - 新生代使用复制算法 + +### 新生代 + +- 伊甸园区(Eden) + + - 新对象会首先分配在 Eden 中 + - 如果新对象过大,会直接分配在老年代中 + +- From 幸存区(survivor) +- To 幸存区(survivor) + + - GC 前保持清空 + - GC 运行时,Eden 中的幸存对象被复制到这里 + +### -XX:NewSize &-XX:MaxNewSize + +- -XX:NewSize + + - 设置新生代初始大小 + +- -XX:MaxNewSize + + - 新生代设置的越大,老年代区域就会减少。一般不允许新生代比老年代还大,最大可以设置为 -Xmx/2。 + +### -XX:NewRatio + +- 设置老年代与新生代的比例 +- -XX:NewRatio=3 指定老年代/新生代为3/1。老年代占堆大小的 3/4 ,新生代占 1/4。 +- 设置新生代和老年代的相对大小的这种方式优点是「新生代大小会随着整个堆大小动态扩展」 + +### -XX:SurvivorRatio + +- 指定伊甸园区(Eden)与幸存区大小比例 +- -XX:SurvivorRatio=10 表示伊甸园区(Eden)是幸存区 To 大小的 10 倍(也是幸存区 From 的 10 倍) +- 目标:最小化短命对象晋升到老年代的数量,也最小化新生代 GC 的次数和持续时间。 + +### -XX:+PrintTenuringDistribution + +- 指定 JVM 在每次新生代 GC 时,输出幸存区中对象的年龄分布。 +- 老年代阀值:意思是对象从新生代移动到老年代之前,经过几次 GC(即对象晋升前的最大年龄) + +### -XX:InitialTenuringThreshold、-XX:MaxTenuringThreshold & -XX:TargetSurvivorRatio + +- -XX:InitialTenuringThreshold + + - 设定老年代阀值的初始值 + +- -XX:MaxTenuringThreshold + + - 设定老年代阀值的最大值 + +- -XX:TargetSurvivorRatio + + - 设定幸存区的目标使用率 + +- 常见场景 + + - 如果从年龄分布中发现,有很多对象的年龄持续增长,在到达老年代阀值之前。这表示 -XX:MaxTenuringThreshold 设置过大。 + - 如果 -XX:MaxTenuringThreshold 的值大于 1,但是很多对象年龄从未大于 1。应该看下幸存区的目标使用率。如果幸存区使用率从未到达,这表示对象都被 GC 回收,这正是我们想要的。 如果幸存区使用率经常达到,有些年龄超过 1 的对象被移动到老年代中。这种情况,可以尝试调整幸存区大小或目标使用率。 + +### -XX:+NeverTenure and -XX:+AlwaysTenure + +- -XX:+NeverTenure + + - 对象永远不会晋升到老年代,但是这种情况至少会浪费至少一半的堆内存 + +- -XX:+AlwaysTenure + + - 没有幸存区,所有对象在第一次 GC 时,会晋升到老年代。 + +## 吞吐量收集器 + +### 评估一个垃圾收集(GC)算法好坏的标准 + +- 吞吐量(throughput)越高算法越好 + + 应用程序线程用时占程序总用时的比例。 + +- 暂停时间(pause times)越短算法越好 + + 一个时间段内应用程序线程让 GC 线程执行而完全暂停的平均时间 + +### 老年代垃圾收集算法 + +- 第一类算法试图最大限度地提高吞吐量,停止-复制停止-复制算法,Parallel Old 收集器 +- 第二类算法试图最小化暂停时间,标记-清除算法,CMS +- G1 垃圾收集算法 + +### 面向吞吐量垃圾收集算法有关的重要JVM配置参数 + +- -XX:+UseSerialGC + + - 激活串行垃圾收集器,例如单线程面向吞吐量垃圾收集器。推荐用于只有单个可用处理器核心的 JVM。 + +- -XX:+UseParallelGC + + - 告诉 JVM 使用多线程并行执行年轻代垃圾收集。 + +- -XX:+UseParallelOldGC + + - 「old」实际上是指年老代,所以 -XX:+UseParallelOldGC 要优于 -XX:+UseParallelGC:除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集。 当期望高吞吐量,并且JVM有两个或更多可用处理器核心时,建议使用。 + +- -XX:ParallelGCThreads + + - 指定并行垃圾收集的线程数量 + + java -XX:ParallelGCThreads=6 + +- -XX:-UseAdaptiveSizePolicy + + - 停用一些人体工程学 + + 通过人体工程学,垃圾收集器能将堆大小动态变动像 GC 设置一样应用到不同的堆区域,只要有证据表明这些变动将能提高 GC 性能。 + +- -XX:GCTimeRatio + + - 告诉 JVM 吞吐量要达到的目标值,数学角度就是 -XX:GCTimeRatio=N 指定了目标应用程序线程的执行时间(与总的程序执行时间)达到 N/(N+1) 的目标比值。默认值 99 + + 通过 -XX:GCTimeRatio=9 我们要求应用程序线程在整个执行时间中至少 9/10 是活动的(因此,GC 线程占用其余 1/10)。 + +- -XX:MaxGCPauseMillis + + - 告诉 JVM 最大暂停时间的目标值(以毫秒为单位)。 + +## CMS 收集器 + +### 主要目标:低应用停顿时间 + +### CMS 收集器的 GC 周期 + +- 初始标记 + + - 为了收集应用程序的对象引用需要暂停应用程序线程,该阶段完成后,应用程序线程再次启动。 + +- 并发标记 + + - 从第一阶段收集到的对象引用开始,遍历所有其他的对象引用。 + +- 并发预清理 + + - 改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果。 + +- 重标记 + + - 由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。这一阶段十分重要,因为必须避免收集到仍被引用的对象。 + +- 并发清理 + + - 所有不再被应用的对象将从堆里清除掉。 + +- 并发重置 + + - 收集器做一些收尾的工作,以便下一次 GC 周期能有一个干净的状态。 + +### CMS 收集器的问题 + +- 堆碎片 + + - 没有足够连续的空间完全容纳对象 + +- 对象分配率高 + + - 并发模式失败:如果获取对象实例的频率高于收集器清除堆里死对象的频率,并发算法将再次失败。从某种程度上说,老年代将没有足够的可用空间来容纳一个从年轻代提升过来的对象。 + +### -XX:+UseConcMarkSweepGC + +- 激活 CMS 收集器,HotSpot JVM 默认使用的就是并行收集器。 + +### -XX:UseParNewGC + +- 激活年轻代使用多线程并行执行垃圾回收,当使用 -XX:+UseConcMarkSweepGC 时,-XX:UseParNewGC会自动开启。 + +### -XX:+CMSConcurrentMTEnabled + +- 并发的 CMS 阶段将以多线程执行(也就是多个 GC 线程会与所有的应用程序线程并行工作),默认开启。 + +### -XX:ConcGCThreads + +- 定义并发 CMS 过程运行时的线程数,默认值:ConcGCThreads = (ParallelGCThreads + 3)/4 + +### -XX:CMSInitiatingOccupancyFraction + +- 代表老年代堆空间的使用率 +- -XX:CMSInitiatingOccupancyFraction==75 意味着第一次 CMS 垃圾收集会在老年代被占用 75% 时被触发,CMSInitiatingOccupancyFraction 的默认值为 68。 + +### -XX:+UseCMSInitiatingOccupancyOnly + +- 命令 JVM 不基于运行时收集的数据来启动 CMS 垃圾收集周期,当该标志被开启时,JVM 通过 CMSInitiatingOccupancyFraction 的值进行每一次 CMS 收集,而不仅仅是第一次。 + +### -XX:+CMSClassUnloadingEnabled + +- CMS 收集器默认不会对永久代进行垃圾回收,该参数是实现对永久代进行垃圾回收。但是永久代耗尽空间 JVM 还是会尝试进行垃圾回收 + +### -XX:+CMSIncrementalMode + +- 开启 CMS 收集器的增量模式。 +- 增量模式:incremental mode, +其实就是进行并发标记、清理时让 GC 线程、用户线程交替运行,尽量减少 GC 线程独占 CPU 资源的时间。 + + 其实这种模式没什么太大的优化,况且增量模式经常暂停 CMS 过程,以便对应用程序线程作出完全的让步。因此,收集器将花更长的时间完成整个收集周期。因此,只有通过测试后发现正常 CMS 周期对应用程序线程干扰太大时,才应该使用增量模式。 + +### -XX:+ExplicitGCInvokesConcurrent & -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses + +- -XX:+ExplicitGCInvokesConcurrent + + - 命令 JVM 无论什么时候调用系统 GC,都执行 CMS GC,而不是 Full GC。 + +- -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses + + - 保证当有系统 GC 调用时,永久代也被包括进 CMS 垃圾回收的范围内。 + +### -XX:+DisableExplicitGC + +- 告诉 JVM 完全忽略系统的 GC 调用(不管使用的收集器是什么类型) + +### -XX:+UseCMSCompactAtFullCollection + +- 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理 + +### -XX:+CMSParallelRemarkEnabled + +- 降低标记停顿,为了减少第二次暂停的时间,开启并行 remark + +### -XX:CMSFullGCsBeforeCompaction + +- 在上一次 CMS 并发 GC 执行过后,到底还要再执行多少次 full GC 才会做压缩,默认是 0。 +- 比如 CMSFullGCsBeforeCompaction 配置为 10,就是每隔 10 次真正的 full GC 才做一次压缩,注意:不是每 10 次 CMS 并发 GC 就做一次压缩 + +## GC日志 + +准确记录了每一次的 GC 的执行时间和执行结果,通过分析 GC 日志可以优化堆设置和 GC 设置,或者改进应用程序的对象分配模式。 + +### -XX:+PrintGC OR -verbose:gc + +- 开启简单的 GC 日志模式,为每一次新生代(young generation)的 GC 和每一次的 Full GC 打印一行信息。 + +### -XX:PrintGCDetails + +- 开启详细的 GC 日志模式 + +### -XX:+PrintGCTimeStamps和-XX:+PrintGCDateStamps + +- 将时间和日期也加到 GC 日志中,表示自 JVM 启动至今的时间戳也会被添加到每一行中。 + +### -Xloggc + +- 输出到指定的文件 + +### 可管理的JVM参数 + +- 对于这些参数,可以在运行时修改他们的值。 +- 「PrintGC」开头的参数都是可管理的参数,任何时候都可以开启或是关闭 GC 日志。 + +## JVM 垃圾收集器参数总结 + +### UseSerialGC + +- 虚拟机运行在 Client 模式下的默认值,打开此开关后,使用 Serial + Serial Old 的收集器组合进行内存回收 + +### UseParNewGC + +- 打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收 + +### UseConcMarkSweepGC + +- 打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收 + +### UseParallelGC + +- 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old 的收集器组合进行内存回收。 + +### UseParallelOldGC + +- 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行回收 + +### SurvivorRatio + +- 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden:Survivor = 8:1 + +### PretenureSizeThreshold + +- 直接晋升到来年代的对象大小,设置这个参数后,大于这个参数对象将直接在老年代分配 + +### MaxTenuringThreshold + +- 晋升到老年代的对象年龄。每个对象再坚持过一次 Minor GC 之后,年龄就增加,当超过这个参数值时就进入老年代 + +### UseAdaptiveSizePolicy + +- 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 + +### HandlePromotionFailure + +- 是否允许分配担保失败,及老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 + +### ParallelGCThreads + +- 设置并行 GC 时进行内存回收的线程数 + +### GCTimeRatio + +- GC 时间占总时间的比率,默认值为 99,即允许 1% 的GC 的时间。仅在使用 Parallel Scavenge 收集器时生效 + +### MaxGCPauseMillis + +- 设置 GC 的最大停顿时间。仅在使用 Parallel Scavenge 收集器时生效 + +### CMSInitiatingOccupancyFraction + +- 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值 68%,仅在使用 CMS 收集器时生效 + +### UseCMSCompactAtFullCollection + +- 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效 + +### CMSFullGCBeforeCompaction + +- 设置 CMS 收集器在进行若干次垃圾收集后在启动一次内存碎片整理。仅在使用 CMS 收集器时生效 + +*glorze.com - 高老四博客* \ No newline at end of file diff --git "a/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.xmind" "b/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.xmind" index d4b2415..2ca64fc 100644 Binary files "a/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.xmind" and "b/JVM/JVM \345\256\236\347\224\250\345\217\202\346\225\260\357\274\210\345\237\272\344\272\216 Java 6 \347\232\204 HotSpot JVM\357\274\211.xmind" differ diff --git "a/JVM/JVM \346\235\202\350\256\260.md" "b/JVM/JVM \346\235\202\350\256\260.md" new file mode 100644 index 0000000..bd898be --- /dev/null +++ "b/JVM/JVM \346\235\202\350\256\260.md" @@ -0,0 +1,565 @@ +# JVM 杂记 + +## 类的加载 + +### 类的加载、连接、初始化 + +- 加载 + + 通过一个类的名字获取此类的二进制字节流,将这个字节流代表的静态存储结构转换为方法区的运行时结构,在内存中生成一个 java.lang.Class 对象,作为方法区这个类的各种数据结构的访问入口。 + +- 连接 + + - 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。 + - 准备:类准备阶段负责为类的类变量分配内存,并设置默认初始值。 + - 解析:将类的二进制数据中的符号引用替换成直接引用。 + +- 初始化:对类变量、静态初始块、类的结构块等进行初始化 + + 一、加入这个类还没有被加载和连接,则程序先加载并连接该类。 + 二、假如该类的直接父类还没有被初始化,则先初始化其直接父类,依次往上进行初始化,所以 JVM 最先初始化的总是 java.lang.Object 类。 + 三、假如类中有初始化语句,则系统一次执行这些初始化语句 + +### 类加载器 + +- new 一个 Object 在内存中占多大? + + - 至少 16 个字节,对象头 Markword 占 8 字节(cafe babe、jdk 版本号),引用类型在 64 位机器上占 4 个字节(不开启指针压缩是8个字节,指针压缩是默认开启的),对齐填充 4 字节,所以至少 16 字节 + +- 双亲委派的优势 + + - 保证 JVM 运行环境中只有一份字节码文件,即保证不会重复加载一个类。 + - 增加程序的安全性,即黑客无法通过重写已经有的类,进行攻击。 + - JAVA 的类随着它的类加载器一起具备了带有优先级的层级关系。 + +- 对象创建过程 + + - 流程:类加载、内存分配、初始化、设置对象头、INIT方法(构造函数) + + - 类加载,需要遇到关键字 new 触发。当虚拟机遇到该关键字的时候,定位到方法区中的常量池,在常量池中寻找 new 对应类的符号引用,并检查该符号引用对应的类是否被类加载系统加载。加载成功进行内存分配,否则继续加载。 + - 内存分配,即 JVM 在堆中进行对象内存分配,对象所需要的内存大小,在类加载过程中就已确定。 + - 初始化,即将内存空间初始化为默认值,包括对象字段。 + - 设置对象头 + + - 存储对象自身运行时的数据,hashCode 码、GC 分代年龄、锁状态标志、线程持有的锁。 + - 类型指针,即对象指向它的元数据的指针,JVM 通过该指针确定这个对象是哪个类的实例。 + + - 执行 INIT 方法,即构造函数。 + +### 自定义类加载器 + +- 无法自定义一个叫 java.lang.System 的类 + + - 会调用自定义类加载器的 loadClass 方法。 + - 我们自定义的 classLoader 必须继承 ClassLoader,loadClass 方法会调用父类的 defineClass 方法。 + - 父类的这个 defineClass 是一个 final 方法,无法被重写 + - 所以自定义的 classLoader 是无论如何也不可能加载到以 java. 开头的类的。 + +### JVM 进程终止 + +- 程序运行正常结束 +- System.exit() 或者 Runtime.getRuntime().exit() +- 异常未捕获或者 error +- 强制结束 JVM 进程或者 JVM 自身故障 + + - JVM 发生致命错误导致崩溃时,会生成一个 hs_err_pid_xxx.log + - 文件包含了导致 JVM crash 的重要信息,生成在工作目录下,也可以通过 JVM 参数指定生成路径 + +- linux 的 OOM killer 杀死 + + - Linux 内核有个机制叫OOM killer,会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。 + - 系统报错日志 + + - /var/log/messages + - egrep -i 'killed process' /var/log/messages + - dmesg | grep java + +- JVM 报 OOM 异常 + + - -XX:+HeapDumpOnOutOfMemoryError + - -XX:HeapDumpPath=*/java.hprof + +### 区别 + +- Class.forName + + - 加载类是将类进了初始化 + - 相当于调用无参构造函数,会调用 static 静态代码来初始化配置 + +- ClassLoader.getSystemClassLoader().loadClass + + - 没有对类进行初始化,只是把类加载到了虚拟机中 + - IOC 的实现就是使用的 ClassLoader + +## JVM 内存模型 + +### JVM 内存区域划分、Java 运行时数据区 + +- 方法区(Method Area) + + - 运行时常量 + - 已被虚拟机加载的类信息 + - 静态变量 + - 即时编译器编译后的代码 + - 运行时常量池 + +- 堆(Heap) + + - 存放对象实例 + - 对象的创建 + + - new 一个对象后,jvm 会先检查类是否已被加载,若未加载则先加载 ,否则在堆区创建该对象。 + - 对象在堆区的存储结构 + + - 对象头 + + - MarkWord + + - hashcode、gc 对象年龄、锁信息 + + - Class Metadata Address + + - 指向对象类型数据的指针 + + - Array Length + + - 数组长度 + + - 实例数据 + - 填充数据 + +- 虚拟机栈(VM Stack) + + - 每个方法执行的同时都会创建一个栈帧,局部变量表、操作数栈、动态连接、返回地址等。 + - 存储局部变量表(boolean、byte、char、short、int、float、long、double、对象引用、returnAddress) + - 方法出口 + - 每个方法从调用直至执行完成的过程,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程。 + - 变量从产生到结束所经历的过程 + + - 局部变量:局部变量随着方法的调用产生和结束,在调用方法的时候会创建栈帧,而栈帧里存在局部变量表,当方法调用结束的时候栈帧销毁,局部变量随着销毁。 + - 常量/静态变量:一般存放在元空间,而元空间的周期与堆相似,声明并赋值后直接放入常量池,之后通过可达性分析判断常量是否存在引用链,如不存在移除常量池。 + +- 本地方法栈(Native Method Stack) + + - 为虚拟机使用到的 Native 方法服务 + +- 程序计数器(Program Counter Register) + + - 唯一一个在 JVM 中没有规定任何 OutOfMemoryError 情况的区域 + - 通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线城恢复等基础功能都需要依赖程序计数器。 + +- 执行引擎 +- 本地库接口 +- 本地方法库 + +### JVM 内存模型 + +- Java 线程 +- 工作内存 +- Save 和 Load 操作 +- 主内存 +- voatile 关键字 + + - 当这个变量的值被修改后,会立即刷新到主内存中,对其他线程可见;当某个线程读取这个变量的时候,也会重新将主内存中的数据刷一份到工作内存中来。保证可见性,但是不保证原子性 + - 禁止指令重排优化,也叫禁止指令重排序.观察voatile变量对应的字节码文件,会发现变量的操作指令后面加了一句lock addl $0x0,(%esp)的操作,这个操作相当于一个内存屏障。 + +- synchronized + + - 当一个线程对一个变量加锁的时候,就会清空这个变量在当前工作内存中的值,因此该关键字同时满足了可见性和原子性。 + +## 垃圾回收与内存分配 + +### 对象的存活 + +- 引用计数法,Java 虚拟机不采用 + + - 基本定义:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。 + - 弊端:很难解决对象之间相互循环引用的问题 + + 对象 objA 和 objB 都有字段 instance,赋值令 objA.instance=objB 及 objB.instance=objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为 0,于是引用计数算法无法通知GC收集器回收它们。 + +- 可达性分析法 + + - 基本思路(图片可拖拽放大) + + - 是通过一系列的称为「GC Roots」的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(或者说从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。 + + - Java 可作为 GC Roots 的对象 + + - 虚拟机栈(栈帧中的本地变量表)中引用的对象 + - 方法区中类静态属性引用的对象 + - 方法区中常量引用的对象 + - 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象 + +- 引用 + + - 基本定义 + + - 如果 reference 引用类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,但是我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。所以在 JDK 1.2 之后重新设计了引用的概念。 + + - 分类 + + - 强引用(Strong Reference) + + - 在程序代码之中普遍存在的,类似「Object obj=new Object()」这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。 + + - 软引用(Soft Reference) + + - 用来描述一些还有用但并非必需的对象。 + + 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。 + + - 弱引用(Weak Reference) + + - 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在 JDK 1.2 之后,提供了 WeakReference 类来实现弱引用。 + + - 虚引用(Phantom Reference) + + - 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。 + - 目的:在这个对象被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。 + +- 无用的类的条件 + + - 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 + - 加载该类的 ClassLoader 已经被回收。 + - 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 + +### 回收算法(heap,堆内存的回收) + +- 标记-清除算法(Mark-Sweep) + + - 首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。标记的方式:可达性分析。 + - 最基础的收集算法,后续所有的算法在此基础上改进而来。 + - 缺点一:效率不高,标记和清除的效率都不高。 + - 缺点二:产生大量的不连续内存碎片,导致程序运行过程中如果分配较大对象时,无法找到足够连续内存而触发垃圾收集动作。 + +- 复制算法(Copying) + + - 将可用内存按容量划分为大小相等的两快,每次只使用其中的一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后已使用过的内存空间一次清理掉,这样使得每次都是对整个半区进行内存回收。 + - HotSpot 虚拟机将内存分为 1 个 Eden 和两个 Survivor 空间,比例 8:1 + - 缺点:将内存玩的只剩下一半了,复制操作比较多,效率比较低。 + +- 标记-整理算法(Mark-Compact) + + - 就是标记之后不直接清除,而是让存活对象向一端移动,最后清理掉边界以外的内存。 + - 适合老年代使用 + +- 分代收集算法 + + - 动态划分算法,根据对象的存活周期划分模块,根据对应的模块采取相应的回收算法。 + - 新生代:因为有大批量的对象死去,存活少量,所以一般采用复制回收算法。 + - 老年代:顾名思义就是对象寿命比较长,所以没有额外空间对其进行分配担保,一般采用「标记-清理」或者标记-整理算法 + - 永久代(Full GC) + + - 永久代是 Hotspot 虚拟机特有的概念,是方法区的一种实现,别的 JVM 都没有这个东西。在Java 8 中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存-元空间。 + +### 垃圾收集器 + +- GC 流程 + + - 当现在有一个新的对象产生,那么对象一定需要内存空间,于是现在就需要为该对象进行内存空间的申请; + - 首先会判断 Eden 区是否有内存空间,如果此时有内存空间,则直接将新对象保存在 Eden 区 + - 但是如果此时 Eden 区的内存空间不足,那么会自动执行一个 MinorGC、Young GC 操作,将 Eden 区的无用内存空间进行清理,清理之后会继续判断 Eden 区的内存空间是否充足?如果内存空间充足,则将新的对象直接在 Eden 区进行空间分配 + - 如果执行了 Minor GC、Young GC 之后发现 Eden 区的内存依然不足,那么这个时候会进行 Survivor 区判断,如果 Survivor 区有剩余空间,则将 Eden 区的部分活跃对象保存在 Survivor ,那么随后继续判断 Eden 区的内存空间是否充足,如果充足,则在 Eden 区进行新对象的空间分配 + - 如果此时 Survivor 区也已经没有内存空间了,则继续判断老年区,如果此时老年区空间充足,则将存活区中的活跃对象保存到老年代,而后 Survivor 区就会出现有空余空间,随后 Eden 区将活跃对象保存在 Survivor 之中,而后在 Eden 区里为新对象开辟空间 + - 如果这个时候老年代也满了,那么这个时候将产生 Major GC(Full GC),进行老年代的内存清理。 + - 如果老年代执行了 Full GC 之后发现依然无法进行对象的保存,就会产生 OOM 异常「OutOfMemoryError」 + +- 两个常见异常原因总结 + + - StackOverflowError + + - 线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常 + - 递归可能造成 StackOverflowError + - 不断创建线程可能造成 StackOverflowError + - 栈的深度(大小类似于弹夹深度)可以自动扩展,扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常 + - 和虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。 + + - OOM + + - 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常 + - 当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常 + - 各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError 异常。 + - 当一个线程抛出 OOM 异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行 + +- G1 + + - 控制回收垃圾的时间:这个是 G1 的优势,可以控制回收垃圾的时间,还可以建立停顿的时间模型,选择一组合适的 Regions 作为回收目标,达到实时收集的目的 + + - JVM 在初始化的时候,会为堆(heap),栈(stack),元数据区(matespace)分配指定的内存大小,JVM 线程启动的时候会向服务器申请指定的内存地址空间进行分配。 + - jdk8 之后,使用了 G1 垃圾回收器,逻辑上依然存在堆、栈、元数据区。 + - 在物理结构上,G1 采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。 + - 在堆的使用上,G1 并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。 + - 启动时可以通过参数 -XX:G1HeapRegionSize=n 可指定分区大小(1MB ~ 32MB,且必须是 2 的幂),默认将整堆划分为 2048 个分区。 + + - 空间整理:和 CMS 一样采用标记-清理(整体上是标记-整理算法)的算法,但是 G1 不会产生空间碎片,这样就有效的使用了连续空间,不会导致连续空间不足提前造成 GC 的触发 + - G1 把 Java 内存拆分成多等份,多个域(Region),逻辑上存在新生代和老年代的概念,但是没有严格区分;Region 最多分为 2048个。 + - 在 CMS 内存中,如果一个对象过大,进入 S1、S2 区域的时候大于改分配的区域,对象会直接进入老年代。G1 处理大对象时会判断对象是否大于一个 Region 大小的 50%,如果大于 50% 就会横跨多个 Region 进行存放 + - 使用 G1 的场景 + + - 实时数据占用超过一半的堆空间 + - 对象分配或者晋升的速度变化大 + - 希望消除长时间的GC停顿(超过0.5-1秒) + +### 内存分配与回收策略 + +- Minor GC + + - 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。 + - 每次 Minor GC 会清理年轻代的内存。 + +- Major GC + + - 清理老年代 + +- Full GC(Stop The World,全世界的暂停) + + - 清理整个堆空间—包括年轻代和老年代。 + - 清理永久代 + + - 废弃常量 + - 无用类 + + - 该类所所有的对象实例已经被回收,也就是 Java 堆中不存在该类的任何实例 + - 加载该类的 ClassLoader 已经被回收 + - 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 + + - 产生条件 + + 频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久带不会溢出。 + + - 大量使用反射 + - 动态代理(CGLib、JDK 动态代理) + + - 触发条件一、显式调用 System.gc() 方法 + - 触发条件二、大对象直接进入老年代,从年轻代晋升上来的老对象,尝试在老年代分配内存时,但是老年代内存空间不够; + - 触发条件三、在执行 Young GC 之前,JVM 会进行空间分配担保——如果老年代的连续空间小于新生代对象的总大小,则触发一次 Full GC。 + +- Young GC + + - 清理年轻代 + - 触发条件:对象优先在新生代 Eden 区中分配,如果 Eden 区没有足够的空间时,就会触发一次 Young GC + +## JVM 常用优化方案 + +参考美团技术团队,原文地址: +https://tech.meituan.com/2017/12/29/jvm-optimize.html + +### 优化前的准备 + +- GC 优化需知 + + - GC相关基础知识 + + - GC 工作原理 + - 理解新生代、老年代、晋升等术语含义 + - 要看懂GC日志 + + - GC 优化不能解决一切性能问题,它是最后的调优手段。 + - JVM 调优可以理解为本质就是为了减少 Full GC的出现。 + +- JVM 基础回顾 + + - JVM 内存结构 + + - 分代回收,针对不同声明周期的对象采取不同的回收方式,提高回收效率 + - 新生代 + + - 大多数对象在新生代中被创建,很对对象声明周期很短。每次 Minor GC 后只有少量对象存活,所以采用复制算法 + - 一个 Eden 区 + + - 大部分对象在 Eden 区中生成,Eden 区满将依然存活的对象复制到两个 Survivor 区中的一个 + + - 两个 Survivor 区 + + - 当一个 Survivor 区满的时候,不满足晋升条件的对象会被复制到另一个 Survivor 区,满足晋升条件的会被放到老年代 + + - 在 Serial 和 ParNew GC 回收期中,晋升年龄阈值通过参数「MaxTenuringThreshold」设定,默认值 15 + + - 老年代 + + - 在新生代中经历了 N 次垃圾回收仍然存活就回被放到老年代,此区域对象存活率高 + - 老年代的垃圾回收叫做「Major GC」,通常使用「标记-整理」、「标记-清除」算法 + - 新生代和老年代一起回收也就是整个堆回收叫做「Full GC」 + + - 常见垃圾回收器 + + - Serial 串行回收器 + + - 单线程回收器,简单、易实现、效率高 + + - ParNew 并行回收器 + + - Serial 的多线程版本,可以充分利用 CPU 资源,减少回收时间 + + - Parallel Scavenge 吞吐量优先回收器 + + - 侧重于吞吐量的控制,吞吐量 = 运行业务代码的时间/(运行业务代码的时间+垃圾回收时间) + + - CMS 并发标记清除回收器 + + - 以获取最短回收停顿时间为目标的回收器,基于「标记-清除」算法实现 + + - G1 回收器 + + - 对分区为一个个相同的「region」的可预测暂停时间的垃圾回收器 + + - GC日志 + +- 参数基本策略 + + - 活跃数据的大小 + + - 应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是 Full GC 后堆中老年代占用空间的大小 + + - 以活跃大小为 300M 为例 + + - 总堆:300 * 4 = 1200M + - 新生代:300 * 1.5 = 450M + - 老年代:1200 - 450 = 750M + +### 优化步骤 + +- 确定目标 + + - 高可用:可用性达到几个 9 + - 低延迟:请求必须多少毫秒内完成响应 + - 高吞吐:每秒完成多少次事务 + +- 优化参数 + + - 合适的 GC 回收器 + - 重新设置内存比例 + - 调整 JVM 参数 + +- 验收结果 + +### GC 优化案例(垃圾回收器均为 ParNew + CMS,CMS 失败时 Serial Old 替补) + +- Major GC和 Minor GC频繁 + + - Minor GC频繁通常由于新生代空间较小,Eden 区很快被填满,导致频繁 Minor GC,可以通过增大新生代空间来降低 Minor GC 的频率 + - 单次 Minor GC 时间更多取决于 GC 后存活对象的数量,而非 Eden 区的大小。 + - 扩容新生代空间后,Minor GC 频率降低,对象在新生代得到充分回收,只有生命周期长的对象才进入老年代。这样老年代增速变慢,Major GC 频率自然也会降低。 + - 根据对象生命周期的分布情况 + + - 如果应用存在大量的短期对象,应该选择较大的年轻代; + - 如果存在相对较多的持久对象,老年代应该适当增大。 + + - 动态年龄计算 + + - Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 Survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值。 + +- 请求高峰期发生 GC,导致服务可用性下降 + + - CMS 四个主要阶段 + + - 初始标记 + + - 进行可达性分析,标记 GC Roots 能直接关联到的对象 + + - 并发标记 + + - 初始标记过得对象重新出发,再次进行标记 + + - 重新标记 + + - Stop The World,重新进行可达性分析 + + - 并发清理 + + - 进行并发的垃圾清理 + + - 新生代 GC 和老年代的 GC 是各自分开独立进行的 + - CMS 为了避免这个阶段没有等到 Minor GC 而陷入无限等待,提供了参数「CMSMaxAbortablePrecleanTime」,默认为 5s,含义是如果可中断的预清理执行超过5s,不管发没发生 Minor GC,都会中止此阶段,进入重新标记阶段。 + - CMS 提供 CMSScavengeBeforeRemark 参数,用来保证重新标记阶段之前强制进行一次 Minor GC。 + +- 发生 Stop The World 的 GC + + - 触发 Full GC 的可能情况 + + - 元空间内存不足 + - CMS GC 时出现 promotion failed 和concurrent mode failure + - 统计得到的 Young GC 晋升到老年代的平均大小大于老年代的剩余空间 + - 主动触发 Full GC 来避免内存空间碎片问题 + + - 启动时将元空间大小固定,避免进行动态扩容。 + - JDK8 及以上版本,已经移除了永久代的概念,字符串常量已经移至到堆内存中,其余内容如类元信息、字段、静态属性、方法、常量等放入堆空间中。区别于永久代,元空间在本地内存中进行分配。 + +### 总结 + +- 进行 GC 优化之前,需要确认项目的架构和代码等已经没有优化空间 +- 不要为了调优而调优,不当的调优可能适得其反 + +## JVM 线程模型 + +### JVM 其实就是操作系统的一种镜像,是软件层次的虚拟机 + +### 在 HotSpot 中的线程模型使用的是一个线程实例(java.lang.Thread)对应一个操作系统线程(这里的对应的是 LWP)。当一个线程实例开始执行 start() 的时候的时候就会创建一个 LWP 进行对应,并且在线程任务结束后回收,其中线程的调度由操作系统负责。 + +- 创建一个堆中的 Thread 对象(java.lang.Thread) +- 在 JVM 内部会创建一个 JavaThread 对象(JVM 内部的,称为 VmThread),用以维护线程当前的状态。同时 VmThread 会持有 java.lang.Thread 的引用及与操作系统对应的 OSThread 的引用 +- JVM 内部会维护一个 OSThread,包含操作系统信息,及与实际操作系统线程对应的 handle。 + +## Java 对象内存布局及占用内存大小计算 + +### 对象的内存布局 + +- 对象头 + + - Mark Word + + - 包含一系列的标记位 + + - 锁状态(默认无锁) + - 对象的 hashCode(25位) + - 对象分代年龄(4位) + - 是否是偏向锁(1位) + - 锁标志位(2位) + + - 在运行期间 Mark Word 里存储的数据会随着锁标志位的变化而变化 + + - 在 32 位系统占 4 字节,在 64 位系统中占 8 字节; + + - Class Pointer + + - 用来指向对象对应的 Class 对象(其对应的元数据对象)的内存地址。 + - 在 32 位系统占 4 字节,在 64 位系统中占 8 字节; + + - Length + + - 如果是数组对象,代表保存数组长度的空间,占 4 个字节; + + - 在 32 位系统下,存放 Class Pointer 的空间大小是 4 字节,MarkWord 是 4 字节,对象头为 8 字节; + - 在 64 位系统下,存放 Class Pointer 的空间大小是 8 字节,MarkWord 是 8 字节,对象头为16字节; + - 64 位开启指针压缩的情况下,存放 Class Pointer 的空间大小是 4 字节,MarkWord 是 8字节,对象头为12字节; + - 如果是数组对象,对象头的大小为:数组对象头 8 字节 + 数组长度4字节 +对齐 4 字节 = 16 字节。其中对象引用占 4 字节(未开启指针压缩的 64 位为 8 字节),数组 MarkWord 为 4 字节(64位未开启指针压缩的为 8 字节); + - 静态属性不算在对象大小内。 + +- 实例数据 + + - 包括了对象的所有成员变量,其大小由各个成员变量的大小决定 + + - byte 和 boolean 是 1 个字节 + - short 和 char 是 2 个字节 + - int 和 float 是 4 个字节 + - long 和 double 是 8 个字节 + - reference 引用类型是 4 个字节(64 位系统中是 8 个字节) + +- 对齐填充 + + - 所有 Java 对象占用 bytes 字节数必须是 8 的倍数 + +- 指针压缩 + + - Ordinary Object Pointer,就是普通对象指针。 + + - 64 位 JVM 消耗的内存会比 32 位的要多大约 1.5 倍,这是因为对象指针在 64 位 JVM 下有更宽的寻址。 + + - -XX:+UseCompressedOops JVM 参数可以启用指针压缩 + - 压缩的对象 + + - 每个 Class 的属性指针(静态成员变量) + - 每个对象的属性指针 + - 普通对象数组的每个元素指针 + diff --git "a/JVM/JVM \346\235\202\350\256\260.xmind" "b/JVM/JVM \346\235\202\350\256\260.xmind" new file mode 100644 index 0000000..9160826 Binary files /dev/null and "b/JVM/JVM \346\235\202\350\256\260.xmind" differ diff --git a/JVM/JVM.xmind b/JVM/JVM.xmind deleted file mode 100644 index 931071e..0000000 Binary files a/JVM/JVM.xmind and /dev/null differ diff --git "a/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 JVM\357\274\210\347\254\254\344\270\211\347\211\210\357\274\211.md" "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 JVM\357\274\210\347\254\254\344\270\211\347\211\210\357\274\211.md" new file mode 100644 index 0000000..656c6b8 --- /dev/null +++ "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 JVM\357\274\210\347\254\254\344\270\211\347\211\210\357\274\211.md" @@ -0,0 +1,942 @@ +# 深入理解 JVM(第三版) + +## 一、走近 Java + +### 1、走近 Java + +- 概述 + + - 摆脱了硬件平台的束缚,实现了「一次编写,到处运行」的理想;它提供了一种相对安全的内存管理和访问机制,避免了绝大部分内存泄漏和指针越界问题;它实现了热点代码检测和运行时编译及优化,这使得Java应用能随着运行时间的增长而获得更高的性能;它有一套完善的应用程序接口,还有无数来自商业机构和开源社区的第三方类库来帮助用户实现各种各样的功能 + +- Java 技术体系 + + - Java 程序设计语言 + - 各种硬件平台上的 Java 虚拟机实现 + - Java 类库 API + - Class 文件格式 + - 来自商业机构和开源社区的第三方 Java 类库 + - JRE(JavaRuntime Environment) + + - Java 类库 API 中的 Java SE API 子集 + - Java 虚拟机 + + - Java 技术体系所包括的内容 +https://pic.imgdb.cn/item/622ed3125baa1a80ababfbca.jpg + +- Java 发展史 + + - Java语言的前身:Oak,James Gosling 博士领导的绿色计划。 + - 1995年5月23日,Oak语言改名为Java,并且在SunWorld大会上正式发布Java 1.0版本。 + - 1996年1月23日,JDK 1.0发布,Java语言有了第一个正式版本的运行环境。 + - 1997年2月19日,Sun公司发布了JDK 1.1,JAR文件格式、JDBC、JavaBeans、RMI等。 + - 1998年12月4日,JDK迎来了一个里程碑式的重要版本:JDK 1.2,Sun在这个版本中把Java技术体系拆分为三个方向,分别是面向桌面应用开发的J2SE(Java 2 Platform,Standard Edition)、面向企业级开发的J2EE(Java 2 Platform,Enterprise Edition)和面向手机等移动终端开发的J2ME(Java 2 Platform,Micro Edition)。 + - 1999年4月27日,HotSpot虚拟机诞生。 + - 2000年5月8日,JDK 1.3发布。 + - 2002年2月13日,JDK 1.4发布 + - 2004年9月30日,JDK 5发布 + - 2006年12月11日,JDK 6发布 + - 2009年2月19日,JDK 7完成了其第一个里程碑版本。 + - JDK 8的第一个正式版本原定于2013年9月发布,最终还是跳票到了2014年3月18日 + - JDK 9最终到2017年9月21日才得以面世。 + - 2018年3月20日,JDK 10如期发布 + - 2018年9月25日,JDK 11发布,这是一个LTS版本的JDK,包含17个JEP,其中有ZGC这样的革命性的垃圾收集器出现,也有把JDK 10中的类型推断加入Lambda语法这种可见的改进 + - 2019年3月20日,JDK 12发布 + +- Java 虚拟机家族 + + - 虚拟机始祖:Sun Classic/Exact VM + - 武林盟主:HotSpot VM + + - HotSpot 虚拟机的热点代码探测能力可以通过执行计数器找出最具有编译价值的代码,然后通知即时编译器以方法为单位进行编译。如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准即时编译和栈上替换编译行为 + + - 小家碧玉:Mobile/Embedded VM + - 天下第二:BEA JRockit/IBM J9 VM + + - IBM J9虚拟机的职责分离与模块化做得比HotSpot更优秀 + + - 软硬合璧:BEA Liquid VM/Azul VM + - 挑战者:Apache Harmony/Google Android Dalvik VM + - 没有成功,但并非失败:Microsoft JVM及其他 + - 百家争鸣 + + - KVM + + - K是「Kilobyte」的意思,它强调简单、轻量、高度可移植,但是运行速度比较慢。 + + - Java Card VM + - Squawk VM + - JavaInJava + - Maxine VM + - Jikes RVM + - IKVM.NET + - JamVM、CacaoVM、SableVM、Kaffe、Jelatine JVM、NanoVM、MRP、Moxie JVM + +- 展望Java技术的未来 + + - Graal VM + + - 在 HotSpot 虚拟机基础上增强而成的跨语言全栈虚拟机,可以作为「任何语言」的运行平台使用 + + - 新一代即时编译器 + + - HotSpot虚 拟机中的两个即时编译器 + + - c1 + + - 编译耗时短但输出代码优化程度较低的客户端编译器 + + - c2 + + - 编译耗时长但输出代码优化质量也更高的服务端编译器 + + - 自JDK 10起,HotSpot中又加入了一个全新的即时编译器:Graal编译器 + + - 向Native迈进 + + - 提前编译是相对于即时编译的概念,提前编译能带来的最大好处是Java虚拟机加载这些已经预编译成二进制库之后就能够直接调用,而无须再等待即时编译器在运行时将其编译成二进制机器码。 + + - 灵活的胖子 + + - 面向各种不同应用场景的全功能Java虚拟机 + + - 语言语法持续增强 + + - Project Loom + + - 准备提供一套与目前Thread类API非常接近的更加轻量级的、由软件自身进行调度的用户线程 + + - Project Valhalla + + - 提供值类型和基本类型的泛型支持,并提供明确的不可变类型和非引用类型的声明。 + + - Project Panama + + - 消弭Java虚拟机与本地代码之间的界线。 + +- 实战:自己编译JDK + + - https://hg.openjdk.java.net/jdk/jdk12/ + +## 二、自动内存管理 + +### 2、Java内存区域与内存溢出异常 + +- 概述 + + - 对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码 + +- 运行时数据区域 +https://pic.imgdb.cn/item/6238088627f86abb2a62d829.jpg + + - 程序计数器(线程私有) + + - 一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。 + - 分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 + - 此内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域。 + + - Java虚拟机栈(线程私有) + + - 生命周期与线程相同 + - 描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 + - 局部变量表 + + - 编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double) + - 对象引用(reference类型) + - returnAddress类型 + - 局部变量槽(Slot) + + - 64位长度的long和double类型的数据会占用两个变量槽 + - 其余的数据类型只占用一个 + - 当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。(槽的数量) + + - StackOverflowError异常 + + - 如果线程请求的栈深度大于虚拟机所允许的深度 + + - OutOfMemoryError异常 + + - 如果Java虚拟机栈容量可以动态扩展,但是栈扩展时无法申请到足够的内存 + + - 本地方法栈(线程私有) + + - 与虚拟机栈所发挥的作用相似,只是虚拟机栈为虚拟机执行Java方法字节码服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。 + + - Java堆(线程共享) + + - 虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,唯一目的就是存放对象实例 + - Java堆可以处于物理上不连续的内存空间中,但在逻辑上应该是连续的,但是向数组这样的以及一些大对象,会要求连续的内存空间 + - Java 堆大小的这是通过参数-Xmx和-Xms设定,主流虚拟均是可扩展的,无法扩展时,抛出OutOfMemoryError异常。 + + - 方法区(线程共享) + + - 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 + - 除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集。 + - 如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。 + - 运行时常量池 + + - 方法区的一部分 + - Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 + - 具备动态性 + + - 并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中 + - String类的intern() + + - 当常量池无法再申请到内存时会抛出OutOfMemoryError异常。 + + - 直接内存 + + - 并不是虚拟机运行时数据区的一部分,但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常 + +- HotSpot虚拟机对象探秘 + + - 对象的创建 + + - 当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程,在类加载检查通过后,接下来虚拟机将为新生对象分配内存。 + - 内存分配方式 + + - 指针碰撞 + + - Java 堆内存规整,使用过的内存和空闲的内存分别放在两边,中间放一个指针分配内存时就将指针向空闲空间方向罗东一段与对象大小相等的距离。 + - Serial、ParNew 垃圾收集器使用简单又高效的指针碰撞 + + - 空闲列表 + + - Java 堆内存不规整,已使用的和空闲的内存交错在一起,虚拟机会维护一个列表进行记录,分配的时候从列表中找到一块足够大的空间划分给对象,并且更新列表 + - CMS 等基于清除算法的垃圾收集器采用空闲列表方式分配内存 + + - 内存分配并发处理 + + - 采用 CAS 配上失败重试的方式更新操作的原子性 + - 把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲 + + - 对象的内存布局 + + - 对象在堆内存中的存储布局 + + - 对象头(Header) + + - 存储对象自身的运行时数据(Mark Word) + + - 哈希码(HashCode) + - GC分代年龄 + - 锁状态标志 + - 线程持有的锁 + - 偏向线程ID + - 偏向时间戳 + - 数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特 + - Mark Word被设计成一个有着动态定义的数据结构,以便在极小的空间内存储尽量多的数据,根据对象的状态复用自己的存储空间。 + + - 对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。 + + - 如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据 + + - 实例数据(Instance Data) + + - 对象真正存储的有效信息 + + - 对齐填充(Padding) + + - 并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。 + - HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,即任何对象的大小都必须是8字节的整数倍。 + - 如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。 + + - 对象的访问定位 + + - 使用一个对象时,Java程序会通过栈上的 reference 数据来操作堆上的具体对象。 + - 对象访问方式 + + - 使用句柄 + + - Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息 + +https://pic.imgdb.cn/item/623bf8ab27f86abb2a0d8ab5.jpg + + - 好处是 reference 中存储的是稳定句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要被修改。 + + - 直接指针 + + - reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销 + +https://pic.imgdb.cn/item/623bf8f027f86abb2a0f3713.jpg + + - 好处是速度更快,它节省了一次指针定位的时间开销 + - HotSpot 使用这种方式 + +- 实战:OutOfMemoryError异常 + + - Java堆溢出 + + - Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。 + + - 异常堆栈信息「java.lang.OutOfMemoryError」会跟随进一步提示『Java heap space』 + + - 处理方法 + + - 通过内存映像分析工具对 Dump 出来的堆转储快照进行分析。 + + - 确认内存中导致OOM的对象是否是必要的,先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow) + + - 内存溢出 + + - 是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。 + + - 内存泄漏 + + - 指长期保持某些资源的引用,垃圾回收器无法回收它,从而造成该资源不能够及时释放,随着程序运行时间的增加,占用存储空间越来越多,致使有效可再利用的存储空间不足,当储存别的资源时引发内存溢出。 + - 内存泄露是造成内存溢出的一个很主要的原因 + + - 如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们 + - 如果不是内存泄漏,也就是内存中的对象确实都是必须存活的,那就应当检查Java虚拟机的堆参数(-Xmx与-Xms)设置,与机器的内存对比,看看是否还有向上调整的空间。 + + - 虚拟机栈和本地方法栈溢出 + + - 两种异常 + + - 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。 + - 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。 + + - HotSpot虚拟机的选择是不支持栈内存动态扩展,除非在创建线程申请内存时就因无法获得足够内存而出现OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。 + + - 方法区和运行时常量池溢出 + + - String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。 + - 方法区的主要职责是用于存放类型的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。 + - JDK 8 元空间设置参数 + + - -XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。 + - -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。 + - -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。 + + - 本机直接内存溢出 + + - 直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致 + +### 3、垃圾收集器与内存分配策略 + +- 概述 + + - 程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。 + - Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。 + +- 对象已死? + + - 引用计数算法 + + - 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。 + - Java虚拟机并不是通过引用计数算法来判断对象是否存活的。 + - 引用计数很难解决对象之间相互循环引用的问题 + + - 可达性分析算法 + + - 通过一系列称为「GC Roots」的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为『引用链』(Reference Chain),如果某个对象到 GC Roots 间没有任何引用链相连,或者用图论的话来说就是从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。 + - 固定作为 GC Roots 的对象 + + - 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。 + - 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。 + - 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。 + - 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。 + - Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。 + - 所有被同步锁(synchronized关键字)持有的对象。 + - 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。 + + - 再谈引用 + + - 引用类型 + + - 强引用(StronglyRe-ference) + + - 强引用是最传统的「引用」的定义,是指在程序代码之中普遍存在的引用赋值,即类似『Object obj=new Object()』这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。 + + - 软引用(Soft Reference) + + - 描述一些还有用,但非必须的对象。 + + - 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。 + + - 弱引用(Weak Reference) + + - 描述那些非必须对象,强度比软引用更弱一些 + + - 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。 + - 当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 + + - 虚引用(Phantom Reference) + + - 也称为「幽灵引用」或者『幻影引用』,它是最弱的一种引用关系。 + + - 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。 + - 为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。 + + - 生存还是死亡 + + - 对象死亡标记过程 + + - 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记 + - 随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法 + + - 对象没有覆盖finalize()方法 + - finalize()方法已经被虚拟机调用过 + + - 如果这个对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。 + + - 任何一个对象的finalize()方法都只会被系统自动调用一次 + + - 回收方法区 + + - 回收内容 + + - 废弃的常量 + - 不再使用的类型 + + - 判定一个类型是否属于「不再被使用的类」 + + - 该类所有的实例都已经被回收,Java 堆中不存在该类及其任何派生子类的实例。 + - 加载该类的类加载器已经被回收 + - 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 + +- 垃圾收集算法 + + - 垃圾收算法分类 + + - 引用计数式垃圾收集(Reference Counting GC),也叫「直接垃圾收集」 + - 追踪式垃圾收集(Tracing GC),也叫「间接垃圾收集」 + + - 分代收集理论 + + - 分代假说 + + - 弱分代假说(Weak Generational Hypothesis) + + - 绝大多数对象都是朝生夕灭的。 + + - 强分代假说(Strong Generational Hypothesis) + + - 熬过越多次垃圾收集过程的对象就越难以消亡。 + + - 多款常用的垃圾收集器的一致的设计原则 + + - 收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。 + + - 跨代引用假说(Intergenerational Reference Hypothesis) + + - 跨代引用相对于同代引用来说仅占极少数。 + + - 收集类型 + + - 部分收集(Partial GC) + + - 新生代收集(Minor GC/Young GC) + + - 指目标只是新生代的垃圾收集。 + + - 老年代收集(Major GC/Old GC) + + - 指目标只是老年代的垃圾收集。 + - 目前只有CMS收集器会有单独收集老年代的行为。 + + - 混合收集(Mixed GC) + + - 指目标是收集整个新生代以及部分老年代的垃圾收集。 + - 目前只有G1收集器会有这种行为。 + + - 整堆收集(Full GC) + + - 收集整个Java堆和方法区的垃圾收集。 + + - 标记-清除算法 + + - 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象 + - 缺点 + + - 执行效率不稳定 + + - 标记和清除两个过程的执行效率都随对象数量增长而降低 + + - 内存空间的碎片化问题 + + - 标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 + + - 标记-复制算法 + + - 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块 + - 缺点 + + - 可用内存缩小为了原来的一半,空间浪费太多 + - 如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销 + + - Appel 式回收 + + - 把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。 + - 发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。 + - HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1 + + - 标记-整理算法 + + - 标记过程仍然与「标记-清除」算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存 + - 标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动 式的。 + - Stop The World + + - 移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行 + +- HotSpot 的算法细节实现 + + - 根节点枚举 + + - 所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的,因此毫无疑问根节点枚举与之前提及的整理内存碎片一样会面临相似的 Stop The World 的困扰。 + - 使用一组称为 OopMap 的数据结构来达到这个目的。一旦类加载动作完成的时候,HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信 息了,并不需要真正一个不漏地从方法区等 GC Roots 开始查找。 + + - 安全点 + + - HotSpot 也的确没有为每条指令都生成OopMap,只是在「特定的位置」记录了这些信息,这些位置被称为安全点(Safepoint)。 + - 如何在垃圾收集发生时让所有线程都跑到最近的安全点,然后停顿下来 + + - 抢先式中断 + + - 不需要线程的执行代码主动去配合,在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。 + + - 现在几乎没有虚拟机实现采用抢先式中断来暂停线程响应 GC 事件。 + + - 主动式中断 + + - 当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。 + + - 安全区域 + + - 能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。 + + - 记忆集与卡表 + + - 记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。 + + - 最简单的实现可以用非收集区域中所有含跨代引用的对象数组来实现这个数据结构 + + - 字长精度 + + - 每个记录精确到一个机器字长,包含跨代指针 + + - 对象精度 + + - 每个记录精确到一个对象,该对象里有字段含有跨代指针。 + + - 卡精度 + + - 每个记录精确到一块内存区域,该区域内有对象含有跨代指针。 + - 卡表 + + - 定义了记忆集的记录精度、与堆内存的映射关系等。 + + - 写屏障 + + - 卡表元素何时变脏 + + - 有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻。 + + - 在 HotSpot 虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的 + + - 并发的可达性分析 + + - 三色标记 + + - 白色 + + - 表示对象尚未被垃圾收集器访问过。 + - 可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。 + + - 黑色 + + - 表示对象己经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。 + - 是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。 + + - 灰色 + + - 表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。 + + - 解决并发扫描时的对象消失问题 + + - 增量更新 + + - 当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。 + - 黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。 + + - 原始快照 + + - 当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。 + - 无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。 + +- 经典垃圾收集器 + + - 收集器搭配、分配关系 + + - https://pic.imgdb.cn/item/624fa55d239250f7c57d45d9.jpg + + - 基本术语概念 + + - 并行 + + - 多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。 + + - 并发 + + - 垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。 + - 由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。 + + - Serial 收集器 + + - https://pic.imgdb.cn/item/624fab4c239250f7c58c3775.jpg +一个单线程工作的收集器,强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。即「Stop the World」 + - 优势 + + - 简单而高效 + + - 对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的 + - 对于单核处理器或处理器核心数较少的环境来说,Serial 收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。 + - Serial 收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。 + + - ParNew 收集器 + + - https://pic.imgdb.cn/item/624fab6f239250f7c58c9579.jpg +本质上是 Serial 收集器的多线程并行版本 + - 只有它能与 CMS 收集器配合工作 + - 默认开启的收集线程数与处理器核心数量相同 + + - Parallel Scavenge 收集器 + + - 新生代收集器,基于「标记-复制」,能够并行收集的多线程收集器 + - 目标 + + - 其他垃圾收集器的目标是缩短垃圾收集时用户线程的停顿时间 + - Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量 + + - 运行用户代码的时间/(运行用户代码的时间+运行垃圾收集的时间) + + - Serial Old 收集器 + + - https://pic.imgdb.cn/item/624fab9a239250f7c58d0a0b.jpg +Serial 收集器的老年代版本,同样是一个单线程收集器,使用标记-整理算法 + - 适用场景 + + - JDK5 及之前与吞吐量收集器搭配之用 + - 客户端模式使用 + - CMS 并发收集失败的后备预案 + + - Parallel Old 收集器 + + - https://pic.imgdb.cn/item/624faaf6239250f7c58b580e.jpg +吞吐量收集器的老年代版本,支持多线程并发收集,基于标记-整理算法 + + - CMS 收集器 + + - https://pic.imgdb.cn/item/624fad34239250f7c59129e2.jpg +第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程与用户线程(基本上)同时工作。 + - 老年代收集器 + + - 无法与新生代收集器 Parallel Scavenge 收集器配合工作 + + - 一种以获取最短回收停顿时间为目标的收集器,基于标记-清除算法 + - 运作过程 + + - 初始标记(CMS initial mark) + + - Stop The World + - 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快 + + - 并发标记(CMS concurrent mark) + + - 从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行 + + - 重新标记(CMS remark) + + - Stop The World + - 修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录 + - 停顿时间通常会比初始标记阶段稍长一 些,但也远比并发标记阶段的时间短 + + - 并发清除(CMS concurrent sweep) + + - 清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。 + + - 缺点 + + - 对处理器资源非常敏感 + + - 并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低总吞吐量。 + - 默认启动的回收线程数 + + - (处理器核心数量+3)/4 + - 如果核心不足 4 个,占据一般的运算能力去执行收集器线程 + + - 无法处理「浮动垃圾」 + + - 在 CMS 的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS 无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为「浮动垃圾」 + + - 收集结束时会有大量空间碎片产生 + + - 空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次 Fu1lGC 的情况。 + + - Garbage First 收集器 + + - https://pic.imgdb.cn/item/624fb889239250f7c5ad5d7f.jpg +G1 是一个面向全堆的收集器,不再需要其他新生代收集器的配合工作。基于标记-整理收集算法 + + - G1 面向堆内存任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是 G1 收集器的 Mixed GC 模式。 + + - 停顿时间模型 + + - 支持指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过 N 毫秒这样的目标 + + - 内存划分与布局 + + - 仍然遵循分代收集理论,但是不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以 +根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。 + - Region 中还有一类特殊的 Humongous 区域,专门用来存储大对象。 + + - G1 认为只要大小超过了一个Region 容量一半的对象即可判定为大对象。 + - G1 的大多数行为都把 Humongous Region 作为老年代 的一部分来进行看待 + + - G1 收集器之所以能建立可预测的停顿时间模型,是因为它将 Region 作为单次回收的最小单元,每次收集到的内存空间都是 Region 大小的整数倍,这样可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。 + + - 具体的处理思路是让 G1 收集器去跟踪各个 Region 里面的垃圾堆积的「价值」大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一 个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些 Region + + - 运作过程 + + - 初始标记 + + - 仅仅只是标记一下 GC Roots 能直接关联到的对象,耗时很短,借用进行 Minor GC 的时候同步完成的,实际并没有额外的停顿。 + + - 并发标记 + + - 从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。 + + - 最终标记 + + - 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB(原始快照) 记录。 + + - 筛选回收 + + - 负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧Region的全部空间。 + - 这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。 + +- 低延迟垃圾收集器 + + - 不可能三角 + + - 内存占用 + - 吞吐量 + - 延迟 + + - Shenandoaht 收集器 + + - https://pic.imgdb.cn/item/624fbc51239250f7c5ba0ec1.jpg +同 G1 一样,基于 Region 的堆内存布局,同样 有着用于存放大对象的 Humongous Region,默认的回收策略也同样是优先处理回收价值最大的 +Region + - 与 G1 不同之处 + + - 支持并发的整理算法 + - 默认不使用分代收集 + - 摒弃了在 G1 中耗费大量内存和计算资源去维护的记忆集,改用名为「连接矩阵」 + + - 运作过程 + + - 初始标记 + + - Stop The World + - 首先标记与 GC Roots 直接关联的对象 + + - 并发标记 + + - 遍历对象图,标记出全部可达的对象,这个阶段 是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。 + + - 最终标记 + + - 处理剩余的 SATB(原始快照)扫描,并在这个阶段统计出回收价值最高的 Region。 + + - 并发清理 + + - 清理那些整个区域内连一个存活对象都没有找到 的 Region + + - 并发回收 + + - 与 G1 的核心差异,把回收集里面的存活对象先复制一份到其他未被使用的 Region 之中,通过「读屏障」解决与用户线程并发的问题 + + - 初始引用更新 + + - 把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新 + - 初始引用更新时间很短,会产生一个非常短暂的 停顿。 + + - 并发引用更新 + + - 真正开始进行引用更新操作,这个阶段是与用户 线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。 + - 按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值即可。 + + - 最终引用更新 + + - 解决了堆中的引用更新后,还要修正存在于 GC Roots 中的引用。这个阶段是 Shenandoah 的最后一次停顿,停顿时间只与 GC Roots 的数量相关。 + + - 并发清理 + + - 调用一次并发清理过程来回收这些 Region 的内存空间,供以后新对象分配使用。 + + - ZGC 收集器 + +- 选择合适的垃圾收集器 +- 实战:内存分配与回收策略 + +### 4、虚拟机性能监控、故障处理工具 + +### 5、调优案例分析与实战 + +## 三、虚拟机执行子系统 + +## 四、程序编译与代码优化 + +## 五、高效并发 + +### 十三、线程安全与锁优化 + +- 概述 +- 线程安全 + + - 线程安全的定义 + + - 当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。 + + - Java 语言中的线程安全 + + - 不可变 + + - final + 基本数据类型,一定是线程安全的(没有发生 this 逃逸) + - final + 对象需要对象自行保证其行为不会对其状态产生任何影响 + + - 对象里面带有状态的变量都声明为 final + + - 绝对线程安全 + + - 如定义所属就是「绝对线程安全」 + - 在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全 + + - 相对线程安全 + + - 通常意义上所讲的线程安全,它需要保证对这个对象单次的操作是线程安全的在调用的时候不需要进行额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。 + + - 线程兼容 + + - 指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。 + + - 线程对立 + + - 线程对立是指不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。 + - suspend() + - resume() + - System.setIn() + - Sytem.setOut() + - System.runFinalizersOnExit() + + - 线程安全的实现方法 + + - 互斥同步 + + - 同步 + + - 在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用 + + - 互斥是实现同步的一种手段 + + - 临界区 + - 互斥量 + - 信号量 + + - synchronized 关键字 + + - synchronized 关键字经过 Javaca 编译之后,会在同步块的前后分别形成monitorenter 和 monitorexit 这两个字节码指令。 + - 这两个字节码指令都需要一个 reference 类型的参数来指明要锁定和解锁的对象。 + + - 如果Java源码中的 synchronized 明确指定了对象参数,那就以这个对象的引用作 为reference + - 如果没有明确指定,将根据 synchronized 修饰的方法类型(如实例方法或类方法)来决定是取代码所在的对象实例还是取类型对应的 Class 对象来作为线程要特有的锁 + + - 同步块也是可重入锁 + + - 被 synchronized 修饰的同步块对同一条线程来说是可重入的。同一线程反复进入同步块不会出现自己把自己锁死的情况。 + - 被 synchronized 修饰的同步块在持有锁的线程执行完毕并释放锁之前,会无条件地阻塞后面其他线程的进入。 + + - 局限 + + - 无法像处理某些数据库中的锁那样,强制己获取锁的线程释放锁 + - 无法强制正在等待锁的线程中断等待或超时退出。 + - 从执行成本的角度看,持有锁是一个重量级的操作。 + + - 主流 Java 虚拟机实现中,Java 的线程是映射到操作系统的原生内核线程之上的,如果要阻塞或唤醒一条线程,则需要操作系统来帮忙完成,这就不可避免地陷入用户态到核心态的转换中,进行这种状态转 换需要耗费很多的处理器时间。 + + - 重入锁 ReentrantLock + + - 解决了 synchronized 的局限 + + - 等待可中断 + + - 持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。 + + - 可实现公平锁 + + - 多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁 + - 不过一旦使用了公平锁,将会导致 ReentrantLock 的性能急剧下降,会明显影响吞吐量。 + + - 锁可以绑定多个条件 + + - 一个 ReentrantLock 对象可以同时绑定多个 Condition 对象 + + - 非阻塞同步 + + - CAS + + - 当且仅当 V 符合 A 时,处理器才会用 B 更新 V 的值,否则它就不执行更新。但是,不管是否更新了 V 的值,都会返回 V 的旧值,上述的处理过程是一个原子操作,执行期间不会被其他线程中断。 + + - ABA 问题 + + - 如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然为 A 值,如果在这段期间它的值曾经被改成B,后来又被改回为A,那 CAS 操作就会误认为它从来没有被改变过。 + + - JUC 使用 AtomicStampedReference 通过控制变量值的版本保证 CAS,但是比较鸡肋,不如直接使用互斥同步 + + - 无同步方案 + + - 可重入代码(纯代码) + + - ThreadLocal + + - ThreadLocal 是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用 ThreadLocal 来维护变量时, ThreadLocal 会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。 + - Thread 如何实现线程隔离 + + - ThreadLocalMap,key 就是 ThreadLocal 变量名(当前线程),value 为对应的对象 + - 其实就是用了 Map 的数据结构给当前线程缓存了, 要使用的时候就从本线程的 threadLocals 对象中获取就可以了,key 就是当前线程。 + - ThreadLocalMap的Entry实现继承了WeakReference> + - 因为 ThreadLocal 需要用 final static 修饰,所以保持强引用,但是内部 map 又是个弱引用,不会释放,造成内存泄漏 + + - ThreadLocal 提供了一个清除线程中对象的方法, 即 remove,其实内部实现就是调用 ThreadLocalMap 的 remove 方法 + +- 锁优化 + + - 自旋锁与自适应锁 + - 锁清除 + - 锁粗化 + - 轻量级锁 + + - https://pic.imgdb.cn/item/624f01e2239250f7c592109e.jpg + + - 偏向锁 + - 总述 + + - 一个锁对象刚刚开始创建的时候,没有任何线程来访问它,它是可偏向的,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问他的时候,它会偏向这个线程。此时线程状态为无锁状态,锁标志位为 01 + + - 无锁 + + - 当一个线程(线程 A)来获取锁的时,会首先检查所标志位,此时锁标志位为 01,然后检查是否为偏向锁,此时不为偏向锁,所以当前线程会修改对象头状态为偏向锁,同时将对象头中的 ThreadID 改成自己的 Thread ID + + - 偏向 + + - 如果再有一个线程(线程 B)过来,此时锁状态为偏向锁,该线程会检查 Mark Word 中记录的线程 ID 是否为自己的线程 ID,如果是,则获取偏向锁,执行同步代码块。如果不是,则利用 CAS 尝试替换 Mark Word 中的 Thread ID,成功,表示该线程(线程 B)获取偏向锁,执行同步代码块 + - 如果线程 B 获取偏向锁失败,则表明当前环境存在锁竞争情况,则执行偏向锁的撤销工作 + + - 检查线程 A 是否还活着 + + - 线程 A 死了的话线程 B 自己重新获取偏向锁 + - 线程 A 如果没死,就继续检查线程 A 是否还需要偏向锁,如果依然需要,升级为轻量级锁 + + - 轻量级锁的释放成功,则表示没有发生竞争,直接释放。如果失败,表明锁对象存在竞争关系,这时会轻量级锁会升级为重量级锁,然后释放锁,唤醒被挂起的线程,开始新一轮锁竞争,此后就是重量级锁 + diff --git "a/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 JVM\357\274\210\347\254\254\344\270\211\347\211\210\357\274\211.xmind" "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 JVM\357\274\210\347\254\254\344\270\211\347\211\210\357\274\211.xmind" new file mode 100644 index 0000000..ed2ccce Binary files /dev/null and "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 JVM\357\274\210\347\254\254\344\270\211\347\211\210\357\274\211.xmind" differ diff --git "a/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 Java \350\231\232\346\213\237\346\234\272 JVM \351\253\230\347\272\247\347\211\271\346\200\247\344\270\216\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 Java \350\231\232\346\213\237\346\234\272 JVM \351\253\230\347\272\247\347\211\271\346\200\247\344\270\216\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 0000000..db539bd --- /dev/null +++ "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 Java \350\231\232\346\213\237\346\234\272 JVM \351\253\230\347\272\247\347\211\271\346\200\247\344\270\216\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,655 @@ +# 深入理解 Java 虚拟机 JVM 高级特性与最佳实践 + +## 2.Java 内存区域与内存溢出异常 + +### 概述 + +### 运行时数据区域 + +- 程序计数器 + + - 当前线程所执行的字节码的行号指示器 + - 唯一一个在 JVM 中没有规定任何 OutOfMemoryError 情况的区域 + - 线程私有:一个处理器智慧之星一条线成中的指令,为了能恢复到正确的执行为之,每个线程都需要有一个独立的程序计数器 + +- Java 虚拟机栈 + + - 线程私有,生命周期与线程相同 + - 每个方法执行的同时都会创建一个栈帧,局部变量表、操作数栈、动态连接、返回地址等。 + - 存储局部变量表(boolean、byte、char、short、int、float、long、double、对象引用、returnAddress) + - 两种异常状况 + + - StackOverflowError + + - 如果线程请求的栈深度大于虚拟机所允许的深度 + + - OutOfMemoryError + + - 虚拟机栈动态扩展时无法申请到足够的内存 + +- 本地方法栈 + + - 与虚拟机栈作用相似,虚拟机栈负责执行 Java 方法字节码服务,本地方法栈负责 Native 方法服务 + - 也会抛出 StackOverflowError 和 OutOfMemoryError 异常 + +- Java 堆 + + - 被所有线程共享,作用就是给几乎所有的对象实例(包括数组)分配内存 + - 垃圾收集器管理的主要区域,所以也叫「GC 堆」 + - 分类(分代收集算法) + + - 新生代 + + - Eden 空间 + - From Survivor 空间 + - To Survivor 空间 + + - 老年代 + + - 分类(内存分配) + + - 多个线程私有的分配缓冲区 + + - 分类的目的都是更好的回收内存和分配内存 + - 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,会抛出 OutOfMemoryError 异常 + +- 方法区,JDK8 以后可以叫做「元空间」,撤销永久代的概念 + + - 方法区(Method Area)存储 + + - 运行时常量 + - 已被虚拟机加载的类信息 + - 静态变量 + - 即时编译器编译后的代码 + - 运行时常量池 + + - 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常 + +- 运行时常量池 + + - 方法区的一部分 + - Class 文件 + + - 类的版本 + - 字段 + - 方法 + - 接口 + + - 特点 + + - 存放编译器生成的各种字面量和符号引用 + - 具备动态性,运行期间也能将新的常量放入池中 + + - String.intern 方法 + +- 直接内存 + + - 不属于虚拟机运行时数据区的一部分,也不是内存区域 + - 依然可能导致 OutOfMemoryError 异常 + + - 各个内存区域总和大于物理内存限制时 + +### HotSpot 虚拟机对象探秘 + +### 实战:OutOfMemoryError 异常 + +## 3.垃圾收集器与内存分配策略 + +### 概述 + +### 对象已死吗 + +- 引用计数算法 +- 可达性分析算法 +- 再谈引用 +- 生存还是死亡 +- 回收方法区 + +### 垃圾收集算法 + +- 标记-清除算法 + + - 分为「标记」和「清除」两个阶段 + - 缺点 + + - 效率不高 + - 大量的空间碎片,没有连续的内存区域,从而导致触发 Full GC + +- 复制算法 + + - 将可用的内存按照容量划分为大小相等的两块,每次只使用其中的一块。 + - 当一块满了,把存活的复制到另一块,然后清除该块内存 + - 所以堆区就有了 Eden 和 Survivor 的划分,默认比例 8:1 + +- 标记-整理算法 + + - 与「标记-清除」中的标记过程一样,只是清理的时候首先让对象都像一端移动,然后清理掉端便捷以外的内存 + +- 分代收集算法 + + - 对不同年龄代的对象采用不同的垃圾回收算法 + - 新生代使用复制算法 + - 老年代就使用标记-整理或者标记清除算法 + +### HotSpot 的算法实现 + +- 枚举根节点 +- 安全点 +- 安全区域 + +### 垃圾收集器 + +- Serial 收集器 + + - 单线程收集器 + - 工作的时候「Stop The World」 + - 简单而高效,没有线程交互的开销,专心做垃圾收集,目前依然是 Client 模式下的默认新生代垃圾收集器 + +- ParNew 收集器 + + - Serial 收集器多线程版本 + - 收集器的工作 + + - 控制参数 + + - -XX:Survivorratio + - -XX:PretenureSizeThreshold + - -XX:HandlePromotionFailure + + - 收集算法 + - Stop The World + - 对象分配规则 + - 回收策略 + + - 除了 Serial 收集器,只能与 CMS 收集器配合工作 + - 默认收集线程数与 CPU 数量相同,有利于有效利用系统和线程资源 + - 相关 JVM 参数 + + - 默认指定 ParNew 收集器 + + - -XX:+UseConcMarkSweepGC + - -XX:+UseParNewGC + + - 限制垃圾收集的线程数 + + - -XX:ParallelGCThreads + +- Parallel Scavenge 收集器,「吞吐量优先」收集器 + + - 新生代收集器,使用复制算法,并行多线程收集器 + - 目标是达到一个可控制的吞吐量:运营用户代码时间 / (CPU 用于运行用户代码时间 + 垃圾收集时间) + - 相关 JVM 参数 + + - 控制最大垃圾收集停顿时间 + + - -XX:MaxGCPauseMillis + + - 直接设置吞吐量大小 + + - -XX:GCTimeRatio + + - 自动设置新生代堆区参数,GC 自适应调节策略 + + - -XX:+UseAdaptiveSizePolicy + - 开启这个开关就不需要手工设置新生代的大小(-Xmn)、Eden 与 Survivor 的比例、晋升老年代对象的年龄 + +- Serial Old 收集器 + + - Serial 收集器的老年代版本 + - 使用标记整理算法 + - 两大用途 + + - JDK 1.5 之前与 Parallel Scavenge 收集器搭配使用 + - 作为 CMS 收集器的后备预案 + +- Parallel Old 收集器 + + - Parallel Scavenge 收集器的老年代版本 + - 使用标记整理算法 + +- CMS(Concurrent Mark Sweep,并发整理清除) 收集器 + + - 目标是获取最短回收停顿时间 + - 使用标记清除算法 + - 步骤 + + - 初始标记,Stop The World + - 并发标记 + - 重新标记,Stop The World + - 并发清除 + + - 并发收集,低停顿 + - 缺点 + + - 对 CPU 资源非常敏感,默认启动垃圾收集线程数:(CPU 数量 + 3)/ 4 + - 无法处理浮动垃圾从而导致 Full GC + - 标记-清除算法固有的空间碎片问题 + +- G1 收集器 + + - 特点 + + - 并行与并发 + - 分代收集 + + - 像 CMS 收集器一样,能与应用程序线程并发执行。 + + - 空间整合 + + - 整体是标记-整理算法 + + - G1 是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。 + - G1 的 Stop The World(STW) 更可控,G1 在停顿时间上添加了预测机制,用户可以指定期望停顿时间。 + + - 可预测的停顿 + + - 步骤 + + - 初始标记 + - 并发标记 + - 重新标记 + - 筛选回收 + + - 相关概念 + + - Region + + - 传统的 GC 收集器将连续的内存空间划分为新生代、老年代和永久代,特点是各代的存储地址是连续的 + - G1 的各代存储地址是不连续的,每一代都使用了 n 个不连续的大小相同的 Region,每个 Region 占有一块连续的虚拟内存地址 + + - Humongous + + - 表示 Region 存储的是巨大对象,大小大于等于 region 一半的对象 + - H-obj 直接分配到了 old gen,防止了反复拷贝移动 + - H-obj 在 global concurrent marking 阶段的 cleanup 和 full GC 阶段回收 + - 为了减少连续 H-objs 分配对 GC 的影响,需要把大对象变为普通的对象,建议增大 Region size。 + + - -XX:G1HeapRegionSize + - 取值范围从 1M 到 32M,且是 2 的指数。如果不设定,那么 G1 会根据 Heap 大小自动决定 + + - SATB(Snapshot-At-The-Beginning,GC 开始时活着的对象的一个快照) + + - 通过 Root Tracing 得到的,作用是维持并发 GC 的正确性。 + + - 三色标记算法 + + - 黑 + + - 对象被标记了,且它的所有 field 也被标记完了 + + - 白 + + - 对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉 + + - 灰 + + - 对象被标记了,但是它的 field 还没有被标记或标记完 + + - RSet(Remembered Set,辅助 GC 过程的一种结构,典型的空间换时间工具) + + - 集合里的 Region 可以是任意年代的 + + - Pause Prediction Model(停顿预测模型) + + - 与 CMS 最大的不同是,用户可以设定整个 GC 过程的期望停顿时间 + + - -XX:MaxGCPauseMillis 指定一个 G1 收集过程目标停顿时间,默认值 200ms,不过它不是硬性条件,只是期望值 + - 根据这个模型统计计算出来的历史数据来预测本次收集需要选择的 Region 数量,从而尽量满足用户设定的目标停顿时间 + + - G1 的 GC 过程 + + - 两种模式(完全 Stop The World 的) + + - Young GC + + - 选定所有年轻代里的 Region。通过控制年轻代的 region 个数,即年轻代内存大小,来控制 young GC 的时间开销 + + - Mixed GC + + - 选定所有年轻代里的 Region,外加根据 global concurrent marking 统计得出收集收益高的若干老年代 Region + +- 理解 GC 日志 + + - GC 发生的时间,从 Java 虚拟机启动以来经过的秒数 + - 垃圾收集停顿的类型 + - GC 发生的区域 + - GC 前该内存区域已使用容量 -> GC 后该内存区域已使用容量 + - GC 前 Java 堆已使用容量 -> GC 后 Java 堆已使用堆容量 + - GC 所占用的时间(秒) + +- 垃圾收集器参数总结 + + - UseSerialGC + + - 虚拟机运行在 Client 模式下的默认值,打开此开关后,使用 Serial + Serial Old 的收集器组合进行内存回收 + + - UseParNewGC + + - 打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收 + + - UseConcMarkSweepGC + + - 打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收 + + - UseParallelGC + + - 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old 的收集器组合进行内存回收。 + + - UseParallelOldGC + + - 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行回收 + + - SurvivorRatio + + - 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden:Survivor = 8:1 + + - PretenureSizeThreshold + + - 直接晋升到来年代的对象大小,设置这个参数后,大于这个参数对象将直接在老年代分配 + + - MaxTenuringThreshold + + - 晋升到老年代的对象年龄。每个对象再坚持过一次 Minor GC 之后,年龄就增加,当超过这个参数值时就进入老年代 + + - UseAdaptiveSizePolicy + + - 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 + + - HandlePromotionFailure + + - 是否允许分配担保失败,及老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 + + - ParallelGCThreads + + - 设置并行 GC 时进行内存回收的线程数 + + - GCTimeRatio + + - GC 时间占总时间的比率,默认值为 99,即允许 1% 的GC 的时间。仅在使用 Parallel Scavenge 收集器时生效 + + - MaxGCPauseMillis + + - 设置 GC 的最大停顿时间。仅在使用 Parallel Scavenge 收集器时生效 + + - CMSInitiatingOccupancyFraction + + - 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值 68%,仅在使用 CMS 收集器时生效 + + - UseCMSCompactAtFullCollection + + - 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效 + + - CMSFullGCBeforeCompaction + + - 设置 CMS 收集器在进行若干次垃圾收集后在启动一次内存碎片整理。仅在使用 CMS 收集器时生效 + +### 内存分配与回收策略 + +- 对象优先在 Eden 分配 +- 大对象直接进入老年代 +- 长期存活的对象将进入老年代 +- 动态对象年龄判定 +- 空间分配担保 + +### 总结 + +## 4.虚拟机性能监控与故障处理 + +### 监控点 + +- 运行日志 +- 异常堆栈 +- GC 日志 +- 线程快照(threaddump/javacore 文件) +- 堆转储快照(heapdump/hprof 文件) + +### JDK 的命令行工具 + +- jps:虚拟机进程状况工具 + + - 显示指定系统内所有的 HotSpot 虚拟机进程 + - 列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID + - jps [options] [hostid] + + - 如果是本地 JVM,hostid 可以忽略 + + - options 参数及相关功能 + + - -q:只输出 LVMID,省略主类的名称 + - -m:输出虚拟机进程启动时传递给主类 main() 函数的参数 + - -l:输出主类的全名,如果进程执行的是 jar 包,输出 jar 路径 + - -v:输出虚拟机进程启动时 JVM 参数 + +- jstat:虚拟机统计信息监视工具 + + - 用于收集 HotSpot 虚拟机各方面的运行数据 + - 显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。 + - jstat [option vmid] [interval] [s|ms] [count] + + - interval 代表查询的时间间隔 + - count 代表查询次数 + + - option 参数及相关功能 + + - 类装载 + + - -class:监视类装载、卸载数量、总空间以及类装载所耗费的时间 + + - Loaded:加载 Java 类的数量 + - Bytes:所占用空间大小 + - Unloaded:未加载数量 + - Bytes:未加载占用空间 + - Time:时间 + + - 垃圾收集 + + - -gc:监视 Java 堆状况,包括 Eden 区、两个 survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息。 + + - S0C:第一个幸存区的大小 + - S1C:第二个幸存区的大小 + - S0U:第一个幸存区的使用大小 + - S1U:第二个幸存区的使用大小 + - EC:伊甸园区的大小 + - EU:伊甸园区的使用大小 + - OC:老年代大小 + - OU:老年代使用大小 + - MC:方法区大小 + - MU:方法区使用大小 + - CCSC:压缩类空间大小 + - CCSU:压缩类空间使用大小 + - YGC:年轻代垃圾回收次数 + - YGCT:年轻代垃圾回收消耗时间 + - FGC:老年代垃圾回收次数 + - FGCT:老年代垃圾回收消耗时间 + - GCT:垃圾回收消耗总时间 + + - -gccapacity:监视内容与「-gc」基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间 + + - NGCMN:新生代最小容量 + - NGCMX:新生代最大容量 + - NGC:当前新生代容量 + - S0C:第一个幸存区大小 + - S1C:第二个幸存区的大小 + - EC:伊甸园区的大小 + - OGCMN:老年代最小容量 + - OGCMX:老年代最大容量 + - OGC:当前老年代大小 + - OC:当前老年代大小 + - MCMN:最小元数据容量 + - MCMX:最大元数据容量 + - MC:当前元数据空间大小 + - CCSMN:最小压缩类空间大小 + - CCSMX:最大压缩类空间大小 + - CCSC:当前压缩类空间大小 + - YGC:年轻代gc次数 + - FGC:老年代GC次数 + + - -gcutil:监视内容与「-gc」基本相同,但输出主要关注已使用空间占总空间的百分比 + + - S0:幸存1区当前使用比例 + - S1:幸存2区当前使用比例 + - E:伊甸园区使用比例 + - O:老年代使用比例 + - M:元数据区使用比例 + - CCS:压缩使用比例 + - YGC:年轻代垃圾回收次数 + - FGC:老年代垃圾回收次数 + - FGCT:老年代垃圾回收消耗时间 + - GCT:垃圾回收消耗总时间 + + - -gccause:与「-gcutil」功能一样,但是会额外输出导致上一次 GC 产生的原因 + + - LGCC:最近垃圾回收的原因 + - GCC:当前垃圾回收的原因 + + - -gcnew:监视新生代 GC 状况 + + - S0C:第一个幸存区大小 + - S1C:第二个幸存区的大小 + - S0U:第一个幸存区的使用大小 + - S1U:第二个幸存区的使用大小 + - TT:对象在新生代存活的次数 + - MTT:对象在新生代存活的最大次数 + - DSS:期望的幸存区大小 + - EC:伊甸园区的大小 + - EU:伊甸园区的使用大小 + - YGC:年轻代垃圾回收次数 + - YGCT:年轻代垃圾回收消耗时间 + + - -gcnewcapacity:监视内容与「-gcnew」基本相同,输出主要关注使用到的最大、最小空间 + + - NGCMN:新生代最小容量 + - NGCMX:新生代最大容量 + - NGC:当前新生代容量 + - S0CMX:最大幸存1区大小 + - S0C:当前幸存1区大小 + - S1CMX:最大幸存2区大小 + - S1C:当前幸存2区大小 + - ECMX:最大伊甸园区大小 + - EC:当前伊甸园区大小 + - YGC:年轻代垃圾回收次数 + - FGC:老年代回收次数 + + - -gcold:监视老年代 GC 状况 + + - MC:方法区大小 + - MU:方法区使用大小 + - CCSC:压缩类空间大小 + - CCSU:压缩类空间使用大小 + - OC:老年代大小 + - OU:老年代使用大小 + - YGC:年轻代垃圾回收次数 + - FGC:老年代垃圾回收次数 + - FGCT:老年代垃圾回收消耗时间 + - GCT:垃圾回收消耗总时间 + + - -gcoldcapacity:监视内容与「-gcold」基本相同,输出主要关注使用到的最大、最小空间 + + - OGCMN:老年代最小容量 + - OGCMX:老年代最大容量 + - OGC:当前老年代大小 + - OC:老年代大小 + - YGC:年轻代垃圾回收次数 + - FGC:老年代垃圾回收次数 + - FGCT:老年代垃圾回收消耗时间 + - GCT:垃圾回收消耗总时间 + + - -gcpermcapacity/-gcmetacapacity:输出永久代使用到的最大、最小空间(JDK8 之后已经永久代已经改为元空间,没有该参数,随之代替的是「-gcmetacapacity」参数) + + - MCMN: 最小元数据容量 + - MCMX:最大元数据容量 + - MC:当前元数据空间大小 + - CCSMN:最小压缩类空间大小 + - CCSMX:最大压缩类空间大小 + - CCSC:当前压缩类空间大小 + - YGC:年轻代垃圾回收次数 + - FGC:老年代垃圾回收次数 + - FGCT:老年代垃圾回收消耗时间 + - GCT:垃圾回收消耗总时间 + + - 运行期编译状况 + + - -compilier:输出 JIT 编译器编译过的方法、耗时等信息 + + - Compiled:编译数量 + - Failed:失败数量 + - Invalid:不可用数量 + - Time:时间 + - FailedType:失败类型 + - FailedMethod:失败的方法 + + - -printcompilation:输出已经被 JIT 编译的方法 + + - Compiled:最近编译方法的数量 + - Size:最近编译方法的字节码数量 + - Type:最近编译方法的编译类型。 + - Method:方法名标识。 + +- jinfo:Java 配置信息工具 + + - 显示虚拟机配置信息 + - 实时地查看和调整虚拟机各项参数 + - jinfo [option] pid + + - jinfo -flag:查询参数配置 + + - jinfo -flag CMSInittiatingOccupancyFaction 9537 + + - java -XX:PrintFlagsFinal(查看参数默认值,列表形式) + - jinfo -sysprops:把虚拟机进程的 System.getProperties() 的内容打印出来。 + +- jmap:Java 内存映像工具 + + - 生成虚拟机的内存转储快照(heapdump 文件) + - -XX:+HeapDumpOnOutOfMemoryError 参数也可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件 + - Linux 下也可以通过 kill -3 命令发送进程退出信号「吓唬」虚拟机拿到 dump 文件 + - jmap [option] vmid + + - -dump:生成 Java 堆转储快照。 +格式:-dump[live,]format=b,file=,其中 live 代表是否只 dump 出存活对象。 + - -finalizerinfo:显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。在 Linux 下有效 + - -heap:显示 Java 堆详细信息 + + - 使用哪种回收器 + - 参数配置 + - 分代状况 + - 各参数代表的含义 + + - -histo:显示堆中对象统计信息 + + - 类、实例数量 + - 合集容量 + + - -permstat:以 ClassLoader 为统计口径显示永久代内存状态,Linux 下有效,JDK8 之后改为「-clstats」选项。 + - -F:当虚拟机进程对 -dump 选项没有响应时,可使用这个选项强制生成 dump 快照。在 Linux 下有效。 + +- jhat:虚拟机堆转储快照分析工具 + + - 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果 + - 一般不用 + + - 一般不会在部署应用程序的服务器上直接分析 dump 文件,分析工作是一个耗时而且消耗硬件资源的过程。 + - jhat 的分析功能相对来说比较简陋 + +- jstack:Java 堆栈跟踪工具 + + - 显示虚拟机的线程快照(threaddump 或者 javacore 文件) + - 就是当前虚拟机内每一条线程正在执行的方法的堆栈的集合,目的是定位线程出现长时间停顿的原因 + + - 线程死锁 + - 死循环 + - 请求外部资源导致的长时间等待 + + - jstack [option] vmid + + - -F:当正常输出的请求不被响应时,强制输出线程堆栈 + - -l:除堆栈外,显示关于锁的附加信息 + - -m:如果调用到本地方法的话,可以显示 C/C++ 的堆栈 + +- hsdis:jit 生成代码反汇编 + +### JDK 的可视化工具 + +- JConsole +- VisualVM + diff --git "a/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 Java \350\231\232\346\213\237\346\234\272 JVM \351\253\230\347\272\247\347\211\271\346\200\247\344\270\216\346\234\200\344\275\263\345\256\236\350\267\265.xmind" "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 Java \350\231\232\346\213\237\346\234\272 JVM \351\253\230\347\272\247\347\211\271\346\200\247\344\270\216\346\234\200\344\275\263\345\256\236\350\267\265.xmind" new file mode 100644 index 0000000..ae38d4b Binary files /dev/null and "b/JVM/\346\267\261\345\205\245\347\220\206\350\247\243 Java \350\231\232\346\213\237\346\234\272 JVM \351\253\230\347\272\247\347\211\271\346\200\247\344\270\216\346\234\200\344\275\263\345\256\236\350\267\265.xmind" differ diff --git "a/Java\345\237\272\347\241\200/J2SE.md" "b/Java\345\237\272\347\241\200/J2SE.md" new file mode 100644 index 0000000..d2019d6 --- /dev/null +++ "b/Java\345\237\272\347\241\200/J2SE.md" @@ -0,0 +1,923 @@ +# J2SE + +## 线程、多线程 + +### 线程的状态 + +- 新创建(New)- 被创建但还没有调用 Start 方法 +- 可运行(Runnable)- 可以在 JVM 中运行的状态,根据系统时间片决定是否正在运行的自己的代码 +- 被阻塞(Blocked) + + 当一个线程试图获取一个内部的对象锁(不是 java.util.concurrent 库中的锁),而该锁此时正被其他线程持有,则该线程进入阻塞状态; + +- 等待(Waiting)- Object.wait、Thread.join、concurrent 中的 Lock 或者 Condition + + 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。 + +- 计时等待(Timed waiting) +- 被终止(Terminated)-因为 run 方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 + +### 进程与线程 + +- 进程是操作系统的资源调度实体,有自己的内存地址空间和运行环境; +- 线程一般被称为轻量级的进程,线程和进程一样,也有自己的运行环境,但是创建一个线程要需要的资源比创建一个进程要少。线程存在于进程之中 +- 每个进程至少有一个线程。一个进程下的多个线程之间可以共享进程的资源,包括内存空间和打开的文件。 +- 进程跟程序(programs)、应用(applications)具备相同的含义,进程间通讯依靠 IPC(Inter-Process Communication,进程间通信) 资源,例如管道(pipes)、套接字(sockets)等; +- 线程间通讯依靠 JVM 提供的 API,例如 wait 方法、notify 方法和 notifyAll 方法,线程间还可以通过共享的主内存来进行值的传递; + +### Thread + +- 实现 Runnable 接口 +- 方法 + + - join() + + - 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。主线程必须等待子线程执行完毕才结束。 + - 如果在一个线程 A 中调用另一个线程 B 的 join 方法,线程 A 将会等待线程 B 执行完毕后再执行。 + + - interrupted() + + - 清除中断状态 + + - isInterrupted() + + - 不清除中断状态 + + - start() + + - 开启一个线程,由新创建状态变为可运行状态 + + - run() + + - 声明线程业务的方法,最终会被 start() 方法调用 + + - holdsLock() + + - 检测线程是否持有锁 + + - yield() + + - 暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃 CPU 占用而不能保证使其它线程一定能占用 CPU,执行 yield() 的线程有可能在进入到暂停状态后马上又被执行。 + - 不会使当前线程阻塞 + + - sleep() + + - 暂停当前线程并让出 cpu 的执行时间,不会释放当前持有的对象的锁资源,到时间后会继续执行。 + - 经常拿来与 Object.wait() 作对比,关于 wait() 参考面向对象分支。 + + - suspend() 和 resume() + + - 两个方法配套使用,suspend() 使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。 + - JDK 1.5 中已经废除了这两个方法,因为存在死锁倾向 + +- 实现线程阻塞的方法 + + - sleep() + - Object.wait() + - join() + - 包含已经过时的 suspend + +### 线程模型 + +- 主线程(每个进程只有一个主线程) + + - main() 方法 + +- 子线程 + + - 非主线程的都是子线程 + + - 守护线程 + + - 为主线程提供一种通用服务的线程,比如 GC 线程。 + + - 非守护线程,也称用户线程 + + - 异步处理一些业务或逻辑 + - 用户线程在 start 之前可以通过 setDaemo(true) 来转变为守护线程 + - 如果在 start 之后调用 setDaemo(true),将会 throw IllegalThreadStateException。 + + - JVM 会一直运行,直到 + + - 调用了 exit() 方法,并且 exit() 有权限被正常执行。 + - JVM中仅仅只有「守护线程」 + +- 内核线程 + + - 由操作系统来直接支持与管理。 + - 除内核线程之外,所有涉及到线程、并发等概念的都可以叫做「用户线程」 + +- 内核线程与用户线程模型 + + - 一对一(Java 语言采用) + + - 又叫作内核级线程模型,即一个用户线程对应一个内核线程,内核负责每个线程的调度,可以调度到其他处理器上面。 + + - 优势 + + - 实现简单 + + - 劣势 + + - 对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换; + - 内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响; + + - 多对一(P\ython 的 gevent) + + - 又叫作用户级线程模型,即多个用户线程对应到同一个内核线程上,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。 + + - 优势 + + - 用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换,使线程的创建、调度、同步等非常快; + + - 劣势 + + - 由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行; + - 内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等; + + - 多对多(Go 语言中的 goroutine 调度器) + + - 又叫作两级线程模型,用户线程与内核线程是多对多(m : n,通常 m >= n)的映射模型。 + + - 优势 + + - 兼具多对一模型的轻量; + - 由于对应了多个内核线程,则一个用户线程阻塞时,其他用户线程仍然可以执行; + - 由于对应了多个内核线程,则可以实现较完整的调度、优先级等; + + - 劣势 + + - 实现复杂 + +## 集合 + +### Collection + +- List(默认大小为10) + + - ArrayList + + - 线程不安全 + - 检索快 + - JDK 8 下的扩容策略: +一、1.5倍扩容 +二、如果超过 MAX_ARRAY_SIZE 进行 huge 扩容 +三、MAX_ARRAY_SIZE 是 Integer 最大值-8,size 最大值就是 Integer.MAX_VALUE。 + - 简单源码分析 + + - 父级继承实现 + + - 实现了 RandomAccess 接口,可以随机访问 + - 实现了 Cloneable 接口,可以克隆 + - 实现了 Serializable 接口,可以序列化、反序列化 + - 实现了 List 接口,是 List 的实现类之一 + - 实现了 Collection 接口,是 Java Collections Framework 成员之一 + - 实现了 Iterable 接口,可以使用 for-each 迭代 + + - 类特点 + + - ArrayList 是实现 List 接口的可自动扩容的数组。实现了所有的 List 操作,允许所有的元素,包括 null 值。 + - ArrayList 大致和 Vector 相同,除了 ArrayList 是非同步的。 + - size isEmpty get set iterator 和 listIterator 方法时间复杂度是 O(1),常量时间。其他方法是 O(n),线性时间。 + - 每一个 ArrayList 实例都有一个 capacity(容量)。capacity 是用于存储列表中元素的数组的大小。capacity至少和列表的大小一样大。 + - 如果多个线程同时访问 ArrayList 的实例,并且至少一个线程会修改,必须在外部保证 ArrayList 的同步。修改包括添加删除扩容等操作,仅仅设置值不包括。这种场景可以用其他的一些封装好的同步的 list。如果不存在这样的Object,ArrayList 应该用 Collections.synchronizedList 包装起来最好在创建的时候就包装起来,来保证同步访问。 + - iterator() 和 listIterator(int) 方法是 fail-fast 的,如果在迭代器创建之后,列表进行结构化修改,迭代器会抛出 ConcurrentModificationException。 + - 面对并发修改,迭代器快速失败、清理,而不是在未知的时间不确定的情况下冒险。请注意,快速失败行为不能被保证。通常来讲,不能同步进行的并发修改几乎不可能做任何保证。因此,写依赖这个异常的程序的代码是错误的,快速失败行为应该仅仅用于防止 bug。 + + - ArrayList 基本特点总结 + + - ArrayList 底层的数据结构是数组 + - ArrayList 可以自动扩容,不传初始容量或者初始容量是0,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建 ArrayList 的时候,给初始容量是必要的 + - Arrays.asList() 方法返回的是的 Arrays 内部的 ArrayList,用的时候需要注意 + - subList() 返回内部类,不能序列化,和 ArrayList 共用同一个数组 + - 迭代删除要用,迭代器的 remove 方法,或者可以用倒序的 for 循环 + - ArrayList 重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间 + - elementData 不使用 private 修饰,可以简化内部类的访问 + + - Vector + + - 线程安全 + - addElement(Object obj)、capacity()、add(int index, Object element)、contains(Object elem) + - 四种构造方法 + + - LinkedList + + - 双向链表 + - 数据操作快,删除,增加 + - 需要更多的内存 + - 线程不安全 + + - List list = Collections.synchronizedList(new LinkedList()); + - 将 LinkedList 替换成 ConcurrentLinkedQueue + +- Set + + - HashSet + - SortedSet + +- Queue + + - Deque + + - LinkedList + + - BlockingQueue + - 声明的方法 + + - boolean add(E e); + + - 将指定的元素添加入队列,如果队列是有界的且没有空闲空间则抛出异常。 + + - boolean offer(E e); + + - 将指定的元素添加入队列,如果队列是有界的且没有空闲空间则返回 false。 + - 在使用有界队列时推荐使用该方法来替代 add 方法。 + + - E remove(); + + - 删除并返回队首的元素。如果队列为空则会抛异常。 + + - E poll(); + + - 删除并返回队首的元素。如果队列为空返回 null。 + + - E element(); + + - 返回队首元素但是不删除。如果队列为空会抛出异常。 + + - E peek(); + + - 返回队首元素但是不删除。如果队列为空则返回 null。 + + - 实现类 + + - 并发队列 + + - ConcurrentLinkedQueue + + - 阻塞队列 + + - ArrayBlockingQueue + + - 一个由数组结构组成的有界阻塞队列。 + + - LinkedBlockingQueue + + - 一个由链表结构组成的有界阻塞队列。 + + - PriorityBlockingQueue + + - 一个支持优先级排序的无界阻塞队列。 + + - DelayQueue + + - 一个使用优先级队列实现的无界阻塞队列。 + + - SynchronousQueue + + - 一个不存储元素的阻塞队列。 + + - LinkedTransferQueue + + - 一个由链表结构组成的无界阻塞队列。 + + - 双端队列 + + - Deque + - ArrayDeque + - LinkedList + - ConcurrentLinkedDeque + + - 优先级队列 + + - PriorityQueue + - PriorityBlockingQueue + +### Map + +- AbstractMap + + - HashMap(16/0.75) + + HashMap 的数据结构是由 Node 作为元素组成的数组: + 一、如果有多个值 hash 到同一个桶中,则组织成一个链表,当这个链表的节点个数超过某个值(TREEIFY_THRESHOLD 参数指定)时,则将这个链表重构为一个二叉树; + 二、如果发现 map 中的元素个数超过了 threshold,则进行二倍空间扩容。 + 三、HashMap 接受 value 为 null。 + 四、线程不安全。 + + - LinkedHashMap + + - WeakHashMap + - TreeMap + - IdentityHashMap + +- SortedMap + + - NavigableMap + + - TreeMap + +- Hashtable + + HashTable 的数据结构和 HashMap 基本相同,但是属于线程安全,只不过是每次操作会锁住整个表结构,一次只能有一个线程访问 HashTable 对象。 + +### Dictionary + +- Hashtable + +## 输入输出流 + +### I/O + +- 字节流 + + - 就是万能流,什么都能读 + +- 字符流 + + - 只能读取普通的文本 + +- 缓冲流 + + - BufferedInputStream + - BufferedOutputStream + - BufferedReader + - BufferedWriter + +### NIO + +## Java 类 + +### 成员变量 + +- 成员变量是可以不用给初始值的,默认就有一个初始值。 + +### 局部变量 + +- 局部变量,必须显示给予一个初始值,否则编译无法通过。 + +### 基本数据类型 + +- String + + - String 中 hashcode 的实现 + + - 以 31 为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模。 + - 31 是一个奇质数,所以 31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。 + + - String 能存储的最大字符串数量 + + - 编译期 65534 个 + - 运行期 Integer.MAX_VALUE 大约 4G + +### 如何写一个不可变的类 + +- 类添加 final 修饰符,保证类不被继承。 +- 保证所有成员变量必须私有,并且加上 final 修饰 +- 不提供改变成员变量的方法,包括 setter +- 通过构造器初始化所有成员,进行深拷贝(deep copy) + +## java.lang.annotation(Java 注解) + +### 基本知识 + +- 注解(Annotation),也叫元数据(Metadata),是Java5的新特性 +- 注解与类、接口、枚举在同一个层次,并可以应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中,用来对这些元素进行说明注释。 + +### 注解的语法与定义形式 + +- 以 @interface 关键字定义 +- 注解包含成员,成员以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。 +- 成员赋值是通过 @Annotation(name=value) 的形式。 +- 注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。 + +### 注解的分类 + +- 第一种分法 + + - 基本内置注解 + + - @Override + - @Deprecated + - @SuppressWarnings + + - 元注解,负责注解其他注解的注解 + + - @Target + + - 标明注解的修饰目标,共有 + + - @Retention + - @Documented + + - 标记注解,用于描述其它类型的注解应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。 + + - @Inherited + + - 标记注解,允许子类继承父类的注解 + + - 自定义注解 + +- 第二种分法 + + - 通过元注解 @Retention 实现,注解的值是 enum 类型的 RetentionPolicy + + - 用来修饰注解,是注解的注解,称为元注解。 + - RetentionPolicy + + - RetentionPolicy.SOURCE + + - 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃; + - 对应 Java 源文件(.java文件) + + - RetentionPolicy.CLASS + + - 注解被保留到 class 文件,但 jvm 加载 class 文件时候被遗弃,这是默认的生命周期; + - 对应 .class 文件 + + - RetentionPolicy.RUNTIME + + - 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在; + - 内存中的字节码 + + - 生命周期长度 SOURCE < CLASS < RUNTIME + + - 一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如 @Deprecated 使用 RUNTIME 注解 + - 如果要在编译时进行一些预处理操作,比如生成一些辅助代码就用 CLASS注解; + - 如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用 SOURCE 注解。 + +## JDBC + +### PreparedStatement + +### Statement + +### 数据库连接 + +- DataSource + + - DataSource 是作为 DriverManager 的替代品而推出的,DataSource 对象是获取连接的首选方法。 + - 类似于一个 DriverManager,拥有对外提供连接的能力,是一个工厂对象 + - DataSource 中获取的连接来自于连接池中,而池中的连接根本也还是从 DriverManager 获取而来 + - 形式是 JNDI (Java Naming Directory Interface),在应用程序与数据库连接之间插入了一个中间层,进而可以实现连接池以及事务管理 + - 通过 DataSource 对象访问的驱动程序本身不会向 DriverManager 注册 + - CommonDataSource + + - DataSource + + - 获取 connection 的接口 + + - XADataSource + + - 用来获取分布式事务连接的接口 + + - ConnectionPoolDataSource + + - 从 connection pool 中拿 connection 的接口 + + - 额外功能 + + - 缓存 PreparedStatement 以便更快的执行 + - 可以设置连接超时时间 + - 提供日志记录的功能 + - ResultSet 大小的最大阈值设置 + - 通过 JNDI 的支持,可以为 servlet 容器提供连接池的功能 + +- DriverManager + + - Connection conn = DriverManager.getConnection(url, user, password); 建立与数据库的连接 + - 建立与数据库的连接是一项较耗资源的工作,频繁的进行数据库连接建立操作会产生较大的系统开销。 + +### JDBC 定义的五种事务隔离级别 + +- TRANSACTION_NONE + + - JDBC驱动不支持事务 + +- TRANSACTION_READ_UNCOMMITTED + + - 允许脏读、不可重复读和幻读。 + +- TRANSACTION_READ_COMMITTED + + - 禁止脏读,但允许不可重复读和幻读。 + +- TRANSACTION_REPEATABLE_READ + + - 禁止脏读和不可重复读,但允许幻读。 + +- TRANSACTION_SERIALIZABLE + + - 禁止脏读、不可重复读和幻读。 + +## 面向对象 + +### 抽象 + +### 封装 + +### 多态 + +### 继承 + +### Object + +- getClass() +- equals(Object obj),重写的时候必须覆写 hashCode() + + - 自反性:obj.equals(obj) 结果一定是 true。 + - 对称性:如果 obj1.equals(obj2) 结果为 true,那么obj2.equals(obj1) 也肯定是true。 + - 传递性:如果 obj1.equals(obj2) 结果为 true,obj2.equals(obj3) 结果也为 true,那obj1.equals(obj3) 肯定也是 true。 + - 一致性:对任意的 obj1 和 obj2,如果对象中用于等价比较的信息没有改变,那么无论调用 obj1.equals(obj2) 多少多少次,返回的结果是要保持一致的。即要么一直是 true,要么一直是 false。 + - 非空性:以上几条都不包含obj 为 null 的情况,而且对于任何非 null 的 obj,obj.equals(null) 都应该返回 false。 + +- hashCode() +- clone() + + - 浅拷贝、浅克隆 + - 深拷贝、深克隆 + + - 一、构造函数,调用构造函数时进行深拷贝 + - 二、重载 clone() 方法 + - 三、序列化 + +- toString() +- notify() + + - 使用 notify() 的时候,在众多等待同一个锁的任务中只有一个会被唤醒,因此如果希望使用notify(),就必须保证被唤醒的是恰当的任务。 + +- notifyAll() + + - notifyAll() 将唤醒“所有正在等待的任务”。这并不是意味着在程序中任何地方,任何处于 wait() 状态中的任务都将被任何对 notify() 的调用唤醒,而是 notifyAll() 因某个特定锁而被调用的时候,只有等待这个锁的任务才会被唤醒。 + +- wait(三种重载) + + - 暂停当前线程并让出 cpu 的执行时间,会放弃所有锁并需要 notify/notifyAll 后重新获取到对象锁资源后才能继续执行。 + - 只能在同步方法或者同步块中使用。 + +- finalize(JDK9被标记过时) + + - 在 GC 决定回收一个不被其他对象引用的对象时调用。 + - 任何对象的 finalize 方法只会被 JVM 调用一次。 + +### Bean 的转换 + +- 分层领域模型对象 + + - PO(Persistent Object): 持久对象,数据;就是 DAO 层操作的对象。 + - BO(Business object) :业务对象,封装对象、复杂对象 ,里面可能包含多个类;你可以当做 service 层(业务层)需要使用的。 + - DTO(Data Transfer Object): 传输对象,前端调用时传输; + - VO(View Object):表现对象,前端界面展示。专门用来表现数据内容的,你可以理解为 SpringMVC 中 model 传输的那个对象,你可能会问它与 DTO 是什么区别,举个例子:当对象中有个 status 字段,它不需要展示在前端中,但是数据传输的时候需要用它来对前端用户做验证,验证用户状态是否正常,这个时候用到的就是 DTO,但是他不负责展示给用户,这么说吧 controller 接受 DTO,发送 VO + - DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。其实它一般也是和数据库中的表对应,更严谨一些。 + +- 转换工具 + + - 自己对每个属性进行 setXX 方法的转换 + - Apache 的 BeanUtils 类 + + - 性能差 + + - Spring 的 BeanUtils + + - 调用次数足够多的时候,会明显的感受到卡顿 + + - Cglib BeanCopier + - MapStruct + + - 推荐 + +## Java 8 新特性 + +### Arrays 类的增强 + +- binarySearch、copyOf、copyOfRange、equals、fill、toString +- Java 8 与时俱进的增加了并发支持 + + - XxxStream(Stream、IntStream、LongStream、DoubleStream)- 将数组转化为流式 API 进行操作,Stream 流也是 Java 8 新增的集合操作特性,下面有记录。 + - Spliterator spliterator、Spliterator.OfInt spliterator、Spliterator.OfLong spliterator、Spliterator.OfDouble spliterator - 将数组元素转换成对应的 Spliterator 对象 + - parallel 系列增强,这些方法增加了并行能力,可以利用 CPU 并行来提高性能。 + + - setAll:使用指定生成器为数组元素赋值 + - parallelSetAll:在 setAll d的基础上增强并行能力 + - parallelPrefix:使用 op 参数指定的算法将计算结果作为元数组得到的新元素结果。op 计算公式包括 left 和 right,分别代表前一索引处的元素和当前索引处元素。 + - parallelSort:跟原生 sort 方法一样,但是增强并行能力。 + +### 包装类的增强 + +- 基本数据类与对应的包装类 + + - byte-Byte + - short-Short + - int-Integer + + - Integer 底层是现在在-128~127 有一个数组缓存,再次范围内的基本数据类型都从缓存数组中取值 + + - long-Long + - char-Character + - float-Float + - double-Double + - boolean-Boolean + +- 自动拆箱/自动装箱 + + - 自动装箱:基本类型变量直接赋给包装类变量,或者赋给 Object + - 自动拆箱:允许包装类对象直接赋给一个对应的基本数据类型变量 + +- Java 8 新增支持无符号运算 + + - Integer、Long + + - toUnsignedString,将整数转换为无符号整数对应的字符串。 + - parseUnsignedXxx,将字符串解析成无符号整数。 + - compareUnsigned,将这个数转换为无符号整数在比较大小。 + - divideUnsigned,将整数转换为无符号整数然后计算他们相除的商。 + - remainderUnsigned,将整数转换为无符号整数然后计算他们相除的余数。 + + - Byte、Short + + - toUnsignedInt + - toUnsignedLong + +### 改进的接口 + +- 允许定义默认方法,提供实现,default 修饰 +- 允许定义类方法,static 修饰 + +### 改进的匿名内部类 + +- 在 Java 8 之前,Java 要求被局部内部类、匿名内部类访问的局部变量必须使用 final 修饰,从 Java 8 开始这个限制被取消。 +- 如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了 final 修饰。 +- 对于被匿名内部类访问的局部变量,可以用 final 修饰,也可以不用 final 修饰,但必须还得按照有 final 修饰的方式来用,不能重新赋值,叫做 effectively final。 +- 匿名内部类的一些特点 + + - 匿名内部类必须继承一个父类或实现一个接口且只能一个父类或者接口。 + - 匿名内部类不能是抽象类 + - 匿名内部类不能定义构造器 + - 匿名内部类只有一个隐式的无参构造器,new 接口名后的括号里面不能传参 + - 如果通过继承父类来创建匿名内部类,匿名内部类可以拥有父类的构造器。 + - 当创建匿名内部类时,必须实现接口或抽象父类里面的所有抽象方法。 + +### 新增的 Lambda 表达式(代码块作为方法参数,主要作用就是代替匿名内部类的繁琐语法) + +- Lambda 基础 + + - 形参列表 + - 箭头(->) + - 代码块 + +- Lambda 表达式与函数式接口 + + - 函数式接口:只包含一个抽象方法的接口 + - @FunctionalInterface 注解 + - Lambda 的限制 + + - 目标类型必须是明确的函数式接口 + - 只能为函数式接口创建对象 + + - java.util.function + + - XxxFunction->apply():指定数据进行转换处理,返回新值。 + - XxxConsumer->accept():同样负责参数处理,但是不返回处理结果。 + - XxxPredicate->test():对参数进行某种判断,返回布尔值,用于数据筛选。 + - XxxSuplier->getAsXxx():根据某周逻辑算法返回某种结果。 + +- 方法引用与构造器引用,两个冒号 + + - 引用类方法,Integer::valueOf + - 引用特定对象的实例方法,"str"::indexOf + - 引用某类对象的实例方法,String::substring + - 引用构造器,String::new + +- Lambda 表达式与匿名内部类的联系与区别 + + - 相同 + + - 都可以直接访问「effectively final」以及外部类的成员变量 + - 生成的对象一样,都可以直接调用从接口中继承的默认方法 + + - 不同 + + - Lambda 表达式只能为函数式接口创建实例,而匿名内部类可以实现所有的抽象方法。 + - 匿名内部类可以为抽象类甚至普通类创建实例。 + - Lambda 不允许调用接口中定义的默认方法 + +### 日期/时间类 + +- java.time + + - Clock,获取指定时区的当前日期、时间,可以取代 System.currentTimeMills() + - Duration,代表持续时间 + - Instant,代表一个具体的时刻,可以精确到纳秒。 + - LocalDate,代表不带时区的日期。 + - LocalTime,代表不带时区的时间 + - LocalDateTime,不带时区的日期、时间 + - MonthDay,代表月日 + - Year,年 + - YearMonth,年月 + - ZonedDateTime,时区化的日期、时间 + - ZoneId,代表一个时区 + - DayOfWeek,枚举类,定义周日到周六的枚举值 + - Month,枚举类,定义一月到十二月的枚举值。 + +### 集合的新增与改进 + +- Collection&Iterator 接口 + + - 增强的 Iterator 遍历集合元素,forEachRemaining(Consumer action) + - 新增的Predicate 操作集合,新增 removeIf(Predicate filter) 方法,批量删除符合 filter 条件的所有元素。 + - 新增的 Stream 操作集合,Stream、IntStream、LongStream、DoubleStream + + - 步骤 + + - builder() 类方法创建对应的 Builder。 + - 重复调用 add() 向流中添加元素 + - 调用 build() 方法获取对应的 Stream + - 调用 Stream 的聚集方法 + + - 中间方法 + + - filter、mapToXxx + - peek、distinct + - sorted、limit + + - 末端方法 + + - forEach、toArray + - reduce、count + - min、max + - anyMatch、allMatch + - findFirst、findAny + + - 有状态的方法,给流添加一些新的属性 + + - sorted + - distinct + - limit + + - 短路方法,尽早的结束对流的操作 + + - limit + +- List 集合 + + - replaceAll(UnaryOperator operator),根据指定的计算规则重新设置所有元素 + - sort(Comparator c),根据 Comparator 参数对 List 进行排序。 + +- Map 集合 + + - remove、putIfAbsent + - compute、computeIfAbsent、computeIfPresent + - forEach、getOrDefault、merge + - replace、replaceAll + +### 泛型,改进了泛型方法的类型推断能力 + +### JDBC + +- executeLargeUpdate(),MySQL驱动暂不支持 +- executeLargeBatch() + +### 注解 + +- 重复注解 +- 类型注解 + +### 多线程 + +- 改进的线程池 + + - newWorkStealingPool(int parallelism),创建持有足够线程的线程池来支持给定的并行级别。 + - newWorkStealingPool,目标并行级别等于当前机器 CPU 数量。 + +- 增强的 ForkJoinPool,通用池 + + - commonPool(),返回一个通用池 + - getCommonPoolParallelism(),返回通用池的并行级别 + +### 反射,新增方法参数反射 + +- getParameterCount(),获取构造器或方法的形参个数 +- Parameter[] getParameters() + + - getModifiers()、getName() + - getParameterizedType()、getType() + - isNamePresent()、isVarArgs + +## Java 9 新特性 + +### 最主要的变化是已经实现的模块化系统,是一个包的容器 + +### HTTP 2 客户端 + +### 改进的 Javadoc + +### 多版本兼容 JAR 包 + +### 集合工厂方法 + +### 私有接口方法 + +### 进程 API + +### 改进的 Stream API + +### 改进 try-with-resources + +### 改进的弃用注解 @Deprecated + +### 改进钻石操作符(Diamond Operator) + +### 改进 Optional 类 + +### 改进的 CompletableFuture API + +### 响应式流(Reactive Streams) API + +## Java 10 新特性 + +### 局部变量类型推断 + +- 引入了 var 语法,可以自动推断变量类型 + +### GC改进和其他内务管理 + +### 线程本地握手 + +### 备用内存设备上的堆分配 + +### 根证书认证程序 + +## Java 11 新特性 + +### Java 8 之后的第一个 LTS 版本,从 Java 11 开始,Oracle JDK 不在以免费的用于商业用途 + +### String API + +- isBlank() 判空 +- lines() 分割获取字符串流 +- repeat() 复制字符串 +- strip() 去除前后空白字符 + +### File API + +### HTTP Client + +- 支持 HTTP/1.1 和 HTTP/2 ,也支持 websockets。 + +### Lambda 局部变量推断 + +### 单命令运行 Java + +### 免费的飞行记录器 + +## Java 12 新特性 + +### Switch 表达式 + +### Shenandoah GC + +### JVM 常量 API + +### G1的可中断 mixed GC + +### G1归还不使用的内存 + +## Java 13 新特性 + +### GC 层面则改进了 ZGC,以支持 Uncommit Unused Memory + +### 语法层面,改进了 Switch Expressions,新增了 Text Blocks,二者皆处于 Preview 状态;API 层面主要使用 NioSocketImpl 来替换 JDK1.0 的 PlainSocketImpl + +## Java 14 新特性 + +### instanceof 的模式匹配 + +- 对目标对象进行检查的断言(predicate) +- 当断言成立时,从目标对象中提取值的绑定变量。 + +### Java 打包工具 + +### G1 支持 NUMA + +### 更有价值的 NullPointerException + +### 删除 CMS 垃圾回收器 + +### 废弃 ParallelScavenge + SerialOld 的 GC 组合 + +## Java 15 新特性 + +### 外内存访问 API + +### 密封类(sealed classes)的预览 + +### 数字签名算法 + +### 套接字的更新实现 + +### instanceof模式匹配 + +### ZGC 产品化 + +### RMI Activation 进入不推荐期 + diff --git "a/Java\345\237\272\347\241\200/J2SE.xmind" "b/Java\345\237\272\347\241\200/J2SE.xmind" index ff88b03..b668bbb 100644 Binary files "a/Java\345\237\272\347\241\200/J2SE.xmind" and "b/Java\345\237\272\347\241\200/J2SE.xmind" differ diff --git "a/Java\345\237\272\347\241\200/Lombok.md" "b/Java\345\237\272\347\241\200/Lombok.md" new file mode 100644 index 0000000..a56b359 --- /dev/null +++ "b/Java\345\237\272\347\241\200/Lombok.md" @@ -0,0 +1,32 @@ +# Lombok + +## 简介 + +### 是一个优秀的 Java 代码库,它采用了一种取巧的语法糖,简化了 Java 的编码,为 Java 代码的精简提供了一种方式,但在使用此代码库时,需要了解到 Lombok 并非一个标准的 Java 库。 + +### 利用 Java 语言在编译时的空档期,使用一种很取巧的方式,将我们所需要的方法注入(写入)到当前的类中 + +## Lombok 的痛点 + +### JDK 版本问题 + +- 从 8 到 11 可能会造成 Lombok 不兼容 + +### 胁迫使用 + +- 你的代码用了,别人就得用 +- 别人用了,你就得用 + +### 可读性差 + +- Lombok 隐藏了 JavaBean 封装的细节 +- 构造器参数的顺序完全由 Lombok 所控制 + +### 代码耦合度增加 + +- 使用 Lombok 来编写某一个模块的代码后,其余依赖此模块的其他代码都需要引入 Lombok 依赖,同时还需要在 IDE 中安装 Lombok 的插件。 + +### 得不偿失 + +- 在一定程度上 Lombok 减少了样板代码的书写,但也带来了一些未知的风险。 + diff --git "a/Java\345\237\272\347\241\200/Lombok.xmind" "b/Java\345\237\272\347\241\200/Lombok.xmind" new file mode 100644 index 0000000..c3b43c6 Binary files /dev/null and "b/Java\345\237\272\347\241\200/Lombok.xmind" differ diff --git "a/Java\345\237\272\347\241\200/README.md" "b/Java\345\237\272\347\241\200/README.md" index 99173b9..4d56b8d 100644 --- "a/Java\345\237\272\347\241\200/README.md" +++ "b/Java\345\237\272\347\241\200/README.md" @@ -1,11 +1,13 @@ # Java 基础 -Java 知识体系涉及到的各方面基础知识点脑图、思维导图总结。 +基于 Java8 的 Java 知识体系涉及到的各方面基础知识点脑图、思维导图总结。 目前大分类列表: - J2SE - 小数 & BigDecimal +在 J2SE 中主要总结 Java 语法相关基础知识,如集合、多线程、Java8 新特性以及面向对象等。 + 根据个人的总结进度持续更新,如果对你有帮助就算我功德无量了。可以提建议,也可以 fork 一起完善,共同进步。 官网:高老四博客 diff --git "a/Java\345\237\272\347\241\200/\345\260\217\346\225\260 & BigDecimal.md" "b/Java\345\237\272\347\241\200/\345\260\217\346\225\260 & BigDecimal.md" new file mode 100644 index 0000000..7227201 --- /dev/null +++ "b/Java\345\237\272\347\241\200/\345\260\217\346\225\260 & BigDecimal.md" @@ -0,0 +1,54 @@ +# 小数 & BigDecimal + +## 小数 + +### 定点数 + +- 纯小数 + + - 小数点固定在数值部分的最高位之前,例如 1111 就是 -1*(2^-1*1+2^-2*1+2^-3*1)=-0.875 + +- 纯整数 + + - 小数点固定在数值部分的最后面,例如 1111 表示 -7 + +### 浮点数 + +- 精度 + + - 单精度 + + - float,4个字节32位 + + - 双精度 + + - double,8个字节64位 + + - 延伸单精度 + - 延伸双精度 + +- 科学记数法(单精度为例) + + - 科学记数法包含:符号、有效数字、指数 + - 符号位:最高二进制位上分配,0 为正 1 为负 + - 尾数,就是有效数字:最右侧分配连续的 23 位用来存储有效数字部分 + - 阶码,也就是指数:符号位右侧分配 8 位存储,二进制转十进制时需要减掉 127,双精度减掉 1023 + +## BigDecimal(精确运算) + +### 通过十进制和小数点解决精度问题 + +### 主要构造方法 + +- public BigDecimal(double val) 建议不要使用,因为 Double 本身就不精确 +- public BigDecimal(int val) +- public BigDecimal(String val) +- public BigDecimal add(BigDecimal augend) +- public BigDecimal subtract(BigDecimal +subtrahend) +- public BigDecimal multiply(BigDecimal +multiplicand) +- +public BigDecimal divide(BigDecimal +divisor) + diff --git "a/Java\350\277\233\351\230\266/Guava.md" "b/Java\350\277\233\351\230\266/Guava.md" new file mode 100644 index 0000000..fff6f47 --- /dev/null +++ "b/Java\350\277\233\351\230\266/Guava.md" @@ -0,0 +1,64 @@ +# Guava + +## 基本工具 + +### 使用和避免 null + +- null 是模棱两可的,会引起令人困惑的错误,有些时候它让人很不舒服。很多 Guava 工具类用快速失败拒绝 null 值,而不是盲目地接受 + +### 前置条件 + +- 让方法调用的前置条件判断更简单。 +- Preconditions + +### 常见 Object 方法 + +- Objects.equals +- Objects.hash(Object...) +- Objects.toStringHelper +- ComparisonChain + +### 排序 + +### Throwables + +## 集合 + +### 不可变集合 + +- 不可变对象的优点 + + - 当对象被不可信的库调用时,不可变形式是安全的; + - 不可变对象被多个线程调用时,不存在竞态条件问题 + - 不可变集合不需要考虑变化,因此可以节省时间和空间。 + - 所有不可变的集合都比它们的可变形式有更好的内存利用率 + - 不可变对象因为有固定不变,可以作为常量来安全使用。 + +### 新集合类型 + +### 强大的集合工具类 + +### 集合扩展工具类 + +## 缓存 + +### 子主题 1 + +## 函数式编程 + +## 并发 + +## 字符串处理:分割,连接,填充 + +## 原生类型 + +## I/O + +## 散列 + +## 事件总线 + +## 数学运算 + +## 反射 + diff --git "a/Java\350\277\233\351\230\266/Guava.xmind" "b/Java\350\277\233\351\230\266/Guava.xmind" new file mode 100644 index 0000000..2e8ac54 Binary files /dev/null and "b/Java\350\277\233\351\230\266/Guava.xmind" differ diff --git "a/Java\350\277\233\351\230\266/Java NIO.xmind" "b/Java\350\277\233\351\230\266/Java NIO.xmind" new file mode 100644 index 0000000..0a5df81 Binary files /dev/null and "b/Java\350\277\233\351\230\266/Java NIO.xmind" differ diff --git "a/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.md" "b/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.md" new file mode 100644 index 0000000..9ecbb80 --- /dev/null +++ "b/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.md" @@ -0,0 +1,116 @@ +# Java 日志体系 + +## 日志门面 + +门面指的是设计模式中的「外观模式」,关于更多资料可以参考一下老四的《浅析设计模式第十二章之外观模式》这篇文章。http://www.glorze.com/483.html + +### JCL(Jakarta Commons Logging),就是现在我们所说的 Apache Commons Logging + +- 实现机制:程序运行时,使用自己的 ClassLoader 寻找和载入本地具体的实现。 + + ClassLoader 是 Java 类中的加载器,相关知识点可以参考一下老四写的《浅析Java反射系列相关基础知识(上)之类的加载以及反射的基本应用》这篇文章。http://www.glorze.com/1180.html + +### SLF4J(Simple Logging Facade for Java) + +- 实现机制:Slf4j 在编译期间,静态绑定本地的 Log 库 +- 核心类与接口 + + - org.slf4j.LoggerFactory(class) + + - 给调用方提供的创建Logger的工厂类,在编译时绑定具体的日志实现组件 + + - org.slf4j.Logger(interface) + + - 给调用方提供的日志记录抽象方法,例如debug(String msg),info(String msg)等方法 + + - org.slf4j.ILoggerFactory(interface) + + - 获取的Logger的工厂接口,具体的日志组件实现此接口 + + - org.slf4j.helpers.NOPLogger(class) + + - 对org.slf4j.Logger接口的一个没有任何操作的实现,也是Slf4j的默认日志实现 + + - org.slf4j.impl.StaticLoggerBinder(class) + + - 与具体的日志实现组件实现的桥接类,具体的日志实现组件需要定义org.slf4j.impl包,并在org.slf4j.impl包下提供此类 + + - MDC(Mapped Diagnostic Contexts) + + - 实现分布式多线程日志数据传递的重要工具 + - 可以将某个或某些所有日志中都需要打印的字符串设置于 MDC 中,就不需要每次打印日志时都专门写出来 + + - 调用链系统中调用链唯一标示 traceID 及其中某次调用关系标示 rpcID。 + + - 基本的日志实现都是利用 ThreadLocal 去实现 MDC,达到不同线程间日志信息互不影响的目的。 + +- 与其他各种日志组件的桥接 + + - logback-classic-X.X.XX.jar + + - Slf4j 的原生实现,Logback 直接实现了 Slf4j 的接口,因此使用 Slf4j 与 Logback 的结合使用也意味更小的内存与计算开销 + + - slf4j-log4jXX-X.X.XX.jar + + - 需要将Log4j.jar加入Classpath + + - slf4j-jdkXX-X.X.XX.jar + + - java.util.logging的桥接器,Jdk原生日志框架。 + + - slf4j-jcl-X.X.XX.jar + + - Jakarta Commons Logging 的桥接器. 这个桥接器将 Slf4j 所有日志委派给 Jcl。 + + - slf4j-nop-X.X.XX.jar + + - NOP 桥接器,默默丢弃一切日志。 + + - slf4j-simple-X.X.XX.jar + + - 一个简单实现的桥接器,该实现输出所有事件到System.err. 只有Info以及高于该级别的消息被打印,在小型应用中它也许是有用的。 + +### JBoss Logging + +## 日志实现 + +### Log4j + +- apache实现的一个开源日志组件。 + +### Log4j2 + +- 为了对抗 LogBack,对 Log4j 进行了全部重写,不兼容 Log4j。 + +### LogBack + +- 其实 LogBack 的作者就是 Log4j 的作者,因为他不喜欢 Apache 公司的工作方式,自己开了公司又写了 Slf4j。 + +### JUL(Java Util Log) + +## 常用的组合使用方式 + +### Commons Logging 与 Log4j 组合使用 + +- 随着 Slf4j 的性能优良,使用越来越高,这个组合逐渐被淡化。相比之下开销比较高。 + +### Slf4j 与 Logback 组合使用 + +LogBack 必须与 Slf4j 使用,因为是同一个作者,保证兼容性,别瞎用。 + +- 优势 + + - Slf4j 实现机制决定 Slf4j 限制较少,使用范围更广。由于 Slf4j 在编译期间,静态绑定本地的 LOG 库使得通用性要比 Commons Logging 要好。 + - Logback拥有更好的性能,更快的执行速度。Logback声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。 + - 充分的测试 + - logback-classic 非常自然的实现了 SLF4J + - 丰富的扩展文档 + - 可以使用使用 XML 配置文件或者 Groovy + - 自动重新载入配置文件 + - 优雅地从 I/O 错误中恢复 + - 自动清除旧的日志归档文件 + - 自动压缩归档日志文件 + - Logback 所有文档免费,Log4j 还有付费支持项 + - 软件工程的角度。抽象,解耦,便于维护。 + - 语法设计角度。slf4j 有 {} 占位符,而 log4j 需要用「+」来连接字符串,既不利于阅读,同时消耗了内存。 + diff --git "a/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.xmind" "b/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.xmind" index 501df86..8d13f41 100644 Binary files "a/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.xmind" and "b/Java\350\277\233\351\230\266/Java \346\227\245\345\277\227\344\275\223\347\263\273.xmind" differ diff --git "a/Java\350\277\233\351\230\266/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.md" "b/Java\350\277\233\351\230\266/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.md" new file mode 100644 index 0000000..6ea97b7 --- /dev/null +++ "b/Java\350\277\233\351\230\266/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.md" @@ -0,0 +1,1135 @@ +# 《码出高效》思维导图 + +## 计算机基础 + +### 走进 0 与 1 的世界 + +### 浮点数 + +- 科学记数法 +- 浮点数表示 +- 加减运算 +- 浮点数使用 + +### 字符集与乱码 + +### CPU 与内存 + +### TCP/IP + +- 网络协议 +- IP 协议 +- TCP 建立连接 +- TCP 断开连接 +- 连接池 + +### 信息安全 + +- 黑客与安全 +- SQL 注入 +- XSS 与 CSRF +- CSRF +- HTTPS + +### 编程语言的发展 + +## 面向对象 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +## 代码风格 + +### 命名规约 + +- 命名规约 + + - 命名符合本语言特性 + - 命名体现代码元素特征 + + - 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词 + - 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类名开始,以 Test 结尾。 + - 类型与中括号紧挨相连来定义数组 + - 枚举类名带上 Enum 后缀,枚举成员名称需要全部大写,单词间用下划线隔开。 + + - 命名最好忘文知义 + +- 常量 + + - 定义 + + - 作用域内保持不变的值,一般用 final 关键字进行修饰 + + - 种类 + + - 全局常量 + + - 类的公开静态属性,public static final 修饰 + + - 类内常量 + + - 类的私有静态属性,private static final 修饰 + + - 局部常量 + + - 方法常量 + + - 在方法或代码块内定义的常量 + + - 参数常量 + + - 定义形式参数时,增加 final 标识,标识此参数值不能被修改。 + + - 常量规约 + + - 字母全部大写,单词间加下划线,但是局部常量采用小驼峰形式即可。 + +- 变量 + +### 代码展示风格 + +- 缩进、空格与空行 +- 换行与高度 +- 控制语句 + +### 代码注释 + +- 注释三要素 +- 注释格式 + +## 走进 JVM + +### 字节码 + +- Java 的使命:一次编写,到处运行。字节码就是实现 Java 跨平台的中间层,JVM 将字节码解释执行,屏蔽底层的操作系统。 +- 字节码相关常识 + + - 初始 4 个字节 + + - cafe babe + + - Coffee Baby,标志该文件是一个 Java 类文件 + + - 版本号,代表 JDK 的版本 + + - ICONST_0 + + - 代表 00000011,十六进制的 0x03 + + - ALOAD_0 + + - 代表 00101010,即 0x2a + + - POP + + - 代表 01010111,即 0x57 + + - 首字母表示具体的数据类型 + + - A 代表引用类型变量 + - I 代表 int 类型相关操作 + - 其他类型均是其类型的首字母 + + - FLOAD_0 + - LLOAD_0 + - FCONST_0 + +- 字节码主要指令 + + - 加载或存储指令 + + - 在某个栈帧中,通过指令操作数据在虚拟机栈的局部变量表与操作栈之间来回传输 + - 将局部变量加载到操作栈中 + + - ILOAD + + - 将 int 类型的局部变量压入栈 + + - ALOAD + + - 将对象引用的局部变量压入栈 + + - 从操作栈顶存储到局部变量表 + + - ISTORE + - ASTORE + + - 将常量加载到操作栈顶(高频使用的指令) + + - ICONST 加载的是 -1 ~ 5 的数(ICONST 与 BIPUSH 的加载界限) + - BIPUSH,即 Byte Immediate PUSH,加载 -128 ~ 127 之间的数 + - SIPUSH,即 Short Immediate PUSH,加载 -32768 ~ 32767 之间的数 + - LDC,即 Load Constant,在 -2147483648 ~ 2147483647 或者是字符串时,JVM 采用 LDC 指令压入栈中 + + - 运算指令(对两个操作栈上的值进行运算,并把结果写入操作栈顶) + + - IADD + - IMUL + + - 类型转换指令(显示转换两种不同的数值类型) + + - I2L + - D2F + + - 对象创建与访问指令 + + - 根据类进行对象的创建、初始化、方法调用相关指令 + - 创建对象指令 + + - NEW + - NEWARRAY + + - 访问属性指令 + + - GETFIELD + - PUTFIELD + - GETSTATIC + + - 检查实例类型指令 + + - INSTANCEOF + - CHECKCAST + + - 操作栈管理指令(直接控制操作栈的指令) + + - 出栈操作 + + - POP 即一个元素,POP2 即两个元素 + + - 复制栈顶元素并压入栈 + + - DUP + + - 方法调用与返回指令 + + - INVOKEVIRTUAL + + - 调用对象的实例方法 + + - INVOKESPECIAL + + - 调用实例初始化方法、私有方法、父类方法等 + + - INVOKESTATIC + + - 调用类静态方法 + + - RETURN + + - 返回 VOID 类型 + + - 同步指令 + + - ACC_SYNCHRONIZED + + - 标志同步方法 + - MONITORENTER + - MONITOREXIT + + - LINENUMBER + + - 存储字节码与源代码行号的对应关系 + + - LOCALVARIABLE + + - 存储当前方法中使用到的局部变量表 + +- 源码转化成字节码 + + - Java 源文件 + - 词法解析 + + - 通过空格分隔出单词、操作符、控制符等信息,将其形成 token 流信息传递给语法解析器 + + - 语法解析 + + - 按照 Java 语法规则组装成一颗语法树 + + - 语义分析 + + - 检查关键字的使用是否合理、类型是否匹配、作用于是否正确等 + + - 生成字节码 + - 字节码 + +- 字节码的执行 + + - 字节码必须通过类加载过程加载到 JVM 环境后才可以执行 + - 执行的三种模式 + + - 解释执行 + - JIT 编译执行 + + - 作用是将 Java 字节码动态地变异成可以直接发送给处理器指令执行的机器码 + + - JIT 编译与解释混合执行(主流 JVM 默认执行模式) + + - 优势 + + - 解释器在启动时先解释执行,省去编译时间 + - 通过热点代码统计分析,识别高频的方法调用、循环体、公共模块等奖热点代码通过 JIT 动态编译技术转换成机器码交给 CPU 执行 + +### 类加载过程 + +- ClassLoader 的使命就是提前加载「.class」类文件到内存中,在加载类时,使用的是双亲委派模型。类加载是一个将「.class」字节码文件实例化成 Class 对象并进行相关初始化的过程 + + 先让父层次级别的类加载器视图加载该 Class,当父类加载器无法加载该类才会尝试从自己的类路径加载该类。所以每一层的类加载器都是如此,将请求委托给自己的父加载器,所有的类加载请求最终都应该传送到顶层的 Bootstrap ClassLoader 中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。 + +- Java 的类加载器 + + - 加载(Load) + + - 读取类文件产生二进制流,并转化为特定的数据结构,初步校验 cafe babe 魔法数、常量池、文件长度、是否有父类等,然后创建对应类的 java.lang.Class 实例 + + - 链接(Link) + + - 验证 + + - 更详细的校验,比如 final 是否合规、类型是否正确、静态变量是否合理 + + - 准备 + + - 为静态变量分配内存并设定默认值 + + - 解析 + + - 确保类与类之间的相互引用的正确性,完成内存结构布局 + + - 初始化(Init) + + - 执行类构造器方法,在虚拟机栈中执行完毕后通过返回值进行赋值 + +- class 关键字用来定义类,Class 是所有 class 的类 + + - Class 类下的 newInstance() 在 JDK9 中已经过时,使用 getDeclaredConstructor().newInstance() 的方式 + - new 与 newInstance 的区别 + + - new 是强类型校验,可以调用任何构造方法,new 的类可以没有被加载过。 + - newInstance 是弱类型,只能调用无参数构造方法,如果没有无参就会抛出「InstantiationException」异常,如果构造方法没有访问权限,就抛出「IllegalAccessException」异常 + +- 类加载器 + + - Bootstrap + + - 最根基的类加载器,在 JVM 启动时创建,负责装载最核心的 Java 类,比如 object、System、String 等 + + - JDK9 之后叫做平台类加载器,Platform ClassLoader +JDK9 之前叫做扩展类加载器,Extension ClassLoader + + - 用来加载一些扩展的系统类,比如 XML、加密、压缩相关的功能类等 + + - 应用类加载器,Application ClassLoader + + - 主要是加载用户自定义的 CLASSPATH 路径下的类。 + + - 自定义类加载器的情况 + + - 隔离加载类 + + - 某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境 + + - 修改类加载方式 + + - 根据实际情况在某个时间点进行按需进行动态加载 + + - 扩展加载源 + + - 比如从数据库、网络、设置是电视机机顶盒进行加载 + + - 防止源码泄露 + + - 先进行编译加密,自定义类加载器进行字节码的还原 + +### 内存布局 + +- JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行 +- 经典的 JVM 内存布局 + + - Heap(堆区) + + - 特点 + + - 存储着几乎所有的实例对象 + - 垃圾收集器自动回收 + - OOM 故障主要发源地,通常也是在内存中占用空间最大的 + - 线上生产环境一般将 Xms 和 Xmx 设置成一样大小,避免 GC 后调整堆大小时带来的额外压力 + - -Xms 和 -Xmx + + - -X 代表他是 JVM 的运行参数 + - ms 全称「memory start」,代表最小堆容量 + - mx 全称「memory max」,代表最大堆容量 + + - 分类 + + - 新生代(Young 区) + + - 对象之初在新生代,步入暮年进入老年代 + - 1 个 Eden 区 + 2 个 Survivor 区 + + - Eden + + - 绝大部分对象在 Eden 区生成 + - Eden 区装填满的时候会触发 Young GC,没有被引用的对象直接回收 + + - Survivor,区分为 S0 和 S1 两块内存空间 + + - 依然存活的对象会被移送到 Survivor 区 + - 每次 Young GC 的时候,将存活的对象复制到未使用的那块空间,将当前正在使用的空间完全清除,交换两块空间的使用状态 + - 如果 Young GC 要移送的对象超过 Survivor 区容量的上限,就直接移交给老年代 + + - 如果老年代也放不下,则直接出发 Full GC + - 如果还是放不下,就抛出 OOM + - 给 JVM 设置 -XX:+HeapDumpOnOutOfMemoryError,可以使 JVM 遇到 OOM 异常时能输出堆内信息 + + - 每个对象都有一个计数器,每次 Young GC 都会加 1,-XX:MaxTenuringThreshold 参数可以配置计数器的阈值使其从新生代晋升至老年代。默认值 15,如果设置为,直接移到老年代。 + + - 老年代(Old 区) + + - 老年代也接纳新生代无法容纳的超大对象 + + - Metaspace(元空间,元数据区) + + - 组成 + + - 常量池 + - 方法元信息 + - klass 类元信息 + + - JDK8 之后,移除了 Perm 区,即永久代,使用元空间替换永久代,在本地内存中分配 + + - JVM Stack(虚拟机栈) + + - 特点 + + - JVM 中的虚拟机栈是描述 Java 方法执行的内存区域,他是线程私有的 + - 栈中的元素组成用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程 + - 栈帧是方法运行的基本结构,方法的运行过程中只有位于栈顶的帧才是有效的,叫做「当前栈帧」,方法叫做「当前方法」 + - StackOverflowError 表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中。 + + - 组成(栈由多个栈帧组成,栈帧里面包括这些元素) + + - 局部变量 + + - 存放方法参数和局部变量的区域 + - 局部变量不像类属性变量存在准备阶段,他必须显示初始化。 + - 如果是非静态方法,还会存储一个方法所属对象的十里引用,随后存储参数和局部变量 + + - 操作栈 + + - 一个初始状态为空的桶式结构栈 + - 所谓的「JVM 的执行引擎是基于栈的执行引擎」,这个栈指的就是操作栈 + - 在方法的执行过程中,各种指令往栈中写入和提取信息。 + - 「i++」与「++i」的本质区别 + + - i++ 属于在栈中先进行取值操作,压入栈顶,然后在进行运算,所以是先原值运算在进行 +1 操作 + - ++i 属于在栈中先进行 +1 操作,然后计算好的值在压入栈顶,所以是先原值 +1 操作再进行运算操作 + + - 动态连接 + + - 每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程的动态连接 + + - 方法返回地址 + + - 方法执行的两种退出情况 + + - 正常退出 + - 异常退出 + + - 返回至方法当前被调用的位置 + + - 方法的退出相当于弹出当前栈帧 + + - 返回值压入上层调用栈帧 + - 异常信息抛给能够处理的栈帧 + - PC 计数器指向方法调用后的下一条指令 + + - Native Method Stacks(本地方法栈) + + - 线程对象私有 + - 虚拟机栈主内,本地方法栈主外,为 Native 方法服务,通过 JNI 来访问虚拟机运行时的数据区,甚至可以调用 CPU 的寄存器 + - 内存不足的情况下本地方法栈会抛出 native heap OutOfMemory 异常 + - JNI(Java Native Interface) + + - System.currentTimeMillis() + + - Program Counter Register(程序计数寄存器) + + - CPU 只有把数据装载到寄存器才能够运行 + - 每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器 + - 程序计数器在各个线程之间互不影响,该区域不会发生内存溢出异常 + + - CodeCache JIT 编译产物 + - 线程对于内存区域模型的共享 + + - 线程私有 + + - 虚拟机栈 + - 本地方法栈 + - 程序计数器 + + - 线程共享 + + - 堆 + + - OutOfMemoryError: Java heap space + + - 元空间 + + - OutOfMemoryError: Metaspace + + - 私有与共享之间的数据交互通过「ThreadLocal」 + +### 对象实例化 + +### 垃圾回收 + +- 目的 + + - 清除不再使用的对象,自动释放内存,从而更好的分配内存的使用 + +- 判断对象是否可回收 + + - GC Roots,可达性分析算法 + - 可以作为 GC Roots 的对象 + + - 类静态属性中引用的对象 + - 常量引用的对象 + - 虚拟机栈中引用的对象 + - 本地方法栈中引用的对象 + +- 垃圾回收算法 + + - 标记-清除算法 + + - 实现:从每个 GC Roots 出发,标记有引用关系的对象,最后没有被标记的对象清除 + - 缺点:带来大量的空间碎片,容易触发 Full GC + + - 标记-整理算法 + + - 实现:主要解决标记-清除算法空间碎片的问题,从 GC Roots 出发标记有引用关系的对象,然后将存活的对象整理到内存空间的一端,形成连续的已使用空间,最后将其余未使用内存空间清除 + + - Mark-Copy 算法 + + - 实现:将空间分为两块进行标记和整理,每次只用一块,每次回收对象时候将活的对象复制到另一块未使用的内存空间,然后清除原空间的对象 + - 主流的 Young GC 算法进行新生代的垃圾回收 + +- 垃圾回收器 + + - Serial + + - 主要应用于 Young GC 的垃圾回收器,串行单线程 + - Stop The World + + - 某个阶段会暂停整个应用程序的执行 + + - Young GC 时使用 「Mark-Copy」算法,Full GC 时使用「标记-整理」算法 + + - CMS + + - 回收暂停时间比较短,比较常用的垃圾回收器 + - 步骤 + + - 初始标记 + + - 会引发「Stop The World」 + + - 并发标记 + + - 可以和应用程序并发执行 + + - 重新标记 + + - 会引发「Stop The World」 + + - 并发清除 + + - 可以和应用程序并发执行 + + - 采用的是「标记-清除」算法 + - CMS 带来的空间碎片问题的优化 + + - -XX:+UseCMSCompactAtFullCollection + + - 强制 JVM 在 Full GC 完成后对老年代进行压缩,执行一次空间碎片整理(空间碎片整理也会引发「Stop The World」) + + - -XX:+CMSFullGCBeforeCompaction=n + + - 执行 n 次 Full GC 后,JVM 再在老年代执行空间碎片整理 + + - G1 + + - 启用方式:-XX:+UseG1GC + - 具备压缩功能,避免碎片问题,暂停时间更加可控,不需要一个连续的内存空间管理对象 + - 实现:将 Java 堆空间分割成若干相同大小的区域,称为「region」 + + - Eden + - Survivor + - Old + - Humongous + + - 特殊的 Old 类型,专门放置大型对象 + + - 采用的是「Mark-Copy」算法 + +- 子主题 1 + +## 异常与日志 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +## 代码规约 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +## 单元测试 + +### 子主题 1 + +### 子主题 1 + +### 子主题 1 + +## 并发与多线程 + +### 线程安全 + +- 并发 + + - 指在某个时间段内,多任务交替处理的能力。 + - 并发的特点 + + - 并发程序之间有相互制约的关系。 + - 并发程序的执行过程是断断续续的。 + - 当并发数设置合理并且 CPU 拥有足够的处理能力时,并发会提高程序的运行效率。 + +- 并行 + + - 指同时处理多任务的能力。 + +- 线程是 CPU 调度和分派的基本单位 + + 线程可以拥有自己的操作栈、程序计数器、局部变量表等资源,它与同一进程内的其他线程共享该进程的所有资源。 + +- 多线程的优缺点 + + - 提高任务的平均执行速度 + - 程序可理解性变差,编程难度加大 + +- 线程的生命周期 + + - NEW(新建状态):线程被创建旦未启动的状态。 + + - 创建线程的三种方式(广度) + + - 继承自 Thread 类(不符合里式代换原则) + + 关于设计模式中的里式代换原则可以参考老四的《浅析设计模式第五章之依赖倒转原则》 + + - 实现 Runnable 接口(推荐) + - 实现 Callable 接口 + + - V call() throws Exception + - 可以通过 call() 获得返回值。前两者不能获取返回值,需要借助共享变量获取。 + - call() 可以抛出异常 + + - 创建线程的七种方式(细分) + + - 继承 Thread 类 + + - 创建多个线程 + - 指定线程名称 + + - 实现 Runnable 接口 + + - 创建线程任务 + - 创建可运行类 + - lambda 表达式方式创建线程任务 + + - 使用内部类的方式 + - 定时器 + + - 指定时间点执行 + - 间隔时间重复执行 + + - 实现 Callable 接口 + - 基于线程池的方式 + - 使用 Spring 来实现多线程 + + - @EnableAsync 和 @Async 注解 + + - RUNNABLE(就绪状态) + + - 调用 start() 之后运行之前的状态 + + - RUNNING(运行状态) + + - run() 正在执行时线程的状态 + + - BLOCKED(阻塞状态) + + - 同步阻塞 + + - 锁被其他线程占用 + + - 主动阻塞 + + - 调用 sleep()、join() 等方法主动让出 CPU 执行权 + + - 等待阻塞 + + - 执行了 wait() + + - DEAD(终止状态) + + - run() 执行结束,或者因为异常退出后的状态,不可逆转。 + +- 线程安全的维度考量(要么只读,要么加锁) + + - 数据单线程内可见 + + - 单线程总是安全的 + - 线程局部变量,存储在独立虚拟机栈帧的局部变量表中,比如 ThreadLocal + + - 只读对象 + + - 只读对象总是安全的,String、Integer 等 + - 只读对象的条件 + + - 使用 final 关键字修饰,避免被继承 + - 使用 private final 关键字避免属性被中途修改 + - 没有任何更新方法 + - 返回值不能可变对象为引用 + + - 线程安全类 + + - 内部有非常明确的线程安全机制,比如 StringBuffer,采用 synchronized 关键字修饰相关方法 + + - 同步与锁机制 + +- 并发包类族(java.util.concurrent) + + - 线程同步类 + + - 逐步淘汰使用 Object 的 wait()、notify() 进行同步的方式 + - CountDownLatch、Semaphore、CyclicBarrier + + - 并发集合类 + + - 由锁分段向 CAS 进化,并发性能不断提升 + - ConcurrentHashMap、ConcurrentSkipListMap、CopyOnWriteArrayList、BlockingQueue + + - 线程管理类 + + - Thread、ThreadLocal + - 线程池 + + - 锁相关类 + + - 以 Lock 接口为核心 + - ReentrantLock + +### 什么是锁 + +- 锁的两种特性 + + - 互斥性 + - 不可见性 + +- 计算机的锁 + + - 悲观锁 + - 乐观锁 + - 偏向锁 + - 分段锁 + +- Java 中常用锁实现的方式 + + - 并发包中的锁类(java.concurrent.locks),利用 volatile 的可见性 + + - Lock + - ReentrantLock + + - 作为 AQS 的子类,定义 state = 0 时可以获取资源并且置为 1。 + - 如果已经获得资源,state 不断加 1,在释放资源时 state 减 1,直至为 0。 + + - AbstractQueuedSynchronizer,JUC 包实现同步的基础工具 + + - 定义 volatile int state 变量作为共享资源,如果线程获取资源失败,就进入 FIFO 队列中等待 + - 如果成功获取资源就执行临界区代码 + - 执行完释放资源时,会通知同步队列中的等待线程来获取资源后出队并执行 + - 抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性方法 + + - CountDownLatch + + - 初始时定义资源总量 state = count,countDown() 不断将 state 减 1,当 state = 0 时才能获得锁,释放后 state 就一直为 0。 + - 所有线程调用 await() 都不会等待,CountDownLatch 是一次性的,用完后在想用就只能重新创建一个 + + - CyclicBarrier + + - 与 CountDownLatch 不同的是 CyclicBarrier 可以循环使用 + - 基于 ReentrantLock 实现 + + - Semaphore + + - 也定义了资源总量 state = permits,当 state > 0 时就能获得锁,并且将 state 减 1,当 state = 0 时只能等待其他线程释放锁 + - 当释放锁时 state 就加 1,其他等待线程又能获得这个锁 + - 当 permits = 1 时,就是互斥锁,当 permits > 1 就是共享锁 + + - StampedLock + + - JDK8 新锁,改进了读写锁 ReentrantReadWriteLock + + - ReentrantReadWriteLock + + - 同步代码块(synchronized 关键字) + + - 使用方式 + + - 方法签名处加 synchronized 关键字 + - 使用 synchronized(对象或类) 进行同步 + - 锁的范围尽可能小,时间尽可能短 + - 能锁对象就不要锁类,能锁代码块,就不要锁方法 + + - 通过监视锁来实现 synchronized 同步 + - 监视锁 + + - 每个对象与生俱来的一个隐藏字段 + - JVM 根据 synchronized 使用环境获取对应对象的监视锁,根据监视锁的状态进行加解锁的判断 + + - synchronized 三种锁的实现 + + - 偏向锁 + + - 利用 CAS 在对象头上设置线程 ID,表示这个对象偏向于当前线程 + - 为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销 + + - 轻量级锁 + + - 如果出现锁的竞争情况,偏向锁会被撤销并升级为轻量级锁 + + - 重量级锁 + + - 如果线程竞争情况激烈,就会升级为重量级锁 + +### 线程同步 + +- 同步是什么 + + - 原子性 + + - 指不可分割的一系列操作指令,在执行完毕前不会被任何其他操作中断,要么全部执行,要么全部不执行 + - CAS (Compare and Swap)操作具有原子性 + + - 实现线程同步的方式 + + - 同步方法 + - 锁 + - 阻塞队列 + +- volatile + + - 指令优化/指令重排序 + + - 计算机并不会根据代码顺序按部就班地执行相关指令,它会进行指令优化,分析哪些读取数据动作可以合并进行,从而减少访问内存的次数,提高执行效率 + + - 可见性 + + - 指某线程修改共享变量的指令对其他线程来说都是可见的,它反映的是指令执行的实时透明度 + + - 每个线程都有独占的内存区域,如操作栈、本地变量等。线程本地内存保存了引用变量在堆内存中的副本,线程对变量的所有操作都在本地内存区域中进行,执行结束后再同步到堆内存中去。 + - 使用 volatile 修饰变量 + + - 任何对此变量的操作都会在内存中进行,不会产生副本,以保证 + - 局部阻止指令重排序的发生 + - 不过加锁是直接在线程得到锁时读入副本,释放时写回内存 + - volatile 解决的是多线程共享变量的可见性问题,但是不具备原子性, + - 如果是一写多读的并发场景,使用 volatile 修饰变量则非常合适 + - 不过使用 volatile 会使线程的执行速度变慢,慎重使用 + + - 双重检查锁定问题 + + - 对象引用在没有同步的情况下进行读操作,导致用户可能会获取未构造完成的对象 + +- 信号量同步 + + - CountDownLatch + + - 基于执行时间的同步类 + + - Semaphore + + - 只有在调用 Semaphore 对象的 acquire 方法成功后,才可以往下执行,完成后执行 release 方法释放持有的信号量,下一个线程就可以马上获取这个空闲信号量进入执行 + - Semaphore 的窗口信号量等于 1,就是典型的互斥锁 + + - CyclicBarrier + + - 基于同步到达某个点的信号量触发机制,是一个可以循环使用的平章事多线程协作方式 + - 通过 CyclicBarrier 的reset 方法来释放线程资源 + + - 避免使用对象的 wait()、notify() 方式来进行同步 + +### 线程池 + +- 线程池的好处 + + - 线程池的作用 + + - 利用线程池管理并复用线程、控制最大并发数等 + - 实现任务线程队列缓存策略和拒绝机制 + - 实现某些与时间相关的功能,如定时执行、周期执行等 + - 隔离线程环境 + + - 线程池对线程的创建 + + - ThreadPoolExecutor 构造 + + - corePoolSize:常驻核心线程数 + - maximumPoolSize:线程池最大同时执行数 + - keepAliveTime:线程的空闲时间 + - unit:空闲时间单位(秒) + - workQueue:缓存队列 + - threadFactory:线程工厂 + - handler:执行拒绝策略的对象 + + - 友好的拒绝策略 + + - 保存到数据库进行削峰填谷。在空闲时再提取出来执行 + - 转向某个提示页面 + - 打印日志 + + - 线程池相关类图 + + - Executors 核心方法 + + - newWorkStealingPool + + JDK8 引入 +  创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争 +  CPU 数量会被设置为默认的并行度 + + - newCachedThreadPool + + 高度可伸缩的线程池 + 工作线程处于空闲状态,则回收工作线程 + 如果任务数增加,再次创建出新线程处理任务 + + - newScheduledThreadPool + + 定时及周期性任务执行 + 相比 Timer,ScheduledExecutorService 更安全,功能更强大 + 特点是不回收线程,而 newCachedThreadPool 回收工作线程 + + - newSingleThreadExecutor + + - 创建一个单线程的线程池,相当于但线程串行执行所有任务,保证按任务的提交顺序依次执行 + + - newFixedThreadPool + + - 固定线程数,既是核心线程数也是最大线程数,不存在空闲线程,所以 keepAliveTime 等于 0 + +- 线程池源码 + + - 使用线程池的注意事项 + + - 合理设置各类参数,应根据实际业务场景来设置合理的工作线程数。 + - 线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。 + - 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯 + + - ThreadPoolExecutor 源码 + + - 四个公开的内部静态类 + + - AbortPolicy + + - 丢弃任务并抛出 RejectedExecutionException 异常 + + - DiscardPolicy + + - 丢弃任务,但是不抛出异常,不推荐 + + - DiscardOldestPolicy + + - 抛弃队列中等待最久的任务,然后把当前任务加入队列中 + + - CallerRunsPolicy + + - 调用任务的 run() 方法绕过线程池直接执行 + + - 线程池状态定义(从小到大) + + - RUNNING(线程池可以接受新任务) + - SHUTDOWN(不再接受新任务,但可以继续执行队列中的任务) + - STOP(全面拒绝,并中断正在处理的任务) + - TIDYING(所有任务已经被终止) + - TERMINATED(已清理完现场) + +### ThreadLocal + +- 利弊 + + - 初衷是在线程并发时,解决变量共享问题 + - 但是由于过度设计,导致了弱引用、哈希碰撞等一系列问题,使用成本高,理解难度大。 + +- 引用类型(引用力度逐级递减) + + 首先你要知道对象存储在 JVM 的堆内存中,然后对象能否被回收的的基本条件是引用的可达性,也就是从 FC Roots 开始遍历,判断引用是否可达。 + + - 强引用(相当于有房产证) + + - Object object = new Object(); + - 只要对象有强引用指向,并且 GC Roots 可达,即使濒临内存耗尽,也不会回收对象 + + - 软引用(相当于租房子住) + + - 主要用在非必需对象的场景,比如缓存服务器中间计算结果以及不需要实时保存的用户行为等。 + - 在即将 OOM 之前垃圾回收器会把这些软引用指向的对象加入回收范围 + + - 弱引用(相当于被房产商给骗了,住了一阶段发现房子不属于自己的) + + - 调用 WeakReference.get() 可能会返回 null,注意 NPE + - 如果弱引用只存在弱引用这一条线路,在下一次 Young GC 时会被回收 + - 也用来描述非必需对象,主要用于指向某个易消失的对象,典型应用在 WeakHaahMap 中。 + + - 虚引用(相当于买房子直接被骗,钱花命没鸡飞蛋打) + + - 主要用来能在这个对象被回收时收到一个系统通知 + - 虚引用必须与引用队列联合使用。如果发现存在虚引用,就会在对象回收之前,把这个虚引用加入到相关引用队列当中 + +- ThreadLocal 价值 + + - 将对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的,这个对象就是 Threadocal + - ThreadLocal 通常是由 private static 修饰的 + - ThreadLocal 无法解决共享对象的更新问题,所以使用某个引用来操作共享对象时,依然需要进行线程同步 + - ThreadLocal 有个内部静态类 ThreadLocalMap,ThreadLocalMap 也有一个内部静态类 Entry + - ThreadLocalMap + + - 三个重要的方法 + + - get() + + - 始终没有 get 操作的 ThreadLocal 对象是没有意义的 + + - set() + + - 如果没有 set 操作的 ThreadLocal,容易引起脏数据问题 + + - remove() + + - 如果没有 remove 操作,容易引起内存泄漏 + + - Thread 中的 ThreadLocalMap 属性赋值是在 ThreadLocal 类中的 createMap() 中进行的 + + - Entry + + - 继承自 WeakReference,没有方法,只有一个 value 成员变量 + - key 是 ThreadLocal 对象 + - ThreadLocal 的弱引用关系 + + - 1 个 Thread 有且仅有 1 个 ThreadLocalMap 对象。 + - 1 个 Entry 对象的 key 弱引用指向 1 个 ThreadLocal 对象。 + - 1 个 ThreadLocalMap 对象存储多个 Entry 对象。 + - 1 个 ThreadLocal 对象可以被多个线程所共享。 + - ThreadLocal 对象不持有 Value,Value 由线程的 Entry 对象持有。 + +- ThreadLocal 副作用 + + - 脏数据 + - 内存泄漏 + - 在每次用完 ThreadLocal 时,必须要及时调用 remove() 方法进行清理,脏数据的问题是要求你使用 set() 操作 + +## 数据结构与集合 + +### 数据结构 + +### 集合框架图 + +- List 集合 + + - 线性数据结构的主要实现,最常用的就是 ArrayList 和 LinkedList + - ArrayList 是容量可以改变的非线程安全集合,底层使用数组支撑,扩容是进行数据复制,查询块,插入删除慢。 + - LinkedList 本质是双向链表,同时因为实现 Deque,所以具有队列和栈的性质,可以将零散的内存单元通过引用的方式关联形成链路按照顺序查找。插入删除快而查询慢,内存利用率高。 + +- Queue 集合 + + - 一种先进先出的数据结构,经常用作数据缓冲区 + - 最主要的是阻塞队列 BlockingDeque 接口的设计 + + - SynchronousQueue + - LinkedBlockingDeque + - ArrayBlockingQueue + - PriorityBlockingQueue + - DelayQueue + +- Map 集合 + + - 最早用于存储键值对的 Hashtable 因为性能瓶颈已经被淘汰 + - HashMap 是线程不安全的 + - 并发环境下推荐使用 ConcurrentHashMap 线程安全的类 + - TreeMap 是 key 有序的键值对集合 + +- Set 集合 + + - 不允许常出现重复元素的集合类型,最常用的有 HashSet、TreeSet、LinkedHashSet + - HashSet 的底层使用的是 HashMap 来实现的,TreeSet 底层使用 TreeMap 来实现。 + - LinkedHashSet 直接继承 HashSet,内部使用链表维护元素的插入顺序 + +### 集合初始化 + +- ArrayList + + - 默认容量 10 + - 50% 扩容 + +- HashMap + + - 默认容量 16 + - 填充比例 0.75 + +### 数组与集合 + +### 集合与泛型 + +### 元素的比较 + +- Comparable 和 Comparator +- hashCode 和 equals + + - 两个方法协同工作用来判断两个对象是否相等 + - 对象相等判断的底层实现 + + - 首先调用 Object.hashCode 生成哈希值 + - 如果哈希 hsahCode 不同,则对象肯定不同 + - 总是会存在哈希冲突的情况,此种情况需要使用 equals 方法做进一步判断 + + - 如果两个对象的 equals 的结果是相等的,则两个对象的 hsahCode 的返回结果也必须是相同的。 + - 任何时候覆写 equals,都必须同时覆写 hashCode + - 尽量避免通过实例对象引用来调用 equals 方法,否则容易抛出 NPE 空指针异常问题,推荐使用 Objects.equals + +### fail-fast 机制 + +### Map 类集合 + +*glorze.com - 高老四博客* \ No newline at end of file diff --git "a/Java\350\277\233\351\230\266/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.xmind" "b/Java\350\277\233\351\230\266/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.xmind" new file mode 100644 index 0000000..bffd15d Binary files /dev/null and "b/Java\350\277\233\351\230\266/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.xmind" differ diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/locks \345\214\205\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer.java" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/locks \345\214\205\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer.java" deleted file mode 100644 index 402173b..0000000 --- "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/locks \345\214\205\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer.java" +++ /dev/null @@ -1,1101 +0,0 @@ -package java.util.concurrent.locks; -import java.util.concurrent.TimeUnit; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import sun.misc.Unsafe; - -public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { - - private static final long serialVersionUID = 7373984972572414691L; - - protected AbstractQueuedSynchronizer() { } - - static final class Node { - static final Node SHARED = new Node(); - static final Node EXCLUSIVE = null; - - // 节点的等待状态 - // 值为 0,表示当前节点在 sync 队列中,等待着获取锁 - volatile int waitStatus; - // 节点操作因为超时或者对应的线程被 interrupt。 - // 节点不应该留在此状态,一旦达到此状态将从 CHL 队列中踢出。 - // 表示当前的线程被取消 - static final int CANCELLED = 1; - // 节点的后继节点是(或者将要成为)BLOCKED 状态(例如通过 LockSupport.park() 操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的后继节点。 - // 表示当前节点的后继节点包含的线程需要运行 - static final int SIGNAL = -1; - // 节点对应的线程因为不满足一个条件(Condition)而被阻塞。 - // 表示当前节点在等待condition,也就是在condition队列中 - static final int CONDITION = -2; - // 表示当前场景下后续的 acquireShared 能够得以执行 - static final int PROPAGATE = -3; - - // 前驱节点,当前节点被取消,那就需要前驱节点和后继节点来完成连接 - volatile Node prev; - - // 后继节点 - volatile Node next; - - // 存储 condition 队列中的后继节点 - Node nextWaiter; - - // 入队列时的当前线程 - volatile Thread thread; - - final boolean isShared() { - return nextWaiter == SHARED; - } - final Node predecessor() throws NullPointerException { - Node p = prev; - if (p == null) - throw new NullPointerException(); - else - return p; - } - - Node() { - } - - Node(Thread thread, Node mode) { - this.nextWaiter = mode; - this.thread = thread; - } - - Node(Thread thread, int waitStatus) { - this.waitStatus = waitStatus; - this.thread = thread; - } - } - - // 头结点 - private transient volatile Node head; - - // 尾节点 - private transient volatile Node tail; - - // 有多少个线程取得了锁,对于互斥锁来说 state <= 1 - private volatile int state; - - protected final int getState() { - return state; - } - - protected final void setState(int newState) { - state = newState; - } - - protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); - } - - static final long spinForTimeoutThreshold = 1000L; - - /** - * 确保进入的 Node 都会有机会顺序的添加到 sync 队列中 - * 1.如果尾节点为空,那么原子化的分配一个头节点,并将尾节点指向头节点,这一步是初始化 - * 2.然后是重复在 addWaiter 中做的工作,但是在一个 while(true) 的循环中,直到当前节点入队为止 - */ - private Node enq(final Node node) { - // CAS"自旋",直到成功加入队尾 - for (;;) { - Node t = tail; - if (t == null) { - // 队列为空,创建一个空的标志结点作为head结点,并将tail也指向它。 - // 最开始 head 和 tail 都是空的,需要通过 CAS 做初始化,如果 CAS 失败,则循环重新检查 tail。 - if (compareAndSetHead(new Node())) - tail = head; - } else { - //正常流程,放入队尾 - // head 和 tail 不是空的,说明已经完成初始化,和 addWaiter 方法的上半段一样,CAS 修改。 - node.prev = t; - if (compareAndSetTail(t, node)) { - t.next = node; - return t; - } - } - } - } - - /** - * 将当前线程加入到等待队列的队尾,并返回当前线程所在的结点。 - * 使用 LockSupport 将当前线程 unpark - * 1.使用当前线程构造Node - * 2.先行尝试在队尾添加 - */ - private Node addWaiter(Node mode) { - // 以给定模式构造结点。mode 有两种:EXCLUSIVE(独占)和SHARED(共享) - Node node = new Node(Thread.currentThread(), mode); - // 快速尝试在尾部添加 - Node pred = tail; - if (pred != null) { - node.prev = pred; - // CAS 把 tail 设置成当前节点,如果成功的话就说明插入成功,直接返回 node,失败说明有其他线程也在尝试插入而且其他线程成功,如果是这样就继续执行 enq 方法。 - if (compareAndSetTail(pred, node)) { - pred.next = node; - return node; - } - } - // 上一步失败则通过 enq 入队。 - enq(node); - return node; - } - - private void setHead(Node node) { - head = node; - node.thread = null; - node.prev = null; - } - - /** - * 通过 LockSupport 的 unpark 方法将休眠中的线程唤醒,让其继续 acquire 状态 - * node 一般为当前线程所在的结点 - */ - private void unparkSuccessor(Node node) { - // 将状态设置为同步状态 - int ws = node.waitStatus; - // 置零当前线程所在的结点状态,允许失败。 - if (ws < 0) - // 获取当前节点的后继节点,如果满足状态,那么进行唤醒操作 - // 如果状态小于 0,把状态改成 0,0 是空的状态,因为 node 这个节点的线程释放了锁后续不需要做任何操作,不需要这个标志位,即便 CAS 修改失败了也没关系,其实这里如果只是对于锁来说根本不需要 CAS,因为这个方法只会被释放锁的线程访问,只不过 unparkSuccessor 这个方法是 AQS 里的方法就必须考虑到多个线程同时访问的情况(可能共享锁或者信号量这种) - compareAndSetWaitStatus(node, ws, 0); - // 如果没有满足状态,从尾部开始找寻符合要求的节点并将其唤醒 - // 找到下一个需要唤醒的结点 s - Node s = node.next; - //如果为空或已取消 - // 如果下一个节点为空或者下一个节点的状态 >0(目前大于 0 就是取消状态),则从 tail 节点开始遍历找到离当前节点最近的且 waitStatus <= 0(即非取消状态)的节点并唤醒 - if (s == null || s.waitStatus > 0) { - s = null; - for (Node t = tail; t != null && t != node; t = t.prev) - // <= 0 的结点,都是还有效的结点。 - if (t.waitStatus <= 0) - s = t; - } - if (s != null) - // 唤醒 - LockSupport.unpark(s.thread); - } - - private void doReleaseShared() { - for (;;) { - Node h = head; - if (h != null && h != tail) { - int ws = h.waitStatus; - if (ws == Node.SIGNAL) { - if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) - continue; // loop to recheck cases - unparkSuccessor(h); - } - else if (ws == 0 && - !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) - continue; // loop on failed CAS - } - if (h == head) // loop if head changed - break; - } - } - - /** - * 主要用于检查状态,看看自己是否真的可以去休息了 - */ - private void setHeadAndPropagate(Node node, int propagate) { - Node h = head; - // head 指向自己 - setHead(node); - // 如果还有剩余量,继续唤醒下一个邻居线程 - if (propagate > 0 || h == null || h.waitStatus < 0 || - (h = head) == null || h.waitStatus < 0) { - Node s = node.next; - if (s == null || s.isShared()) - doReleaseShared(); - } - } - - private void cancelAcquire(Node node) { - if (node == null) - return; - - node.thread = null; - - Node pred = node.prev; - while (pred.waitStatus > 0) - node.prev = pred = pred.prev; - Node predNext = pred.next; - - node.waitStatus = Node.CANCELLED; - - if (node == tail && compareAndSetTail(node, pred)) { - compareAndSetNext(pred, predNext, null); - } else { - int ws; - if (pred != head && - ((ws = pred.waitStatus) == Node.SIGNAL || - (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && - pred.thread != null) { - Node next = node.next; - if (next != null && next.waitStatus <= 0) - compareAndSetNext(pred, predNext, next); - } else { - unparkSuccessor(node); - } - - node.next = node; - } - } - - /** - * 主要用于检查状态,看看自己是否真的可以去休息了 - */ - private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { - // 拿到前驱的状态 - // 获取 pred 前置节点的等待状态 - int ws = pred.waitStatus; - // 前置节点状态是 signal,那当前节点可以安全阻塞,因为前置节点承诺执行完之后会通知唤醒当前节点 - if (ws == Node.SIGNAL) - // 如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了 - return true; - if (ws > 0) { - // 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。 - // 那些放弃的结点会被 GC 回收 - // 前置节点如果已经被取消了,则一直往前遍历直到前置节点不是取消状态,与此同时会修改链表关系 - do { - node.prev = pred = pred.prev; - } while (pred.waitStatus > 0); - pred.next = node; - } else { - // 如果前驱正常,那就把前驱的状态设置成 SIGNAL,告诉它拿完号后通知自己一下。 - // SIGNAL 表示当前节点的后继节点包含的线程需要运行 - // 前置节点是 0 或者 propagate 状态,这里通过 CAS 把前置节点状态改成 signal - compareAndSetWaitStatus(pred, ws, Node.SIGNAL); - } - // 这里不返回 true 让当前节点阻塞,而是返回 false,目的是让调用者再 check 一下当前线程是否能成功获取锁,失败的话再阻塞 - return false; - } - - static void selfInterrupt() { - Thread.currentThread().interrupt(); - } - - - /** - * 主要用于检查状态,看看自己是否真的可以去休息了 - * 让线程去休息,真正进入等待状态。 - */ - private final boolean parkAndCheckInterrupt() { - // 阻塞当前线程,当前 sync 对象 - LockSupport.park(this); - // 阻塞返回后,返回当前线程是否被中断 - return Thread.interrupted(); - } - - /** - * 在等待队列中排队拿号(中间没其它事干可以休息),直到拿到号后再返回 - * 1.获取当前节点的前驱节点 - * 2.当前驱节点是头结点并且能够获取状态,代表该当前节点占有锁 - * 3.否则进入等待状态 - */ - final boolean acquireQueued(final Node node, int arg) { - // 标记是否成功拿到资源 - boolean failed = true; - try { - // 标记等待过程中是否被中断过 - boolean interrupted = false; - // 自旋 - for (;;) { - // 拿到前驱 - final Node p = node.predecessor(); - //如果前驱是 head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被 interrupt 了)。 - // 如果前置节点是 head,说明当前节点是队列第一个等待的节点,这时去尝试获取锁,如果成功了则获取锁成功。 - if (p == head && tryAcquire(arg)) { - // 拿到资源后,将 head 指向该结点。所以 head 所指的标杆结点,就是当前获取到资源的那个结点或 null。 - // 队首且获取锁成功,把当前节点设置成 head,下一个节点成了等待队列的队首 - setHead(node); - // setHead 中 node.prev 已置为 null,此处再将 head.next 置为 null,就是为了方便 GC 回收以前的 head 结点。也就意味着之前拿完资源的结点出队了! - p.next = null; // help GC - failed = false; - // 返回等待过程中是否被中断过 - return interrupted; - } - // 如果自己可以休息了,就进入 waiting 状态,直到被 unpark() - // 如果获取锁失败是否需要阻塞,如果需要的话就执行 parkAndCheckInterrupt 方法,如果不需要就继续循环。 - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - // 如果等待过程中被中断过,哪怕只有那么一次,就将 interrupted 标记为 true - interrupted = true; - } - } finally { - if (failed) - cancelAcquire(node); - } - } - - private void doAcquireInterruptibly(int arg) - throws InterruptedException { - final Node node = addWaiter(Node.EXCLUSIVE); - boolean failed = true; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; // help GC - failed = false; - return; - } - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - throw new InterruptedException(); - } - } finally { - if (failed) - cancelAcquire(node); - } - } - - /** - * 提供了具备有超时功能的获取状态的调用,如果在指定的 nanosTimeout 内没有获取到状态,那么返回false,反之返回true。 - * 可以将该方法看做 acquireInterruptibly 的升级版,也就是在判断是否被中断的基础上增加了超时控制。 - * 1.加入sync队列 - * 2.条件满足直接返回 - * 3.获取状态失败休眠一段时间 - * 4.计算再次休眠的时间 - * 5.休眠时间的判定 - */ - private boolean doAcquireNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (nanosTimeout <= 0L) - return false; - final long deadline = System.nanoTime() + nanosTimeout; - final Node node = addWaiter(Node.EXCLUSIVE); - boolean failed = true; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head && tryAcquire(arg)) { - setHead(node); - p.next = null; - failed = false; - return true; - } - nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) - return false; - if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > spinForTimeoutThreshold) - LockSupport.parkNanos(this, nanosTimeout); - if (Thread.interrupted()) - throw new InterruptedException(); - } - } finally { - if (failed) - cancelAcquire(node); - } - } - - /** - * 以共享模式获取状态,共享模式和独占模式有所区别。 - * 以文件的查看为例,如果一个程序在对其进行读取操作,那么这一时刻,对这个文件的写操作就被阻塞,相反,这一时刻另一个程序对其进行同样的读操作是可以进行的。 - * 如果一个程序在对其进行写操作,那么所有的读与写操作在这一时刻就被阻塞,直到这个程序完成写操作。 - * 1.尝试获取共享状态 - * 2.获取失败进入sync队列 - * 3.循环内判断退出队列条件 - * 4.获取共享状态成功 - * 5.获取共享状态失败 - */ - private void doAcquireShared(int arg) { - // 加入队列尾部 - final Node node = addWaiter(Node.SHARED); - // 是否成功标志 - boolean failed = true; - try { - // 等待过程中是否被中断过的标志 - boolean interrupted = false; - for (;;) { - // 前驱 - final Node p = node.predecessor(); - // 如果到 head 的下一个,因为 head 是拿到资源的线程,此时 node 被唤醒,很可能是 head 用完资源来唤醒自己的 - if (p == head) { - // 尝试获取资源 - int r = tryAcquireShared(arg); - // 成功 - if (r >= 0) { - // 将 head 指向自己,还有剩余资源可以再唤醒之后的线程 - setHeadAndPropagate(node, r); - p.next = null; // help GC - // 如果等待过程中被打断过,此时将中断补上。 - if (interrupted) - selfInterrupt(); - failed = false; - return; - } - } - // 判断状态,寻找安全点,进入 waiting 状态,等着被 unpark() 或 interrupt() - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - interrupted = true; - } - } finally { - if (failed) - cancelAcquire(node); - } - } - - private void doAcquireSharedInterruptibly(int arg) - throws InterruptedException { - final Node node = addWaiter(Node.SHARED); - boolean failed = true; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - int r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - failed = false; - return; - } - } - if (shouldParkAfterFailedAcquire(p, node) && - parkAndCheckInterrupt()) - throw new InterruptedException(); - } - } finally { - if (failed) - cancelAcquire(node); - } - } - - private boolean doAcquireSharedNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (nanosTimeout <= 0L) - return false; - final long deadline = System.nanoTime() + nanosTimeout; - final Node node = addWaiter(Node.SHARED); - boolean failed = true; - try { - for (;;) { - final Node p = node.predecessor(); - if (p == head) { - int r = tryAcquireShared(arg); - if (r >= 0) { - setHeadAndPropagate(node, r); - p.next = null; // help GC - failed = false; - return true; - } - } - nanosTimeout = deadline - System.nanoTime(); - if (nanosTimeout <= 0L) - return false; - if (shouldParkAfterFailedAcquire(p, node) && - nanosTimeout > spinForTimeoutThreshold) - LockSupport.parkNanos(this, nanosTimeout); - if (Thread.interrupted()) - throw new InterruptedException(); - } - } finally { - if (failed) - cancelAcquire(node); - } - } - - /** - * 获取排他锁 - * 1.尝试获取(调用 tryAcquire 更改状态,需要保证原子性) - * 2.如果获取不到,将当前线程构造成节点 Node 并加入 sync 队列 - * 3.再次尝试获取,如果没有获取到那么将当前线程从线程调度器上摘下,进入等待状态 - */ - public final void acquire(int arg) { - if (!tryAcquire(arg) && - acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) - selfInterrupt(); - } - - /** - * 释放排他锁 - * 1.尝试释放状态 - * 2.唤醒当前节点的后继节点所包含的线程 - */ - public final boolean release(int arg) { - if (tryRelease(arg)) { - // 找到头结点 - Node h = head; - if (h != null && h.waitStatus != 0) - // 唤醒等待队列里的下一个线程 - unparkSuccessor(h); - return true; - } - return false; - } - - /** - * 获取共享锁 - */ - public final void acquireShared(int arg) { - if (tryAcquireShared(arg) < 0) - doAcquireShared(arg); - } - - /** - * 释放共享锁 - */ - public final boolean releaseShared(int arg) { - if (tryReleaseShared(arg)) { - doReleaseShared(); - return true; - } - return false; - } - - /** - * 这几个判断声明都是一样的。 - * 都是父类中只有定义,在子类中实现。 - * 子类根据功能需要的不同,有选择的对需要的方法进行实现。 - * 父类中提供一个执行模板,但是具体步骤留给子类来定义,不同的子类有不同的实现 - */ - protected boolean tryAcquire(int arg) { - throw new UnsupportedOperationException(); - } - - // 尝试去释放指定量的资源 - protected boolean tryRelease(int arg) { - throw new UnsupportedOperationException(); - } - - protected int tryAcquireShared(int arg) { - throw new UnsupportedOperationException(); - } - - protected boolean tryReleaseShared(int arg) { - throw new UnsupportedOperationException(); - } - - /** - * 在排它模式下,状态是否被占用。 - */ - protected boolean isHeldExclusively() { - throw new UnsupportedOperationException(); - } - - /** - * 提供获取状态能力,当然在无法获取状态的情况下会进入 sync 队列进行排队。 - * 类似 acquire,但是和 acquire 不同的地方在于它能够在外界对当前线程进行中断的时候提前结束获取状态的操作。 - * 换句话说,就是在类似 synchronized 获取锁时,外界能够对当前线程进行中断,并且获取锁的这个操作能够响应中断并提前返回。 - * 1.检测当前线程是否被中断 - * 2.尝试获取状态 - * 3.构造节点并加入sync队列 - * 4.中断检测 - */ - public final void acquireInterruptibly(int arg) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - if (!tryAcquire(arg)) - doAcquireInterruptibly(arg); - } - - public final boolean tryAcquireNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - return tryAcquire(arg) || - doAcquireNanos(arg, nanosTimeout); - } - - public final void acquireSharedInterruptibly(int arg) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - if (tryAcquireShared(arg) < 0) - doAcquireSharedInterruptibly(arg); - } - - public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - return tryAcquireShared(arg) >= 0 || - doAcquireSharedNanos(arg, nanosTimeout); - } - - public final boolean hasQueuedThreads() { - return head != tail; - } - - public final boolean hasContended() { - return head != null; - } - - public final Thread getFirstQueuedThread() { - return (head == tail) ? null : fullGetFirstQueuedThread(); - } - - private Thread fullGetFirstQueuedThread() { - Node h, s; - Thread st; - if (((h = head) != null && (s = h.next) != null && - s.prev == head && (st = s.thread) != null) || - ((h = head) != null && (s = h.next) != null && - s.prev == head && (st = s.thread) != null)) - return st; - - Node t = tail; - Thread firstThread = null; - while (t != null && t != head) { - Thread tt = t.thread; - if (tt != null) - firstThread = tt; - t = t.prev; - } - return firstThread; - } - - public final boolean isQueued(Thread thread) { - if (thread == null) - throw new NullPointerException(); - for (Node p = tail; p != null; p = p.prev) - if (p.thread == thread) - return true; - return false; - } - - final boolean apparentlyFirstQueuedIsExclusive() { - Node h, s; - return (h = head) != null && - (s = h.next) != null && - !s.isShared() && - s.thread != null; - } - - public final boolean hasQueuedPredecessors() { - Node t = tail; - Node h = head; - Node s; - return h != t && - ((s = h.next) == null || s.thread != Thread.currentThread()); - } - - public final int getQueueLength() { - int n = 0; - for (Node p = tail; p != null; p = p.prev) { - if (p.thread != null) - ++n; - } - return n; - } - - public final Collection getQueuedThreads() { - ArrayList list = new ArrayList(); - for (Node p = tail; p != null; p = p.prev) { - Thread t = p.thread; - if (t != null) - list.add(t); - } - return list; - } - - public final Collection getExclusiveQueuedThreads() { - ArrayList list = new ArrayList(); - for (Node p = tail; p != null; p = p.prev) { - if (!p.isShared()) { - Thread t = p.thread; - if (t != null) - list.add(t); - } - } - return list; - } - - public final Collection getSharedQueuedThreads() { - ArrayList list = new ArrayList(); - for (Node p = tail; p != null; p = p.prev) { - if (p.isShared()) { - Thread t = p.thread; - if (t != null) - list.add(t); - } - } - return list; - } - - public String toString() { - int s = getState(); - String q = hasQueuedThreads() ? "non" : ""; - return super.toString() + - "[State = " + s + ", " + q + "empty queue]"; - } - - final boolean isOnSyncQueue(Node node) { - if (node.waitStatus == Node.CONDITION || node.prev == null) - return false; - if (node.next != null) - return true; - return findNodeFromTail(node); - } - - private boolean findNodeFromTail(Node node) { - Node t = tail; - for (;;) { - if (t == node) - return true; - if (t == null) - return false; - t = t.prev; - } - } - - final boolean transferForSignal(Node node) { - if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) - return false; - - Node p = enq(node); - int ws = p.waitStatus; - if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) - LockSupport.unpark(node.thread); - return true; - } - - final boolean transferAfterCancelledWait(Node node) { - if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { - enq(node); - return true; - } - while (!isOnSyncQueue(node)) - Thread.yield(); - return false; - } - - final int fullyRelease(Node node) { - boolean failed = true; - try { - int savedState = getState(); - if (release(savedState)) { - failed = false; - return savedState; - } else { - throw new IllegalMonitorStateException(); - } - } finally { - if (failed) - node.waitStatus = Node.CANCELLED; - } - } - - public final boolean owns(ConditionObject condition) { - return condition.isOwnedBy(this); - } - - public final boolean hasWaiters(ConditionObject condition) { - if (!owns(condition)) - throw new IllegalArgumentException("Not owner"); - return condition.hasWaiters(); - } - - public final int getWaitQueueLength(ConditionObject condition) { - if (!owns(condition)) - throw new IllegalArgumentException("Not owner"); - return condition.getWaitQueueLength(); - } - - public final Collection getWaitingThreads(ConditionObject condition) { - if (!owns(condition)) - throw new IllegalArgumentException("Not owner"); - return condition.getWaitingThreads(); - } - - public class ConditionObject implements Condition, java.io.Serializable { - private static final long serialVersionUID = 1173984872572414699L; - private transient Node firstWaiter; - private transient Node lastWaiter; - - public ConditionObject() { } - - private Node addConditionWaiter() { - Node t = lastWaiter; - if (t != null && t.waitStatus != Node.CONDITION) { - unlinkCancelledWaiters(); - t = lastWaiter; - } - Node node = new Node(Thread.currentThread(), Node.CONDITION); - if (t == null) - firstWaiter = node; - else - t.nextWaiter = node; - lastWaiter = node; - return node; - } - - private void doSignal(Node first) { - do { - if ( (firstWaiter = first.nextWaiter) == null) - lastWaiter = null; - first.nextWaiter = null; - } while (!transferForSignal(first) && - (first = firstWaiter) != null); - } - - private void doSignalAll(Node first) { - lastWaiter = firstWaiter = null; - do { - Node next = first.nextWaiter; - first.nextWaiter = null; - transferForSignal(first); - first = next; - } while (first != null); - } - - private void unlinkCancelledWaiters() { - Node t = firstWaiter; - Node trail = null; - while (t != null) { - Node next = t.nextWaiter; - if (t.waitStatus != Node.CONDITION) { - t.nextWaiter = null; - if (trail == null) - firstWaiter = next; - else - trail.nextWaiter = next; - if (next == null) - lastWaiter = trail; - } - else - trail = t; - t = next; - } - } - - public final void signal() { - if (!isHeldExclusively()) - throw new IllegalMonitorStateException(); - Node first = firstWaiter; - if (first != null) - doSignal(first); - } - - public final void signalAll() { - if (!isHeldExclusively()) - throw new IllegalMonitorStateException(); - Node first = firstWaiter; - if (first != null) - doSignalAll(first); - } - - public final void awaitUninterruptibly() { - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - boolean interrupted = false; - while (!isOnSyncQueue(node)) { - LockSupport.park(this); - if (Thread.interrupted()) - interrupted = true; - } - if (acquireQueued(node, savedState) || interrupted) - selfInterrupt(); - } - - private static final int REINTERRUPT = 1; - private static final int THROW_IE = -1; - - private int checkInterruptWhileWaiting(Node node) { - return Thread.interrupted() ? - (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : - 0; - } - - private void reportInterruptAfterWait(int interruptMode) - throws InterruptedException { - if (interruptMode == THROW_IE) - throw new InterruptedException(); - else if (interruptMode == REINTERRUPT) - selfInterrupt(); - } - - public final void await() throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - LockSupport.park(this); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - } - - public final long awaitNanos(long nanosTimeout) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - final long deadline = System.nanoTime() + nanosTimeout; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (nanosTimeout <= 0L) { - transferAfterCancelledWait(node); - break; - } - if (nanosTimeout >= spinForTimeoutThreshold) - LockSupport.parkNanos(this, nanosTimeout); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - nanosTimeout = deadline - System.nanoTime(); - } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return deadline - System.nanoTime(); - } - - public final boolean awaitUntil(Date deadline) - throws InterruptedException { - long abstime = deadline.getTime(); - if (Thread.interrupted()) - throw new InterruptedException(); - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - boolean timedout = false; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (System.currentTimeMillis() > abstime) { - timedout = transferAfterCancelledWait(node); - break; - } - LockSupport.parkUntil(this, abstime); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return !timedout; - } - - public final boolean await(long time, TimeUnit unit) - throws InterruptedException { - long nanosTimeout = unit.toNanos(time); - if (Thread.interrupted()) - throw new InterruptedException(); - Node node = addConditionWaiter(); - int savedState = fullyRelease(node); - final long deadline = System.nanoTime() + nanosTimeout; - boolean timedout = false; - int interruptMode = 0; - while (!isOnSyncQueue(node)) { - if (nanosTimeout <= 0L) { - timedout = transferAfterCancelledWait(node); - break; - } - if (nanosTimeout >= spinForTimeoutThreshold) - LockSupport.parkNanos(this, nanosTimeout); - if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) - break; - nanosTimeout = deadline - System.nanoTime(); - } - if (acquireQueued(node, savedState) && interruptMode != THROW_IE) - interruptMode = REINTERRUPT; - if (node.nextWaiter != null) - unlinkCancelledWaiters(); - if (interruptMode != 0) - reportInterruptAfterWait(interruptMode); - return !timedout; - } - - final boolean isOwnedBy(AbstractQueuedSynchronizer sync) { - return sync == AbstractQueuedSynchronizer.this; - } - - protected final boolean hasWaiters() { - if (!isHeldExclusively()) - throw new IllegalMonitorStateException(); - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) - return true; - } - return false; - } - - protected final int getWaitQueueLength() { - if (!isHeldExclusively()) - throw new IllegalMonitorStateException(); - int n = 0; - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) - ++n; - } - return n; - } - - protected final Collection getWaitingThreads() { - if (!isHeldExclusively()) - throw new IllegalMonitorStateException(); - ArrayList list = new ArrayList(); - for (Node w = firstWaiter; w != null; w = w.nextWaiter) { - if (w.waitStatus == Node.CONDITION) { - Thread t = w.thread; - if (t != null) - list.add(t); - } - } - return list; - } - } - - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long stateOffset; - private static final long headOffset; - private static final long tailOffset; - private static final long waitStatusOffset; - private static final long nextOffset; - - static { - try { - stateOffset = unsafe.objectFieldOffset - (AbstractQueuedSynchronizer.class.getDeclaredField("state")); - headOffset = unsafe.objectFieldOffset - (AbstractQueuedSynchronizer.class.getDeclaredField("head")); - tailOffset = unsafe.objectFieldOffset - (AbstractQueuedSynchronizer.class.getDeclaredField("tail")); - waitStatusOffset = unsafe.objectFieldOffset - (Node.class.getDeclaredField("waitStatus")); - nextOffset = unsafe.objectFieldOffset - (Node.class.getDeclaredField("next")); - - } catch (Exception ex) { throw new Error(ex); } - } - - private final boolean compareAndSetHead(Node update) { - return unsafe.compareAndSwapObject(this, headOffset, null, update); - } - - private final boolean compareAndSetTail(Node expect, Node update) { - return unsafe.compareAndSwapObject(this, tailOffset, expect, update); - } - - private static final boolean compareAndSetWaitStatus(Node node, - int expect, - int update) { - return unsafe.compareAndSwapInt(node, waitStatusOffset, - expect, update); - } - - private static final boolean compareAndSetNext(Node node, - Node expect, - Node update) { - return unsafe.compareAndSwapObject(node, nextOffset, expect, update); - } -} diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/locks \345\214\205\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock.java" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/locks \345\214\205\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock.java" deleted file mode 100644 index 0bcdeab..0000000 --- "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/locks \345\214\205\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock.java" +++ /dev/null @@ -1,236 +0,0 @@ -package java.util.concurrent.locks; -import java.util.concurrent.TimeUnit; -import java.util.Collection; - -public class ReentrantLock implements Lock, java.io.Serializable { - private static final long serialVersionUID = 7373984872572414699L; - private final Sync sync; - - abstract static class Sync extends AbstractQueuedSynchronizer { - private static final long serialVersionUID = -5179523762034025860L; - - abstract void lock(); - - /** - * 如果当前状态为初始状态,那么尝试设置状态; - * 如果状态设置成功后就返回; - * 如果状态被设置,且获取锁的线程又是当前线程的时候,进行状态的自增; - * 如果未设置成功状态且当前线程不是获取锁的线程,那么返回失败。 - */ - final boolean nonfairTryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (compareAndSetState(0, acquires)) { - setExclusiveOwnerThread(current); - return true; - } - } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); - return true; - } - return false; - } - - /** - * 主要计算了释放状态后的值,如果为0则完全释放,返回true,反之仅是设置状态,返回false。 - */ - protected final boolean tryRelease(int releases) { - // 释放后 c 的状态值 - int c = getState() - releases; - // 如果持有锁的线程不是当前线程,直接抛出异常 - if (Thread.currentThread() != getExclusiveOwnerThread()) - throw new IllegalMonitorStateException(); - boolean free = false; - // 如果 c=0,说明所有持有锁都释放完了,其他线程可以请求获取锁 - if (c == 0) { - free = true; - setExclusiveOwnerThread(null); - } - // 这里只会有一个线程执行到这,不存在竞争,因此不需要 CAS - setState(c); - return free; - } - - protected final boolean isHeldExclusively() { - return getExclusiveOwnerThread() == Thread.currentThread(); - } - - final ConditionObject newCondition() { - return new ConditionObject(); - } - - final Thread getOwner() { - return getState() == 0 ? null : getExclusiveOwnerThread(); - } - - final int getHoldCount() { - return isHeldExclusively() ? getState() : 0; - } - - final boolean isLocked() { - return getState() != 0; - } - - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - setState(0); // reset to unlocked state - } - } - - static final class NonfairSync extends Sync { - private static final long serialVersionUID = 7316153563782823691L; - - final void lock() { - if (compareAndSetState(0, 1)) - setExclusiveOwnerThread(Thread.currentThread()); - else - acquire(1); - } - - protected final boolean tryAcquire(int acquires) { - return nonfairTryAcquire(acquires); - } - } - - static final class FairSync extends Sync { - private static final long serialVersionUID = -3000897897090466540L; - - final void lock() { - acquire(1); - } - - /** - * 加入了当前线程(Node)之前是否有前置节点在等待的判断。hasQueuedPredecessors() 方法命名有些歧义,其实应该是 - * currentThreadHasQueuedPredecessors() - * 更为妥帖一些,也就是说当前面没有人排在该节点(Node)前面时候队且能够设置成功状态,才能够获取锁。 - */ - protected final boolean tryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - // c=0 说明没有其他线程占有锁 - if (c == 0) { - // 队列中没有其他线程在等待锁,而且 CAS 把 state 设置成入参的值成功,则当前线程获取锁成功并将 owner 线程设置为当前线程 - if (!hasQueuedPredecessors() && - compareAndSetState(0, acquires)) { - setExclusiveOwnerThread(current); - return true; - } - } - // 可重入设置,当前线程重复请求锁成功,只是增加请求锁的计数 - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); - return true; - } - return false; - } - } - - public ReentrantLock() { - sync = new NonfairSync(); - } - - public ReentrantLock(boolean fair) { - sync = fair ? new FairSync() : new NonfairSync(); - } - - public void lock() { - sync.lock(); - } - - public void lockInterruptibly() throws InterruptedException { - sync.acquireInterruptibly(1); - } - - public boolean tryLock() { - return sync.nonfairTryAcquire(1); - } - - public boolean tryLock(long timeout, TimeUnit unit) - throws InterruptedException { - return sync.tryAcquireNanos(1, unit.toNanos(timeout)); - } - - public void unlock() { - sync.release(1); - } - - public Condition newCondition() { - return sync.newCondition(); - } - - public int getHoldCount() { - return sync.getHoldCount(); - } - - public boolean isHeldByCurrentThread() { - return sync.isHeldExclusively(); - } - - public boolean isLocked() { - return sync.isLocked(); - } - - public final boolean isFair() { - return sync instanceof FairSync; - } - - protected Thread getOwner() { - return sync.getOwner(); - } - - public final boolean hasQueuedThreads() { - return sync.hasQueuedThreads(); - } - - public final boolean hasQueuedThread(Thread thread) { - return sync.isQueued(thread); - } - - public final int getQueueLength() { - return sync.getQueueLength(); - } - - protected Collection getQueuedThreads() { - return sync.getQueuedThreads(); - } - - public boolean hasWaiters(Condition condition) { - if (condition == null) - throw new NullPointerException(); - if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) - throw new IllegalArgumentException("not owner"); - return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); - } - - public int getWaitQueueLength(Condition condition) { - if (condition == null) - throw new NullPointerException(); - if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) - throw new IllegalArgumentException("not owner"); - return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); - } - - protected Collection getWaitingThreads(Condition condition) { - if (condition == null) - throw new NullPointerException(); - if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) - throw new IllegalArgumentException("not owner"); - return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); - } - - public String toString() { - Thread o = sync.getOwner(); - return super.toString() + ((o == null) ? - "[Unlocked]" : - "[Locked by thread " + o.getName() + "]"); - } -} diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/\347\272\277\347\250\213\346\261\240\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/TimeUnit.java" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/\347\272\277\347\250\213\346\261\240\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/TimeUnit.java" deleted file mode 100644 index 294106e..0000000 --- "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\346\272\220\347\240\201\347\261\273\344\270\255\346\226\207\346\263\250\351\207\212/\347\272\277\347\250\213\346\261\240\347\233\270\345\205\263\347\261\273\346\272\220\347\240\201\350\247\243\346\236\220/TimeUnit.java" +++ /dev/null @@ -1,180 +0,0 @@ -package java.util.concurrent; - -public enum TimeUnit { - - // 每个枚举中的参数「d」代表对应枚举的数量,然后将其转化为对应方法要转化的单位数 - NANOSECONDS { - public long toNanos(long d) { return d; } - - // 将 d 纳秒转化为对应的微秒数 - public long toMicros(long d) { return d/(C1/C0); } - public long toMillis(long d) { return d/(C2/C0); } - public long toSeconds(long d) { return d/(C3/C0); } - public long toMinutes(long d) { return d/(C4/C0); } - public long toHours(long d) { return d/(C5/C0); } - public long toDays(long d) { return d/(C6/C0); } - public long convert(long d, TimeUnit u) { return u.toNanos(d); } - int excessNanos(long d, long m) { return (int)(d - (m*C2)); } - }, - - MICROSECONDS { - public long toNanos(long d) { return x(d, C1/C0, MAX/(C1/C0)); } - public long toMicros(long d) { return d; } - public long toMillis(long d) { return d/(C2/C1); } - public long toSeconds(long d) { return d/(C3/C1); } - public long toMinutes(long d) { return d/(C4/C1); } - public long toHours(long d) { return d/(C5/C1); } - public long toDays(long d) { return d/(C6/C1); } - public long convert(long d, TimeUnit u) { return u.toMicros(d); } - int excessNanos(long d, long m) { return (int)((d*C1) - (m*C2)); } - }, - - MILLISECONDS { - public long toNanos(long d) { return x(d, C2/C0, MAX/(C2/C0)); } - public long toMicros(long d) { return x(d, C2/C1, MAX/(C2/C1)); } - public long toMillis(long d) { return d; } - public long toSeconds(long d) { return d/(C3/C2); } - public long toMinutes(long d) { return d/(C4/C2); } - public long toHours(long d) { return d/(C5/C2); } - public long toDays(long d) { return d/(C6/C2); } - public long convert(long d, TimeUnit u) { return u.toMillis(d); } - int excessNanos(long d, long m) { return 0; } - }, - - SECONDS { - public long toNanos(long d) { return x(d, C3/C0, MAX/(C3/C0)); } - public long toMicros(long d) { return x(d, C3/C1, MAX/(C3/C1)); } - public long toMillis(long d) { return x(d, C3/C2, MAX/(C3/C2)); } - public long toSeconds(long d) { return d; } - public long toMinutes(long d) { return d/(C4/C3); } - public long toHours(long d) { return d/(C5/C3); } - public long toDays(long d) { return d/(C6/C3); } - public long convert(long d, TimeUnit u) { return u.toSeconds(d); } - int excessNanos(long d, long m) { return 0; } - }, - - MINUTES { - public long toNanos(long d) { return x(d, C4/C0, MAX/(C4/C0)); } - public long toMicros(long d) { return x(d, C4/C1, MAX/(C4/C1)); } - public long toMillis(long d) { return x(d, C4/C2, MAX/(C4/C2)); } - public long toSeconds(long d) { return x(d, C4/C3, MAX/(C4/C3)); } - public long toMinutes(long d) { return d; } - public long toHours(long d) { return d/(C5/C4); } - public long toDays(long d) { return d/(C6/C4); } - public long convert(long d, TimeUnit u) { return u.toMinutes(d); } - int excessNanos(long d, long m) { return 0; } - }, - - HOURS { - public long toNanos(long d) { return x(d, C5/C0, MAX/(C5/C0)); } - public long toMicros(long d) { return x(d, C5/C1, MAX/(C5/C1)); } - public long toMillis(long d) { return x(d, C5/C2, MAX/(C5/C2)); } - public long toSeconds(long d) { return x(d, C5/C3, MAX/(C5/C3)); } - public long toMinutes(long d) { return x(d, C5/C4, MAX/(C5/C4)); } - public long toHours(long d) { return d; } - public long toDays(long d) { return d/(C6/C5); } - public long convert(long d, TimeUnit u) { return u.toHours(d); } - int excessNanos(long d, long m) { return 0; } - }, - - DAYS { - public long toNanos(long d) { return x(d, C6/C0, MAX/(C6/C0)); } - public long toMicros(long d) { return x(d, C6/C1, MAX/(C6/C1)); } - public long toMillis(long d) { return x(d, C6/C2, MAX/(C6/C2)); } - public long toSeconds(long d) { return x(d, C6/C3, MAX/(C6/C3)); } - public long toMinutes(long d) { return x(d, C6/C4, MAX/(C6/C4)); } - public long toHours(long d) { return x(d, C6/C5, MAX/(C6/C5)); } - public long toDays(long d) { return d; } - public long convert(long d, TimeUnit u) { return u.toDays(d); } - int excessNanos(long d, long m) { return 0; } - }; - - static final long C0 = 1L; - static final long C1 = C0 * 1000L; - static final long C2 = C1 * 1000L; - static final long C3 = C2 * 1000L; - static final long C4 = C3 * 60L; - static final long C5 = C4 * 60L; - static final long C6 = C5 * 24L; - - static final long MAX = Long.MAX_VALUE; - - // 边界值校验 - static long x(long d, long m, long over) { - if (d > over) return Long.MAX_VALUE; - if (d < -over) return Long.MIN_VALUE; - return d * m; - } - - public long convert(long sourceDuration, TimeUnit sourceUnit) { - throw new AbstractMethodError(); - } - - public long toNanos(long duration) { - throw new AbstractMethodError(); - } - - public long toMicros(long duration) { - throw new AbstractMethodError(); - } - - public long toMillis(long duration) { - throw new AbstractMethodError(); - } - - public long toSeconds(long duration) { - throw new AbstractMethodError(); - } - - public long toMinutes(long duration) { - throw new AbstractMethodError(); - } - - public long toHours(long duration) { - throw new AbstractMethodError(); - } - - public long toDays(long duration) { - throw new AbstractMethodError(); - } - - // 返回纳秒,这里其实就是实现 sleep 或者 join 的方法用于计算等待的超过纳秒的参数 - // 其实他就是调用了 Object.sleep(), 实现线程休眠。 - abstract int excessNanos(long d, long m); - - /** - * 根据枚举属性让当前线程进入等待状态,持续 timeout 个单位. - */ - public void timedWait(Object obj, long timeout) - throws InterruptedException { - if (timeout > 0) { - long ms = toMillis(timeout); - int ns = excessNanos(timeout, ms); - obj.wait(ms, ns); - } - } - - /** - * 根据枚举属性让「主线程」等待「子线程」timeout 个单位之后继续运行 - */ - public void timedJoin(Thread thread, long timeout) - throws InterruptedException { - if (timeout > 0) { - long ms = toMillis(timeout); - int ns = excessNanos(timeout, ms); - thread.join(ms, ns); - } - } - - /** - * 根据枚举属性暂停线程 timeout 个单位 - */ - public void sleep(long timeout) throws InterruptedException { - if (timeout > 0) { - long ms = toMillis(timeout); - int ns = excessNanos(timeout, ms); - Thread.sleep(ms, ns); - } - } - -} \ No newline at end of file diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Fork & Join \346\241\206\346\236\266\357\274\214\345\210\206\350\200\214\346\262\273\344\271\213\357\274\214\345\267\245\344\275\234\347\252\203\345\217\226.xmind" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Fork & Join \346\241\206\346\236\266\357\274\214\345\210\206\350\200\214\346\262\273\344\271\213\357\274\214\345\267\245\344\275\234\347\252\203\345\217\226.xmind" index f94467e..c53f31d 100644 Binary files "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Fork & Join \346\241\206\346\236\266\357\274\214\345\210\206\350\200\214\346\262\273\344\271\213\357\274\214\345\267\245\344\275\234\347\252\203\345\217\226.xmind" and "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Fork & Join \346\241\206\346\236\266\357\274\214\345\210\206\350\200\214\346\262\273\344\271\213\357\274\214\345\267\245\344\275\234\347\252\203\345\217\226.xmind" differ diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230.md" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230.md" new file mode 100644 index 0000000..aa5357c --- /dev/null +++ "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230.md" @@ -0,0 +1,256 @@ +# Java 并发编程实战 + +## 15.原子变量与非阻塞同步机制 + +### 锁的劣势 + +- 非阻塞算法 + + - 底层的原子机器指令代替锁来确保数据再并发访问中的一致性 + - 应用场景 + + - 操作系统和 JVM 中实现线程/进程调度机制 + - 垃圾回收机制 + - 锁和其他并发数据结构 + +- 原子变量 + + - 提供了与 volatile 类型变量相同的内存语义,此外还支持原子的更新操作 + - 适用场景 + + - 计数器 + - 序列发生器 + - 统计数据收集 + +- 锁的劣势 + + - 多线程请求锁,一些线程会被挂起。即使线程恢复也需要等待其他线程执行完其对应的时间片。挂起和恢复线程存在很大的性能开销 + - volatile 的局限 + + - 虽然提供可见性的保证,但是不能用于原子的复合操作 + - 当新值依赖于旧值时,就不能使用 volatile + + - 当一个线程正在等待锁时,他不能做任何其他事情,如果持有锁的线程被永久阻塞,等待这个锁的线程就永远无法执行下去 + - 锁定方式对于细粒度的操作仍然是一种高开销的机制 + - 优先级反转 + + - 如果被阻塞的线程优先级比较高,而持有锁的线程优先级比较低,这样会导致高优先级线程降级至低优先级的级别 + +### 硬件对并发的支持 + +- CAS(比较并交换) + + - 冲突检查机制 + + - 判断在更新的过程中是否存在来自其他线程的干扰,如果存在操作置为失败,并且可以重试或者放弃 + + - 几乎所有线代处理器都包含了某种形式的原子「读-改-写」指令 + + - 比较并交换(CAS) + - 关联加载/条件存储 + + - CAS 的三个操作数 + + - 需要读写的内存位置 V + - 进行比较的值 A + - 拟写入的新值 B + + - 当且仅当 V 的值等于 A 时,CAS 才会通过原子方式用新值 B 来更新 V 的值,(比较并设置,Compare and Set)否则不执行任何操作,直接返回 V 原有的值 + + - 我认为 V 的值应该为 A,如果是,那么将 V 的值更新为 B,否则不修改并告诉 V 的值实际为多少。 + + - 多个线程尝试 CAS 的时候,只有其中一个线程能更新变量的值,其他线程都将失败,但是不会线程挂起 + +- 非阻塞的计数器 + + - 当竞争程度不高时,基于 CAS 的计数器在性能上远远超过了基于锁的计数器 + - CAS 的缺点 + + - 让调用者自己处理竞争问题(重试、回退、放弃) + - 锁能自动处理竞争问题(阻塞、挂起) + +- JVM 对 CAS 的支持 + + - 最坏的情况下,如果 JVM 不支持 CAS 指令,则会使用自旋锁 + - 原子变量类(java.util.concurrent.atomic) + + - JVM 支持为数字类型和引用类型提供一种高效的 CAS 操作 + - java.util.concurrent 中大多数类在实现时直接或者间接使用了原子变量类 + +### 原子变量类 + +- 原子变量是一种「更好的 volatile」,不想基本类型的包装类不可修改,原子变量类是可以修改的 + + - 原子变量比锁的粒度更细,量级更轻 + - 原子变量类分组 + + - 标量类 + + - AtomicInteger + - AtomicLong + - AtomicBoolean + - AtomicReference + + - 更新器类 + + - AtomicReferenceFieldUpdater + + - 数组类 + + - AtomicIntegerArray + - AtomicLongArray + - AtomicReferenceArray + + - 复合变量类 + +- 性能比较:锁与原子变量 + + - 高度竞争 + + - 锁的性能超过原子变量的性能,因为锁的机制是将线程挂起,所以会降低 CPU 的使用率 + + - 更真实的竞争 + + - 原子变量的性能超过锁的性能 + + - 中低程度的竞争 + + - 原子变量提供更高的可伸缩性 + +### 非阻塞算法 + +- 非阻塞的栈 + + - 非阻塞算法 + + - 如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,这种算法就是「非阻塞算法」 + + - 无锁算法 + + - 如果在非阻塞的算法中每个步骤都存在某个线程能够执行下去,那么就是「无锁算法」 + + - 非阻塞算法中通常不会产生死锁和优先级反转问题 + +- 非阻塞的链表 +- 原子的域更新器 +- ABA 问题 + + - 如果 V 的值首先由 A 变成 B,再由 B 变成 A,那么仍然要认为发生了变化 + - 解决方案 + + - 加版本号 + +## 16.Java 内存模型 + +对某个线程的内存操作在哪些情况下对于其他线程是可见的说明 + +### 什么是内存模型,为什么需要它 + +- 平台的内存模型 + + - 使得线程无法看到变量的最新值的几种情况 + + - 缺少同步 + - 指令重排序,编译器还会把变量保存在寄存器而不是内存中 + - 处理器可以采用乱序或者并行的方式执行指令 + - 缓存改编将写入变量提交到主内存的次序 + - 保存在处理器本地缓存中的值对于其他处理器是不可见的 + + - 内存栅栏 + + - 在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还订一块了一些特殊的指令 + - 这些特殊的指令就是「内存栅栏」,当需要数据共享时,这些指令就能实现额外的存储协调放置出现一些奇怪的情况。 + +- 重排序 + + - 各种使操作延迟或者看似乱序执行的不同原因,都可以归为重排序 + - 内存及的重排序会使程序的行为变得不可预测 + +- Java 内存模型简介 + + - Java 的内存模型是通过各种操作来定义的 + + - 对变量的读写操作 + - 监视器的加锁与释放操作 + - 线程的启动和合并操作 + + - Happens-Before 关系 + + - 一种偏序关系,保证执行操作 B 的线程看到操作 A 的结果 + + - 程序顺序规则 + + - 如果在程序中操作 A 先于操作 B,那么线程中 A 操作也要先于操作 B + + - 监视器锁规则 + + - 在监视器锁上的操作必须在用一个监视器锁上的加锁操作之前执行 + + - volatile 变量规则 + + - 对 volatile 变量的写入操作必须在对该变量的读操作之前执行 + + - 线程启动规则 + + - 在线程上对 Thread.start 的调用必须在该线程中执行任何操作之前执行 + + - 线程结束规则 + + - 线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从 Thread.join 中成功返回,或者在调用 Thread.isAlive 时返回 false + + - 中断规则 + + - 当一个线程在另一个线程调用 interrupt 时,必须在被中断线程监测到 interrupt 调用之前执行 + + - 终结器规则 + + - 对象的构造函数必须在启动该对象的终结器之前执行完成 + + - 传递性 + + - 如果操作 A 先于操作 B,并且操作 B 先于操作 C,那么操作 A 必须先于操作 B + +- 借助同步 + + - 安全发布 + + - 如果一个线程将对象置入队列并且另一个线程随后获取这个对象 + + - Java 类库提供的 Happens-Before 排序规则 + + - 将一个元素放入一个线程安全容器的操作将在另一个线程从该容器中获得这个元素的操作之前执行 + - 在 CountDownLatch 上的倒数操作将在线程从闭锁上的 await 方法中返回之前执行 + - 释放 Semaphore 许可的操作将在从该 Semaphore 上获得一个许可之前执行 + - Future 表示的任务的所有操作将在从 Future.get 中返回之前执行 + - 向 Excutor 提交一个 Runnable 或 Callable 的操作将在任务开始执行之前执行 + - 一个线程到达 CycliicBarrier 或 Exchanger 的操作姜在其他到达该栅栏或交换点的仙鹤草呢个被释放之前执行。 + +### 发布 + +- 不安全的发布 + + - 当缺少 happens-Before 关系时,就可能出现重排序的问题 + - 被部分构造对象,错误的延迟初始化,双重检查锁问题,多线程环境下的懒汉式单例。 + +- 安全的发布 + + - 使用一个锁保护共享变量 + - 使用共享的 volatile 类型变量 + +- 安全初始化模式 + + - 提前初始化 + + - 参考饿汉式单例 + + - 延迟初始化占位类模式,也可以叫做按需初始化,参考单例的「IoDH」写法 + +- 双重检查加锁 + + - 在没有同步的情况下读取一个共享对象时,可能发生最糟糕的事情只是看到一个失效值 + - 对共享变量声明 volatile,解决该问题,但是性能开销变大 + +### 初始化过程中的安全性 + +- 通过 final 域科大的值从构造过程完成时开始的可见性 + diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230.xmind" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230.xmind" new file mode 100644 index 0000000..c6007b4 Binary files /dev/null and "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230.xmind" differ diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.md" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.md" index 913a058..1d5f6c2 100644 --- "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.md" +++ "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.md" @@ -1,150 +1,209 @@ # Java 并发编程(java.util.concurrent) -## atomic +## 并发工具类 -## locks +### CountDownLatch,计数器工具类 -### AbstractOwnableSynchronizer,主要提供一个exclusiveOwnerThread属性,用于关联当前持有该锁的线程。 +- 初始时需要指定一个计数器的大小,然后可被多个线程并发的实现减 1 操作,并在计数器为 0 后调用 await 方法的线程被唤醒,从而实现多线程间的协作。 +- 任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。 +- 这 N 个子线程是并行执行的,每个子线程执行完后 countDown() 一次,state 会 CAS 减 1。 +- 等到所有子线程都执行完后(即 state=0),会 unpark(线程唤醒) 主调用线程,然后主调用线程就会从 await() 函数返回,继续后余动作。 +- 实现 AQS 的共享 API -- AbstractQueuedSynchronizer,一个同步器,获取锁和释放锁,分独占和共享两种模式,模板设计模式,抽象类 +### CyclicBarrier,循环屏障式计数器 - tryAcquire、tryRelease不定义成抽象的原因:独占和共享没必要都去实现一遍。 +- 可循环使用(Cyclic)的屏障(Barrier),通过它可以实现让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会继续执行。 +- 与 CountDownLatch 的区别 - - CLH锁,一种基于单向链表(隐式创建)的高性能、公平的自旋锁,申请加锁的线程只需要在其前驱节点的本地变量上自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。 + - CountDownLatch 计数为 0 时,无法重置,而 CyclicBarrier 可循环,计数到 0 时重新置为传入的值重新开始 + - CountDownLatch 的 await() 只阻塞线程不影响计数,而 CyclicBarrier 调用 await() 计数减一,-1 后如果值不为 0,线程继续阻塞 + - CountDownLatch 不可重复使用,而 CyclicBarrier 可重复使用 - - CLH 锁的节点对象只有一个 active 属性 - - CLH 锁的节点属性 active 的改变是由其自身触发的 - - CLH 锁是在前驱节点的 active 属性上进行自旋 +### Semaphore,信号量 - - 主要工作思路 +- 用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。 +- 类似于 CountDownLatch,资源总量 state=permits,当 state>0 时就能获得锁,并将 state减 1。 +- 当 state=0 时只能等待其他线程释放锁,当释放锁时 state 加 1,其他等待线程又能获得这个锁。 +- 当 Semaphore 的 permits 定义为 1 时,就是互斥锁,当 permits > 1 就是共享锁。 - - 在获取锁时候,先判断当前状态是否允许获取锁,若是可以则获取锁,否则获取不成功。获取不成功则会阻塞,进入阻塞队列。而释放锁时,一般会修改状态位,唤醒队列中的阻塞线程。 +### Executors,线程池静态工厂 - - CAS + state 状态值 +- newFixedThreadPool - - 比较并交换,是并发控制操作的基础。CAS 有三个值:内存值、原始值、修改值,如果原始值不等于内存值,返回 false;如果等于则修改,返回 true,并将内存值修改为修改值。 + - 固定线程数,既是核心线程数也是最大线程数,不存在空闲线程 - - 实现 +- newWorkStealingPool - - 共享 + - 创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争 + - CPU 数量会被设置为默认的并行度 - - CountDownLatch(门阀) - - Semaphor(信号量) - - ReadWriteLock(读写锁) +- newSingleThreadExecutor - - 排他 + - 创建一个单线程的线程池,相当于但线程串行执行所有任务,保证按任务的提交顺序依次执行 - - ReentrantLock +- newCachedThreadPool - - Node + - 高度可伸缩的线程池 + - 工作线程处于空闲状态,则回收工作线程 + - 如果任务数增加,再次创建出新线程处理任务 - - int waitStatus +- newScheduledThreadPool - - CANCELLED = 1,表示当前的线程被取消 - - waitStatus = 0,表示当前节点在 sync 队列中,等待着获取锁 - - SIGNAL = -1,表示当前节点的后继节点包含的线程需要运行 - - CONDITION = -2,表示当前节点在等待 condition,也就是在 condition 队列中 - - PROPAGATE = -3,表示当前场景下后续的 acquireShared 能够得以执行 + - 定时及周期性任务执行 + - 相比 Timer,ScheduledExecutorService 更安全,功能更强大 + - 特点是不回收线程,而 newCachedThreadPool 回收工作线程 - - Node prev、Node next、Node nextWaiter +### Exchanger,交换者,线程间协作的工具类 - - state +- 提供一个同步点,在这个同步点两个线程可以交换彼此的数据。 +- 这两个线程通过 exchange 方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange +- 当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。 - - 有多少个线程取得了锁,对于互斥锁来说 state <= 1 - - state = 0,锁不被任何线程所占有 +## concurrent - - acquire +### Executor 框架,线程池 - - 功能 +- Future(接口) - - 状态的维护 + - 代表异步计算的结果,通过 Future 接口提供的方法可以查看异步计算是否执行完成,或者等待执行结果并获取执行结果,同时还可以取消执行。 + - RunnableFuture - - 需要在锁定时,需要维护一个状态(int 类型),而对状态的操作是原子和非阻塞的,通过同步器提供的对状态访问的方法对状态进行操纵,并且利用 compareAndSet 来确保原子性的修改。 + - RunnableFuture 继承了 Runnable 接口和 Future 接口 + - RunnableScheduledFuture + - FutureTask - - 状态的获取 + - 事实上,FutureTask 是 Future 接口的一个唯一实现类。 - - 一旦成功的修改了状态,当前线程或者说节点,就被设置为头节点。 + - FutureTask(Callable callable) + - FutureTask(Runnable runnable, V result) - - sync 队列的维护 + - 将一个 Callable 置为 FutureTask 的内置成员 + - 执行 Callable 中的 call 方法 + - 调用 futureTask.get(timeout, TimeUnit) 方法, 获取 call 的执行结果, 超时的话就报 TimeoutException - - 在获取资源未果的过程中条件不符合的情况下(不该自己,前驱节点不是头节点或者没有获取到资源)进入睡眠状态,停止线程调度器对当前节点线程的调度。 + - ScheduledFuture + - ForkJoinTask + - CompletableFuture - - tryAcquire -acquire -tryAcquireShared -doAcquireShared -acquireQueued -tryAcquireNanos -doAcquireNanos -tryAcquireSharedNanos -doAcquireSharedNanos -acquireSharedInterruptibly -doAcquireSharedInterruptibly -acquireInterruptibly -doAcquireInterruptibly -shouldParkAfterFailedAcquire -cancelAcquire + - 使用 Future 获得异步执行结果时,要么调用阻塞方法 get(),要么轮询看 isDone() 是否为 true,这两种方法都不是很好,因为主线程也会被迫等待。 + - CompletableFuture,它针对 Future 做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。 + - 在异步任务完成后,使用任务结果时就不需要等待,可以直接通过 thenAccept、thenApply、thenCompose 等方法将前面异步处理的结果交给另外一个异步事件处理线程来处理 + - CompletableFuture 更强大的功能是,多个 CompletableFuture 可以串行执行,也可以并行执行 + - 内置方法声明规则 - - release + - xxx():表示该方法将继续在已有的线程中执行; + - xxxAsync():表示将异步在线程池中执行。 + - 主要方法 - - tryRelease -release -releaseShared -tryReleaseShared -doReleaseShared -fullyRelease + - thenAccept() 处理正常结果 + - exceptional() 处理异常结果; + - thenApplyAsync() 用于串行化另一个 CompletableFuture; + - anyOf() 和 allOf() 用于并行化多个 CompletableFuture。 -- AbstractQueuedLongSynchronizer,64位版本的同步器 + - 声明的方法 -### Condition + - cancel(boolean mayInterruptIfRunning); -### Lock(接口) + - 用来取消异步任务的执行。 + - 如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回 false。 + - 如果任务还没有被执行,则会返回 true 并且异步任务不会被执行。 + - 如果任务已经开始执行了但是还没有执行完成,若 mayInterruptIfRunning 为 true,则会立即中断执行任务的线程并返回 true + - 若 mayInterruptIfRunning 为 false,则会返回 true 且不会中断任务执行线程。 -- 实现 + - isCancelled(); - - ReentrantLock + - 判断任务是否被取消,如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回 true,否则返回 false。 - - 公平锁 - - 非公平锁 + - isDone(); - - ReentrantReadWriteLock.ReadLock - - ReentrantReadWriteLock.WriteLock + - 任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回 true。 + - 返回计算是否完成 , 若任务完成则返回 true (任务完成 state = narmal, exception, interrupted) -- 核心方法声明 + - V get() - - tryLock() + - 获取计算的结果, 若计算没完成, 直接 await, 直到计算结束或线程中断 + - 获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。 + - 如果任务被取消则会抛出 CancellationException 异常,如果任务执行过程发生异常则会抛出 ExecutionException 异常 + - 如果阻塞等待过程中被中断则会抛出 InterruptedException 异常。 - - 仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true 。如果锁不可用,则此方法将立即返回值 false 。通常对于那些不是必须获取锁的操作可能有用。 + - V get(long timeout, TimeUnit unit) - - lock() + - 获取计算的结果, 若计算没完成, 直接 await, 直到计算结束或线程中断或 time 时间超时 - - 获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。 +- Callable - - unlock() - - lockInterruptibly() - - tryLock(long time, TimeUnit unit) + - 是个泛型接口,泛型 V 就是要 call() 方法返回的类型。 + - Callable 接口和 Runnable 接口很像,都可以被另外一个线程执行,但是 Runnable 不会返回数据也不能抛出异常。 + - Callable 一般是和 ExecutorService 配合来使用的 -### LockSupport +- Executor,接口,只有一个「void execute(Runnable command);」方法 -### ReadWriteLock + - ExecutorService,接口,定义完整的线程池行为 -### ReentrantReadWriteLock + - AbstractExecutorService,抽象类,主要是实现 ExecutorService 接口生命的任务提交等方法 -### StampedLock + - ThreadPoolExecutor,创建一个线程池 -## concurrent + - 构造方法参数 -### Executor 框架,线程池 + - corePoolSize(常驻核心线程数) -- 子主题 1 -- 子主题 2 -- Executor,接口,只有一个「void execute(Runnable command);」方法 + - 如果当前运行的线程少于 corePoolSize(常驻核心线程数),则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁) - - ExecutorService,接口,定义完整的线程池行为 + - runnableTaskQueue - - AbstractExecutorService,抽象类,主要是实现 ExecutorService 接口生命的任务提交等方法 + - 用于保存等待执行的任务的阻塞队列 + + - ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 + - LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列。 + - SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。 + - PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 + + - maximumPoolSize + + - 如果队列已满,则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁) + + - ThreadFactory + + - 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字 + + - RejectedExecutionHandler + + - 当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。 + + - AbortPolicy:直接抛出异常。 + - DiscardPolicy:不处理,丢弃掉。 + - DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 + - CallerRunsPolicy:只用调用者所在线程来运行任务。 + - 自定义实现 RejectedExecutionHandler 接口 + + - keepAliveTime + + - 线程池的工作线程空闲后,保持存活的时间。 + + - 任务提交 + + - 使用 execute 提交的任务(Runnable),但是 execute 方法没有返回值,所以无法判断任务知否被线程池执行成功 + - 使用 submit 方法来提交任务,它会返回一个 Future,可以通过这个 Future 来判断任务是否执行成功,通过 Future 的 get 方法来获取返回值,get 方法会阻塞住直到任务完成 + + - 线程池的关闭 + + - shutdown + + - 只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。 + + - shutdownNow + + - 遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。 + + - 线程池数据监控 + + - taskCount:线程池需要执行的任务数量。 + - completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。 + - largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。 + - getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。 + - getActiveCount:获取活动的线程数。 - - ThreadPoolExecutor - ForkJoinPool,分而治之,工作窃取 老四之前在博客中写过关于 Fork/Join 框架的一片文章,可以参考《浅析Java中的Fork和Join并发编程框架》。http://www.glorze.com/792.html @@ -290,6 +349,570 @@ fullyRelease ### Collections -### Tools +- Queue + + - ConcurrentLinkedQueue + + - 使用 CAS 非阻塞算法实现 + - 并发队列,提供了比 synchronized 机制更高的性能和可伸缩性。 + - 空队列通常都包含一个「哨兵节点」或者叫「哑节点」,并且头节点和尾节点在初始化时都指向该哨兵节点。 + - 尾节点通常要么指向哨兵节点(队列为空时),要么指向倒数第二个元素(有操作正在执行更新) + + - BlockingQueue(使用锁实现) + + - ArrayBlockingQueue + + - 一个由数组结构组成的有界阻塞队列,默认为非公平锁 + - 通过使用全局独占锁实现同时只能有一个线程进行入队或者出队操作,这个锁的粒度比较大,有点类似在方法上添加synchronized的意味。 + - 另外相比 LinkedBlockingQueue、ArrayBlockingQueue的size 操作的结果是精确的,因为计算前加了全局锁。 + + - LinkedBlockingQueue + + - 一个由链表结构组成的无界阻塞队列,使用独占锁 ReentrantLock 实现,这里边无界的概念是只要有内存就能一直存储元素 + - 主要构成 + + - 两个 Node 分别用来存放首尾节点 + - 初始值为 0 的原子变量 count 用来记录队列元素个数 + - 两个ReentrantLock 的独占锁,分别用来控制元素入队和出队加锁,其中 takeLock 用来控制同时只有一个线程可以从队列获取元素,其他线程必须等待,putLock 控制同时只能有一个线程可以获取锁去添加元素,其他线程必须等待。 + - 另外 notEmpty 和 notFull 用来实现入队和出队的同步。由于出入队是两个非公平独占锁,所以可以同时有一个线程入队和一个线程出队,其实这个是个生产者-消费者模型。 + + - 主要方法 + + - offer 操作-生产者,带超时间和不带 + - put 操作-生产者 + - poll 操作-消费者,带超时时间和不带 + - take 操作-消费者 + - size 操作 + - peek 操作 + - remove操作 + + - PriorityBlockingQueue + + - 一个支持优先级排序的无界阻塞队列,每次出队都返回优先级最高的元素,是二叉树最小堆的实现,直接遍历队列元素是无序的。 + + - DelayQueue + + - 一个使用优先级队列实现的无界阻塞队列 + - DelayQueue 队列中每个元素都有个过期时间,并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队列。 + - 内部使用的是 PriorityQueue 存放数据,使用 ReentrantLock 实现线程同步,所以是阻塞队列。 + - 队列里面的元素要实现 Delayed 接口,一个是获取当前剩余时间的接口,一个是元素比较的接口,因为是有优先级的队列。 + + - SynchronousQueue + + - 一个不存储元素的阻塞队列 + - 生产者线程对其的插入操作 put 必须等待消费者的移除操作 take,反过来也一样。 + - 生产者和消费者互相等待对方,握手,然后一起离开。 + + - TransferQueue + + - LinkedTransferQueue + + - 一个由链表结构组成的无界阻塞队列 + - 生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事) + - 在队列中已有元素的情况下,调用 transfer 方法,可以确保队列中被传递元素之前的所有元素都能被处理。 + + - Deque + + - ArrayDeque + - LinkedList + + - 双端队列、线程不安全 + - 双链表实现了 List 和 Deque 接口。 + - 实现所有可选列表操作,并允许所有元素(包括 null )。 + - 链表批量增加,是靠 for 循环遍历原数组,依次执行插入节点操作。对比 ArrayList 是通过 System.arraycopy 完成批量增加的。 + + - BlockingQueue + + - LinkedBlockingDeque + + - 一个由链表结构组成的双向阻塞队列 + +- CopyOnWriteArrayList + + - Copy-On-Write 简称 COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容 Copy 出去形成一个新的内容然后再改,这是一种延时懒惰策略。 + - 注意事项 + + - 减少扩容开销,尽量合理设置初始容量大小 + - 尽量使用批量添加,因为每次添加都需要复制,因此尽量避免减少添加次数 + + - 缺点 + + - 内存占用,毕竟要进行复制,还要就得容器提供使用,容易引起频繁的 Young GC 或者是 Full GC + - 数据一致性问题 + + - 只能保证最终的一致性而不能保证数据的实时一致性 + +- CopyOnWriteArraySet +- ConcurrentSkipListSet + + - 通过 ConcurrentNavigableMap 来实现的,它是一个有序的线程安全的集合。 + +- ConcurrentMap + + - ConcurrentHashMap + + - 在 1.7 及以下,ConcurrentHashMap 使用的是数组加链表,Segment + ReentrantLock,锁分段 + - 在 1.8 后,对 ConcurrentHashMap 做了一些调整 + + - 链表长度 >= 8时,链表会转换为红黑树,<= 6 时又会恢复成链表; + - 1.7 及以前,链表采用的是头插法,1.8 后改成了尾插法; + - Segment + ReentrantLock 改成了 CAS + synchronized。取消 Segment,直接利用 table 数组单元作为锁,实现了可对每行数据加锁,进一步提高了并发性能。 + - 链表的长度超过了 8,那么链表将转换为红黑树。(桶的数量必须大于 64,小于 64 的时候只会扩容) + + - 1.7 查询遍历链表效率太低,因此 1.8 做了一些数据结构上的调整,也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。 + + - ConcurrentNavigableMap + + - ConcurrentSkipListMap + + - HashMap & ConcurrentHashMap + + - HashMap(jdk1.7) + + - 核心成员变量 + + - 初始桶大小----16,可自定义 + - 桶最大值----2^30 + - 负载因子----0.75,可自定义 + - table----也就是俗话说的桶,整整存放数据的数组 + - size----Map 存放元素的数量 + + - 方法 + + - put 方法 + + - 判断当前数组是否需要初始化。 + - 如果 key 为空,则 put 一个空值进去。 + - 根据 key 计算出 hashcode。 + - 根据计算出的 hashcode 定位出所在桶。 + - 如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。 + - 如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置。 + - 当调用 addEntry 写入 Entry 时需要判断是否需要扩容。如果需要就进行两倍扩充,并将当前的 key 重新 hash 并定位。 + + - get 方法 + + - 首先也是根据 key 计算出 hashcode,然后定位到具体的桶中。 + - 判断该位置是否为链表。 + - 不是链表就根据 key、key 的 hashcode 是否相等来返回值。 + - 为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。 + - 啥都没取到就直接返回 null 。 + + - HashMap(jdk1.8) + + - Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。 + - 核心改进项 + + - TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。 + - HashEntry 修改为 Node。 + + - 方法 + + - put 方法 + + - 判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始化)。 + - 根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可。 + - 如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等 + - 如果当前桶为红黑树,那就要按照红黑树的方式写入数据。 + - 如果是个链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面(形成链表)。 + - 接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。 + - 如果在遍历过程中找到 key 相同时直接退出遍历。存在相同的 key,那就需要将值覆盖。 + - 最后判断是否需要进行扩容。 + + - get 方法 + + - 首先将 key hash 之后取得所定位的桶。 + - 如果桶为空则直接返回 null 。 + - 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。 + - 如果第一个不匹配,则判断它的下一个是红黑树还是链表。 + - 红黑树就按照树的查找方式返回值。 + - 不然就按照链表的方式遍历匹配返回值。 + + - HashMap 的缺陷(或者说为什么不是线程安全的) + + - 并发场景下使用时容易出现死循环 + - 多个线程进行扩容只会有一个成功,数据丢失 + + - ConcurrentHashMap(1.7) + + - Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。 + - 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。 + - 方法 + + - put 方法 + + - 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。 + - 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。 + - 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。 + - 最后会解除所获取当前 Segment 的锁。 + + - get 方法 + + - 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 + + - ConcurrentHashMap(1.8) + + - 解决查询遍历链表效率太低,抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。 + - 方法 + + - put 方法 + + - 根据 key 计算出 hashcode 。 + - 判断是否需要进行初始化。 + - 利用 CAS 尝试写入,失败则自旋保证成功。 + - 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。 + - 如果都不满足,则利用 synchronized 锁写入数据。 + - 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。 + + - get 方法 + + - 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 + - 如果是红黑树那就按照树的方式获取值。 + - 不满足那就按照链表的方式遍历获取值。 + +## locks + +### AbstractOwnableSynchronizer,主要提供一个exclusiveOwnerThread 属性,用于关联当前持有该锁的线程。 + +- AbstractQueuedSynchronizer,一个同步器,获取锁和释放锁,分独占和共享两种模式,模板设计模式,抽象类 + + tryAcquire、tryRelease不定义成抽象的原因:独占和共享没必要都去实现一遍。 + + - CLH 锁,一种基于双向链表(隐式创建)的高性能、公平的自旋锁,申请加锁的线程只需要在其前驱节点的本地变量上自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。 + + - CLH 锁的节点对象只有一个 active 属性 + - CLH 锁的节点属性 active 的改变是由其自身触发的 + - CLH 锁是在前驱节点的 active 属性上进行自旋 + + - 主要工作思路 + + - 在获取锁时候,先判断当前状态是否允许获取锁,若是可以则获取锁,否则获取不成功。获取不成功则会阻塞,进入阻塞队列。而释放锁时,一般会修改状态位,唤醒队列中的阻塞线程。 + + - CAS + state 状态值 + + - 比较并交换,是并发控制操作的基础。CAS 有三个值:内存值、原始值、修改值,如果原始值不等于内存值,返回 false;如果等于则修改,返回 true,并将内存值修改为修改值。 + + - 实现 + + - 共享 + + - CountDownLatch(门阀) + - Semaphor(信号量) + - ReadWriteLock(读写锁的读锁) + + - 排他 + + - ReentrantLock + - ReadWriteLock(读写锁的写锁) + + - Node + + - int waitStatus + + - CANCELLED = 1,表示当前的线程被取消 + - waitStatus = 0,表示当前节点在 sync 队列中,等待着获取锁 + - SIGNAL = -1,表示当前节点的后继节点包含的线程需要运行 + - CONDITION = -2,表示当前节点在等待 condition,也就是在 condition 队列中 + - PROPAGATE = -3,表示当前场景下后续的 acquireShared 能够得以执行 + + - Node prev、Node next、Node nextWaiter + + - state + + - 有多少个线程取得了锁,对于互斥锁来说 state <= 1 + - state = 0,锁不被任何线程所占有 + + - acquire + + - 功能 + + - 状态的维护 + + - 需要在锁定时,需要维护一个状态(int 类型),而对状态的操作是原子和非阻塞的,通过同步器提供的对状态访问的方法对状态进行操纵,并且利用 compareAndSet 来确保原子性的修改。 + + - 状态的获取 + + - 一旦成功的修改了状态,当前线程或者说节点,就被设置为头节点。 + + - sync 队列的维护 + + - 在获取资源未果的过程中条件不符合的情况下(不该自己,前驱节点不是头节点或者没有获取到资源)进入睡眠状态,停止线程调度器对当前节点线程的调度。 + + - tryAcquire +acquire +tryAcquireShared +doAcquireShared +acquireQueued +tryAcquireNanos +doAcquireNanos +tryAcquireSharedNanos +doAcquireSharedNanos +acquireSharedInterruptibly +doAcquireSharedInterruptibly +acquireInterruptibly +doAcquireInterruptibly +shouldParkAfterFailedAcquire +cancelAcquire + + - release + + - tryRelease +release +releaseShared +tryReleaseShared +doReleaseShared +fullyRelease + +- AbstractQueuedLongSynchronizer,64位版本的同步器 + +### Condition + +- 多线程间协调通信的工具类,使得某个或者某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll 方法被调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁。 +- 两个 node 分别用来存放条件队列的首尾节点,条件队列就是调用条件变量的 await 方法被阻塞后的节点组成的单向链表。 +- ConditionObject 还要依赖 AQS 的 state,ConditionObject 是 AQS 类的一个内部类。 + +### Lock(接口) + +- 实现 + + - ReentrantLock + + - 公平锁 + - 非公平锁 + + - ReentrantReadWriteLock.ReadLock + - ReentrantReadWriteLock.WriteLock + +- 核心方法声明 + + - tryLock() + + - 仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true 。如果锁不可用,则此方法将立即返回值 false 。通常对于那些不是必须获取锁的操作可能有用。 + + - lock() + + - 获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。 + + - unlock() + + - 释放锁。对应于 lock()、tryLock()、tryLock(xx)、lockInterruptibly() 等操作,如果成功的话应该对应着一个 unlock(),这样可以避免死锁或者资源浪费。 + + - lockInterruptibly() + + - 如果当前线程未被中断,则获取锁。 + - 如果锁可用,则获取锁,并立即返回。 + - 如果锁不可用,出于线程调度目的,将禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态: + + - 锁由当前线程获得; + + - 如果当前线程:在进入此方法时已经设置了该线程的中断状态; + + - 或者其他某个线程中断 当前线程,并且支持对锁获取的中断。 + + - 或者在获取锁时被中断 ,并且支持对锁获取的中断,则将抛出 InterruptedException ,并清除当前线程的已中断状态。 + + - tryLock(long time, TimeUnit unit) + + - 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。如果锁可用,则此方法将立即返回值 true 。如果锁不可用,出于线程调度目的,将禁用当前线程 + + - newCondition() + + - 返回用来与此 Lock 实例一起使用的 Condition 实例。 + +### LockSupport + +- 线程的阻塞与唤醒 + + - park() + - unpark() + +### ReadWriteLock(接口) + +- 声明读锁和写锁 + + - Lock readLock(); + - Lock writeLock(); + +- ReentrantReadWriteLock + + - 读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。只要没有写,读取锁可以由多个读线程同时保持。写入锁是独占的。 + - 当队列中的头节点为读锁时,代表读操作可以执行,而写操作不能执行,因此请求写操作的线程会被挂起,当读操作依次退出后,写锁成为头节点,请求写操作的线程被唤醒,可以执行写操作,而此时的读请求将被封装成 Node 放入 AQS 的队列中。如此往复,实现读写锁的读写交替进行。 + - 适用于读多写少的情况。 + - AQS 的状态是32位(int 类型)的,分成两份,读锁用高 16 位,表示持有读锁的线程数(sharedCount),写锁低16位,表示写锁的重入次数 (exclusiveCount)。 + - 状态值为 0 表示锁空闲,sharedCount不为 0 表示分配了读锁,exclusiveCount 不为 0 表示分配了写锁,sharedCount 和 exclusiveCount 肯定不会同时不为 0。 + +- StampedLock.ReadWriteLockView + +### StampedLock + +- jdk8 版本新增的一个锁,该锁提供了三种模式的读写控制 + + - 写锁 writeLock + + - 是个排它锁或者叫独占锁,同时只有一个线程可以获取该锁,当一个线程获取该锁后,其它请求的线程必须等待,当目前没有线程持有读锁或者写锁的时候才可以获取到该锁,请求该锁成功后会返回一个 stamp 票据变量用来表示该锁的版本,当释放该锁时候需要 unlockWrite 并传递参数 stamp。 + + - 悲观读锁 readLock + + - 是个共享锁,在没有线程获取独占写锁的情况下,同时多个线程可以获取该锁,如果已经有线程持有写锁,其他线程请求获取该读锁会被阻塞。这里讲的悲观其实是参考数据库中的乐观悲观锁的,这里说的悲观是说在具体操作数据前悲观的认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据加锁,这是在读少写多的情况下的一种考虑,请求该锁成功后会返回一个 stamp 票据变量用来表示该锁的版本,当释放该锁时候需要 unlockRead 并传递参数 stamp。 + + - 乐观读锁 tryOptimisticRead + + - 是相对于悲观锁来说的,在操作数据前并没有通过 CAS 设置锁的状态,如果当前没有线程持有写锁,则简单的返回一个非 0 的 stamp 版本信息,获取该 stamp 后在具体操作数据前还需要调用 validate 验证下该 stamp 是否已经不可用,也就是看当调用 tryOptimisticRead 返回 stamp 后到到当前时间间是否有其他线程持有了写锁,如果是那么 validate 会返回 0,否则就可以使用该 stamp 版本的锁对数据进行操作。由于 tryOptimisticRead 并没有使用 CAS 设置锁状态所以不需要显示的释放该锁。 该锁的一个特点是适用于读多写少的场景,因为获取读锁只是使用与或操作进行检验,不涉及 CAS 操作,所以效率会高很多,但是同时由于没有使用真正的锁,在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性还是得到保障的。 + +## atomic(原子并发类包),主要是对 CAS 原理的掌握 + +### AtomicBoolean + +- 可以用原子方式更新的 boolean 值。 + +### AtomicInteger + +- 可以用原子方式更新的 int 值。 + +### AtomicIntegerArray + +- 可以用原子方式更新其元素的 int 数组。 + +### AtomicIntegerFieldUpdater + +- 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。 + +### AtomicLong + +- 可以用原子方式更新的 long 值。 + +### AtomicLongArray + +- 可以用原子方式更新其元素的 long 数组。 + +### AtomicLongFieldUpdater + +- 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。 + +### AtomicMarkableReference + +- 维护带有标记位的对象引用,可以原子方式对其进行更新。 + +### AtomicReference + +- 可以用原子方式更新的对象引用。 + +### AtomicReferenceArray + +- 可以用原子方式更新其元素的对象引用数组。 + +### AtomicReferenceFieldUpdater + +- 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。 + +### AtomicStampedReference + +- 维护带有整数「标志」的对象引用,可以用原子方式对其进行更新。 + +### Striped64 + +把逻辑上连续的数据分为多个段,使这一序列的段存储在不同的物理设备上。通过把段分散到多个设备上可以增加访问并发性,从而提升总体的吞吐量。 + +通过一个 Cell 数组维持了一序列分解数的表示,通过 base 字段维持数的初始值,通过 cellsBusy 字段来控制 resizing 和/或创建 Cell。它还提供了对数进行累加的机制。 + +- 这个类维护一个延迟初始的、原子地更新值的表,加上额外的「base」 字段。表的大小是 2 的幂。当表的条目上出现竞争时,在到达容量前表扩容一倍,通过增加条目来减少竞争。 +- DoubleAccumulator +- DoubleAdder + + - 底层通过转换为 Long 来进行运算 + +- LongAccumulator + + - 相比于 LongAdder,支持各种自定义运算的 long 运算 + +- LongAdder + + - JDK8 新增,高并发环境下比 AtomicLong 更高效 + +## 关键字总结 + +### volatile + +- 变量可见性问题 + + 在一个多线程的应用中,线程在操作非 volatile 变量时,出于性能考虑,每个线程可能会将变量从主存拷贝到 CPU 缓存中。如果你的计算机有多个 CPU,每个线程可能会在不同的 CPU 中运行。这意味着,每个线程都有可能会把变量拷贝到各自 CPU 的缓存中。 + + 对于非 volatile 变量,JVM 并不保证会从主存中读取数据到 CPU 缓存,或者将 CPU 缓存中的数据写到主存中。 + + - volatile 关键字就是设计用来解决变量可见性问题。将变量声明为 volatile,则在写入变量时,也会同时将变量值写入到主存中。同样的,在读取变量值时,也会直接从主存中读取。 + +- 完整的 volatile 可见性保证 + + - 如果线程 A 写入一个 volatile 变量,线程 B 随后读取了同样的 volatile 变量,则线程 A 在写入 volatile 变量之前的所有可见的变量值,在线程 B 读取 volatile 变量后也同样是可见的。 + - 如果线程A读取一个volatile变量,那么线程A中所有可见的变量也会同样从主存重新读取。 + +- 指令重排序问题 + + - volatile 可以保证 Happens-Before 原则 + +- volatile 不能保证原子性 + + - 多个线程都能写入共享的 volatile 变量,主存中也能存储正确的变量值,然而这有一个前提,变量新值的写入不能依赖于变量的旧值。换句话说,就是一个线程写入一个共享 volatile 变量值时,不需要先读取变量值,然后以此来计算出新的值。 + - 如果线程需要先读取一个 volatile 变量的值,以此来计算出一个新的值,那么 volatile 变量就不足够保证正确的可见性。 + +### synchronized + +- CAS + + - Compare and Swap,比较并设置。用于在硬件层面上提供原子性操作。在 Intel 处理器中,比较并交换通过指令 cmpxchg实现。比较是否和给定的数值一致,如果一致则修改,不一致则不修改。 + +- Java中的每一个对象都可以作为锁 + + - 对于同步方法,锁是当前实例对象。 + - 对于静态同步方法,锁是当前对象的Class对象。 + - 对于同步方法块,锁是 synchonized 括号里配置的对象。 + +- synchronized 同步的原理 + + - JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,使用 monitorenter 和 monitorexit 指令实现,monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处, JVM 要保证每个 monitorenter 必须有对应的 monitorexit 与之配对 + - 任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。 + +- Java 对象头 + + - Mark Word(存储对象的 hashCode 或锁信息等) + + - 锁状态(默认无锁) + - 对象的 hashCode(25位) + - 对象分代年龄(4位) + - 是否是偏向锁(1位) + - 锁标志位(2位) + + - 在运行期间 Mark Word 里存储的数据会随着锁标志位的变化而变化 + + - https://pic.downk.cc/item/5eec6f1314195aa594e6dc17.png + + - Class Metadata Address + + - 存储到对象类型数据的指针 + + - Array length + + - 数组的长度(如果当前对象是数组) + +- 锁的升级,只能升级不能降级 + + - 无锁 + - 偏向锁 + + - 大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁 + - 优点:加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 +缺点:如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 +适用场景:适用于只有一个线程访问同步块场景。 + + - 轻量级锁 + + - 线程在执行同步块之前,JVM 会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中。然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 + - 优点:竞争的线程不会阻塞,提高了程序的响应速度。 +缺点:如果始终得不到锁竞争的线程使用自旋会消耗 CPU。 +适用场景:追求响应时间;同步块执行速度非常快; + + - 重量级锁 + + - 优点:线程竞争不使用自旋,不会消耗CPU。 +缺点:线程阻塞,响应时间缓慢。 +适用场景:追求吞吐量;同步块执行速度较长; -*XMind: ZEN - Trial Version* \ No newline at end of file diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.xmind" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.xmind" index 3af70f0..42626e5 100644 Binary files "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.xmind" and "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \345\271\266\345\217\221\347\274\226\347\250\213\357\274\210java.util.concurrent\357\274\211.xmind" differ diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \347\232\204\347\272\277\347\250\213\345\256\211\345\205\250.md" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \347\232\204\347\272\277\347\250\213\345\256\211\345\205\250.md" new file mode 100644 index 0000000..ec63520 --- /dev/null +++ "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \347\232\204\347\272\277\347\250\213\345\256\211\345\205\250.md" @@ -0,0 +1,209 @@ +# Java 的线程安全 + +## 类的状态角度看线程安全 + +原文地址:http://www.hyhblog.cn/2018/09/16/java_safe_concurrency/ + +### 类变量 + +- 公有变量(public)、私有变量(private)、保护变量(protect) +- 静态变量(static) +- 不可变变量(final) +- 外部变量、内部变量、局部变量 + +### Java 类的安全并发设计 + +- 无状态类(无任何成员变量声明)(多线程并发安全) +- 有状态类(Java 类中有成语变量声明) + + - 私有状态类(ThreadLocal)(多线程并发安全) + - 共享状态类(类变量可以被多线程访问) + + - 不可变状态(final 常量)(多线程并发安全) + - 可变状态 + + - 静态状态(static)(多线程并发不不安全) + - 非阻塞设计 + + - 非阻塞算法(多线程并发安全) + + - 原子类(多线程并发安全) + + - 阻塞设计(加锁)(多线程并发安全) + + - 资源死锁(多线程并发不不安全) + - 锁顺序死锁(多线程并发不不安全) + - 状态公开(Publish)(多线程并发不不安全) + +### Java 安全并发分解 + +- 无状态类 + + - 一个无状态类是指其没有任何声明的成员变量,无状态类是线程安全的。 + +- 有状态类 + + - 有状态类是指类中有声明的成员变量,有状态是导致线程不安全的必要条件,但不是充分条件 + +- 私有状态类 + + - 如果 Java 类的状态通过 ThreadLocal 等方法,使得状态被隔离在各个线程中,相互不干扰 + +- 共享状态类 + + - Java 成员变量是线程共享的,即多个线程通过 Java 类提供的类方法访问类对象时,类对象中的成员变量可以被共享访问到 + +- 不可变状态类(常量状态) + + - 变量被声明为 final,这说明这个变量是一个常量对象,初始化之后不再改变。 + - final 声明使得变量变为常量状态,多线程在访问时不能更改状态,在一定程度上实现了只读,从而是线程安全的。 + +- 可变状态类 + + - 可变的共享状态,当多线程访问时,必然出现协同操作和同步问题,若代码设计不当,则很容易出现线程不安全问题。 + - 为了实现线程安全,一般通过「非阻塞设计(多线程并行执行,算法保证线程安全)」、「阻塞设计(加锁让多线程实现串行执行)」两种方法, + +- 非阻塞设计 + + - CAS 算法,即 Compare And Swap 方法,过程叫做 Compare And Set。 + +- 阻塞设计 + + - 通过锁来控制线程对类状态的访问,使得当前状态只能由一个线程访问,其它访问线程则挂起等待,一直等到锁被释放后,所有的等待线程竞争锁,获得下一次访问权。 + - 若两个线程之间相互持有对方需要的资源或锁,则进入死锁状态。 + - 资源死锁 + + - 资源不够多个线程调用,解决办法就是增加更多的资源。比如线程 1 获取了打印机,线程 2 获取了文件对象,相互都不是放锁,文件无法打印。 + + - 锁顺序死锁 + + - 线程 1 持有对象 A 的锁,线程 2 持有对象 B 的锁,而方法的执行需要 AB 同时交互执行,比如转账,导致锁顺序死锁 + - 实现锁的按序持有,即对于任何两个对象锁 A 和B,先进行排序,多线程都必须按照锁的排序依次获取,从而避免相互持有对方需要的锁。 + + - 状态公开 + + - 类成员变量被公开,在一定程度上破坏了面向对象设计的数据封装性。 + - 成员变量声明为私有,在执行读操作时,对外克隆一份数据副本,从而保证类内部数据对象不被泄露, + +### 类的静态状态 + +- 类中被 static 声明的成员变量,这个状态会在类初次加载时初始化,被所有的类对象所共享。 +- 若是 static 成员变量,必须考虑是否为 final。 + +### 类外部状态和多线程安全并发 + +- 类方法需要对外部传入的对象进行操作,外部状态的安全性取决于外部的并发设计。 +- 在调用类方法的地方,传入一个外部状态的副本,隔离内外部数据的关联性。 + +## 线程安全的分类及方案 + +原文地址:https://www.cnblogs.com/duanxz/p/6099983.html + +### 共享的数据(排除反射的干扰项) + +- 不可变 + + - final 关键字带来的可见性,只要一个不可变的对象被正确地构建出来(没有发生 this 引用逃逸的情况),那其外部的可见状态永远也不会改变 + - 如果共享数据是一个基本数据类型,那么只要在定义时使用 final 关键字修饰它就可以保证它是不可变的 + - 如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行 + + - 保证对象行为不影响自己状态的途径有很多种,最简单的就是把对象中带有状态的变量都声明为 final + +- 绝对线程安全 + + - 在 Java API 中标注自己是线程安全的类,大多数都不是绝对的线程安全。 + - 可以理解为绝对的线程安全其实就是串行化了 + +- 相对线程安全 + + - 我们通常意义上所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施 + - 但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。 + +- 线程兼容 + + - 对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用 + +- 线程对立 + + - 无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。 + - Thread类的 suspend() 和 resume() 方法,已经过时 + +### 线程安全的一些方式 + +- 互斥同步,阻塞同步,锁同步 + + - 同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用 + - 互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式 + - 最基本的互斥同步手段就是 synchronized 关键字 + - 还可以使用 J.U.C 包中的可重入锁 ReentrantLock 来实现同步,ReentrantLock增加了「等待可中断」、「可实现公平锁」、「锁可以绑定多个条件」等高级功能 + + - 等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情 + - 公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,默认非公平锁 + - 锁绑定多个条件是指一个 ReentrantLock 对象可以同时绑定多个 Condition 对象 + + - 互斥同步最主要的问题是进行线程阻塞和唤醒所带来的性能问题,悲观的并发策略 + +- 非阻塞同步 + + - 不需要把线程挂起的乐观并发策略 + - 先进行操作,如果没有其他线程争用共享数据即操作成功; +如果共享数据有争用,产生冲突,那就再采取补偿措施,常见的是不断地重试,直到成功为止) + +- 无同步方案 + + - 可重入性是更基本的特性,它可以保证线程安全 + + 可重入代码(Reentrant Code):这种代码也叫做纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。 + + - 线程本地存储,可以通过 ThreadLocal 类来实现线程本地存储的功能 + +## 如何正确的停止(一个)线程 + +### 通过修改共享变量来通知目标线程停止运行 + +- 目标线程必须有规律的检查变量,当该变量指示它应该停止运行时,该线程应该按一定的顺序从它执行的方法中返回。 +- 该变量必须定义为 volatile,或者所有对它的访问必须同步(synchronized)。 + +### 通过 Thread.interrupt 方法中断线程 + +- 该方法实际上只是设置了一个中断状态 + + - 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。 + - 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。这时候处理方法一样,只是捕获的异常不一样而已。 + +- Thread.currentThread().isInterrupted() + +## Happen-Before 八大原则 + +### 单线程 happen-before 原则 + +- 在同一个线程中,书写在前面的操作 happen-before 后面的操作。 + +### 锁的 happen-before 原则 + +- 同一个锁的 unlock 操作 happen-before 此锁的 lock 操作。 + +### volatile 的 happen-before 原则 + +- 对一个 volatile 变量的写操作 happen-before对此变量的任意操作(也包括写操作)。 + +### happen-before 的传递性原则 + +- 如果 A 操作 happen-before B 操作,B 操作happen-before C 操作,那么 A 操作 happen-before C 操作。 + +### 线程启动的 happen-before 原则 + +- 同一个线程的 start 方法 happen-before 此线程的其它方法。 + +### 线程中断的 happen-before 原则 + +- 对线程 interrupt 方法的调用 happen-before 被中断线程的检测到中断发送的代码。 + +### 线程终结的 happen-before 原则 + +- 线程中的所有操作都 happen-before 线程的终止检测。 + +### 对象创建的 happen-before 原则 + +- 一个对象的初始化完成先于他的 finalize 方法调用。 + diff --git "a/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \347\232\204\347\272\277\347\250\213\345\256\211\345\205\250.xmind" "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \347\232\204\347\272\277\347\250\213\345\256\211\345\205\250.xmind" new file mode 100644 index 0000000..ca3e06c Binary files /dev/null and "b/Java\350\277\233\351\230\266/\345\271\266\345\217\221\347\274\226\347\250\213\350\204\221\345\233\276/Java \347\232\204\347\272\277\347\250\213\345\256\211\345\205\250.xmind" differ diff --git "a/Java\350\277\233\351\230\266/\347\240\201\345\207\272\351\253\230\346\225\210 &Java \345\274\200\345\217\221\346\211\213\345\206\214\346\200\235\347\273\264\345\257\274\345\233\276/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.xmind" "b/Java\350\277\233\351\230\266/\347\240\201\345\207\272\351\253\230\346\225\210 &Java \345\274\200\345\217\221\346\211\213\345\206\214\346\200\235\347\273\264\345\257\274\345\233\276/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.xmind" deleted file mode 100644 index 90b26aa..0000000 Binary files "a/Java\350\277\233\351\230\266/\347\240\201\345\207\272\351\253\230\346\225\210 &Java \345\274\200\345\217\221\346\211\213\345\206\214\346\200\235\347\273\264\345\257\274\345\233\276/\343\200\212\347\240\201\345\207\272\351\253\230\346\225\210\343\200\213\346\200\235\347\273\264\345\257\274\345\233\276.xmind" and /dev/null differ diff --git a/Linux/CentOS.md b/Linux/CentOS.md new file mode 100644 index 0000000..b431bc8 --- /dev/null +++ b/Linux/CentOS.md @@ -0,0 +1,135 @@ +# CentOS + +## 目录简介 + +### bin(binary) + +- 放置一些系统必备的执行档,普通用户和 root 用户都可以执行 + + - cat 连接文件或标准输入并打印,常用来显示文件内容 + - cp 复制文件或者目录 + - chmod 改变 linux 系统文件或目录的访问权限 + - df 显示指定磁盘文件的可用空间 + - dmesg 检查和控制内核的环形缓冲区 + - gzip 对文件进行压缩和解压缩 + - kill 终止指定的进程的运行 + - ls(list)打印出当前目录的清单 + - mkdir 创建指定的名称的目录 + - more 类似 cat ,cat 命令是整个文件的内容从上到下显示在屏幕上。 more 会以一页一页的显示方便使用者逐页阅读 + - mount 将分区挂接到 Linux 的一个文件夹下,从而将分区和该目录联系起来 + - rm 删除一个目录中的一个或多个文件或目录 + - su 切换当前用户身份到其他用户身份 + - tar 为 linux 的文件和目录创建档案 + +### sbin + +- 放置一些系统管理的必备程式,只有root用户可以执行 + + - cfdisk + - dhcpcd + - dump + - e2fsck + - fdisk + - halt + - ifconfig + - ifup + - ifdown + - init + - insmod + - lilo + - lsmod + - mke2fs + - modprobe + - quotacheck + - reboot + - rmmod + - runlevel + - shutdown + +### boot + +- 启动目录,存的是启动相关的文件,该目录下不要乱存东西 + +### dev + +- 设备文件保存目录 + +### etc + +- 配置文件保存目录 + +### home + +- 普通用户的家目录 + +### lib + +- 系统库保存目录 + +### lib64 + +- X86_64 的 Linux 系统, 就会有 /usr/lib64/ 目录产生 + +### media + +- 挂载目录 + +### mnt + +- 暂时挂载某些额外的装置 + +### opt + +- 第三方协力软体放置的目录 + +### proc + +- 直接写入内存的,虚拟文件系统 + +### root + +- 超级用户的家目录 + +### run + +- 某些程序或者是服务启动后,会将他们的PID放置在这个目录下 + +### srv(service)一些网路服务启动之后,这些服务所需要取用的资料目录 + +### sys + +- 直接写入内存的,虚拟文件系统 + +### tmp + +- 临时目录 + +### usr + +- Unix 操作系统软件资源所放置的目录 + + - /usr/bin + + - 放置一些应用软体工具的必备执行档 + + - c++、g++、gcc、chdrv + - diff、dig、du、eject + - elm、free、gnome*、 gzip + - htpasswd、kfm、ktop、last + - less、locale、m4、make + - man、mcopy、ncftp、 newaliases + - nslookup passwd、quota、smb*、wget + + - /usr/sbin + + - 放置一些网路管理的必备程式 + + - dhcpd、httpd、imap、in.*d + - inetd、lpd、named、netconfig + - nmbd、samba、sendmail、squid + - swap、tcpd、tcpdump + +### var + +- 系统相关文档内容 + diff --git a/Linux/CentOS.xmind b/Linux/CentOS.xmind index b8a0640..3644090 100644 Binary files a/Linux/CentOS.xmind and b/Linux/CentOS.xmind differ diff --git a/Linux/Shell.md b/Linux/Shell.md new file mode 100644 index 0000000..23b630f --- /dev/null +++ b/Linux/Shell.md @@ -0,0 +1,10 @@ +# Shell + +## 命令杂记 + +### 通过 Nginx access 日志实时统计单台机器 QPS + +- tail -f access.log | awk -F '[' '{print $2}' | awk 'BEGIN{key="";count=0}{if(key==$1){count++}else{printf("%s\t%d\r\n", key, count);count=1;key=$1}}' +- tail -f access.log | awk -F '[' '{print $2}' | awk '{print $1}' | uniq -c +- cat access.log | awk -F '[' '{print $2}' | awk '{print $1}' | sort | uniq -c |sort -k1,1nr + diff --git a/Linux/Shell.xmind b/Linux/Shell.xmind new file mode 100644 index 0000000..7675ee1 Binary files /dev/null and b/Linux/Shell.xmind differ diff --git a/ORM/Hibernate.md b/ORM/Hibernate.md new file mode 100644 index 0000000..ea1a2e7 --- /dev/null +++ b/ORM/Hibernate.md @@ -0,0 +1,16 @@ +# Hibernate + +## 注解汇总 + +### @MappedSuperclass + +- 基于代码复用和模型分离的思想,在项目开发中使用 JPA 的 @MappedSuperclass 注解将实体类的多个属性分别封装到不同的非实体类中。 +- 只能标注在类上 +- 标注为 @MappedSuperclass 的类将不是一个完整的实体类。 + + - 不会映射到数据库表,但是它的属性都将映射到其子类的数据库表字段中 + +- 标注为 @MappedSuperclass 的类不能再标注 @Entity 或 @Table 注解,也无需实现序列化接口。 + + - 如果一个标注为 @MappedSuperclass 的类继承了另外一个实体类或者另外一个同样标注了 @MappedSuperclass 的类的话,他将可以使用 @AttributeOverride 或 @AttributeOverrides 注解重定义其父类(无论是否是实体类)的属性映射到数据库表中的字段。 + diff --git a/ORM/Hibernate.xmind b/ORM/Hibernate.xmind new file mode 100644 index 0000000..947bc4b Binary files /dev/null and b/ORM/Hibernate.xmind differ diff --git a/ORM/MyBatis.md b/ORM/MyBatis.md new file mode 100644 index 0000000..0a09c96 --- /dev/null +++ b/ORM/MyBatis.md @@ -0,0 +1,243 @@ +# MyBatis + +## MyBatis 快速入门 + +### ORM(Object Relational Mapping,对象-关系映射) 简介 + +- JDBC 查询操作步骤 + + - 注册数据库驱动类,明确指定数据库 URL 地址、数据库用户名、密码等连接信息。 + - 通过 DriverManager 打开数据库连接 + - 通过数据库连接创建 Statement 对象。 + - 通过 Statement 对象执行 SQL 语句,得到 ResultSet 对象。 + - 通过 ResultSet 读取数据,并将数据转换成 JavaBean 对象。 + - 关闭 ResultSet、Statement 对象以及数据库连接,释放相关资源。 + +### 常见持久化框架 + +- Hibernate +- JPA(Java Persistence API) +- Spring JDBC +- MyBatis + +### MyBatis 整体架构 + +- 基础支持层 + + - 数据源模块 + + - 连接池 + - 监控等 + + - 事务管理模块 + + - 多半与 Spring 集成,由 Spring 进行管理 + + - 缓存模块 + + - 一级缓存 + - 二级缓存 + + - Binding 模块 + + - 将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免映射文件中定义的 SQL 节点拼写错误等问题。 + - 无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。 + + - 反射模块 + + - 对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。 + + - 类型转换 + + - 别名 + - 实现 JDBC 类型与 Java 类型之间的转换 + + - 日志模块 + + - 提供详细的日志输出信息,还能够集成多种日志框架。 + + - 资源加载 + + - 对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。 + + - 解析器模块 + + - 对 XPath 进行封装,为 MyBatis 初始化时解析 mybatis-config.xml 配置文件以及映射配置文件提供支持 + - 处理动态 SQL 语句中的占位符提供支持 + +- 核心处理层 + + - 配置解析 + + - 在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。 待 MyBatis 初始化之后,开发人员可以通过初始化得到 SqlSessionFactory 创建的 SqlSession 对象并完成数据库操作。 + + - 参数映射 + + - scripting 模块 + + - 根据用户传入的实参,解析映射文件中定义的动态 SQL 节点,并形成数据库可执行的 SQL 语句。然后处理 SQL 语句中的占位符,绑定用户传入的实参。 + + - SQL 解析 + + - 动态 SQL 语句,提供多种动态 SQL 语句对应的节点,等 + + - SQL 执行 + + - Executor + + - 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,会将数据库相关操作委托给 StatementHandler 完成 + + - StatementHandler + + - 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过 java.sql.Statement 对象执行 SQL 语句并得到结果集 + + - ParameterHandler + - ResultHandler + + - 完成结果集的映射,得到结果对象并返回 + + - 结果集映射 + - 插件 + +- 接口层 + + - SqlSession + +## 基础支持层 + +### 解析器模块 + +### 反射工具箱 + +### 类型转换 + +### 日志模块 + +### 资源加载 + +### DataSource + +### Transaction + +### binding 模块 + +### 缓存模块 + +- 装饰器模式 +- Cache 接口及其实现 + + - 方法声明 + + - getId + + - 缓存对象的 ID + + - putObject + + - 向缓存中添加数据,一般情况下,key 是 CacheKey,value 是查询结果 + + - getObject + + - 根据指定的 key,在缓存中查找对应的结果对象 + + - removeObject + + - 删除 key 对应的缓存项 + + - clear + + - 清空缓存 + + - getSize + + - 缓存项的个数,该方法不会被 MyBatis 核心代码使用,所以可提供空实现 + + - getReadWriteLock + + - 获取读写锁,该方法不会被 MyBatis 核心代码使用,所以可提供空实现 + + - PerpetualCache + + - Cache 接口的基本实现 + - 底层使用 HashMap 记录缓存项,也是通过该 hashMap 对象的方法实现的 Cache 接口中定义的相应方法 + + - 装饰类 + + - BlockingCache + + - 阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据 + + - FifoCache + + - 在很多场景中,为了控制缓存的大小,系统需要按照一定的规则清理缓存。 + - FifoCache 是先入先出的装饰器,当向缓存添加数据时,如果缓存项的个数己经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。 + + - LruCache + + - 按照近期最少使用算法进行缓存清理的装饰器,在需要清理缓存时,它会清除最近最少使用的缓存项。 + - 底层使用 LinkedHashMap + + - SoftCache & WeakCache + + - 在 SoftCache 中,最近使用的一部分缓存项不会被 GC 回收,这就是通过将其 value 添加到 hardLinksToAvoidGarbageCollection 集合中实现的(即有强引用指向其 value) + - hardLinksToAvoidGarbageCollection 集合是 LinkedList 类型 + - WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry 封装真正的 value 对象 + + - ScheduledCache + + - 周期性清理缓存的装饰器 + - clearInterval 属性记录两次缓存清理之间的时间间隔,默认是一小时 + - lastClear 属性记录最近一次清理的时间戳 + + - LoggingCache + + - 在 Cache 的基础上提供了日志功能 + - requests 属性记录了缓存的缓存的访问次数 + - hits 属性记录了缓存的命中次数 + + - SynchronizedCache + + - 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能 + + - SerializedCache + + - 提供将 value 对象序列化的功能 + - 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,并将序列化后的 byte 数组作为 value 存入缓存 + - 在获取缓存项时,会将缓存项中的 byte 数组反序列化成 Java 对象。 + - 使用其他装饰器实现进行装饰之后,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程以及缓存中的对象; + +而 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 + +- CacheKey + + - 表示缓存项的 key + + - 在 Cache 中唯一确定一个缓存项需要使用缓存项的 key, Mybatis 中因为涉及动态 SOL 等多方面因素,其缓存项的 key 不能仅仅通过一个 String 表示,所以 Mybatis 提供了 CacheKey 类来表示缓存项的 key,在一个 CacheKey 对象中可以封装多个影响缓存项的因素。 + - CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象是否相同 + + - 重要属性 + + - multiplier + + - 参与计算 hashcode,默认值是 37 + + - hashcode + + - CacheKey 对象的 hashcode,起始值是 17 + + - checksum + + - 校验和 + + - count + + - updateList 集合的个数 + + - updateList + + - 由该集合中的所有对象共同决定两个 CacheKey 是否相同 + +## 高级主题 + +## 核心处理层 + diff --git a/ORM/MyBatis.xmind b/ORM/MyBatis.xmind index b1ebfc5..2404156 100644 Binary files a/ORM/MyBatis.xmind and b/ORM/MyBatis.xmind differ diff --git "a/ORM/Mybatis Plus \345\205\245\351\227\250\346\226\207\346\241\243\347\237\245\350\257\206\346\242\263\347\220\206.png" "b/ORM/Mybatis Plus \345\205\245\351\227\250\346\226\207\346\241\243\347\237\245\350\257\206\346\242\263\347\220\206.png" new file mode 100644 index 0000000..a23432f Binary files /dev/null and "b/ORM/Mybatis Plus \345\205\245\351\227\250\346\226\207\346\241\243\347\237\245\350\257\206\346\242\263\347\220\206.png" differ diff --git "a/ORM/Mybatis Plus \345\205\245\351\227\250\346\226\207\346\241\243\347\237\245\350\257\206\346\242\263\347\220\206.txt" "b/ORM/Mybatis Plus \345\205\245\351\227\250\346\226\207\346\241\243\347\237\245\350\257\206\346\242\263\347\220\206.txt" new file mode 100644 index 0000000..16abcea --- /dev/null +++ "b/ORM/Mybatis Plus \345\205\245\351\227\250\346\226\207\346\241\243\347\237\245\350\257\206\346\242\263\347\220\206.txt" @@ -0,0 +1,531 @@ +链接:https://app.gitmind.cn/doc/dcc56428 +官方文档地址:https://mp.baomidou.com/ +MyBatis-Plus + 快速入门 + 简介 + MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 + 特性 + 无侵入 + 损耗小 + 强大的 CRUD 操作 + 支持 Lambda 形式调用 + 支持主键自动生成 + 支持 ActiveRecord 模式 + 支持自定义全局通用操作 + 内置代码生成器 + 内置分页插件 + 分页插件支持多种数据库 + 内置性能分析插件 + 内置全局拦截插件 + 支持数据库 + mysql 、 mariadb 、 oracle 、 db2 、 h2 、 hsql 、 sqlite 、 postgresql 、 sqlserver + 快速开始 + 安装 + 配置 + 注解 + @TableName(表名注解) + value + 表名 + schema + keepGlobalPrefix + 是否保持使用全局的 tablePrefix 的值 + resultMap + xml 中 resultMap 的 id + autoResultMap + 是否自动构建 resultMap 并使用 + @TableId(主键注解) + value(主键字段名) + type(主键类型) + IdType + AUTO,数据库自增 + INPUT,自行输入 + ID_WORKER,分布式全局唯一ID 长整型类型 + UUID,32位UUID字符串 + NONE,无状态 + ID_WORKER_STR,分布式全局唯一ID 字符串类型 + @TableField(非主键字段注解) + value,字段名 + el,映射为原生 #{ ... } 逻辑 + exist,是否为数据库表字段 + condition,字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s} + update,字段 update set 部分注入, 例如:update="%s+1":表示更新时会set version=version+1(该属性优先级高于 el 属性) + strategy + insertStrategy + updateStrategy + whereStrategy + FieldStrategy + IGNORED,忽略判断 + NOT_NULL,非NULL判断 + NOT_EMPTY,非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断) + DEFAULT,追随全局配置 + fill,字段自动填充策略 + FieldFill + DEFAULT,默认不处理 + INSERT,插入时填充字段 + UPDATE,更新时填充字段 + INSERT_UPDATE,插入和更新时填充字段 + select,是否进行 select 查询 + keepGlobalFormat,是否保持使用全局的 format 进行处理 + @Version + 乐观锁注解、标记 @Verison 在字段上 + @EnumValue + 通枚举类注解(注解在枚举字段上) + @TableLogic,表字段逻辑处理注解(逻辑删除) + value,逻辑未删除值 + delval,逻辑删除值 + @SqlParser,租户注解 + filter + true: 表示过滤SQL解析,即不会进入ISqlParser解析链 + @KeySequence + 序列主键策略 oracle + value,序列名 + clazz,id 的类型 + 核心功能 + 代码生成器 + CRUD 接口 + Service CRUD 接口 + 说明 + 通用 Service CRUD 封装 IService 接口,进一步封装 CRUD 采用 get 查询单行,remove 删除,list 查询集合,page 分页,前缀命名方式区分 Mapper 层避免混淆。 + 泛型 T 为任意实体对象 + 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类 + 对象 Wrapper 为条件构造器 + Save + boolean save(T entity); + 插入一条记录(选择字段,策略插入) + boolean saveBatch(Collection entityList); + 插入(批量) + boolean saveBatch(Collection entityList, int batchSize); + 插入(批量,带插入批次数量) + SaveOrUpdate + boolean saveOrUpdate(T entity); + 主键存在即更新记录,否插入一条记录 + boolean saveOrUpdate(T entity, Wrapper updateWrapper); + 根据 updateWrapper 尝试更新,否继续执行 saveOrUpdate(T) 方法 + boolean saveOrUpdateBatch(Collection entityList); + 批量修改插入 + boolean saveOrUpdateBatch(Collection entityList, int batchSize); + 批量修改插入(带修改插入批次数量) + Remove + boolean remove(Wrapper queryWrapper); + 根据 entity 条件,删除记录 + boolean removeById(Serializable id); + 根据 ID 删除 + boolean removeByMap(Map columnMap); + 根据 columnMap 条件,删除记录 + boolean removeByIds(Collection idList); + 删除(根据ID 批量删除) + Update + boolean update(Wrapper updateWrapper); + 根据 UpdateWrapper 条件,更新记录,需要设置 sqlset + boolean update(T entity, Wrapper updateWrapper); + 根据 whereEntity 条件,更新记录 + boolean updateById(T entity); + 根据 ID 选择修改 + boolean updateBatchById(Collection entityList); + 根据ID 批量更新 + boolean updateBatchById(Collection entityList, int batchSize); + 根据ID 批量更新(带删除批次数量) + Get + T getById(Serializable id); + 根据 ID 查询 + T getOne(Wrapper queryWrapper); + 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1") + T getOne(Wrapper queryWrapper, boolean throwEx); + 根据 Wrapper,查询一条记录 + Map getMap(Wrapper queryWrapper); + 根据 Wrapper,查询一条记录 + V getObj(Wrapper queryWrapper, Function mapper); + 根据 Wrapper,查询一条记录 + List + List list(); + 查询所有 + List list(Wrapper queryWrapper); + 查询列表 + Collection listByIds(Collection idList); + 查询(根据ID 批量查询) + Collection listByMap(Map columnMap); + 查询(根据 columnMap 条件) + List> listMaps(); + 查询所有列表 + List> listMaps(Wrapper queryWrapper); + 查询列表 + List listObjs(); + 查询全部记录 + List listObjs(Function mapper); + 查询全部记录 + List listObjs(Wrapper queryWrapper); + 根据 Wrapper 条件,查询全部记录 + List listObjs(Wrapper queryWrapper, Function mapper); + 根据 Wrapper 条件,查询全部记录 + Page + IPage page(IPage page); + 无条件翻页查询 + IPage page(IPage page, Wrapper queryWrapper); + 翻页查询 + IPage> pageMaps(IPage page); + 无条件翻页查询 + IPage> pageMaps(IPage page, Wrapper queryWrapper); + 翻页查询 + Count + int count(); + 查询总记录数 + int count(Wrapper queryWrapper); + 根据 Wrapper 条件,查询总记录数 + Chain + QueryChainWrapper query(); + 链式查询 普通 + LambdaQueryChainWrapper lambdaQuery(); + 链式查询 lambda 式。注意:不支持 Kotlin + UpdateChainWrapper update(); + 链式更改 普通 + LambdaUpdateChainWrapper lambdaUpdate(); + 链式更改 lambda 式。注意:不支持 Kotlin + Mapper CRUD 接口 + 说明 + 通用 CRUD 封装 BaseMapper 接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器 + 泛型 T 为任意实体对象 + 参数 Serializable 为任意类型主键 Mybatis-Plus,不推荐使用复合主键约定每一张表都有自己的唯一 id 主键 + 对象 Wrapper 为条件构造器 + Insert + int insert(T entity); + 插入一条记录 + Delete + int delete(@Param(Constants.WRAPPER) Wrapper wrapper); + 根据 entity 条件,删除记录 + int deleteBatchIds(@Param(Constants.COLLECTION) Collection idList); + 删除(根据ID 批量删除) + int deleteById(Serializable id); + 根据 ID 删除 + int deleteByMap(@Param(Constants.COLUMN_MAP) Map columnMap); + 根据 columnMap 条件,删除记录 + Update + int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper updateWrapper); + 根据 whereEntity 条件,更新记录 + int updateById(@Param(Constants.ENTITY) T entity); + 根据 ID 修改 + Select + T selectById(Serializable id); + 根据 ID 查询 + T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 entity 条件,查询一条记录 + List selectBatchIds(@Param(Constants.COLLECTION) Collection idList); + 查询(根据ID 批量查询) + List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 entity 条件,查询全部记录 + List selectByMap(@Param(Constants.COLUMN_MAP) Map columnMap); + 查询(根据 columnMap 条件) + List> selectMaps(@Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 Wrapper 条件,查询全部记录 + List selectObjs(@Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值 + IPage selectPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 entity 条件,查询全部记录(并翻页) + IPage> selectMapsPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 Wrapper 条件,查询全部记录(并翻页) + Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper); + 根据 Wrapper 条件,查询总记录数 + mapper 层选装件 + 说明 + 选装件位于 com.baomidou.mybatisplus.extension.injector.methods.additional 包下,需要配合 Sql 注入器使用 + AlwaysUpdateSomeColumnById + int alwaysUpdateSomeColumnById(T entity); + insertBatchSomeColumn + int insertBatchSomeColumn(List entityList); + deleteByIdWithFill + int deleteByIdWithFill(T entity); + 条件构造器 + 说明 + 出现的第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中 + 代码块内的多个方法均为从上往下补全个别 boolean 类型的入参,默认为 true + 出现的泛型 Param 均为 Wrapper 的子类实例(均具有 AbstractWrapper 的所有方法) + 方法在入参中出现的 R 为泛型,在普通 wrapper 中是 String,在 LambdaWrapper 中是函数(例:Entity::getId,Entity 为实体类,getId 为字段 id 的 getMethod) + 方法入参中的 R column 均表示数据库字段,当 R 具体类型为 String 时则为数据库字段名而不是实体类数据字段名(字段名是数据库关键字的自己用转义符包裹),另当 R 具体类型为 SFunction 时项目 runtime 不支持 eclipse 自家的编译器。 + 如果入参的 Map 或者 List 为空,则不会加入最后生成的 sql 中。 + 警告 + 不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输 + wrapper 很重 + 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场) + 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作 + AbstractWrapper + QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类 + 用于生成 sql 的 where 条件,entity 属性也用于生成 sql 的 where 条件 + entity 生成的 where 条件与使用各个 api 生成的 where 条件没有任何关联行为 + allEq + allEq(Map params) + allEq(Map params, boolean null2IsNull) + allEq(boolean condition, Map params, boolean null2IsNull) + allEq(BiPredicate filter, Map params) + allEq(BiPredicate filter, Map params, boolean null2IsNull) + allEq(boolean condition, BiPredicate filter, Map params, boolean null2IsNull) + params:key 为数据库字段名,value 为字段值。 + null2IsNull:为 true 则在 map 的 value 为 null 时调用 isNull 方法,为 false 时则忽略 value 为 null 的情况。 + filter:过滤函数,是否允许字段传入比对条件中。 + eq + eq(R column, Object val) + eq(boolean condition, R column, Object val) + ne + ne(R column, Object val) + ne(boolean condition, R column, Object val) + gt + gt(R column, Object val) + gt(boolean condition, R column, Object val) + ge(大于等于) + ge(R column, Object val) + ge(boolean condition, R column, Object val) + lt + lt(R column, Object val) + lt(boolean condition, R column, Object val) + le(小于等于) + le(R column, Object val) + le(boolean condition, R column, Object val) + between + between(R column, Object val1, Object val2) + between(boolean condition, R column, Object val1, Object val2) + notBetween + notBetween(R column, Object val1, Object val2) + notBetween(boolean condition, R column, Object val1, Object val2) + like + like(R column, Object val) + like(boolean condition, R column, Object val) + notLike + notLike(R column, Object val) + notLike(boolean condition, R column, Object val) + likeLeft + likeLeft(R column, Object val) + likeLeft(boolean condition, R column, Object val) + likeRight + likeRight(R column, Object val) + likeRight(boolean condition, R column, Object val) + isNull + isNull(R column) + isNull(boolean condition, R column) + isNotNull + isNotNull(R column) + isNotNull(boolean condition, R column) + in + in(R column, Collection value) + in(boolean condition, R column, Collection value) + in(R column, Object... values) + in(boolean condition, R column, Object... values) + notIn + notIn(R column, Collection value) + notIn(boolean condition, R column, Collection value) + notIn(R column, Object... values) + notIn(boolean condition, R column, Object... values) + inSql + inSql(R column, String inValue) + inSql(boolean condition, R column, String inValue) + notInSql + notInSql(R column, String inValue) + notInSql(boolean condition, R column, String inValue) + groupBy + groupBy(R... columns) + groupBy(boolean condition, R... columns) + orderByAsc + orderByAsc(R... columns) + orderByAsc(boolean condition, R... columns) + orderByDesc + orderByDesc(R... columns) + orderByDesc(boolean condition, R... columns) + orderBy + orderBy(boolean condition, boolean isAsc, R... columns) + having + having(String sqlHaving, Object... params) + having(boolean condition, String sqlHaving, Object... params) + or + or() + or(boolean condition) + or(Consumer consumer) + or(boolean condition, Consumer consumer) + and + and(Consumer consumer) + and(boolean condition, Consumer consumer) + nested(嵌套) + nested(Consumer consumer) + nested(boolean condition, Consumer consumer) + apply(拼接 sql) + apply(String applySql, Object... params) + apply(boolean condition, String applySql, Object... params) + last(无视优化规则直接拼接到 sql 的最后) + last(String lastSql) + last(boolean condition, String lastSql) + 只能调用一次,多次调用以最后一次为准,有 sql 注入的风险,请谨慎使用 + exists + exists(String existsSql) + exists(boolean condition, String existsSql) + notExists + notExists(String notExistsSql) + notExists(boolean condition, String notExistsSql) + QueryWrapper + 继承自 AbstractWrapper,自身的内部属性 entity 也用于生成 where 条件 + LambdaQueryWrapper,可以通过 new QueryWrapper().lambda() 方法获取 + select + select(String... sqlSelect) + select(Predicate predicate) + select(Class entityClass, Predicate predicate) + excludeColumns + UpdateWrapper + 继承自 AbstractWrapper,自身的内部属性 entity 也用于生成 where 条件 + LambdaUpdateWrapper, 可以通过 new UpdateWrapper().lambda() 方法获取 + set + set(String column, Object val) + set(boolean condition, String column, Object val) + setSql + setSql(String sql) + lambda + 获取 LambdaWrapper + 在 QueryWrapper 中是获取 LambdaQueryWrapper + 在 UpdateWrapper 中是获取 LambdaUpdateWrapper + 使用 Wrapper 自定义SQL + 注解方式 Mapper.java + XML形式 Mapper.xml + 分页插件 + Sequence 主键 + 主键生成策略必须使用 INPUT + 支持父类定义 @KeySequence 子类继承使用 + 支持主键类型指定 + 内置支持 + DB2KeyGenerator + H2KeyGenerator + KingbaseKeyGenerator + OracleKeyGenerator + PostgreKeyGenerator + 如果内置支持不满足需求,可实现 IKeyGenerator 接口来进行扩展 + 自定义 ID 生成器 + 插件扩展 + 热加载 + 功能已经移除 + 多数据源配置多个 MybatisMapperRefresh 启动 bean + 默认情况下,eclipse 保存会自动编译,idea 需自己手动编译一次 + 逻辑删除 + 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。 + 如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。 + 通用枚举 + 声明通用枚举属性 + 使用 @EnumValue 注解枚举属性 + 枚举属性,实现 IEnum 接口 + 配置扫描通用枚举 + 如何序列化枚举值为数据库存储值? + Jackson + 重写 toString 方法 + 注解处理 @JsonValue + Fastjson + 重写 toString 方法 + 字段类型处理器 + 类型处理器,用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值 + 自动填充功能 + Sql 注入器 + 攻击 SQL 阻断解析器 + 阻止恶意的全表更新删除 + 性能分析插件 + 性能分析拦截器,用于输出每条 SQL 语句及其执行时间 + maxTime SQL 执行最大时长,超过自动停止运行,有助于发现问题。 + format SQL SQL是否格式化,默认false。 + 该插件只用于开发环境,不建议生产环境使用。 + 执行 SQL 分析打印 + 该功能依赖 p6spy 组件,完美的输出打印 SQL 及执行时长 + 乐观锁插件 + 当要更新一条记录的时候,希望这条记录没有被别人更新 + 乐观锁实现方式 + 取出记录时,获取当前 version + 更新时,带上这个 version + 执行更新时, set version = newVersion where version = oldVersion + 如果 version 不对,就更新失败 + 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime + 整数类型下 newVersion = oldVersion + 1 + newVersion 会回写到 entity 中 + 仅支持 updateById(id) 与 update(entity, wrapper) 方法 + 在 update(entity, wrapper) 方法下, wrapper 不能复用 + 动态数据源 + 分布式事务 + 暂时支持 rabbit 实现可靠消息分布式事务 + 多租户 SQL 解析器 + 动态表名 SQL 解析器 + 实现 ITableNameHandler 接口注入到 DynamicTableNameParser 处理器链中,将动态表名解析器注入到 MP 解析链 + 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换 + MybatisX 快速开发插件 + 常见问题 + 如何排除非表中字段? + 使用 transient 修饰 + 使用 static 修饰 + 使用 TableField 注解 + 排除实体父类属性 + 使用 transient 修饰 + 出现 Invalid bound statement (not found) 异常 + 检查是不是引入 jar 冲突 + 检查 Mapper.java 的扫描路径 + 在 Configuration 类上使用注解 MapperScan + 在Configuration类里面,配置MapperScannerConfigurer + 检查是否指定了主键? + qlSessionFactory 不要使用原生的,请使用 MybatisSqlSessionFactory + 检查是否自定义了 SqlInjector,是否复写了 getMethodList() 方法,该方法里是否注入了你需要的方法 + 自定义 SQL 无法执行 + 在 XML 中里面自定义 SQL,却无法调用 + 启动时异常 + java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class + MapperScan 需要排除 com.baomidou.mybatisplus.mapper.BaseMapper 类及其子类(自定义公共 Mapper) + Injection of autowired + 低版本不支持泛型注入,请升级 Spring 版本到 4+ 以上 + java.lang.NoSuchMethodError: org.apache.ibatis.session.Configuration.getDefaultScriptingLanguageInstance() Lorg/apache/ibatis/scripting/LanguageDriver + 版本引入问题:3.4.1 版本里没有,3.4.2 里面才有 + 关于 Long 型主键填充不生效的问题 + 检查是不是用了long而不是Long + ID_WORKER 生成主键太长导致 js 精度丢失 + JavaScript 无法处理 Java 的长整型 Long 导致精度丢失,具体表现为主键最后两位永远为 0 + FastJson 处理方式:配置序列化策略 + JackJson 处理方式 + 比较一般的处理方式:增加一个 public String getIdStr() 方法,前台获取 idStr + 插入或更新的字段 空字符串或者 null + FieldStrategy 的三种策略 + IGNORED:忽略 + NOT_NULL:非 NULL,默认策略 + NOT_EMPTY:非空 + 调整全局的验证策略 + 注入配置 GlobalConfiguration 属性 fieldStrategy + 调整字段验证注解 + 使用 UpdateWrapper + 字段类型为 bit、tinyint(1) 时映射为 boolean 类型 + 默认 mysql 驱动会把 tinyint(1) 字段映射为 boolean: 0=false, 非0=true + 修改类型 tinyint(1) 为 tinyint(2) 或者 int + 需要修改请求连接添加参数 tinyInt1isBit=false + 出现 2 个 limit 语句 + 配了 2 个分页拦截器,检查配置文件或者代码,只留一个 + insert 后如何返回主键 + getId() + MP 如何查指定的几个字段 + EntityWrapper.sqlSelect 配置要查询的字段 + mapper 层二级缓存问题 + 建议缓存放到 service 层,可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现。 + 使用 CachePaginationInterceptor 替换默认分页,这样支持分页缓存 + mapper 层二级缓存刷新问题 + 在代码中 mybatis 的 mapper 层添加缓存注释,声明 implementation 或 eviction 的值为 cache 接口的实现类 + 在对应的 mapper.xml 中将原有注释修改为链接式声明,以保证 xml 文件里的缓存能够正常 + Cause: org.apache.ibatis.type.TypeException:Error setting null for parameter #1 with JdbcType OTHER + 配置 jdbcTypeForNull=NULL Spring Bean + 自定义 sql 里使用 Page 对象传参无法获取 + Page 对象是继承 RowBounds,是 Mybatis 内置对象,无法在 mapper 里获取 + 使用自定义 Map/对象,或者通过@Param("page") int page,size 来传参 + Java Config Bean 配置 + 配置自定义的 SqlInjector + 配置自己的全局 baseMapper 并使用 + 如何使用:【Map下划线自动转驼峰】 + 在 wrapper 中如何使用 limit 限制 SQL + wrapper.last("limit 1"); + 通用 insertBatch 为什么放在 service 层处理 + SQL 长度有限制海量数据量单条 SQL 无法执行,就算可执行也容易引起内存泄露 JDBC 连接超时等 + 不同数据库对于单条 SQL 批量语法不一样不利于通用 + 目前的解决方案:循环预处理批量提交,虽然性能比单 SQL 慢但是可以解决以上问题。 + 逻辑删除下 自动填充 功能没有效果 + 自动填充的实现方式是填充到入参的 entity 内,由于 baseMapper 提供的删除接口入参不是 entity 所以逻辑删除无效 + 使用 update 方法:UpdateWrapper.set("logicDeleteColumn","deleteValue") + 配合Sql注入器 + 3.x数据库关键字如何处理? + 不同的数据库对关键字的处理不同,很难维护。 + 在数据库设计时候本身不建议使用关键字。 + 交由用户去处理关键字。 + MybatisPlusException: Your property named "xxx" cannot find the corresponding database column name! + 单元测试没问题,启动服务器进行调试就出现这个问题 + 去掉dev-tools插件 + Error attempting to get column 'create_time' from result set. Cause: java.sql.SQLFeatureNotSupportedException + mp版本从3.1.0及以下版本升级到高版本,JDK8日期新类型LocalDateTime等无法映射(报错) + Failed to bind properties under 'mybatis-plus.configuration.incomplete-result-maps[0].assistant.configuration.mapped-statements[0].parameter-map.parameter-mappings[0]' to org.apache.ibatis.mapping.ParameterMapping diff --git a/ORM/README.md b/ORM/README.md new file mode 100644 index 0000000..203328a --- /dev/null +++ b/ORM/README.md @@ -0,0 +1,393 @@ +# MyBatis + +## MyBatis 快速入门 + +### ORM(Object Relational Mapping,对象-关系映射) 简介 + +- JDBC 查询操作步骤 + + - 注册数据库驱动类,明确指定数据库 URL 地址、数据库用户名、密码等连接信息。 + - 通过 DriverManager 打开数据库连接 + - 通过数据库连接创建 Statement 对象。 + - 通过 Statement 对象执行 SQL 语句,得到 ResultSet 对象。 + - 通过 ResultSet 读取数据,并将数据转换成 JavaBean 对象。 + - 关闭 ResultSet、Statement 对象以及数据库连接,释放相关资源。 + +### 常见持久化框架 + +- Hibernate +- JPA(Java Persistence API) +- Spring JDBC +- MyBatis + +### MyBatis 整体架构 + +- 基础支持层 + + - 数据源模块 + + - 连接池 + - 监控等 + + - 事务管理模块 + + - 多半与 Spring 集成,由 Spring 进行管理 + + - 缓存模块 + + - 一级缓存 + - 二级缓存 + + - Binding 模块 + + - 将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免映射文件中定义的 SQL 节点拼写错误等问题。 + - 无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。 + + - 反射模块 + + - 对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。 + + - 类型转换 + + - 别名 + - 实现 JDBC 类型与 Java 类型之间的转换 + + - 日志模块 + + - 提供详细的日志输出信息,还能够集成多种日志框架。 + + - 资源加载 + + - 对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。 + + - 解析器模块 + + - 对 XPath 进行封装,为 MyBatis 初始化时解析 mybatis-config.xml 配置文件以及映射配置文件提供支持 + - 处理动态 SQL 语句中的占位符提供支持 + +- 核心处理层 + + - 配置解析 + + - 在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。 待 MyBatis 初始化之后,开发人员可以通过初始化得到 SqlSessionFactory 创建的 SqlSession 对象并完成数据库操作。 + + - 参数映射 + + - scripting 模块 + + - 根据用户传入的实参,解析映射文件中定义的动态 SQL 节点,并形成数据库可执行的 SQL 语句。然后处理 SQL 语句中的占位符,绑定用户传入的实参。 + + - SQL 解析 + + - 动态 SQL 语句,提供多种动态 SQL 语句对应的节点,等 + + - SQL 执行 + + - Executor + + - 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,会将数据库相关操作委托给 StatementHandler 完成 + + - StatementHandler + + - 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过 java.sql.Statement 对象执行 SQL 语句并得到结果集 + + - ParameterHandler + - ResultHandler + + - 完成结果集的映射,得到结果对象并返回 + + - 结果集映射 + - 插件 + +- 接口层 + + - SqlSession + +## 基础支持层 + +### 解析器模块 + +### 反射工具箱 + +### 类型转换 + +### 日志模块(org.apache.ibatis.logging) + +- 适配器模式 + + http://www.glorze.com/699.html + + + - 参考当前项目的《设计模式.xmind》 + +- 日志适配器 + + - Log + + - 定义了日志模块的功能 + + - LogFactory + + - 负责创建对应的日志组件适配器 + + - 总体逻辑 + + - LogFactory 类加载时会执行其静态代码块,其逻辑是按序加载并实例化对应日志组件的适配器,然后使用 LogFactory.logConstructor 这个静态字段,记录当前使用的第三方日志组件的适配器。 + +- 代理模式与 JDK 动态代理 + + http://www.glorze.com/1713.html + + http://www.glorze.com/1193.html + + + - 代理模式参考当前项目的《设计模式.xmind》 + - JDK 动态代理的总体逻辑 + + - JDK 动态代理的实现原理是动态创建代理类并通过指定类加载器加载,然后在创建代理对象时将 InvokerHandler 对象作为构造参数传入。

 + - 当调用代理对象是,会调用 InvokerHandler.invoke() 方法,并最终调用真正业务对象的相应方法。 + +- JDBC 调试 + +### 资源加载 + +### DataSource + +- 工厂方法模式 + + - MyBatis 使用不同的 DataSourceFactory 接口实现创建不同类型的 DataSource + + - 连接池数据资源 + - 非连接池数据资源 + +- DataSourceFactory(抽象工厂) + + - PooledDataSourceFactory + - UnpooledDataSourceFactory + +- DataSource(抽象产品) + + - PooledDataSource + - UnpooledDataSource + +### Transaction + +### binding 模块 + +### 缓存模块 + +- 装饰器模式 +- Cache 接口及其实现 + + - 方法声明 + + - getId + + - 缓存对象的 ID + + - putObject + + - 向缓存中添加数据,一般情况下,key 是 CacheKey,value 是查询结果 + + - getObject + + - 根据指定的 key,在缓存中查找对应的结果对象 + + - removeObject + + - 删除 key 对应的缓存项 + + - clear + + - 清空缓存 + + - getSize + + - 缓存项的个数,该方法不会被 MyBatis 核心代码使用,所以可提供空实现 + + - getReadWriteLock + + - 获取读写锁,该方法不会被 MyBatis 核心代码使用,所以可提供空实现 + + - PerpetualCache + + - Cache 接口的基本实现 + - 底层使用 HashMap 记录缓存项,也是通过该 hashMap 对象的方法实现的 Cache 接口中定义的相应方法 + + - 装饰类 + + - BlockingCache + + - 阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据 + + - FifoCache + + - 在很多场景中,为了控制缓存的大小,系统需要按照一定的规则清理缓存。 + - FifoCache 是先入先出的装饰器,当向缓存添加数据时,如果缓存项的个数己经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。 + + - LruCache + + - 按照近期最少使用算法进行缓存清理的装饰器,在需要清理缓存时,它会清除最近最少使用的缓存项。 + - 底层使用 LinkedHashMap + + - SoftCache & WeakCache + + - 在 SoftCache 中,最近使用的一部分缓存项不会被 GC 回收,这就是通过将其 value 添加到 hardLinksToAvoidGarbageCollection 集合中实现的(即有强引用指向其 value) + - hardLinksToAvoidGarbageCollection 集合是 LinkedList 类型 + - WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry 封装真正的 value 对象 + + - ScheduledCache + + - 周期性清理缓存的装饰器 + - clearInterval 属性记录两次缓存清理之间的时间间隔,默认是一小时 + - lastClear 属性记录最近一次清理的时间戳 + + - LoggingCache + + - 在 Cache 的基础上提供了日志功能 + - requests 属性记录了缓存的缓存的访问次数 + - hits 属性记录了缓存的命中次数 + + - SynchronizedCache + + - 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能 + + - SerializedCache + + - 提供将 value 对象序列化的功能 + - 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,并将序列化后的 byte 数组作为 value 存入缓存 + - 在获取缓存项时,会将缓存项中的 byte 数组反序列化成 Java 对象。 + - 使用其他装饰器实现进行装饰之后,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程以及缓存中的对象; + +而 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 + +- CacheKey + + - 表示缓存项的 key + + - 在 Cache 中唯一确定一个缓存项需要使用缓存项的 key, Mybatis 中因为涉及动态 SOL 等多方面因素,其缓存项的 key 不能仅仅通过一个 String 表示,所以 Mybatis 提供了 CacheKey 类来表示缓存项的 key,在一个 CacheKey 对象中可以封装多个影响缓存项的因素。 + - CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象是否相同 + + - 重要属性 + + - multiplier + + - 参与计算 hashcode,默认值是 37 + + - hashcode + + - CacheKey 对象的 hashcode,起始值是 17 + + - checksum + + - 校验和 + + - count + + - updateList 集合的个数 + + - updateList + + - 由该集合中的所有对象共同决定两个 CacheKey 是否相同 + +## 高级主题 + +## 核心处理层 + +### MyBatis 初始化 + +- MyBatis 中的配置文件 + + - mybatis-config.xml + - 映射配置文件 + +- 初始化主要工作 + + - 加载并解析 mybatis-config.xml 配置文件 + - 映射配置文件以及相关的注解信息 + +- 建造者模式 +- BaseBuilder(建造者接口) + + - Configuration configuration + + - Configuration 是 MyBatis 初始化过程的核心对象,MyBatis 中几乎全部的配置信息会保存到 Configuration 对象中 + - Configuration 对象是在 MyBatis 初始化过程中创建且是全局唯一的,也叫「All-In-One」 配置对象 + + - TypeAliasRegistry typeAliasRegistry + + - 在 mybatis-config.xml 配置文件中可以使用 标签定义别名,这些定义的别名都会记录在该 TypeAliasRegistry 对象中 + + - TypeHandlerRegistry typeHandlerRegistry + + - 在 mybatis-config.xml 在配置文件中可以使用 标签添加自定义 TypeHandler 处理器,完成指定数据库类型与 Java 类型的转换这些 TypeHandler 都会记录在 TypeHandlerRegistry 中。 + +- XMLConfigBuilder + + - BaseBuilder 的一个具体建造者,主要负责解析 mybatis-config.xml 配置文件 + - 主要变量 + + - boolean parsed + + - 标识是否已经解析过 mybatis-config.xml 配置文件 + + - XPathParser parser + + - 用于解析 mybatis-config.xml 配置文件的 XPathParser 对象 + + - String environment + + - 标识 配置的名称,默认读取 标签的 default 属性 + + - ReflectorFactory localReflectorFactory + + - ReflectorFactory 负责创建和缓存 Reflector 对象 + + - 解析节点 + + - properties 节点 + - settings 节点 + - typeAliases、typeHandlers 节点 + - plugins 节点 + - objectFactory 节点 + - environments 节点 + - databaseIdProvider 节点 + - mappers 节点 + +- XMLMapperBuilder + + - 负责解析映射配置文件,也是继承 BaseBuilder 抽象类,具体建造者的角色 + - 解析 节点 + - 解析 节点 + - 解析 节点 + - 解析 节点 + - 解析 节点 + +- XMLStatementBuilder + + - 解析主要用于定义 SQL 语句的 节点 + - 解析 节点 + - 解析 节点 + - 解析 SQL 节点 + +- 绑定 Mapper 接口 + + - 每个映射配置文件的命名空间可以绑定一个 Mapper 接口,并且注册到 mapperRegistry 中。 + +- 处理 incomplete*集合 + + - XMLMapperBuilder.configurationElement 方法解析映射配置文件时,是按照从文件头到文件尾的顺序解析的,但是有时候在解析一个节点时,会引用定义在该节点之后的、还未解析的节点,这就会导致解析失败并抛出 IncompleteElementException。 + - 根据抛出异常的节点不同,MyBatis 会创建不同的 *Resolver 对象,并添加到 Configuration 的不同 incomplete* 集合中。 + +### SqlNode&SqlSource + +### ResultSethandler + +### KeyGenerator + +### StatementHandler + +### Executor + +### 接口层 + diff --git a/README.md b/README.md index 3790dbc..e5cfa9e 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,29 @@ -# Java-Brain-Map -Java 知识体系涉及到的各方面知识点脑图、思维导图总结。 - -目前大分类列表: - -- Java 基础 -- Java 进阶 -- JVM -- Linux -- ORM -- Spring Framework -- 其他分类 -- 设计模式 -- 数据库 - -目前 Java 进阶里面前期主要分析 Java 并发编程的知识,这里的知识点比较重要,也是面试和工作中的重中之重,老四也是之前没有好好学习,这次所以比较重视,希望和你一起学习,共同进步。 - -根据个人的总结进度持续更新,如果对你有帮助就算我功德无量了。可以提建议,也可以 fork 一起完善,共同进步。 - -官网:高老四博客 +# Java-Brain-Map + +Java 知识体系涉及到的各方面知识点脑图、思维导图总结。 + +根据个人的总结进度持续更新,如果对你有帮助就算我功德无量了。可以提建议,也可以 fork 一起完善、学习,共同进步。 + +# 如何搜索 + +可以使用 sourcegraph.com 进行 Markdown 文本搜索。 + +# 知识目录分类 + +- Java 基础 +- Java 进阶 +- JVM +- Linux +- ORM +- Spring Framework +- web +- 其他分类 +- 设计模式 +- 数据结构 +- 数据库 +- 分布式 + +# 联系 + +- 高老四导航 + diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/01 - \350\216\267\345\217\226 Bean.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/01 - \350\216\267\345\217\226 Bean.jpg" new file mode 100644 index 0000000..c2e47e4 Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/01 - \350\216\267\345\217\226 Bean.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/02 - \345\210\233\345\273\272 Bean \344\271\213\345\211\215.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/02 - \345\210\233\345\273\272 Bean \344\271\213\345\211\215.jpg" new file mode 100644 index 0000000..3bf2e0e Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/02 - \345\210\233\345\273\272 Bean \344\271\213\345\211\215.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/03 - \347\234\237\346\255\243\347\232\204\345\210\233\345\273\272 Bean - doCreateBean.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/03 - \347\234\237\346\255\243\347\232\204\345\210\233\345\273\272 Bean - doCreateBean.jpg" new file mode 100644 index 0000000..6bbe5b1 Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/03 - \347\234\237\346\255\243\347\232\204\345\210\233\345\273\272 Bean - doCreateBean.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/04 - \345\256\236\344\276\213\345\214\226 Bean(createBeanInstance).jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/04 - \345\256\236\344\276\213\345\214\226 Bean(createBeanInstance).jpg" new file mode 100644 index 0000000..798240a Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/04 - \345\256\236\344\276\213\345\214\226 Bean(createBeanInstance).jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/05 - \345\241\253\345\205\205 Bean(populateBean).png" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/05 - \345\241\253\345\205\205 Bean(populateBean).png" new file mode 100644 index 0000000..fa6c7a3 Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/05 - \345\241\253\345\205\205 Bean(populateBean).png" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/06 - \345\256\236\344\276\213\345\214\226\345\211\215\347\232\204\345\207\206\345\244\207\351\230\266\346\256\265.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/06 - \345\256\236\344\276\213\345\214\226\345\211\215\347\232\204\345\207\206\345\244\207\351\230\266\346\256\265.jpg" new file mode 100644 index 0000000..c98f2b8 Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/06 - \345\256\236\344\276\213\345\214\226\345\211\215\347\232\204\345\207\206\345\244\207\351\230\266\346\256\265.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/07 - \345\256\236\344\276\213\345\214\226\345\211\215.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/07 - \345\256\236\344\276\213\345\214\226\345\211\215.jpg" new file mode 100644 index 0000000..c07170a Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/07 - \345\256\236\344\276\213\345\214\226\345\211\215.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/08 - \345\256\236\344\276\213\345\214\226\345\220\216.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/08 - \345\256\236\344\276\213\345\214\226\345\220\216.jpg" new file mode 100644 index 0000000..628acef Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/08 - \345\256\236\344\276\213\345\214\226\345\220\216.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/09 - \345\210\235\345\247\213\345\214\226\345\211\215.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/09 - \345\210\235\345\247\213\345\214\226\345\211\215.jpg" new file mode 100644 index 0000000..4265275 Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/09 - \345\210\235\345\247\213\345\214\226\345\211\215.jpg" differ diff --git "a/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/10 - \345\210\235\345\247\213\345\214\226\345\220\216\345\222\214\351\224\200\346\257\201.jpg" "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/10 - \345\210\235\345\247\213\345\214\226\345\220\216\345\222\214\351\224\200\346\257\201.jpg" new file mode 100644 index 0000000..58ff7a6 Binary files /dev/null and "b/Spring Framework/Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\233\276\350\247\243/10 - \345\210\235\345\247\213\345\214\226\345\220\216\345\222\214\351\224\200\346\257\201.jpg" differ diff --git a/Spring Framework/Spring Boot.md b/Spring Framework/Spring Boot.md new file mode 100644 index 0000000..5e2f586 --- /dev/null +++ b/Spring Framework/Spring Boot.md @@ -0,0 +1,554 @@ +# Spring Boot + +## @SpringBootApplication 注解 + +### @Configuration + +- 配置 spring 容器(应用上下文) + +### @EnableAutoConfiguration + +- 将所有符合自动配置条件的 bean 定义加载到 IoC 容器 + +### @ComponentScan + +- @Component + + - 自动扫描并加载符合条件的组件或 bean 定义,最终将这些 bean 定义加载到容器中 + +- @Service + +- @Repository + + - 用在持久层的接口上,将接口的一个实现类交给 Spring 管理。 + +- @Controller + +- @Entity + +## 多 Profile 使用与切换 + +### Profile 是 Spring 对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境。 + +- 默认使用 application.properties、application.yml 配置文件。 + +- application-{profile}.properties + +- application-{profile}.yml + +### yml 多文档块 + +- yml 文件中支持使用三个短横线分割文档块的方式 + +### 激活指定配置方式 + +- 配置文件方式 + + - active: dev(prod、test) + + - spring.profiles.active=dev + +- 命令行方式 + + - java -jar glorze.jar --spring.profiles.active=prod; + +## Spring Boot Actuator + +### conditions + +- 显示自动配置的信息 + +### beans + +- 显示应用程序上下文所有的 Spring bean + +### configprops + +- 显示所有 @ConfigurationProperties 的配置属性列表 + +### dump + +- 显示线程活动的快照 + +### env + +- 显示环境变量,包括系统环境变量以及应用环境变量 + +### health + +- 显示应用程序的健康指标,值由 HealthIndicator 的实现类提供;结果有 UP、 DOWN、OUT_OF_SERVICE、UNKNOWN + +### info + +- 显示应用的信息 + +### mappings + +- 显示所有的 URL 路径 + +### metrics + +- 显示应用的度量标准信息 + +## Spring Boot 对 Spring Boot Starter 加载的原理 + +SpringBoot 在启动时会去依赖的 starter 包中寻找 /META-INF/spring.factories 文件,然后根据文件中配置的路径去扫描项目所依赖的 Jar 包 +### Spring Boot 在启动时扫描项目所依赖的 JAR 包,寻找包含 spring.factories 文件的 JAR 包 + +### 根据 spring.factories 配置加载 AutoConfigure 类 + +### 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context + +## Spring Boot 自动配置的几种方式 + +### @EnableXXX + +- @EnableFeignClients + +- @EnableDiscoveryClient + +### 基于 class 是否存在的自动化配置 + +- @ConditionalOnClass + +### 基于配置属性是否存在的配置 + +- @ConditionalOnProperty + +## 注解 + +### @Conditional 注解及作用 + +- @ConditionalOnBean + + - 当容器中有指定的 Bean 的条件下 + +- @ConditionalOnClass + + - 当类路径下有指定的类的条件下 + +- @ConditionalOnExpression + + - 基于 SpEL 表达式作为判断条件 + +- @ConditionalOnJava + + - 基于 JVM 版本作为判断条件 + +- @ConditionalOnJndi + + - 在 JNDI 存在的条件下查找指定的位 + +- @ConditionalOnMissingBean + + - 当容器中没有指定 Bean 的情况下 + +- @ConditionalOnMissingClass + + - 当类路径下没有指定的类的条件下 + +- @ConditionalOnNotWebApplication + + - 当前项目不是 Web 项目的条件下 + +- @ConditionalOnProperty + + - 指定的属性是否有指定的值 + +- @ConditionalOnResource + + - 类路径下是否有指定的资源 + +- @ConditionalOnSingleCandidate + + - 当指定的 Bean 在容器中只有一个,或者在有多个 Bean 的情况下,用来指定首选的 Bean + +- @ConditionalOnWebApplication + + - 当前项目是 Web 项目的条件下 + +## SpringBoot Spring IOC BeanFactory 运行时动态注册 bean + +### 一个没有被 Spring 管理的 Controller,实现 InitializingBean 接口 + +### 获取 Spring 上下文 + +- private static ApplicationContext applicationContext; + +- 通过名字获取上下文中的 bean + +- 通过类型获取上下文中的 bean + +### 将 applicationContext 转换为 ConfigurableApplicationContext + +### 获取 bean 工厂并转换为 DefaultListableBeanFactory + +### 通过 BeanDefinitionBuilder 创建 bean 定义 + +### 设置属性 + +### 注册bean + +- defaultListableBeanFactory.registerBeanDefinition("userController", beanDefinitionBuilder.getRawBeanDefinition()); + +## https://pic.imgdb.cn/item/62469d3327f86abb2a4453fa.png +Spring Boot 事件模型 + +### SpringApplicationEvent + +- 和 SpringApplication 生命周期有关的所有事件的父类,继承自 Spring Framwork 的ApplicationEvent,确保了事件和应用实体 SpringApplication 产生关联 + +### ApplicationStartingEvent + +- 开始启动中(SpringApplication 本实例实例化、本实例属性赋值、日志系统实例化) + + - SpringApplication 实例已实例化,new SpringApplication(primarySources) + + - 推断出应用类型 webApplicationType、main 方法所在类 + + - 给字段 initializers 赋值:拿到 SPI 方式配置的 ApplicationContextInitializer 上下文初始化器 + + - SPI(Service provider interface),服务提供发现接口,JDK 内置的一种服务提供发现机制。一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。 + + - 当服务的提供者提供了一种接口的实现之后,需要在 classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个 jar 包(一般都是以jar包做依赖)的 META-INF/services/ 中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。 + + - 给字段 listeners 赋值:拿到 SPI 方式配置的 ApplicationListener 应用监听器 + + - 发送 ApplicationStartingEvent 事件 + + - LoggingApplicationListener + + - 对日志系统抽象 LoggingSystem 执行实例化以及初始化之前的操作,默认 Logback + + - BackgroundPreinitializer + + - 启动一个后台进行对一些类进行预热 + + - ValidationInitializer + + - JacksonInitializer + + - DelegatingApplicationListener + + - 监听 ApplicationEvent,实际上只有 ApplicationEnvironmentPreparedEvent 到达时生效 + + - LiquibaseServiceLocatorApplicationListener + +### ApplicationEnvironmentPreparedEvent + +- 环境已准备好,Spring Boot 的环境抽象 Enviroment 已经准备完毕,但此时其上下文 ApplicationContext 还没有创建 + + - 封装命令行参数(main 方法的 args)到 ApplicationArguments 里面 + + - 创建出一个环境抽象实例 ConfigurableEnvironment 的实现类,并且填入 Profiles 配置和 Properties 属性 + + - 发送 ApplicationEnvironmentPreparedEvent 事件 + + - 对环境抽象 Enviroment 的填值,均是由监听此事件的监听器去完成 + + - BootstrapApplicationListener + + - 优先级最高,用于启动/创建 Spring Cloud 的应用上下文,此时 Spring Boot 的上下文 ApplicationContext 还并没有创建,类似于 Bean 的初始化,初始化 A 的时候遇到 B,需要先完成 B 的初始化 + + - LoggingSystemShutdownListener + + - 对 LogbackLoggingSystem 先清理,再重新初始化一次。 + + - ConfigFileApplicationListener + + - 加载 SPI 配置的所有的 EnvironmentPostProcessor 实例,并且排好序。 + + - 排好序后,分别一个个的执行 EnvironmentPostProcessor + + - SystemEnvironmentPropertySourceEnvironmentPostProcessor + + - SpringApplicationJsonEnvironmentPostProcessor + + - 把环境中 spring.application.json=xxx 值解析成为一个 MapPropertySource 属性源,然后放进环境里面去 + + - CloudFoundryVcapEnvironmentPostProcessor + + - ConfigFileApplicationListener + + - 加载 application.properties/yaml 等外部化配置,解析好后放进环境里 + + - 外部化配置默认的优先级 + + - classpath:/ + + - classpath:/config/ + + - file:./ + + - file:./config/ + + - 当前工程下的 config 目录里的 application.properties 优先级最高,当前工程类路径下的 application.properties 优先级最低 + + - bootstrap.xxx 也是由它负责加载的,处理规则一样 + + - DebugAgentEnvironmentPostProcessor + + - AnsiOutputApplicationListener + + - 终端(可以是控制台、可以是日志文件)支持Ansi彩色输出,使其更具可读性。 + + - LoggingApplicationListener + + - 根据 Enviroment 环境完成 initialize() 初始化动作:日志等级、日志格式模版等 + + - ClasspathLoggingApplicationListener + + - 用于把 classpath 路径以 log.debug() 输出 + + - BackgroundPreinitializer + + - 本事件达到时无动作 + + - DelegatingApplicationListener + + - 执行通过外部化配置 context.listener.classes = xxx,xxx 的监听器们,然后把该事件广播给关心此事件的监听器执行 + + - FileEncodingApplicationListener + + - 检测当前系统环境的 file.encoding 和 spring.mandatory-file-encoding 设置的值是否一样,不一样则抛出异常 + + - 如果不配置 spring.mandatory-file-encoding 则不检查 + + - bindToSpringApplication(environment) + + - 把环境属性中 spring.main.xxx = xxx 绑定到当前的 SpringApplication 实例属性上 + +### ApplicationContextInitializedEvent,也叫 contextPrepared 事件 + +- 上下文已实例化,完成应用上下文 ApplicationContext 的准备工作,并且执行所有注册的上下文初始化器 ApplicationContextInitializer。但是此时,单例 Bean 是仍旧还没有初始化,并且 WebServer 也还没有启动 + + - printBanner + + - 打印 Banner 图,默认打印的是 Spring Boot 字样 + + - 根据是否是 web 环境、是否是 REACTIVE 等,用空构造器创建出一个 ConfigurableApplicationContext 上下文实例 + + - SERVLET + + - AnnotationConfigServletWebServerApplicationContext + + - REACTIVE + + - AnnotationConfigReactiveWebServerApplicationContext + + - 非 web 环境 + + - AnnotationConfigApplicationContext,Spring Cloud 使用 + + - 上下文参数设置 + + - 环境 Enviroment 环境设置给它 + + - 设置 beanNameGenerator、resourceLoader、ConversionService 等组件 + + - 实例化所有的 ApplicationContextInitializer 上下文初始化器,并且排序好后挨个执行它 + + - BootstrapApplicationListener.AncestorInitializer + + - 把 Spring Cloud 容器设置为 Spring Boot 容器的父容器 + + - BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer + + - 提前解密 Enviroment 环境里面的属性 + + - PropertySourceBootstrapConfiguration + + - PropertySourceLocator 属性源定位器 + + - EnvironmentDecryptApplicationInitializer + + - 解密 Enviroment 环境里面的属性 + + - DelegatingApplicationContextInitializer + + - 外部化配置 context.initializer.classes = xxx,xxx + + - SharedMetadataReaderFactoryContextInitializer + + - ContextIdApplicationContextInitializer + + - 设置应用 ID -> applicationContext.setId()。默认取值为 spring.application.name,再为application,再为自动生成 + + - ConfigurationWarningsApplicationContextInitializer + + - 错误的配置进行警告(不会终止程序),以 warn() 日志输出在控制台。默认内置的只有对包名的检查 + + - RSocketPortInfoApplicationContextInitializer + + - ServerPortInfoApplicationContextInitializer + + - 将自己作为一个监听器注册到上下文 ConfigurableApplicationContext 里,专门用于监听 WebServerInitializedEvent 事件 + + - ServletWebServerInitializedEvent + + - ReactiveWebServerInitializedEvent + + - ConditionEvaluationReportLoggingListener + + - 将 ConditionEvaluationReport 报告(自动配置中哪些匹配了,哪些没匹配上)写入日志,只有 LogLevel#DEBUG 时才会输出 + + - 发送 ApplicationContextInitializedEvent 事件 + + - BackgroundPreinitializer + + - DelegatingApplicationListener + +### ApplicationPreparedEvent + +- 上下文已准备好,应用上下文 ApplicationContext 初始化完成,该赋值的赋值了,Bean 定义信息也已全部加载完成。但是,单例 Bean 还没有被实例化,web 容器依旧还没启动。 + + - 把 applicationArguments、printedBanner 等都作为一个 Bean 放进 Bean 工厂里 + + - 若 lazyInitialization = true 延迟初始化,那就向 Bean 工厂放一个:new LazyInitializationBeanFactoryPostProcessor() + + - 根据 primarySources 和 allSources,交给 BeanDefinitionLoader(Sprig Boot 提供的实现)实现加载 Bean 的定义信息 + + - AnnotatedBeanDefinitionReader + + - 基于注解 + + - XmlBeanDefinitionReader + + - 基于 xml 配置 + + - GroovyBeanDefinitionReader + + - Groovy 文件 + + - ClassPathBeanDefinitionScanner + + - classpath 中加载 + + - 发送 ApplicationPreparedEvent 事件 + + - CloudFoundryVcapEnvironmentPostProcessor + + - ConfigFileApplicationListener + + - 向上下文注册一个 new PropertySourceOrderingPostProcessor(context),Bean 工厂结束后对环境里的属性源进行重排序,把名字叫 defaultProperties 的属性源放在最末位 + + - LoggingApplicationListener + + - 向工厂内放入 Log 相关 Bean + + - BackgroundPreinitializer + + - 本事件达到时无动作 + + - RestartListener + + - DelegatingApplicationListener + + - 本事件达到时无动作 + +### ApplicationStartedEvent + +- 应用成功启动,SpringApplication 的生命周期到这一步,正常的启动流程全部完成。Spring Boot 应用可以正常对对外提供服务。 + + - 启动Spring容器 + + - AbstractApplicationContext#refresh() + + - 实例化单例 Bean + + - 在 Spring 容器 refresh() 启动完成后, WebServer 也随之完成启动,成功监听到对应端口 + + - 输出启动成功的日志 + + - 发送ApplicationStartedEvent事件 + + - BackgroundPreinitializer + + - 本事件达到时无动作 + + - DelegatingApplicationListener + + - 本事件达到时无动作 + + - TomcatMetricsBinder + + - tomcat 指标信息 TomcatMetrics绑 定到MeterRegistry,从而收集相关指标 + + - callRunners() + + - 依次执行容器内配置的 ApplicationRunner/CommandLineRunner 的 Bean 实现类,支持 sort 排序 + +### ApplicationReadyEvent + +- 应用已准备好,应用已经完完全全的准备好,并且也已经完成了相关组件的周知工作。 + + - SpringApplicationAdminMXBeanRegistrar + + - 当此事件到达时,告诉 Admin Spring 应用已经ready,可以使用 + + - BackgroundPreinitializer + + - DelegatingApplicationListener + + - RefreshEventListener + + - 当此事件到达时,告诉 Spring 应用已经 ready,接下来可以执行 ContextRefresher.refresh() + + - 还有一个过程就是 command-line runners 被调用的内容 + +### ApplicationFailedEvent + +- 应用启动失败 + + - LoggingApplicationListener + + - 执行 loggingSystem.cleanUp() 清理资源 + + - ClasspathLoggingApplicationListener + + - 输出一句 debug 日志 + + - BackgroundPreinitializer + + - DelegatingApplicationListener + + - ConditionEvaluationReportLoggingListener + + - 自动配置输出报告,输出错误日志 + + - BootstrapApplicationListener.CloseContextOnFailureApplicationListener + + - 执行 context.close() + +## SpringApplition.run 生命周期 + +### 执行 run 方法 + +- SpringApplicationRunListener.starting + +### 准备 environment + +- SpringApplicationRunListener.environmentPrepared + +### 构建 context + +- SpringApplicationRunListener.contextPrepared + +### context 资源加载 + +- SpringApplicationRunListener.contextLoaded + +### 刷新 context + +- SpringApplicationRunListener.started + +### 调用 CommandLineRunner 和 ApplicationRunner + +- SpringApplicationRunListener.ready + +### run 方法结束 + +### 运行期间程序出现异常 + +- SpringApplicationRunListener.failed + diff --git a/Spring Framework/Spring Boot.xmind b/Spring Framework/Spring Boot.xmind new file mode 100644 index 0000000..adf1e6c Binary files /dev/null and b/Spring Framework/Spring Boot.xmind differ diff --git "a/Spring Framework/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.xmind" "b/Spring Framework/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.xmind" deleted file mode 100644 index 289f7db..0000000 Binary files "a/Spring Framework/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.xmind" and /dev/null differ diff --git "a/Spring Framework/Spring Framework \346\272\220\347\240\201.md" "b/Spring Framework/Spring Framework \346\272\220\347\240\201.md" new file mode 100644 index 0000000..e2a00a6 --- /dev/null +++ "b/Spring Framework/Spring Framework \346\272\220\347\240\201.md" @@ -0,0 +1,479 @@ +# Spring Framework 源码 + +## spring-aop + +### AspectJAutoProxyBeanDefinitionParser,解析器,一旦遇到 aspectj-autoproxy 注解时就会使用这个解析器接口,在parse() 方法中进行注册;动态 AOP 自定义标签 + +- registerAspectJAnnotationAutoProxyCreatorIfNecessary + + - registerAspectJAnnotationAutoProxyCreatorIfNecessary,注册或升级 AutoProxyCreator 定义 beanName 为 internalAutoProxyCreator 的 BeanDefinition + + - registerOrEscalateApcAsRequired,自动注册 AnnotationAwareAspectJAutoProxyCreator 类的功能,该方法存在一个优先级判断:如果存在自动代理创建器,并且与现在的不一致,需要根据优先级来判断使用哪个 + + - useClassProxyingIfNecessary,对于 proxy-target-class 以及 expose-proxy 属性的处理 + + - JDK 动态代理 + + - 如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。 + - 代理对象必须是某个接口的实现,它是通过在运行期问创建一个接口的实现类来完成对目标时象的代理。 + + - CGLIB 代理 + + - 若该目标时象没有 +实现任何接口,则创建一个CGLIB代理。 + - 实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针时目标类扩展的子类。CGLIB 是高效的代码生成包,底层是依靠 ASM(开源的 Java 字节码编样类库)操作字节码实现的.性能比 JDK 强。 + - JDK 本身就提供了动态代理,强制使用 CGLI B代理需要将 的 proxy-target-class 属性设为 true: +... + - 当需要使用 CGLIB 代理和 @Aspect 自动代理支持,可以按照以下方式设置 的 proxy-target-class 属性为 true: + + + - expose-proxy + + - 解决有时候目标对象内部的自我调用将无法实施切面中的增强 + + - registerComponentIfNecessary,注册组件并通知,便于监听器进一步处理 + +### AnnotationAwareAspectJAutoProxyCreator,创建 AOP 代理 + +- postProcessAfterInitialization,父类 AbstractAutoProxyCreator 的方法,在这里实现代理的创建 + + - getAdvicesAndAdvisorsForBean + + - 获取增强方法或者增强器 + - 根据获取的增强进行代理 + +- findCandidateAdvisors,获取增强器 + + - buildAspectJAdvisors + + - 获取所有 beanName,这一步骤中所有在 beanFacotry 中注册的 bean 都会被提取出来。 + - 遍历所有 beanName,并找出声明 AspectJ 注解的类,进行进一步的处理。 + - 对标记为 AspectJ 注解的类进行增强器的提取。 + + - 普通增强器的获取 + + - 切点信息的获取 + - 根据切点信息生成增强 + + - 增加同步实例化增强器 + + - 如果寻找的增强器不为空而且又配置了增强延迟初始化,那么就需要在首位加入同步实例化增强器 - SyntheticInstantiationAdvisor。 + + - 获取 DeclareParents 注解 + + - 将提取结果加入缓存。 + +- findAdvisorsThatCanApply,寻找匹配的增强器 + + - findAdvisorsThatCanApply + + - 寻找所有增强器中适用于当前 class 的增强器。引介增强与普通的增强处理是不一样的,所以分开处理。 + + - canApply + + - 真正的匹配在 canApply +中实现。 + +- createProxy + + - 获取当前类中的属性 + - 添加代理接口 + - 封装 Advisor 并加入到 ProxyFactory 中 + + - 创建代理 + + - 影响 Spring 使用 JDKProxy 还是 CglibProxy 的判断 + + - optimize + + - 用来控制通过 CGLIB 创建的代理是否使用激进的优化策略。目前这个属性仅用于CGLIB代理,对于 JDK 动态代理(默认代理)无效。 + + - ProxyTargetClass + + - 这个属性为 true 时,目标类本身被代理而不是目标类的接口。如果这个属性值被设为true, CGLIB 代理将被创建,设置方式为。 + + - hasNoUserSuppliedProxyInterfaces + + - 是否存在代理接口 + + - JDK 与 Cglib 方式的总结 + + - 如果目标时象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP。 + - 如果目标时象实现了接口,可以强制使用 CGLIB 实现 AOP。 + - 如果目标对象没有实现接口,必须采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。 + + - 如何强制使用 CGLIB 实现 AOP? + + - 添加 CGLIB 库,Spring_HOME/cglib/*.jar + - 在 Spring 配置文件中加入 。 + + - JDK 动态代理和 CGLIB 字节码生成的区别? + + - JDK 动态代理只能时实现了接口的类生成代理,而不能针对类。 + - CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为 +是继承,所以该类或方法最好不要声明成 final。 + + - 获取代理 + + - JDK 代理 + + - 业务接口 + - 业务接口实现类 + - 自定义 InvocationHandler + + - 构造函数,将代理的对象传入 + - invoke 方法,实现了 AOP 增强的所有逻辑 + - getProxy 千篇一律,但是必不可少 + + - JdkDynamicAopProxy + + - getProxy(@Nullable ClassLoader classLoader) + - invoke(Object proxy, Method method, Object[] args) + + 创建了一个拦截器链,并使用 ReflectiveMethodlnvocation 类进行了链的封装,而在 ReflectiveMethodlnvocation 类的 proceed 方法中实现了拦截器的逐一调用。 + + - CGLIB + + - CglibAopProxy + + - getProxy(@Nullable ClassLoader classLoader) + - getCallbacks(Class rootClass) + - DynamicAdvisedInterceptor,内部类 + + - intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) + + - 设置要代理的类 + - 进行获取代理操作 + +### 创建静态 AOP + +AOP 的静态代理主要是在虚拟机启动时通过改变目标对象字节码的方式来完成对目标对象的增强,它与动态代理相比具有更高的效率,因为在动态代理调用的过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则是在启动时便完成了字节码增强,当系统再次调用目标类时与调用正常的类并无差别,所以在效率上会相对高些。 + +- Instrumentation 使用 +- 自定义标签 + + - 是否开启 AspectJ + - 将 org.Springframework.context.weaving.AspectJWeavingEnabler 封装在 BeanDefinition 中 + +- 织入 + + - AspectJWeavingEnabler 类型的 bean 中的 loadTimeWeaver 属性被初始化为 DefaultContextLoadTimeWeaver 类型的 bean。 + - DefaultContextLoadTimeWeaver 类型的 bean 中的 loadTimeWeaver 属性被初始化为 InstrumentationLoadTimeWeaver。 + +## Spring MVC + +### web.xml + +- contextConfigLocation + + - Web 与 Spring 的配置文件相结合的配置参数,Spring、MVC 初始化的开端 + +- DispatcherServlet + + - 包含 SpringMVC 的请求逻辑,使用这个类拦截 Web 请求并进行相应的业务逻辑处理。 + +### ContextLoaderListener + +- 实现 ServletContextListener 接口,从而实现 contextInitialized 方法,每一个 Web 应用都有一个 ServletContext 与之关联,启动创建,关闭销毁。 +- contextInitialized(ServletContextEvent event) + + - initWebApplicationContext(ServletContext servletContext),初始化的大致步骤 + + - WebApplicationContext 存在性的校验 + + 查看 ServletContext 实例中是否有对应 key 的属性。这个 key 就是「String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";」 + + - 创建 WebApplicationContext 实例 + + - createWebApplicationContext(ServletContext sc) + + 在初始化的过程中,程序首先会读取 Contextloader 类的同目录下的属性文件 ContextLoader.properties,并根据其中的配置提取将要实现 WebApplicationContext 接口的实现类,并根据这个实现类通过反射的方式进行实例的创建。 + + - 将实例记录在 servletContext 中。 + - 映射当前的类加载器与创建的实例到全局变量 currentContextPerThread 中。 + +### DispatcherServlet + +### DispatcherServlet 逻辑处理 + +## 事务 + +### 事务自定义标签 + +- TxNamespaceHandler.init + + - 解析 《tx:annotation-driven 开头的配置 + - 使用 AnnotationDrivenBeanDefinitionParser 类的 parse 方法进行解析 + +- 注册 InfrastructureAdvisorAutoProxyCreator + + - AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer.configureAutoProxyCreator + + - AnnotationTransactionAttributeSource + - TransactionInterceptor + + - invoke 方法完成整个事务的逻辑 + + - BeanFactoryTransactionAttributeSourceAdvisor,上面两个 bean 注册到了这个类的实例里面 + + - AbstractAutoProxyCreator.wrapIfNecessary + + - 找出指定 bean 对应的增强器 + - 根据找出的增强器创建代理 + +- 获取对应 class/method 的增强器 + + - 寻找候选增强器:AbstractAdvisorAutoProxyCreator.findCandidateAdvisors + + - 获取增强器 + + - 候选增强器中寻找到匹配项:AopUtils.findAdvisorsThatCanApply + + - 增强器是否与对应的 class 匹配 + + - 提取事务标签:AbstractFallbackTransactionAttributeSource.computeTransactionAttribute + + - 提取事务标签 + +### 事务增强器 + +- 声明式事务处理步骤(TransactionAspectSupport.invokeWithinTransaction) + + - 获取事务的属性 + - 加载配置中配置的 TransactionManager + - 不同的事务处理方式使用不同的逻辑 + - 在目标方法执行前获取事务并收集事务信息 + - 执行目标方法 + - 一旦出现异常,常识异常处理,默认只对 RuntimeException 回滚 + - 提交事务前的事务信息清除 + - 提交事务 + +- 创建事务 + + - TransactionAspectSupport.createTransactionIfNecessary + + - 使用 DelegatingTransactionAttribute 封装传人的 TransactionAttribute 实例。 + - 获取事务 + - 构建事务信息 + + - 获取事务 + + - 事务的准备工作:AbstractPlatformTransactionManager.getTransaction + + - 获取事务 + - 如果当前先线程存在事务则转向嵌套事务的处理 + - 事务超时设置验证 + - 事务 propagationBehavior 属性的设置验证 + - 构建 DefaultTransactionStatus + - 完善 transaction,包括设置 ConnectionHolder、隔离级别、timeout,如果是新连接,则绑定到当前线程 + + - 事务的开始:DataSourceTransactionManager.doBegin,构造 transaction,包括设置 ConnectionHolder、隔离级别、timeout;如果是新连接,绑定到当前线程 + + - 尝试获取连接 + - 设置隔离级别以及只读标识 + - 更改默认的提交设置 + - 设置标志位,标识当前连接已经被事务激活 + - 设置过去时间 + - 将 connectionHolder 绑定到当前线程 + - 将事务信息记录在当前线程中 + + - 处理已经存在的事务:AbstractPlatformTransactionManager.handleExistingTransaction + - 准备事务信息:TransactionAspectSupport.prepareTransactionInfo + +- 回滚处理:TransactionAspectSupport.completeTransactionAfterThrowing + + - 回滚条件:DefaultTransactionAttribute.rollbackOn + + - 默认情况下 Spring 中的事务异常处理机制只对 RuntimeException 和 Error两种情况感兴趣 + + - 回滚处理:AbstractPlatformTransactionManager.rollback + + - 自定义触发器的调用,包括回滚前、完成回滚后的调用 + - 真正的回滚逻辑 + + - 当之前已经保存的事务信息中有保存点信息的时候,使用保存点信息进行回滚。常用于嵌入式事务,对于嵌入式的事务的处理,内嵌的事务异常并不会引起外部事务的回滚。 + - 当之前已经保存的事务信息中的事务为新事务,那么直接回滚。常用于单独事务的处理对于没有保存点的回滚。 + - 当前事务信息中表明是存在事务的,又不属于以上两种情况,多数用于 JTA,只做回滚标识,等到提交的时候统一不提交。 + + - 回滚后的信息清除:AbstractPlatformTransactionManager.cleanupAfterCompletion + + - 设置状态是对事务信息作完成标识以避免重复调用。 + - 如果当前事务是新的同步状态,需要将绑定到当前线程的事务信息清除。 + - 如果是新事务需要做些清除资源的工作。 + - 如果在事务执行前有事务挂起,那么当前事务执行结束后需要将挂起事务恢复。 + +- 事务提交:TransactionAspectSupport.commitTransactionAfterReturning + + - 符合提交的条件 + + - 当事务状态中有保存点信息的话便不会去提交事务 + - 当事务非新事务的时候也不会去执行提交事务操作。 + +### Spring 事务核心 API 类总结 + +- TransactionDefinition + + - 给定的事务规则 + - 用于定义一个事务 + - 包含了事务的静态属性,比如:事务传播行为、超时时间等等 + - 默认实现类:DefaultTransactionDefinition + +- PlatformTransactionManager + + - 按照规则来执行提交或者回滚操作,也就是用于执行具体的事务操作 + + - TransactionStatus getTransaction(TransactionDefinition definition) + - void commit(TransactionStatus status) + - void rollback(TransactionStatus status) + + - 主要实现类 + + - DataSourceTransactionManager + + - 适用于使用 JDBC 和MyBatis 进行数据持久化操作的情况。 + + - HibernateTransactionManager + + - 适用于使用 Hibernate 进行数据持久化操作的情况。 + + - JpaTransactionManager + + - 适用于使用 JPA 进行数据持久化操作的情况。 + + - JtaTransactionManager + - JdoTransactionManager + - JmsTransactionManager + +- TransactionStatus + + - 表示一个运行着的事务的状态 + +## IoC & DI + +### BeanDefinition 运行时序(xml web 配置文件为例) + +- BeanFactory + + - ListableBeanFactory + + - 可列表化 + + - HierarchicalBeanFactory + + - 负责 bean 的继承关系 + + - AutowireCapableBeanFactory + + - 负责 Bean 的自动注入 + +- BeanDefinition,实例化之前都用 BeanDefinition 描述 Bean,负责定义的各种 Bena 对象及其相互关系 +- BeanDefinitionReader + + - 负责对 Spring 配置文件的解析 + +- 资源定位 + + - ClasspathXmlApplicationContext +SystemFileXmlApplicationContext +XmlWebApplicationContext + + - 执行构造,加载资源文件 + + - AbstractRefreshableConfigApplicationContext + + - refresh() + + - 启动 IoC 的入口,IoC 容器的初始化,如果已经存在会执行覆盖操作,规定了 IoC 的启动流程,对 Bean 配置资源进行载入 + + - loadBeanDefinitions() + + - AbstractXmlApplicationContext + + - ConfigurableListableBeanFactory() + +- 加载 + + - AbstractXmlApplicationContext + + - loadBeanDefinitions(),通过 XmlBeanDefinitionReader 的 doLoadBeanDefinitions() 方法加载配置文件中声明的 Bean + + - XmlBeanDefinitionReader + + - doLoadBeanDefinitions() + +- 注册(registerBeanDefinition) + + - DefaultBeanDefinitionDocumentReader + - BeanDefinitionParserDelegate + - BeanDefinitionReaderUtils + + - 解析 bean 元素的过程中不对 Bean 进行实例化,始终使用 BeanDefinition 来描述 + + - DefaultListableBeanFactory + + - 真正的完成注册功能 + - 维护一个 HashMap,存放 BeanDefinition + +### 基于注解的 IoC 初始化 + +- 相对于配置文件,区别在于扫描读取的方式不同 +- 定位 Bean 扫描路径 + + - AnnotationConfigApplicationContext + - AnnotationConfigWebApplicationContext + +- 读取注解的元数据 +- 扫描包并解析为 BeanDefinition +- 注册 + +### Spring 管理 Bean 的生命周期 + +- 获取 Bean + + - 三级缓存获取 Bean,三级缓存解决的是循环依赖,针对单例 Bean + +- 创建 Bean + + - 创建之前的预处理 + + - 使用 BeanDefinition 对 Bean 进行描述,处理对象间的各种依赖关系,对 Bean 进行空校验等一系列操作,也包括容器刷新的一些后置处理器的处理等操作 + + - 真正的创建 Bean + + - doCreateBean + + - createBeanInstance + + - 通过构造实例化 Bean + + - populateBean + + - 对 Bean 进行填充 + - 后置处理器 + + - initializeBean + + - 初始化前后处理器的处理 + +- 销毁 Bean + + - destory +## Spring Bean 的生命周期(源码层面) +### 获取 Bean +首先根据 BeanName 从缓存中获取单例,如果缓存中存在该 Bean,判断当前单例是否在创建中,创建中的话就等待,否则直接返回即可; +如果缓存中不存在该 Bean,判断当前单例是否在创建当中,如果是在创建当中,可能会抛出不可解的循环依赖异常; +如果该 Bean 已经创建好了,那么会获取当前 BeanFactory 的 ParentBeanFactory,如果 ParentBeanFactory 不为空且 BeanDefinitionMap 中也不存在这个 Bean,则会调用 ParentBeanFactory 的 getVBean。 +如果不满足上述两个条件,继续判断当前 Bean 是否存在父类的 Bean,合并 RootBeanDefinition,接下来就是实例化当前 Bean 的 dependsOn 的 Bean,然后判断依赖 Bean 是否也是单例的,满足的话就调用 CreateBean。 +### 创建 Bean 之前 +创建 Bean 之前首先雀稗关联了 Bean 的 Class 对象,然后会封装成 RootBeanDefinition,在此期间会进行 InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation、postProcessAfterInstantiation 的调用,过程要保证方法的调用返回的 Bean 不为空,最后调用 doCreateBean。 +### 真正的创建 Bean +真正的创建 Bean 的过程就相对比较复杂,首先获取并移除未完成的 BeanWrapper 实例,在 BeanWrapper 为空的情况下调用 createBeanInstance 实例化 Bean,然后当前 Bean 要满足「是单例 Bean」、「能够尝试解析 Bean 之间的循环依赖」、「Bean 目前在创建中」这三点,在此之前还有一个 Bean 是否实现了 MergedBeanDefinitionPostProcessor 接口的 postProcessMergedBeanDefinition 方法用来对实例化之后的 Bean 进行合并操作。 + +满足前面三点之后,接下来判断是否实现了 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法,主要用来将创建的 Bean 加入到单例对象的工厂缓存跟实例化完毕后单例缓存的集合中。然后就是调用 populateBean 对 Bean 进行填充,接着调用 initializeBean 对 Bean 进行初始化操作。 +#### 填充 Bean +填充 Bean 是默认的行为,首先判断 Bean 是否实现了 InstantiationAwareBeanPostProcessor 的 postProcessAfterInstantiation 方法,如果实现的话需要进一步做填充。填充基本就是从给定的 BeanWrapper 中提取一组经过过滤的属性描述符,排除忽略的依赖项类型或者在忽略的依赖项接口上定义的属性,然后判断它们是否实现了前面说的 postProcessAfterInstantiation 方法,统一进行属性填充。 +#### 实例化 Bean +这里其实就是代理,利用反射或者 CGLIB 构造 Bean。首先获取 Bean 的 Class 对象,对其是否是 public 的、是否定义了 Supplier 函数、是否制定了工厂实例 Bean、getBean 的时候是否已经缓存,如果依次满足条件就会直接返回实例化的 Bean。这里存在一种情况就是 当 getBean 的时候如果传入的构造参数不为 null,则需要判断 Bean 是否实现了 SmartInstantiationAwareBeanPostProcessor 的 determineCandidateConstructors 从而来决定使用哪一个构造器,否则就会使用默认的构造器直接进行 initializeBean。 +### 实例化之后以及销毁 +实例化之后就是 postProcessProperties 属性操作了。然后 Bean 就已经初始化好了,销毁就是注册 destory 方法使用指定的 destory 方法进行销毁。 + diff --git "a/Spring Framework/Spring Framework \346\272\220\347\240\201.xmind" "b/Spring Framework/Spring Framework \346\272\220\347\240\201.xmind" index a233a11..f6c36d7 100644 Binary files "a/Spring Framework/Spring Framework \346\272\220\347\240\201.xmind" and "b/Spring Framework/Spring Framework \346\272\220\347\240\201.xmind" differ diff --git a/Spring Framework/Spring Framework.md b/Spring Framework/Spring Framework.md new file mode 100644 index 0000000..4ecedb7 --- /dev/null +++ b/Spring Framework/Spring Framework.md @@ -0,0 +1,703 @@ +# Spring Framework + +## Spring + +### Spring 事务 + +- 优点 + + - 统一编程模型,JDBC、Hibernate、JPA + - 支持声明式事务(注解常用(Transactional)、xml) + + - @Transactional 失效的场景,Spring 事务失效的场景 + + - 方法不是 public 的 + + - Spring AOP 代理时,事务拦截器会检查目标方法的修饰符是否为 public + + - 异常捕获 + + - catch 捕获异常之后进行自己的业务处理,所以不进行回滚 + + - 方法 A 调用方法 B,但是 A 没有声明,B 声明了 + - 事务的传播属性设置为「support」相关 + - 数据库不支持 + - rollfallback 设置错误 + + - Spring 事务默认 runtimeException 异常或者 Error 才回滚 + - 如果想自己指定异常类型进行回滚,需要使用 rollbackFor 指定异常类型 + + - @Transactional(rollbackFor = Exception.class) + + - 自调用 + + - 编程式事务(TransactionTemplate) + - 对spring数据层的完美抽象 + +- 传播性(TransactionDefinition) + + - PROPAGATION_REQUIRED(默认) + + - 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中 + - 默认情况下只有一个事务 + + - PROPAGATION_REQUIRES_NEW + + - 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 + + - PROPAGATION_NESTED + + - 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 + + - PROPAGATION_SUPPORTS + + - 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 + + - PROPAGATION_NOT_SUPPORTED + + - 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 + + - PROPAGATION_NEVER + + - 以非事务方式运行,如果当前存在事务,则抛出异常。 + + - PROPAGATION_MANDATORY + + - 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 + +- 隔离级别 + + - read uncommitted(未提交读) + - read committed(提交读、不可重复读) + - repeatable read(可重复读) + - serializable(可串行化) + - Spring 在此基础上抽象出一种隔离级别为default,表示以数据库默认配置的为主。 + + - MySQL默认的事务隔离级别为 repeatable-read。 + - Oracle 默认隔离级别为读已提交。 + - 当数据库的隔离级别与 Spring 的隔离级别配置不一致的时候,以 Spring 为准,如果 Spring 的隔离级别对应数据库不支持,那么数据库的隔离级别生效 + +### AOP + +按照一定的规则,将代码织入实现约定的流程当中。目的当然就是解耦、去重、服务增强。 + +- 动态代理 + + - 当 Bean 有实现接口时,Spring 就会用 JDK 动态代理(JdkDynamicAopProxy) + - 当 Bean 没有实现接口时,Spring 会选择 CGLIB 代理(CglibAopProxy) + - Spring 可以通过配置强制使用 CGLIB 代理。 + +- 核心概念 + + - 连接点(join point) + + - 在 Spring AOP 中通俗点指的就是具体被拦截,你要切入的方法。因为在 Spring AOP 中只支持方法的切入,是一种基于方法的 AOP,通过动态代理技术把它织入对应的流程中。 + + - 通知(advice) + + 所谓「通知」就是按照规定将业务代码织入到连接点的前后了。 + + - 前置通知(before advice) + + - 在目标方法被调用之前调用通知功能 + + - 后置通知(after advice) + + - 在目标方法完成之后调用通知,是不管方法是否遇到异常都会执行的。 + + - 环绕通知(around advice) + + - 环绕通知就是拿到目标方法,然后在目标方法可以自定义一些行为,然后在 Spring AOP 中通过叫 proceed() 的方法对目标方法进行主调实现环绕形式的通知。 + + - 事后返回通知(after-returning advice) + + - 在目标方法执行成功之后调用通知,这里就是要求方法必须正常执行成功才会执行,遇到异常通知失效。 + + - 异常通知(after-throwing advice) + + - 顾名思义,抛出异常后调用通知。 + + - 切点(point cut) + + 正则表达式描述的某个方法,将这个方法抽象描述一下,避免代码重复。例如要在某个类的 HelloGlorze() 进行切入前置、后置等通知,在每个通知上面写同样的方法全限定路径是糟糕的行为。当然,我们的切面很多时候不单单应用于单个方法,它可能是多个类的不同方法,所以切点就是提供一种通过正则表达式以及指示器来定义和适配连接点的功能。 + + - arg()/@args() + + - 限制连接点匹配参数为(指定类型、指定注解标注)的执行方法。 + + - execution() + + - 用于匹配连接点的执行方法 + + - this() + + - 限制连接点匹配 AOP 代理 Bean 引用为指定的类型 + + - target/@target() + + - 目标对象/限制目标对象配置指定的注解 + + - within/@within + + - 限制连接点匹配 指定/注解 类型 + + - @annotation + + - 限定带有指定注解的连接点 + + - 目标对象(target) + + - 就是被代理的对象,就是目标方法所属类的实例。 + + - 引入(introduction) + + - 说白一点就是引入新的类和方法,来增强对目标方法的补充。 + + - 织入(weaving) + + 这个其实就是所谓的「动态代理」技术,为原有的对象生成代理对象,然后根据切点拦截连接点,然后按照约定将引入的方法织入到流程当中。 + + - 编译器时期切入 + + - 切面会在目标类编译时期被织入,独立的 AspectJ 切面框架就支持这种织入方式。 + + - 类加载器时期切入 + + - 此时期切面会在目标类加载到 JVM(Java Virtual Machine,Java虚拟机)时被织入,当然这种方式是需要特定的类加载器才可以的,同样独立的 AspectJ 框架也支持这种方式织入切面 + + - 切面(Aspect) + + - 上述切点、各类通知以及引入的定义集,他们集体定义了面向切面编程。在软件开发中,散布于应用中多处的功能被称为横切关注点(cross-cutting concern),以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。所以通知和切点是切面的最基本元秦。 + +### 控制反转(IoC)、依赖注入(DI) + +- 依赖注入的方式 + + - 构造器注入 + + - 采用反射的方式,通过使用构造方法来完成注入 + + - Setter 方法注入 + + - 消除了使用构造器注入时出现多个参数的可能性,首先可以把构造方法声明为无参数的,然后使用 setter 注入为其设置对应的值,也是通过 Java 反射技术得以实现。 + + - 接口注入 + + - 资源并非来自于自身系统,而是来自于外界,通过 JNDI 等形式去获取它,采用接口注入。 + +### Spring 管理 Bean 相关知识总结 + +- 装配 bean 的三种方式 + + - 在 XML 中显式配置,由于现在基本是 Spring Boot 的天下了,所以现在 XML 的配置主键被淡化。 + + + + + + + - 在 Java 类中显式配置 + + - @Bean 注解 + + - 隐式的 bean 发现机制和自动装配 + + - @Component 注解 + + 声明这是一个 bean 组件,在 Spring 启动时就可以将这个类作为一个 bean 加入上下文中。 + + - @Autowired 注解 + + 如果两个类之间存在依赖时,就需要用到 @Autowired 注解,这个注解的作用就是将类自动注入到所用到的参数中。 + +- 自动装配 bean + + - byName 模式 + - byType 模式 + - 构造函数模式 + + - 与 byType 模式功能上相同,只不过是使用的是构造函数而不是 setter 来执行注入 + + - 默认模式 + + - 自动在构造函数和 byType 模式之间选择 + + - 如果 Bean 默认无参构造函数,就使用 byType 模式 + - 如果存在显示构造函数,就使用构造函数模式 + + - 无 + + - 这就是 Spring 的默认设置 + +- bean 的高级装配 + + - bean 的作用域 + + - 默认 bean 都是单例创建,不管一个 bean 被注入多少次都是同一个。 + - 创建 bean 的四种作用域 + + - 单例(Singleton)在整个应用中,只创建 bean 的一个实例。 + - 原型(Prototype)每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 实例。 + + - @Scope(Configurablebeanfactory.SCOPE PROTOTYPE) + - @Scope(“prototype”) + + - 会话(Session)在 Web 应用中,为每个会话创建一个 bean 实例。 + + - @Scope(Configurablebeanfactory.SCOPE SESSION) + + - 请求(Rquest)在 Web 应用中,为每个请求创建一个 bean 实例。 + - @Scope + + - value:设置作用域 + - proxymode(针对会话和请求作用域),解决将会话或请求作用域的 bean 注入到单例 bean中所遇到的问题。 + + Spring 并不会将世纪的 oneBean 注入到 twoBean 中,Spring 会注入一个到 twoBean 的代理。这个代理会暴露与 oneBean 相同的方法,所以 twoBean 会认为 oneBean 就是一个「抽象」的bean。但是,当 twoBean 调用 oneBean 的方法是,代理会对其进行懒解析并将调用委托给作用域内真正的 oneBean。 + + - Scopedproxymode.INTERFACES + + - 表明这个代理要实现某个 bean 的接口,并将调用委托给实现 bean。 + + - Scopedproxymode.TARGET CLASS + + - bean 类型是具体的类,表明要以生成目标类扩展的方式创建代理。 + + - 会话和请求作用域 + +- Spring Bean 结合源码理解生命周期(图解见当前目录下的「Spring Bean 生命周期图解」) + + - 获取 Bean + + - 先处理 Bean 的名称,因为如果以「&」开头的 Bean 名称表示获取的是对应的 FactoryBean 对象; + - 从缓存中获取单例 Bean,有则进一步判断这个 Bean 是不是在创建中,如果是的就等待创建完毕,否则直接返回这个 Bean 对象 + - 如果不存在单例 Bean 缓存,则先进行循环依赖的解析 + - 解析完毕之后先获取父类 BeanFactory,获取到了则调用父类的 getBean 方法,不存在则先合并然后创建 Bean + + - 创建 Bean + + - 创建 Bean 之前 + + - 先获取 RootBeanDefinition 对象中的 Class 对象并确保已经关联了要创建的 Bean 的 Class 。 + - 检查 3 个条件 + + - Bean 的属性中的 beforeInstantiationResolved 字段是否为 true,默认是 false。 + - Bean 是否是原生的 Bean + - Bean 的 hasInstantiationAwareBeanPostProcessors 属性为 true,这个属性在 Spring 准备刷新容器前转为 BeanPostProcessors 的时候会设置,如果当前 Bean 实现了 InstantiationAwareBeanPostProcessor 则这个就会是 true。 + - 当三个条件都存在的时候,就会调用实现的 InstantiationAwareBeanPostProcessor 接口的 postProcessBeforeInstantiation 方法,然后获取返回的 Bean,如果返回的 Bean 不是 null 还会调用实现的 BeanPostProcessor 接口的 postProcessAfterInitialization 方法 + + - 真正的创建 Bean(doCreateBean) + + - 先检查 instanceWrapper 变量是不是 null,这里一般是 null,除非当前正在创建的 Bean 在 factoryBeanInstanceCache 中存在的是保存还没创建完成的 FactoryBean 的集合。 + - 调用 createBeanInstance 方法实例化 Bean + - 如果当前 RootBeanDefinition 对象还没有调用过实现了的 MergedBeanDefinitionPostProcessor 接口的方法,则会进行调用 。 + - 当满足三点 + + - 是单例 Bean + - 尝试解析 Bean 之间的循环引用 + - Bean 目前正在创建中 + - 会进一步检查是否实现了 SmartInstantiationAwareBeanPostProcessor 接口,如果实现了则调用是实现的 getEarlyBeanReference 方法 + + - 调用 populateBean 方法进行属性填充 + - 调用 initializeBean 方法对 Bean 进行初始化 + + - destory 方法销毁 Bean + +- FactoryBean 和 BeanFactory + + - BeanFactory + + - Bean 工厂,Spring IoC 容器的最高层接口就是 BeanFactory,作用是管理 Bean,即实例化、定位、配置应用程序中的对象及监理这些对象之间的依赖 + + - FactoryBean + + - 工厂 Bean,是一个 Bean,作用是产生其他 Bean 实例 + - 一个 Bean 如果实现了 FactoryBean 接口,那么根据该 Bean 的名称获取到的实际上是 getObject() 返回的对象,而不是这个 Bean 自身实例,如果要获取这个 Bean 自身实例,那么需要在名称前面加上 '&' 符号。 + - 可以向容器中注册两个 Bean,一个是它本身,一个是 FactoryBean.getObject() 方法返回值所代表的 Bean + +### 缓存数据 + +- 循环依赖 + + - spring单例对象的初始化 + + - createBeanInstance:实例化 + + - 调用对象的构造方法实例化对象 + + - populateBean:填充属性 + + - 对多个 bean 的依赖属性进行填充 + + - initializeBean:调用 Spring XML 中的 init 方法 + + - 构造器循环依赖 + + - Spring 处理不了,会直接抛出BeanCurrentlylnCreationException 异常 + - 如在创建 TestA 类时,构造器需要 TestB 类,那将去创建 TestB,在创建 TestB 类时又发现需要 TestC 类,则又去创建 TestC,最终在创建 TestC 时发现又需要 TestA,从而形成一个环,没办法创建。 + + - setter 循环依赖,单例循环依赖 + + - 三级缓存 + + A 创建过程中需要 B,于是 A 将自己放到三级缓里面 ,去实例化 B; + + B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A; + + 接着 B 把三级缓存里面的 A 放到二级缓存里面,并删除三级缓存里面的 A; + + B 顺利初始化完毕,将自己放到一级缓存里面; + + 然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面。 + + - 一级缓存:singletonObjects,singletonFactories,完成初始化的单例对象的缓存 + - 二级缓存:earlySingletonObjects,完成实例化但是尚未初始化的,提前曝光的单例对象的缓存 + - 三级缓存:singletonFactories,进入实例化阶段的单例对象工厂的缓存 + + - 非单例循环依赖 + + - 对于「prototype」作用域的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存「prototype」作用域的 bean,因此无法提前暴露一个创建中的 bean 。 + +### Spring/Spring MVC 注解大全 + +- Spring + + - 声明 Bean 的注解 + + - @Component + + - 元注解,可以标注在其它的注解上。 + - 任何被「@Component」注解标识的组件均为组件扫描的候选对象 + - 被「@Component」元注解标注的注解,在任何组件标注它时,也被视作组件扫描的候选对象。 + - 总之被标注之后,要作为候选组件被添加到 Spring 容器当中 + + - @Service + + - 业务逻辑层使用(service层) + + - @Repository + + - 数据访问层使用(dao层) + + - @Controller + + - 声明该类为 SpringMVC 中的 Controller(控制器) + + - 注入 Bean 的注解 + + - @Autowired + + - 对于属性(推荐)、Setter、构造均可以使用 byType 的形式注入 + - 默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false + - @Qualifier + + - 可以曲线将「@Autowired」按照名称(byName)来装配,通过类型匹配找到多个 Bean 实例,然后再根据 @Qualifier 找到对应的 Bean 进行注入 + + - @Primary + + - 优先考虑,优先考虑被注解的对象注入 + + - @Resource + + - 同「@Autowired」,可以直接在属性、Setter 上(推荐)直接进行注入 + - 不同于「@Autowired」,@Resource 默认以 byName 的形式进行注入 + + - @Resource 既有 byName 也有 byType,有哪个用哪个,没有默认使用 baName + + - 不属于 Spring 的注解,隶属于 javax.annotation,只不过 Spring 支持该注解 + + - @Inject + + - 同 @Autowired + - 注入优先级 + + - 类型匹配(Match by Type) + - 限定符匹配(Match by Qualifier) + - 命名匹配(Match by Name) + + - Bean 的属性相关注解 + + - @Value + + - 字段或方法/构造函数参数级别的注释,表示受影响参数的默认值 + + - @Order/ Ordered 接口 + + - 是定义 Spring IOC 容器中 Bean 的执行顺序的优先级,而不是定义 Bean 的加载顺序,Bean 的加载顺序不受 @Order 或 Ordered 接口的影响; + + - 配置类注解 + + - @Configuration + + - 用于定义配置类,可替换 xml 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法,用于构建bean定义,初始化 Spring 容器。 + + - @Bean + + - 注解在方法上,声明当前方法的返回值为一个 bean,替代 xml 中的方式 + + - @ComponentScan + + - 用于对 Component 进行扫描 + + - @WishlyConfiguration + + - 为 @Configuration 与 @ComponentScan 的组合注解 + + - @PropertySource + + - 提供了一个方便的声明机制,用于添加一个 PropertySource 到 Spring 的环境中,与 @Configuration 一起使用。 + + - AOP 切面相关注解 + + - @Aspect + + - 把当前类标识为一个切面类供容器读取。 + + - @Before + + - 标识一个前置增强方法,相当于 BeforeAdvice 的功能。 + + - @After + + - 不管是抛出异常或者正常退出都会执行。 + + - @Around + + - 环绕增强,相当于 MethodInterceptor。 + + - @PointCut + + - 声明一个切入点。 + + - @EnableAspectJAutoProxy + + - 支持处理使用AspectJ + + - 事务相关 + + - @Transactional + + - 描述方法或类的事务属性。 + + - 缓存 + + - 可以缓存调用方法(或类中的所有方法)的结果。 + + - 环境切换及单测 + + - @Profile + + - 表示当一个或多个指定的文件处于活动状态时,这个组件是有资格注册的。使用 @Profile 注解类或者方法,达到在不同情况下选择实例化不同 的 Bean。 + + - @Conditional + + - 表示只有在所有指定条件匹配时,组件才有资格进行注册。 + + - @RunWith + + - JUnit 用例都是在 Runner(运行器)来执行的。通过它,可以为这个测试类指定一个特定的 Runner。 + + - @ContextConfiguration + + - 定义 class 级元数据,用于确定如何加载和配置 ApplicationContext 集成测试。 + + - @ActiveProfiles + + - class 级别注释,用于声明在加载 ApplicationContext 来测试时应使用哪些活动 Bean 定义配置文件。 + + - 同步异步 + + - @EnableAsync + + - 启用Spring的异步方法执行功能 + - 要与 @Configuration 一起使用。 + + - @Async + + - 将方法标记为异步执行候选的注释。也可以在 class 级别使用,在这种情况下,所有类型的方法都被视为异步。 + + - 定时相关 + + - @EnableScheduling + + - 启用 Spring 的计划任务执行功能,要和 @Configuration 一同使用。 + + - @Scheduled + + - 用于标记一个需要定期执行的方法。 + +- Spring MVC + + - @Controller + + - 声明该类为 SpringMVC 中的 Controller(控制器) + + - @PostConstruct + + - 在服务器加载 Servlet 的时候运行,并且只会被服务器执行一次。PostConstruct 在构造函数之后执行,init() 方法之前执行。 + + - @PreDestroy + + - 在 destroy() 方法之后执行。 + + - @EnableWebMvc + + - 将此注释添加到带有 @Configuration 的类中会从 WebMvcConfigurationSupport 中导入 Spring MVC 配置。 + +### Spring 时间监听 + +- 早期事件监听 + + - 要从 Spring 事件获知自定义域事件中获取通知,那么组件必须实现 ApplicationListener 接口并覆写 onApplicationEvent 方法。 + - 此种方式会针对每一个事件都创建一个新类,从而造成代码瓶颈。 + +- 注释驱动的事件监听器(@EventListener) + + - Spring 会为事件创建一个 ApplicationListener 实例,并从方法参数中获取事件的类型。 + - 一个类中被事件注释的方法数量没有限制,所有相关的事件句柄都会分组到一个类中。 + - 可以与注释 @Async 进行组合使用,以提供异步事件处理的机制。 + +- Spring 内置事件 + + - ContextRefreshedEvent + + - ApplicationContext 被初始化或刷新时,该事件被触发。 + - 这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。 + - 初始化是指:所有的 Bean 被成功装载,后处理Bean被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用 + + - ContextStartedEvent + + - 当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。 + - 可以查询数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。 + + - ContextStoppedEvent + + - 当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。 + - 可以在接收到这个事件后做必要的清理的工作。 + + - ContextClosedEvent + + - 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。 + - 一个已关闭的上下文到达生命周期末端 + - 它不能被刷新或重启。 + + - RequestHandledEvent + + - 一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。 + - 只能应用于使用 DispatcherServlet 的 Web 应用。 + - 使用 Spring 作为前端的 MVC 控制器时,当 Spring 处理用户请求结束后,系统会自动触发该事件。 + +## Spring MVC + +### 核心流程 + +- 请求过来 + + - DispatcherServlet + + - HandlerMapping + + - Controller + + - ModelAndView + + - ViewResolver + - View + +### 九大组件 + +- HandlerMapping,处理器映射器组件 + + - HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;可自定义 + - 根据 request 找到相应的处理器 Handler 和 Interceptors + +- HandlerAdapter + + - boolean supports(Object handler);判断是否可以使用某个 Handler。 + - ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;具体干活。 + - long getLastModified(HttpServletRequest request, Object handler);获取资源最后一次修改的时间。 + - 使用处理器干活的人。从名字上看,它就是一个适配器。因为 SpringMVC 中的 Handler 可以是任意的形式,只要能处理请求就可以,但是Servlet 需要的处理方法的结构却是固定的,都是以 request 和 response 为参数的方法。如何让固定的 Servlet 处理方法调用灵活的 Handler来进行处理就是 HandlerAdapter 要做的事情。 + +- HandlerExceptionResolver + + - ModelAndView resolveException( + HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); + - 根据异常设置 ModelAndView,再交给 render 方法进行渲染,分工明确互不干涉。 + +- ViewResolver + + - View resolveViewName(String viewName, Locale locale) throws Exception; + - 将 String 类型的视图名和 Locale 解析为 View 类型的视图。 + +- RequestToViewNameTranslator + + - String getViewName(HttpServletRequest request) throws Exception; + - 根据 ViewName 查找 View,但是有的 Handler 处理完后并没有设置 View 也没有设置 viewName,这个时候需要从 request 获取 viewName,这个过程就由 RequestToViewNameTranslator 完成。 + +- LocaleResolver,国际化 i18n 的处理 + + - Locale resolveLocale(HttpServletRequest request); + - void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale); + - 解析视图需要两个参数:视图名和 Locale。LocaleResolver 用于从 request 解析出 Locale。 + +- ThemeResolver + + - String resolveThemeName(HttpServletRequest request); + - void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName); + - 用来设置页面主题,theme.properties 放到 classpath 下面。 + +- MultipartResolver,处理上传请求 + + - boolean isMultipart(HttpServletRequest request);是不是上传请求。 + - MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;将 request 包装成 MultipartHttpServletRequest + - void cleanupMultipart(MultipartHttpServletRequest request);处理完后清理上传过程产生的临时资源。 + +- FlashMapManager,在重定向中传递参数。 + + - FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);用于恢复参数,并将回复过的和超时的参数从保存介质中删除 + - void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);将参数保存。 + +### DispatcherServlet + +- load-on-startup + + - 标记容器是否在启动的时候就加载这个servlet(实例化并调用其 init() 方法)。 + - 值必须是一个整数,表示 servlet 应该被载入的顺序 + - 值为 0 或者大于 0 时,表示容器在应用启动时就加载并初始化这个 servlet; + - 值小于 0 或者没有指定时,则表示容器在该 servlet 被选择时才会去加载。 + - 正数的值越小,该 servlet 的优先级越高,应用启动时就越先加载。 + - 值相同时,容器就会自己选择顺序来加载。所以 load-on-startup 代表的是优先级,而非启动延迟时间。 + +## Spring 结构 + +### Data Access/Integration,数据访问、集成 + +- JDBC +- ORM +- OXM +- JMS +- Transactions + +### Web + +- WebSocket +- Servlet +- Web +- portlet + +### Core Container + +- Beans +- Core +- Context +- SpEL + +### AOP + +### Aspects + +### Instrumentation + +### Messaging + +### Test + diff --git a/Spring Framework/Spring Framework.xmind b/Spring Framework/Spring Framework.xmind index fb9331b..239b804 100644 Binary files a/Spring Framework/Spring Framework.xmind and b/Spring Framework/Spring Framework.xmind differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/ApplicationContext \347\232\204\346\236\266\346\236\204\345\233\276.png" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/ApplicationContext \347\232\204\346\236\266\346\236\204\345\233\276.png" new file mode 100644 index 0000000..5e0e0b7 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/ApplicationContext \347\232\204\346\236\266\346\236\204\345\233\276.png" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/BeanFactory \346\211\200\346\234\211\347\232\204\345\256\236\347\216\260\343\200\201\346\216\245\345\217\243.png" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/BeanFactory \346\211\200\346\234\211\347\232\204\345\256\236\347\216\260\343\200\201\346\216\245\345\217\243.png" new file mode 100644 index 0000000..cdfba3c Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/BeanFactory \346\211\200\346\234\211\347\232\204\345\256\236\347\216\260\343\200\201\346\216\245\345\217\243.png" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/BeanFactory \346\211\200\346\234\211\347\232\204\345\256\236\347\216\260\347\261\273\343\200\201\346\216\245\345\217\2432.png" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/BeanFactory \346\211\200\346\234\211\347\232\204\345\256\236\347\216\260\347\261\273\343\200\201\346\216\245\345\217\2432.png" new file mode 100644 index 0000000..6a5cfe7 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/BeanFactory \346\211\200\346\234\211\347\232\204\345\256\236\347\216\260\347\261\273\343\200\201\346\216\245\345\217\2432.png" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring AOP\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring AOP\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" new file mode 100644 index 0000000..d85784e Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring AOP\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring DI\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring DI\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" new file mode 100644 index 0000000..6c71ce0 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring DI\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring IoC\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring IoC\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" new file mode 100644 index 0000000..0669af4 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring IoC\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring MVC\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring MVC\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" new file mode 100644 index 0000000..4f54af1 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/Spring MVC\350\277\220\350\241\214\346\227\266\345\272\217\345\233\276.jpg" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/bean \347\232\204\350\275\254\346\215\242\350\277\207\347\250\213.png" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/bean \347\232\204\350\275\254\346\215\242\350\277\207\347\250\213.png" new file mode 100644 index 0000000..66da104 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/bean \347\232\204\350\275\254\346\215\242\350\277\207\347\250\213.png" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/getBean \347\232\204\345\205\250\346\265\201\347\250\213.png" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/getBean \347\232\204\345\205\250\346\265\201\347\250\213.png" new file mode 100644 index 0000000..e731bd0 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/getBean \347\232\204\345\205\250\346\265\201\347\250\213.png" differ diff --git "a/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/loadBean \347\232\204\345\205\250\346\265\201\347\250\213.png" "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/loadBean \347\232\204\345\205\250\346\265\201\347\250\213.png" new file mode 100644 index 0000000..7cb9a11 Binary files /dev/null and "b/Spring Framework/Spring \350\277\220\350\241\214\346\227\266\345\272\217\346\265\201\347\250\213\345\233\276/loadBean \347\232\204\345\205\250\346\265\201\347\250\213.png" differ diff --git "a/Spring Framework/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204Spring Cloud.xmind" "b/Spring Framework/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204Spring Cloud.xmind" deleted file mode 100644 index 584bf6c..0000000 Binary files "a/Spring Framework/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204Spring Cloud.xmind" and /dev/null differ diff --git "a/web/HTTP \346\216\245\345\217\243\350\256\276\350\256\241\350\247\204\350\214\203.md" "b/web/HTTP \346\216\245\345\217\243\350\256\276\350\256\241\350\247\204\350\214\203.md" new file mode 100644 index 0000000..fa64fa3 --- /dev/null +++ "b/web/HTTP \346\216\245\345\217\243\350\256\276\350\256\241\350\247\204\350\214\203.md" @@ -0,0 +1,106 @@ +# HTTP 接口设计规范 + +源自开源项目:https://github.com/flmn/http-api-demo + +## 概述 + +### 基于 Java 的前后端分离开发,旨在要求一种 HTTP API 规范 + +## HTTP 协议 + +### HTTP 请求方法 + +- 所有请求使用 POST 方法 +- 理由 + + - 不遵循 RESTful 规范 + - 使用 POST,相对于 GET 的 Query String,可以支持复杂的请求参数,即使是读请求,可以构造复杂的筛选请求结构 + - 便于对请求和响应统一做签名、加密、日志等处理 + +### URL 规则 + +- URL 中只能包含英文,使用英文单词或简称,不要使用汉语拼音 +- 所有字符使用小写字母 +- 多个单词使用「-」分隔,如 create-user,不要使用 createuser、createUser 或者 create_user +- URL 的 path 部分,使用「系统/模块/操作」的格式,如 app/account/login + + - 系统:表示这个接口是给谁用的,比如:app、web、h5、webapp 等。命名可以使用简称 + - 模块:表示系统的子模块,比如:account(账户)、project(项目)、contract(合同)等。模块名字使用名词全称,且使用单数形式。 + - 操作:表示这个模块的具体的接口,比如 create-user(创建用户)、list-users(用户列表)等。使用动词 + 名词的形式,需要考虑复数。 + +### HTTP 头 + +- 将具体接口业务无关的数据放在 HTTP Headers +- 后端系统可以在不涉及请求和响应的情况下,处理一些公用逻辑 + + - X-Access-Token:身份认证 + - X-App-OS:客户端 OS + - X-App-Version:客户端版本 + - X-Network:客户端网络模式,Wi-Fi、蜂窝网络 + - X-Sign:请求签名 + +### 请求体和响应体 + +- 使用 UTF-8 编码 +- JSON 格式 +- 如果有加密功能,可以将正常的 JSON 加密后,使用 Base64 编码 + +### HTTP 状态码 + +- 业务的处理结果不体现在 HTTP 状态码,由响应体的错误码字段表示 +- 只使用部分 HTTP 状态码来表示一些业务无关的响应 + + - 200:业务已经处理,但是处理成功还是失败由响应体表示 + - 400:错误的请求,多用在请求验证,客户端开发要保证向服务器提交正确格式的请求,400 错误不该在生产环境出现 + - 401:认证失败,一般是没有 Acess Token 或者已经过期 + - 403:无权限,指没有权限调用这个接口,客户端应该在界面上将用户无权限的操作隐藏 + - 500:服务器发生了未处理的异常,500 错误不该在生产环境出现 + +## JSON(如何构造请求和响应体) + +### 字段命名 + +- 由于 JSON 来自于 JS,所以字段命名遵循 JS 语言,采用驼峰写法(首字母小写),lowerCamelCase +- 不要采用 snake_case + +### 数据类型 + +- 常用数据类型映射 + + - bool:映射为 string,使用 Y 表示 true,N 表示 false + - int:映射为 number + - long:映射为 string + - float、double、decimal:映射为 string + - 日期、时间:映射为 string + +- 特殊说明 + + - 表示 ID 概念的字段,统一使用 string + - long 映射为 string,是因为 JS 的 number 能处理的数值范围不够,导致各种奇怪的问题 + +### 空值处理 + +- 在数据传输时,如果某个字段是空值,则直接省略此字段不传,减少网络开销 + +### 嵌套 VS 平铺 + +- 嵌套优先级高于平铺,即 JSONObject 包含 JSONObject 、JSONArray 等结构,而不是一个 JSONObject 包含所有平级字段 + +### 响应体 + +- code:错误码,任何情况下必须返回 +- message:错误信息,可选 +- data:业务数据,可选,该字段最好是一个 Map +- errors:错误表,发生 HTTP 400 错误时,存在此字段,该字段最好是一个 Map,Key 为发生错误的请求字段,Value 为错误描述 + +### 错误码 + +- code 字段表示业务处理的错误码 +- 如果业务处理成功,必须返回「成功的错误或标识」,比如「OK」或者标识成功的数字码 +- 如果业务处理失败,使用不同的错误码区分不同的错误,错误码使用简短的能够体现错误种类的英文单词或者一串数字,英文的话使用大写字母,使用下划线分隔单词,建议使用英文单词。 +- 数字错误码的劣势 + + - 如果错误种类比较多,维护错误码表就是一个很大的工作 + - 数字错误码一般都是连续使用的,修改错误的定义是个麻烦事 + - 数字一眼很难辨识代表的是发生了什么错误 + diff --git "a/web/HTTP \346\216\245\345\217\243\350\256\276\350\256\241\350\247\204\350\214\203.xmind" "b/web/HTTP \346\216\245\345\217\243\350\256\276\350\256\241\350\247\204\350\214\203.xmind" new file mode 100644 index 0000000..20a82c0 Binary files /dev/null and "b/web/HTTP \346\216\245\345\217\243\350\256\276\350\256\241\350\247\204\350\214\203.xmind" differ diff --git a/web/JSON.xmind b/web/JSON.xmind new file mode 100644 index 0000000..8db5775 Binary files /dev/null and b/web/JSON.xmind differ diff --git a/Spring Framework/REST & RESTful.md b/web/REST & RESTful.md similarity index 100% rename from Spring Framework/REST & RESTful.md rename to web/REST & RESTful.md diff --git a/Spring Framework/REST & RESTful.xmind b/web/REST & RESTful.xmind similarity index 100% rename from Spring Framework/REST & RESTful.xmind rename to web/REST & RESTful.xmind diff --git a/web/web server/Nginx.xmind b/web/web server/Nginx.xmind new file mode 100644 index 0000000..ece97d8 Binary files /dev/null and b/web/web server/Nginx.xmind differ diff --git a/web/web server/Tomcat.md b/web/web server/Tomcat.md new file mode 100644 index 0000000..d35a9e0 --- /dev/null +++ b/web/web server/Tomcat.md @@ -0,0 +1,48 @@ +# Tomcat + +## session 共享方案 + +### 使用 Cookie 实现 + +- 将系统用户的 Session 信息加密、序列化后,以 Cookie 的方式, 统一种植在根域名下 +- 缺点 + + - 占用带宽,最主要的是保存在客户端,存在很大的安全隐患 + +### 使用 Nginx 中的 ip 绑定策略 + +- 配置 ip_hash +- 缺点 + + - 无法负载均衡 + +### 使用数据库同步 session + +- 将 session 数据存到数据库中 +- 缺点 + + - Session 的并发读写能力取决于数据库的性能,对数据库的压力大,同时需要自己实现 Session 淘汰逻辑,以便定时从数据表中更新、删除 Session 记录,当并发过高时容易出现死锁 + +### 使用 token(JWT) 代替 session + +- JWT 基本流程 + + - 客户端通过用户名和密码登录服务器 + - 服务端对客户端身份进行验证 + - 服务端对该用户生成 Token,返回给客户端 + - 客户端将 Token 保存到本地浏览器,一般保存到 cookie 中 + - 客户端发起请求,需要携带该 Token + - 服务端收到请求后,首先验证 Token,之后返回数据 + +### 使用 tomcat 内置的 session 同步 + +### Tomcat 通过 Redis 实现 session 共享 + + +- 当客户端访问 Nginx 服务器时,Nginx 负载均衡会自动将请求转发到 Tomcat1 节点或 Tomcat2 节点服务器,以减轻 Tomcat 压力,从而达到 Tomcat 集群化部署,为了使各 Tomcat 之间共享同一个 Session,可以采用 Redis 缓存服务来集中管理 Session 存储。Nginx 实现负载均衡,并使用 Redis 实现 session 共享。 +- 可以给这个 redis 节点配置一个从节点,采用 redis 主从模式,连接 redis 的 master 节点,redis 默认不支持主主模式 + +### Spring-Session + Redis 实现 + +- 当 Web 服务器接收到请求后,请求会进入对应的 Filter 进行过滤,将原本需要由 Web 服务器创建会话的过程转交给 Spring-Session 进行创建。Spring-Session 会将原本应该保存在 Web 服务器内存的 Session 存放到 Redis 中。然后 Web 服务器之间通过连接 Redis 来共享数据,达到 Sesson 共享的目的。 + diff --git a/web/web server/Tomcat.xmind b/web/web server/Tomcat.xmind new file mode 100644 index 0000000..fa69841 Binary files /dev/null and b/web/web server/Tomcat.xmind differ diff --git "a/web/\345\271\266\345\217\221\346\200\247\350\203\275&\350\277\220\350\220\245\347\233\270\345\205\263\346\214\207\346\240\207.xmind" "b/web/\345\271\266\345\217\221\346\200\247\350\203\275&\350\277\220\350\220\245\347\233\270\345\205\263\346\214\207\346\240\207.xmind" new file mode 100644 index 0000000..671de0a Binary files /dev/null and "b/web/\345\271\266\345\217\221\346\200\247\350\203\275&\350\277\220\350\220\245\347\233\270\345\205\263\346\214\207\346\240\207.xmind" differ diff --git "a/web/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/web/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" new file mode 100644 index 0000000..a581948 --- /dev/null +++ "b/web/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -0,0 +1,135 @@ +# 计算机网络 + +## OSI 七层模型 + +### 物理层 + +- 以二进制数据形式在物理媒体上传输数据 + +### 数据链路层 + +- 传输有地址的帧以及错误检测功能 + +### 网络层 + +- 为数据包选择路由 + +### 传输层 + +- 提供端对端的接口 + + - TCP + + - 三次握手 + + - 目的是「了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误」;解决网络中存在延迟的重复分组 + - 问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。 + - 三次握手是一个理论上的最小值,并不是说是 tcp 协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。 + - 过程 + + - A SYN 请求建立连接 + - B ACK 针对 SYN 的确认应答 +SYN 请求建立连接 + - A ACK 针对 B SYN 的确认应答 + + - 四次挥手 + + - 因为 TCP 是全双工模式,接收到 FIN 时意味着没有数据再发送过来,但是还是可以继续发送数据 + - 过程 + + - A FIN 请求切断连接 + - B ACK 针对 A FIN 的应答 + - B FIN 请求切断连接 + - A ACK 针对 B FIN 的确认应答 + + - UDP + +### 会话层 + +- 解除或建立与别的接点的联系 + +### 表示层 + +- 数据格式化 + + - 代码转换,数据加密 + +### 应用层 + +- 文件传输 + + - HTTP + - FTP + - SMTP + +## TCP/IP 五层模型的协议 + +### 应用层 + +### 传输层 + +### 网络层 + +### 数据链路层 + +### 物理层 + +## TCP + +### 拥塞控制和流量控制 + +- 拥塞控制 + + - 作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况 + - 慢开始、拥塞避免 + + - 发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。 + - 慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。 + + - 快重传、快恢复 + + - 快重传要求接收方在收到一个失序的报文段后就立即发出重复确认而不要等到自己发送数据时捎带确认。 + - 发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 + +- 流量控制 + + - 作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。 + +## HTTP 状态码 + +### 301 + +- 永久重定向 +- 对于 GET 请求, 301 跳转会默认被浏览器 cache。 + +### 302 + +- 临时重定向 +- 对于 GET 请求, 302 跳转默认不会被浏览器缓存,除非在 HTTP 响应中通过 Cache-Control 或 Expires 暗示浏览器缓存。 + +## 域名系统(Domain Name System,缩写 DNS)& HTTP 杂汇 + +### 因特网的一项核心服务,它作为可以将域名和 IP 地址相互映射的一个分布式数据库,这个过程就是域名解析 + +### 一个域名往往对应多个 DNS 地址 + +### 浏览器中输入一个 URL 到页面返回的全过程 + +- 根据域名,进行 DNS 域名解析 + + - 域名解析用 UDP 协议,因为快 + +- 拿到解析的 IP 地址,建立 TCP 连接 +- 向 IP 地址,发送 HTTP 请求 +- 服务器处理请求并返回响应结果 +- 关闭 TCP 连接 +- 浏览器解析 HTML 并渲染布局 + +### 一个 TCP 连接后是否会在一个 HTTP 请求完成后断开 + +- HTTP/1.0 中 Connection 默认是 close 的,即每次请求都会重新建立和断开 TCP 连接 +- HTTP/1.1 中 Connection 默认是 keep-alive 的,即 tcp 连接可以复用,不用每次都要重新建立和断开 TCP 连接 +- 所以,在 HTTP/1.1 情况下,一个 TCP 连接是可以发送多个 HTTP 请求 +- 在 HTTP/1.1 存在 Pipelining 技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭。 +- 在 HTTP2 中由于 Multiplexing 特点的存在,多个 HTTP 请求可以在同一个 TCP 连接中并行进行 + diff --git "a/web/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.xmind" "b/web/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.xmind" new file mode 100644 index 0000000..3db2a5a Binary files /dev/null and "b/web/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/Git.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/Git.md" new file mode 100644 index 0000000..a472910 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/Git.md" @@ -0,0 +1,459 @@ +# Git + +## Git 的工作流程及区域划分 + +### Workspace + +- 工作区 + +### Staging/Index + +- 暂存区 + +### Local Repository + +- 本地仓库(可修改) + +### /refs/remotes + +- 远程仓库的引用(不可修改) + +### Remote + +- 远程仓库 + +## Git 常用命令集合 + +### 简单命令 + +- git init + + - 在当前目录新建一个 git 仓库 + +- gitk + + - 打开 git 仓库图形界面 + +- git status + + - 显示所有变更信息 + +- git clean -fd + + - 删除所有 Untracked files + +- git fetch remote + + - 下载远程仓库的所有更新 + +- git pull romote branch-name + + - 下载远程仓库的所有更新,并且 Merge(合并) + +- git rev-parse HEAD + + - 查看上次 commit id + +- git merge branch-name + + - 将指定分支合并到当前分支 + +- git format-patch HEAD^ + + - 将最近的一次 commit 打包到 patch 文件中 + +- git am patch-file + + - 将 patch 文件 添加到本地仓库 + +- git blame file-name + + - 看指定文件修改历史 + +### 常用命令 + +- git clone + + - git clone url + + - 将远程 git 仓库克隆到本地 + + - git clone -b branch url + + - 将远程 git 仓库对应分支克隆到本地 + +- git stash + + - git stash + + - 将修改过,未 add 到 Staging 暂存区的文件,暂时存储起来 + + - git stash apply + + - 恢复之前 stash 存储的内容 + + - git stash save "stash test" + + - 保存 stash 并写 message + + - git stash list + + - 查看 stash 了哪些存储 + + - git stash apply stash@{1} + + - 将 stash@{1} 存储的内容还原到工作区 + + - git stash drop stash@{1} + + - 删除 stash@{1} 存储的内容 + + - git stash clear + + - 除所有缓存的 stash + +- git config + + - git config --global gui.encoding=utf-8 + + - 配置 git 图形界面编码为 utf-8 + + - git config --global user.name name + + - 置全局提交代码的用户名 + + - git config --global user.email email + + - 置全局提交代码时的邮箱 + + - git config user.name name + + - 设置当前项目提交代码的用户名 + +- git remote + + - git remote -v + + - 显示所有远程仓库 + + - git remote add name url + + - 增加一个新的远程仓库 + + - git remote remove name + + - 删除指定远程仓库 + + - git remote show origin + + - 获取指定远程仓库的详细信息 + +- git add + + - git add . +git add --all + + - 添加所有的修改到 Staging 暂存区 + + - git add file + + - 添加指定文件到 Staging 暂存区 + + - git add file1 file2 + + - 添加多个修改的文件到 Staging 暂存区 + + - git add dir + + - 添加修改的目录到 Staging 暂存区 + + - git add src/main* + + - 添加所有 src 目录下 main 开头的所有文件到 Staging 暂存区 + +- git commit + + - git commit -m "message" + + - 提交 Staging 暂存区的代码到本地仓库区 + + - git commit file1 file2 -m "message" + + - 提交 Staging 暂存区中在指定文件到本地仓库区 + + - git commit --amend -m "message" + + - 使用新的一次 commit,来覆盖上一次 commit + + - git commit --amend --author="name " --no-edit + + - 修改上次提交的用户名和邮箱 + +- git branch + + - git branch + + - 列出本地所有分支 + + - git branch -v + + - 列出本地所有分支 并显示最后一次提交的哈希值 + + - git branch -vv + + - 在-v 的基础上显示上游分支的名字 + + - git branch -r + + - 出上游所有分支 + + - git branch branch-name + + - 新建一个分支,但依然停留在当前分支 + + - git branch -d branch-name + + - 删除分支 + + - git branch --set-upstream-to origin/master + + - 设置分支上游 + + - git branch -m old-branch new-branch + + - 本地分支重命名 + +- git checkout + + - git checkout -b local-branch origin/remote-branch + + - 创建本地分支并关联远程分支 + + - git checkout -b branch-name + + - 新建一个分支,且切换到新分支 + + - git checkout branch-name + + - 切换到另一个分支 + + - git checkout commit-file + + - 撤销工作区文件的修改,跟上次 Commit 一样 + +- git tag + + - git tag -a v1.4 -m 'my version 1.4' + + - 创建带有说明的标签 + + - git tag tag-name + + - 打标签 + + - git tag + + - 查看所有标签 + + - git tag tag-name commit-id + + - 给指定 commit 打标签 + + - git tag -d tag-name + + - 删除标签 + +- git push + + - git push origin master + + - 将本地的 master 分支推送到 origin 主机的master 分支。如果 master 不存在,则会被新建。 + + - git push origin :master +git push origin --delete master + + - 删除远程分支,注意和上面的区别 + + - git push origin --delete tag tag-name + + - 删除远程标签 + + - git push remote branch-name + + - 上传本地仓库到远程分支 + + - git push remote branch-name --force + + - 强行推送当前分支到远程分支 + + - git push remote --all + + - 推送所有分支到远程仓库 + + - git push --tags + + - 推送所有标签 + + - git push origin tag-name + + - 推送指定标签 + + - git push origin :refs/tags/tag-name + + - 删除远程标签(需要先删除本地标签) + + - git push origin dev:master + + - 将本地 dev 分支 push 到远程 master 分支 + +- git reset + + - git reset HEAD + + - 将未 commit 的文件移出 Staging 暂存区 + + - git reset --hard + + - 重置 Staging 暂存区与上次 commit 的一样 + + - git reset --hard origin/master + + - 重置 Commit 代码和远程分支代码一样 + + - git reset --hard HEAD^ + + - 回退到上个 commit + + - git reset --hard HEAD~3 + + - 回退到前 3 次提交之前,以此类推,回退到 n 次提交之前 + + - git reset --hard commit-id + + - 回退到指定 commit + +- git diff + + - git diff file-name + + - 查看文件在工作区和暂存区区别 + + - git diff --cached file-name + + - 查看暂存区和本地仓库区别 + + - git diff branch-name file-name + + - 查看文件和另一个分支的区别 + + - git diff commit-id commit-id + + - 查看两次提交的区别 + +- git show + + - git show tag-name + + - 查看指定标签的提交信息 + + - git show commit-id + + - 查看具体的某次改动 + +- git log + + - git log --pretty=format:"%h %cn %s %cd" --author="Glorze\|高老四" --date=short src + + - 指定文件夹 log + + - git log --pretty=format:"%h %cn %s %cd" --author=Glorze--date=short + + - 查看指定用户指定 format 提交 + + - git log --pretty=oneline file + + - 查看该文件的改动历史 + + - git log --graph --pretty=oneline --abbrev-commit + + - 图形化查看历史提交 + + - git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5 + + - 统计仓库提交排名前 5 + + - git log --author="Glorze" --pretty=tformat: --numstat | awk '{ add += $1 ; subs += $2 } END { printf "added lines: %s removed lines : %s \n",add,subs }' + + - 查看指定用户添加代码行数,和删除代码行数 + +- git rebase + + - git rebase branch-name + + - 将指定分支合并到当前分支 + + - git rebase -i commit-id + + - 执行 commit id 将 rebase 停留在指定 commit 处 + + - git rebase -i --root + + - 执行 commit id 将 rebase 停留项目首次 commit 处 + +- git restore + + - git restore --staged file + + - 恢复第一次 add 的文件,同 git rm --cached + + - git restore file + + - 移除 staging 区的文件,同 git checkout + +- git revert + + - git revert HEAD + + - 撤销前一次 commit + + - git revert HEAD^ + + - 撤销前前一次 commit + + - git revert commit-id + + - 撤销指定某次 commit + +## Git 常见问题处理方式 + +### 代码未完成并且要切换到其他分支 + +- git stash 当前分支 + + - 去其他分支处理事情 + - 回到当前分支还原暂存的代码 git stash apply + - git stash save "修改的信息" + - git stash pop + - git stash list + - git stash apply stash@{0} + +- 及时 commit 代码,不 push + +### 合并其他分支的指定 Commit + +- git cherry-pick 指定commit-id + +## git merge 的三种场景 + +### 快进式合并(fast-forward) + +- 如果顺着一个分支走下去能够到达另一个分支,那么Git在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧 + +### 三方合并 + +- 做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。 + +### 遇到冲突时的合并 + +- 如果在两个分支分别对同一个文件做了改动,Git 就没法直接合并他们。当遇到冲突的时候,Git 会自动停下来,等待我们解决冲突。 + diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/Git.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/Git.xmind" new file mode 100644 index 0000000..9b2ab29 Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/Git.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/UML(Unified Model Language\357\274\214\347\273\237\344\270\200\345\273\272\346\250\241\350\257\255\350\250\200) \345\233\276.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/UML(Unified Model Language\357\274\214\347\273\237\344\270\200\345\273\272\346\250\241\350\257\255\350\250\200) \345\233\276.md" new file mode 100644 index 0000000..2a2fd68 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/UML(Unified Model Language\357\274\214\347\273\237\344\270\200\345\273\272\346\250\241\350\257\255\350\250\200) \345\233\276.md" @@ -0,0 +1,202 @@ +# UML(Unified Model Language,统一建模语言) 图 + +## 定义 + +### 一整套图表组成的标准化建模语言。 + +## UML 的主要目的 + +### 为用户提供现成的、有表现力的可视化建模语言,以便他们开发和交换有意义的模型。 + +### 为核心概念提供可扩展性 (Extensibility) 和特殊化 (Specialization) 机制。 + +### 独立于特定的编程语言和开发过程。 + +### 为了解建模语言提供一个正式的基础。 + +### 鼓励面向对象工具市场的发展。 + +### 支持更高层次的开发概念,如协作,框架,模式和组件。 + +### 整合最佳的工作方法 (Best Practices)。 + +## 分类 + +### 结构图 + +- 类图 + + - 概念 + + - 类图是一切面向对象方法的核心建模工具。类图描述了系统中对象的类型以及它们之间存在的各种静态关系。 + + - 目的 + + - 用来表示类、接口以及它们之间的静态结构和关系。 + + - 常见关系 + + - 泛化(Generalization) + + - 一种继承关系,表示子类继承父类的所有特征和行为。 + - 带三角箭头的实线,箭头指向父类。 + + - 实现(Realization) + + - 是一种类与接口的关系,表示类是接口所有特征和行为的实现。 + - 带三角箭头的虚线,箭头指向接口。 + + - 关联(Association) + + - 是一种拥有关系,它使得一个类知道另一个类的属性和方法。代码体现为「静态变量」 + - 带普通箭头的实线,指向被拥有者。双向的关联可以有两个箭头,或者没有箭头。单向的关联有一个箭头。 + + - 聚合(Aggregation) + + - 一种整体与部分的关系。且部分可以离开整体而单独存在。聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。 + - 带空心菱形的实线,空心菱形指向整体。代码体现为「成员变量」 + + - 组合(Composition) + + - 一种整体与部分的关系。但部分不能离开整体而单独存在,组合关系是关联关系的一种,是比聚合关系还要强的关系。 + - 带实心菱形的实线,实心菱形指向整体。代码体现为「成员变量」 + + - 依赖(Dependency) + + - 一种使用关系,即一个类的实现需要另一个类的协助。 + - 带普通箭头的虚线,普通箭头指向被使用者。 + +- 轮廓图 + + - 概念 + + - 轮廓图提供了一种通用的扩展机制,用于为特定域和平台定制 UML 模型。 + + - 目的 + + - 用于在特定领域中构建 UML 模型。 + +- 组件图 + + - 概念 + + - 描绘了系统中组件提供的、需要的接口、端口等,以及它们之间的关系。 + + - 目的 + + - 用来展示各个组件之间的依赖关系。 + +- 组合结构图 + + - 概念 + + - 描述了一个「组合结构」的内部结构,以及他们之间的关系。这个『组合结构』可以是系统的一部分,或者一个整体。 + + - 目的 + + - 用来表示系统中逻辑上的「组合结构」。 + +- 对象图 + + - 概念 + + - 对象图是类图的一个实例,是系统在某个时间点的详细状态的快照。 + + - 目的 + + - 用来表示两个或者多个对象之间在某一时刻之间的关系。 + +- 部署图 + + - 概念 + + - 描述了系统内部的软件如何分布在不同的节点上。 + + - 目的 + + - 用来表示软件和硬件的映射关系。 + +- 包图 + + - 概念 + + - 描绘了系统在包层面上的结构设计。 + + - 目的 + + - 用来表示包和包之间的依赖关系。 + +### 行为图 + +- 活动图 + + - 概念 + + - 描述了具体业务用例的实现流程。 + + - 目的 + + - 用来表示用例实现的工作流程。 + +- 用例图 + + - 概念 + + - 用例图是指由参与者、用例,边界以及它们之间的关系构成的用于描述系统功能的视图。 + + - 目的 + + - 用来描述整个系统的功能。 + +- 状态机图 + + - 概念 + + - 状态机图对一个单独对象的行为建模,指明对象在它的整个生命周期里,响应不同事件时,执行相关事件的顺序。 + + - 目的 + + - 用来表示指定对象,在整个生命周期,响应不同事件的不同状态。 + +- 交互图 + + - 序列图 + + - 概念 + + - 序列图根据时间序列展示对象如何进行协作。它展示了在用例的特定场景中,对象如何与其他对象交互。 + + - 目的 + + - 通过描述对象之间发送消息的时间顺序显示多个对象之间的动态协作。 + + - 时序图 + + - 概念 + + - 时序图被用来显示随时间变化,一个或多个元素的值或状态的更改。也显示时控事件之间的交互和管理它们的时间和期限约束。 + + - 目的 + + - 用来表示元素状态或者值随时间的变化而变化的视图。 + + - 通讯图 + + - 概念 + + - 描述了收发消息的对象的组织关系,强调对象之间的合作关系而不是时间顺序。 + + - 目的 + + - 用来显示不同对象的关系。 + + - 交互概览图 + + - 概念 + + - 交互概览图与活动图类似,但是它的节点是交互图。 + + - 目的 + + - 提供了控制流的概述。 + diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/UML(Unified Model Language\357\274\214\347\273\237\344\270\200\345\273\272\346\250\241\350\257\255\350\250\200) \345\233\276.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/UML(Unified Model Language\357\274\214\347\273\237\344\270\200\345\273\272\346\250\241\350\257\255\350\250\200) \345\233\276.xmind" new file mode 100644 index 0000000..24cf953 Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/UML(Unified Model Language\357\274\214\347\273\237\344\270\200\345\273\272\346\250\241\350\257\255\350\250\200) \345\233\276.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.md" new file mode 100644 index 0000000..d1d7117 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.md" @@ -0,0 +1,38 @@ +# **万能险(万用寿险)** + +## 基本特点 + +### 可以增减保额 + +### 可以追加保费 + +### 随时部分提取账户价值 + +### 明码标价的结算利率 + +### 灵活 = 不确定性 + +## 基本结构 + +### 期交保费+初始费用 +- 保险公司扣除初始费用作为运营成本 +- 生育作为保单价值 + +### 保单利息 +- 日计息,月复利 + +### 保障成本 +- 从现金价值种扣除 +- 作为寿险、重疾、医疗、意外的承保 + +### 追加保费 +- 可以另外将资金放入保单中 + +### 部分领取 +- 万能险的保单价值是可以随时部分领取的 + +## + +### 保单价值 =A+C+E-B-D-F + +### 万能险是最好计算、最透明的险种 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.mindnode" index aafa3b7..68fdaa8 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\207\350\203\275\351\231\251\345\237\272\346\234\254\346\200\273\347\273\223.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.md" new file mode 100644 index 0000000..1d7b8be --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.md" @@ -0,0 +1,26 @@ +# **两年不可抗辩条款** + +## 是否如实告知 + +### 基本无法作为可信理由 +- 明知自己有病,故意不如实告知 +- 没仔细看,不知道要告知 +- 被业务员误导/隐瞒 + +### 保险公司败诉的常见原因 +- 告知项不明确、不准确,概括性问题 +- 保险公司负责举证 + - 保险公司同时要证明自己尽到对如实告知的提示责任 + +## 未告知内容的严重程度 + +### 判决结果整体倾向投保人 +- 小问题,不足以影响保险公司承保/提高费率的 +- 大问题,足以影响承保/提高费率的 + +## 是否满两年 + +### 具体判定也有不同情况不可定论 +- 开始时间,从合同生效起计算 +- 截止时间,一般指保险事故发生时间,而不是申请理赔时间 +- 事故发生后未及时申请理赔,不利于投保人 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.mindnode" index fbb8214..6e9fb76 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\244\345\271\264\344\270\215\345\217\257\346\212\227\350\276\251\346\235\241\346\254\276.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.md" new file mode 100644 index 0000000..6867cd1 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.md" @@ -0,0 +1,72 @@ +# **个人寿险** + +## 主要责任 + +### 身故责任 +- 意外身故 +- 疾病身故 + - 猝死和寿终正寝其实都属于疾病身故 + +### 全残责任 +- 比如双眼眼球缺失,可以得到和身故一样的赔偿 + +## 概念 + +### 广义 +- 寿险(保的是人) + - 重疾险 + - 医疗险 + - 养老险 + - 教育金 +- 财产险 + - 车险 + - 房屋险 + +### 狭义 +- 以「身故」为赔付标准的保险。 + +## 纯粹寿险 + +### 终身寿 +- 保障终身 +- 不碰免责一定拿到钱 +- 比较贵,适合做大规模的遗产传承 + +### 定期寿 +- 一定时期内的保障,便宜,杠杆高 +- 保费交了可能拿不到钱 +- 相对便宜,主要是给需要抚养的人留钱 +- 意外险包含的猝死责任定义比较严格,且保额有限,建议用定期寿来解决更稳妥。 + +### 两全寿险 +- 除了身故责任,满期也可获得满期金,因此也比单纯的定期寿险贵 + +### 增额终身寿险 +- 保额会增长的终身寿险。时间越长,保额越高。 +- 要么是较贵的「有杠杆」终身寿,要么干脆是「超低杠杆」储蓄类产品 +- 增额终身寿其实跟寿险本质的责任关系不大,更像是一个年金的替代品 +- 缺点 + - 现金流释放太过依赖投保人的主动操作,纪律性不强。 +- 优势 + - 锁定还不错的长期利率,使用又相对灵活。 + +### 与重疾和意外中身故的区别 +- 重疾险中的身故责任,是在没有重疾赔付的前提下身故。 +- 意外险的身故责任,身故原因必须是意外事故 +- 纯寿险,不管是终身寿还是定期寿,只要别碰到「责任免除」条款就会赔付 + +## 个人寿险 + +### 以身故为给付条件的人寿保险,「定向、定量传承」的确定性是最好的 + +### 一份小小的寿险保单,体现的是年迈父母对子女的舍不得,是年轻父母对孩子的放不下 + +### 寿险是,投保人将自己作为被保险人,并指定其受益人,以此来承担家庭责任和分担未来风险的工具 + +## 寿险三大新险种 + +### 投资连结险 + +### 变额年金险 + +### 万能险 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.mindnode" index 856408b..6b9b7dd 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\270\252\344\272\272\345\257\277\351\231\251.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\345\215\225\346\243\200\350\247\206.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\345\215\225\346\243\200\350\247\206.md" new file mode 100644 index 0000000..ee91f4c --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\345\215\225\346\243\200\350\247\206.md" @@ -0,0 +1,48 @@ +# **保单检视** + +## 意义 + +### 回顾现有保单的具体保障 + +### 查看保障是否有缺口 + +### 结合自身和环境的动态变化 + +### 考虑保障是否有优化方向 + +## 整理好所有保单 + +### 表格整理 + +### 保单集中管理 + +## 检查是否按时续保 + +### 短期险及时续保,避免保障空白期 + +### 长期险是否按时缴费 + +## 决定是否调整 + +## + +### 意外险 +- 是否含意外伤残 +- 是否含意外医疗 +- 受益人有没有指定 +- 免责条款有没有看清 + +### 医疗险 +- 社保有没有交 +- 百万医疗险有没有 +- 医院限制是什么 + +### 寿险 +- 保额够不够 +- 受益人有没有指定 + +### 重疾险 +- 保额够不够 +- 定期还是终身 +- 多次还是单次 +- 是否含身故责任 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\345\215\225\346\243\200\350\247\206.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\345\215\225\346\243\200\350\247\206.mindnode" new file mode 100644 index 0000000..0bfcf00 Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\345\215\225\346\243\200\350\247\206.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.md" new file mode 100644 index 0000000..b4e5e9c --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.md" @@ -0,0 +1,71 @@ +# **保险相关法律基础** + +## 遗嘱 + +### 新的优于旧的,公证过的优于未公证过的 + +### 公证遗嘱 + +### 自书遗嘱 + +### 代书遗嘱 + +### 录音遗嘱 + +### 口头遗嘱 + +### 怎样写一份有效力的自书遗嘱(尽量公正遗嘱) +- 遗嘱人必须是具有完全民事行为能力的人 +- 必须由遗嘱人亲笔书写、签名,注明年月日 +- 遗嘱中涉及的财产必须属于遗嘱人的个人财产 +- 遗嘱应当对缺乏劳动能力又没有生活来源的继承人保留必要的财产份额 + +## 继承法 + +### 配偶、子女、父母,都是第一顺位继承人,继承权相同,继承比例平均分配 + +### 第二顺位继承人,有兄弟姐妹、祖父母、外祖父母等。 + +### 有指定受益人的身故受益金不视为遗产,不按照《继承法》来分配 + +## 资产的所有权给出去之后,同时就失去了资产的控制权 + +## 非婚生子女享有与婚生子女同等的权利,任何人不得加以危害和歧视 + +## 身故金受益权高于债权 + +## 人们会考虑一件事是可能还是不可能,但从不会考虑概率问题,这就是保险公司对不确定性风险提供保险的原因。 + +## 保险公司赚钱的秘诀:保险公司卖保险的收入大于潜在的理赔成本。 + +## 外汇 + +### 换汇额度是每人5万美金/年 +- 「每年」是指自然年,从当年的1月1日到12月31日 +- 当年度未用完的额度,不会累积到下一年度 +- 在此期间,不论是:人民币换成外币/外币换成人民币/外币换成外币,加在一起的额度都只有5万美金等值。 +- 个人分拆结售汇 + 5个以上不同个人同日、隔日或连续多日分别购汇后,将外汇汇给境外同一个人或机构。 + +### 外汇分「现钞」和「现汇」 +- 真实的货币就是「现钞」,银行账户里的数字是「现汇」 +- 买外币的时候,「现钞」「现汇」一样价格 +- 卖外币的时候,「现钞」没有「现汇」值 +- 中资银行区分「现汇」和「现钞」,外资银行普遍不区分 + +### 外汇买卖价格不一样 +- 同一时间,买外汇和卖外汇的价格是不一样的 +- 「中间价」就是「买入价」和「卖出价」的平均值,用来参考和结算的 +- 「买入价」和「卖出价」是可以商量的 +- 外汇价格是时刻在变化的 + +### 跨境汇款有很多限制 +- 向境外汇款,不能汇人民币 +- 向境外汇款,时间要慢得多 +- 境外汇款,手续费比较高 + +### 原则 +- 配置外币,最基本的理念是「分散原则」,而不是因为外币比人民币好 +- 风险是不能完全规避的,只能转移或者降低 + +## 骗保是刑事案件 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.mindnode" index 97939e0..a686e8b 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\347\233\270\345\205\263\346\263\225\345\276\213\345\237\272\347\241\200.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.md" new file mode 100644 index 0000000..1f1abf6 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.md" @@ -0,0 +1,61 @@ +# **保险购买原则** + +## 买保险不是目的,转移风险才是最终目标。 + +### 保险是一种风险管理工具。 +- 避免窘境 +- 完成目标 + +### 目标永远是获得保障 + +## 关注产品之间的本质区别,而不是细枝末节。不要无休止的在挑选产品上浪费时间。 + +### 年金还是重疾? + +### 多次赔付还是单次赔付 + +### 含不含轻疾 + +## 买保险不是为了「占便宜」,羊毛可以薅,但别因小失大。 + +## 不存在绝对和统一的「哪个更好」,分析自己的情况,尊重自己的偏好。 + +## 有顾虑的时候,再多思考一步。 + +### 保单的安全性,比你在银行的理财存款还要高。 + +### C-ROSS,偿付能力二代监管规则,与欧盟一起领先全球 + +### 大到不能倒 +当一些规模极大或在产业中具有关键性重要地位的企业濒临破产时,政府不能等闲视之,甚至要不惜投入公帑相救,以避免那些企业倒闭后所掀起的巨大连锁反应造成社会整体更严重的伤害,这种情况称为 「大到不能倒」。 + +## 保险合同属于射幸合同 +射幸合同(Aleatory Contract),就是指合同当事人一方支付的代价所获得的只是一个机会,对投保人而言,他有可能获得远远大于所支付的保险费的效益,但也可能没有利益可获;对保险人而言,他所赔付的保险金可能远远大于其所收取的保险费,但也可能只收取保险费而不承担支付保险金的责任。保险合同的这种射幸性质是由保险事故的发生具有偶然性的特点决定的,即保险人承保的危险或者保险合同约定的给付保险金的条件的发生与否,均为不确定。 + +### 合理配置保险 + +### 做好如实告知 + +### 定期检视保障 + +## 十条成熟建议 + +### 父母保障优先做足,不要只给孩子买,或者孩子买很多,大人买不够 + +### 保障类做好,再考虑教育金/退休金 + +### 缴费期尽量不要超过退休年龄 + +### 重疾保额覆盖年收入3-5年即可 + +### 40岁优化空间小,尽量一次到位 + +### 用好「附加投保人豁免」 + +### 孩子的选择比大人多,优化空间大 + +### 先买意外险和医疗险,决策难度最小 + +### 预算不够就要做出取舍 + +### 保障是个动态变化的过程 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.mindnode" index 2e98f56..bd06557 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\344\277\235\351\231\251\350\264\255\344\271\260\345\216\237\345\210\231.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\204\277\347\253\245\344\277\235\351\231\251.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\204\277\347\253\245\344\277\235\351\231\251.md" new file mode 100644 index 0000000..461d202 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\204\277\347\253\245\344\277\235\351\231\251.md" @@ -0,0 +1,17 @@ +# **儿童保险** + +## 知识概念 + +### 早产 +- 妊娠满 28 周至不足 37 周的分娩者 + +### 低出生体重 +- 出生体重低于 2500g(5 斤) + +## 少儿意外险相关产品 + +### 平安少儿意外险 + +### 萌宝保 + +## 新生儿满了28天,趁着没有异常情况,能过健康告知的,赶紧把保险买了。 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\204\277\347\253\245\344\277\235\351\231\251.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\204\277\347\253\245\344\277\235\351\231\251.mindnode" new file mode 100644 index 0000000..b78c808 Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\204\277\347\253\245\344\277\235\351\231\251.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.md" new file mode 100644 index 0000000..0057c1f --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.md" @@ -0,0 +1,234 @@ +# **关哥说险(资产配置是反人性的)** + +## 保险的分类 + +### 寿险与财产险 +- 广义 + - 寿险(保的是人) + - 重疾险 + - 医疗险 + - 养老险 + - 教育金 + - 财产险 + - 车险 + - 房屋险 +- 狭义 + - 以「身故」为赔付标准的保险。 + +### 保障的目的 +- 重疾险 +- 教育险 +- 年金险 + +### 定价策略角度 +只有投连险是真正参与市场的。其他类别的保险,是远离市场风险的。 +- 分红险 +- 万能险 +- 投资连结险 + - 一部分保费用于风险保障 + - 剩下的资金进入投资账户 +- 定价在前,赔付滞后,但赔付又决定着定价,所以精算师们只能利用过去的数据经验或高深的精算模型对未来发生的成本进行预测。 + +## 一款产品的基本形态 + +### 名字 +- 产品名称 + - 比较喜庆的字眼,跟保障责任相关 +- 保障责任 + - 主要的保障责任,重疾险、百万医疗等 +- 定价模式 + - 括号里面的内容,万能、分红等,没有基本是传统保险 + +### 犹豫期 +- 投保成功后,有若干天的「反悔」时间。在犹豫期内,退保可以退回全部保费,没有损失。 +- 犹豫期后退保,就只能退回现金价值。 +- 常见犹豫期 10 天/15 天/20 天 +- 收到保单合同开始计算 + +## 健康告知 + +### 诚信原则 +- 投保时一定要如实告知 + +### 询问告知 + +### 临床医学 +研究疾病的病因、诊断、治疗和预后。有些健康问题,之所以医生不治疗或不干预,是因为这个问题「现在、当下」还没有发展到影响到健康。 + +### 保险医学 +对被保人群发病率及死亡率的评估、预测,注重长期预后。它要预估的是一个人「未来几十年甚至一生」患病的可能性。 + +### 补充告知 +在投保时(无论有意无意)未如实告知,承保之后才想起有既往病史或住院史未告知,此时再告知保险公司,重新进行核保,进行风险评估 + +## 企业风险 + +### 政治风险 +- 财富即是原罪,遇到权力也不好使 + +### 道德风险 + +### 婚姻风险 + +### 企业经营不善风险 + +### 资金链断裂风险 + +### 企业管理者身故风险 + +## 核保 + +### 核保结果 +- 「标准体」承保 +- 「加费」承保 +- 「除责」承保 + - 把某些特定的保险责任剔除掉 +- 「延期」承保 +- 拒保 + +### 带病核保技巧 +- 多看看不同保险公司的核保标准 +- 先买不能核保的,再买能核保的 +- 同时投保多家保险公司 + +### 是否提前体检 +- 尽量先投保,再体检 +- 如果核保需要,有针对性的检查即可 +- 一定要定期体检 + +## 条款和合同 + +### 区别 +- 条款是固定,直接决定了这份保险产品是保什么的 +- 合同包含个人信息,每个人不一样 + - 合同号 + - 保费 + - 受益人 + - 投保申请扫描件 + - 产品说明书 + - 现金价值表 + - 费率表 + - 服务指南 +- 保险条款只是保险合同的一部分,但也是核心的部分。 + +### 买保险之前,好好看条款;买保险之后,定期看合同。 + +### 保单的「复杂」,不是为了挖坑,既为了保护客户,也为了保护保险公司。 + +### 会看保险条款,目的是明确细节,规避风险。别犯被迫害妄想症。 + +### 买保险除了看保障责任,也要重点看免责条款。 + +## 保险费率的厘定 + +### 【损失】预计未来的赔款+直接理赔的费用; + +### 【费用】固定费用和变动费用;主要是一些手续费、保险公司的运营费,税和保险保障基金; + +### 【风险加成】应付未来真实损失比预期高的风险准备金; + +### 【利润】保险公司或投资人合理的利润; + +## 自付和自费的区别 + +### 如果用的药不在社保目录内,则费用需要完全由自己承担,俗称自费药。 + +### 如果这种药在社保目录内,社保给报销,但报销是有比例的。比如这个药100块,报销比例是50%,那么社保报销50块,剩下的50块由自己承担,就叫自付。 + +## 退保须知 + +### 金钱上的损失:退保只能拿回来一小部分保费 +- 现金价值(保单退保时能退多少钱) + +### 保障的缺失:保险责任从退保那一刻停止 + +### 再投保的劣势:健康条件和年龄都不如当年了 + +## 保单缴费期限 + +### 等待期 +- 防范带病投保 +- 等待期内非意外出险无保障 +- 常见等待期 30 天/90 天/180 天 +- 从保单生效期开始计算 + +### 宽限期(没问题) +- 概念 + - 晚一点交保费的权利 + - 宽限期内保障仍然有效 + - 常见宽限期 60 天 + - 从约定缴费日次日开始计算 +- 权益 + - 保障不变 + - 出险会赔 + - 出险时扣掉保费 + +### 中止期(2年),还有机会 +- 保单冻结 +- 没有保障 +- 出险不赔 +- 可以申请复效 +- 可补缴保费但有可能重新核保 + +### 终止期(彻底没戏) +- 保单彻底解除 +- 返还现金价值 +- 不能复效 +- 只能重新投保 + +## 缴费期的选择 + +### 杠杆 +- 缴费时间越长,每年的保费越低,杠杆就越高,意味着我们可以用更少的钱去转移更高的风险。 +- 用「较少的保费」撬动「较大的保额」 + +### 保障类的险种,首选拉长缴费时间,保证足够高的杠杆。 + +### 交费期间尽量不要超过退休年龄,保证交费能力 + +### 有足够的理由,也可以选短期交费 + +## 买险顺序 + +### 省保费 +- 优先买重疾 + +### 决策难度 +- 意外险,便宜 + +### 只买一份 +- 百万医疗 + +## 保单角色 + +### 投保人 +- 保单持有人/买保单的人/交保费的人 +- 拥有保单的【所有权】和大部分的【控制权】 + +### 被保险人 +- 享受保单的保障/保额所有人 +- 拥有保单的【使用权】和小部分的【控制权】 +- 被保险人一般不能更改 + +### 受益人 +- 获得身故赔偿金 +- 拥有保单的【受益权】 + +### 承保人 +- 承保人就是保险公司,主要认保单 + +## 理赔数据 + +### 理赔难不难,和公司大小无关 + +### 重疾保额普遍不足 + +### 每个百万医疗险都能消灭一例XX筹 + +### 癌症理赔,仍高居榜首 +- 男性最高发的是肺癌 +- 女性最高发的是乳腺癌 + +### 重疾理赔集中在40-59岁 + +### 疾病身故占比高 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.mindnode" index 4802a11..c47b96b 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\263\345\223\245\350\257\264\351\231\251\346\200\273\344\275\223\347\273\223\346\236\204.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\273\350\200\201\351\207\221.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\273\350\200\201\351\207\221.md" new file mode 100644 index 0000000..9ce828b --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\273\350\200\201\351\207\221.md" @@ -0,0 +1,37 @@ +# **养老金** + +## 养老金替代率 + +### 退休前工资是1万元,退休后工资是8000,养老金替代率就是8000/10000=80% + +## 基础养老 + +### 维持基本生活需求,吃喝拉撒睡。 + +### 方式 +- 社保养老金 + - 工资越高,未来社保养老金能够提供的替代率越低。 + - 女性比同条件男性退休金低 + - 很多公司可能不按实际工资缴纳,甚至不缴纳;还有很多人自由职业,中间断交,或者根本不交养老保险。 +- 商业养老年金 + - 如何用养老年金保险做补充 + - 确定养老金目标(通货膨胀) + - 计算社保养老金的差额 + - 倒推需要多少年金 + +### 特点 +- 长期、稳定的现金流 +- 不参与市场风险 +- 不需要自己操作 +- 数额相对确定 +- 收益率一般 +- 用来托底,未必能帮你过得多好,但能保证你不会过得很惨 + +## 品质养老 + +### 除了基本需求之外,还追求点品质或精神层面的满足。 + +### 方式 +- 房租收入 +- 理财收入 +- 子女孝敬 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\273\350\200\201\351\207\221.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\273\350\200\201\351\207\221.mindnode" new file mode 100644 index 0000000..41d31af Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\205\273\350\200\201\351\207\221.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.md" new file mode 100644 index 0000000..fce5af0 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.md" @@ -0,0 +1,56 @@ +# **分红险** + +## 注意事项 + +### 分红≠利息,不能用投资收益的概念去看待分红,更不是用计算器能算出来的。 + +### 分红不是目的,特别是保障类险种,一定要弱化分红的作用。 + +### 不要用获利思维去评价一个止损工具 + +## 分红的来源 + +### 死差 +- 实际死亡率和预定死亡率的差别产生的利益或者损失 + 比如按照生命表,每 1 万个人里有 1 个赔付的。而保险公司有 100 万张保单,按比例应该有 100 个赔付。但是年底一看,今年只有 50 个赔付,少赔那 50 个,就是「死差」。当然,如果年底一看,赔了 150 个,那就变成「死差损」,保险公司就自己担着了。 + +### 利差 +- 保险公司的资金实际收益情况和预估收益情况的区别 + 比如按照预定利率,资产收益率是 3.5%,但年底一看,实际收益率是 5%,那多出来的 1.5%,就是「利差」。 + +### 费差 +- 保险公司实际运营费用和预计运营费用的差别。 + 比如今年预估运营成本 2000 万,年底发现钱没花完,剩了 500 万,那这个就是「费差」。 + +## 分红与利息的区别 + +### 利息 +- 本金 * 利率 = 利息 + +### 分红 +- 保险公司这一年下来「三差」带来的实际利润,分给保单持有人。 + +## 金融业三驾马车 + +### 保险 + +### 银行 + +### 证券 + +## 分红的类别 + +### 英式分红(保额分红) +保险公司的分红险可分配盈余不直接给你,而且自动回到保单里,又买了一点这份保险,而这份保单无论是保额,还是现金价值,都随之增加。 + +### 美式分红(现金分红) +- 分红是直接给出来,在保单之外,是可以直接拿走的 + - 现金领取 + - 累积生息 + - 万能金账户 + - 增额交清 + - 交保费 + +## 储蓄险的本质 + +### 用资金的短期控制权,换取对资金的长期控制权 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.mindnode" index 6e95e92..fd6c801 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\210\206\347\272\242\351\231\251\346\200\273\347\273\223.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\345\255\246\347\237\245\350\257\206\346\261\207\346\200\273.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\345\255\246\347\237\245\350\257\206\346\261\207\346\200\273.md" new file mode 100644 index 0000000..8b83b53 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\345\255\246\347\237\245\350\257\206\346\261\207\346\200\273.md" @@ -0,0 +1,162 @@ +# **医学知识汇总** + +## 子宫肌瘤 + +### 女性生殖器官中最常见的一种良性肿瘤,又称子宫平滑肌瘤,病因不明,可能与激素有关。 + +### 主要表现为子宫出血、腹部包块、压迫、疼痛 、白带增多、不孕、流产、贫血、红细胞增多、低血糖等,腹部和盆腔超声检查可以辅助诊断。 + +## 高血压 + +### 高血压是指(收缩压≥140mmHg,舒张压≥90mmHg),可伴有心、脑、肾等器官的功能或器质性损害的临床综合征。 + +### 级别 +- 一级:收缩压≥140mmHg,舒张压≥90mmHg +- 二级:收缩压≥160mmHg,舒张压≥100mmHg +- 三级:收缩压≥180mmHg,舒张压≥110mmHg + +## 乙肝 + +### 乙肝病毒携带者是指血液检测单独乙肝表面抗原(HBsAg)阳性,但无肝炎症状和体征,各项肝功能检查正常,经半年观察无变化者。 + +### 小三阳 +- 乙肝病毒的免疫学指标:即乙肝表面抗原(HBsAg)、乙肝e抗体(HBeAb)、乙肝核心抗体(抗HBC)三项阳性。 +- 乙肝病毒复制受到了抑制,其传染性较弱。 + +### 大三阳 +- 乙肝病毒的免疫学指标:即乙肝表面抗原(HBsAg)、乙肝e抗原(HBeAg)、乙肝核心抗体(抗HBC)三项阳性,乙肝病毒复制体比较活跃。 + +## 三高 + +### 高血压 +- 原发性高血压 + 单纯血压升高,并无其他确切病因,90%以上的患者都属于此类 +- 继发性高血压 + 指其他疾病导致的血压升高,病因祛除后,血压可能恢复原状。 +- 级别 + - 正常血压:收缩<140,舒张<90 + - 轻度1级高血压:140<收缩<159,90<舒张<99 + - 中度2级高血压:160<收缩<179,100<舒张<109 + - 重度3级高血压:180<收缩,舒张>110 + +### 高血糖(糖耐量异常) +- 血糖指标 + - 空腹血糖 + - 餐后两小时血糖 +- 血糖类型 + - 正常血糖:3.9<空腹血糖<6.1,餐后两小时<7.8 + - 糖耐量异常:6.1<空腹血糖<6.9,7.9<餐后两小时<11.0 + - 糖尿病:空腹血糖>7.0,餐后两小时>11.1 +- 糖尿病种类 + - I型糖尿病 + 体内无法产生足够胰岛素,必须用胰岛素治疗,多发生于儿童和青少年; + - II型糖尿病 + 体内产生胰岛素能力并未完全丧失,可用药物刺激体内胰岛素分泌。多发于成年人,占糖尿病患者90%以上。 + +### 高血脂 +- 指标 + - 胆固醇 + - 甘油三酯 + - 低密度脂蛋白胆固醇 + - 高密度脂蛋白胆固醇 + +## 贫血 + +### 指人体外周血红细胞容量减少,低于正常范围下限的一种常见的临床症状。 + +### 贫血分型中,最常见的是缺铁性贫血,它是由不充分的铁供应或失血过多引起的。 + +### 贫血程度(血红蛋白(Hb)(克/100毫升)评估) +- 女性:轻度 9.6 - 11.0;中度 8.0- 9.5;重度 < 8.0; +- 男性:轻度 11.1 - 12.5;中度 9.5- 11.0;重度 < 9.5。 + +## 癌症基础知识 + +### 具有普遍性的癌症风险因素 +- 年龄 +- 吸烟 +- 酗酒 +- 肥胖 + +### 常见癌症的风险因素 +- 肺癌 + - 吸烟 + - 室内煤烟 + - 氯气 + - 其他 + - 家族癌症史 + - 每周高温油炸食物 + - 肺结核 + - 二手烟 + - 厨房油烟 + - 肺部疾病 + - 慢性支气管炎 +- 乳腺癌 + - 遗传因素 + - 生育、哺乳因素 + - 年龄 + - 肥胖 + - 环境因素和生活方式 + - 良性乳腺疾病 + - 熬夜 +- 胃癌 + - 胃病史、胃癌家族史 + - 盐渍食品、饮酒 + - 高盐饮食、烫食 + - 吸烟、饮食不规律 + - 感染幽门螺旋杆菌 +- 肝癌 + - 肥胖 + - 糖尿病 + - 乙肝、丙肝病毒 + - 酗酒 +- 甲状腺癌 + - 家族遗传 + - 放射性照射 + - 食盐中过多摄入的碘 + - 甲状腺结节是指在甲状腺内的肿块,可能与碘摄入量、肿瘤或炎症有关。 + - 主要表现为甲状腺肿大,触诊时可扪及大小不等的多个结节,结节的质地多为中等硬度,可通过甲状腺超声、甲功等确诊; + - 甲状腺结节分级(彩超) + - TI-RADS0级:临床疑似病例超声无异常所见,需要追加其它检查。 + - TI-RADS1级:阴性结果,未发现异常病变,超声显示腺体无肿大,回声正常,无结节、亦无囊肿或钙化。 + - TI-RADS2级:检查所见为良性,恶性肿瘤风险为0%,均需要临床随访。 + - TI-RADS3级:可能良性,恶性肿瘤风险为<5%,一般定期观察即可。 + - TI-RADS4级:恶性的可能比例为5~80%需要结合临床诊断,一般建议超声引导下穿刺活检。 + - 4a:恶性可能性较低(5~10%)。如活检良性结果可以信赖,可以转为半年随访。 + - 4b:恶性可能性较高(10~80%)。 + - TI-RADS5级:高度可能恶性,几乎可以肯定。恶性可能性≧95%,应采取积极的诊断及处理。 + - TI-RADS6级:已经过活检证实为恶性,用于患者接受新辅助化疗、手术肿物切除。 + - 甲状腺超声评级,TI-RADS 分类 + - 0类:甲状腺弥漫性病变,无结节,需要实验室等检查进一步诊断,如桥本甲状腺炎和亚急性甲状腺炎等; + - 1类:正常甲状腺,无结节,或手术全切的甲状腺复查(无异常发现者); + - 2类:典型而明确的良性结节,如腺瘤和囊性为主的结节; + - 3类:不太典型的良性结节,如表现复杂的结节性甲状腺肿,恶性风险小于5%; + - 4类:可疑恶性结节,4类再分成4a、4b和4c亚型,恶性风险5%~85%; + - 5类:是典型的甲状腺癌。 +- 宫颈癌 + - 病毒感染 + - 吸烟 +- 鼻咽癌 + - EBV病毒感染 + - 吸烟 + - 家族风险 + +## 乳腺结节 + +### 常见于乳腺增生(可形成乳腺囊肿)及乳腺肿瘤性疾病,包括乳腺良性肿瘤(如乳腺纤维瘤、分叶状肿瘤等)以及乳腺恶性肿瘤(乳腺癌); + +### 通过乳腺B超,钼钯等检查诊断 + +### 乳腺超声评级,BI-RADS 分类 +- 0类:超声检查不能全面评价的病变。需要补充其他相关影像检查,或者需要结合以前的检查结果进行对比来进一步评估。 +- 1类:阴性结果,超声检查未见异常的表现,亦即正常乳腺; +- 2类:良性病变,可基本排除恶性,6-12个月定期超声复查。 +- 3类:可能是良性病变,建议短期随访(一年以内,一般建议3~6个月),恶性率一般 <2%。如连续2-3年稳定,或肿块缩小,消失,可改为BI-RADS2级。要是肿块增大,向恶性征象变化,则改为BI-RADS 4级。 +- 4类:可疑恶性病变。4类再分成4a、4b和4c亚型,恶性风险3%~90%;建议穿刺活检:包括空心针穿刺活检或手术活检等 +- 5类:高度可能恶性。恶性可能性≧95%,应该开始进行确定性治疗 + +## 甲亢 + +### 甲状腺功能亢进,是由于甲状腺合成释放过多的甲状腺激素,造成机体代谢亢进和交感神经兴奋,引起心悸、出汗、进食和便次增多和体重减少的病症。 + +### 常常同时有突眼、眼睑水肿、视力减退等症状,可同时伴有甲状腺不同程度的肿大,一般通过超声和甲功的检查可确诊。 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\345\255\246\347\237\245\350\257\206\346\261\207\346\200\273.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\345\255\246\347\237\245\350\257\206\346\261\207\346\200\273.mindnode" new file mode 100644 index 0000000..07f6dad Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\345\255\246\347\237\245\350\257\206\346\261\207\346\200\273.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.md" new file mode 100644 index 0000000..b6a928e --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.md" @@ -0,0 +1,80 @@ +# **医疗险** + +## 高端医疗 + +### 医疗品质 +- 你买的产品保险公司有对接的高端医疗服务 +- 你所在公司给你参保的团险,是高端医疗险 +- 你自己购买高端医疗服务的个人健康险产品 + +### 医疗资源 + +### 大多数提供高端医疗服务的机构,本身并不是保险公司,而是高端健康险管理服务提供商 + +### 百万医疗险并不是真正的高端医疗险 + +## 意外医疗 + +## 百万医疗险 + +### 特点 +- 高保额 +- 不限社保 +- 各种附加服务 +- 价格便宜 +- 报销型保险 + - 先自己付钱治病,然后拿花费的医疗费用相关材料到保险公司报销。 + +### 注意事项 +- 1-2万免赔额 +- 先花钱再报销 +- 代替不了重疾险 +- 不要相信保证续保 +- 百万医疗险做不成终身的,是因为长期医疗数据不完善,没有官方可参考数据。 + +### 主要责任 +- 住院部分的费用 +- 特殊门诊 +- 1 万元免赔额 + +### 相关产品 +- 尊享 e 生 2019 +- e 生保(保证续保版) +- 定心丸(本人投保) + - 5 年共享 1 万免赔额、涵盖医院范围广 + - 5 年合同,是真正意义上的长期险 + - 可投保年龄短,最高只到 49 岁 + - 价格偏贵 + - 没有智能核保 +- 好医保长期医疗 +- 微医保长期医疗 + +### 基础保障 +- 住院医疗 +- 住院前后门急诊 +- 门诊手术 +- 特殊门诊 + +## 医疗险 + +### 赔付标准 +- 住院 +- 百万医疗险最关键的赔付标准就是「住院」 + +### 优势 +- 保额高 +- 相对重疾险,用到的可能性比较大 +- 报销范围比医保广 +- 便宜,决策容易 +- + +### 不足,劣势 +- 免赔额高 + - 大多数百万医疗险,免赔额都设置在1万元左右。需要自己承担。 +- 管大不管小 + - 大病报销多,小病报销少,不合适 +- 续保不确定性大 + - 百万医疗并不是重疾险的替代品,而是它的有力搭档。 +- 无法补偿医疗费用之外的花费 + - 医院的收费单花多少赔多少,其余自行承担 +- diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.mindnode" index 6c87e81..870c276 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\214\273\347\226\227\351\231\251\346\200\273\347\273\223.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\236\203\345\234\276\345\210\206\347\261\273.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\236\203\345\234\276\345\210\206\347\261\273.mindnode" deleted file mode 100644 index cd4ed61..0000000 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\236\203\345\234\276\345\210\206\347\261\273.mindnode" and /dev/null differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.md" new file mode 100644 index 0000000..e6797cc --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.md" @@ -0,0 +1,107 @@ +# **年金险选购指南** + +## 为什么会有年金险这个东西 + +### 财产的3项权益 +- 所有权 +- 控制权(你的钱不一定是你来控制) +- 使用权(收益权) + +### 保险的三权 +- 投保人拥有保单的所有权和大部分控制权 +- 受益权给谁,也是投保人来定 + +## 两个本质(每年给钱) + +### 定时、定量、定向的现金流 +- 定时:什么时候去释放现金流是确定的 +- 定向:现金流给谁是确定的 +- 定量:领多少钱是确定的 + +### 现在的钱挪到未来花 + +## 年金险收益如何衡量 + +### 预定利率 +- 现金价值 + - 一张保单去保险公司能退出来的钱数就是保单的现金价值 + - 我们俗称的「退保金」 +- 现金价值放在保单中要给利息,这个利息率是就是保单的预定利率。 + - 一般来说,在其他条件相同的情况下,预定利率越高,产品价格越便宜/现金价值增值越快。 +- 现金价值以什么样的速度增长 + +### IRR(实际收益率) +- 固化的资产量即现金价值 +- 拿出来的资产量即领取的现金流 +- 综合以上两者计算年收益率 +- 内部收益率,就是资金流入现值总额与资金流出现值总额相等、净现值等于零时的折现率。简单的理解为收益率,越高越好。 +- IRR 就是实际收益率,与预定利率相对。 + +## 现金价值低好,还是高好? + +### 理财纪律性角度 +- 保单现金价值高,退保就没啥损失 + +### 财产分割角度 + +## 年金险的3种形式 + +### 教育金 +- 给孩子攒上学钱的 +- 前提 + - 家庭有基本保障,重疾医疗意外都先买买好。 + - 手里有点闲钱,交保费不会影响正常的生活质量。 +- 注意事项 + - 教育金大概交多少钱,交多长时间 + - 预定利率 + - 注意孩子投保年龄 + +### 养老金 +- 退休之后拿来养老的 + +### 不定向年金(即期年金) +- 从买完第5年开始释放现金流 + +## 相关产品 + +### 相伴一生 + +### 福禄一声 + +## 年金险应用的5个案例 + +### 预防二代败家 + +### 解决扶弟魔 + +### 重组家庭问题 + +### 遗产纠纷问题 + +### 单身主义养老问题 + +## 成熟小建议 + +### 保障类产品优先考虑,万万不可裸奔买年金 + +### 缴费期不宜过长 + +### 给钱留出「长大」的时间 + +### 务必安排好「投保人、被保险人、受益人」 + +### 早领钱/晚领钱怎么选?认清自己是「强需求」还是「弱需求」 +- 强需求 + - 能确定下来年金释放出来的钱,什么时候用、给谁用、用多少 + - 教育金、退休金 + - 到用的时候再开始领生存金 + - 早期现金价值低 +- 弱需求 + - 不确定什么时候用,有需要就取点出来,不用就放着 + +### 万能金账户要注意投保人和被保人的设定 +- 万能险的投保人和被保险人,一定要和年金险保持一致 + +### 年金是用来「托底」的资产 + +### 买完定期「复习」,不要冲动买,更不要冲动退 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.mindnode" index c08db9d..a98763d 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\345\271\264\351\207\221\351\231\251\351\200\211\350\264\255\346\214\207\345\215\227.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.md" new file mode 100644 index 0000000..adf9851 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.md" @@ -0,0 +1,102 @@ +# **意外险** + +## 概念 + +### 意外伤害 +意外伤害事故:指外来的、突发的、非本意的、非疾病的使身体受到伤害的客观事件。 + +## 意外险种类 + +### 综合意外险 +- 关于电动车的注意事项 + - 我们平时骑的所谓「电动车」其实都属于电动摩托车或者叫电动轻便摩托车,这类车属于机动车 + - 机动车按照相关规定,需要考取驾驶证,否则基本所有的意外险免责条款都有「无证驾驶」这一目 + +### 交通意外险 +- 航空意外险 +- 公共交通(火车、轮船、公共汽车、出租车)意外险 +- 私家车意外险 + +### 学平险(中小学生平安保险) + +### 电梯意外险 + +## 保险(主要)责任 + +### 意外身故 +- 杠杆高 +- 没有等待期 +- 无需健康告知,一般为职业限制 + +### 意外伤残 +https://baike.baidu.com/item/%E4%BA%BA%E8%BA%AB%E4%BF%9D%E9%99%A9%E4%BC%A4%E6%AE%8B%E8%AF%84%E5%AE%9A%E6%A0%87%E5%87%86/18913747?fr=aladdin +- 与意外身故共用保额,按伤残等级比例赔付 +- 共分为十级,一级最高,十级最低,每一级对应不同的赔付比例 +- 在意外伤残的标准里,如果手指重新接上了,那就不算「双手缺失/丧失功能」,会得不到、或者只能得到很少的赔偿。 + +### 意外医疗 +- 意外住院 +- 意外门诊 +- 住院补贴 +- 注意事项 + - 时间限制 + - 赔付意外伤害事故发生之日起180日内(含第180日)的医疗费用 + - 赔付额度不会特别高 + - 赔付范围区分有无社保 + - 大多数意外医疗都是限定社保范围内的 + - 免赔额 + - 赔付比例 + - 只负责意外导致的受伤或疾病,顶多包含突发性疾病 + - 意外险的大部分成本都在意外医疗上,意外医疗责任也是意外险无法做长期的原因,长期意外险大多数不包括意外医疗 + - 一般情况下,包含门诊保障 + +### 猝死 + +### 附加服务 + +## 特点 + +### 是变化快。意外险很少能长期销售 + +### 每年都要重新买 + +### 针对的是意外导致的问题 + +## 保障人群 + +### 成年人 + +### 未成年人 + +### 老年人 + +## 旅游意外险 + +### 保障责任 +- 意外身故+意外医疗(叠加赔付) +- 紧急救援服务 + - 旅行救援(一般限于国内) + - 医疗救援 + - 国内医疗救援 + - 国际医疗救援 + - 电话医疗咨询 + - 医疗机构推荐、介绍和建议 + - 协助、安排就医住院 + - 医疗翻译服务工作 + - 住院期间医疗费用的担保和/或垫付,及住院期间医疗情况的跟踪、观察和监控 + - 递送必需药物和医疗用品 + - 安排紧急医疗转送 + - 安排医疗转运回本国或常住国 + - 安排遗体运送回本国或常住国或安排在身故地安葬 + - 紧急口讯传递 +- 损失赔偿 + - 特点 + - 损失不会伤筋动骨 + - 保额不会太高 + - 主要是心理安慰 + - 理赔需要提供相应材料 + - 分类 + - 航班延误 + - 行李延误/丢失 + - 旅行证件丢失 + - 随身财产丢失 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.mindnode" index bf04cf7..57d12fe 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\346\204\217\345\244\226\351\231\251.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.md" new file mode 100644 index 0000000..722e13b --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.md" @@ -0,0 +1,68 @@ +# **社会保险** + +## 医疗保险 + +### 职工基本医疗保险 +- 企业和个人共同缴费 + 自己交的部分进医保卡,公司交的部分进社会统筹。 + +### 城乡居民基本医疗保险 +- 城镇居民基本医疗保险 + - 个人缴费、政府补助 +- 新型农村合作医疗保险 + - 个人缴费、政府补助 + +### 医保异地就医 +- 异地就医前先备案 + - 异地就医是指「参保地点」和「就医地点」不一致 + - 包括跨省异地,也包括省内异地。 +- 备案需要什么资料 + - 1、异地安置:「户口本首页」和「常驻人口登记卡」的电子图像。 + - 2、异地转诊:转诊转院证明 + - 3、异地长期居住:居住证照片 + - 4、常驻异地工作:参保单位派出证明、异地工作单位证明和员工劳动合同。 +- 社保局电话:12333 + +## 养老保险 + +## 生育保险 + +## 工伤保险 + +## 失业保险 + +## 优势(广覆盖) + +### 投保几乎没有门槛 + +### 费率一致,不分年龄性别 + +### 保证终身续保 + +### 保障范围和报销比例逐渐在拓宽,报销上限比较大 + +### + +### + +## 特点 + +### 低水平、广覆盖 + +### 现在没有真正能终身续保的商业医疗险,只有续保可能大小的区别。 + +### 对中低收入群体来讲,社会医保绝对是福音——因为社会医保是国家机制,不需要盈利。 + +### 社保是很好,但是仍然存在很多限制,解决医疗费用的作用有限,也满足不了高质量医疗的需求。 + +## 劣势(低水平) + +### 社保报销范围、报销比例、住院时间都有限制 + +### 住院管的多,门诊管的少 + +### 只能报销,没有补偿 + +### 受地域限制影响较大 + +### diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.mindnode" index d28f528..8e6fd3a 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\231\251\346\200\273\347\273\223.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.md" new file mode 100644 index 0000000..a287cf9 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.md" @@ -0,0 +1,77 @@ +# **保障体系** + +## 社会医保 + +### 职工医疗保险 +- 公司和个人一起缴费,强制 + +### 城乡居民保险 +- 城镇居民医疗保险 +- 新农合 + +## 公司团险 + +### 寿险 + +### 重疾险 + +### 医疗险 + +### 意外险 + +### 退休年金 + +### 特点 +- 好不好看公司,一般5人成团 +- 可以带家人,费率便宜 +- 保额有限 +- 离职终止 + +## 商业保险 + +### 寿险 + +### 重疾险 + +### 医疗险 + +### 意外险 + +### 教育金 + +### 退休年金 + +### 特点 +- 自救行为 +- 风险转移 +- 有「杠杆」 +- 需求为先 + +## 其他资产 + +### 房产 + +### 理财 + +### 存款 + +### 特点 +- 风险自担 + +## 社会力量 + +### 相互抱 + +### 滴滴互助 + +### 美团互助 + +### 水滴筹等各种筹钱平台 + +### 慈善基金平台 + +### 特点 +- 他救行为 +- 很少的金钱成本 +- 不十分保证效果 +- 没有隐私性 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.mindnode" index 88e4ee8..a97a63b 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\347\244\276\344\274\232\344\277\235\351\232\234\344\275\223\347\263\273.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.md" new file mode 100644 index 0000000..dfa917b --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.md" @@ -0,0 +1,43 @@ +# **职场的可迁移能力** + +## 了解自己、了解职场 + +### 「契合度」 + +### 「匹配度」 + +### 避免盲目与迷茫,得「自知」。 + +## 兴趣、能力、价值观 + +### 自己喜欢的,自己能做的,自己看重的。 + +## 「可迁移能力」 + +### 「带得走的能力」 +- 任务管理 + - 协调能力 + - 高效执行 + - 计划组织 +- 团队管理 + - 识人用人 + - 激励他人 + - 发展他人 +- 思维能力 + - 学习领悟 + - 问题解决 + - 创新能力 +- 人际能力 + - 沟通能力 + - 团队合作 + - 关系管理 + +## 「自评」和「他评」(乔哈里窗) + +### 你知道你有,别人也知道你有的能力 + +### 你知道你有,但别人不知道你有的能力 + +### 你自己觉得没有,但别人认为你有的能力 + +### 自己和别人都觉得你没有的能力 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.mindnode" index 64b22a4..ea9158a 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\350\201\214\345\234\272\347\232\204\345\217\257\350\277\201\347\247\273\350\203\275\345\212\233.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.md" new file mode 100644 index 0000000..1e7395c --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.md" @@ -0,0 +1,115 @@ +# **重疾险总结** + +## 防癌险 + +### 单纯以癌症作为赔付标的的健康险,赔付范围比一般的重疾险、医疗险都要窄 + +### 产品初衷 +- 癌症是目前重大疾病中发病率最高的病种 +- 癌症的治疗费用高,而且花钱速度快、周期长 +- 单独的防癌险,费率低,核保宽松。适合老年人和非标体。 + +### 分类 +- 给付型和报销型 +- 长期型和一年期型 +- 全年龄段/老年专享 + +### 购买思路 +- 辅助型保险 +- 有些重疾险设计了防癌险 +- 考虑是否保到终身 +- 已经有重疾的,首选报销型防癌医疗险,因为保额高 +- 防癌险核保相对宽松 + +## 少儿重疾险 + +### 购买三要素 +- 定期还是终身 + - 定期便宜,不过一旦理赔,以后不好买 + - 终身贵,但是确定性更好 +- 多次还是单次 + - 终身少儿重疾,建议选多次赔付。 + - 定期少儿重疾,就没必要选多次赔付。 +- 预算合理分配,做好取舍 + - 家庭保险预算要在孩子、父母之间合理分配 + +### 相关产品 +- 终身 + - 慧馨安健康保 + - 妈咪保贝 + - 多倍宝宝 + - 健康福少儿重疾险 +- 定期 + - 晴天保保 + - 妈咪保贝 + - 爱宝保 + - 健康福少儿重疾险 + +### 儿童常见疾病 +- 早产/低重 +- 黄疸 +- 先天性心脏病 +- 卵圆孔未闭 +- 手足口病 + +## 重疾险相关知识点 + +### 没得大病身故返保额或者保费或者现金价值 + +### 尽量买多次重疾 + +### 三大标准(赔付标准) +- 确诊即赔 +- 约定手术 +- 约定状态 + +### 赔付次数 +- 单次重疾 +- 多次重疾 + - 分组重疾险 + - 不分组重疾险 + +### 中国肺癌两个高发地(据说已经被列入某保险公司的黑名单,不予销售防癌保险) +- 云南个旧 +- 云南宣威 + +### 特点 +- 重疾险是给付型的,只要得了符合条款的疾病,且经保险公司审核通过,赔付金会一次性给到被保险人 + +### 主要责任 +- 疾病责任 +- 身故责任 + - 身故责任与疾病责任共用保额,若因重症赔付了保额,身故不再赔付 + - 买重疾险不加身故,就一定要加一款寿险。 +- 豁免责任 + - 既包括投保人豁免,也包括被保险人豁免 + - 只有当投保人和被保险人不是同一人时才可选择「投保人豁免责任」 + - 加了投保人豁免,投保人若出现轻症/重症/身故/全残,则后续保费就不用再交了,保单效力持续。 + - 投保人和被保险人是同一人时,只有被保险人豁免。 + - 一般情况下,重疾险的被保险人豁免责任绑定在产品中,不用额外选择。 + +### 疾病责任 +- 重症责任 +- 中症责任 +- 轻症责任 +- 前症 + - 比轻症还要轻的疾病,可以理解为重疾的萌芽状态。 +- 轻症和中症出险不影响重症赔付 + - 但是只要进行过重症赔付,轻症和中症的责任就结束了 + +## 重疾与医疗的区别 + +### 重疾险(大病险) +- 给付型 +- 可以重复买,重复赔 +- 可以重复买 +- 长期的,一次核保,长期保障,费率几十年不变。 + +### 医疗险(住院险) +- 补偿型 +- 也可以重复买,但是不能重复赔 +- 消费型,一(N)年一保 + +### 重疾险和医疗险也可以重复赔偿,也就是可以一起用,申请赔偿。 + +### 互为补充的关系 diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.mindnode" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.mindnode" index 8f8d093..f34f104 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.mindnode" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\205\263\345\223\245\350\257\264\351\231\251/\351\207\215\347\226\276\351\231\251\346\200\273\347\273\223.mindnode" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.md" new file mode 100644 index 0000000..ad709d5 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.md" @@ -0,0 +1,44 @@ +# 各种特性总结 + +## 事务的特性(ACID) + +### 原子性(Atomicity) + +事务中的所有元素作为一个整体提交或回滚,事务的个体元素是不可分的,事务是一个完整操作。 + +### 一致性(Consistemcy) + +事物完成时,数据必须是一致的,也就是说,和事物开始之前,数据存储中的数据处于一致状态。保证数据的无损。 + +### 隔离性(Isolation) + +对数据进行修改的多个事务是彼此隔离的。这表明事务必须是独立的,不应该以任何方式以来于或影响其他事务。 + +### 持久性(Durability) + +事务完成之后,它对于系统的影响是永久的,该修改即使出现系统故障也将一直保留,真实的修改了数据库 + +## Java 并发编程的特性 + +### 原子性(Atomicity) + +一个操作或者多个操作,要么全部执行并不会被打断,要么就全都不执行。比如经典的银行转账操作。Java中由Java内存模型通过read、load、assign、use、store、write等操作直接保证变量的原子性。 + +### 可见性(Visibility) + +当一个线程修改了共享共享变量的值,其他线程能够立刻感知到修改。Java内存模型通过将新值同步回主存(store、write)操作在变量读取之前从主内存刷新变量值实现可见性。 + +### 有序性(Ordering) + +即程序执行的顺序按照代码的先后顺序执行。 + +## 数据库事务属性 + +## 接口的幂等性 + +### token + +### 数据库唯一索引(组合索引 MD5) + +### Redis + diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.xmind" index a302c85..928747c 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.xmind" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\347\211\271\346\200\247\346\200\273\347\273\223.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\2012.0\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\2012.0\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.md" new file mode 100644 index 0000000..2c468c7 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\2012.0\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.md" @@ -0,0 +1,167 @@ +# 各种锁2.0(参考美团技术团队) + +## MySQL 相关锁概念(MVCC) + +### 乐观锁、悲观锁 + +- 乐观锁 + + - 定义 + + - 在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。 + + - 流程 + + - 数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。 + + - 优缺点 + + - 乐观并发控制相信事务之间的数据竞争(data race)的概率较小 + - 不会产生任何锁和死锁 + - 两个事务都读取了数据库的某一行,经过修改以后写回数据库会有问题 + +- 悲观锁 + + - 定义 + + - 在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。 + + - 流程 + + - 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。 + + - 优缺点 + + - “先取锁再访问”的保守策略 + - 为数据处理的安全提供了保证 + - 在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会; + - 在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载 + - 会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理行数 + +### 共享锁、排他锁 + +- 共享锁 + + - 其他事务可以读,但不能写。 + +- 排他锁 + + - 其他事务不能读取,也不能写。 + +### GAP锁、间隙锁 + +- 间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。 + +### Next-Key锁 + +- 锁定一个范围,并且锁定记录本身。 + +### 2PL锁 + +### 粒度锁 + +- 行锁 + + - 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 + +- 表锁 + + - 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 + +- 页锁 + + - 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 + +## Java 中主流锁 + +### 多个线程能不能共享一把锁? + +- 共享锁 + + - 允许多个线程同时获取锁,并发访问,共享资源。AQS 的内部类 Node 定义了两个常量 SHARED 和 EXCLUSIVE,他们分别标识 AQS 队列中等待线程的锁获取模式。实现如:CountDownLatch(门阀)、Semaphor(信号量)、ReadWriteLock(读写锁) + +- 排他锁、独占锁 + + - 每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。 + +### 多个线程竞争锁是是否需要排队? + +- 公平锁 + + - 每个线程抢占锁的顺序为先后调用 lock 方法的顺序依次获取锁。换句话说,先对锁进行获取的请求一定被先满足,那么这个锁是公平的。在 ReentrantLock 中会体现线程具有不连续性,偏线性化。 + +- 非公平锁(先尝试插队,插队失败再排队) + + - 每个线程抢占锁的顺序不定,谁运气好,谁就获取到锁,和调用 lock 方法的先后顺序无关。在 ReentrantLock 中会体现一个线程连续获取锁的情况比较多,插队现象严重。 + +### 多个线程竞争同步资源的流程细节 + +- 无锁 + + - 不锁住资源,多个线程中只有一个能修改资源成功,其它线程会重试。 + +- 偏向锁 + + - 同一个线程执行同步资源时自动获取资源 + +- 轻量级锁 + + - 多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放。 + +- 重量级锁(Synchronized) + + - 多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒 + - 无论你的同步块是正常还是异常退出的,里面的线程都会释放锁 + +### 锁住同步资源失败的话,线程是否阻塞 + +- 直接阻塞 +- 自旋锁 + + 通俗:当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。 + + - 线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 + +- 适应性自旋锁 + + - 自旋的时间不再是固定的, 而是由前一次在同一个锁上的自旋时间以及锁拥有者的状态来决定。 + +- 特殊的自旋锁:读写锁 + + - 只要没有写模式下的加锁,任意线程都可以进行读模式下的加锁; + - 只有读写锁处于不加锁状态时,才能进行写模式下的加锁; + +### 线程要不要锁住同步资源 + +- 悲观锁 + + - Lock 的实现类 + - Synchronized + +- 乐观锁(CAS) + + - 认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作 + +### 一个线程能不能多次获取锁? + +- 可重入锁、可递归锁 +- 不可重入锁、非递归锁 + +### 单一时刻线程拥有锁的顺序行为 + +- 互斥锁 + + - 指在某一时刻指允许一个进程运行其中的程序片,具有排他性和唯一性。 + +- 同步锁 + + - 在互斥的基础上,实现进程之间的有序访问。假设现有线程 A 和线程 B,线程A需要往缓冲区写数据,线程 B 需要从缓冲区读数据,但他们之间存在一种制约关系,即当线程A写的时候,B 不能来拿数据;B 在拿数据的时候 A 不能往缓冲区写,也就是说,只有当 A 写完数据(或B取走数据),B 才能来读数据(或 A 才能往里写数据)。这种关系就是一种线程的同步关系。 + +### 死锁 + +- 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 + +### 分段锁(ConcurrentHashMap) + +- 首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。 + diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\201\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\201\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.xmind" index aaf68df..71576e6 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\201\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.xmind" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\220\204\347\247\215\351\224\201\357\274\210\345\217\202\350\200\203\347\276\216\345\233\242\346\212\200\346\234\257\345\233\242\351\230\237\357\274\211.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\236\203\345\234\276\345\210\206\347\261\273.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\236\203\345\234\276\345\210\206\347\261\273.xmind" index 447bf92..aef9c14 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\236\203\345\234\276\345\210\206\347\261\273.xmind" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\236\203\345\234\276\345\210\206\347\261\273.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.md" new file mode 100644 index 0000000..ab31334 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.md" @@ -0,0 +1,248 @@ +# 大厂装 X 名词 + +## 二字动词 + +### 复盘 + +- 协同 + + - 响应 + + - 梳理 + + - 聚合 + + - 抓手 + + - 打通 + + - 分装 + + - 扩展 + +### 赋能 + +- 反哺 + + - 量化 + + - 输出 + + - 解耦 + + - 拆解 + + - 打透 + + - 穿梭 + + - 开拓 + +### 沉淀 + +- 兼容 + + - 发力 + + - 加速 + + - 集成 + + - 拉通 + + - 吃透 + + - 辐射 + +### 倒逼 + +- 包装 + + - 布局 + + - 共建 + + - 对齐 + + - 抽象 + + - 迁移 + + - 围绕 + +### 落地 + +- 重组 + + - 联动 + + - 支撑 + + - 对标 + + - 摸索 + + - 分发 + + - 复用 + +### 串联 + +- 履约 + + - 细分 + + - 融合 + + - 对焦 + + - 提炼 + + - 分层 + + - 渗透 + +## 二字名词 + +### 漏斗 + +- 矩阵 + + - 格局 + + - 玩法 + + - 合力 + + - 横向 + +### 中台 + +- 刺激 + + - 形态 + + - 体感 + + - 心力 + + - 通道 + +### 闭环 + +- 规模 + + - 生态 + + - 感知 + + - 赛道 + + - 补位 + +### 打法 + +- 场景 + + - 话术 + + - 调性 + + - 因子 + + - 链路 + +### 拉通 + +- 聚焦 + + - 体系 + + - 心智 + + - 模型 + + - 是点 + +### 纽带 + +- 维度 + + - 认知 + + - 战役 + + - 载体 + +## 三字名词 + +### 颗粒度 + +- 精细化 + + - 易用性 + +### 感知度 + +- 差异化 + + - 一致性 + +### 方法论 + +- 平台化 + + - 端到端 + +### 组合拳 + +- 结构化 + + - 短平快 + +### 引爆点 + +- 影响力 + +### 点线面 + +- 耦合性 + +## 四字名词 + +### 生命周期 + +- 复用打法 + + - 结果导向 + +### 价值转化 + +- 商业模式 + + - 垂直领域 + +### 强化认知 + +- 快速响应 + + - 如何收口 + +### 资源倾斜 + +- 定性定量 + + - 归因分析 + +### 完善逻辑 + +- 关键路径 + + - 体验度量 + +### 抽离透传 + +- 去中心化 + + - 信息屏障 + diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.png" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.png" new file mode 100644 index 0000000..140d29c Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.png" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.xmind" new file mode 100644 index 0000000..7c03791 Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\247\345\216\202\350\243\205 X \345\220\215\350\257\215.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\253\345\246\273\345\205\261\345\220\214\344\271\260\346\210\277.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\253\345\246\273\345\205\261\345\220\214\344\271\260\346\210\277.xmind" index 643ece7..203c565 100644 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\253\345\246\273\345\205\261\345\220\214\344\271\260\346\210\277.xmind" and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\244\253\345\246\273\345\205\261\345\220\214\344\271\260\346\210\277.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\246\202\344\275\225\350\256\251\347\256\200\345\216\206\350\204\261\351\242\226\350\200\214\345\207\272.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\345\246\202\344\275\225\350\256\251\347\256\200\345\216\206\350\204\261\351\242\226\350\200\214\345\207\272.xmind" deleted file mode 100644 index 82c01db..0000000 Binary files "a/\345\205\266\344\273\226\345\210\206\347\261\273/\345\246\202\344\275\225\350\256\251\347\256\200\345\216\206\350\204\261\351\242\226\350\200\214\345\207\272.xmind" and /dev/null differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\346\200\247\350\203\275\347\223\266\351\242\210\345\217\212\344\274\230\345\214\226\346\226\271\345\274\217.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\346\200\247\350\203\275\347\223\266\351\242\210\345\217\212\344\274\230\345\214\226\346\226\271\345\274\217.md" new file mode 100644 index 0000000..6cf37e5 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\346\200\247\350\203\275\347\223\266\351\242\210\345\217\212\344\274\230\345\214\226\346\226\271\345\274\217.md" @@ -0,0 +1,293 @@ +# 性能瓶颈及优化方式 + +来自于公众号「阿里技术」,原文链接:https://mp.weixin.qq.com/s/zWNPeSkDj9EFRhsHSdnFIg + +## 1.代码相关 + +### 遇到性能问题,首先应该做的是检查否与业务代码相关。性能优化的最佳位置,是应用内部。 + +### 业务日志 + +- 日志级别设置不合理 +- 日志输出不规范,性能不做优化 + +### 代码主要逻辑 + +- for 循环的不合理使用 +- NPE +- 正则表达式 +- 常见的数学计算 + +### 高频编码要点 + +- 正则表达式 + + - 正则表达式必须要预编译 + - 正则表达式非常消耗 CPU + - 慎用 String 的 split()、replaceAll() 等正则相关方法 + +- String.intern() + + - JDK8 之前可能会造成方法区(永久代)内存溢出 + - JDK8 之后内存划分重新设计为了元空间,不过如果字符串常量池如果设置太小而缓存的字符串过多,性能开销也会变大。 + +- 异常日志 + + - 如果堆栈异常信息明确,可以取消输出减少异常堆栈构造的成本 + - 同一位置如果抛出大量重复的堆栈信息,JIT 会进行优化抛出一个类型匹配的异常,导致无法看到异常堆栈信息 + +- 自动拆箱装箱 + + - 避免引用类型和基础类型之间无谓的拆装箱操作 + - 自动拆装箱如果太频繁,会非常严重消耗性能 + +- 尽量使用 Stream 流式 API,对于多核 CPU、复杂、并行有绝对的优势 +- 线程池 + + - 强烈建议通过 ThreadPoolExecutor 手动创建线程池。指定常驻核心线程数和队列大小 + - 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯 + +- 合理选择并发容器 + + - 对数据要求强一致性:Map + 锁 + - 读大于写:CopyOnWriteArrayList + - 存取数据量小、没有强一致性要求、变更不频繁:ConcurrenthashMap + - 存取数据量大、没有强一致性要求、读写频繁:ConcurrentSkipListMap + +- 锁优化思路 + + - 减少锁的粒度 + - 循环中使用锁粗化 + - 减少锁的持有时间 + - 使用 JDK 优化后的并发类,比如:LongAdder、ThreadLocalRandom + +- 空间换时间 + + - 缓存 + +- 时间换空间 + + - 分批次数据传输 + +- 各种连接池、队列、并行化框架等 + +## 2.CPU 相关 + +### CPU 负载是判断系统计算资源是否健康的关键依据。 + +### CPU 利用率高 && 平均负载高 + +- 常见于 CPU 密集型的应用 +- 大量消耗 CPU 资源的应用场景 + + - 正则操作 + - 数学运算 + - 序列化/反序列化 + - 反射操作 + - 死循环或者不合理的大量循环 + - 基础/第三方组件缺陷 + - 频繁的 GC + - CPU 本身的性能不足 + +- 排查高 CPU 占用的一般思路 + + - 通过 jstack 打印线程栈,一般可以定位到消耗 CPU 较多的线程堆栈 + - 通过 java Profiler 工具查看 CPU 火焰图 + - 使用Z「jstat -gcutil」命令持续输出当前应用的 GC 统计次数和时间 + - free 或者 top 命令查看当前机器内存的大小及占用情况 + +### CPU 利用率低 && 平均负载高 + +- 常见于 I/O 密集型的应用 +- 排查思路 + + - 耗时较长的网络请求 + - I/O 等待严重 + +### CPU 上下文切换次数变高 + +- 自愿上下文切换 + + - 多半是线程状态发生转换导致 + - sleep、join、wait 等方法或者使用了 Lock、synchronized 锁结构 + - 如果次数较高,意味着 CPU 存在资源获取等待 + + - I/O + - 内存资源不足 + +- 非自愿上下文切换 + + - 线程由于被分配的时间片用完或由于执行优先级被调度器调度所致。 + - 如果次数较高,可能由于线程数过多 + +## 3.内存相关 + +### 系统内存不足 + +- Java 应用内存占用 + + - Heap(堆区) + - Code Cache(代码缓存区) + - Metaspace(元空间) + - Symbol tables(符号表) + - Thread stacks(线程栈区) + - Direct buffers(堆外内存) + - JVM structures(其他的一些 JVM 自身占用) + - Mapped files(内存映射文件) + - Native Libraries(本地库) + +- 排查思路 + + - 首先使用 free 查看当前内存的可用空间大小,然后使用 vmstat 查看具体的内存使用情况及内存增长趋势,这个阶段一般能定位占用内存最多的进程; + - 分析缓存 / 缓冲区的内存使用。如果这个数值在一段时间变化不大,可以忽略。如果观察到缓存 / 缓冲区的大小在持续升高,则可以使用 pcstat、cachetop、slabtop 等工具,分析缓存 / 缓冲区的具体占用; + - 排除掉缓存 / 缓冲区对系统内存的影响后,如果发现内存还在不断增长,说明很有可能存在内存泄漏。 + +### Java 内存溢出(OutOfMemoryError) + +- 内存溢出是指应用新建一个对象实例时,所需的内存空间大于堆的可用空间。 +- 内存溢出分类 + + - OutOfMemoryError: Java heap space + + - 堆中无法继续分配对象 + - 对象的引用长期被持有,不释放,垃圾回收器无法回首 + - 使用了大量的 Finalizer 对象,这些对象不在 GC 的回收期 + - 一般内存溢出是因为系统内存泄漏,如果不是可以尝试加大堆内存 + + - OutOfMemoryError:GC overhead limit exceeded + + - 垃圾回收器超过98%的时间用来垃圾回收,但回收不到2%的堆内存 + - 一般是因为存在内存泄漏或堆空间过小 + + - OutOfMemoryError: Metaspace(OutOfMemoryError: PermGen space) + + - 检查是否有动态的类加载但没有即使卸载 + - 检查是否有大量的字符串常量池 + - 元空间内存设置过小 + + - OutOfMemoryError : unable to create new native Thread + + - 虚拟机在拓展栈空间时,无法申请到足够的内存空间。 + - 适当降低每个线程栈的大小以及应用整体的线程个数 + + - Swap 分区溢出 + - 本地方法栈溢出 + - 数组分配溢出 + +### Java 内存泄漏 + +- 内存泄漏的表现 + + - 应用运行一段时间后,内存利用率越来越高,响应越来越慢,直到最终出现进程「假死」。 + +- 排查思路 + + - 通过「jmap」命令定期输出堆内对象统计,定位数量和大小持续增长的对象; + - 使用 Java Profiler 工具对应用进行 Profiling,寻找内存分配热点。 + - 在堆内存持续增长时,建议 dump 一份堆内存的快照 + +### 垃圾回收相关 + +- 通用的 GC 调优策略 + + - 选择合适的 GC 回收器 + + - 推荐使用 G1 替换 CMS 垃圾回收器 + - CMS 垃圾回收器有参数太过复杂、容易造成空间碎片化、对 CPU 消耗较高等弊端 + + - 合理的堆内存大小设置 + + - 建议不要超过系统内存的 75% + - 最大堆大小和初始化堆的大小保持一致,避免堆震荡。 + + - 降低 Full GC 的频率 + + - 频繁的 Full GC 或者 老年代 GC,很有可能是存在内存泄漏,导致对象被长期持有,通过 dump 内存快照进行分析,一般能较快地定位问题。 + - 新生代和老年代的比例不合适,导致对象频频被直接分配到老年代,也有可能会造成 Full GC + +## 4.磁盘 I/O 和网络 I/O + +### 磁盘 I/O 问题排查思路 + +- 使用工具输出磁盘相关的输出的指标 +- 使用 pidstat 定位到具体进程,关注下读或写的数据大小和速率 +- 使用 lsof + 进程号,可查看该异常进程打开的文件列表 + +### 网络 I/O 存在瓶颈,可能的原因 + +- 一次传输的对象过大,可能会导致请求响应慢,同时 GC 频繁; +- 网络 I/O 模型选择不合理,导致应用整体 QPS 较低,响应时间长; +- RPC 调用的线程池设置不合理。 +- RPC 调用超时时间设置不合理,造成请求失败较多; + +## 5.问题定位相关命令 + +### 查看系统当前网络连接数 + +- netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' + +### 查看堆内对象的分布 Top 50(定位内存泄漏) + +- jmap –histo:live $pid | sort-n -r -k2 | head-n 50 + +### 按照 CPU/内存的使用情况列出前 10 的进程 + +- 内存:ps axo %mem,pid,euser,cmd | sort -nr | head -10 +- CPU:ps -aeo pcpu,user,pid,cmd | sort -nr | head -10 + +### 显示系统整体的 CPU 利用率和闲置率 + +- grep "cpu " /proc/stat | awk -F ' ' '{total = $2 + $3 + $4 + $5} END {print "idle \t used\n" $5*100/total "% " $2*100/total "%"}' + +### 按线程状态统计线程数(加强版) + +- jstack $pid | grep java.lang.Thread.State:|sort|uniq -c | awk '{sum+=$1; split($0,a,":");gsub(/^[ \t]+|[ \t]+$/, "", a[2]);printf "%s: %s\n", a[2], $1}; END {printf "TOTAL: %s",sum}'; + +### 查看最消耗 CPU 的 Top10 线程机器堆栈信息 + +- show-busy-java-threads 脚本 + +### 火焰图生成(需要安装 perf、perf-map-agent、FlameGraph 这三个项目) + +- 收集应用运行时的堆栈和符号表信息(采样时间30秒,每秒99个事件); +sudo perf record -F 99 -p $pid -g -- sleep 30; ./jmaps +- 使用 perf script 生成分析结果,生成的 flamegraph.svg 文件就是火焰图。 +sudo perf script | ./pkgsplit-perf.pl | grep java | ./flamegraph.pl > flamegraph.svg + +### 按照 Swap 分区的使用情况列出前 10 的进程 + +- for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head -10 + +### JVM 内存使用及垃圾回收状态统计 + +- 显示最后一次或当前正在发生的垃圾收集的诱发原因 + + - jstat -gccause $pid + +- 显示各个代的容量及使用情况 + + - jstat -gccapacity $pid + +- 显示新生代容量及使用情况 + + - jstat -gcnewcapacity $pid + +- 显示老年代容量 + + - jstat -gcoldcapacity $pid + +- 显示垃圾收集信息(间隔1秒持续输出) + + - jstat -gcutil $pid 1000 + +### 其他的一些日常命令 + +- 快速杀死所有的 java 进程 + + - ps aux | grep java | awk '{ print $2 }' | xargs kill -9 + +- 查找 / 目录下占用磁盘空间最大的 top10 文件 + + - find / -type f -print0 | xargs -0 du -h | sort -rh | head -n 10 + +*glorze.com - 高老四博客* \ No newline at end of file diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\346\200\247\350\203\275\347\223\266\351\242\210\345\217\212\344\274\230\345\214\226\346\226\271\345\274\217.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\346\200\247\350\203\275\347\223\266\351\242\210\345\217\212\344\274\230\345\214\226\346\226\271\345\274\217.xmind" new file mode 100644 index 0000000..bf9d3de Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\346\200\247\350\203\275\347\223\266\351\242\210\345\217\212\344\274\230\345\214\226\346\226\271\345\274\217.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\346\212\225\350\265\204.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\346\212\225\350\265\204.xmind" new file mode 100644 index 0000000..a85990c Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\346\212\225\350\265\204.xmind" differ diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\350\201\214\344\270\232\345\217\221\345\261\225.md" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\350\201\214\344\270\232\345\217\221\345\261\225.md" new file mode 100644 index 0000000..ea9cc93 --- /dev/null +++ "b/\345\205\266\344\273\226\345\210\206\347\261\273/\350\201\214\344\270\232\345\217\221\345\261\225.md" @@ -0,0 +1,133 @@ +# 职业发展 + +## 误区 + +### 一份简历走天下 + +- 多分职业或某职业的多模块下拆分成多份简历,如果是集成在一份简历里面,重点则是要突出匹配你当前寻找的工作职位相关的工作经历重点。 + +### 简历写成职位说明书集锦 + +- 列举在各家公司担当的职位名称和职责内容是大忌 +- 重点要体积工作业绩和结果,突出个人优势 +- 雇主方关注的不仅是你做了什么,更关注做的结果。 + +### 开门见山,长篇大论 + +- 大段描述职业经历没什么用,开篇不要写这东西 +- 简历的最重要突出部分要出现在页面水平中线偏上的区域。 + + - 自我评价:精炼出你与应聘职位关键需求相关的重要经验或核心能力 + - 简历上的自我评价就像是目录 + + - 既然是精练,最好用短语,而不是长长的句子。 + - 写太少无法引起注意,太多无法聚焦。列出4-5条为最佳。 + - 突出匹配职位要求的关键词。可以考虑对照应聘职位的职位说明书 + +### 细节体现态度 + +- 内容的细节 + + - 职业经历部分,年份要准确,无错别字; + - 字体、字号、颜色要一致; + +- 简历的篇幅 + + - 如果是应届生,1页就可以了。如果有经验的人士,2-4页相对合适。若工作年限长,最好也不要超过4页 + +- 照片的选择 + + - 选取的照片符合着装职业、妆容淡雅、表情亲和的原则 + +- 邮件的标题 + + - 邮件的标题和简历文件的命名,最好是「应聘职位+全名 + 最快入职时间」 + +## 严峻的就业形势该如何应对 + +### 判断你是否属于头部人群 + +- 企业缩招但是不代表不招人,所以你要定位、评估自己的能力水平是否因为缩招但依然可以在头部竞争中有一定的地位 + +### 在不确定中抓住确定 + +- 尽自己最大的努力增加自己的可能性 +- 扩宽求职渠道、提升求职技巧、提高自我展示的能力 + + - 新型的云端应聘平台 + - 新面试形式的适应 + + - 视频面试 + + - 光线与背景 + + - 保持画面明亮,确保面试官清楚看到你的面部,但不要让强光直射摄像头避免曝光过度 + - 如晚上面试,灯光暗,可考虑在镜头后放置台灯照在脸上进行补光 + - 背景浅色墙壁为最佳 + + - 动作 + + - 确保人身处于镜头正中间 + - 坐姿端正挺拔,适当加以手部动作,可以看上去身体没那么僵硬 + - 身体微微前倾,适当点头回应,可拉近心理沟通距离 + + - 表情与视线 + + - 避免夸张表情,保持适当微笑 + - 摄像头位置与眼睛持平,直视摄像头而非手机显示屏,保持与面试官眼神交流 + + - 语速与音量 + + - 语速不要过快,保证吐字清晰。平时说话较快的人,提前练习0.75倍速说话 + - 面试官提问后,停顿1-2秒再应答,以免因网络延迟而打断面试官 + - 声音不要过大过尖,建议提前做声音测试,确保音量适中 + + - 在线敲代码平台等 + + - 自我优势的梳理 + + - 你在学习/社会实践/实习中取得了哪些成果?从中你能看到自己的哪些优势? + - 他人对你做出的积极评价都集中在哪些方面?为什么? + - 有什么事,你会想:「如果我来做,一定会做得比他们还要好!」 + - 你有哪些特质,是你不以为然,别人却羡慕不已的? + - 以上哪些,和你的目标职位更加相关? + - 你将如何把你的优势展示在简历和面试中? + +### 第一步重要,但也没那么重要 + +- 第一份工作不太满意,而后通过自身努力,在第二份工作中实现愿望的例子比比皆是。 + + - 努力做好第一份工作 + - 做得多于被要求的 + - 工作之余,学习积累目标职位的知识 + - 积极参与目标领域相关的沙龙、论坛等活动,链接资源 + - 内部有能接触到目标职位的实践机会,积极争取 + +## 后端开发甩锅 + +### 甩锅不能慌 + +### 甩锅不爆粗口 + +- 带粗口,很难听 +- 你带了粗口后,人容易处于一种激动的状态之中 + +### 甩锅不能速成 + +### 不要甩锅的情况 + +- 被留下证据 +- 明摆着甩不出结果的情况 +- 某些特定对象不要甩 + + - 单身萌妹,除非你对人家没兴趣 + - 尽量不要甩给新人,都是从新人过来的 + - 不要甩给上级 + +### 普遍招数 + +- 针对测试,强调这种BUG难以复现 +- 针对产品,强调他们的需求有问题 +- 针对运维,强调操作失误 +- 针对开发,随机应变 + diff --git "a/\345\205\266\344\273\226\345\210\206\347\261\273/\350\201\214\344\270\232\345\217\221\345\261\225.xmind" "b/\345\205\266\344\273\226\345\210\206\347\261\273/\350\201\214\344\270\232\345\217\221\345\261\225.xmind" new file mode 100644 index 0000000..ae823f9 Binary files /dev/null and "b/\345\205\266\344\273\226\345\210\206\347\261\273/\350\201\214\344\270\232\345\217\221\345\261\225.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/Apoll.xmind" "b/\345\210\206\345\270\203\345\274\217/Apoll.xmind" new file mode 100644 index 0000000..73e498f Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/Apoll.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/Dubbo.md" "b/\345\210\206\345\270\203\345\274\217/Dubbo.md" new file mode 100644 index 0000000..c629912 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/Dubbo.md" @@ -0,0 +1,120 @@ +# Dubbo + +## Dubbo 简介及背景 + +### 背景 + +- 单一应用架构 + + - 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。 + +- 垂直应用架构 + + - 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。 + +- 分布式服务架构 + + - 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 + +- 流动计算架构 + + - 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。 + +### 需求 + +- 当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 +- 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 +- 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? + +### Dubbo 架构 + +- Provider + + - 暴露服务的服务提供方 + +- Consumer + + - 调用远程服务的服务消费方 + +- Registry + + - 服务注册与发现的注册中心 + +- Monitor + + - 统计服务的调用次数和调用时间的监控中心 + +- Container + + - 服务运行容器 + +- 调用关系说明 + + - 服务容器负责启动,加载,运行服务提供者。 + - 服务提供者在启动时,向注册中心注册自己提供的服务。 + - 服务消费者在启动时,向注册中心订阅自己所需的服务。 + - 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 + - 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 + - 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 + +### Dubbo 的特点 + +- 连通性 + + - 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小 + - 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示 + - 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销 + - 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销 + - 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外 + - 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者 + - 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表 + - 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者 + +- 健状性 + + - 监控中心宕掉不影响使用,只是丢失部分采样数据 + - 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务 + - 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 + - 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯 + - 服务提供者无状态,任意一台宕掉后,不影响使用 + - 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复 + +- 伸缩性 + + - 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心 + - 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者 + +- 升级性 + + - 当服务集群规模进一步扩大,带动 IT 治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。 + +## 发布 Dubbo 服务 + +### 一个小 Demo + +- 引入 Maven 依赖 +- 服务提供者接口 +- 定义接口实现类 +- dubbo 配置文件 + + - 提供方应用名称, 用于计算依赖关系 + - 使用 ZooKeeper 注册中心暴露服务地址 + - 使用 dubbo 协议在 20880 端口暴露服务 + - service 实现类作为本地的一个 bean + - 声明需要暴露的服务接口 + - 先启动zookeeper服务 + - 运行测试类,发布服务注册到 ZooKeeper 注册中心去 + +## 消费 Dubbo 服务 + +### 一个小 Demo + +- 引入依赖 +- 配置 dubbo-consumer.xml + + - 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 + - 使用 ZooKeeper 注册中心暴露服务地址 + - 生成远程服务代理,可以和本地 bean 一样使用demoProviderService。check属性,启动的时候是否检查,一般设置为 false,启动的时候不检查 + +- 创建服务提供者接口(代理) + diff --git "a/\345\210\206\345\270\203\345\274\217/Dubbo.xmind" "b/\345\210\206\345\270\203\345\274\217/Dubbo.xmind" new file mode 100644 index 0000000..80d9078 Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/Dubbo.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/Kafka.md" "b/\345\210\206\345\270\203\345\274\217/Kafka.md" new file mode 100644 index 0000000..e0b6d13 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/Kafka.md" @@ -0,0 +1,82 @@ +# Kafka + +## 队列的普遍作用 + +### 解耦 + +### 冗余 + +### 扩展性 + +### 灵活性 & 峰值处理能力 + +### 可恢复性 + +### 顺序保证 + +### 缓冲 + +### 异步通信 + +## Kafka 架构 + +### Broker + +- Kafka 集群包含一个或多个服务器,这种服务器被称为 broker + +### Topic + +- 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。 +- Topic 在逻辑上可以被认为是一个 queue,每条消费都必须指定它的 Topic +- 为了使 Kafka 的吞吐率可以线性提高,物理上把 Topic 分成一个或多个 Partition,每个 Partition 在物理上对应一个文件夹,该文件夹下存储这个 Partition 的所有消息和索引文件。 + +### Partition + +- 物理上的概念,每个 Topic 包含一个或多个 Partition。 +- log entry + + - 4字节整型数值,message length + - 1个字节的「magic value」 + - 4个字节的CRC校验码 + - 消息体 + - log entry 并非由一个文件构成,而是分成多个segment,每个 segment 以该 segment 第一条消息的 offse t命名并以「.kafka」为后缀。 + - 还有一个索引文件,它标明了每个 segment 下包含的 log entry 的 offset 范围 + +### Producer + +- 负责发布消息到 Kafka broker,推模式 +- Producer 发送消息到 broker 时,会根据 Paritition 机制选择将其存储到哪一个 Partition。如果 Partition 机制设置合理,所有消息可以均匀分布到不同的 Partition 里,这样就实现了负载均衡。 + +### Consumer + +- 消息消费者,向 Kafka broker 读取消息的客户端,拉模式 +- 使用 Consumer high level API 时,同一 Topic 的一条消息只能被同一个 Consumer Group 内的一个 Consumer 消费,但多个 Consumer Group 可同时消费这一消息。 + +### Consumer Group + +- 每个 Consumer 属于一个特定的 Consumer Group + +### 一个典型的 Kafka 集群中包含若干 Producer,若干 broker(Kafka 支持水平扩展,一般 broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个 Zookeeper 集群。Kafka 通过 Zookeeper 管理集群配置,选举 leader,以及在 Consumer Group 发生变化时进行 rebalance。Producer 使用 push 模式将消息发布到 broker,Consumer 使用 pull 模式从 broker 订阅并消费消息。    + +### Kafka 如何保证 Exactly once + +消息正好传输一次 + +- 当 Producer 向 broker 发送消息时,一旦这条消息被 commit,它就不会丢。但是如果 Producer发送数据给 broker 后,遇到网络问题而造成通信中断,那 Producer 就无法判断该条消息是否已经 commit。虽然 Kafka 无法确定网络故障期间发生了什么,但是 Producer 可以生成一种类似于主键的东西,发生故障时幂等性的重试多次,这样就做到了 Exactly once。 + +## Kafka Consumer 设计 + +### High Level Consumer + +实现传统 Message Queue 消息只被消费一次的语义 + +- Kafka 并不删除已消费的消息,为了实现传统 Message Queue 消息只被消费一次的语义,Kafka 保证每条消息在同一个 Consumer Group里只会被某一个 Consumer 消费。 +- Kafka 还允许不同 Consumer Group 同时消费同一条消息 +- Kafka 对消息的分配是以 Partition 为单位分配的,而非以每一条消息作为分配单元。 + + - 无法保证同一个 Consumer Group 里的 Consumer 均匀消费数据 + - 但是是每个 Consumer 不用都跟大量的 Broker 通信,减少通信开销,同时也降低了分配难度,实现也更简单。 + - 同一个 Partition 里的数据是有序的,这种设计可以保证每个 Partition 里的数据可以被有序消费。 + +## 分支主题 4 + diff --git "a/\345\210\206\345\270\203\345\274\217/Kafka.xmind" "b/\345\210\206\345\270\203\345\274\217/Kafka.xmind" new file mode 100644 index 0000000..be18066 Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/Kafka.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/RocketMQ.md" "b/\345\210\206\345\270\203\345\274\217/RocketMQ.md" new file mode 100644 index 0000000..55fc2a3 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/RocketMQ.md" @@ -0,0 +1,416 @@ +# RocketMQ + +## 一、概念和特性 + +### 概念 + +- 消息模型(Message Model) + + - Producer + + - 负责生产消息 + + - Broker + + - 负责存储消息 + - 实际部署过程中对应一台服务器,每个 Broker 可以存储多个 Topic 的消息,每个 Topic 的消息也可以分片存储于不同的 Broker。 + + - Consumer + + - 负责消费消息 + + - ConsumerGroup + + - 由多个 Consumer 实例构成。 + + - Message Queue + + - 用于存储消息的物理地址 + - 每个 Topic 中的消息地址存储于多个 Message Queue 中 + +- 消息生产者(Producer) + + - 负责生产消息,一般由业务系统负责生产消息。 + - 消息生产者会把业务应用系统里产生的消息发送到 broker 服务器。 + - 发送方式 + + - 同步发送 + - 异步发送 + - 顺序发送 + - 单向发送 + +- 消息消费者(Consumer) + + - 负责消费消息,一般是后台系统负责异步消费。 + - 消息消费者会从 Broker 服务器拉取消息、并将其提供给应用程序。 + - 消费形式 + + - 拉取式消费 + - 推动式消费 + +- 主题(Topic) + + - 表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是 RocketMQ 进行消息订阅的基本单位。 + +- 代理服务器(Broker Server) + + - 消息中转角色,负责存储消息、转发消息。 + - 代理服务器在 RocketMQ 系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。 + - 代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。 + +- 名字服务(Name Server) + + - 名称服务充当路由消息的提供者。 + - 生产者或消费者能够通过名字服务查找各主题相应的 Broker IP 列表。 + - 多个 Namesrv 实例组成集群,但相互独立,没有信息交换。 + +- 拉取式消费(Pull Consumer) + + - Consumer 消费的一种类型,应用通常主动调用 Consumer 的拉消息方法从 Broker 服务器拉消息、主动权由应用控制。 + - 一旦获取了批量消息,应用就会启动消费过程。 + +- 推动式消费(Push Consumer) + + - Consumer 消费的一种类型,该模式下 Broker 收到数据后会主动推送给消费端,该消费模式一般实时性较高。 + +- 生产者组(Producer Group) + + - 同一类 Producer 的集合,这类 Producer 发送同一类消息且发送逻辑一致。 + - 如果发送的是事务消息且原始生产者在发送之后崩溃,则 Broker 服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。 + +- 消费者组(Consumer Group) + + - 同一类 Consumer 的集合,这类 Consumer 通常消费同一类消息且消费逻辑一致。 + - 消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。 + - 消费者组的消费者实例必须订阅完全相同的 Topic。 + - 消息模式 + + - 集群消费(Clustering) + + - 相同 Consumer Group 的每个 Consumer 实例平均分摊消息。 + - 一条消息只会被同 Group 中的一个 Consumer 消费 + - 多个 Group 同时消费一个 Topic 时,每个 Group 都会有一个 Consumer 消费到数据 + + - 广播消费(Broadcasting) + + - 相同 Consumer Group 的每个 Consumer 实例都接收全量的消息。 + - 消息将对一个 Consumer Group 下的各个 Consumer 实例都消费一遍 + + - 即使这些 Consumer 属于同一个 Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。 + +- 普通顺序消息(Normal Ordered Message) + + - 消费者通过同一个消费队列收到的消息是有顺序的,不同消息队列收到的消息则可能是无顺序的。 + +- 严格顺序消息(Strictly Ordered Message) + + - 消费者收到的所有消息均是有顺序的。 + +- 消息(Message) + + - 消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。 + - RocketMQ 中每个消息拥有唯一的 Message ID,且可以携带具有业务标识的 Key。 + - 系统提供了通过 Message ID 和 Key 查询消息的功能。 + +- 标签(Tag) + + - 为消息设置的标志,用于同一主题下区分不同类型的消息。 + - 来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。 + - 有效地保持代码的清晰度和连贯性,并优化 RocketMQ 提供的查询系统。 + - 消费者可以根据 Tag 实现对不同子主题的不同消费逻辑,实现更好的扩展性。 + +### 特性 + +- 订阅与发布 + + - 消息的发布是指某个生产者向某个 topic 发送消息; + - 消息的订阅是指某个消费者关注了某个 topic 中带有某些 tag 的消息,进而从该 topic 消费数据。 + +- 消息顺序 + + - 一类消息消费时,能按照发送的顺序来消费。 + - 顺序消息 + + - 全局顺序消息 + + - 某个 Topic 下的所有消息都要保证顺序; + - 对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。 + - 适用场景 + + - 性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景 + + - 分区顺序消息 + + - 只要保证每一组消息被顺序消费即可。 + - 对于指定的一个 Topic,所有消息根据 sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。 + - Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。 + - 适用场景 + + - 性能要求高,以 sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景。 + +- 消息过滤 + + - 消费者可以根据 Tag 进行消息过滤,也支持自定义属性过滤。 + - 在 Broker 端实现 + + - 优点是减少了对于 Consumer 无用消息的网络传输 + - 缺点是增加了 Broker 的负担、而且实现相对复杂。 + +- 消息可靠性 + + - 影响消息可靠性的几种情况 + + - Broker 非正常关闭 + - Broker 异常 Crash + - OS Crash + - 机器掉电,但是能立即恢复供电情况 + - 机器无法开机 + - 磁盘设备损坏 + +- 至少一次 + + - 每个消息必须投递一次。 + - Consumer 先 Pull 消息到本地,消费完成后,才向服务器返回 ack,如果没有消费一定不会 ack 消息 + +- 回溯消费 + + - 指 Consumer 已经消费成功的消息,由于业务上需求需要重新消费,Broker 在向 Consumer 投递成功消息后,消息仍然需要保留。 + +- 事务消息 + + - 指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。 + - 提供类似 X/Open XA 的分布事务功能,通过事务消息能达到分布式事务的最终一致。 + +- 定时消息 + + - 指消息发送到 broker 后,不会立即被消费,等待特定时间投递给真正的 topic。 + - broker 有配置项 messageDelayLevel,是 broker 的属性,不属于某个 topic。 + - 发消息时,设置 delayLevel 等级即可 + + - level == 0,消息为非延迟消息 + - 1<=level<=maxLevel,消息延迟特定时间,例如level==1,延迟1s + - level > maxLevel,则level== maxLevel,例如level==20,延迟2h + +- 消息重试 + + - Consumer 消费消息失败后,要提供一种重试机制,令消息再消费一次。 + - Consumer 消费消息失败的情况 + + - 由于消息本身的原因,例如反序列化失败,消息数据本身无法处理 + - 由于依赖的下游应用服务不可用 + +- 消息重投 + + - 生产者在发送消息时,同步消息失败会重投,异步消息有重试,oneway 没有任何保证。 + - 消息重投保证消息尽可能发送成功、不丢失,但可能会造成消息重复,消息重复在 RocketMQ 中是无法避免的问题。 + + - retryTimesWhenSendFailed + + - 同步发送失败重投次数,默认为 2 + + - retryTimesWhenSendAsyncFailed + + - 异步发送失败重试次数,异步重试不会选择其他 broker,仅在同一个 broker 上做重试,不保证消息不丢。 + + - retryAnotherBrokerWhenNotStoreOK + + - 消息刷盘(主或备)超时或slave不可用,是否尝试发送到其他broker,默认false。 + + - 消息的重复消费 + + - 原因 + + - 正常情况下在消费者真正消费完消息后应该发送 ack,通知 broker 该消息已正常消费,从 queue 中剔除 + - 当 ack 因为网络原因无法发送到 broker,broker 会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到 consumer。 + - 在集群模式下,消息在 broker 中会保证相同 group 的 consumer 消费一次,但是针对不同 group 的 consumer 会推送多次 + + - 解决方案 + + - 数据库表 + + - 处理消息前,使用消息主键在表中带有约束的字段中进行插入操作 + + - 单机映射(Map) + + - 单机时可以使用 map 做限制,消费时查询当前消息 id 是不是已经存在 + + - 集群分布式锁 + +- 流量控制 + + - 生产者流控,因为broker处理能力达到瓶颈; + - 消费者流控,因为消费能力达到瓶颈。 + +- 死信队列 + + - 死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。 + - 存储死信消息的特殊队列称为死信队列 + +- 消息堆积 + + - 添加消费者的数据量 + +### MQ 普遍作用 + +- 提升性能 +- 系统解耦 +- 流量消峰 + +## 二、架构设计 + +### Producer + +- 消息发布的角色,支持分布式集群方式部署。 +- Producer 通过 MQ 的负载均衡模块选择相应的 Broker 集群队列进行消息投递,投递的过程支持快速失败并且低延迟。 + +### Consumer + +- 消息消费的角色,支持分布式集群方式部署。 +- 支持以 push 推,pull 拉两种模式对消息进行消费。 +- 也支持集群方式和广播方式的消费,它提供实时消息订阅机制 + +### NameServer + +- 是一个非常简单的 Topic 路由注册中心,其角色类似 Dubbo 中的 zookeeper,支持 Broker 的动态注册与发现。 +- 主要包括两个功能 + + - Broker 管理 + + - 接受 Broker 集群的注册信息并且保存下来作为路由信息的基本数据。 + - 提供心跳检测机制,检查 Broker 是否还存活; + + - 路由信息管理 + + - 保存关于 Broker 集群的整个路由信息和用于客户端查询的队列信息。 + +### BrokerServer + +- 负责消息的存储、投递和查询以及服务高可用保证 + + - Remoting Module + + - 整个 Broker 的实体,负责处理来自 clients 端的请求。 + + - Client Manager + + - 负责管理客户端(Producer/Consumer)和维护 Consumer 的 Topic 订阅信息 + + - Store Service + + - 提供方便简单的 API 接口处理消息存储到物理硬盘和查询功能。 + + - HA Service + + - 高可用服务,提供 Master Broker 和 Slave Broker 之间的数据同步功能。 + + - Index Service + + - 根据特定的 Message key 对投递到 Broker 的消息进行索引服务,以提供消息的快速查询。 + +- 任意一台 Broker 宕机 + + - Broker 主从架构以及多副本策略 + - Master 收到消息后会同步给 Slave,这样一条消息就不止一份了,Master 宕机了还有 slave 中的消息可用,保证了 MQ 的可靠性和高可用性。 + +### https://pic.imgdb.cn/item/624d447f239250f7c57d0dd3.jpg +整体流程 + +- 启动 Namesrv,Namesrv起 来后监听端口,等待 Broker、Producer、Consumer 连上来,相当于一个路由控制中心。 +- Broker 启动,跟所有的 Namesrv 保持长连接,定时发送心跳包。 + + - 心跳包中,包含当前 Broker 信息(IP+端口等)以及存储所有 Topic 信息。 + - 注册成功后,Namesrv 集群中就有 Topic 跟 Broker 的映射关系。 + +- 收发消息前,先创建 Topic 。创建 Topic 时,需要指定该 Topic 要存储在哪些 Broker上。也可以在发送消息时自动创建 Topic。 +- Producer 发送消息 + + - 启动时,先跟 Namesrv 集群中的其中一台建立长连接,并从Namesrv 中获取当前发送的 Topic 存在哪些 Broker 上,然后跟对应的 Broker 建立长连接,直接向 Broker 发消息。 + +- Consumer 消费消息 + + - 跟 Producer 类似。跟其中一台 Namesrv 建立长连接,获取当前订阅 Topic 存在哪些 Broker 上,然后直接跟 Broker 建立连接通道,开始消费消息。 + +## 三、语义 + +### 消费语义 + +- 如何保证消息最多消费一次 + + - 幂等性 + +- 如何保证消息至少消费一次 + + - 消费者从 RocketMQ 拉取到消息之后,需要返回消费成功来表示业务方正常消费完成。 + + 如果返回 CONSUME_LATER 则会按照不同的 messageDelayLevel 时间进行再次消费,时间分级从秒到小时,最长时间为2个小时后再次进行消费重试,如果消费满 16 次之后还是未能消费成功,则不再重试,会将消息发送到死信队列,从而保证消息存储的可靠性。 + + +- 如何保证消息恰好消费一次 + + - 幂等性 + +### 投递语义 + +- 如何保证消息最多投递一次 +- 如何保证消息至少投递一次 + + - producer 向 broker 发送消息后,没有收到 broker 的 ack 时,rocketmq 会自动重试。重试的次数可以设置,默认为 2 次 + +- 如何保证消息恰好投递一次 + +## 四、RocketMQ 如何保证消息不丢 + +### 生产者端 + +- 采取 send() 同步发消息,发送结果是同步感知的。发送失败后可以重试,设置重试次数。默认 3 次。 + +### Broker 队列存储端 + +- 修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。 +- 集群部署 +- 存储可靠性挑战 + + - Broker正常关闭 + + - Broker 可以正常启动并恢复所有数据 + + - Broker异常Crash + - OS Crash + - 机器掉电,但是能立即恢复供电情况 + - 机器无法开机(可能是cpu、主板、内存等关键设备损坏) + - 磁盘设备损坏 + +### 消费者端 + +- 完全消费正常后在进行手动 ack 确认 + +## 五、常用队列对比 + +### kafka + +- Scala 开发 +- 吞吐量所有 MQ 里最优秀,QPS 十万级、性能毫秒级、支持集群部署 +- 功能单一 +- 丢数据, 因为数据先写入磁盘缓冲区,未直接落盘。机器故障会造成数据丢失 +- 适当丢失数据没有关系、吞吐量要求高、不需要太多的高级功能的场景,比如大数据场景 + +### RabbitMQ + +- Erlang 开发 +- 吞吐量比较低,QPS 几万级、性能 u 秒级、主从架构 +- 功能单一 +- Erlang 小众语言开发,吞吐量低,集群扩展麻烦 +- 中小公司对并发和吞吐量要求不高的场景 + +### RocketMQ + +- java 开发 +- 吞吐量高,QPS 十万级、性能毫秒级、支持集群部署 +- 支持各种高级功能,比如延迟消息、事务消息、消息回溯、死信队列、消息积压等等 +- 官方文档相对简单 +- 适当丢失数据没有关系、吞吐量要求高、不需要太多的高级功能的场景 + +### ActiveMQ + +- Java 开发,简单,稳定,性能不如前面三个 + diff --git "a/\345\210\206\345\270\203\345\274\217/RocketMQ.xmind" "b/\345\210\206\345\270\203\345\274\217/RocketMQ.xmind" new file mode 100644 index 0000000..681e50b Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/RocketMQ.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.md" "b/\345\210\206\345\270\203\345\274\217/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.md" new file mode 100644 index 0000000..48ba0d6 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.md" @@ -0,0 +1,507 @@ +# Spring Cloud 与 Docker 微服务架构实战 + +## 使用 Zuul 构建微服务网关 + +## 使用 Spring Cloud Config 统一管理微服务配置 + +### 为什么要统一管理微服务配置 + +- 集中管理配置 +- 不同环境,不同配置 +- 运行期间可动态调整 + +### Spring Cloud Config 简介 + +### 编写 Config Server + +### 编写 Config Client + +### Config Server 的 Git 仓库配置详解 + +- 占位符支持 + + - {application} + - {profile} + - {label} + +- 模式匹配 + + - 通配符 + +- 搜索目录 + + - search-path + +- 启动时加载配置文件 + +### Config Server 的健康状况指示器 + +### 配置内容的加解密 + +- 安装 JCE +- Config Server 加解密端点 +- 对称加密 +- 存储加密的内容 +- 非对称加密 + +### 使用「/refresh」端点手动刷新配置 + +### 使用 Spring Cloud Bus 自动刷新配置 + +- Spring Cloud Bud 简介 +- 实现自动刷新 +- 局部刷新 +- 架构改进 +- 跟踪总线事件 + +### Spring Cloud Config 与 Eureka 配合使用 + +### Spring Clkoud Config 的用户认证 + +### Config Server 的高可用 + +- Git 仓库的高可用 + + - 子主题 1 + +- RabbitMQ 的高可用 +- Config DServer 自身的高可用 + +## 使用 Spring Cloud Sleuth 实现微服务跟踪 + +### 为什么要实现微服务跟踪 + +- 微服务之间通过网络进行通信 +- 跟踪每个请求,了解请求经过哪些微服务、请求耗费时间、网络延迟、业务逻辑耗费时间等指标 +- 分布式跟踪的解决方案 + +### Spring Cloud Sleuth 简介 + +- span(跨度) + + - 基本工作单元,初始化 span 被称为「root span」 + +- trace(跟踪) + + - 一组共享「root span」的 span 组成的树状结构称为 trace + +- annotation(标注) + + - CS(Client Sent 客户端发送):客户端发起一个请求,描述了 span 的开始 + - SR(Server Received 服务器端接收) + + - 服务器端获得请求并准备处理它 + - 网络延迟 + + - SR - CS 时间戳 + + - SS(Server Sent 服务器端发送) + + - 完成请求处理(响应发回客户端) + - 服务器端处理请求所需的时间 + + - SS - SR 时间戳 + + - CR(Client Received 客户端接收) + + - span 结束的标识。客户端成功接收到服务器端的响应 + - 客户端发送请求到服务器响应的时间 + + - CR - CS 时间戳 + +### 整合 Spring Cloud Sleuth + +### Spring Cloud Sleuth 与 ELK 配合使用 + +### Spring Cloud Sleuth 与 Zipkin 配合使用 + +- Zipkin 简介 + + - 收集系统的时序数据,追踪微服务架构的系统延时等问题 + +- 编写 Zipkin Server + + - 界面查询条件的含义 + + - Service Name + + - 服务名称,各个微服务 spring.application.name 的值 + + - span 的名称 + + - all 标识所有 span,也可选择指定 span + + - Start time、End time + + - 用于指定起始时间和截止时间 + + - Duration + + - 持续时间,span 从创建到关闭所经历的时间 + + - Limit + + - 表示查询几条数据 + + - Annotations Query + + - 自定义查询条件 + +- 微服务整合 Zipkin +- Zipkin 与 Eureka 配合使用 +- 使用消息中间件收集数据 + + - 优势 + + - 微服务与 Zipkin Server 解耦 + - 某些场景可以解决 Zipkin Server 与微服务网络不通的情况 + +- 使用 Elasticsearch 存储跟踪数据 +- 依赖关系图 + +## Spring Cloud 常见问题与总结 + +### Eureka 常见问题 + +- Eureka 注册服务慢 +- 已停止的微服务节点注销慢或不注销 +- 如何自定义微服务的 Instance ID +- Eureka 的 UNKNOWN 问题总结与解决 + +### 整合 Hystrix 后首次请求失败 + +- 原因分析 +- 解决方案 + +### Turbine 聚合的数据不完整 + +### Spring Cloud 各组件超时 + +- RestTemplate 的超时 +- Ribbon 的超时 +- Feign 的超时 +- Hystrix 的超时 +- Zuul 的超时 + +### Spring Cloud 各组件重试 + +## Doeker 入门 + +## 将微服务运行在 Docker 上 + +## 使用 Docker Compare 编排微服务 + +## 使用 Hystrix 实现微服务的容错处理 + +## 使用 Feign 实现声明式 REST 调用 + +## 使用 Ribbon 实现客户端侧负载均衡 + +## 微服务注册与发现 + +## 实战基础相关概念 + +### 服务提供者:服务的被调用方,为其他服务提供服务的服务 + +### 服务消费者:服务的调用方,依赖其它服务的服务 + +### @SpringBootApplication 注解 + +- @Configuration +- @EnableAutoConfiguration +- @ComponentScan + +### Spring Initializr 的四种创建方式 + +- (推荐)网页:https://start.spring.io/ +- Spring Tool Suite +- Intellij IDEA 自带 spring.io +- Spring Boot CLI + +### @Bean + +- 方法注解,作用是实例化一个 Bean 并使用该方法的名称命名 + +### Spring Boot Actuator,主要就是端点,http(s)://{ip/域名}:{port}/{endpoint} + +- autoconfig + + - 显示自动配置信息 + +- beans + + - 显示应用程序上下文所有的 Spring bean + +- configprops + + - 显示所有 @ConfigurationProperties 的配置属性列表 + +- dump + + - 显示线程活动的快照 + +- env + + - 显示应用的环境变量 + +- health + + - 显示应用程序的健康指标 + +- info + + - 显示应用的信息 + +- mappings + + - 显示所有的 @RequestMapping 的路径列表 + +- metrics + + - 显示应用的度量标准信息 + +- shutdown + + - 关闭应用 + +- trace + + - 显示跟踪信息,默认最近 100 个 HTTP 请求 + +### RestTemplate 硬编码存在的问题 + +- 适用场景局限:比如服务提供者的 IP 发生变化会影响消费者的服务地址请求配置。 +- 无法动态伸缩:生产环境一个微服务可能部署多个实例来进行负载均衡和容灾,但是硬编码满足这种需求很繁琐。 + +## Spring Cloud 简介 + +### 概念 + +- 基于 Spring Boot 构建,用于快速构建分布式系统的通用模式的工具集 + +### Spring Cloud 特点 + +- 约定优于配置 + + 关于「约定优于配置」的概念可以参考一下老四的这片《阿里巴巴Java开发手册第三章-单元测试篇》文章。里面有相关的解读。http://www.glorze.com/437.html + + - 子主题 1 + +- 适用于各种服务器环境。云服务器、Docker 等 +- 隐藏组件复杂性,提供声明式、无 XML 的配置方式 +- 开箱即用,快速启动 +- 轻量级组件:Eureka、Zuul +- 完整的微服务体系 + + - 配置管理 + - 服务发现 + - 断路器 + - 微服务网关 + +- 选型中立、丰富 + + - Eureka + - ZooKeeper + - Consul + +- 灵活,各个部分均为解耦设计 + +### Spring Cloud 版本 + +- SR 代表「Service Release」,一般代表 Bug 修复,后面代表的是第几次的版本 Bug 修复。 +- 发行版均用伦敦地铁站命名 + + - Greenwich + - Finchley + - Edgware + - Dalston + - Camden + - Brixton + - Angle + +## 微服务架构概述 + +### 单体应用 + +- 定义 + + - 一个归档包(例如 war 格式)包含所有功能的应用程序 + +- 存在的问题 + + - 复杂性高 + + 模块边界模糊,依赖关系不清晰。 + + - 技术债务 + + - 不坏不修 + + - 部署频率低 + + 全量部署的方式耗时长、影响范围大、风险高。 + + - 可靠性差 + + - 一个 Bug 可能导致整个应用崩溃 + + - 扩展能力受限 + + - 密集型应用模块需要强劲的 CPU + - IO密集型的需要更大的内存 + + - 阻碍技术创新 + + - 重构或者改造成本大,也基本无法跨语言 + +### 微服务 + +- 定义 + + - 将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间采用轻量级通信机制(通常用 HTTP 资源 API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式管理,服务可用不同的语言开发,使用不同的数据存储技术。 + +- 特性 + + - 每个微服务可独立运行在自己的进程里 + - 一系列微服务共同构建整个系统 + - 每个服务为独立的业务开发,分模块或者业务 + - 通过轻量级通信机制进行通信,如 Restful API。 + - 可以使用不同的语言和数据库 + - 全自动部署机制 + +- 优势 + + - 易于开发和维护 + - 单个微服务启动较快 + - 局部修改容易部署 + - 技术站不受限 + - 按需伸缩 + +- 存在的问题与挑战 + + - 运维要求较高 + - 分布式固有的复杂性 + + - 系统容错 + - 网络延迟 + - 分布式事务 + + - 接口调整成本高 + - 重复劳动 + +- 设计原则 + + - 单一职责原则 + + 这里其实指的是设计模式中的七个面向对象设计(可以先短暂的参考:https://git.io/fjr9M)原则之一,而且单一职责原则属于「SOLID 原则」之一,这个 SOLID 代表的就是单一职责原则、开放-封闭原则、里氏里氏代换原则、接口隔离原则以及依赖倒转原则的组合,关于这几个基本的面向对象设计原则也可以简单的参考一下老四写的《设计模式系列》。http://www.glorze.com/tag/%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f + + - 服务自治原则 + + - 服务是独立的业务单元 + + - 轻量级通信机制 + + - REST(Representational State Transfer,表现层状态转换) + - AMQP(Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议) + - STOMP(Streaming Text Orientated Message Protocol,流文本定向消息协议) + - MQTT(Message Queuing Telemetry Transport,消息队列遥测传输) + + - 微服务力度 + + - DDD 领域驱动设计 + + - 基本概念 + + - 在开发前,通常需要进行大量的业务知识梳理,而后到达软件设计的层面,最后才是开发。而在业务知识梳理的过程中,我们必然会形成某个领域知识,根据领域知识来一步步驱动软件设计,就是领域驱动设计的基本概念。 + - 软件开发模式 + + - 瀑布式 + - 敏捷式 + + - 小步迭代,周期性交付,那么获取客户的反馈也就比较频繁和及时 + - 拥抱变化 + + - DDD + + - 小粒度的迭代设计,它的最小单元是领域模型(Domain Model) + + - 能够精确反映领域中某一知识元素的载体,这种知识的获取需要通过与领域专家(Domain Expert)进行频繁的沟通才能将专业知识转化为领域模型。 + - 无关技术,具有高度的业务抽象性,它能够精确的描述领域中的知识体系 + - 独立的,我们还需要学会如何让它具有表达性,让模型彼此之间建立关系,形成完整的领域架构。 + + - 贫血领域对象 + + - 仅用作数据载体,而没有行为和动作的领域对象 + - 简单的业务系统采用这种贫血模型和过程化设计是没有问题的 + + - 模型是我们解决实际问题所抽象出来的概念模型,领域模型则表达与业务相关的事实;设计模型则描述了所要构建的系统。 + - 解决复杂和大规模软件的方案 + + - 分治 + + - 把问题空间分割为规模更小且易于处理的若干子问题 + + - 抽象 + + - 精简问题空间,而且问题越小越容易理解。 + + - 知识 + + - DDD 可以认为是知识的一种 + + - 核心诉求 + + - 架构设计 + + - 业务架构 + + - 根据业务需求设计业务模块及其关系 + + - 系统架构 + + - 设计系统和子系统的模块 + + - 技术架构 + + - 决定采用的技术及框架 + + - 将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。而微服务追求业务层面的复用,设计出来的系统架构和业务一致; + - 在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。 + + - 一般步骤 + + - 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系; + - 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象; + - 对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根; + - 为聚合根设计仓储,并思考实体或值对象的创建方式; + - 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。 + + - 康威定律:设计系统的架构受制于产生这些设计的组织的沟通结构。 + + 系统设计(产品结构)等同组织形式,每个设计系统的组织,其产生的设计等同于组织之间的沟通结构。 + + - 组织沟通方式会通过系统设计表达出来 + - 时间再多一件事情也不可能做的完美,但总有时间做完一件事情 + - 线型系统和线型组织架构间有潜在的异质同态特性 + - 大的系统组织总是比小系统更倾向于分解 + +- 服务架构 + + - 自动化部署工具,如持续集成架构 + + - IaaS:基础设施服务,Infrastructure-as-a-service + - PaaS:平台服务,Platform-as-a-service + - SaaS:软件服务,Software-as-a-service + + - 开发框架 + + - Spring Cloud + - Dubbo + - Dropwizard + - Armada + + - 运行平台 + + - 直接部署 + - Docker + diff --git "a/\345\210\206\345\270\203\345\274\217/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.xmind" "b/\345\210\206\345\270\203\345\274\217/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.xmind" new file mode 100644 index 0000000..71bc85b Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/Spring Cloud \344\270\216 Docker \345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\345\256\236\346\210\230.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/Spring \345\223\215\345\272\224\345\274\217\345\276\256\346\234\215\345\212\241.md" "b/\345\210\206\345\270\203\345\274\217/Spring \345\223\215\345\272\224\345\274\217\345\276\256\346\234\215\345\212\241.md" new file mode 100644 index 0000000..835b1ff --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/Spring \345\223\215\345\272\224\345\274\217\345\276\256\346\234\215\345\212\241.md" @@ -0,0 +1,400 @@ +# Spring 响应式微服务 + +本思维导图原文来自于《Spring 响应式微服务:Spring Boot 2 + Spring 5 + Spring Cloud 实战》 + +## 一、直面响应式微服务架构 + +## 二、响应式编程模型与 Reactor 框架 + +### 响应式编程模型 + +- 流 + + - 概念 + + - 由生产者生产并由一个或多个消费者消费的元素的序列(发布/订阅模型) + + - 推模型 + + - 生产者将元素推送给消费者 + + - 拉模型 + + - 消费者从生产者中拉取元素 + + - 生产者与消费者之间的同步异步区别 + + - 流量控制 + + - 节流 + + - 消费者直接丢弃无法处理的元素 + + - 使用缓冲区 + + - 当生产者比消费者快的时候,消费者可以采用无边界缓冲区来保存生产者产生的元素 + - Java 中的无边界缓冲队列 + + - LinkedBlockingQueue(几乎无边界,Integer 最大值) + - PriorityBlockingQueue(受系统资源的影响) + - DelayQueue、LinkedTransferQueue + - ConcurrentLinkedQueue(非阻塞队列) + + - 调用栈阻塞 + + - 同步线程 + + - 使用背压 + + - 消费者需要多少,生产者就生产多少 + +- 背压 + + - 背压机制 + + - 消费者在消费由生产者生产的数据的同时,也需要有一种能够向上游反馈流量需求的机制,这种机制就是背压机制 + + - 背压的实现方式 + + - 阻塞式背压 + + - 生产者和消费者处于同一线程,当消费者消费的时候阻塞生产者的生产 + + - 非阻塞式背压(推荐) + + - 采用拉模型,即消费者会要求生产者生成多少消息量,最多只能生产多少,然后等待消费者的命令。 + +- 响应式流(预先定义好的接口) + + - 响应式流规范 + + - 目标是定义将数据流从生产者传递到消费者而不需要生产者阻塞 + - 消费者向生产者发送多个元素的异步请求,生产者向消费者发送合适数量的元素 + + - 响应式流接口 + + - Publisher + + - 潜在的包含无限数量的有序元素的生产者,他根据收到的请求向当前订阅者发送元素 + + - Subscriber + + - 订阅者从发布者哪里订阅并接收元素。 + + - Subscription + + - 表示订阅者订阅的一个发布者的令牌 + + - Processor + + - 处理器充当订阅者和发布者之间的处理媒介,类似于将发布者提供的 T 类型数据转换为 R 类型数据发给订阅者 + +### Reactor 框架 + +- 响应式编程实现技术概述 + + - 实现异步操作的常见方式 + + - 回调 + + - 类 A 调用类 B 的方法 b,b 方法执行完毕后主动调用类 A 的方法 a + - 对于大规模的嵌套多层异步组合来说显得非常笨拙,很快导致代码那一理解和维护 + + - Future + + - 虽然可以实时获取异步执行的结果,但是没有通知机制,无法得知任务什么时候完成,为了获取结果,要么使用阻塞的 get() 方法,要么就是使用 isDone() 轮询判断任务是否完成 + - CompletableFuture + + - 响应式编程的主流实现技术 + + - Rxjava + + - 类库 + + - Akka Streams + + - 运行在 JVM 上 + + - Vert.x + + - Eclipse 旗下开源的 Java 工具 + + - Project Reactor + + - Spring 5 引入的响应式编程机制 + - 核心组件 + + - Flux + + - 代表包含 0 到 n 个元素的异步序列 + + - Mono + + - 表示包含 0 个或 1 个元素的异步序列 + +- 引入 Reactor 框架 + + - Reactor 异步数据序列 + + - onNext() + + - 正常的包含元素的消息通知 + + - onComplete() + + - 序列结束的消息通知,可以没有 + - 不调用就是无限异步序列 + + - onError() + + - 序列出错的消息通知,可以没有 + + - Flux 组件 + + - 包含 0 到 n 个元素的异步序列,上述三种消息通知都适用于 Flux + + - Mono 组件 + + - 表示包含 0 个或 1 个元素的异步序列,也可以包含上述三种类型的消息通知 + - 也可用来表示一个空的异步序列,该序列没有任何元素,仅仅包含序列结束的高年,使用 Mono 表示 + +### 创建 Flux 和 Mono + +- 创建 Flux + + - 通过静态方法创建 Flux + + - just() + + - 可以指定序列中包含的全部元素,创建出来的 Flux 序列在发布这些元素之后会自动结束 + + - fromArray()、fromIterable()、fromStream() + + - 基于已经存在一个数组、Iterable 对象或者 Stream 对象,可以使用这些方法从对象中自动创建 Flux 对象 + + - empty()、error()、never() + + - empty() 可以创建一个不包含任何元素而只发布结束消息的序列 + - error() 方法创建一个只包含错误消息的序列 + - never() 方法创建一个不包含任何消息通知的序列 + + - range() + + - 可以创建包含从 start 起始的 count 个对象的序列,序列中所有的对象类型都是 Integer + + - interval() + + - 表现为一个方法序列,序列中的元素按照指定的时间间隔来发布 + + - 动态创建 Flux + + - generate() + + - 通过同步和注意的方式来产生 Flux 序列,依赖于 Reactor 提供的 SynchronousSink 组件 + + - next(),最多只能被调用一次 + - complete() + - error() + + - create() + + - 使用 FluxSink 组件,支持同步和异步的消息生产方式,在一次调用中可以产生多个元素 + +- 创建 Mono + + - Flux 的上述创建方法 Mono 同样适用 + - delay() + + - 在指定的延迟时间之后会产生数字 0 作为唯一值 + + - justOrEmpty() + + - 从一个 Optional 对象创建 Mono,只有当 Optional 对象中包含值是,Mono 序列才产生对应的元素 + + - Mono 的动态创建也是使用 create() 并使用 MonoSink 组件 + +### Flux 和 Mono 操作符 + +- 操作符类型 + + - 转换 + + - 负责对序列中的元素进行转变 + + - 过滤 + + - 将不需要的数据从序列中进行过滤 + + - 组合 + + - 负责将序列中的元素进行合并和连接 + + - 条件 + + - 负责根据特定条件对序列中的元素进行处理 + + - 数学 + + - 负责对序列中的元素执行各种数学操作 + + - Observable 工具 + + - 提供一些针对流失处理的辅助性工具类 + + - 日志和调试 + + - 针对运行时日志以及如何对序列进行代码挑食的工具类 + +- 转换操作符 + + - buffer + + - 把当前流中的元素收集到集合中,并把集合对象作为流中的新元素 + - 指定收集的时间间隔 + - bufferUntil + - bufferWhile + + - map + + - 映射操作,对流中的每个元素应用一个映射函数,从而达到变换效果 + + - flatMap + + - 把六中的每个元素转换成一个流,再把转换之后得到的所有流中的元素进行合并 + + - window + + - 类似于 buffer,但是是把当前流中的元素收集到另外一个 Flux 中 + +- 过滤操作符 + + - filter + + - 过滤器 + + - first + + - 返回流中第一个元素 + + - last + + - 返回流中最后一个元素 + + - skip/skipLast + + - 忽略数据流的前(后) n 个元素 + + - take/takeLst + + - 从当前流中的前(后)面取 n 个元素 + +- 组合操作符 + + - then/when + + - then:等到上一个操作完成再做下一个 + - when:等到多个操作一起完成 + + - merge + + - 把多个流合并成一个 Flux 序列 + + - startWith + + - 在数据元素序列的开头插入指定的元素项 + + - zipWith + + - 把当前流中的元素与另外一个六中的元素按照一对一的方式进行合并 + +- 条件操作符 + + - defaultEmpty + + - 如果原始数据流中没有元素,返回一个默认的元素 + + - takeUntil + + - 提取元素直到断言条件返回 true + + - takeWhile + + - 在断言条件返回 true 时才进行元素的提取 + + - skipUntil + + - 丢弃原始数据流中的元素直到断言为 true + + - skipWhile + + - 断言为 true 时才进行元素的丢弃 + +- 数学操作符 + + - concat + + - 合并来自于不同的 Flux 的数据 + + - count + + - 统计 Flux 中所有元素的个数 + + - reduce + + - 对所有的元素进行累计操作,得到一个包含计算结果的 Mono 序列 + +- Observable 工具操作符 + + - delay + + - 将事件的传递向后延迟一段时间 + + - subscribe + + - 添加相应的订阅逻辑 + + - timeout + + - 特定时间段内没有产生任何时间时,将生成一个异常 + + - block + + - 在接收到下一个元素之前一直阻塞 + +- 日志和调试操作符 + + - log + + - 用于观察所有的数据并使用日志工具进行跟踪 + + - debug + + - 所有的操作符在执行时都会保存与执行立案相关的额外信息 + +### Reactor 框架中的背压机制 + +- 处理策略 + + - ERROR + + - 当下游跟不上节奏时发出一个错误信号 + + - DROP + + - 当下游没有准备好接收新的元素时抛弃这个元素 + + - LATEST + + - 让下游只得到上游最新的元素 + + - BUFFER + + - 缓存下有没有来得及处理的元素 + + - IGNORE + + - 完全忽略下游背压请求 + +## 分支主题 3 + +*高老四博客 - glorze.com* \ No newline at end of file diff --git "a/\345\210\206\345\270\203\345\274\217/Spring \345\223\215\345\272\224\345\274\217\345\276\256\346\234\215\345\212\241.xmind" "b/\345\210\206\345\270\203\345\274\217/Spring \345\223\215\345\272\224\345\274\217\345\276\256\346\234\215\345\212\241.xmind" new file mode 100644 index 0000000..2938575 Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/Spring \345\223\215\345\272\224\345\274\217\345\276\256\346\234\215\345\212\241.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/gRPC \344\270\216\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221.md" "b/\345\210\206\345\270\203\345\274\217/gRPC \344\270\216\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221.md" new file mode 100644 index 0000000..6706a39 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/gRPC \344\270\216\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221.md" @@ -0,0 +1,197 @@ +# gRPC 与云原生应用开发 + +## 一、gRPC 入门 + +### 微服务架构 + +- 微服务架构将软件应用程序构建为一组独立、自治(独立开发、部署和扩展)、松耦合、面向业务能力的服务 + +### RESTful 局限性 + +- 基于文本的低效消息协议 +- 应用程序之间缺乏强类型接口 +- REST 架构风格难以强制实施 + +### 编排/解排 + +- 编排(marshal) + + - 将参数和远程函数打包的过程 + +- 解排(unmarshal) + + - 解包消息到对应的方法调用的过程 + +### 进程间通信技术 + +- 传统的 RPC + + - CORBA(通用对象请求代理体系结构) + - RMI(Java 远程方法调用) + - 弊端:构建在 TCP 通信协议之上,妨碍互操作性,还有大量的规范限制 + +- SOAP(简单对象访问协议) + + - 面向服务的架构(SOA)中的标准通信技术 + - 用于在服务之间交换基于 XML 的结构化数据,能够基于任意的底层通信协议进行通信,最常用的是 HTTP + - 弊端:消息格式复杂,规范复杂 + +- REST + + - 面向资源的结构(ROA)的基础,通用实现是 HTTP + - 弊端:基于文本的低效消息协议;应用程序之间缺乏强类型接口;REST架构风格难以强制实施; + +- gRPC + + - 优势 + + - 提供高效的进程间通信 + - 具有简单且定义良好的服务接口和模式 + - 属于强类型 + - 支持多语言 + - 支持双工流 + - 具备内置的商业化特性 + - 与云原生生态系统进行了集成 + + - 劣势 + + - gRPC 可能不太适合面向外部的服务 + - 巨大的服务定义变更是复杂的开发流程 + - gRPC生态系统相对较小 + +## 二、开始使用 gRPC + +### 消息(message)是客户端和服务器端交换的数据结构。 + +## 三、gRPC 的通信模式 + +### 一元 RPC 模式(简单 RPC 模式) + +- 当客户端调用服务器端的远程方法时,客户端发送请求至服务器端并获得一个响应,与响应一起发送的还有状态细节以及 trailer 元数据。 +- 一元RPC模式中,gRPC 服务器端和 gRPC 客户端在通信时始终只有一个请求和一个响应 + +### 服务端流 RPC 模式 + +- 服务器端在接收到客户端的请求消息后,会发回一个响应的序列。这种多个响应所组成的序列也被称为「流”」,在将所有的服务器端响应发送完毕之后,服务器端会以 trailer 元数据的形式将其状态发送给客户端,从而标记流的结束。 + +### 客户端流 RPC 模式 + +- 客户端会发送多个请求给服务器端,而不再是单个请求。 +- 服务器端会发送一个响应给客户端。但是,服务器端不一定要等到从客户端接收到所有消息后才发送响应。 +- 可以在接收到流中的一条消息或几条消息之后就发送响应,也可以在读取完流中的所有消息之后再发送响应。 + +### 双向流 RPC 模式 + +- 客户端以消息流的形式发送请求到服务器端,服务器端也以消息流的形式进行响应。 +- 调用必须由客户端发起,但在此之后,通信完全基于 gRPC 客户端和服务器端的应用程序逻辑。 +- 流的操作完全独立,客户端和服务器端可以按照任意顺序进行读取和写入,一旦建立连接,客户端和服务器端之间的通信模式就完全取决于客户端和服务器端本身。 + +### 使用 gRPC 实现微服务通信 + +## 四、gRPC 的底层原理 + +### RPC 流 + +- https://pic.imgdb.cn/item/6246d59727f86abb2ab3c1a5.jpg +protocol buffers 作为 gRPC 的编码技术,HTTP/2 作为 gRPC 的通信协议 +- RPC 系统中,服务器端会实现一组可以远程调用的方法。客户端会生成一个存根,该存根为服务器端的方法提供抽象。这样一来,客户端应用程序可以直接调用存根方法,进而调用服务器端应用程序的远程方法。 + +### 使用 protocol buffers 编码消息 + +- 编码技术 + + - Varint 类型(可变长度整数) + + - 使用单字节或多字节来序列化整数的方法 + + - 有符号整数类型 + + - 有符号类型,会使用 zigzag 编码来将有符号整数转换成无符号整数。无符号整数会使用前面的 Varint 编码技术来进行编码。 + + - 非 Varint 类型 + + - 分配固定数量的字节,字节数与实际值没有关系 + + - 64 位的数据类型 + - 32 位的数据类型 + + - 字符串类型 + + - 字符串类型属于基于长度分隔的线路类型,首先会有一个经过 Varint 编码的长度值,随后才是指定数量的字节数据 + +### 基于长度前缀的消息分帧 + +- 在写入消息本身之前,写入长度信息,来表明每条消息的大小,每条消息都有额外的 4 字节用来设置其大小,意味着 gRPC 通信可以处理大小不超过 4GB 的所有消息 +- 帧中还有单字节的无符号整数,用来表明数据是否进行了压缩 + + - 0 - 没有进行压缩 + - 1 - 使用 Message-Encoding 头信息中声明的机制进行了压缩 + +- 对于简单的消息,只需处理一条以长度为前缀的消息;而对于流消息,就会有多条以长度为前缀的消息要处理。 + +### 基于 HTTP/2 的 gRPC + +- http/2 核心术语 + + - 流 + + - 在一个已建立的连接上的双向字节流。一个流可以携带一条或多条消息。 + + - 帧 + + - HTTP/2 中最小的通信单元。每一帧都包含一个帧头,它至少要标记该帧所属的流。 + + - 消息 + + - 完整的帧序列,映射为一条逻辑上的 HTTP 消息,由一帧或多帧组成。这样的话,允许消息进行多路复用,客户端和服务器端能够将消息分解成独立的帧,交叉发送它们,然后在另一端进行重新组合。 + +- https://pic.imgdb.cn/item/6246f02527f86abb2aeae969.jpg +主流程 + + - gRPC 通道代表一个到端点的连接,也就是一个 HTTP/2 连接。 + - 客户端应用程序创建 gRPC 通道的时候,它会在幕后创建一个到服务器端的 HTTP/2 连接 + - 通道创建完成之后,就可以重用它来发送多个到服务器端的远程调用 + - 远程调用会映射为 HTTP/2 中的流,远程调用中的消息以 HTTP2 帧的形式进行发送,帧可以携带一条 gRPC 长度前缀消息,也可跨多帧 + +- 请求消息 + + - 组成 + + - 请求头信息 + - 以长度作为前缀的消息 + - 流结束标记 + + - 当完成对服务器端调用的初始化之后,客户端会以 HTTP2 数据帧的形式发送以长度作为前缀的消息。如果这条消息不适合放到一个数据帧中,那么它可以跨多个数据帧。请求消息的结束通过在最后一个 DATA 帧上添加 END_STREAM 标记来实现。 + +- 响应消息 + + - 组成 + + - 响应头信息 + - 以长度作为前缀的消息 + - trailer + - 如果没有发送以长度作为前缀的消息来响应客户端,则响应消息只会包含头信息和 trailer + + - END_STREAM 标记并不会随数据帧一起发送,而会作为单独的头信息来发送 + +- 理解 gRPC 通信模式中的消息流 + + - https://pic.imgdb.cn/item/6246f7c027f86abb2afc9d88.jpg +一元 RPC 模式 + - https://pic.imgdb.cn/item/6246f8cb27f86abb2aff3921.jpg +服务端流 RPC 模式 + - https://pic.imgdb.cn/item/6246f90927f86abb2affd1cd.jpg +客户端流 RPC 模式 + - https://pic.imgdb.cn/item/6246f93f27f86abb2a004caf.jpg +双向流 RPC 模式 + +### gRPC 实现结构 + +## 五、gRPC:超越基础知识 + +## 六、安全的 gRPC + +## 七、在生产环境中运行 gRPC + +## 八、gRPC 的生态系统 + diff --git "a/\345\210\206\345\270\203\345\274\217/gRPC \344\270\216\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221.xmind" "b/\345\210\206\345\270\203\345\274\217/gRPC \344\270\216\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221.xmind" new file mode 100644 index 0000000..e1eb7eb Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/gRPC \344\270\216\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/\344\273\216 Paxos \345\210\260 ZooKeeper.md" "b/\345\210\206\345\270\203\345\274\217/\344\273\216 Paxos \345\210\260 ZooKeeper.md" new file mode 100644 index 0000000..1f45c41 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/\344\273\216 Paxos \345\210\260 ZooKeeper.md" @@ -0,0 +1,784 @@ +# 从 Paxos 到 ZooKeeper + +## 一、分布式架构 + +### 从集中式到分布式 + +- 集中式的特点 + + - 一台或者多台主机组成中心节点,数据集中存储,所有业务单元集中部署在这个中心节点上,负责所有的功能处理。 + - 客户端仅仅负责数据的录入和输出,数据的存储与控制完全交由主机完成 + - 最大的特点就是部署结构简单,无须考虑多个节点之间的分布式写作问题 + +- 分布式的特点 + + - 分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。 + - 分布式的特征 + + - 分布性 + + - 多台计算机在空间上随意分布 + + - 对等性 + + - 计算机没有主从之分 + - 副本指的是分布式系统对数据和服务提供的一种冗余方式 + + - 并发性 + + - 并发性操作是非常常见的行为,比如一些一些共享的资源、数据库或分布式存储等 + + - 缺乏全局时钟 + + - 在分布式系统中,很难定义两个时间酒精谁先谁后,原因就是缺乏一个全局的时钟序列控制 + + - 故障总是会发生 + + - 有可能发生任何形式的故障,任何设计阶段考虑到的异常情况,一定会在系统实际运行中发生 + +- 分布式环境的各种问题 + + - 通信异常 + + - 光纤 + - 路由器 + - DNS + - 网络延时 + + - 网络分区(脑裂) + + - 出现局部小集群,不能组合成完整的分布式功能,可能会对数据库事务造成影响 + + - 三态 + + - 成功 + - 失败 + - 超时 + + - 发送过程中消息丢失 + - 响应反馈消息丢失 + + - 节点故障 + + - 节点宕机或者僵死 + +### 从 ACID 到 CAP/BASE + +- ACID + + - 事务 + + - 由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元(Unit),狭义上的事务特指数据库事务。 + + - 原子性(Atomicity) + + - 原子操作 + + - 全部成功执行 + - 全部不执行 + + - 一致性(Consistency) + + - 事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。 + - 诗句哭只包含成功事务提交的结果时,就能说数据库处于一致性状态 + + - 隔离性(Isolation) + + - 并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰 + - 未提交读 + + - 允许脏读,隔离级别最低 + + - 已提交读 + - 可重复读 + - 串行化 + + - 持久性(Durability) + + - 指一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的。 + +- 分布式事务 + + - 分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点之上。 + - 通常一个分布式事务中会涉及对多个数据源或业务系统的操作。 + +- CAP/BASE 理论 + + - CAP 定理 + + - 一个分布式系统不可能同时满足一致性、可用性和分区容错性这三个基本需求,最多只能同时满足其中的两项 + - 一致性 + + - 数据在多个副本之间是否能够保持一致的特性,执行更新操作后,应该保证系统的数据仍然处于一致的状态 + + - 可用性 + + - 服务必须一致处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果 + + - 分区容错性 + + - 分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障 + + - BASE 理论 + + - 基本可用 + + - 响应时间 上的损失 + - 功能上的损失 + + - 弱状态 + + - 允许系统在不用几点的数据副本之间进行数据同步的过程存在延时 + + - 最终一致性 + + - 需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性 + - 因果一致性 + + - A 更新后通知了 B,B 之后对数据的访问应该能够获取到 A 更新后的最新值,B 的更新也要基于 A 更新后的最新值,不能不能发生丢失更新的情况 + + - 读已之所写 + + - A 更新一个数据项之后自己总是能够访问到更新过的最新值 + + - 会话一致性 + + - 执行更新操作之后,客户端能够在同一个会话中时钟读取到该数据项的最新值。 + + - 单调读一致性 + + - 如果一个进程从系统中读取出一个数据项的某个值,系统对于该进程后续的任何数据访问都不应该返回旧值 + + - 单调写一致性 + + - 同一个进程的写操作被顺序地执行 + +## 二、一致性戏协议 + +### 2PC 与 3PC + +- 2PC(二阶段提交) + + - 保持原子性和一致性的一中短发 + - 阶段一:提交事务请求(投票阶段) + + - 事务询问 + + - 协调者向所有的参与者发送食物内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应 + + - 执行事务 + + - 各参与者节点执行事务操作,并将 Undo 和 Redo 信息记入事务日志中 + + - 各参与者向协调者反馈事务询问的响应 + + - 如果参与者成功执行事务操作,反馈 Yes,表示事务可以执行 + - 反之反馈 No,表示事务不可以执行 + + - 阶段二:执行事务提交 + + - 执行事务提交 + + - 发送提交请求 + + - 协调者向所有参与者节点发出 Commit 请求 + + - 事务提交 + + - 参与者接收到 Commit 请求后正式执行事务的提交操作,完成比较滞后释放占用的事务资源 + + - 反馈事务提交结果 + + - 参与者在完成事务提交之后向协调者发动 Ack 消息 + + - 完成事务 + + - 协调者接收到所有参与者反馈的 Ack 消息后完成事务 + + - 中断事务 + + - 发送回滚请求 + + - 协调者向所有参与者节点发出回滚 Rollback 请求 + + - 事务回滚 + + - 参与者收到回滚请求后利用 Undo 信息执行事务回滚,完成回滚之后释放占用的资源 + + - 反馈事务回滚结果 + + - 参与者完成事务回滚之后向协调者发送 Ack 消息 + + - 中断事务 + + - 协调者接收到所有参与者反馈的 Ack 消息之后,完成事务中断 + + - 优点 + + - 原理简单,实现方便 + + - 缺点 + + - 同步阻塞 + + - 所有参与事务的逻辑都处于阻塞状态,无法进行其他任何操作 + + - 单点问题 + + - 协调者如果出问题,流程无法运转,甚至在第二阶段会让参与者一直处于索性事务资源的状态中 + + - 脑裂 + + - 阶段二如果发生了局部网络异常或者协调者在尚未发送完 Commit 请求之前自身崩溃会导致收到 Commit 的参与者执行事务,没收到的不执行,数据不一致 + + - 太过保守 + + - 如果参与者崩溃会导致协调者不能回去所有参与饿着的响应信息,协调者只能依靠自身的超时机制判断是否需要中断事务 + - 二阶段提交斜体没有较为完善的容错机制,任意一个节点的失败都会导致整个事务的失败 + +- 3PC(三阶段提交) + + - 将 2PC 的第二阶段的「提交事务请求」一分为二,形成 CanCommit、PreCommit 和 do Commit 三个阶段组成的事务处理协议 + - 阶段一:CanCommit + + - 事务询问 + - 各参与者向协调者反馈事务询问的响应 + + - 阶段二:PreCommit + + - 执行事务预提交 + + - 发送与提交请求 + - 事务预提交 + - 各参与者向协调者反馈事务执行的响应 + + - 中断事务 + + - 发送中断请求 + - 中断事务 + + - 阶段三:doCommit + + - 执行提交 + + - 发送提交请求 + - 事务提交 + - 反馈事务提交结果 + - 完成事务 + + - 中断事务 + + - 发送中断请求 + - 事务回滚 + - 反馈事务回滚结果 + - 中断事务 + + - 可能故障 + + - 协调者出现问题 + - 协调者和参与者之间的网络之间出现故障 + + - 优点 + + - 降低参与者的阻塞范围,单点故障后继续达成一致 + + - 缺点 + + - 参与者接收到 PreCommit 消息之后,如果出现网络分区,协调者与参与者无法正常网络通信,参与者会进行实物的提交,出现数据的不一致性 + +### Paxos 算法 + +- 追本溯源 +- Paxos 理论的诞生 +- Paxos 算法详解 + + - 「过半」理念 + +## 三、Paxos 的工程实践 + +### Chubby + +- 概述 +- 应用场景 +- 设计目标 +- Chubby 技术架构 +- Paxos 协议实现 + +### Hypertable + +- 概述 +- 算法实现 + +## 四、ZooKeeper 与 Paxos + +### 初识 Zookeeper + +- ZooKeeper 介绍 + + - Chubby 的开源实现,典型的分布式数据一致性的解决方案 + + - 发布/订阅 + - 负载均衡 + - 命名服务 + - 分布式协调/通知 + - 集群管理 + - Master 选举 + - 分布式锁 + - 分布式队列 + + - ZooKeeper 可以保证分布式一致性的类型 + + - 顺序一致性 + + - 按照发起顺序执行 + + - 原子性 + + - 整个集群所有机器成功应用事务 + + - 单一视图 + + - 无论客户端连接任意一个 ZK 服务器看到的服务端诗句模型都是一致的 + + - 实时性 + + - ZK 仅仅保证一定的时间段内客户端最终的最新数据状态 + + - 可靠性 + + - 服务端在完成事务之后的状态变更会被一直保留 + + - 设计目标 + + - 简单的数据模型 + + - 一系列 ZNode 数据节点组成,类似于一个文件系统,存储在内存中以此实现提高服务器吞吐、减少延迟的目的 + + - 可以构建集群 + + - 在内存中维护当前的服务器状态,机器之间互相通信 + - 只要集群中存在超过一半的机器正常工作,整个集群就能够正常对外服务 + + - 顺序访问 + + - 来自客户端的每个更新请求,ZK 都会分配一个全局唯一的递增编号,这个编号反应快乐所有事物操作的先后顺序,从而实现更高层次的同步原语 + + - 高性能 + + - 全量数据存储在内存中,直接服务与客户端的所有非事物请求,尤其适用与读操作为主的应用场景 + +- ZooKeeper 从何而来 +- ZooKeeper 的基本概念 + + - 集群角色 + + - 最典型的集群模式就是主备模式(Master/Slave) + + - Master 处理所有写操作 + - Slave 负责异步复制获取最新数据,并且提供读服务 + + - ZK 中引入了 Leader、Follower、Observer + + - 通过选举选定一台「Leader」,为客户端提供读和写服务 + - Follower 和 Observer 都提供读服务,Observer 不参与 Leader 选举过程,也不参与「过半写成功」策略,单纯的提高读性能 + + - 会话(Session) + - 数据节点 + + - 机器节点 + + - 构成 ZK 集群的机器称之为机器节点 + + - 数据模型中的数据单元称之为数据节点(ZNode) + + - 持久节点 + + - 除非手动删除 + + - 临时节点 + + - 生命周期和客户端会话绑定,客户端失效,临时节点被移除 + + - 版本 + + - 每个 ZNode 都会维护一个叫做 Stat 的数据结构,Stat 记录 Znode 的三个数据版本 + + - version + + - ZNode 的当前版本 + + - cversion + + - ZNode 子节点的当前版本 + + - aversion + + - ZNode 的 ACL 版本 + + - Watcher(事件监听器) + + - 允许用户在指定节点上注册一些 Watcher,在一些特定事件触发的时候,ZK 会通知到感兴趣的客户端上去 + + - ACL(权限控制) + + - CREATE + + - 创建子节点的权限 + + - READ + + - 获取节点数据和子节点列表的权限 + + - WRITE + + - 更新节点数据的权限 + + - DELETE + + - 删除子节点的权限 + + - ADMIN + + - 设置节点 ACL 的权限 + +- 为什么选择 ZooKeeper + + - 成熟稳定且被大规模应用 + - 开源 + - 免费 + +### ZooKeeper 的 ZAB 协议 + +- ZAB 协议 + + 所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为 Leader 服务器,而余下的其他服务器则成为 Follower 服务器。Leader 服务器负责将一个客户端事务请求转换成一个事务 Proposal(提议),并将该 Proposal 分发给集群中所有的 Follower 服务器。之后 Leader 服务器需要等待所有 Follower 服务器的反馈,一旦超过半数的 Follower 服务器进行了正确的反馈后,那么 Leader 就会再次向所有的 Follower 服务器分发 Commit 消息,要求其将前一个 Proposal 进行提交。 + + - ZK 并没有完全采用 Paxos 短发,而是采用 ZAB 协议作为其数据一致性的核心算法 + - 实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性。 + +- 协议介绍 + + - 崩溃恢复 + + - 选举产生新的 Leader 服务器 + - 只允许唯一的一个 Leader 服务器来进行失去请求的处理 + - 让最高编号事务 Proposal 的机器来称为 Leader + + - 消息广播 + + - 原子广播协议,类似于一个二甲段提交过程 + - 对客户端的事务请求,Leader 服务器会为其生成对应的事务 Proposal,并将其发送给集群中其余所有的机器,然后再分别收集各自的选票,最后进行事务提交。 + +- 深入 ZAB 协议 + + - 系统模型 + + - 完整性 + - 前置性 + + - 问题描述 + + - 主进程周期 + - 事务 + + - 算法描述 + + - 发现 + + - Leader 选举 + + - 同步 + - 广播 + + - 接收客户端的时区请求并进行消息广播流程 + + - 运行分析 + + - 每一个进程的可能状态 + + - LOOKING + + - Leader 选举阶段 + + - FOLLOWING + + - Follower 服务器和 Leader 保持同步状态 + + - LEADING + + - Leader 服务器作为主进程领导状态 + +- ZAB 与 Paxos 算法的联系与区别 + + - 两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行 + - Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交。 + - 在 ZAB 协议中,每个 Proposal 中都包含了一个 epoch 值,用来代表当前的 Leader 周期,在 Paxos 算法中,同样存在这样的一个标识,只是名字变成了 Ballot + - 协议主要用于构建一个高可用的分布式数据主备系统 + - Paxs 算法则是用于构建一个分布式的一致性状态机系统。 + +## 八、ZooKeeper 运维 + +## 七、ZooKeeper 技术内幕 + +### 系统模型 + +- 数据模型 + + - 树 + + - ZNode 是 ZooKeeper 中数据的最小单元,每个 ZNode 上都可以保存数据,同时还可以挂载子节点,因此构成了一个层次化的命名空间,我们称之为树。 + + - 事务 ID + + - 在 Zookeeper 中,事务是指能够改变 Z0 keeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。 + - 对于每一个事务请求,ZooKeeper 都会为其分配一个全局唯一的事务 ID,用 ZXID 来表示,通常是一个 64 位的数字。 + - 每一个 ZXID 対应一次更新操作,从这些 ZXID 中可以间接地识别出 ZooKeeper 处理这些更新操作请求的全局顺序。 + +- 节点特性 + + - 节点类型 + + - 每个数据节点都是有生命周期的,长短取决于数据节点的类型 + + - 持久节点 + + - 手动删除 + + - 临时节点 + + - 和客户端的会话绑定在一起 + + - 顺序节点 + + - 持久顺序节点 + - 临时顺序节点 + + - 状态信息 + + - 每个数据节点除了存储了数据内容之外,还存储了数据节点本身的一些状态信息 + + - 事务 ID + - 版本信息 + - 子节点个数 + +- 版本 + + - version + - cversion + - aversion + +- Watcher:数据变更的通知 + + - Watcher 接口 + - Watch 事件 + +- ACL:保障数据的安全 + +### 序列化与协议 + +- Jute 介绍 +- 使用 Jute 进行序列化 +- 深入 Jute +- 通信协议 + +### 客户端 + +- ZooKeeper 实例 + + - 客户端的入口 + +- ClientWatchManager + + - 客户端 Watch 管理器 + +- HostProvider + + - 客户端地址列表管理器 + +- ClientCnxn + + - 客户端核心线程 + + - SendThread + + - 客户端和服务端之间的所有网络 I/O + + - EventThread + + - 客户端的事件处理 + +- 一次会话的创建过程 + + - 初始化 ZooKeeper 对象 + - 设置会话默认 Watcher + - 构造 ZooKeeper 服务器地址列表管理器:HostProvider。 + - 创建并初始化客户端网络连接器:ClientCnxn。 + - 初始化 SendThread 和 EventThread。 + - 启动 SendThread 和 EventThread + - 获取一个服务器地址 + - 创建 TCP 连接 + - 构造 ConnectRequest 请求 + - 发送请求 + - 接收服务端响应 + - 处理 Response + - 连接成功 + - 生成时间:SyncConnected-Node + - 查询 Watcher + - 处理事件 + +- 服务器地址列表 +- ClientCnxn:网络 I/O + +### 会话 + +- 会话状态 + + - CONNECTING + - CONNECTED + - RECONNECTING + - RECONNECTED + - CLOSE + +- 会话创建 +- 会话管理 + + - 分桶策略 + +- 会话清理 +- 重连 + +### 服务器启动 + +- 单机版服务器启动 +- 集群版服务器启动 + +### Leader 选举 + +- Leader 选举概述 + + - 服务器启动时期的 Leader 选举 + + - 每个 Server 会发出一个投票 + - 接收来自各个服务器的投票 + - 处理投票 + - 统计投票 + - 改变服务器状态 + + - 服务器运行期间的 Leader 选举 + + - 变更状态 + + - 将自己的状态改为 LOOKING,然后开始进入 Leader 选举过程 + + - 每个 Server 会发出一个投票 + - 接收来自各个服务器的投票 + - 处理投票 + - 统计投票 + - 改变服务器状态 + +- Leader 选举的算法分析 +- Leader 选举的实现细节 + +### 各服务器角色介绍 + +- Leader + + - 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。 + - 集群内部各服务器的调度者。 + - 请求处理链 + +- Follower + + - 处理客户端非事务请求,转发事务请求给 Leader 服务器。 + - 参与事务请求 Proposal 的投票。 + - 参与 Leader 选举投票 + +- Observer + + - 对于事务请求,则会转发给 Leader 服务器进行处理。 + +- 集群间消息通信 + +### 请求处理 + +- 会话创建请求 +- SetData 请求 +- 事务请求转发 +- GetData 请求 + +### 数据与存储 + +- 内存数据 +- 事务日志 +- snapshot-数据快照 +- 初始化 + +## 六、ZooKeeper 的典型应用场景 + +### 典型应用场景及实现 + +- 数据发布/订阅 +- 负载均衡 + + - 动态 DNS + +- 命名服务 +- 分布式协调、通知 +- 集群管理 +- Master 选举 +- 分布式锁 + + - 获取锁 + + - 在需要获取排他锁时,所有的客户端都会试图通过调用 create 接口,在 / exclusive_lock 节点下创建临时子节点 /exclusive_ lock/lock。 + - Zookeeper 会保证在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。 + - 没有获取到锁的客户端注册一个子节点变更的 Watcher 监听,实时监听到 lock 节点的变更情况 + + - 释放锁 + + - 当前获取锁的客户端机器发生宕机,那么 Zookeeper 上的这个临时节点就会被移除。 + - 正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。 + +- 分布式队列 + +### ZooKeeper 在大型分布是系统中的应用 + +- Hadoop +- HBase +- Kafka + +### ZooKeeper 在阿里巴巴的实践与应用 + +- RPC 服务框架:Dubbo + + - Dubbo 核心部分 + + - 远程通信 + + - 提供对多种基于长连接的 NIO 框架抽象封装,包括多种线程模型、序列化,以及「请求一响应」模式的信息交换方式。 + + - 集群容错 + + - 提供基于接口方法的远程过程透明调用,包括对多协议的支持,以及对软负载均衡、失败容错、地址路由和动态配置等集群特性的支持。 + + - 自动发现 + + - 提供基于注册中心的目录服务,使服务消费方能动态地査找服务提供方,使地址透明,使服务提供方可以平滑地增加或减少机器。 + +## 五、使用 ZooKeeper + +### 部署与运行 + +- 系统环境 +- 集群与单机 +- 运行服务 + +### 客户端脚本 + +### Java 客户端 API 使用 + +### 开源客户端 + +- ZKClient +- Curator + diff --git "a/\345\210\206\345\270\203\345\274\217/\344\273\216 Paxos \345\210\260 ZooKeeper.xmind" "b/\345\210\206\345\270\203\345\274\217/\344\273\216 Paxos \345\210\260 ZooKeeper.xmind" new file mode 100644 index 0000000..c99784b Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/\344\273\216 Paxos \345\210\260 ZooKeeper.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204 Spring Cloud \345\217\212\345\210\206\345\270\203\345\274\217\347\233\270\345\205\263\346\246\202\345\277\265.md" "b/\345\210\206\345\270\203\345\274\217/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204 Spring Cloud \345\217\212\345\210\206\345\270\203\345\274\217\347\233\270\345\205\263\346\246\202\345\277\265.md" new file mode 100644 index 0000000..3150573 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204 Spring Cloud \345\217\212\345\210\206\345\270\203\345\274\217\347\233\270\345\205\263\346\246\202\345\277\265.md" @@ -0,0 +1,368 @@ +# 杂乱总结的 Spring Cloud 及分布式相关概念 + +## Feign + +### FeignClient注解参数 + +- name + + - value 的别名 + +- value + + - name 的别名 + +- serviceId + + - 同 value 和 name,现在已经被弃用 + +- contextId + + - @FeignClient 在 Greenwich.SR1 版本之后增加一个属性值 contextId, +之前的版本如果有存在两个 name 属性相同 @FeignClient 对象,不会报错,但是升级后,会将相同 name 属性的实例对象认为是同一个 Bean ,从而抛出如下异常:「The bean 'icc-authority.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.」,解决方案就是在 @FeignClient 注解里增加 contextId 值将为 bean id。 + - Spring Cloud Feign 支持的核心概念是可以指定所要调用的服务(即指定服务名:spring.application.name)。一个消费者应用可能有多个 feign 客户端组件构成为一个集合,各个组件按需调用远程服务。该组件集合有一个名称,也可以使用 @FeignClient 注解的 contextId 属性自定义。 + - Spring Cloud 使用 FeignClientsConfiguration 按需为每个命名客户端创建一个新集合作作为 ApplicationContext ,包含了a feign.Decoder, a feign.Encoder, a feign.Contract,可以使用 @FeignClient 注解的 contextId 属性重写该集合的名称。 + +- qualifier +- url +- decode404 +- configuration +- fallback +- fallbackFactory +- path +- primary + +## Spring Cloud 微服务项目优雅重启 + +### 基于 SC 架构下,gateway、eureka、Spring Boot,请求会先经过网关,网关根据注册中心获取业务项目服务器地址,再转发到业务服务接口上,由此对应的服务实例重启总不是平滑的,在瞬间总是存在服务宕机的情况 + +- 业务项目实例 shutdown 时,会停止当前未完成的 REQUEST 请求。 +- 某个业务项目实例已经停止了,但是网关仍会转发请求过去,导致请求失败。 +- 个业务项目实例已经重新启动了,但是网关并不会马上向这个实例转发请求 + +### gateway、Ribbon 基本知识 + +- 关键类 RoutePredicateHandlerMapping,继承了 AbstractHandlerMapping,是 WebFlux 的 HandlerMapping,作用相当于 WebMVC 的 HandlerMapping,将请求映射到对应的 Handler 来处理 +- RoutePredicateHandlerMapping 会遍历所有路由 Route,获取符合规则的路由,并将获取到的 route 放入当前请求上下文的属性中。 +- 转发流程的重点就是选择服务节点,在选择服务节点之前,需要先获取负载均衡策略器。 + + - 根据 serviceId 去 spring-context 里获取负载均衡策略器,如果没有获取到,则自己初始化一个,默认负载均衡策略器是 ZoneAwareLoadBalancer。 + - 获取到负载均衡策略器之后,就要获取服务列表,并选择其中的一个节点 + +- 最终是获取 Eureka 里的节点信息,也就是 Ribbon通过 Eureka 获取服务列表,获取的服务列表会缓存在本地,每隔一段时间刷新(默认 30 秒) + +### Eureka 基本知识(核心类 DiscoveryClient) + +- 服务提供者启动后,会向 eureka-server 注册,同时会定时续约, +- 为了提升性能,服务提供者启用了本地缓存,缓存存在 localRegionApps 里,定时更新缓存。 +- Eureka 为了保证性能,引入了三级缓存机制 + + - 一级只读缓存 + + - readOnlyCacheMap + + - 默认配饰 + + - 如果开启了 useReadOnlyCache,则从 readOnlyCacheMap 获取数据。 + + - 二级缓存 + + - readWriteCacheMap + + - guava cache + + - 如果没有开启一级只读缓存,或者获取不到数据,则从 readWriteCacheMap 获取 + - 获取到数据后,如果开启了 useReadOnlyCache,则将结果设置到一级缓存中。 + + - 三级存储 register 存储在线服务信息 + + - 如果二级获取不到数据,则从 register 中获取数据,获取到数据后,将结果设置到 readWriteCacheMap 中。 + +### 基本解决方案 + +- 服务停止之前 + + - 注销注册中心信息 + + - DiscoveryClient 有个 shutdown 方法 + + - 清除 gateway、eureka-client 注册信息 + - 让 gateway、ribbon 重新获取服务列表(刷新缓存) + - 服务提供者停止接收请求 + + - 期间包含平滑处理文玩城的请求 + + - 停止服务提供者 + +- 服务启动且 Eureka 完成注册之后 + + - 启动服务提供者 + - 注册服务 + - 让 gateway、eureka-client 立即获取服务 + - 让 gateway ribbon 重新获取服务列表(刷新缓存) + +## Eureka 和 ZooKeeper 的区别 + +### ZK 保证 CP,即分区容错性和一致性 + +- 当 Master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 Leader 选举。问题在于,如果选举 Leader 的时间太长且选举期间整个 zk 集群都是不可用的,这就导致在选举期间注册服务瘫痪。 + +### Eureka 保证 AP,即分区容错性和可用性 + +- Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。 +- 只要有一台 Eureka 还在,就能保证注册服务可用 + +## 分布式事务 + +### 事务的基本知识 + +- 原子、一致、隔离、持久 +- 脏读(事务A读取到了事务B未提交的数据)、幻读(事务A读取某条数据两次,第二次被事务B进行了插入、删除、更新等操作)、不可重复读(事务A读取某条数据两次却得到不同的结果) +- 读未提交、读已提交、重复读(MySQL 默认)、序列化 +- CAP 理论 + + - Consistency 一致性 同一数据的多个副本是否实时相同。 + - Availability 可用性 一定时间内系统返回一个明确的结果则称为该系统可用。 + - Partition tolerance 分区容错性 将同一服务分布在多个系统中,从而保证某一个系统宕机,仍然有其他系统提供相同的服务。 + +- BASE 理论 + + - 通过牺牲一致性来换取系统的可用性和分区容错性 + - Basic Available 基本可用 + - Soft State:柔性状态 同一数据的不同副本的状态,可以不需要实时一致。 + - Eventual Consisstency:最终一致性 同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。 + +- 分布式事务三种场景 + + - 跨数据库分布式事务 + - 跨服务分布式事务 + - 混合式分布式事务 + +- 两阶段提交(XA) + + - 核心角色 + + - RM - 资源管理器 + - TM - 事务管理器 + - AP - 应用程序 + - CRMs - 跨服务的事务的传播 + + - 投票阶段 + + - TM 记录事务开始日志,并询问各个 RM 是否可以执行提交准备操作。 + - RM 收到指令后,评估自己的状态,尝试执行本地事务的预备操作:预留资源,为资源加锁、执行操作等,但是并不提交事务,并等待 TM 的后续指令 + + - 提交执行 + + - 如果所有 RM 在上一个步骤都返回执行成功 + + - TM 记录事务 commit 日志,并向所有 RM 发起事务提交指令。 + - RM 收到指令后,提交事务,释放资源,并向 TM 响应「提交完成」。 + - 如果 TM 收到所有 RM 的响应,则记录事务结束日志。 + + - 如果有 RM 在上一个步骤中返回执行失败或者超时没有应答,即事务失败处理 + + - 记录事务 abort 日志,向所有 RM 发送事务回滚指令。 + - RM 收到指令后,回滚事务,释放资源,并向 TM 响应回滚完成。 + - 如果 TM 收到所有 RM 的响应,则记录事务结束日志。 + + - 产生的问题 + + - TM 事务管理器的单点故障 + - RM 资源管理器一直持有资源锁,过多的 RM 导致系统吞吐量下降 + - 脑裂 + + - TM 在第二阶段通知 RM 提交事务的时候突然宕机,恢复的时候无法重新协调事务的一致性 + +- 三阶段提交 + + - 主要解决两阶段提交的单点故障问题 + - 基本不用 + +### 全局消息 + +### TCC(两阶段提交) + +- 补偿型事务,要求应用提供下面三个接口。核心思想是通过对资源的预留(提供中间态),尽早释放对资源的加锁,如果事务可以提交,则完成对预留资源的确认,如果事务要回滚,则释放预留的资源。 + +- Try + + - 完成业务检查(一致性)、预留业务资源(准隔离性) + +- Commit + + - 如果 try 阶段所有业务资源都预留成功,则执行 confirm 操作 + - 不做任何业务检查,仅仅使用预留的资源执行业务操作,如果失败会一直重试。 + +- Cancel + + - 如果 try 阶段所有业务资源都预留失败,则执行 cancel 操作 + - 取消执行业务操作,释放预留的资源,如果失败会一直重试。 + +- TCC 模式中,事务的发起者和参与者都需要记录事务日志,事务的发起者需要记录全局事务和各个分支事务的状态和信息;事务的参与者需要记录分支事务的状态。 +- 需要遵循三个策略 + + - 允许空回滚 + - 保持幂等性 + - 防止资源悬挂 + +### Sega + +- 补偿事务,但是它没有 try 阶段,而是把分布式事务看作一组本地事务构成的事务链。即按顺序执行事务 +- 不保证隔离性,与 TCC 一样遵循三个策略,适合长事务流程场景 + +### 队列 + +- 难点在于解决本地事务执行和消息发送的一致性:两者要同时执行成功或者同时取消执行。 +- 事务消息 + + - 事务消息和普通消息的区别在于事务消息发送成功后,处于 prepared 状态,不能被订阅者消费,等到事务消息的状态更改为可消费状态后,下游订阅者才可以监听到次消息。 + - 事务发起者预先发送一个事务消息。 + - MQ 系统收到事务消息后,将消息持久化,消息的状态是「待发送」,并给发送者一个 ACK 消息。 + - 事务发起者如果没有收到 ACK 消息,则取消本地事务的执行;如果收到了 ACK 消息,则执行本地事务,并给 MQ 系统再发送一个消息,通知本地事务的执行情况。 + - MQ 系统收到消息通知后,根据本地事务的执行情况更改事务消息的状态,如果成功执行,则将消息更改为「可消费」并择机下发给订阅者;如果事务执行失败,则删除该事务消息。 + - 本地事务执行完毕后,发给 MQ 的通知消息有可能丢失了。所以支持事务消息的 MQ 系统有一个定时扫描逻辑,扫描出状态仍然是“待发送”状态的消息,并向消息的发送方发起询问,询问这条事务消息的最终状态如何并根据结果更新事务消息的状态。因此事务的发起方需要给 MQ 系统提供一个事务消息状态查询接口。 + - 如果事务消息的状态是「可发送」,则 MQ 系统向下游参与者推送消息,推送失败会不停重试。 + - 下游参与者收到消息后,执行本地事务,本地事务如果执行成功,则给 MQ 系统发送 ACK 消息;如果执行失败,则不发送 ACK 消息,MQ 系统会持续推送给消息。 + +- 普通消息加数据库维护 + + - 核心思想是事务的发起方维护一个本地消息表,业务执行和本地消息表的执行处在同一个本地事务中。业务执行成功,则同时记录一条「待发送」状态的消息到本地消息表中。 + - 启动一个定时任务定时扫描本地消息表中状态为“待发送”的记录,并将其发送到 MQ 系统中,如果发送失败或者超时,则一直发送,直到发送成功后,从本地消息表中删除该记录。后续的消费订阅流程则与基于事务消息的模式一样。 + +- 最大努力通知型分布式事务 + + - 本质是通过引入定期校验机制来对最终一致性做兜底,对业务侵入性较低、对 MQ 系统要求较低,实现比较简单,适合于对最终一致性敏感度比较低、业务链路较短的场景 + +## 分布式锁 + +### 分布式锁的目的 + +- 保证同一时间只有一个客户端可以对共享资源进行操作。 + + - 多个客户端操作共享资源,对共享资源的操作一定是幂等性操作,在这里使用锁,是为了避免重复操作共享资源从而提高效率。 + - 只允许一个客户端操作共享资源,对共享资源的操作一般是非幂等性操作。 + +### 分布式锁的特点 + +- 互斥性: 同一时刻只能有一个线程持有锁 +- 可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁 +- 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁 +- 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效 +- 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒 + +### 实现方式 + +- 基于数据库 + + - 使用表的唯一约束特性 + - 创建一张锁表,表中设计主键、锁定的方法名、客户端唯一编号等核心字段,加锁的时候可以根据服务器 mac 地址与线程编号唯一来确定,释放锁的时候删除记录即可 + - 性能开销大 + +- 基于Redis + + - 利用 setnx+expire 命令 + + - SETNX 是 SET IF NOT Exists 的缩写,即 setnx key value,将 key 设置为 value,当键不存在时,才能成功,若键存在,什么也不做,成功返回 1,失败返回 0 。 + - 因为 setnx + expire 命令过程中不具有原子性,所以不推荐直接使用 + - 可以优化为使用 Lua 脚本将两个命令封装为一个操作进行使用,不过依然不推荐 + + - 如果客户端在操作共享资源的过程中,因为长期阻塞的原因,导致锁过期,那么接下来访问共享资源就不安全。(过期时间的设置问题) + + - 使用 set key value [EX seconds][PX milliseconds][NX|XX] 命令 (推荐) + + - EX seconds: 设定过期时间,单位为秒 + - PX milliseconds: 设定过期时间,单位为毫秒 + - NX: 仅当key不存在时设置值 + - XX: 仅当key存在时设置值 + + - Redlock 算法 与 Redisson 实现 + + - 主要解决上一条锁在集群、多节点的情况下锁失效的问题 + + - 数据同步过程中,master 宕机,slave 来不及同步数据就被选为 master,从而数据丢失。 + + - 客户端 C1 从 Master 获取了锁。 +Master 宕机了,存储锁的 key 还没有来得及同步到 Slave 上。 +Slave 升级为 Master。 +客户端 C2 从新的 Master 获取到了对应同一个资源的锁。 + + - 获取当前时间(毫秒) + - 发起请求,并设置超时时间,超时时间要小于锁的过期时间 + - 使用当前时间减掉开始获取锁的时间得到获取锁所使用的时间,这个时间要满足小于锁失败的时间并且实例当中超过一半的实例都获取到了锁才算成功 + - 如果锁获取失败,Redis 所有实例进行解锁,及时某些实例没有枷锁成功 + - 存在的问题 + + - 节点崩溃重启,会出现多个客户端持有锁 + + - 线程1持有 ABCDE 中的 ABC,C 宕机,重启之后线程2获取 CDE 的锁,这样的话线程12同时获得了 C 的锁 + - 延迟重启解决 + + - 时间跳跃 + + - 线程1持有 ABCDE 中的 ABC,C 的时钟向前跳跃,锁快速失效,线程2获取 CDE 的锁,这样的话线程12同时获得了 C 的锁 + - 禁止人为修改系统时间 + + - 超时导致锁失效 + + - RedLock 算法并没有解决,操作共享资源超时,导致锁失效的问题 + + - 运维成本高,操作不好容易死锁 + +- 基于 ZooKeeper + + - 临时有序节点可以实现的分布式锁 + + - 当 znode 被声明为 EPHEMERAL 后,如果创建 znode 的客户端崩溃,相应的 znode 会被自动删除。避免了设置过期时间的问题。 + - 客户端尝试创建一个 znode 节点,第一个客户端就创建成功了,相当于拿到了锁;而其它的客户端会创建失败(znode 已存在),获取锁失败。 + - 虽然避免了设置了有效时间问题,但是依然有可能出现多个客户端操作共享资源的。 + + - 每个客户端对某个方法加锁时,在 zookeeper 上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 + - 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 + - 集群情况下 ZooKeeper 节点数量一般是奇数,且一定 >= 3。 + + - 集群情况下 ZooKeeper 一般的写数据流程 + + - Client 向 Follwer 发出一个写的请求 +Follwer 把请求发送给 Leader +Leader 接收到以后开始发起投票并通知 Follwer 进行投票 +Follwer 把投票结果发送给 Leader,只要半数以上返回了 ACK 信息,就认为通过 +Leader 将结果汇总后如果需要写入,则开始写入的同时把写入操作通知给 Leader,然后 commit; +Follwer把请求结果返回给Client + - 全局串行化操作 + + - 采用ZooKeeper 作为分布式锁,你要么就获取不到锁,一旦获取到了,必定节点的数据是一致的 + - 不像 Redis 那样依赖全局时间和锁的有效时间 + +### 分布式锁与本地锁 + +- 本地锁 + + - 单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行,以防止并发修改变量带来数据不一致或者数据污染的现象。​ + - 线程之间共享内存,只要使用线程锁就可以解决并发问题。 + + - synchronized + - Lock + +- 分布式锁 + + - 分布式情况下(多JVM),线程 A 和线程 B 很可能不是在同一 JVM 中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。 + - 分布式锁是控制分布式系统同步访问共享资源的一种方式。 + - 分布式锁的条件 + + - 排他性 + + - 同一时间,只能有一个客户端获取锁,其他客户端不能同事获取锁。 + + - 避免死锁 + + - 这把锁在一定的时间后需要释放,否则会产生死锁,这里面包括正常释放锁和非正常释放锁,比如即使一个客户端在持锁期间发生故障而没有释放锁也要保证后续的客户端能加锁。 + + - 自己解锁 + + - 加锁和解锁都应该是同一个客户端去完成,不能去解别人的锁。 + + - 高可用 + + - 获取和释放锁必须高可用且优秀。 + diff --git "a/\345\210\206\345\270\203\345\274\217/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204 Spring Cloud \345\217\212\345\210\206\345\270\203\345\274\217\347\233\270\345\205\263\346\246\202\345\277\265.xmind" "b/\345\210\206\345\270\203\345\274\217/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204 Spring Cloud \345\217\212\345\210\206\345\270\203\345\274\217\347\233\270\345\205\263\346\246\202\345\277\265.xmind" new file mode 100644 index 0000000..f1d2b8f Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/\346\235\202\344\271\261\346\200\273\347\273\223\347\232\204 Spring Cloud \345\217\212\345\210\206\345\270\203\345\274\217\347\233\270\345\205\263\346\246\202\345\277\265.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\346\265\205\345\207\272 Docker.md" "b/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\346\265\205\345\207\272 Docker.md" new file mode 100644 index 0000000..b664b4c --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\346\265\205\345\207\272 Docker.md" @@ -0,0 +1,1068 @@ +# 深入浅出 Docker + +## 第一部分 Docker 概览 + +### 第1章 容器发展之路 + +### 第2章 走进Docker + +- Docker——简介 + + - Docker 是一种运行于 Linux 和 Windows 上的软件,用于创建、管理和编排容器。Docker 是在 GitHub 上开发的 Moby 开源项目的一部分。 + +- Docker 运行时与编排引擎 + + - Docker 引擎是用于运行和编排容器的基础设施工具。 + +### 第3章 Docker 安装 + +### 第4章 纵观 Docker + +- 运维视角 + + - 镜像 + + - 在 Docker 世界中,镜像实际上等价于未运行的容器。 + + - 镜像包含了基础操作系统,以及应用程序运行所需的代码和依赖包。 + + - Docker 的每个镜像都有自己的唯一 ID。用户可以通过引用镜像的 ID 或名称来使用镜像。如果用户选择使用镜像 ID,通常只需要输入 ID 开头的几个字符即可——因为 ID 是唯一的,Docker 知道用户想引用的具体镜像是哪个。 + + - 容器 + + - 使用 docker container run 命令从镜像来启动容器 + + - 连接到运行中的容器 + + - 执行 docker container exec 命令,可以将 Shell 连接到一个运行中的容器终端。 + + - 通过 docker container stop 和 docker container rm 命令来停止并杀死容器。 + + - 通过运行 docker container ls -a 命令,让 Docker 列出所有容器,甚至包括那些处于停止状态的。 + +- 开发视角 + + - Dockerfile 是一个纯文本文件,其中描述了如何将应用构建到 Docker 镜像当中。 + + - 使用 docker image build 命令,根据 Dockerfile 中的指令来创建新的镜像。 + +## 第二部分 Docker 技术 + +### 第5章 Docker 引擎 + +- 简介 + + - Docker 引擎是用来运行和管理容器的核心软件。 + + - 基于开放容器计划(OCI)相关标准的要求,Docker 引擎采用了模块化的设计原则,其组件是可替换的。 + + - Docker 引擎主要的组件构成 + + - Docker客户端(Docker Client) + + - Docker守护进程(Docker daemon) + + - containerd + + - runc + +- https://pic1.imgdb.cn/item/6334421016f2c2beb157ed33.jpg +详解 + + - runc + + - runc 是 OCI 容器运行时规范的参考实现,生来只有一个作用——创建容器,实质上就是一个独立的容器运行时工具。 + + - https://pic1.imgdb.cn/item/63353a4916f2c2beb1265faf.jpg +containerd + + - 主要任务是容器的生命周期管理——start | stop | pause | rm .... + + - shim + + - shim是实现无daemon的容器不可或缺的工具。 + + - containerd 指挥 runc 来创建新容器。事实上,每次创建容器时它都会 fork 一个新的 runc 实例。不过,一旦容器创建完毕,对应的 runc 进程就会退出。 + + - 一旦容器进程的父进程 runc 退出,相关联的 containerd-shim 进程就会成为容器的父进程。 + + - 保持所有 STDIN 和 STDOUT 流是开启状态,从而当 daemon 重启的时候,容器不会因为管道(pipe)的关闭而终止。 + + - 将容器的退出状态反馈给 daemon。 + +### 第6章 Docker 镜像 + +- https://pic1.imgdb.cn/item/6342695316f2c2beb153f72e.jpg +简介 + + - Docker 镜像就像停止运行的容器,可以将镜像理解为类(Class),镜像可以理解为一种构建时(build-time)结构,而容器可以理解为一种运行时(run-time)结构 + +- 详解 + + - 镜像和容器 + + - 使用 docker container run 和 docker service create 命令从某个镜像启动一个或多个容器。 + + - 一旦容器从镜像启动后,二者之间就变成了互相依赖的关系,并且在镜像上启动的容器全部停止之前,镜像是无法被删除的。 + + - 镜像通常比较小 + + - 拉取镜像 + + - docker image ls + + - 检查 Docker 主机的本地仓库中是否包含镜像 + + - 提供 --filter 参数来过滤镜像列表内容 + + - dangling + + - 可以指定true 或者false ,仅返回悬虚镜像(true),或者非悬虚镜像(false) + + - 那些没有标签的镜像被称为悬虚镜像,在列表中展示为 : + + - docker image ls --filter dangling=true + + - before + + - 需要镜像名称或者 ID 作为参数,返回在之前被创建的全部镜像 + + - since + + - 与 before 类似,不过返回的是指定镜像之后创建的全部镜像 + + - label + + - 根据标注(label)的名称或者值,对镜像进行过滤 + + - reference + + - docker image ls --filter=reference="*:latest" + + - 使用 --format 参数来通过 Go 模板对输出内容进行格式化 + + - docker image ls --format "{{.Size}}" + + - 返回 Docker 主机上镜像的大小属性 + + - docker image ls --format "{{.Repository}}: {{.Tag}}: {{.Size}}" + + - 返回全部镜像,但是只显示仓库、标签和大小信息 + + - 将镜像取到 Docker 主机本地的操作是拉取。 + + - 镜像仓库服务 + + - Docker 客户端的镜像仓库服务是可配置的,默认使用 Docker Hub + + - 镜像仓库服务包含多个镜像仓库(Image Repository)。同样,一个镜像仓库中可以包含多个镜像。 + + - Docker Hub 也分为官方仓库(Official Repository)和非官方仓库(Unofficial Repository) + + - 镜像命名和标签 + + - docker image pull : + + - 只需要给出镜像的名字和标签,就能在官方仓库中定位一个镜像 + + - 如果没有在仓库名称后指定具体的镜像标签,则 Docker 会假设用户希望拉取标签为 latest 的镜像。 + + - 为镜像打多个标签 + + - 一个镜像可以根据用户需要设置多个标签 + + - latest 是一个非强制标签,不保证指向仓库中最新的镜像 + + - 通过 CLI 方式搜索 Docker Hub + + - docker search + + - 允许通过 CLI 的方式搜索 Docker Hub + + - 可以通过「NAME」字段的内容进行匹配,并且基于返回内容中任意列的值进行过滤。 + + - 可以使用 --filter "is-official=true" ,使命令返回内容只显示官方镜像 + + - 可以使用 --filter "is-automated=true",使命令返回只显示自动创建的仓库 + + - 默认情况下,Docker 只返回 25 行结果。可以指定 --limit 参数来增加返回内容行数,最多为 100 行。 + + - https://pic1.imgdb.cn/item/6342747216f2c2beb1675947.jpg +镜像和分层 + + - Docker 镜像由一些松耦合的只读镜像层组成。Docker 负责堆叠这些镜像层,并且将它们表示为单个统一的对象。 + + - docker image pull 命令的输出,以 Pull complete 结尾的每一行都代表了镜像中某个被拉取的镜像层。 + + - docker image inspect + + - 使用了镜像的 SHA256 散列值来标识镜像层。 + + - docker history + + - 显示了镜像的构建历史记录,但其并不是严格意义上的镜像分层。 + + - 所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。 + + - 共享镜像层 + + - 多个镜像之间可以并且确实会共享镜像层。这样可以有效节省空间并提升性能。 + + - 根据摘要拉取镜像 + + - 每一个镜像都有一个基于其内容的密码散列值,在 docker image ls 命 令之后添加 --digests 参数即可在本地查看镜像摘要 + + - 已知镜像的摘要,那么可以使用摘要值再次拉取这个镜像 + + - 多层架构的镜像 + + - 某个镜像仓库标签(repository:tag)下的镜像可以同时支持 64 位 Linux、PowerPC Linux、64 位 Windows 和 ARM 等多种架构 + + - Manifest列表(新) + + - 某个镜像标签支持的架构列表。其支持的每种架构,都有自己的 Mainfest 定义,其中列举了该镜像的构成。 + + - 删除镜像 + + - docker image rm + + - 在当前主机上删除该镜像以及相关的镜像层。 + + - 如果某个镜像层被多个镜像共享,那只有当全部依赖该镜像层的镜像都被删除后,该镜像层才会被删除。 + + - 如果被删除的镜像上存在运行状态的容器,那么删除操作不会被允许。 + + - docker image rm $(docker image ls -q) -f + + - 删除 Docker 主机上全部镜像的快捷方式 + +- 命令 + + - docker image pull + + - 下载镜像的命令 + + - docker image ls + + - 列出了本地 Docker 主机上存储的镜像 + + - docker image inspect + + - 完美展示了镜像的细节,包括镜像层数据和元数据。 + + - docker image rm + + - 删除镜像 + +### 第7章 Docker 容器 + +- 简介 + + - 容器是镜像的运行时实例 + +- 详解 + + - 容器vs虚拟机 + + - Hypervisor 是硬件虚拟化,将硬件物理资源划分为虚拟资源 + + - 容器是操作系统虚拟化,容器将系统资源划分为虚拟资源。 + + - 虚拟机的额外开销 + + - 使用容器可以在更少的资源上运行更多的应用,启动更快,并且支付更少的授权和管理费用,同时面对未知攻击的风险也更小 + + - 检查 Docker daemon + + - docker version + + - service docker status + + - systemctl is-active docker + + - 启动一个简单容器 + + - docker container run + + - 容器进程 + + - 容器如果不运行任何进程则无法存在 + + - docker container ls + + - 观察当前系统正在运行的容器列表。 + + - docker container exec + + - 将终端重新连接到 Docker + + - 容器生命周期 + + - 可以根据需要多次停止、启动、暂停以及重启容器,并且这些操作执行得很快。 + + - 容器及其数据是安全的。直至明确删除容器前,容器都不会丢弃其中的数据。 + + - 如果将容器数据存储在卷中,数据也会被保存下来。 + + - 优雅地停止容器 + + - 先停止容器然后删除,给容器中运行的应用/进程一个停止运行并清理残留数据的机会 + + - 利用重启策略进行容器的自我修复 + + - 重启策略 + + - always + + - 除非容器被明确停止,比如通过 docker container stop 命令,否则该策略会一直尝试重启处于停止状态的容器。 + + - unless-stopped + + - 指定了 --restart unless-stopped 并处于 Stopped (Exited) 状态的容器,不会在 Docker daemon 重启的时候被重启。 + + - on-failed + + - 退出容器并且返回值不是 0 的时候,重启容器。就算容器处于 stopped 状态,在 Docker daemon 重启的时候,容器也会被重启。 + +- 命令 + + - docker container run + + - 启动新容器 + + - docker container ls + + - 列出所有在运行(UP)状态的容器 + + - 使用-a 标记,还可以看到处于停止(Exited)状态的容器。 + + - docker container exec + + - 允许用户在运行状态的容器中,启动一个新进程。 + + - docker container stop + + - 停止运行中的容器,并将状态置为Exited(0) + + - docker container start + + - 重启处于停止(Exited)状态的容器,可以指定容器的名称或者 ID + + - docker container rm + + - 删除停止运行的容器 + + - 推荐首先使用 docker container stop 命令停止容器,然后使用 docker container rm 来完成删除 + +### 第8章 应用的容器化 + +- 简介 + + - 将应用整合到容器中并且运行起来的这个过程,称为「容器化」,容器能够简化应用的构建、部署和运行过程 + + - https://pic1.imgdb.cn/item/63428a8316f2c2beb18f20c6.jpg +应用容器化过程主要步骤 + + - 编写应用代码 + + - 创建一个 Dockerfile,其中包括当前应用的描述、依赖以及该如何运行这个应用 + + - 对该 Dockerfile 执行 docker image build 命令。 + + - 等待 Docker 将应用程序构建到 Docker 镜像中 + +- 详解 + + - 单体应用容器化 + + - Dockerfile 用途 + + - 对当前应用的描述 + + - 指导 Docker 完成应用的容器化(创建一个包含当前应用的镜像) + +- 命令 + + - docker image build + + - 读取 Dockerfile,并将应用程序容器化 + + - 使用 -t 参数为镜像打标签 + + - 使用 -f 参数指定 Dockerfile 的路径和名称 + + - Dockerfile 中的 FROM 指令用于指定要构建的镜像的基础镜像。它通常是 Dockerfile 中的第一条指令。 + + - Dockerfile 中的 RUN 指令用于在镜像中执行命令,这会创建新的镜像层。每个 RUN 指令创建一个新的镜像层。 + + - Dockerfile 中的 COPY 指令用于将文件作为一个新的层添加到镜像中。通常使用 COPY 指令将应用代码赋值到镜像中。 + + - Dockerfile 中的 EXPOSE 指令用于记录应用所使用的网络端口。 + + - Dockerfile 中的 ENTRYPOINT 指令用于指定镜像以容器方式启动后默认运行的程序。 + +### 第9章 使用 Docker Compose 部署应用 + +- 简介 + + - Docker Compose 解决部署和管理繁多的服务问题,通过一个声明式的配置文件描述整个应用,从而使用一条命令完成部署。 + +- 详解 + + - docker-compose.yml 文件结构 + + - version + + - 必须指定,总是位于文件的第一行。定义了 Compose 文件格式(主要是API)的版本。 + + - services + + - 定义不同的应用服务 + + - Docker Compose 会将每个服务部署为一个容器,并且会使用 key 作为容器名字的一部分 + + - 服务定义指令 + + - build + + - 指定 Docker 基于当前目录下 Dockerfile 中定义的指令来构建一个新镜像。该镜像会被用于启动该服务的容器 + + - command + + - 指定 Docker 在容器中执行名为 XXX 作为主程序 + + - ports + + - 指定 Docker 将容器内(-target )的 XXX 端口映射到主机(published )的 XXX 端口 + + - networks + + - 使得 Docker 可以将服务连接到指定的网络上 + + - volumes + + - 卷挂载 + + - networks + + - 指引 Docker 创建新的网络 + + - volumes + + - 指引 Docker 来创建新的卷 + +- 命令 + + - docker-compose up + + - 部署一个 Compose 应用 + + - 默认情况下该命令会读取名为 docker-compose.yml 或 docker-compose.yaml 的文件,当然用户也可以使用 -f 指定其他文件名 + + - 通常情况下,会使用 -d 参数令应用在后台启动。 + + - docker-compose stop + + - 停止 Compose 应用相关的所有容器,但不会删除它们 + + - 被停止的应用可以很容易地通过 docker-compose restart 命令重新启动。 + + - docker-compose rm + + - 删除已停止的 Compose 应用。它会删除容器和网络,但是不会删除卷和镜像。 + + - docker-compose restart + + - 重启已停止的 Compose 应用 + + - 如果用户在停止该应用后对其进行了变更,那么变更的内容不会反映在重启后的应用中,这时需要重新部署应用使变更生效。 + + - docker-compose ps + + - 列出 Compose 应用中的各个容器 + + - 输出内容包括当前状态、容器运行的命令以及网络端口。 + + - docker-compose down + + - 停止并删除运行中的 Compose 应用 + + - 删除容器和网络,但是不会删除卷和镜像。 + +### 第10章 Docker Swarm + +- 简介 + + - Swarm 核心组件 + + - 企业级的 Docker 安全集群 + + - Swarm 将一个或多个 Docker 节点组织起来,使得用户能够以集群方式管理它们 + + - 微服务应用编排引擎 + + - Swarm 提供了一套丰富的 API 使得部署和管理复杂的微服务应用变得易如反掌。 + +- 详解 + + - https://pic1.imgdb.cn/item/634397cf16f2c2beb1636f9b.jpg +Swarm 概括 + +- 命令 + + - docker swarm init + + - 创建一个新的Swarm。 + + - 执行该命令的节点会成为第一个管理节点,并且会切换到 Swarm 模式。 + + - docker swarm join-token + + - 查询加入管理节点和工作节点到现有 Swarm 时所使用的命令和 Token。 + + - 要获取新增管理节点的命令,请执行 docker swarm join-token manager 命令; + + - 要获取新增工作节点的命令,请执行 docker swarm join-token worker 命令。 + + - docker node ls + + - 要获取新增工作节点的命令,请执行 docker swarm join-token worker 命令。 + + - docker service create + + - 创建一个新服务 + + - docker service ls + + - 列出 Swarm 中运行的服务,以及诸如服务状态、服务副本等基本信息。 + + - docker service ps + + - 给出更多关于某个服务副本的信息。 + + - docker service inspect + + - 获取关于服务的详尽信息。附加 --pretty 参数可限制仅显示重要信息。 + + - docker service scale + + - 对服务副本个数进行增减。 + + - docker service update + + - 对运行中的服务的属性进行变更。 + + - docker service logs + + - 查看服务的日志。 + + - docker service rm + + - 从 Swarm 中删除某服务。 + +### 第11章 Docker 网络 + +- 简介 + + - 容器网络模型(CNM) + + - 单机桥接网络(Single-Host Bridge Network) + + - 多机覆盖网络(Multi-Host Overlay) + +- 详解 + + - 基础理论 + + - Docker 网络架构主要部分构成 + + - CNM + + - 设计标准,在 CNM 中,规定了 Docker 网络架构的基础组成要素。 + + - https://pic1.imgdb.cn/item/6343b6d816f2c2beb1a571a9.jpg +基本要素 + + - 沙盒(Sandbox) + + - 一个独立的网络栈 + + - 包括以太网接口、端口、路由表以及 DNS 配置。 + + - 终端(Endpoint) + + - 虚拟网络接口 + + - 负责创建连接。在 CNM 中,终端负责将沙盒连接到网络。 + + - 网络(Network) + + - 802.1d 网桥(类似交换机)的软件实现,网络就是需要交互的终端的集合,并且终端之间相互独立。 + + - Libnetwork + + - CNM 的具体实现,并且被 Docker 采用。 + + - 通过 Go 语言编写,并实现了 CNM 中列举的核心组件。 + + - 此外它还实现了本地服务发现(Service Discovery)、基于 Ingress 的容器负载均衡,以及网络控制层和管理层功能。 + + - https://pic1.imgdb.cn/item/6343b83116f2c2beb1a80e41.jpg +驱动 + + - 通过实现特定网络拓扑的方式来拓展该模型的能力。 + + - 单机桥接网络 + + - 最简单的 Docker 网络 + + - 单机 + + - 意味着该网络只能在单个 Docker 主机上运行,并且只能与所在 Docker 主机上的容器进行连接。 + + - 桥接 + + - 802.1.d 桥接的一种实现(二层交换机) + + - 多机覆盖网络 + + - 允许单个网络包含多个主机,这样不同主机上的容器间就可以在链路层实现通信。 + +- 命令 + + - docker network ls + + - 列出运行在本地 Docker 主机上的全部网络。 + + - docker network create + + - 创建新的 Docker 网络 + + - docker network create -d overlay overnet 会创建一个新的名为 overnet 的覆盖网络,其采用的驱动为 Docker Overlay 。 + + - docker network inspect + + - 提供 Docker 网络的详细配置信息 + + - docker network prune + + - 删除 Docker 主机上全部未使用的网络 + + - docker network rm + + - 删除 Docker 主机上指定网络 + +### 第12章 Docker 覆盖网络 + +- 简介 + + - 创建扁平的、安全的二层网络来连接多个主机,容器可以连接到覆盖网络并直接互相通信。 + + - Docker 提供了原生覆盖网络的支持,易于配置且非常安全。 + +- 详解 + + - https://pic1.imgdb.cn/item/6345227816f2c2beb120e52b.jpg +VXLAN + + - 是一种封装技术,能使现存的路由器和网络架构看起来就像普通的IP/UDP包一样,并且处理起来毫无问题。 + +- 命令 + + - docker network create + + - 创建新网络所使用的命令 + + - -d 参数允许用户指定所用驱动,常见的驱动是Overlay + + - docker network ls + + - 列出 Docker 主机上全部可见的容器网络 + + - docker network inspect + + - 查看特定容器网络的详情,包括范围、驱动、IPv6、子网配置、VXLAN 网络 ID 以及加密状态 + + - docker network rm + + - 删除指定网络 + +### 第13章 卷与持久化数据 + +- 简介 + + - 数据分类 + + - 持久化 + + - 卷与容器是解耦的,可以独立地创建并管理卷,卷并未与任意容器生命周期绑定 + + - 用户可以删除一个关联了卷的容器,但是卷并不会被删除 + + - 非持久化 + + - 每个 Docker 容器都有自己的非持久化存储 + + - 非持久化存储自动创建,从属于容器,生命周期与容器相同 + + - 删除容器也会删除全部非持久化数据 + +- 详解 + + - 容器与非持久数据 + + - 容器擅长无状态和非持久化事务 + + - 非持久存储属于容器的一部分,并且与容器的生命周期一致 + + - 容器创建时会创建非持久化存储,同时该存储也会随容器的删除而删除 + + - 容器与持久化数据 + + - 用户创建卷,然后创建容器,接着将卷挂载到容器上 + + - 卷会挂载到容器文件系统的某个目录之下,任何写到该目录下的内容都会写到卷中。 + + - 即使容器被删除,卷与其上面的数据仍然存在。 + + - 创建和管理容器卷 + + - 卷插件 + + - 块存储 + + - 相对性能更高,适用于对小块数据的随机访问负载。 + + - 文件存储 + + - 包括NFS和SMB协议的系统,同样在高性能场景下表现优异。 + + - 对象存储 + + - 适用于较大且长期存储的、很少变更的二进制数据存储。通常对象存储是根据内容寻址,并且性能较低。 + + - 卷在容器和服务中的使用 + + - 如果指定了已经存在的卷,Docker 会使用该卷 + + - 如果指定的卷不存在,Docker 会创建一个卷 + +- 命令 + + - docker volume create + + - 创建新卷。 + + - 默认情况下,新卷创建使用 local 驱动,但是可以通过 -d 参数来指定不同的驱动。 + + - docker volume ls + + - 列出本地 Docker 主机上的全部卷 + + - docker volume inspect + + - 查看卷的详细信息 + + - 查看卷在 Docker 主机文件系统中的具体位置 + + - docker volume prune + + - 删除未被容器或者服务副本使用的全部卷。 + + - docker volume rm + + - 删除未被使用的指定卷 + +### 第14章 使用 Docker Stack 部署应用 + +- 简介 + + - Stack 提供了简单的方式来部署应用并管理其完整的生命周期:初始化部署 > 健康检查 > 扩容 > 更新 > 回滚,以及其他功能 + +- https://pic1.imgdb.cn/item/6346339716f2c2beb1e72426.jpg +详解 + + - Stack 文件 + + - Stack 文件就是 Docker Compose 文件。唯一的要求就是 version: 需要是「3.0」或者更高的值。 + + - 网络 + + - 密钥 + + - 服务 + +- 命令 + + - docker stack deploy + + - 根据 Stack 文件(通常是 docker-stack.yml )部署和更新 Stack 服务的命令 + + - docker stack ls + + - 列出 Swarm 集群中的全部 Stack,包括每个 Stack 拥有多少服务 + + - docker stack ps + + - 列出某个已经部署的 Stack 相关详情,服务副本在节点的分布情况,以及期望状态和当前状态。 + + - docker stack rm + + - 从 Swarm 集群中移除 Stack + +### 第15章 Docker 安全 + +- 简介 + + - Linux Docker 利用了大部分 Linux 通用的安全技术 + + - 命名空间(Namespace) + + - 控制组(CGroup) + + - 系统权限(Capability) + + - 强制访问控制(MAC)系统 + + - 安全计算(Seccomp) + + - Docker 平台原生安全技术 + + - Docker Swarm模式 + + - 默认是开启安全功能的 + + - 无须任何配置,就可以获得加密节点 ID、双向认证、自动化 CA 配置、自动证书更新、加密集群存储、加密网络等安全功能。 + + - Docker内容信任(Docker Content Trust, DCT) + + - 允许用户对镜像签名,并且对拉取的镜像的完整度和发布者进行验证。 + + - Docker安全扫描(Docker Security Scanning) + + - 分析 Docker 镜像,检查已知缺陷,并提供对应的详细报告。 + + - Docker 密钥 + + - 存储在加密集群存储中,在容器传输过程中实时解密,使用时保存在内存文件系统并运行了一个最小权限模型。 + +- 详解 + + - Linux 安全技术 + + - Namespace + + - 将操作系统(OS)进行拆分,使一个操作系统看起来像多个互相独立的操作系统一样。 + + - Docker 容器是由各种命名空间组合而成的 + + - Docker 容器本质就是命名空间的有组织集合 + + - Docker 如何使用每个命名空间 + + - 进程 ID 命名空间 + + - Docker 使用 PID 命名空间为每个容器提供互相独立的容器树 + + - 每个容器都拥有自己的进程树,意味着每个容器都有自己的 PID 为 1 的进程 + + - PID 命名空间也意味着容器不能看到其他容器的进程树,或者其所在主机的进程树。 + + - 网络命名空间 + + - Docker 使用 NET 命名空间为每个容器提供互相隔离的网络栈。 + + - 网络栈中包括接口、ID 地址、端口地址以及路由表。 + + - 每个容器都有自己的 eth0 网络接口,并且有自己独立的 IP 和端口地址。 + + - 挂载点命名空间 + + - 每个容器都有互相隔离的根目录 / + + - 个容器都有自己的 /etc 、/var 、/dev 等目录 + + - 容器内的进程不能访问 Linux 主机上的目录,或者其他容器的目录,只能访问自己容器的独立挂载命名空间。 + + - 进程内通信命名空间 + + - Docker 使用 IPC 命名空间在容器内提供共享内存。 + + - IPC 提供的共享内存在不同容器间也是互相独立的。 + + - 用户命名空间 + + - Docker 允许用户使用 USER 命名空间将容器内用户映射到 Linux 主机不同的用户上。 + + - 容器内的 root 用户映射到 Linux 主机的非 root 用户上。 + + - UTS 命名空间 + + - Docker 使用 UTS 命名空间为每个容器提供自己的主机名称 + + - Control Group + + - 控制组用于限额 + + - 容器之间是互相隔离的,但却共享 OS 资源,比如 CPU、RAM 以及硬盘 I/O。 + + - CGroup 允许用户设置限制,这样单个容器就不能占用主机全部的 CPU、RAM 或者存储 I/O 资源了。 + + - Capability + + - Linux root 用户由许多能力组成 + + - CAP_CHOWN + + - 允许用户修改文件所有权。 + + - CAP_NET_BIND_SERVICE + + - 允许用户将 socket 绑定到系统端口号。 + + - CAP_SETUID + + - 允许用户提升进程优先级。 + + - CAP_SYS_BOOT + + - 允许用户重启系统。 + + - Docker 采用 Capability 机制来实现用户在以 root 身份运行容器的同时,还能移除非必须的 root 能力。 + + - MAC + + - Docker 采用主流 Linux MAC 技术,允许用户在启动容器的时候不设置相应策略,还允许用户根据需求自己配置合适的策略。 + + - Seccomp + + - Docker 使用过滤模式下的 Seccomp 来限制容器对宿主机内核发起的系统调用。 + + - Docker平台安全技术 + + - Swarm 模式 + + - 加密节点 ID + + - 基于 TLS 的认证机制 + + - 安全准入令牌 + + - 支持周期性证书自动更新的 CA 配置 + + - 加密集群存储(配置 DB) + + - 加密网络 + + - Swarm 安全原理 + + - Swarm 准入令牌 + + - 向某个现存的 Swarm 中加入管理者和工作者所需的唯一凭证就是准入令牌 + + - 管理者所需准入令牌 + + - 工作者所需准入令牌 + + - TLS 和双向认证 + + - 每个加入 Swarm 的管理者和工作者节点,都需要发布自己的客户端证书 + + - 证书用于双向认证,定义了节点相关信息,包括从属的 Swarm 集群以及该节点在集群中的身份(管理者还是工作者)。 + + - 配置一些 CA 信息 + + - Swarm 允许节点在证书过期前重新创建证书,这样可以保证 Swarm 中全部节点不会在同一时间尝试更新自己的证书信息。 + + - 集群存储 + + - 集群存储是 Swarm 的大脑,保存了集群配置和状态数据。 + + - Docker 安全扫描 + + - 一种深入检测 Docker 镜像是否存在已知安全缺陷的好方式。 + + - Docker 内容信任 + + - 帮助用户检查从 Docker 服务中拉取的镜像 + + - Docker 密钥 + +### 第16章 企业版工具 + +- 简介 + + - Docker EE 是企业版的 Docker。其内部包括了上百个引擎、操作界面以及私有安全注册。用户可以本地化部署,并且其中包括了一份支持协议。 + +- 详解 + + - Docker EE 引擎 + + - 提供 Docker 全部核心功能 + + - 镜像 + + - 容器管理 + + - 网络 + + - 卷 + + - 集群 + + - 安全 + + - Docker 统一控制平台(UCP) + + - 企业级的容器即服务平台的图形化操作界面,基于 Swarm 模式下的 Docker EE 构建的 + + - Docker 可信镜像仓库服务(DTR) + + - 私有的 Docker Hub,可以在本地部署,并且自行管理。 + +### 第17章 企业级特性 + +- 简介 + + - 企业版 Docker 是一个强化版本,包含 Docker 引擎、运维界面、安全镜像库以及一系列面向企业的特性。它可以被部署在私有云或公有云上,用户可自行管理,并会获取一份支持协议。 + +- 详解 + + - 基于角色的权限控制(RBAC) + + - 主体(Subject) + + - 一个或多个用户,或一个团队。 + + - 角色(Role) + + - 角色是一系列权限的组合 + + - 集合(Collection) + + - 权限作用的资源 + + - 集成活动目录 + + - 能够与活动目录及其他 LDAP 目录服务进行集成,从而利用组织中现有的单点登录系统中的用户和组 + + - Docker 内容信任机制(DCT) + + - Docker 镜像的发布者可以在将镜像推送到库中时对其进行签名。使用者可以在拉取镜像时进行校验,或进行构建或运行等操作。 + + - DCT 确保使用者能够得到他们想要的镜像 + + - 配置 Docker 可信镜像仓库服务(DTR) + + - 使用 Docker 可信镜像仓库服务 + + - Docker 可信镜像服务是一种安全的、自行配置和管理的私有镜像库 + + - 镜像提升 + + - 利用镜像提升功能可以构建一条基于一定策略的自动化流水线,它能够通过同一个 DTR 中的多个镜像库实现镜像提升。 + + - HTTP 路由网格(HRM) + + - 利用镜像提升功能可以构建一条基于一定策略的自动化流水线,它能够通过同一个 DTR 中的多个镜像库实现镜像提升。 + diff --git "a/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\346\265\205\345\207\272 Docker.xmind" "b/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\346\265\205\345\207\272 Docker.xmind" new file mode 100644 index 0000000..a99bd2f Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\346\265\205\345\207\272 Docker.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\347\220\206\350\247\243 Kafka\357\274\232\346\240\270\345\277\203\350\256\276\350\256\241\344\270\216\345\256\236\350\267\265\345\216\237\347\220\206.xmind" "b/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\347\220\206\350\247\243 Kafka\357\274\232\346\240\270\345\277\203\350\256\276\350\256\241\344\270\216\345\256\236\350\267\265\345\216\237\347\220\206.xmind" new file mode 100644 index 0000000..75e2944 Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/\346\267\261\345\205\245\347\220\206\350\247\243 Kafka\357\274\232\346\240\270\345\277\203\350\256\276\350\256\241\344\270\216\345\256\236\350\267\265\345\216\237\347\220\206.xmind" differ diff --git "a/\345\210\206\345\270\203\345\274\217/\347\263\273\347\273\237\346\236\266\346\236\204\347\233\270\345\205\263.md" "b/\345\210\206\345\270\203\345\274\217/\347\263\273\347\273\237\346\236\266\346\236\204\347\233\270\345\205\263.md" new file mode 100644 index 0000000..e380301 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/\347\263\273\347\273\237\346\236\266\346\236\204\347\233\270\345\205\263.md" @@ -0,0 +1,44 @@ +# 系统架构相关 + +## 系统双写解决方案 + +### 系统双写、多写的概念 + +- 一般来讲是指系统业务需要将业务数据保存到多个数据源当中,比如一条用户信息的数据需要同时存储到 MySQL 数据库,ElasticSearch 存储引擎,Redis 缓存,HDFS 等。 + +### 双写的缺点 + +- 一致性问题 + + - 并发情况下向多个数据源写入数据就会发生数据混乱 + +- 原子性问题 + + - 比如有两个数据源需要写入数据,那么就需要保证这两个数据源同时写入成功,或者同时失败,不能一个成功,一个失败的现象发生,类似于数据库的事务原子性概念。 + +### 双写的解决方案 + +- 按顺序记录数据的变更 + + - 消息队列 + + - 将数据按顺序记录,写入某个消息队列,然后其他系统按消息顺序恢复数据 + - 所有的数据变更写入一个消息队列里去。其他各数据源从消息队列里恢复数据即可 + - 缺点 + + - 一般不符合「写后即读」的系统要求,通常在实际的业务中,我们要求数据库的更新与查询应该都是最新最及时的。 + +- 其他存储源提取数据库的数据变更 + + - 客户端请求 +应用程序 +Database +中间件(监控数据的变化) +变化的数据写入消息队列 +Redis、ES、HDFS 等数据源消费队列中变更的数据 + - 将数据库作为应用的单一存储源,其余的存储源监控数据库的数据变化来对自己进行更新,从而实现双写的高可用性。 + +## 分支主题 2 + +## 分支主题 3 + diff --git "a/\345\210\206\345\270\203\345\274\217/\347\263\273\347\273\237\346\236\266\346\236\204\347\233\270\345\205\263.xmind" "b/\345\210\206\345\270\203\345\274\217/\347\263\273\347\273\237\346\236\266\346\236\204\347\233\270\345\205\263.xmind" new file mode 100644 index 0000000..922f3ed Binary files /dev/null and "b/\345\210\206\345\270\203\345\274\217/\347\263\273\347\273\237\346\236\266\346\236\204\347\233\270\345\205\263.xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/ SQL \345\217\215\346\250\241\345\274\217.md" "b/\346\225\260\346\215\256\345\272\223/ SQL \345\217\215\346\250\241\345\274\217.md" new file mode 100644 index 0000000..8f52505 --- /dev/null +++ "b/\346\225\260\346\215\256\345\272\223/ SQL \345\217\215\346\250\241\345\274\217.md" @@ -0,0 +1,478 @@ +# SQL 反模式 + +## 1、乱穿马路 + +### 通常使用逗号分隔的列表来避免在多对多的关系中创建交叉表,这种设计方式定义为一种反模式,称为乱穿马路(Jaywalking),乱穿马路也是避免过十字路口的一 种方式。 + +### 目标:存储多值属性 + +### 反模式:格式化的逗号分隔列表 + +### 解决方案:创建一张交叉表 + +- 当一张表有指向另外两张表的外键时,我们称这种表为一张交叉表(联合表、多对多表、映射表),它实现了两张表之间 的多对多关系。 +- 每个值都应该存储在各自的行与列中。 + +## 2、单纯的树 + +### 在层级数据中,你可能需要查询与整个集合或其子集相关的特定对象 + +- 组织架构图 +- 话题型讨论 + +### 目标:分层存储与查询 + +### 反模式:总是依赖父节点 + +- 邻接表 + + - 最常见的简单解决方案是添加 parent_id 字段 + - 使用邻接表查询树 + + - 无法完成树操作中最普通的一项:查询一个节点的所有后代。 + + - 使用邻接表维护树 + + - 从一棵树中删除一个节点会变得比较复杂 + +### 解决方案:使用其他树模型 + +- 路径枚举 + + - 路径枚举是一个由连续的直接层级关系组成的完整路径。如/usr/local/lib 的 UNIX 路径 是文件系统的一个路径枚举,其中 usr 是 local 的父亲,这也就意味着 usr 是 lib 的祖先。 + +- 嵌套集 + + - 存储子孙节点的相关信息,而不是节点的直接祖先。我们使用两个数字来编码每个节点,从而表示这一信息 + +- 闭包表 + + - 闭包表是解决分级存储的一个简单而优雅的解决方案,它记录了树中所有节点间的关系,而不仅仅只有那些直接的父子关系。 + - + +## 3、需要 ID + +### 目标:建立主键规范 + +### 反模式:以不变应万变 + +- 冗余键值 +- 允许重复项 +- 意义不明的关键字 +- 使用USING关键字 +- 使用组合键之难 + +### 解决方案:裁剪设计 + +- 直截了当地描述设计 + + - 为主键选择更有意义的名称:一个能够反应这个主键所代表的实体的类型的名字。 + +- 打破传统 +- 拥抱自然键和组合键 + + - 如果你的表中包含一列能确保唯一、非空以及能够用来定位一条记录,就别仅仅因为传统而觉得有必要再加上一个伪主键。 + - 在合适的时候也可以使用组合键,比如一条记录可以通过多列的组合完全定位 + +## 4、不用钥匙的入口 + +### 目标:简化数据库架构 + +### 反模式:无视约束 + +- 假设无瑕代码 +- 检查错误 +- 那不是我的错 +- 进退维谷 + +### 解决方案:声明约束 + +- 支持同步修改 + + - 外键有另一个在应用程序中无法模拟的特性:级联更新。 + +- 系统开销过度?不见得 + + - 外键约束需要多那么一点额外的系统开销,但相比于其他的一些选择,外键确实更高 效一点。 + +## 5、实体-属性-值 + +### 目标:支持可变的属性 + +### 反模式:使用泛型属性表 + +- 当需要支持可变属性时,第一反应便是创建另一张表,将属性当成行来存储。 + + - 实体:通常来说这就是一个指向父表的外键,父表的每条记录表示一个实体对象。 + - 属性:在传统的表中,属性即每一列的名字,但在这个新的设计中,我们需要根据不同的记录来解析其标识的对象属性。 + - 值:对于每个实体的每一个不同属性,都有一个对应的值。 + +- 查询属性 +- 支持数据完整性 +- 无法声明强制属性 +- 无法使用 SQL 的数据类型 +- 无法确保引用完整性 +- 无法配置属性名 +- 重组列 + +### 解决方案:模型化子类型 + +- 单表继承 +- 实体表继承 +- 类表继承 +- 半结构化数据模型 +- 后处理 + +## 6、多态关联 + +### 目标:引用多个父表 + +### 反模式:使用双用途外键 + +- 定义多态关联 +- 使用多态关联进行查询 +- 非面向对象范例 + +### **解决方案:让关系变得简单** + +- 反向引用 +- 创建交叉表 +- 设立交通灯 +- 双向查找 +- 合并跑道 +- 创建共用的超级表 + +## 7、多列属性 + +### 目标:存储多值属性 + +### 反模式:创建多个列 + +- 查询数据 +- 添加及删除值 +- 确保唯一性 +- 处理不断增长的值集 + +### 解决方案:创建从属表 + +## 8、元数据分裂 + +### 目标:支持可扩展性 + +### 反模式:克隆表与克隆列 + +- 不断产生的新表 +- 管理数据完整性 +- 同步数据 +- 确保唯一性 +- 跨表查询 +- 同步元数据 +- 管理引用完整性 +- 标识元数据分裂列 + +### 解决方案:分区及标准化 + +- 使用水平分区 +- 使用垂直分区 +- 解决元数据分裂列 + +## 9、取整错误 + +### 目标:使用小数取代整数 + +### 反模式:使用 FLOAT 类型 + +- 舍入的必要性 +- 在SQL中使用FLOAT + +### 解决方案:使用 NUMERIC 类型 + +## 10、每日新花样 + +### 目标:限定列的有效值 + +### 反模式:在列定义上指定可选值 + +- 中间的是哪个 +- 添加新口味 +- 老的口味永不消失 +- 可移植性低下 + +### 解决方案:在数据中指定值 + +- 查询候选值集合 +- 更新检查表中的值 +- 支持废弃数据 +- 良好的可移植性 + +## 11、幽灵文件 + +### 目标:存储图片或其他多媒体大文件 + +### 反模式:假设你必须使用文件系统 + +- 文件不支持DELETE +- 文件不支持事务隔离 +- 文件不支持回滚操作 +- 文件不支持数据库备份工具 +- 文件不支持SQL的访问权限设置 +- 文件不是SQL数据类型 + +### 解决方案:在需要时使用 BLOB 类型 + +## 12、乱用索引 + +### 目标:优化性能 + +### 反模式:无规划地使用索引 + +- 无索引 +- 索引过多 +- 索引也无能为力 + +### 解决方案:MENTOR 你的索引 + +- 测量(Measure) + + - 记录执行 SQL 查询的时耗,因此可以以此来定位最耗时的查询。 + +- 解释(Explain) + + - 找出它之所以会这么慢的原因。 + +- 挑选(Nominate) + + - 查找那些没有使用索引的查询操作。 + +- 测试(Test) + + - 创建完索引之后,需要重新跟踪那些查询。需要确认你的改动确实提升了性能,然后就能确定工作完成了。 + +- 优化(Optimize) + + - 索引是小型的、频繁使用的数据结构,因而很适合将它们常驻在内存中。内存操作的性能是磁盘 I/O 操作的好几倍。 + +- 重建(Rebuild) + + - 索引在平衡的时候其效率最高,当你更新或者删除记录时,索引就逐渐变得不平衡,就如 同文件系统随着时间的推移会产生很多磁盘碎片一样。 + - 想要最大限度地使用索引,因此要定期对索引进 行维护。 + +## 13、对未知的恐惧 + +### 目标:辨别悬空值 + +### 反模式:将 NULL 作为普通的值,反之亦然 + +- 在表达式中使用NULL +- 搜索允许为空的列 +- 在查询参数中使用NULL +- 避免上述问题 +- 使用 NULL 并不是反模式,反模式是将 NULL 作为一个普通值处理或者使用一个普通的值来取代 NULL 的作用。 + +### 解决方案:将 NULL 视为特殊值 + +- 在标量表达式中使用NULL +- 在布尔表达式中使用NULL +- 检索NULL值 +- 声明NOT NULL的列 +- 动态默认值 + +## 14、模棱两可的分组 + +### 目标:获取每组的最大值 + +### 反模式:引用非分组列 + +- 单值规则 + + - 跟在 SELECT 之后的选择列表中的每一列,对于每个分组来说都必须返回且仅返回一个值。 + +- 我想要的查询 + +### 解决方案:无歧义地使用列 + +- 只查询功能依赖的列 +- 使用关联子查询 +- 使用衍生表 +- 使用JOIN +- 对额外的列使用聚合函数 +- 连接同组所有值 + +## 15、随机选择 + +### 目标:获取样本记录 + +### 反模式:随机排序 + +### 解决方案:没有具体的顺序 + +- 从 1 到最大值之间随机选择 +- 选择下一个最大值 +- 获取所有的键值,随机选择一个 +- 使用偏移量选择随机行 +- 专有解决方案 + +## 16、可怜人的搜索引擎 + +### 目标:全文搜索 + +### 反模式:模式匹配断言 + +- 使用模式匹配操作符的最大缺点就在于性能问题。它们无法从传统的索引上受益,因此必须进行全表遍历 + +## 17、意大利面条式查询 + +### 目标: 减少 SQL 查询数量 + +### 反模式: 使用一步操作解决复杂问题 + +### 解决方案:分而治之 + +- 寻找 UNION 标记 + +## 18、隐式的列 + +### 目标: 减少输入 + +### 反模式: 捷径会让你迷失方向 + +- 破坏代码重构 +- 隐藏的开销 + + - 一次查询所获取的列越多,客户端程序和数据库之间的网络传输的字节数也越多。 + +### 解决方案: 明确列出列名 + +## 19、明文密码 + +### 目标:恢复或重置密码 + +### 反模式:使用明文存储密码 + +- 存储密码 +- 验证密码 +- 在 E-mail 中发送密码 + +### 解决方案:先哈希,后存储 + +- 理解哈希函数 + + - 哈希是指将输入字符串转化成另一个新的、不可识 别的字符串的函数。 + - 哈希的另一个特征就是不可逆。 + + - 哈希函数的算法设计就是要「丢失」一些输入串的信息, 所以你无法从一个哈希串恢复出原始输入串。 + + - MD5 是另一个流行的哈希函数,产生 128 位的哈希串。 + +- 在 SQL 中使用哈希 +- 给哈希加料 + + - 将用户密码传入哈希函数进行加密之前,将其和一个无意义的串拼接在一起,即使用户选择了一个在字 典中存在的单词作为密码,对加料密码进行哈希得到的串是不太会出现在攻击者的哈希数据库中的,你可以发现增加了随机串得到的哈希值和原始值是不一样的 + +- 在 SQL 中隐藏密码 +- 重置密码,而非恢复密码 + +## 20、SQL 注入 + +### 目标:编写 SQL 动态查询 + +### 反模式:将未经验证的输入作为代码执行 + +- 意外无处不在 +- 对 Web 安全的严重威胁 +- 寻找治愈良方 + + - 转义 + - 查询参数 + - 存储过程 + - 数据访问框架 + +### 解决方案:不信任任何人 + +- 过滤输入内容 +- 参数化动态内容 +- 给动态输入的值加引号 +- 将用户与代码隔离 +- 找个可靠的人来帮你审查代码 + +## 21、伪键洁癖 + +### 目标:整理数据 + +### 反模式:填充角落 + +- 不按照顺序分配编号 +- 为现有行重新编号 +- 制造数据差异 + +### 解决方案:克服心里障碍 + +- 定义行号 +- 使用 GUID + +## 22、非礼勿视 + +### 目标:写更少的代码 + +### 反模式:无米之炊 + +- 没有诊断的诊断 +- 字里行间 + +### 解决方案:优雅地从错误中恢复 + +- 保持节奏 +- 回溯你的脚步 + +## 23、外交豁免权 + +### 目标:采用最佳实践 + +### 解决方案:建立一个质量至上的文化 + +- 陈列A:编写文档 +- 寻找证据:源代码版本控制 +- 举证:测试 +- 例证:同时处理多个分支 + +## 24、魔豆 + +### 目标:简化 MVC 的模型 + +## 25、规范化规则 + +### 关系是什么 + +- 行之间没有上下顺序 +- 列之间没有左右顺序 +- 重复行是不允许的 +- 每一列只有一种类型,每一行只有一个值 +- 行没有隐藏组件 + +### 规范化的神话 + +- 规范化的目标 + + - 以一种我们能够理解的方式表达这个世界中的事物; + - 减少数据的冗余存储,防止异常或者不一致的数据; + - 支持完整性约束。 + +- 第一范式 + + - 第一范式的最根本要求是,该表必须是一个关系。 + +- 第二范式 +- 第三范式 +- 博伊斯—科德范式 + + - 在第三范式中,所有的非关键字列都必须直接依赖于这张表中的关键字列,而在博伊斯—科 德范式中,所有关键字列也必须遵循这一规则,这一点在一张表有多种列的集合可作为表的关键 字时才有效。 + +- 第四范式 +- 第五范式 + + - 任何满足博伊斯—科德范式并且没有复合主键的表将同时满足第五范式。 + +- 更多的范式 + + - DK范式(Domain-Keynormalform)认为表上的每个约束都是这张表的数据域约束和关键字约束的逻辑结果。DK 范式涵盖了第三、四、五范式和博伊斯—科德范式。 + diff --git "a/\346\225\260\346\215\256\345\272\223/ SQL \345\217\215\346\250\241\345\274\217.xmind" "b/\346\225\260\346\215\256\345\272\223/ SQL \345\217\215\346\250\241\345\274\217.xmind" new file mode 100644 index 0000000..262b232 Binary files /dev/null and "b/\346\225\260\346\215\256\345\272\223/ SQL \345\217\215\346\250\241\345\274\217.xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/ElasticSearch.xmind" "b/\346\225\260\346\215\256\345\272\223/ElasticSearch.xmind" new file mode 100644 index 0000000..7e9f117 Binary files /dev/null and "b/\346\225\260\346\215\256\345\272\223/ElasticSearch.xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.md" "b/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.md" new file mode 100644 index 0000000..8cce36a --- /dev/null +++ "b/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.md" @@ -0,0 +1,222 @@ +# MySQL 中的锁(详细版) + +## MySQL 锁按照特性和设计分类 + +### 表级锁 + +- MyISAM 和 MEMORY 存储引擎采用,BDB 和 InnoDB 也支持表级锁。 +- 开销小,加锁快 +- 不会出现死锁(因为 MyISAM 总是一次性获得 SQL 语句需要的全部锁,所以不会出现死锁) +- 锁定粒度大(锁整张表),发生锁冲突概率高,并发度最低,基本都是串行化了。 +- 适合以查询为主,辅以少量按索引条件更新数据的应用 + +### 行级锁 + +- InnoDB 默认采用 +- 开销大,加锁慢 +- 会出现死锁 +- 锁定粒度小,发生锁冲突的概率最低,并发度自然就最高。 +- 适合大量按索引条件并发更新少量数据不同数据,同时又有并发查询的应用 + +### 页面锁 + +- BDB 存储引擎采用,不过 BDB 已经被 InnoDB 取代,即将成为历史。 +- 开销和加锁时间介于上面二者之间 +- 会出现死锁 +- 粒度和并发度也介于上述二者之间 + +## MyISAM 表级锁 + +### 表级锁的两种锁模式 + +- 表共享读锁 + + - 不影响其他线程的读,但是不能写 + +- 表独占写锁 + + - 其他线程的读锁、写锁都被阻塞 + +### 加表级锁的特性以及注意事项 + +- 一般不需要直接用「lLOCK TABLE」命令给 MyISAM 表显式加锁 +- MyISAM 不支持锁升级,即当执行「LOCK TABLES」命令的时候只能访问显式加锁的这些表,不能访问没加锁的表 +- MyISAM 锁表的时候需要别名加锁,否则没效 + +### MyISAM 的并发插入 + +- 虽然 MyISAM 读写串行,不过也支持查询和插入并发进行 +- 系统变量:concurrent_insert + + - 当 concurrent_insert = 0,不允许并发插入 + - 当 concurrent_insert = 1,MySQL默认值设置,如果 MyISAM 表的中间被删除的行,允许在一个进程读表的同时,另一个进程从表尾插入记录 + - 当 concurrent_insert = 2,无限制插入 + +### MyISAM 的锁调度 + +- MySQL 认为写请求一般比读请求重要。MyISAM 默认写进程先获得锁。 +- 调节 MyISAM 读进程优先获得锁 + + - 通过指定启动参数「low-priority-updates」 + - 通过执行「SET LOW_PRIORITY_UPDATES=1」命令 + - 通过指定 INSERT、UPDATE、DELETE 语句的 LOW_PRIORITY 属性,降低写进程的优先级 + +## InnoDB 行级锁 + +### 事务及其 ACID 属性 + +- 原子性 + + - 事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全都不执行 + +- 一致性 + + - 事务的开始和完成,所有相关的数据规则都必须应用于事务的修改,所有的内部数据结构(B树索引、双向链表等)都是正确的。 + +- 隔离性 + + - 事务处理过程中的中间状态对外部是不可见的,保证事务不受外部并发操作的影响。 + +- 持久性 + + - 事务完成之后,对于数据的修改是永久性的、持久化的。 + +### 并发事务的问题 + +- 更新丢失 + + 当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题—最后的更新覆盖了由其他事务所做的更新。 + + - 防止更新丢失应该是应用的责任 + +- 脏读 + + 一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系 + +- 不可重复读 + + 一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变或某些记录已经被删除了 + +- 幻读 + + 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据 + +### 事务的隔离级别 + +- 实现事务隔离的方式 + + - 读取数据前对其加锁 + - MVCC(数据多版本并发控制) + + - 不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照,并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。 + - 基于 undo log 使用 innodb 的两个隐藏字段 trx_id 和 roll_ptr 建立的一个数据请求时间点的一致性数据快照,只能查找创建时间小于当前事务 id 以及删除时间大鱼当前事务 id 的行 + - 在 RC 已提交读和 RR 可重复读两种隔离级别,对于 MVCC 来讲,RC 是每次查询时获取当前活跃事务的快照,后者 RR 是开启事务时就获取活跃事务快照 + +- 未提交读 + + - 脏读、不可重复读、幻读均不能避免 + +- 已提交读 + + - 可以避免脏读,不可重复度、幻读不能避免 + +- 可重复读 + + - 可以避免脏读和不可重复度,幻读不能避免 + +- 可序列化、串行化 + + - 相当于串行化操作,均可避免 + +### InnoDB 的行锁模式及加锁方式 + +- 行锁模式 + + - 共享锁(S) + + - 允许一个事务去读一行,其他事务也可以读,但是不能写,即其他事务不能获得排他锁。 + + - 排他锁(X) + + - 允许一个事务进行更新操作,其他事务既不能读取也不能更新数据,即其他事务不能获得共享读锁也不能获取排他写锁 + + - 意向共享锁(IS) + - 意向排他锁(IX) + +- 加锁方式 + + - 共享锁 + + - SELECT * FROM table_name WHERE ... LOCK IN SHAREMODE; + - 主要用于数据依存关系时来确认某行记录是否存在。不建议进行更新或者删除操作,因为其他事务也可以获得共享锁,容易造成死锁。 + + - 排他锁 + + - SELECT * FROM table_name WHERE ... FOR UPDATE; + +### InnoDB 行锁的实现方式 + +- 通过给索引上的索引项加锁来实现,如果没有索引,会对隐藏的聚簇索引加锁。如果不通过索引条件检索数据,那么 InnoDB 会获取表锁。 +- 索引项加锁(Record lock) +- 间隙锁(Gap lock) + + - 索引项之间的「间隙」 + - 记录之前的「间隙」 + - 记录之后的「间隙」 + +- 对记录及其前面的间隙加锁(Next-key lock) + + - 使用场景 + + - 范围查询请求共享或者排他锁 + - 使用相等条件请求给不存在的记录加锁 + + - 目的 + + - 防止幻读 + - 满足恢复和复制的需要,即满足从 BINLOG 恢复数据 + +- InnoDB 行锁实际使用注意事项 + + - 在不通过索引条件查询时,InnoDB会锁定表中的所有记录 + - 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。 + - 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁 + - 即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB也会对所有记录加锁 + +### InnoDB 主动使用表锁的情景 + +- 事务需要更新大部分或全部数据,表又比较大 +- 事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚 +- 要将AUTOCOMMIT设为0,否则MySQL不会给表加锁 + +### InnoDB 中的死锁 + +- 发生死锁后,InnoDB 一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。(干预的外部锁和表锁的情况除外) +- 避免死锁的常用的方法 + + - 表访问约定顺序 + + - 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会 + + - 线程排序 + + - 在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能 + + - 处理数据就申请排他锁 + + - 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁 + + - 使用已提交读解决可重复读的死锁问题 + + - 在可重复读的隔离级别下,如果两个线程同时对相同条件记录用 SELECT...FOR UPDATE 加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。 + +程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。 + +这种情况下,将隔离级别改成已提交读的隔离级别 + + - 通过主键主键不可重复的特点解决已提交读的死锁问题 + + 当隔离级别为已提交读时,如果两个线程都先执行 SELECT...FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第3个线程又来申请排他锁,也会出现死锁。 + + 对于这种情况,可以直接做插入操作,然后再捕获主键重异常,或者在遇到主键重错误时,总是执行 ROLLBACK 释放获得的排他锁。 + diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" "b/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" index 1a4bfce..0c46331 100644 Binary files "a/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" and "b/\346\225\260\346\215\256\345\272\223/MySQL \344\270\255\347\232\204\351\224\201\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.md" "b/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.md" new file mode 100644 index 0000000..df0997f --- /dev/null +++ "b/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.md" @@ -0,0 +1,936 @@ +# MySQL 技能图谱概括版 + +## 一、数据库基础 + +### 概念 + +简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增、截取、更新、删除等操作。所谓「数据库」系以一定方式储存在一起、能予多个用户共享、具有尽可能小的冗余度、与应用程序彼此独立的数据集合。一个数据库由多个表空间(Tablespace)构成。 +### 模型 + +- 分类 + + - 概念数据模型 + + - 面向用户的,按照用户的观点进行建模,典型代表:E-R 图 + + - 结构数据模型 + + - 面向计算机系统的,用于 DBMS 的实现,典型代表有:层次模型,网状模型、关系模型、面向对象模型 + +- 三要素 + + - 数据结构 + - 数据操作 + - 数据约束 + +- 概念数据模型 + + - E-R 图(实体-联系图,Entity Relationship Diagram) + + - 实体、联系、属性 + - 一对一(1:1)、一对多(1:N)、多对多(M:N) + - 既表示实体,也表示实体之间的联系,是现实世界的抽象,与计算机系统没有关系, 是可以被用户理解的数据描述方式。用户与系统设计者进行交流的工具 + - 实体用矩形框表示,联系用菱形表示,属性用椭圆表示 + + - https://pic.imgdb.cn/item/624d0704239250f7c50862d1.png +E-R 图示例 + +- 结构数据模型 + + - 层次模型 + + - 树形结构表示数据与数据之间的关系 + - 不能直接表示多对多的联系 + + - 网状模型 + + - 用网络结构表示数据与数据之间的联系的模型 + - 子节点和父节点联系不唯一,需要为联系命名 + - 更直观的描述世界,良好的性能,缺点是结构复杂 + + - 关系模型 + + - 采用表格结构表达实体集以及实体之间的联系,最大的特色就是描述的一致性。 + - 关系是一张表,关系数据模型由若干个表组成。 + - 可以存在1对1,1对多,多对多的关系 + + - 面向对象模型 + + - 指属性和操作属性的方法封装在称为对象类的结构中的模型。 + - 成分、概念 + + - 对象 + - 类(对象类) + - 类层次 + - 对象标识 + - 对象包含 + +### 常见数据库 + +- Oracle、DB2、SQL Server、PostgreSQL、MySQL、Sybase、Access、Informix、FoxPro、Redis、MongoDB + +### 事务的基本概念 + +- 定义 + + - 作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。 + +- ACID 原则 + + - 原子性(Atomicity) + + - 事务中的所有元素作为一个整体提交或回滚,事务的个体元素是不可分的,事务是一个完整操作。 + + - 一致性(Consistemcy) + + - 事务完成时,数据必须是一致的,也就是说,和事务开始之前,数据存储中的数据处于一致状态。保证数据的无损。 + + - 隔离性(Isolation) + + - 对数据进行修改的多个事务是彼此隔离的。这表明事务必须是独立的,不应该以任何方式依赖于或影响其他事务。 + + - 持久性(Durability) + + - 事务完成之后,它对于系统的影响是永久的,该修改即使出现系统故障也将一直保留,真实的修改了数据库。 + +### 数据库逻辑模式 + +- 基本概念定义 + + - 数据库中全体数据的逻辑结构和特征的描述,是所有用户的公共数据视图。 + + - 一个数据库只有一个模式 + - 是数据库数据在逻辑级上的视图 + - 数据库模式以某一种数据模型为基础 + - 定义模式时不仅要定义数据的逻辑结构,而且要定义与数据有关的安全性、完整性要求,定义这些数据之间的联系 + +- 三级模式 + + - 内模式 + + - 也称存储模式,是数据物理结构和存储方式的描述,是数据在数据库内部的表示方式 + + - 一个数据库只有一个内模式 + - 一个表可能由多个文件组成 + + - 减少数据冗余,实现数据共享 + - 提高存取效率,改善性能。 + + - 外模式 + + - 也称子模式。或用户模式,是数据库用户能够看见和使用的局部数据的逻辑结构和特征的描述,是数据库用户的数据视图,是与某一应用有关的数据的逻辑表示。 + + - 一个数据库可以有多个外模式 + - 外模式就是用户视图 + - 外模式是保证数据安全性的一个有力措施。 + +- 两级映射 + + - 外模式-模式 + - 模式-内模式 + +## 二、数据库设计基础 + +### 设计规范 + +- 三范式 + + - 第一范式(1NF) + + - 字段具有「原子性」 , 不可再分 。所有关系型数据库系统都满足第一范式。数据库表中的字段都是单一属性的,不可再分。例如,姓名字段,其中的姓和名必须作为一个整体,无法区分哪部分是姓,哪部分是名,如果要区分出姓和名,必须设计成两个独立的字段。 + + - 第二范式(2NF) + + - 在第一范式的基础上建立起来的,满足第二范式必须先满足第一范式。要求数据库表中的每个实例或行必须可以被惟一地区分 。通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键 。 + + - 第三范式(3NF) + + - 必须先满足第二范式 。要求一个数据库表中不包含已在其它表中已包含的非主关键字信息 + - 特征 + + - 每一列只有一个值 + - 每一行都能区分 + - 每一个表都不包含其他表已经包含的非主关键字信息 + +### 设计方法 + +- 设计目标 + + - 满足应用功能 + - 良好的数据库性能 + +- 设计过程 + + - 需求分析 + - 结构设计 + + - 概念结构设计 + - 逻辑结构设计 + - 物理结构设计 + + - 行为设计 + + - 功能设计 + - 事务设计 + - 程序设计 + + - 数据库实施 + + - 加载数据库数据 + - 调试运行应用程序 + + - 数据库运行和维护阶段 + +### 设计评审 + +## 三、MySQL 基础知识 + +### 安装与配置 + +- Windows + + - 傻瓜式安装 + +- Linux + + - 安装 5.6 + + - https://www.glorze.com/322.html + + - 安装 5.7 + + - https://www.glorze.com/1412.html + +- Mac + + - 傻瓜式安装 + +### 启动与关闭 + +- 主要体现在 Linux 中的一些命令,不同版本也有不同的实现,主要有 syetemctl start/stop mysql、service start/stop mysql 等 + +### 常用工具 + +- Navicat + + - https://www.glorze.com/320.html + +- SqlYog +- DataGrip +- DBeaver + +### 基本结构(SQL 的执行过程) + +- 客户端 + + - https://pic.imgdb.cn/item/5d89976a451253d178477d7b.png +MySQL 的 SQL 执行过程 + +- 连接器 + + - 管理连接,权限验证 + +- 查询缓存 + + - 命中则直接返回结果 + +- 分析器 + + - 词法分析,语法分析 + +- 优化器 + + - 执行计划生成,索引选择 + +- 执行器 + + - 操作引擎,返回结果 + +- 存储引擎 + + - 存储数据,提供读写接口 + +### 常用存储引擎 + +- MyISAM存储引擎(Full-text 索引,解决 like 查询低下) +- Innodb存储引擎(事务、外键) +- NDBCluster 存储引擎(分布式集群) +- Merge 存储引擎(为 MyISAM 提供统一接口) +- Memory 存储引擎(数据存储在内存中,不支持BLOB 和 TEXT) +- BDB 存储引擎(和 InnoDB 基本一样) +- FEDERATED 存储引擎(远程服务) +- BLACKHOLE 存储引擎(有去无回) +- CSV存储引擎(报表文件) + +## 四、MySQL开发基础 + +### SQL 语句 + +- 数据操纵语言 + + - DDL(Data Definition Language),数据定义语言 + - DCL(Data Control Language),数据控制语言 + - DML(data manipulation language),数据操纵语言。 + + - INSERT 插入、UPDATE 更新、DELETE 删除 + + - DQL(Data Query Language),数据查询语言-select 关键字 + +- 同构/异构 + + - 同构 + + - 两个 SQL 语句可编译的部分是相同的,只是参数不一样而已 + - 在 JDBC 中,PreparedStatement 执行同构 SQL 语句的效率是比较高的,因为 PreparedStatement 对象一旦绑定了 SQL 语句,就只能执行这一条 SQL 语句 + + - 异构 + + - 两个 SQL 语句整个的格式都是不同的。 + - Statement 则执行异构的 SQL 语句效率更高 + +- 各种关键字含义 + + - EXIST、NOT EXIST + + - 带有 EXISTS 的子查询不返回任何数据,只产生逻辑真值「true」或者逻辑假值「false」。 + - 使用 NOT EXISTS 后,若对应查询结果为空,则外层的 WHERE 子语句返回值为真值,否则返回假值。 + +- SQL 语句函数 + +### CRUD + +- 增加 +- 查询 +- 更新 +- 删除 + + - DELETE + + - delete 是 DML(数据操纵语言),执行 delete 操作时,每次从表中删除一行,并且同时将该行的的删除操作记录在 redo 和 undo 表空间中以便进行回滚(rollback)和重做操作 + + - 表空间要足够大,需要手动提交(commit)操作才能生效,可以通过 rollback 撤消操作。 + + - delete 可根据条件删除表中满足条件的数据,如果不指定 where 子句,那么删除表中所有记录。 + - delete 语句不影响表所占用的 extent,高水线(high watermark)保持原位置不变。 + + - DROP + + - drop 是 DDL(数据定义语言),会隐式提交,所以,不能回滚,不会触发触发器。 + - drop 语句删除表结构及所有数据,并将表所占用的空间全部释放。 + - drop 语句将删除表的结构所依赖的约束,触发器,索引,依赖于该表的存储过程/函数将保留,但是变为 invalid 状态。 + + - TRUNCATE + + - truncate 是 DDL(数据定义语言),会隐式提交,所以,不能回滚,不会触发触发器。 + - truncate 会删除表中所有记录,并且将重新设置高水线和所有的索引,缺省情况下将空间释放到 minextents 个 extent,除非使用 reuse storage。 + - truncate 不会记录日志,所以执行速度很快,所以不能通过 rollback 撤消操作 + + - 如果一不小心把一个表 truncate 掉,也是可以恢复的,只是不能通过 rollback 来恢复 + + - 对于外键(foreignkey )约束引用的表,不能使用 truncate table,而应使用不带 where 子句的 delete 语句。 + - truncate table 不能用于参与了索引视图的表。 + - 在drop table的时候,innodb维护了一个全局锁,drop完毕锁就释放了。 + + - 大表删除方案 + + - Linux 硬链接 + +### 视图 + +### 存储过程 + +### 游标 + +### 触发器 + +### 事务 + +## 七、MySQL 高级开发 + +### 事务 + +- 原子性(Atomic) + + 事务中包含的操作被看作一个整体的业务单元,这个业务单元中的操作要么全部成功,要么全部失败,不会出现部分失败、部分成功的场景。 +- 一致性(Consistency) + + 事务在完成时,必须使所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性。 +- 隔离性(Isolation) + + - 隔离级别(当前读) + + - 未提交读(Read Uncommited):脏读 + + - 可以读取未提交记录。此隔离级别,不会使用,忽略。 + + - 读写提交(Read Committed,RC):幻读 + + - 一个事务只能读取另外一个事务己经提交的数据, +不能读取未提交的数据。 + - 幻读的意思其实就是读写提交会产生不可重复读的问题 + + - 可重复读(Repeatable Read,RR) + + - 字面的意思就是必须等上一个事务提交才能进行当前事务的读取操作,保证数据正确性。 + - RR 隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。 + + + - 在这里里面也有一个「幻读」的概念,不过可重复读产生幻读的现象不属于数据库存储的值,多半是统计值或者计算值。 + + - 串行化(Serializable) + + - 从 MVCC 并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁,共享锁),写加写锁 (X锁,排它锁)。 + - Serializable 隔离级别下,读写冲突,因此并发度急剧下降,在 MySQL/InnoDB 下不建议使用。 + +- 持久性(Durability) + + 事务结束后,所有的数据会固化到一个地方,如保存到磁盘当中,即使断电重启后也可以提供给应用程序访问。 +### 锁 + +- 锁 + + - 共享锁 + - 排它锁 + - 乐观锁 + - 悲观锁 + - GAP 锁 + - 2PL(二阶段锁): + + - 加锁阶段 + - 解锁阶段 + - 保证加锁阶段与解锁阶段不相交。 + + - 死锁 + + 死锁的发生与否,并不在于事务中有多少条 SQL 语句,死锁的关键在于:两个或以上的 Session 加锁的顺序不一致。 +### 索引 + +- 索引(B+树) + + 索引是一种数据结构,用于加快mysql获取数据的速度; + + 常见索引模型:哈希表、有序数组、搜索树。 + + 索引类型:主键索引(存储整行数据)、非主键索引(主键的值,需要回表两次查询) + - 数据结构角度 + + - B+ 树索引(查询性能高、IO 次数少、范围查询简便) + + - 每一个父节点的元素都出现在子节点中,是子节点的最大或最小元素。 + - 非叶子节点不保存数据,只保存关键字用作索引,所有数据都保存在叶子节点中。 + - 内部节点不保存数据,所以能在内存中存放更多索引,增加缓存命中率。 + + - 哈希索引(单行查询快) + + 哈希索引基于哈希表实现,只有精确匹配索引的所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 + + 作者:counterxing + 链接:https://www.zhihu.com/question/67094336/answer/250034118 + 来源:知乎 + 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 + - 无法用于排序 + - 仅支持 < = > 以及 IN 操作 + - 哈希冲突比较多的话,维护代价高 + - 不能避免全表扫描 + - 不支持部分索引列匹配查找 + + - FULLTEXT 全文索引(MyISAM 和 InnoDB) + + - 更建议使用 Lucene、Solar 等 + + - R-Tree索引 + + - 解决空间数据检索的问题。 + + - 物理存储角度 + + - 聚簇索引(Cluster Index) + + - InnoDB 存储引擎的数据组织方式就是聚簇索引表,完整的记录,存储在主键索引中,通过主键索引,就可以获取记录所有的列。 + + - 非聚簇索引 + + - MyISAM 就是非聚簇索引 + - 叶子节点仍然是索引节点,只不过有指向对应数据块的指针。 + + - 逻辑角度 + + - 主键索引,特殊的唯一索引,不允许为空,整行索引 + - 普通索引、单列索引 + - 多列索引、复合索引(遵循最左前缀原则) + - 唯一索引或者非唯一索引 + - 空间索引(只存在 MYISAM 存储引擎中) + + - 设计索引的原则 + + - 搜索的索引列,出现在 where 子句中的列 + - 使用唯一索引 + - 使用短索引,对字符串列进行索引,去字符串的前 N 个字符 + - 利用最左前缀,可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。 + - 不要过度索引,占磁盘空间,降低写操作性能。 + - InnoDB 尽量自己指定主键 + + - 面试常问的「主键索引、唯一索引和普通索引的区别」 + + - 主键索引 + + - **主键索引只要搜索ID这个B+Tree即可拿到数据。** + - **不允许空值** + + - 唯一索引 + + - **必须唯一,允许空值** + + - 普通索引 + + - **普通索引先搜索索引拿到主键值,再到主键索引树搜索一次(回表)** + + 回到主键索引树搜索的过程,称为回表 +### SQL 模式 + +- SELECT @@GLOBAL.sql_mode; +- ANSI +- DB2 +- MSSQL +- POSTGRESQL +- ORACLE +- MAXDB +- TRADITIONAL + +### 分区表 + +- 分区类型 + + - 水平分区 + + - 将同一表中不同行的记录分配到不同的物理文件中; + + - 垂直分区 + + - 将同一表中不同列的记录分配到不同的物理文件中; + +- 分区表类型 + + - 范围分区(RANGE) + - 列表分区(LIST) + - 哈希分区(HASH) + - KEY 分区 + +- 分区管理 + + - 增删改查 + +## 六、MySQL 运维 + +### 日志 + +### 监控 + +### 备份与恢复 + +### 复制 + +## 五、MySQL 优化 + +### 优化思路 + +- 自顶向下 + + - 第一级别 + + - 应用系统的 SQL 优化 + + - 慢查询日志定位执行效率低的 SQL 语句 + - 用 explain 分析 SQL 的执行计划 + + - 第二级别 + + - MySQL 的服务器优化 + + - 第三级别 + + - 操作系统\硬件优化 + + - 第四级别 + + - 系统架构全局优化 + + - 负载均衡 + - 缓存 + - 分布式 + - 读写分离 + - 主从 + + - master 写入数据时会留下写入日志,slave 根据 master 留下的日志模仿其数据执行过程进行数据写入。 + - 主从不一致的诱因 + + - mater 日志写入不成功导致 slave 不能正常模仿。 + - slave 根据 master 日志模仿时写入不成功。 + + - 保证主从一致的方案 + + - Master 角度 + + - InnoDB Redo Log 记录了对数据文件的物理更改,并保证总是日志先行,在持久化数据文件前,保证之前的 redo 日志已经写到磁盘。 + - Binlog和InnoDB Redo Log是否落盘将直接影响实例在异常宕机后数据能恢复到什么程度。 + - InnoDB 提供了相应的参数来控制事务提交时,写日志的方式和策略 + + - innodb_support_xa=ON + + - 事务提交流程会变成两阶段提交,MySQL 内部 xa 事务 + + - innodb_doublewrite=ON + + - 证不论是 MySQL Crash 还是 OS Crash 或者是主机断电重启都不会丢失数据 + + - innodb_flush_log_at_trx_commit = 1 +sync_binlog = 1 + + - 保证每次事务提交后,都能实时刷新到磁盘中,尤其是确保每次事务对应的 binlog 都能及时刷新到磁盘中,只要有了 binlog,InnoDB 就有办法做数据恢复,不至于导致主从复制的数据丢失 + + - slave 角度 + + - 异步复制(不推荐) + + - 主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理 + + - 半同步复制 + + - 主库在应答客户端提交的事务前需要保证至少一个从库接收并写到 relay log 中 + - 存在数据丢失的问题 + + - 全同步复制 + + - 在调用 binlog sync 之后,engine 层 commit 之前等待 Slave ACK。这样只有在确认 Slave 收到事务 events 后,事务才会提交。 + + - 垂直拆分 + - 水平拆分 + +- 软硬结合 + + - SQL 语句以及索引优化 + - 表结构\表设计优化 + - 系统配置 + - 服务器硬件配置 + +### 慢 SQL 优化方案 + +- 慢 SQL 的因素 + + - 索引设计问题 + - SQL 编写问题 + - 表结构(类型、长度等)设计问题 + - 锁 + - 并发对 IO/CPU 资源争用 + - 服务器硬件 + - MySQL 本身的 Bug + +- 解决之道 + + - 优化分析流程 + + - 了解 SQL 的执行效率 + + - show status like 'Com_%'; -- 了解 SQL 的执行频率 + - show status like 'Innodb_rows_%'; + + - 定位慢查询 + + - MyBatis 插件实现 SQL 监控 + - 通过慢查询日志定位那些执行效率较低的 SQL 语句 + + - 打开慢查询 + + - set global slow_query_log=on; + + - 查询 long_query_time 的值 + + - show variables like 'long_query_time'; -- 慢查询阈值:默认为 10s + + - 查看慢查询日志路径 + + - show variables like "slow_query_log%"; + + - 注意:慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题时,查询慢查询日志并不能定位问题。 + + - 连接数 + + - 当数据库连接池被占满时,如果有新的 SQL 语句要执行,只能排队等待,等待连接池中的连接被释放。如果监控发现数据库连接池的使用率过高,甚至是经常出现排队的情况,则需要进行调优。 + + - show variables like '%max_connection%'; -- 查看最大连接数 + - 在 /etc/my.cnf 里面设置数据库的最大连接数:max_connections = XXX + - show status like 'Threads%'; -- 查看当前连接数 + + - 执行计划(explain)详解 + + - id + + - 执行编号,有几个 select 就有几个 id + + - select_type + + - 表示本行是简单的还是复杂的 select + + - simple + + - 简单查询,即查询不包含子查询和 union。 + + - primary + + - 复杂查询中最外层的 select。 + + - subquery + + - 包含在 select 中的子查询 + + - derived + + - 包含在 from 子句中的子查询。MySQL 会将结果存放在一个临时表中,也称为派生表 + + - union + + - 在 union 中的第二个和之后的 select 。 + + - union result + + - 从 union 临时表检索结果的 select 。 + + - dependent union + + - 首先需要满足 UNION 的条件及 UNION 中第二个以及后面的 SELECT 语句,同时该语句依赖外部的查询。 + + - dependent subquery + + - 和 DEPENDENT UNION 相对 UNION 一样。 + + - table + + - 正在访问哪一个表 + + - type + + - 表示关联类型或访问类型,即 MySQL 决定如何查找表中的行 + + - 结果值从好到坏依次是:NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。 + + - NULL + + - MySQL 能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。 + + - const、system + + - MySQL 能对查询的某部分进行优化并将其转化成一个常量,常用于 primary key 或 unique key 的所有列与常数比较时,因此表最多有一个匹配行,读取 1 次,速度比较快。 + + + - eq_ref + + - primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。 + + - ref + + - 相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一索引的部分前缀。索引要和某个值相比较,可能会找到多个符合条件的行。 + + - ref_or_null + + - 类似 ref,但是可以搜索值为 NULL 的行。 + + - index_merge + + - 表示使用了索引合并的优化方法。 + + - range + + - 范围扫描通常出现在 in()、between、>、<、>= 等操作中,表示使用一个索引来检索给定范围的行。一个良好的 SQL 效率至少要保证到该级别。 + + - index + + - 和 ALL 一样,不同就是 MySQL 只需扫描索引树,这通常比 ALL 快一些。 + + - ALL + + - 全表扫描,意味着 MySQL 需要从头到尾去查找所需要的行。 + + - 一般来说,得保证查询至少达到 range 级别,最好能达到 ref 。 + + - possible_keys + + - 哪些索引可以优化查询 + + - key + + - 实际采用哪个索引来优化查询 + + - 如果没有使用索引,则该列是 NULL。 + + - key_len + + - 索引字段的长度 + + - ref + + - 显示了之前的表在 key 列记录的索引中查找值所用的列或常量 + + - rows + + - 为了找到所需的行而需要读取的行数 + + - Extra + + - 执行情况的额外描述和说明 + + - distinct + + - 一旦 MySQL 找到了与行相联合匹配的行,就不再搜索了。 + + - Using index + + - 这发生在对表的请求列都是索引的时候,不需要读取数据文件,而从索引树(索引文件)中即可获得信息。这也是覆盖索引的标识,是性能高的表现。 + + - 如果同时出现 using where,表明索引被用来执行索引键值的查找; + - 没有 using where,表明索引用来读取数据而非执行查找动作。 + + - Using where + + - 使用了 WHERE 从句来限制哪些行将与下一张表匹配或者是返回给用户。 + - Extra 列出现 using where 表示 MySQL 服务器将存储引擎返回服务层以后再应用 WHERE 条件过滤,符合就留下,不符合就丢弃。 + + - Using temporary + + - 表示需要用临时表保存中间结果,常用于 GROUP BY 和 ORDER BY 操作中,一般看到它说明查询需要优化了 + + - Using filesort + + - MySQL需要额外的一次传递,以找出如何按排序顺序检索行。 + + - Not exists + + - MYSQL 优化了 LEFT JOIN,一旦它找到了匹配 LEFT JOIN 标准的行,就不再搜索了。 + + - Using index condition + + - 索引条件推送,MySQL 原来在索引上是不能执行如 like 这样的操作的,但是现在可以,这样少了不必要的 I/O 操作,但是只能用在二级索引上。 + + - Using join buffer + + - 表示使用了连接缓存。 + + - impossible where + + - 子句的值总是 false,不能用来获取任何元组。 + + - select tables optimized away + + - 在没有 GROUP BY 子句的情况下,基于索引优化 MIN/MAX 操作 + - 对于 MyISAM 存储引擎优化 COUNT(*) 操作,而不必等到执行阶段再进行计算,即在查询执行计划生成的阶段就完成优化。 + + - partitions(MySQL 8 新增) + + - 如果查询是基于分区表的话,会显示查询将访问的分区 + + - filtered(MySQL 8 新增) + + - 按表条件过滤的行百分比。 + - rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数 + - 前一个表指 explain 中的 id 值比当前表 id 值小的表 + + - 索引设计策略 + + - 索引设计准则:三星索引 + + - 第一颗星 + + - 参与条件查询(where、join、order by、group by)的列可以组成单列索引或联合索引。 + + - 第二颗星 + + - 避免排序,如果 SQL 语句中出现 order by column,那么取出的结果集就应该已经是按照 column 排序好的,而不需要再进行排序生成临时表。 + + - 第三颗星 + + - SELECT 的列应尽量都是索引列,即尽量使用覆盖索引,避免回表查询。 + + - SQL 优化 + + - 开启查询缓存(MySQL 8.0 已废弃该功能) + - 使用连接查询代替子查询 + - 当只要一行数据时使用 LIMIT 1 + - 多表关联查询时,小表在前,大表在后 + - 调整 where 子句中的连接顺序 + - 不要使用 ORDER BY RAND() + - 优化 GROUP BY + + - GROUP BY … ORDER BY NULL; -- 禁止排序 + + - JOIN 查询 + + - 表结构优化 + + - 永远为每张表创建主键 + - 固定长度的表会更快 + - 通过拆分表,提高访问效率 + - 越小的列会越快 + - 使用 ENUM 而不是 VARCHAR + + - 事务和锁优化 + + - 死锁调优 + + - 排查死锁 + + - 查看死锁日志:show engine innodb status; + - 找出死锁 SQL + - 分析 SQL 加锁情况 + - 模拟死锁案发 + - 分析死锁日志 + - 分析死锁结果 + + - 如何减少死锁发生 + + - 使用合适的索引。 + - 使用更小的事务。 + - 经常性的提交事务,避免事务被挂起。 + + - 高并发事务调优 + + - 结合业务场景,使用低级别事务隔离 + - 避免行锁升级表锁 + - 控制事务的大小,减少锁定的资源量和锁定时间长度 + + - MySQL 服务端参数优化 + + - Innodb_buffer_pool_size + + - 影响性能的最主要参数,一般建议配置为系统总内存的 70-80%,这个参数决定了服务可分配的最大内存。 + + - Innodb_log_buffer_size + + - 用来设置 Innodb 的 Log Buffer 大小的,系统默认值为 1MB 。 Log Buffer 的主要作用就是缓冲 Log 数据,提高写 Log 的 I/O 性能。 + - 一般来说,如果你的系统不是写负载非常高且以大事务居多的话, 8MB 以内的大小就完全足够了。 + + - 刷盘策略 + + - Sync_binlog + + - 控制日志刷盘策略的,基于安全一般设置为 1。 + + - Innodb_flush_log_at_trx_commit + + - 控制事务日志刷盘策略,基于安全一般设置为 1。 + + - InnoDB 性能监控 + + - create table innodb_monitor(a int) engine=innodb; -- 持续获取状态信息的方法 + - 创建一个 innodb_monitor 空表后,InnoDB 会每隔 15 秒输出一次信息并记录到 Error Log 中。通过删除该表可停止监控。 + - 还可以通过相同的方式打开和关闭 innodb_tablespace_monitor、innodb_lock_monitor、innodb_table_monitor 这三种监控功能。 + + - 硬件优化 + + - CPU:多核高频 + - 内存:高内存 + - 磁盘:选择 SSD;且一般要求做 RAID + + - 架构优化 + + - 主从复制&读写分离 + - 分库分表 + diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.xmind" "b/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.xmind" index 6186034..802a869 100644 Binary files "a/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.xmind" and "b/\346\225\260\346\215\256\345\272\223/MySQL \346\212\200\350\203\275\345\233\276\350\260\261\346\246\202\346\213\254\347\211\210.xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.md" "b/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.md" new file mode 100644 index 0000000..c520d44 --- /dev/null +++ "b/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.md" @@ -0,0 +1,455 @@ +# MySQL 索引总结(详细版) + +## 一、索引的概念及作用 + +### 概念 + +- 索引是对数据库表中一列或多列的值进行排序的一种数据结构,好比是一本书前面的目录,可以增加对特定信息的查询速度。 +- 索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中的 + + - 可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中 + +### 作用 + +- 索引能极大地减少扫描行数 + + - 通过索引定位到要读取的页,大大减少了需要扫描的行数,能极大地提升效率。 + +- 索引可以帮助服务器避免排序和临时表 + + - MySQL 的基本执行流程 +(select * from user order by age desc;) + + - 扫描所有行,把所有行加载到内存后,按 age 排序生成一张临时表,再把这表结果返回给客户端。更糟的情况是,如果这张临时表的大小大于 tmp_table_size 的值(默认为 16 M),内存临时表会转为磁盘临时表,性能会更差。 + + - 如果加了索引,因为索引本身是有序的,所以从磁盘读的行数据本身就是按 age 排序好的,也就不会生成临时表(空间消耗)和额外排序(CPU 消耗),所以提升了性能。 + +- 索引可以将随机 I/O 变成顺序 I/O + + - 磁盘的基础知识 + + - 磁道 + + - 扇区组成磁道,磁盘就是多个磁道组成 + + - 扇区 + + - 硬盘读写的基本单位,通常情况下每个扇区的大小是 512B。 + + - 磁盘块 + + - 操作系统(文件系统)读写数据的最小单位,相邻的扇区组合在一起形成一个块,一般是 4KB。 + + - 页 + + - 内存的最小存储单位,页的大小通常为磁盘块大小的 2^n 倍。 + + - Seek Time(寻道时间) + + - 磁头移动到扇区所在的磁道。 + + - Rotational Latency(旋转时延) + + - 磁头移动到同一磁道扇区对应的位置所需求时间。 + + - Transfer Time(传输时间) + + - 从磁盘读取信息传入内存时间。 + + - 如果信息在一个磁道中分散地分布在各个扇区中,或者分布在不同磁道的扇区上(寻道时间是随机 I/O 主要瓶颈所在),将会造成随机 I/O,影响性能。 + - MySQL 的数据是一行行存储在磁盘上的,并且这些数据并非物理连续地存储,这样的话要查找数据就无法避免随机在磁盘上读取和写入数据。 + - 当出现大量磁盘随机 I/O 时,大部分时间都被浪费到寻道上,随机 I/O 和顺序 I/O 大概相差百倍 + +### 索引的缺点 + +- 索引需要占用物理空间,因此也增加了磁盘存储空间。 +- 索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL 不仅要保存数据,还要保存或者更新对应的索引文件。 +- 创建索引和维护索引要耗费空间和时间,这种耗费随着数据量的增加而增加,因此索引也不是越多越好,在数据量小的情况下则没必要建索引。 + +### 不适合建立索引的场景 + +- 数据量少 +- 数据更新频繁 +- 区分度低的字段(如性别、是否删除) + +## 二、MySQL 存储引擎及对应索引 + +### InnoDB + +- B+Tree +- B-Tree +- 哈希索引 + + - 自适应哈希索引 + +### BDB + +- 唯一哈希索引 + +### MyISAM + +- R-Tree + +### MEMORY + +- 非唯一哈希索引(默认) +- B-Tree + +### MERGE,也叫 MRG_MyISAM + +### EXAMPLE + +### NDB Cluster + +### ARCHIVE + +### CSV + +### BLACKHOLE + +### FEDERATED + +### PERFORMANCE_SCHEMA + +### TokuDB + +### ScaleDB + +## 三、MySQL 支持的索引类型 + +### B+树(B 代表的平衡树而不是二叉树,B+树索引并不是一颗二叉树) + +- 支持的查询类型 + + - 全关键字 + - 关键字范围 + - 关键字前缀 + +- 几种常用的使用场景 + + - 全值匹配 + - 匹配值范围查询 + - 匹配最左前缀 + - 匹配列前缀 + - 精准或者匹配部分精准索引且匹配值范围查询匹配另外一列(覆盖索引) + - 仅对索引进行查询匹配,即 select 索引列 from xx 这样的效率的更高 + - 匹配索引列为空一样会使用索引,类似于 「索引列 is null」的形式,优化器依然会进行索引优化查询 + - ICP 优化 + + - 过滤操作下推操作,索引存在相关的列值,会直接在索引上进行条件过滤,然后回表查询,直接返回数据行指针,减少 I/O 操作。 + +- 缺点、限制,也算是几种索引失效的场景 + + - 联合索引中如果不是按照索引的最左列开始查询,无法使用索引(col1,col2,col3),如果从 col2 或 col3 查询不会使用索引。 + - 联合索引中不能跳列查询,即必须满足最左原则。否则也不会使用索引。(col1,col2,col3),如果使用类似 col1=XX and col3=XX 查询不会使用索引。注意和上一条限制的区别。 + - 联合索引中如果某个列是范围查询,则后面的列索引失效。(col1,col2,col3)如果存在类似 col1=xx and col2 like ‘xx%’ and col3=xx,那么 col3 不会进行索引优化。 + - 以百分号「%」开头的 LIKE 查询不会使用 B-Tree 索引 + - 条件查询出现类型对应错误,会进行全表扫描,不会使用索引。例如:name=glorze,但是 name 是字符串类型,glorze 必须加引号。 + - 过滤性越高,MySQL 越容易使用索引。所以当 MySQL 认为全表扫描来得更快的时候不会使用索引。 + - 如果条件选择使用关键字「or」,即使前面的列有索引但是后面的列没有索引,涉及到的索引不会被使用。 + - 索引列是表达式或函数的一部分 + + - 改造成基本字段区间的查找 + + - 隐式类型转换 + - 隐式编码转换 + - 使用 order by 造成的全表扫描 + - like 通配符可能会导致索引失效 + - where 语句中包含 or 时,可能会导致索引失效 + +### HASH 哈希索引 + +- 只有 MEMORY 存储引擎显示支持 +- 优点 + + - 索引只需要存储哈希值,索引结构紧凑,查找速度非常快 + +- 缺点 + + - 因为不存储字段值,所以无法避免要读取行 + - 因为哈希无序,所以无法排序 + - 联合索引中不支持部分列索引查询,只能捆在在一起使用 + - 不支持范围查询,只能用于等值查询 + - 哈希冲突的问题 + +### 全文索引 + +- 仅可用于 MyISAM 表, 主要用于在长篇文章中检索关键字信息。针对较大的数据,生成全文索引很耗时耗空间。 + +### R-Tree,空间数据索引 + +- MyISAM +- 地理数据存储 + +### 分形树索引 + +- TokuDB + +## 四、索引的数据结构 + +### 索引解决的问题 + +- 等值查询 +- 范围查询 + +### 哈希表 + +- 索引自身只存储对应的哈希值,索引的结构十分紧凑,所以哈希索引查找速度非常快 +- 但是哈希索引不支持区间查找(需要挨个数据遍历,效率低),更多的时候哈希表是与 B+ 树等一起使用的。 + + - InnoDB 引擎中就有一种名为「自适应哈希索引」的特殊索引 + +### B+ 树 + +- 二叉查找树(BST) + + - 综合了顺序表(查找效率高)和链表(增删效率高)优势的折中方案,每个节点最多有 2 个子节点,且左子树和右子树数据顺序是左小右大。 + - 为了保证每次查找都可以这折半而减少 I/O 次数,但是二叉树很考验第一个根节点的取值,因为很容易在这个特点下出现「树不分叉」的情况 + +- 平衡二叉查找树(AVL) + + - 采用二分法思维,平衡二叉查找树除了具备二叉查找树的特点,主要的特征是树的左右两个子树的层级最多相差 1。 + - 插入删除数据时通过左旋/右旋操作保持二叉树的平衡,不会出现左子树很高、右子树很矮的情况。 + - 缺点 + + - 时间复杂度和树高相关 + + - 树有多高就需要检索多少次,每个节点的读取,都对应一次磁盘 IO 操作。 + - 树的高度等于每次查询数据时磁盘 I/O 操作的次数。 + + - 平衡二叉树不支持范围查询的快速查找 + + - 范围查询时需要从根节点多次遍历,查询效率不高。 + +- B 树:改造二叉树,变为多叉平衡查找树 + + - 特点 + + - B 树的节点中存储着多个元素,每个节点有多个分叉。 + - 节点中的元素包含键值和数据,节点中的键值从大到小排列。所有的节点都储存数据。 + - 父节点当中的元素不会出现在子节点中。 + - 所有的叶子节点都位于同一层,且叶子节点之间没有指针连接。 + + - 缺点 + + - B 树也不支持范围查询的快速查找 + - 如果 data 存储的是行记录,行的大小随着列数的增多,所占空间会变大。 + +- B+ 树:改造 B 树(非叶子节点是否存储数据) + + - 只有叶子节点才会存储数据,非叶子节点至存储键值。 + - 叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。 + +## 六、常用的索引设置策略,InnoDB 索引实现 + +### 避免几种索引失效的场景 + +### 独立的列 + +- 索引的列不能是表达式的一部分 +- 不能是函数的参数 + +### 前缀(后缀)索引和索引选择性 + +- 索引选择性 + + - 定义:不重复的索引值和表的总记录数的比例。所以比例越高,索引的性能的就越高。 + - 唯一索引的索引选择性是 1,所以相对来讲,唯一索引的性能是最好的 + +- 如果某一字段类似于 http 链接那样长且在生产情景下需要添加索引,那么就可以考虑前缀索引。这样既能保证索引的高性能,又能避免性能开销过大。但是要注意前缀索引的合适选取长度。 +- 前缀索引的缺点 + + - 无法进行排序 order by + - 无法进行分组 group by + - 无法进行覆盖扫描 + +### 复合索引,多列索引、组合索引 + +- 把 where 后面的常用列都建立单独索引其实不是一个优秀的解决方案。 +- MySQL 中的有个「索引合并」的概念,对于多个单列索引进行的复杂查询会进行优化,从而也说明单列索引的性能在大多数时候失效 +- 建立联合索引的时候需要考虑联合索引的合理顺序以及要注意联合索引的经常失效的场景。 +- 合适的索引列顺序 + + - 在不考虑排序和分组的情况下,如果用于优化 where 条件的查找,可以考虑将选择性最高的列放到索引最前列。 + +- 最左匹配原则,在遇到范围查询的时候,就会停止匹配。 + +### 聚簇索引 + +- 聚簇索引不是一种索引形式,而是一种数据存储形式,在 InnoDB 中,主键索引存储完整数据行,叶子节点存储的数据是整行记录 +- 优点 + + - 相关数据保存在一起,数据访问更快 + - 使用覆盖索引扫描的查询可以直接使用叶子节点的值 + +- 缺点 + + - 虽然将数据放在内存中会是速度加快,但是也增加了存储开销,所以将数据全部放入内存也就意味着聚簇索引失去了意义 + - 插入速度依赖于插入顺序 + - 更新聚簇索引列的代价、开销会变得更高 + - 基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题。 + + - 平衡树的数据结构,如果插入不规则的数据,会导致当前节点所属的页满,然后进行页分裂操作,容纳新增的数据,这样的话会极大的影响性能。 + - 这也是建议 InnoDB 建议逐渐设置为自增的原因,而尽量不要使用 UUID 或者无规则的字符串作为主键。 + + - 可能导致全表扫描变慢,尤其是行比较稀疏或者存储不连续 + +### 覆盖索引(只有 B+树支持) + +- 索引包含所有需要查询的字段的值,就称之为「覆盖索引」 +- 优点 + + - 不需要回表查询,减少数据访问量。 + - 简单的范围查询可以使用顺序的索引访问 + +### 使用索引扫描做排序 + +- 两种排序 + + - order by 排序操作 + - 索引顺序扫描 + +- 在索引设计的时候需要设置某一索引,让其尽量既能满足手动排序,又能做查找,设计就算是比较好的。要求索引的列顺序和 order by 顺序一致。 + +### 避免冗余和重复索引 + +## 五、索引(B+树) + +索引是一种数据结构,用于加快mysql获取数据的速度; + +常见索引模型:哈希表、有序数组、搜索树。 + +索引类型:主键索引(存储整行数据)、非主键索引(主键的值,需要回表两次查询) +### 数据结构角度 + +- https://pic.imgdb.cn/item/624c5345239250f7c529594f.png +B+ 树索引(查询性能高、IO 次数少、范围查询简便) + + - 每一个父节点的元素都出现在子节点中,是子节点的最大或最小元素。 + - 非叶子节点不保存数据,只保存关键字用作索引,所有数据都保存在叶子节点中。 + - 内部节点不保存数据,所以能在内存中存放更多索引,增加缓存命中率。 + +- 哈希索引(单行查询快) + + 哈希索引基于哈希表实现,只有精确匹配索引的所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 + + 作者:counterxing + 链接:https://www.zhihu.com/question/67094336/answer/250034118 + 来源:知乎 + 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 + - 无法用于排序 + - 仅支持 < = > 以及 IN 操作 + - 哈希冲突比较多的话,维护代价高 + - 不能避免全表扫描 + - 不支持部分索引列匹配查找 + +- FULLTEXT 全文索引(MyISAM 和 InnoDB) + + - 更建议使用 Lucene、Solar 等 + +- R-Tree索引 + + - 解决空间数据检索的问题。 + +### 物理存储角度 + +- 聚簇索引(Cluster Index) + + - InnoDB 存储引擎的数据组织方式就是聚簇索引表,完整的记录,存储在主键索引中,通过主键索引,就可以获取记录所有的列。 + +- 非聚簇索引 + + - MyISAM 就是非聚簇索引 + - 叶子节点仍然是索引节点,只不过有指向对应数据块的指针。 + +### 逻辑角度 + +- 主键索引,特殊的唯一索引,不允许为 null,整行索引 + + - 主键可作外键,唯一索引不可。 + +- 普通索引、单列索引、二级索引、辅助索引 + + - 一个索引只包含单个列。一个表可以有多个单列索引,注意:单列索引不是组合索引。 + - 唯一任务是加快对数据的访问速度 + - 会回表查询:先搜索索引拿到主键值,再到主键索引树搜索一次 + - 叶子节点存储的数据是该行的主键值 + +- 多列索引、复合索引、联合索引(遵循最左前缀原则) + + - 一个索引包含多个列 + - 最左前缀原则 + + - 当创建 (a,b,c) 联合索引时,相当于创建了 (a) 单列索引、(a,b) 联合索引以及 (a,b,c) 联合索引。 + - 要想索引生效的话,只能使用 a 和 a,b 和 a,b,c 这三种组合(a,c 组合也可以,但实际上只用到了 a 的单列索引,c 并没有用到)。 + + - 创建联合索引时,应该仔细考虑列的顺序 + - 联合索引场景示例 + + - select * from myTest  where a=3 and b=5 and c=4; + + - abc 三个索引都在 where 条件里面用到了,而且都发挥了作用 + + - select * from myTest  where  c=4 and b=6 and a=3; + + - where 里面的条件顺序在查询之前会被 MySQL 自动优化,效果跟上一句一样 + + - select * from myTest  where a=3 and c=7; + + - 用到索引,b 没有用,所以 c 是没有用到索引效果的 + + - select * from myTest  where a=3 and b>7 and c=3;  + + - a 用到了,b 也用到了,c 没有用到,这个地方 b 是范围值,也算断点,只不过自身用到了索引 + + - select * from myTest  where b=3 and c=4;   + + - 因为 a 索引没有使用,所以这里 bc 都没有用上索引效果 + + - select * from myTest  where a>4 and b=7 and c=9; + + - a 用到了  b 没有使用,c 没有使用 + + -  select * from myTest  where a=3 order by b; + + - a 用到了索引,b 在结果排序中也用到了索引的效果,a 下面任意一段的 b 是排好序的 + + - select * from mytable where b=3 order by a; + + - b 没有用到索引,排序中 a 也没有发挥索引效果 + +- 唯一索引或者非唯一索引 + + - 唯一索引索引列的值必须唯一,但允许有 NULL。 + - 唯一索引可以有多个,如果是组合索引,则列值的组合必须唯一。 + - 如果在一个列上同时建唯一索引和普通索引的话,Mysql 会自动选择唯一索引。 + - 唯一索引可以保证数据记录的唯一性。 + - 在许多场合,人们创建唯一索引的目的往往不是为了提高访问速度,而只是为了避免数据出现重复。 + +- 空间索引(只存在 MYISAM 存储引擎中) + +### 设计索引的原则 + +- 基本原则 + + - 搜索的索引列,出现在 where 子句中的列 + - 使用唯一索引 + - 使用短索引,对字符串列进行索引,取字符串的前 N 个字符 + - 利用最左前缀,可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。 + - 不要过度索引,占磁盘空间,降低写操作性能。 + - InnoDB 尽量自己指定主键 + +- 三星原则 + + - 第一颗星 + + - 参与条件查询(where、join、order by、group by)的列可以组成单列索引或联合索引。 + + - 第二颗星 + + - 避免排序,如果 SQL 语句中出现 order by column,那么取出的结果集就应该已经是按照 column 排序好的,而不需要再进行排序生成临时表。 + + - 第三颗星 + + - SELECT 的列应尽量都是索引列,即尽量使用覆盖索引,避免回表查询。 + diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" "b/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" index 0ce46de..4217af8 100644 Binary files "a/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" and "b/\346\225\260\346\215\256\345\272\223/MySQL \347\264\242\345\274\225\346\200\273\347\273\223\357\274\210\350\257\246\347\273\206\347\211\210\357\274\211.xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).md" "b/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).md" new file mode 100644 index 0000000..2377e89 --- /dev/null +++ "b/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).md" @@ -0,0 +1,519 @@ +# https://pic.imgdb.cn/item/624c1415239250f7c5a44da7.png +MySQL(InnoDB) + +## 一、索引(B+树) + +索引是一种数据结构,用于加快mysql获取数据的速度; + +常见索引模型:哈希表、有序数组、搜索树。 + +索引类型:主键索引(存储整行数据)、非主键索引(主键的值,需要回表两次查询) +### 参考隔壁《MySQL 索引总结(详细版)》 + +## 二、事务 + +### 事务的概念 + +- 据库事务是数据库系统执行过程中的一个逻辑处理单元,保证一组数据库操作要么全部成功(提交),要么全部失败(回滚)。 + +### 事务的特性 + +- 原子性(Atomic) + + 事务中包含的操作被看作一个整体的业务单元,这个业务单元中的操作要么全部成功,要么全部失败,不会出现部分失败、部分成功的场景。 +- 一致性(Consistency) + + 事务在完成时,必须使所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性。 +- 隔离性(Isolation) + + - 必须要掌握的知识点 + - 一个事务的修改在最终提交前,对其他事务是不可见的。主要针对并发场景。 + +- 持久性(Durability) + + 事务结束后,所有的数据会固化到一个地方,如保存到磁盘当中,即使断电重启后也可以提供给应用程序访问。 +- 底层实现 + + - 利用 undo log 保证原子性,记录了需要回滚的日志信息。 + + - 事务回滚时会撤销已经执行成功的 SQL。 + + - 由内存 + redo log 来保证 + + - MySQL 修改数据同时在内存和 redo log 记录这次操作,事务提交的时候通过 redo log 刷盘,宕机的时候可以从 redo log 恢复。 + + - 利用锁和 MVCC 机制保证隔离性 + - 通过原子性、持久性、隔离性来保证一致性 + + - 一般由代码层面来保证。 + +### 事务的隔离级别 + +- 并发事务的问题 + + - 数据丢失 + + - 一个事务的更新被另一个事务的更新所覆盖。 + + - 由于事务 A 更新失败回滚,导致事务 B 更新的数据被覆盖掉,造成数据丢失 + + - 解决方案 + + - 可以基于数据库中的悲观锁来避免发生,即在查询时通过在事务中使用 select xx for update 语句来实现一个排他锁,保证在该事务结束之前其他事务无法更新该数据; + - 基于乐观锁来避免,即将某一字段作为版本号,如果更新时的版本号跟之前的版本一致,则更新,否则更新失败。 + + - 脏读 + + - 一个事务读到另一个事务没有提交的数据。 + + - 由于事务 A 更新失败回滚,导致事务 B 读取的数据为脏数据 + + - 解决方案 + + - 已提交读及以上隔离级别 + + - 不可重复读 + + - 一个事务读到另一个事务已提交的数据(update) + + - 事务多次读取读取到了不同的数据 + + - 解决方案 + + - 可重复读及以上隔离级别 + + - 幻读 + + - 一个事务读到另一个事务已提交的数据(insert) + - 解决方案 + + - 串行化的隔离级别,但是因为可序列化级别的方案锁粒度太大,所以一般采用间隙锁的方式防止幻读 + +- 隔离级别(解决并发事务问题) + + - 未提交读(Read Uncommited) + + - 直接带来脏读的问题,可以读取未提交记录。此隔离级别不会使用,忽略。 + - 脏读、不可重复读、幻读均不能解决 + + - 已提交读(Read Committed,RC) + + - 直接带来不可重复读的问题,一个事务只能读取另外一个事务己经提交的数据, +不能读取未提交的数据。 + - 可以避免脏读,但依然存在不可重复读以及幻读的问题。 + + - 可重复读(Repeatable Read,RR)(默认的隔离级别) + + - 字面的意思就是必须等上一个事务提交才能进行当前事务的读取操作,保证数据正确性。可以避免脏读、不可重复读,但依然存在幻读的问题。 + - RR 隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。 + - 在这里里面也有一个「幻读」的概念,不过可重复读产生幻读的现象不属于数据库存储的值,多半是统计值或者计算值。 + - 底层实现 + + - 利用间隙锁,防止幻读的出现,保证了可重复读 + - MVCC 的快照生成时机不同 + + - 串行化(Serializable),相当于表锁 + + - 从 MVCC 并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁,共享锁),写加写锁 (X锁,排它锁)。 + - Serializable 隔离级别下,读写冲突,因此并发度急剧下降,在 MySQL/InnoDB 下不建议使用。 + +### 事务的底层日志 + +- undo log + + - undo log 是回滚日志,提供回滚操作。 + - undo 用来回滚行记录到某个版本。undo log 一般是逻辑日志,根据每行记录进行记录。 + - 主要用来主从复制和恢复数据用。 + +- redo log + + - redo log 是重做日志,提供前滚操作 + - redo log 通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。 + +- MySQL 如何解决 undo log 和 redo log 的原子一致性 + + - MySQL 的内部 XA 事务,即两阶段提交 + + - prepare:写入 redo log,并将回滚段置为 prepared 状态,此时 binlog 不做操作。 + - commit:InnoDB 释放锁,释放回滚段,设置提交状态,写入 binlog,然后存储引擎层提交。 + +- MySQL 崩溃恢复 + + - 扫描最后一个 Binlog 文件,提取其中的 xid; + - InnoDB 维持了状态为 Prepare 的事务链表,将这些事务的 xid 和 binlog 中记录的 xid 做比较,如果在 binlog 中存在,则提交,否则回滚事务。 + +### MVCC(Multi-Version Concurrency Control,基于多版本的并发控制协议):读不加锁,读写不冲突。 + +- 快照读(snapshot read) + + - 简单的select操作,属于快照读,不加锁。前提是隔离级别不是串行级别 + - 快照读可能读到的并不一定是数据的最新版本,而有可能是历史版本。 + - MVCC 避免了对数据重复加锁的过程,大大提高了并发性能。 + +- 当前读(current read) + + - 特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。 + + - select * from table where ? lock in share mode;(共享锁) + - select * from table where ? for update;(排它锁) + - insert into table values (…); + - update table set ? where ?; + - delete from table where ?; + + - 读取的是数据的最新版本。 + +- MVCC + + - 为了实现读写冲突时不加锁,而这个读指的就是快照读,而非当前读。 + - MVCC为数据库解决的问题 + + - 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。 + - 解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。 + - MVCC 常用组合方案 + + - MVCC + 悲观锁:MVCC 解决读写冲突,悲观锁解决写写冲突。 + - MVCC + 乐观锁:MVCC 解决读写冲突,乐观锁解决写写冲突 + + - MVCC 模型在 MySQL 中的具体实现 + + - 3 个隐式字段 + + - DB_TRX_ID + + - 最近修改(修改/插入)事务 ID,记录创建这条记录/最后一次修改该记录的事务 ID。 + + - DB_ROLL_PTR + + - 回滚指针,指向这条记录的上一个版本 + + - DB_ROW_ID + + - 隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以 DB_ROW_ID 产生一个聚集索引。 + + - 实际还有一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了。 + + - undo 日志 + + - insert undo log + + - 事务在 insert 新记录时产生的 undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃。 + + - update undo log + + - 事务在进行 update 或 delete 时产生的 undo log,不仅在事务回滚时需要,在快照读时也需要,所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除。 + + - Read View + + - 事务进行快照读操作的时候生产的读视图(Read View) + - 事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID + +### 事务调优 + +- 高并发事务调优 + + - 结合业务场景,使用低级别事务隔离 + - 避免行锁升级表锁 + - 控制事务的大小,减少锁定的资源量和锁定时间长度 + +- 大事务(长时间执行的事务)的解决方案 + + - 带来的问题 + + - 造成大量的阻塞和锁超时,容易造成主从延迟 + - 大事务如果执行失败,回滚也会很耗时 + + - 排查大事务的方式 + + - 监控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警或者杀进程 + - select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60; + - 生产中,会将监控大事务的语句,配成定时脚本,进行监控。 + + - 结合业务场景,优化 SQL,将一个大事务拆成多个小事务执行,或者缩短事务执行时间即可。 + +- 数据库宕机重启,事务丢失的情况 + + - innodb_flush_log_at_trx_commit & sync_binlog + + - 默认值为 1 & 0 + + - 每次事务提交时都将 redo log 直接持久化到磁盘。但是 MySQL 不控制 binlog 的刷新,由操作系统自己控制它的缓存的刷新。 + - 一旦系统宕机,在 binlog_cache 中的所有 binlog 信息都会被丢失。 + + - 双 1 配置 + + - 每次事务提交时都将 redolog 直接持久化到磁盘,binlog 也会持久化到磁盘。 + - 性能是最差的,适合金融系统 + + - 2 & 0 配置 + + - 每次事务提交时,只是把 redolog 写到 OS cache,隔一秒,MySQL 主动将 OS cache 中的数据批量 fsync。 + - 一旦系统宕机,在 binlog_cache 中的所有 binlog 信息都会被丢失。 + - 相对性能最好的一套配置 + +## 锁 + +### 共享锁(S,读锁) + +- 共享的,只能读不能写。 + +### 排它锁(X,写锁) + +- 排他的,会阻塞其他的写锁和读锁。 + +### 表锁 + +- 锁定整张表并且阻塞其他用户对该表的所有读写操作 +- 优势 + + - 开销小;加锁快;无死锁。 + +- 劣势 + + - 锁粒度大,发生锁冲突的概率高,并发处理能力低。 + +### 行锁 + +- 乐观锁 + + - 一段执行逻辑加上乐观锁,不同线程同时执行时,线程可以同时进入执行阶段,在最后更新数据的时候要检查这些数据是否被其他线程修改了,没有修改则进行更新,否则放弃本次操作。 + - 乐观锁一般使用版本号实现,其实并没有加锁 + +- 悲观锁 + + - 一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。 + - 也就是排他锁 + +### 加锁方式 + +- 自动加锁 + + - 查询操作(SELECT)会自动给涉及的所有表加读锁 + - 更新操作(UPDATE、DELETE、INSERT)会自动给涉及的表加写锁 + +- 显式加锁 + + - 共享读锁 + + - lock table tableName read + + - 独占写锁 + + - lock table tableName write + + - 批量解锁 + + - unlock tables + +### GAP 锁 + +### 2PL(二阶段锁): + +- 加锁阶段 +- 解锁阶段 +- 保证加锁阶段与解锁阶段不相交。 + +### 死锁 + +死锁的发生与否,并不在于事务中有多少条 SQL 语句,死锁的关键在于:两个或以上的 Session 加锁的顺序不一致。 +- 死锁发生在当两个事务均尝试获取对方已经持有的排他锁时,在 innodb 中,select 不会对数据加锁,而 update/delete 会加行级别的排他锁。 +- 死锁场景 + + - 表锁死锁 + + - https://pic.imgdb.cn/item/624cfdac239250f7c5f6f804.jpg +InnoDB 既实现了行锁,也实现了表锁。行锁是通过索引实现的,如果不通过索引条件检索数据,那么 InnoDB 将对表中所有的记录进行加锁,升级为表锁。 + + - 行锁死锁 + + - https://pic.imgdb.cn/item/624cfe29239250f7c5f7bb67.jpg +当 update 数据作用于索引时,会发生行锁 + + - 共享排它死锁 + + - https://pic.imgdb.cn/item/624cfed4239250f7c5f9004d.png +两个事物对同一条记录同时持有共享锁的情况下,再次尝试获取该条记录的排他锁,从而导致互相等待引发死锁。 + + - 死锁回滚 + + - 当 InnoDB 检测到死锁时,会回滚其中一个事务,让另一个事务得以完成。 + +- 死锁调优 + + - 排查死锁 + + - 查看死锁日志:show engine innodb status; + - 找出死锁 SQL + - 分析 SQL 加锁情况 + - 模拟死锁案发 + - 分析死锁日志 + - 分析死锁结果 + + - 如何减少死锁发生 + + - 使用合适的索引。 + - 使用更小的事务。 + - 经常性的提交事务,避免事务被挂起。 + +## MySQL 日志体系 + +### binlog(归档日志,逻辑日志,二进制) + +- Server 层(缓存、连接器、分析器、优化器、执行器)记录的操作日志 +- 记录的是这个更新语句的原始逻辑 + + - 追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。 + +- 底层相关概念 + + - binlog 是记录所有数据库表结构变更(例如 CREATE、ALTER TABLE 等)以及表数据修改(INSERT、UPDATE、DELETE 等)的二进制日志。 + - binlog 不会记录 SELECT 和 SHOW 这类操作,因为这类操作对数据本身并没有修改,可以通过查询通用日志来查看 MySQL 执行过的所有语句。 + - binlog 组成 + + - 索引文件(文件名后缀为.index)用于记录哪些日志文件正在被使用 + - 日志文件(文件名后缀为.00000*)记录数据库所有的 DDL 和 DML(除了数据查询语句)语句事件。 + + - 用途 + + - 恢复 + - 复制 + + - 主库有一个 log dump 线程,将 binlog 传给从库 + - 从库有两个线程,一个 I/O 线程,一个 SQL 线程 + + - I/O 线程读取主库传过来的 binlog 内容并写入到 relay log + - SQL 线程从 relay log 里面读取内容,写入从库的数据库。 + + - 审计 + + - 防 SQL 注入 + + - 日志查看 + + - mysqlbinlog -vv mysql-bin.000001 + + - binlog 日志格式 + + - statement + + - 记录的是修改 SQL 的语句 + + - row(推荐) + + - 记录的是每行实际数据的变更 + + - mixed + + - 前两者的混合 + +### redo log(重做日志文件,提供前滚操作) + +- InnoDB 存储引擎层的日志,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。 + + - 可应对断电时候的数据恢复,主从复制搭建 + - 数据库进行异常重启的时候,可以根据redo log日志进行恢复 + +- 在一条更新语句进行执行的时候,InnoDB 引擎会把更新记录写到 redo log 日志中,然后更新内存,此时算是语句执行完了,然后在空闲的时候或者是按照设定的更新策略将 redo log中的内容更新到磁盘中 + + - 先写日志,在更新硬盘 + - redo log 日志的大小是固定的,即记录满了以后就从头循环写 + +- 记录事务执行后的状态,用来恢复未写入 data file 的已成功事务更新的数据。 + +### undo log(回滚日志) + +- 用来回滚行记录到某个版本。 +- 一般是逻辑日志,根据每行记录进行记录。 +- 保存了事务发生之前的数据的一个版本,可以用于回滚(MVCC) +- 用于记录事务开始前的状态,用于事务失败时的回滚操作 + +## binlog + +## 一条 SQL 的生命周期 + +### 词法解析、语法解析、权限检查、查询优化、SQL执行等 + +### where 的提取 + +- Index Key,索引的起始范围 + + - Index First Key + - Index Last Key + +- Index Filter,索引起始范围之外依然满足查询条件的查询范围,逐个索引列检索 +- Table Filter,不属于索引列的查询条件 + +### 执行计划 + +- 主键扫描 +- 唯一键扫描 +- 范围扫描 +- 全表扫描 + +### inner join、left join、right join 的区别 + +- inner join(等值联接):只返回两个表中联接字段相等的记录。 +- left join(左联接):返回左表中的所有记录以及和右表中的联接字段相等的记录。 +- right join(右联接):返回右表中的所有记录以及和左表中的联接字段相等的记录。 + +## 表 + +### 分类 + +- 堆表:无序存储 +- 聚簇索引表:按照主键顺序存储 + +### MySQL 大表分页 + +- 分页查询 + + - select * from order where user_id = xxx and 【其它业务条件】 order by created_time, id limit offset, pageSize + + - 定位到 offset 的成本过高,未能充分利用索引的有序性 + + - select * from order where id > 'pre max id' order by id limit 50 + - 行比较 + + - select * from order where user_id = xxx and 【其它业务条件】 and (created_time > 'created_time of latest recode' or (created_time = 'created_time of latest recode' and id > 'id of latest recode')) order by created_time, id limit pageSize + +- 索引(b+ tree)的特点在于,数据是有序的,虽然找到第 N 条记录的效率比较低,但找到某一条数据在索引中的位置,其效率是很高的 + +### 线上修改表结构的方案 + +- ALTER TABLE table_name CHANGE(不推荐) + + - 修改表的过程中,对绝大部分操作,原表可读,也可以写。 + - 但是对于这个要修改的列,不支持并发的 DML 操作 + +- 借助第三方工具 + + - 支持在线修改表结构,能够让你在执行 ALTER 操作的时候,表不会阻塞 + + - pt-online-schema-change + + - 创建一个新的表,表结构为修改后的数据表,用于从源数据表向新表中导入数据 + - 创建触发器,用于记录从拷贝数据开始之后,对源数据表继续进行数据修改的操作记录下来,用于数据拷贝结束后,执行这些操作,保证数据不会丢失。 + - 拷贝数据,从源数据表中拷贝数据到新表中。 + - rename 源数据表为 old 表,把新表 rename 为源表名,并将 old 表删除。 + - 删除触发器 + + - gh-ost(GitHub 旗下) + +- 改从库表结构,然后主从切换 + + - 主从切换过程中可能会有数据丢失的情况 + +## InnoDB 架构 + +### 逻辑架构 + +- 主图就是逻辑架构 + +### https://pic.imgdb.cn/item/624c5bf1239250f7c53cfc23.png +内存和磁盘架构 + +- 执行器更新内存数据 +- 写 redo log 日志 +- 准备提交事务,写入磁盘 +- 准备提交事务,binlog 写入磁盘 +- 写入 commit 标记 + diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).xmind" "b/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).xmind" index c78892e..b57d0f3 100644 Binary files "a/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).xmind" and "b/\346\225\260\346\215\256\345\272\223/MySQL(InnoDB).xmind" differ diff --git "a/\346\225\260\346\215\256\345\272\223/README.md" "b/\346\225\260\346\215\256\345\272\223/README.md" index 7d74214..69dadff 100644 --- "a/\346\225\260\346\215\256\345\272\223/README.md" +++ "b/\346\225\260\346\215\256\345\272\223/README.md" @@ -1,11 +1,28 @@ # 数据库 MySQL Oracle -主要总结 InnoDB 存储引擎下得 MySQL 系列相关知识点,顺便与 MyISAM 做一些对比。包括数据库事务、锁、索引、以及 sql 语句的生命周期等偏底层的一些相关知识点。 +主要总结 InnoDB 存储引擎下得 MySQL 系列相关知识点,顺便与 MyISAM 做一些对比。包括数据库事务、锁、索引、以及 sql 语句的生命周期等偏底层的一些相关知识点。 ### MySQL 技能图谱概括版 ### -梳理了一下 MySQL 的相关技能图谱,这样对于我们来说在学习的时候会有一个清晰的脉络,所以这张思维导图里面不对知识点进行详细的划分,只是围绕 MySQL 相关知识点由浅入深总结,然后你可以自行根据相关分类,自行去细化、总结各个部分的知识点,形成自己的只是体系。 +梳理了一下 MySQL 的相关技能图谱,这样对于我们来说在学习的时候会有一个清晰的脉络,所以这张思维导图里面不对知识点进行详细的划分,只是围绕 MySQL 相关知识点由浅入深总结,然后你可以自行根据相关分类,自行去细化、总结各个部分的知识点,形成自己的知识体系。 ### MySQL(InnoDB) ### 在学习或者工作中,老四经常用到的就是 InnoDB 存储引擎,所以单独针对该存储引擎进行一些细化的、高级一点知识点总结。 ### MySQL 索引总结(详细版) ### 将 MySQL 中的索引单独拿出来做一个总结,并且在博客总结了一篇文章。因为无论是工作还是笔面试中,我们需要对索引的一些基础以及高阶知识有所掌握,索引涉及到 SQL 优化,对于索引的把握会帮助我们写出高性能的 SQL 语句,使我们学会优化数据库、优化表结构,数据等重要的一步。 ### Redis ### -关于 NoSQL 的应用现在也是越来越广泛,而 Redis 几乎应用在各种各样、大大小小的应用系统当众,无论是作为缓存还是大数据索引,Redis 的性能一直为人们所喜爱。所以针对非关系型数据库 Redis 的总结也会一直更新下去,后期也会扩展一些 Spring-data-Redis 等中对 Redis 集成等的总结。 \ No newline at end of file +关于 NoSQL 的应用现在也是越来越广泛,而 Redis 几乎应用在各种各样、大大小小的应用系统当众,无论是作为缓存还是大数据索引,Redis 的性能一直为人们所喜爱。所以针对非关系型数据库 Redis 的总结也会一直更新下去,后期也会扩展一些 Spring-data-Redis、Redis 锁、微服务、分布式系统中对于 Redis 使用和集成等的总结。 + +目前 Redis 已经进行的总结分类: + +1. redis-cli 命令相关 +2. Redis 基础数据结构(key 都是字符串,value 对应着不同的数据结构) +3. Redis 持久化 +4. Redis 内存划分 +5. Redis 数据模型 +6. Redis 分区(配合集群使用) +7. Redis 集群方案 +8. Redis 为什么快? +9. Redis 数据淘汰策略(lru、随机、将要过期、不淘汰) +10. Redis 并发 +11. Redis 锁 +12. Redis 缓存 +### MySQL 中的锁(详细版) ### +主要对 InnoDB 行级锁中涉及到的一些各种相对复杂的知识点进行总结,后期也会逐步对 MVCC 底层的原理进行介绍。此外就是将 MySQL 中各种所得概念通过一个相对清晰的思维导图的方式梳理出来。 \ No newline at end of file diff --git "a/\346\225\260\346\215\256\345\272\223/Redis.md" "b/\346\225\260\346\215\256\345\272\223/Redis.md" new file mode 100644 index 0000000..9e46ddf --- /dev/null +++ "b/\346\225\260\346\215\256\345\272\223/Redis.md" @@ -0,0 +1,1108 @@ +# Redis + +## 一、Redis 通信协议(RESP) + +### 特征 + +- TCP 层 +- 是二进制安全的 +- 基于请求 - 响应模式 +- 简单、易懂 + +### 传输结构 + +- 单行字符串,第一个字节为 + +- 错误消息,第一个字节为 - +- 整型数字,第一个字节为 :,后跟整数的字符串 +- 多行字符串,第一个字节为 $,后跟字符串的长度 +- 数组,第一个字节为 *,后跟跟着数组的长度 + +## https://pic.imgdb.cn/item/624e8ebd239250f7c59cd4f8.png +二、Redis 线程模型 + +### 多个 Socket + +### IO 多路复用程序 + +### 文件事件分派器 + +- 连接应答处理器 + + - 客户端要连接 Redis + +- 命令请求处理器 + + - 客户端要写数据到 Redis(读、写请求命令) + +- 命令回复处理器 + + - 客户端要从 Redis 读数据 + +### 事件处理器 + +- AE_READABLE + + - 当 socket 变得可读时或者有新的可以应答的 socket 出现时,socket 就会产生一个 AE_READABLE 事件 + +- AE_WRITABLE + + - 当 socket 变得可写时,socket 就会产生一个 AE_WRITABLE 事件。 + +### 多个 socket 会产生不同的事件,不同的事件对应着不同的操作,IO 多路复用程序监听着这些 Socket,当这些 Socket 产生了事件,IO 多路复用程序会将这些事件放到一个队列中,通过这个队列,以有序、同步、每次一个事件的方式向文件时间分派器中传送。当事件处理器处理完一个事件后,IO 多路复用程序才会继续向文件分派器传送下一个事件。 + +- https://pic.imgdb.cn/item/624e8fcf239250f7c59ef089.png + +## 三、Redis 之 pipeline + +### Redis 的性能瓶颈主要在客户端和服务端之间的网络性能 + +### pipeline + +- 将一组 Redis 命令进行组装,通过一次 RTT 传输给 Redis,同时再将这组命令的执行结果按照顺序返回给客户端 。 + +## 四、Redis 持久化 + +### RDB + +- 快照存储持久化方式,具体就是将 Redis 某一时刻的内存数据全量保存到硬盘的文件当中,默认保存的文件名为 dump.rdb,而在 Redis 服务器启动时,会重新加载 dump.rdb 文件的数据到内存当中恢复数据。 + + - RDB 优劣势以及与 AOF 的比较 + + - 与 AOF 方式相比,通过 rdb 文件恢复数据比较快。 + - rdb 文件非常紧凑,适合于数据备份。 + - 通过 RDB 进行数据备,由于使用子进程生成,所以对 Redis 服务器性能影响较小。 + - 如果服务器宕机的话,采用 RDB 的方式会造成某个时段内数据的丢失 + - 使用 save 命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。 + - 由于 AOF 的优先级更高,因此当 AOF 开启时,Redis 会优先载入 AOF 文件来恢复数据 + - 只有当 AOF 关闭时,才会在 Redis 服务器启动时检测 RDB 文件,并自动载入,载入过程中 Redis 处于阻塞状态 + + - 触发条件 + + - 手动触发 + + - save + + - 阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在 Redis 服务器阻塞期间,服务器不能处理任何命令请求。 + - 线上环境要杜绝 save 的使用 + + - bgsave + + - 创建一个子进程,由子进程来负责创建 RDB 文件,父进程(即 Redis 主进程)则继续处理请求。 + + - 自动触发 + + - save m n + + - 在配置文件中通过「save m n」,指定当 m 秒内发生 n 次变化时,触发 bgsave 操作。 + + - 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行 bgsave 命令,并将 rdb 文件发送给从节点 + - 执行 shutdown 命令时,自动执行 rdb 持久化 + +### AOF(主流) + +- 记录客户端对服务器的每一次写操作命令,并将这些写操作以 Redis 协议追加保存到以后缀为 aof 文件末尾,在 Redis 服务器重启时,会加载并运行 aof 文件的命令,以达到恢复数据的目的。 + + - AOF 执行流程 + + - 命令追加(append) + + - 将 Redis 的写命令追加到缓冲区 aof_buf,避免硬盘 IO 成为 Redis 负载的瓶颈。 + + - 文件写入(write)、文件同步(sync) + + - 根据不同的同步策略将 aof_buf 中的内容同步到硬盘 + - 策略 + + - no + + - 命令写入 aof_buf 后调用系统 write 操作,不对 AOF 文件做 fsync 同步;同步由操作系统负责,通常同步周期为 30 秒 + + - always + + - 客户端的每一个写操作都保存到 aof 文件当中,这种策略很安全,但是每个写请注都有 IO 操作,所以也很慢。 + + - everysec(默认配置) + + - 默认写入策略,每秒写入一次 aof 文件,因此,最多可能会丢失 1s 的数据。 + + - 文件重写(rewrite) + + - 定期重写 AOF 文件,达到压缩的目的 + - 把 Redis 进程内的数据转化为写命令,同步到新的 AOF 文件,所以不会对旧的 AOF 文件进行任何读取、写入操作 + - 触发条件 + + - 手动触发 + + - 直接调用 bgrewriteaof 命令 + + - 自动触发 + + - 根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数,以及 aof_current_size 和 aof_base_size 状态确定触发时机 + +## 五、redis-cli 命令相关 + +### info XXX + +- info memory + + - 查看内存使用情况 + + - used_memory + + - Redis 分配器分配的内存总量(单位是字节) + + - used_memory_rss + + - Redis 进程占据操作系统的内存(单位是字节) + + - mem_fragmentation_ratio + + - 内存碎片比率,该值是 used_memory_rss / used_memory 的比值。1.03 是比较正常的值 + + - > 1 + + - 内存碎片多,内存浪费严重 + - 考虑重启 redis 服务,在内存中对数据进行重排,减少内存碎片 + + - < 1 + + - 内存不足,部分数据使用了虚拟内存,访问速度可能会变得很慢 + + - mem_allocator + + - Redis 使用的内存分配器,在编译时指定 + + - libc + - jemalloc(默认) + + - 在减小内存碎片方面做的相对比较好 + - jemalloc 在 64 位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当 Redis 存储数据时,会选择大小最合适的内存块进行存储。 + + - tcmalloc + +- info 命令可以显示 redis 服务器的许多信息 + + - 服务器基本信息 + + - info server + + - CPU + - 内存 + - 查看持久化相关状态 + + - info Persistence + + - aof_enabled + + - AOF 是否开启 + + - aof_last_rewrite_time_sec + + - 上次文件重写执行时间(s),用于发现文件重写是否耗时过长 + + - aof_last_bgrewrite_status + + - 上次 bgrewrite 执行结果 + + - aof_delayed_fsync + + - AOF 追加阻塞情况的统计 + + - rdb_last_bgsave_status + + - 上次 bgsave 执行结果 + + - rdb_last_bgsave_time_sec + + - 上次 bgsave 执行时间(s) + + - info stats + + - latest_fork_usec + + - 上次 fork 耗时 + + - 客户端连接信息 + +### type key[XXX] + +- 读取 RedisObject 的 type 字段获得对象 key 对应 Value 的类型 + +### object 相关命令 + +- object encoding key[XXX] + + - 查看对象 key 对应 Value 采用的编码方式 + +- object idletime key[XXX] + + - 显示当前 key 对应 Value 的空转时间(单位是秒)。空转时间就是该 Value 最后一次呗命令程序访问的时间.object idletime 命令不改变对象的 lru 值。 + +- object refcount key[XXX] + + - 查看 key 对应 value 的共享对象引用次数 + +### 发布与订阅 + +- SUBSCRIBE channel [channel …] +- UNSUBSCRIBE [channel [channel …]] +- PUBLISH channel message + +### 事务相关命令 + +- discard + + - 取消事务,放弃执行事务块内的所有命令。 + - 如果正在使用 WATCH 命令监视某个或某些 key,那么取消所有监视,等同于执行命令 UNWATCH + +### 线上 Redis 禁止使用 Keys 正则匹配操作 + +## 六、内存划分 + +### 数据 + +- 数据是最主要的部分 +- 占用的内存会统计在 used_memory 中 + +### 进程本身运行需要的内存 + +- Redis 主进程本身运行肯定需要占用内存,如代码、常量池等等 +- 这部分内存不是由 jemalloc 内存分配器分配,因此不会统计在 used_memory 中。 +- 除了主进程外,Redis 创建的子进程运行也会占用内存,如 Redis 执行 AOF、RDB + +### 缓冲内存 + +- 客户端缓冲区 + + - 存储客户端连接的输入输出缓冲 + +- 复制积压缓冲区 + + - 用于部分复制功能 + +- AOF 缓冲区 + + - 进行 AOF 重写时,保存最近的写入命令 + +- 内存由 jemalloc 内存分配器分配,因此会统计在 used_memory 中。 + +### 内存碎片 + +- Redis 在分配、回收物理内存过程中产生的 +- 内存碎片不会统计在 used_memory 中。 + +### 优化内存占用 + +- 利用 jemalloc(内存分配器)特性进行优化 +- 使用整型/长整型 +- 共享对象 + + - 可以减少对象的创建,节省内存空间 + +- 避免过度设计 +- 设置合理的数据回收策略 + +## 七、基础数据结构(key 都是字符串,value 对应着不同的数据结构) + +### string(字符串) + +- 缓存用户信息(JSON 序列化)、计数 +- 预分配冗余空间的形式设计,每次扩容 1M,最多 512M,类似于 Java 中的 ArrayList 的设计 + +### list(列表) + +- 常用于做异步队列 +- 相当于 Java 中的链表(LinkedList),插入删除块,索引慢,弹出最后一个元素的时候数据结构自动被删除。 +- ziplist(快速列表) + + - 列表元素较少的情况是使用一块连续的内存存储加速元素的访问 + - 元素量大的时候 ziplist 会与链表结合组成 quicklist,将 ziplist 使用双向指针串联起来使用 + +- 最多存储 2^32 - 1 个元素,可以充当数组、队列、栈使用 + +### hash(字典) + +- 类似 Java 中的 hashMap,采用数组加链表的形式 +- 当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。 +- 一般也用于存储用户信息,可以针对字段做自定义单独存储 +- 存储消耗相对于要高于字符串 + +### set(集合) + +- 相当于 Java 里面的 HashSet,键值对无序但唯一 +- 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。 +- 常用于存储活动中奖的用户 ID +- 最多存储 2^32 - 1 个元素 + +### zset(有序列表) + +- 类似于 Java 中的 SortedSet 和 HashMap 的结合体 +- zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。 +- 常用于存储粉丝列表,比如 value 代表粉丝 ID,score 代表关注时间,从而可以按照时间排序 +- 也可用于存储学生成绩,value 代表学生 ID,score 代表考试成绩,从而按照成绩排名 +- 跳跃列表 + + - zset 的内部排序功能通过「跳跃列表实现」 + - 类似于层级制,或者你可以想象为这张思维导图的样子 + - 一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。 + +- 有序集合为每个元素设置一个分数(score)作为排序依据 +- 底层实现 + + - 有序集合对象的编码可以是 ziplist 或者 skiplist + - 使用 ziplist 编码的条件(同时满足) + + - 元素数量小于 128 个 + - 所有 member 的长度都小于 64 字节 + + - ziplist + + - 使用紧挨在一起的压缩列表节点来保存,第一个节点保存 member,第二个保存 score。 + - ziplist 内的集合元素按 score 从小到大排序,score 较小的排在表头位置。 + + - skiplist + + - 底层是一个命名为 zset 的结构体,而一个 zset 结构同时包含一个字典和一个跳跃表。 + - 跳跃表按 score 从小到大保存所有集合元素。 + - 字典则保存着从 member 到 score 的映射,这样就可以用 O(1) 的复杂度来查找 member 对应的 score 值。 + - 虽然同时使用两种结构,但它们会通过指针来共享相同元素的 member 和 score,因此不会浪费额外的内存。 + - 跳表(skip List)是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为 O(logN)。简单说来跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能,正是这个跳跃的功能,使得在查找元素时,跳表能够提供 O(logN) 的时间复杂度。 + +## 十四、如何排查 Redis 性能问题 + +### 业务服务器到 Redis 服务器之间的网络存在问题 + +### Redis 本身存在问题 + +- latency 命令测试 Redis 的基准性能 +- Redis 的慢日志(默认 20 毫秒),慢日志会记录对应的比较慢的执行命令以及对应的时间节点 +- 是否存在复杂的命令才做 +- 检查 bigkey + + - 如果一个 key 写入的 value 非常大,那么 Redis 在分配内存时就会比较耗时。同样的,当删除这个 key 时,释放内存也会比较耗时,这种类型的 key 一般称之为 bigkey。 + - 使用 bigkey 命令查询对应的 bigkey + +- 集中过期 + + - Redis 数据过期处理方式 + + - 被动过期 + + - 只有当访问某个 key 时,才判断这个 key 是否已过期,如果已过期,则从实例中删除 + + - 主动过期 + + - edis 内部维护了一个定时任务,默认每隔 100 毫秒(1秒10次)就会从全局的过期哈希表中随机取出 20 个 key,然后删除其中过期的 key,如果过期 key 的比例超过了 25%,则继续重复此过程,直到过期 key 的比例下降到 25% 以下,或者这次任务的执行耗时超过了 25 毫秒,才会退出循环 + - 这个主动过期 key 的定时任务,是在 Redis 主线程中执行的。 + - 如果在执行主动过期的过程中,出现了需要大量删除过期 key 的情况,那么此时应用程序在访问 Redis 时,必须要等待这个过期任务执行结束,Redis 才可以服务这个客户端请求。 + + - 此种情况下应用访问 Redis 延时变大 + + - 解决方案 + + - 集中过期 key 增加一个随机过期时间,把集中过期的时间打散,降低 Redis 清理过期 key 的压力 + - 开启 lazy-free 机制,当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程 + +- 实例内存达到上限 +- fork 耗时严重 + + - 当 Redis 开启了后台 RDB 和 AOF rewrite 后,在执行时,它们都需要主进程创建出一个子进程进行数据的持久化。 + - 主进程创建子进程,会调用操作系统提供的 fork 函数。fork 在执行过程中,主进程需要拷贝自己的内存页表给子进程,如果这个实例很大,那么这个拷贝的过程也会比较耗时。 + +- 开启内存大页 +- aof 刷盘机制不合理 +- 绑定CPU +- 使用Swap +- 碎片整理 +- 网络带宽过载 + +## 十三、缓存简介 + +### 缓存模式 + +- Cache-Aside + + - 读操作 + + - 程序接收数据查询的请求 + - 程序检查要查询的数据是否在缓存上 + + - 如果存在,从缓存上查询出来 + - 如果不存在,从数据库中检索数据并存入缓存中 + + - 程序返回要查询的数据 + + - 更新操作 + + - 使缓存失效 + + - 当请求需要更新数据库数据的时候,缓存中的值需要被删除掉,下次数据读取的时候会存进缓存 + + - 缓存更新 + + - 在数据库更新的时候被更新 + +- Read-Through + + - 直接从缓存中读数据,该场景下是缓存去决定从哪查询数据。 + +- Write-Through + + - 所有的写操作都经过缓存,每次我们向缓存中写数据的时候,缓存会把数据持久化到对应的数据库中去,且这两个操作都在一个事务中完成。 + +### 缓存击穿 + +- 概念:请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去。 +- 问题:黑客拿数据库中不存在的字段值进行大量的请求导致服务宕机 +- 解决方式 + + - 缓存控制,定期设置过期时间 + - 哈希过滤 + - 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试 + - 用异步更新策略,无论 key 是否取到值,都直接返回。value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。 + - 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。 + +### 缓存穿透 + +- 概念:大量的请求同时查询一个 key 时,此时这个 key 正好失效了,就会导致大量的请求都打到数据库上面去 +- 问题:造成数据库瞬间请求过载,压力陡增,服务宕机 +- 解决方式 + + - 请求线程加锁,或者数据库查询加锁 + - 数据返回记录之后作为缓存承载给其他请求 + +### 缓存雪崩 + +- 概念:缓存服务宕机,导致大量请求直接打进数据库造成服务宕机 +- 问题:数据库由于请求量过大而崩溃 +- 解决方式 + + - 缓存集群 + - 如果是微服务,可以设置网关限流 + - 使用缓存技术中的本地缓存(缓存在硬盘中) + - 设置 Redis 持久化,服务重启之后自动加载数据缓存到 Redis 中 + - 给缓存的失效时间,加上一个随机值,避免集体失效。 + +### 缓存预热 + +- 系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题 +- 缓存预热设置方案 + + - 直接写个缓存刷新页面,上线时手工操作下 + - 数据量不大,可以在项目启动的时候自动进行加载 + - 定时刷新缓存 + +### 缓存更新策略 + +- 除了 Redis 自有的缓存数据淘汰策略「数据淘汰策略(lru、随机、将要过期、不淘汰)」,还可以根据业务需求自定义缓存淘汰策略 + + - 定时去清理过期的缓存 + + - 维护大量缓存的 key 是比较麻烦的 + + - 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存 + + - 每次用户请求过来都要判断缓存失效,逻辑相对比较复杂 + +- 四种缓存更新策略 + + - 先更新缓存,再更新数据库(不推荐使用) + + - 很少的业务场景会去使用这种策略 + + - 先更新数据库,再更新缓存(不推荐使用) + + - 线程安全 + + - 同时有请求 A 和请求 B 进行更新操作,会出现线程 A 更新了数据库,线程 B 更新了数据库,线程 B 更新了缓存,线程 A 更新了缓存,可是按道理请求 A 更新缓存应该比请求 B 更新缓存早才对 + + - 业务场景 + + - 如果写多读少的业务场景,采用这种方案就会导致数据压根还没读到,缓存就被频繁的更新,浪费性能。 + - 如果写入数据库的值并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,是浪费性能的。这种情况下删除缓存更为合适。 + + - 先删除缓存,再更新数据库(会导致数据库与缓存不一致) + + - 如果不采用给缓存设置过期时间策略,出现不一致时缓存永远都是脏数据。 + + - 请求 A 进行更新操作 +请求 B 进行查询操作 +请求 A 进行写操作,删除缓存 +请求 B 查询发现缓存不存在 +请求 B 去数据库查询得到旧值 +请求 B 将旧值写入缓存 +请求 A 将新值写入数据库 + + - 解决方案 + + - 延时双删策略(将一定时间内的脏数据删除,该方案也适合 MySQL 读写分离架构下主从不及时产生脏数据缓存的情况) + + - 先淘汰缓存 + - 再写数据库 + - 延时一定时间 + - 再次淘汰缓存 + + - 产生脏数据的概率较大,查询得到旧数据,多数不建议这样做 + + - 先更新数据库,再删除缓存 + + - 应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。 + - 存在的并发问题(缓存脏数据) + + - 请求 A 做查询操作 +请求 B 做更新操作 +缓存刚好失效 +请求 A 查询数据库,得一个旧值 +请求 B 将新值写入数据库 +请求 B 删除缓存 +请求 A 将查到的旧值写入缓存 + + - 发生概率基本可以忽略,因为对于数据库来讲读快写慢 + + - 脏数据产生概率低,但是会出现一致性异步问题,但是问题不大,数据更新之后更新缓存即可。多数情况建议选择此种方式 + + - 如何解决后两种缓存脏数据的问题 + + - 设置缓存有效时间 + - 双删缓存第二次删除缓存失败的解决方案(重试机制) + + - 消息队列,将需要删除的 key 发送至消息队列 + - 订阅数据库的 binlog + +### 缓存降级 + +- 当访问量剧增、服务出现问题(响应时间慢、不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务 +- 最终目的是保证核心服务可用,即使是有损的 +- 可以参考日志级别进行降级预案 + +### 热点数据集中失效 + +- 概念:缓存都有失效时间,那么作为热点数据失效的瞬间也会有大量的请求直接涌入到数据库 +- 解决方式 + + - 不同的热点数据设置不同的失效时间 + - 互斥锁 + +## 十二、Redis 锁 + +### 客户端可以使用同步机制 + +### 服务器端 + +- setnx + +### 分布式锁 + +- 利用 setnx+expire 命令 + + - SETNX 是 SET IF NOT Exists 的缩写,即 setnx key value,将 key 设置为 value,当键不存在时,才能成功,若键存在,什么也不做,成功返回 1,失败返回 0 。 + - 因为 setnx + expire 命令过程中不具有原子性,所以不推荐直接使用 + - 可以优化为使用 Lua 脚本将两个命令封装为一个操作进行使用,不过依然不推荐 + +- 使用 set key value [EX seconds][PX milliseconds][NX|XX] 命令 (推荐) + + - EX seconds: 设定过期时间,单位为秒 + - PX milliseconds: 设定过期时间,单位为毫秒 + - NX: 仅当key不存在时设置值 + - XX: 仅当key存在时设置值 + +- Redlock算法 与 Redisson 实现 + + - 主要解决上一条锁在集群、多节点的情况下锁失效的问题 + - 获取当前时间 + - 发起请求,并设置超时时间,超时时间要小于锁的过期时间 + - 使用当前时间减掉开始获取锁的时间得到获取锁所使用的时间,这个时间要满足小于锁失败的时间并且实例当中超过一半的实例都获取到了锁才算成功 + - 如果锁获取失败,Redis 所有实例进行解锁,及时某些实例没有加锁成功 + +## 十一、数据淘汰策略(lru、随机、将要过期、不淘汰) + +### volatile-lru + +- 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 + +### volatile-ttl + +- 从已设置过期时间的数据集中挑选将要过期的数据淘汰 + +### volatile-random + +- 从已设置过期时间的数据集中任意选择数据淘汰 + +### allkeys-lru + +- 从数据集中挑选最近最少使用的数据淘汰 + +### allkeys-random + +- 从数据集中任意选择数据淘汰 + +### no-enviction + +- 禁止驱逐数据 + +## 十、数据模型 + +### dictEntry + +- 每个键值对都会有一个 dictEntry,里面存储了指向 Key 和 Value 的指针; +- next 指向下一个 dictEntry,与本 Key-Value 无关。 + +### Key + +- Key 并不是直接以字符串存储,而是存储在 SDS 结构中 +- SDS(Simple Dynamic String,简单动态字符串) + + - len + + - buf 已经使用的长度 + + - free + + - buf 未使用的长度 + + - buf[] + + - 字节数组,用来存储字符串 + - buf.length = free + len + 1 + +### redisObject + +- Value 既不是直接以字符串存储,也不是像 Key 一样直接存储在 SDS 中,而是存储在 redisObject 中 +- 不论 Value 是 5 种类型的哪一种,都是通过 redisObject 来存储的 +- redisObject 中的 type 字段指明了 Value 对象的类型,ptr 字段则指向对象所在的地址 +- 不过字符串对象虽然经过了 redisObject 的包装,但仍然需要通过 SDS 存储 +- 底层构成及细节 + + - type + + - 表示对象的类型,占4个 bit (比特) + + - encoding + + - 表示对象的内部编码,占4个 bit (比特) + - string + + - int(8 字节) + + - 使用整数值实现的字符串对象 + - 当 int 数据不再是整数,或大小超过了 long 的范围时,自动转化为 raw。 + + - embstr(<= 39字节) + + - 使用 embstr 编码的 SDS 实现的字符串对象 + - 只分配一次内存空间 + - 实现为「只读」 + + - 对 embstr 对象进行修改时,都会先转化为 raw 再进行修改,所以只要是修改 embstr 对象,修改后的对象一定是 raw 的,无论是否达到了 39 个字节 + + - raw(> 39 字节的字符串) + + - 使用 SDS 实现的字符串对象 + - 需要分配两次内存空间 + + - 为啥以 39 作为区分? + + - redisObject 的长度是 16 字节 + - sds 的长度是 9+ + - jemalloc 正好可以分配 64 字节的内存单元 + + - list + + - 压缩列表(ziplist) + + - 由一系列特殊编码的连续内存块组成的顺序型数据结构 + - ziplist 是一个双向链表,目的是为了提供存储效率 + - 用于存储字符串或者整数。当一个列表键或者哈希键值对包含少量的元素项,且每项要么是小整数型,要么就是长度比较短的字符串时,Redis 就会使用 ziplist 来做他们的底层实现。 + - 使用满足条件 + + - 列表中元素数量小于 512 个 + - 列表中所有字符串对象都不足 64 字节(便于统一分配每个节点的长度) + + - 双端链表(linkedlist) + + - 使用双端链表实现的列表对象 + + - hash + + - 压缩列表(ziplist) + + - 使用满足条件 + + - 哈希中元素数量小于 512 个 + - 哈希中所有键值对的键和值字符串长度都小于 64 字节 + + - ht + + - 使用字典实现的哈希对象 + + - set + + - intset + + - 使用整数集合实现的集合对象 + + - 当集合元素不大于设定值并且元素都是整数时,就会用 intset 作为 Set 的底层数据结构。 + - 特点 + + - 元素类型只能为数字 + - 元素有三种类型:int16_t、int32_t、int64_t + - 元素有序且不可重复 + - 和 SDS 一样,intset 也是内存连续的,就像数组一样 + + - 使用满足条件 + + - 集合中元素数量小于 512 个 + - 集合中所有元素都是整数值 + + - ht + + - 使用字典实现的集合对象 + + - zset + + - 压缩列表(ziplist) + + - 使用压缩列表实现的列表对象 + - 使用满足条件 + + - 有序集合中元素数量小于 128 个 + - 有序集合中所有成员长度都不足 64 字节 + + - skiplist + + - 可以进行二分查找的有序链表 + + - 由很多层结构组成,level是通过一定的概率随机产生的 + - 每一层都是一个有序的链表,默认是升序,也可以根据创建映射时所提供的Comparator进行排序 + - 最底层(Level 1)的链表包含所有元素 + - 如果一个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现 + - 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素 + + - 使用跳跃表和字典实现的有序集合对象 + + - 编码转换在 Redis 写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。 + + - lru + + - 对象最后一次被命令程序访问的时间 + + - refcount + + - 记录的是对象被引用的次数,类型为整型 + - 主要用于对象的引用计数和内存回收 + - 创建新对象时,refcount 初始化为 1;当有新程序使用该对象时,refcount 加 1;当对象不再被一个新程序使用时,refcount 减 1;当 refcount 变为 0 时,对象占用的内存会被释放。 + - 被多次使用的对象(refcount > 1),称为共享对象,仅支持整数值的字符串对象 + + - Redis 服务器在初始化时,会创建 10000个 字符串对象,值分别是 0~9999 的整数值;当 Redis 需要使用值为 0~9999 的字符串对象时,可以直接使用这些共享对象。 + + - ptr + + - 指针,指向具体的数据 + +- 一个 redisObject 对象的大小为16字节(byte) + +### 以后依靠 jemalloc(内存分配器)进行分配 + +## 九、集群方案 + +### Redis 高可用方案 + +- 持久化 + + - 见左侧「Redis 持久化」 + +- 主从复制(即读写分离) + + - 作用 + + - 数据冗余 + + - 实现数据的热备份,属于持久化之外的一种数据冗余方式 + + - 故障恢复 + + - 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 + - 也就是服务冗余的一种方式 + + - 负载均衡 + + - 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 + + - 高可用基石 + + - 哨兵模式和集群能够实施的基础 + + - 如何开启主从复制(只需要从「从节点」开启复制)? + + - 配置文件 + + - 在从服务器的配置文件中加入:slaveof + + - 启动命令 + + - redis-server启动命令后加入 --slaveof + + - 客户端命令 + + - 通过客户端执行命令:slaveof ,当前实例即为从节点 + + - 主从复制实现原理 + + - 连接建立阶段 + + - 在主从节点之间建立连接,为数据同步做好准备。 + + - 保存主节点信息 + - 建立 socket 连接 + - 发送 ping 命令 + - 身份验证 + - 发送从节点端口信息 + + - 数据同步阶段 + + - 从节点向主节点发送 psync 命令,开始同步。 + - 复制类型 + + - 全量复制 + + - 用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。 + + - 部分复制 + + - 用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。 + - 三个概念 + + - 复制偏移量 + + - 主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数 + + - 复制积压缓冲区 + + - 由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小 1MB + - 备份主节点最近发送给从节点的数据。 + - 无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。 + - bgsave 生成 RDB 文件、RDB 文件由主节点发往从节点、从节点清空老数据并载入 RDB 文件中的数据。 + + - 服务器运行 ID + + - 主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个 runid 保存起来 + - 当断线重连时,从节点会将这个 runid 发送给主节点;主节点根据 runid 判断能否进行部分复制: + + - psync 复制命令 + + - 从节点根据当前状态决定如何调用 psync + + - 如果没有执行过 slaveof 或者最近执行了 slaveof no one 命令,会向主节点发送「psync ? -1」命令,请求全量复制 + - 如果执行过 slaveof 命令,会发送「psync 」命令请求部分复制 + + - 主节点根据收到的 psync 命令以及当前服务器的状态决定执行全量复制还是部分复制 + + - 如果 Redis 版本低于 2.8,直接执行全量复制 + - 如果版本符合部分复制的要求,并且接收到从节点发来的 runid 与当前自身 runid 相同,复制挤压缓冲区也包含从节点发送过来的 offset,则进行部分复制 + - 如果 runid 不同或者 offset 已经在复制挤压缓冲区中不完整,则发起全量复制 + + - 命令传播阶段 + + - 主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。 + - 主从心跳机制 + + - PING + + - 每隔指定的时间,主节点会向从节点发送PING命令,这个 PING 命令的作用,主要是为了让从节点进行超时判断。 + + - REPLCONF ACK + + - 从节点会向主节点发送 REPLCONF ACK 命令,频率是每秒 1 次 + + - 实时监测主从节点网络状态 + - 检测命令丢失 + - 辅助保证从节点的数量和延迟 + + - 主从复制、读写分离的普遍问题 + + - 读写分离 + + - 延迟与不一致 + - 数据过期 + + - 惰性删除 + + - 服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。 + + - 定期删除 + + - 服务器执行定时任务删除过期数据 + + - Redis 3.2 版本以后,从节点在读取数据时,增加了对数据是否过期的判断 + + - 故障切换 + + - 主从复制 + + - 复制超时 + + - 主节点判断连接超时,其会释放相应从节点的连接,从而释放各种资源 + - 从节点判断连接超时,则可以及时重新建立连接,避免与主节点数据长期的不一致 + + - 复制中断 + + - 复制缓冲区溢出 + + - 故障恢复无法自动化 + - 写操作无法负载均衡 + - 存储能力受到单机的限制 + +- 哨兵模式 + + - 见下方「哨兵模式(Sentinel)」 + +- 集群 + +### 官方 redis-cluster 路由查询 + +- 优势及作用 + + - 所有功能都集成在 redis cluster 中,路由分片、拓扑信息的存储、探活都在 redis cluster 中实现;各实例间通过 gossip 通信;这样的好处是简单,依赖的组件少 + - 数据分区(最核心的功能) + + - 将数据分散到多个节点,突破了 Redis 单机内存大小的限制,存储容量大大增加 + - 每个主节点都可以对外提供读服务和写服务,提高集群的响应能力 + - 关于 Redis 分区参考上面「Redis 分区(配合集群使用)」 + + - 高可用 + +- 劣势:需要支持更大的规模时,由于使用 gossip 协议导致协议之间的通信消耗太大,redis cluster 不再合适; +- +- 服务器端分片技术 +- Redis 集群的键空间被分割为 16384 个槽(slot), 集群的最大节点数量也是 16384 个。 + +### twemproxy 代理、nutcracker + +- 原理:twemproxy 处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的redis服务器。 +- 客户端不直接访问 redis 服务器,而是通过 twemproxy 代理中间件间接访问。降低了客户端直连后端服务器的连接数量,并且支持服务器集群水平扩展。 +- 中间件做分片的技术 +- + + - + + - + +### codis + +- 特点 + + - 分片算法:基于 slot hash 桶; + - 分片实例之间相互独立,每组一个 master 实例和多个 slave; + - 路由信息存放到第三方存储组件,如 zookeeper 或 etcd + - 旁路组件探活 + +- 划分了 1024 个 slot, slots 信息在 proxy 层感知; redis 进程中维护本实例上的所有 ke y的一个 slot map; +- 图片可拖拽放大 + +### 哨兵模式(Sentinel) + +- 解决主节点故障恢复的自动化问题,进一步提高系统的高可用性。在主从复制的基础上解决了「自动化的故障恢复」这个问题 + + - 自动完成故障发现和故障转移并通知应用方,从而实现真正的高可用的分布式架构 。 + +- 原理:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器以及这些主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。 +- https://pic.imgdb.cn/item/624e95f4239250f7c5ab0941.png +至少需要 3 个实例,来保证自己的健壮性 + + - https://pic.imgdb.cn/item/624e95f4239250f7c5ab093d.png + + - https://pic.imgdb.cn/item/624e95f4239250f7c5ab093a.png + +- 功能及架构 + + - 哨兵功能 + + - 监控 + + - 不断地检查主节点和从节点是否运作正常 + + - 自动故障转移(核心功能) + + - 主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。 + + - 配置提供者 + + - 客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。 + + - 通知 + + - 哨兵可以将故障转移的结果发送给客户端。 + + - 架构 + + - 哨兵节点 + + - 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 redis 节点,不存储数据。 + + - 数据节点 + + - 主节点和从节点都是数据节点。 + +### 客户端分片,一致性哈希 + +- 关于一致性 Hash 算法,可以参考一下老四的这片文章《Java 十道由浅入深的笔面试题第四期》。http://www.glorze.com/1476.html + + - 构造一个 0 ~ 2^32-1 大小的环。 + + - 在 Redis 中是 16384 个 + + - 如果槽位为 65536,发送心跳信息的消息头达 8k,发送的心跳包过于庞大。 + - redis 的集群主节点数量基本不可能超过 1000 个。 + - 槽位越小,节点少的情况下,压缩率高 + + - 服务节点经过 hash 之后将自身存放到环中的下标中。 + - 客户端根据自身的某些数据 hash 之后也定位到这个环中。 + - 通过顺时针找到离他最近的一个节点,也就是这次路由的服务节点。 + - 考虑到服务节点的个数以及 hash 算法的问题导致环中的数据分布不均匀时引入了虚拟节点。 + +## 八、Redis 分区(配合集群使用) + +### 分区的概念 + +- 分区就是将数据分割到多个 Redis 实例中的一个过程,因此每个实例仅仅包含部分键(key) + +### 分区的优势 + +- 利用多台机器的内存构建一个更大数据库 +- 可以在多核和多计算机之间弹性扩展计算能力,也可以在多计算机和网络适配器之间弹性扩展网络带宽 + +### 分区的劣势 + +- 不支持多个键的操作(可以曲线、间接实现) +- 不支持多个键的事务 +- 不能使用单个大键对数据集进行分片 +- 数据的处理会变得复杂,比如必须处理多个 RDB 和 AOF 文件 +- 添加和删除节点也会变得复杂 + +### 分区基础、方式 + +- 范围分区 +- 哈希分区(取余) +- 一致性哈希 +- 带虚拟节点的一致性哈希分区(Redis 集群默认采用的方案) + +### 分区不同的实现方式 + +- 客户端分区 + + - 客户端直接选择正确节点读写指定键 + +- 代理辅助分区(Twemproxy) + + - 客户端通过 Redis 协议把请求发送给代理,而不是直接发送给真正的 Redis 实例服务器。 + - 代理根据请求根据配置分区策略发送到正确的 Redis 实例上,并返回给客户端。 + +- 查询路由 + + - 把一个请求发送给一个随机的实例,这时实例会把该查询转发给正确的节点。 + +### 分区的使用 + +- Redis 如果作为可伸缩缓存服务器来使用,那么用一致性哈希比较简单 +- Redis 被作为数据持久化服务器,需要提供节点和键值的固定映射,还有节点数目必须是固定的,不能改变。(使用集群解决问题) + diff --git "a/\346\225\260\346\215\256\345\272\223/Redis.xmind" "b/\346\225\260\346\215\256\345\272\223/Redis.xmind" index 8525422..f67877d 100644 Binary files "a/\346\225\260\346\215\256\345\272\223/Redis.xmind" and "b/\346\225\260\346\215\256\345\272\223/Redis.xmind" differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/LeetCode/LeetCode \346\240\207\347\255\276\345\210\206\347\261\273.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/LeetCode/LeetCode \346\240\207\347\255\276\345\210\206\347\261\273.md" new file mode 100644 index 0000000..8e00397 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/LeetCode/LeetCode \346\240\207\347\255\276\345\210\206\347\261\273.md" @@ -0,0 +1,386 @@ +# LeetCode 标签分类 + +## 队列 + +### 队列是一种与栈相反的 先进先出(first in - first out, FIFO)的数据结构,队列中元素只能从 后端(rear)入队(push),然后从 前端(front)端出队(pop) + +## 极小化最大 + +## 蓄水池抽样 + +## 几何 + +## Map + +## 数组 + +### 题目列表 + +- 1.两数之和 +- 48.旋转图像 +- 56.合并区间 +- 78.子集 + +### 矩阵转置 + +- 把 m×n 矩阵 A 的行换成同序数的列得到一个 n×m 矩阵,此矩阵叫做 A 的转置矩阵 + +## 哈希表 + +### 题目列表 + +- 3.无重复字符的最长子串 + +### 哈希表的底层就是数组; + +### 哈希函数; + +### 哈希冲突的解决办法 + +- 链接法 +- 开放地址法 +- 哈希表的扩容 + +## 链表 + +### 题目列表 + +- 2.两数相加 +- 23.合并K个排序链表 + +### 题目类型 + +- 链表的合并 + +## 数学 + +## 双指针 + +### 题目列表 + +- 15.三数之和 + +## 字符串 + +### 分类 + +- 子序列 + + - 不一定是连续的,原始字符串的一个子集 + +- 子串 + + - 一定是连续的,原始字符串的一个连续子集 + +- 回文 + + - 指正序(从左向右)和倒序(从右向左)读都是一样的整数或者字符。 + +### KMP 算法 + +- 算法流程 + + - 假设现在文本串 S 匹配到 i 位置,模式串 P 匹配到 j 位置 + - 如果 j = -1,或者当前字符匹配成功(即 S[i] == P[j]),都令 i++,j++,继续匹配下一个字符; + - 如果 j != -1,且当前字符匹配失败(即 S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串 P 相对于文本串 S 向右移动了 j - next [j] 位。 + - 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的 next 值,即移动的实际位数为:j - next[j],且此值大于等于 1。 + +- 步骤 + + - 寻找前缀后缀最长公共元素长度 + + - 寻找模式串中最大长度的相同前缀和后缀,找到了模式串中每个字符之前的前缀和后缀公共部分的最大长度后,便可基于此匹配。 + + - 求 next 数组 + + - 失配时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值 + + - 根据next数组进行匹配 + +### 题目列表 + +- 6.Z 字形变换 +- 44.通配符匹配 +- 459.重复的子字符串 + +## 二分查找 + +### 关键点 + +- 二分查找真正的坑在于到底要给 mid 加一还是减一,while 里到底用 <= 还是 <。 +- 分析二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。 +- int mid = left + (right - left) / 2; + +### 分类 [1,2,2,2,3] + +- 无边界二分查找 +- 左侧边界二分查找 +- 右侧边界二分查找 + +### 题目列表 + +- 4.寻找两个正序数组的中位数v +- 34.在排序数组中查找元素的第一个和最后一个位置 +- 74.搜索二维矩阵 +- 95.96.不同的二叉搜索树 +- 240.搜索二维矩阵 II +- 300.字符串最长上升子序列 +- 927.三等分 + +## 分治算法 + +### 分治就是不断缩小其规模,再不断合并扩大的过程 + +### 题目列表 + +- 23.合并K个排序链表 + +## 动态规划 + +### 动态规划问题的⼀般形式就是求最值 + +- 子序列 + + - 最长上升子序列(LISLIS):Longest Increasing Subsequence + - 最长连续序列(LCSLCS):Longest Consecutive Sequence + - 最长连续递增序列(LCISLCIS):Longest Continuous Increasing Subsequence + - 最长公共子序列(LCSLCS):Longest Common Subsequence + +- 最⼩编辑距离 +- 字符串动态规划 + + - 解决两个字符串的动态规划问题,⼀般都是⽤两个指针 i,j 分别指向两个字符串的最后,然后⼀步步往前⾛,缩⼩问题的规模。 + - 只要涉及⼦序列问题,⼗有⼋九都需要 +动态规划来解决 + +### 求解动态规划的核⼼问题是穷举、核心设计思想是数学归纳法 + + +- 存在「重叠⼦问题」,暴⼒穷举的话效率会极其低下 +- 优化穷举 + + - 「备忘录」 + + - 每次算出某个子问题的答案后不返回,先记到「备忘录」里再返回;每次遇到一个子问题先去「备忘录」里查一查,如果发现之前已经存在泽直接把答案拿出来。 + - 一般使用数组、哈希表(字典)实现备忘录 + - 自顶向下 + + - 「DP table」 + + - 动态规划⼀般都脱离了递归,⽽是由循环迭代完成计算。(自底向上) + - 把「备忘录」独⽴出来成为⼀张表,叫 DP table ,在这张表上完成「⾃底向上」的推算 + - 遍历 + + - 正向遍历 + - 反向遍历 + - 斜向遍历 + + - 动态规划问题⼀定会具备「最优⼦结构」,才能通过⼦问题的最值得到原问题的最值。 + - 只有列出正确的「状态转移⽅程」才能正确地穷举。 + +### 动态规划三要素 + +- 重叠子问题 +- 最优子结构 + + - 要符合「最优⼦结构」,⼦问题间必须互相独⽴。 + +- 状态转移方程(最难) + +### 解题思维框架 + +- 明确「状态」、明确「选择」 +- 定义 dp 数组/函数的含义 +- 明确 base case +- 代码模板 + + - + +### 题目列表 + +- 5.最长回文子串 +- 44.通配符匹配 +- 322.零钱兑换 +- 416.分割等和子集 +- 516.最长回文子序列 +- 518.零钱兑换 II +- 877.石子游戏 +- 1143.最长公共子序列 + +## 回溯算法 + +### 一种通过穷举所有可能情况来找到所有解的算法。 + +- 回溯算法关键在于:不合适就退回上一步 + +### 如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。 + +### 如果解决一个问题有多个步骤,每一个步骤有多种方法,题目又要我们找出所有的方法,可以使用回溯算法 + +### 回溯算法是在一棵树上的 深度优先遍历(因为要找所有的解,所以需要遍历) + +### 题目列表 + +- 22.括号生成 +- 44.通配符匹配 +- 77.组合 +- 78.子集 + +## 随机 + +## Rejection Sampling + +## Sliding Window + +## Ordered Map + +## Line Sweep + +## 记忆化 + +## 脑筋急转弯 + +## 递归 + +### 递归算法的时间复杂度 + +- ⼦问题个数乘以解决⼀个⼦问题需要的时间。 + +### 记忆递归 + +- 优化递归的标准方法。创建一个记忆的哈希映射。将所有已经检查的 (s, p) 保留在哈希映射中。这样,如果有任何重复的检查,只需查看哈希表,而不需再次进行计算。 + +## 二叉搜索树 + +## 线段树 + +## 树状数组 + +## 字典树 + +## 拓扑排序 + +## 设计 + +## 图 + +## 并查集 + +### 并查集,在一些有 N 个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。 + +### 一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。 + +### 合并 + +- 把两个不相交的集合合并为一个集合 + +### 查询 + +- 查询两个元素是否在同一个集合中 + +## 广度优先搜索(BFS) + +### 对树由上到下一层层遍历,需要一个数据结构来保存本层所有节点,方便进行下一层遍历 + +### 选择使用队列Queue,遍历本层节点的同时将下层节点加入队列尾部,且先进先出的特性,保证上层节点一定比下层节点先遍历。 + +## 深度优先搜索(DFS) + +### 遍历由根节点到叶子节点的所有路径 + +### 关联回溯算法 + +### 树的 dfs 从上往下开始执行的时候因为递归分为递和归两部分(也就是往下传递和往回走) + +## 树 + +### 做二叉树题目时候,第一想到的应该是用递归来解决。 + +### 二叉树的遍历 + +- 前序遍历、先序遍历 + + - 根左右 + +- 中序遍历 + + - 左根右 + +- 后序遍历 + + - 左右根 + +- 深度优先搜索(DFS) +- 宽度优先搜索(BFS),层序遍历 +- Morris(莫里斯)前中后遍历 + +### 题目列表 + +- 105.从前序与中序遍历序列构造二叉树 + +## 位运算 + +### 题目列表 + +- 78.子集 + +## 排序 + +### 分类 + +- 冒泡排序 +- 选择排序 +- 插入排序 +- 快速排序 +- 归并排序 +- 堆排序 +- 桶排序 +- 基数排序 +- 希尔排序 +- 计数排序 +- 位图排序 +- 耐心排序 + +### 题目列表 + +- 56.合并区间 +- 300.字符串最长上升子序列 +- 977.有序数组的平方 + +## 贪心算法 + +### 一种在当前时间做出最佳可能决策的算法; + +### 题目列表 + +- 12.整数转罗马数字 +- 44.通配符匹配 + +## 堆 + +### 题目列表 + +- 23.合并K个排序链表 + +### 最大堆、最小堆,在 Java 中 PriorityQueue 就是其的实现 + +### 关于 Comparator :如果 return 的大于 0 则前一位排在后一位的后面即升序,如果 return 的小于 0 则前一位排在后一位的的前面,为降序 + +## 栈 + +### 题目列表 + +- 20.有效的括号 +- 剑指 offer 9.双栈实现队列 + +### 栈是一种 后进先出(last in - first out, LIFO)的数据结构,栈内元素从顶端压入(push),从顶端弹出(pop)。一般我们用数组或者链表来实现栈 + +### 单调栈 + +- 比普通的栈多一个性质,即维护一个栈内元素单调。 +- 比如当前某个单调递减的栈的元素从栈底到栈顶分别是:[10, 9, 8, 3, 2],如果要入栈元素5,需要把栈顶元素pop出去,直到满足单调递减为止,即先变成[10, 9, 8],再入栈5,就是[10, 9, 8, 5]。 + +### 在表示问题的递归结构时,栈数据结构可以派上用场。 + +### 双栈可实现列表倒序 + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/LeetCode/LeetCode \346\240\207\347\255\276\345\210\206\347\261\273.xmind" "b/\346\225\260\346\215\256\347\273\223\346\236\204/LeetCode/LeetCode \346\240\207\347\255\276\345\210\206\347\261\273.xmind" new file mode 100644 index 0000000..4ab6e65 Binary files /dev/null and "b/\346\225\260\346\215\256\347\273\223\346\236\204/LeetCode/LeetCode \346\240\207\347\255\276\345\210\206\347\261\273.xmind" differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.xmind" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.xmind" index e1cd978..73f12cf 100644 Binary files "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.xmind" and "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.xmind" differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204\343\200\201\347\256\227\346\263\225.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204\343\200\201\347\256\227\346\263\225.md" new file mode 100644 index 0000000..e67ba2f --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204\343\200\201\347\256\227\346\263\225.md" @@ -0,0 +1,570 @@ +# 数据结构、算法 + +## 算法概述 + +## 数据结构基础 + +### 数组(Array) + +- 定义 + + - 有限个相同类型的变量所组成的有序集合 + +- 特点 + + - 顺序存储,在内存中占用了连续完整的存储空间 + - 有限,并且数组的实际元素有可能小于数组的长度 + - 元素类型相同 + +- 基本操作 + + - 增 + + - 尾部插入 + + - 直接放到尾部即可(在存储空间还有空余的情形),也相当于一个更新操作。 + + - 中间插入 + + - 先把插入位置及后面的元素向后移动,再在对应位置插入要插入的元素。 + + - 超范围插入 + + - 数组扩容 + + - 时间复杂度:O(n) + + - 尾部插入或者中间插入 + + - 时间复杂度:O(n) + + - 删 + + - 中间删除的话,删除元素后面的元素需要向前移动 + - 时间复杂度:O(n) + - 投机取巧的方式:将数组最后元素赋值给要被删除的元素,再讲最后一个元素删除,这样会改变数组的顺序,但是删除的时间复杂度变成 O(n)。 + + - 改 + + - 利用数组下标直接更新某个元素的值 + - 时间复杂度:O(1) + + - 查 + + - 下标读取,从 0 开始计数,称为「随机读取」 + - 时间复杂度:O(1) + +- 适用场景,与链表的区别 + + - 快速定位元素 + - 读多写少 + +### 链表(Linked List) + +- 定义 + + - 物理上非连续、非顺序的数据结构,由若干节点(Node)组成。 + - 节点(单向) + + - 存放数据的变量 data + - 指向下一个节点的指针 next + - 第一个节点称为「头节点」 + - 最后一个节点称为「尾节点」 + +- 特点 + + - 随机存储,见缝插针,每一个节点分布在内存的不同位置。 + - 无限,只要内存足够,不考虑扩容问题 + +- 单向链表 + + - 增 + + - 尾部插入 + + - 最后一个节点的 next 指针指向新插入的节点 + + - 头部插入 + + - 把新节点的 next 指针指向原先的头节点 + - 把新节点变成链表的头节点 + + - 中间插入 + + - 把新节点的 next 指针指向插入位置的节点 + - 插入位置的前置节点的 next 指针指向新插入的节点 + + - 时间复杂度:O(1) + + - 删 + + - 尾部删除 + + - 把倒数第二个节点的 next 指针指向 NULL + + - 头部删除 + + - 第二个节点设置为头节点 + + - 中间删除 + + - 删除节点的前置节点的 next 指针指向删除节点的下一个节点 + + - 时间复杂度:O(1) + + - 改 + + - 直接替换节点 data 的值即可 + - 时间复杂度:O(1) + + - 查(逐一查找) + + - 指针定位头节点 + - 根据头节点 next 指针找到第二个节点 + - 根据第二个节点的 next 指针循环的找下去,直到找到。 + - 时间复杂度:O(n) + +- 双向链表 + + - 相别于单向链表,节点多了一个指向前置节点的 prev 指针 + +- 适用场景,与数组的区别 + + - 灵活地进行插入、删除操作 + - 写多读少 + +### 栈和队列 + +### 散列表 + +## 树 + +### 树的一些基本概念 + +- 定义 + + - n 个节点的有限集,有且仅有一个特定的称为根的节点。当 n > 1 时,其余节点可分为 m 个互不相交的有限集,每一个集合又是一个树,称为根的子树。 + +- 树的路径 + + - 概念 + + - 从一个节点到另一个节点所经过的所有节点就是两个节点直接的路径 + + - 路径长度 + + - 从一个节点到另一个节点所经过的「边」的数量 + + - 节点的带权路径长度 + + - 树的根节点到该节点的路径长度 × 该节点的权重值 + + - 树的带权路径长度 + + - 所有叶子节点的带权路径长度之和,简称「WPL」 + +### 二叉树 + +- 物理存储方式 + + - 链式存储 + + - 存储数据的 data 变量 + - 指向左孩子的 left 指针 + - 指向右孩子的 right 指针 + + - 数组 + + - 按照层级顺序吧二叉树的节点放到数组中对应的位置上。如果某个节点的左孩子或者右孩子为空,数组对应的位置也留空 + - 父节点的下标如果是 P + + - 左孩子节点下标:2 * P + 1 + - 右孩子节点下标:2 * P + 2 + + - 二叉堆使用数组形式存储完全二叉树 + +- 二叉树的遍历 + + - 深度优先遍历 + + - 前序遍历 + + - 输出顺序是「根节点、左子树、右子树」 + + - 中序遍历 + + - 首先访问根节点的左孩子,如果左孩子还拥有左孩子,继续深入访问下去,知道找不到再有左孩子节点,输出该节点 + - 输出顺序是「左子树、父节点、右子树」 + + - 后序遍历 + + - 输出顺序是「左子树、右子树、根节点」 + + - 广度优先遍历 + + - 层序遍历 + +- 满二叉树 + + - 一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上 + +- 完全二叉树 + + - 对一个有 N 个节点的二叉树,按照层级顺序编号,则所有节点的编号为从 1 到 N。如果这个数所有 节点和同样深度的满二叉树的编号为从 1 到 n 的节点位置相同,则这个二叉树为完全二叉树 + +- 哈夫曼树、最优二叉树 + + - 叶子节点和权重确定的情况下,带权路径长度最小的二叉树 + - 构建步骤 + + - 构建森林 + + - 把每个叶子节点当做独立的只有根节点的树,形成一个森林,按照权重从小到大按照队列展示 + + - 选择权重最小的两个节点,生成新的根节点(权重相加) + - 删除已构建的节点,将新的根节点加入到队列 + - 再次选择两个权重最小的节点生成新的父节点 + - 重复依照上述进行构建,最后去除掉两两节点相加的父节点权重节点,就是一颗哈夫曼树 + +- 二叉树的应用 + + - 二叉查找树 + + - 定义及限制条件 + + - 如果左子树不为空,则左子树上所有节点的值均小于根节点的值 + - 如果右子树不为空,则右子树上所有节点的值均大于根节点的值 + - 左、右子树也都是二叉查找树 + + - 自平衡 + + - 红黑树 + + - 解决二叉查找树多次插入导致的不平衡 + - 特点 + + - 节点是红色或黑色。 + - 根节点是黑色。 + - 每个叶子节点都是黑色的空节点(NIL节点)。 + - 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) + - 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。 + + - 调整 + + - 变色 + - 旋转 + + - 左旋转 + - 右旋转 + + - AVL 树 + - 树堆 + + - 二叉排序树 + +### 二叉堆 + +- 最大堆 + + - 任何一个父节点的值都大于等于左右节点的值 + - 最大堆的堆顶是整个堆中的最大元素 + +- 最小堆 + + - 任何一个父节点的值小于等于左右孩子节点的值 + - 最小堆的堆顶是整个堆中的最小元素 + +- 二叉堆的自我调整 + + - 插入节点 + + - 完全二叉树的最后一个位置插入,在逐层比较 + + - 删除节点 + + - 删除处于堆顶的节点 + - 完全二叉树的最后一个节点补位到堆顶 + + - 构建二叉堆 + + - 从最后一个非叶子节点轮询调整 + +- 优先队列 + + - 最大优先队列 + + - 无论入队顺序如何,都是当前最大的元素优先出队 + + - 最小优先队列 + + - 无论入队顺序如何,都是当前最小的元素优先出队 + +## 算法的实际应用 + +## 面试中的算法 + +## 排序算法 + +## 图 + +### 定义 + +- 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G (V, E),其中,G 表示一个图,V 是图 G 中顶点的集合,E 是图 G 中边的集合。 + + - 数据元素叫做「顶点」 + - 在图结构中,不允许没有顶点 + - 任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以为空。 + +### 各种图定义 + +- 无向边 + + - 顶点 Vi 到 Vj 之间的边没有方向,用无序偶对 (Vi, Vj) 来表示 + +- 无向图 + + - 图中任意两个顶点之间的边都是无向边 + +- 有向边 + + - 顶点 Vi 到 Vj 之间的边有方向,也称为弧,用有序偶对 表示,Vi 称为弧尾(Tail),Vj 称为弧头(Head)。 + +- 有向图 + + - 图中任意两个顶点之间的边都是有向边 + +- 简单图 + + - 不存在顶点到其自身的边,且同一条边不重复出现 + +- 无向完全图 + + - 在无向图中,任意两个顶点之间都存在边 + + - n 个顶点的无向完全图有 n * (n - 1) / 2 条边 + +- 有向完全图 + + - 在有向图中,任意两个顶点之间都存在方向互为相反的两条弧 + + - n 个顶点的无向完全图有 n * (n - 1) 条边 + +- 稀疏图、稠密图 + + - 相对而言,以边的数量为目的 + +- 权 + + - 有些图的边或弧具有与它相关的数字,可以表示从一个顶点到另一个顶点的距离或耗费 + +- 网 + + - 带权的图 + +- 子图 + + - 假设有两个图 G = (V, {E}) 和 G' = (V', {E'}),如果 V' 是 V 的子集且 E' 是 E 的子集,那么 G' 就是 G 的子图 + +### 图的顶点与边 + +- 互为邻接点 + + - 对于无向图 + +- 顶点的度 + + - 与当前顶点相关联的边的数目,记为 TD + - 对于无向图,边数 = 给顶点度数和的一半 + +- 对于有向图,邻接到顶点或者叫做邻接自顶点 +- 有向图的入度、出度 + + - 以顶点 V 为头的弧的数目叫入度(ID) + - 以 V 为尾的弧的数目称为 V 的出度(OD) + - 对于有向图,顶点 V 的度为 TD = ID + OD + - 对于有向图,边数 = ID = OD + +- 顶点序列 + + - 对于无向图,从顶点 V 到顶点 V' 的路径 + - 对于有向图,要满足 是 E 的子集 + - 路径长度 + + - 路径上的边或弧的数目 + +- 回路或环 + + - 第一个顶点到最后一个顶点相同的路径 + - 简单路径、简单环 + + - 除了第一个顶点he最后一个顶点之外,其余顶点不重复出现 + +### 连通图相关术语 + +- 连通图 + + - 对于无向图,图中任意两个顶点 Vi,Vj,Vi 和 Vj 都是连通的,就是连通图 + +- 连通分量 + + - 无向图中的极大连通子图 + + - 要是子图 + - 子图要是连通的 + - 连通子徒含有极大顶点数 + - 具有极大顶点数的连通子图包含依附于这些顶点的所有边 + +- 强连通图 + + - 对于有向图,图中任意两个顶点 Vi,Vj,Vi 到 Vj 和 Vj 到 Vi 都存在路径,就是强连通图 + +- 强连通分量 + + - 有向图中的极大强连通子图 + +- 生成树 + + - 一个极小的连通子图,他含有图中全部的 n 个顶点,但只有足以构成一棵树的 n - 1 条边。 + +- 有向树 + + - 恰好有一个顶点的入度为 0,其余顶点的入度均为 1。 + +### 图的抽象数据类型 + +- Data + + - 顶点的有穷非空集合和边的集合 + +- Operation + + - CreateGraph + + - 按照顶点集 V 和边弧集 VR 的定义构造图 G + + - DestroyGraph + + - 图 G 存在则销毁 + + - LocateVex + + - 若图 G 中存在顶点 u,则返回图中的位置 + + - GetVex + + - 返回图 G 中顶点 V 的值 + + - PutVex + + - 将图 G 中顶点 v 赋值 value + + - FirstAdjVex + + - 返回顶点 v 的一个邻接顶点,若顶点在 G 中无邻接顶点返回空 + + - NextAdjVex + + - 返回顶点 v 相对于顶点 w 的下一个邻接点,若 w 是 v 的最后一个邻接点则返回「空」 + + - InsertVex + + - 在图 G 中增添新顶点 v 及其相关的弧 + + - DeleteVex + + - 删除图 G 中顶点 v 及其相关的弧 + + - InsertArc + + - 在图 G 中增添弧 ,若 G 是无向图,还需要增添对称弧 + + - DeleteArc + + - 在图 G 中删除弧 ,若 G 是无向图,则还删除对称弧 。 + + - DFSTraverse + + - 对图 G 中进行深度优先遍历,在遍历过程对每个顶点调用 + + - HFSTraverse + + - 对图 G 中进行广度优先遍历,在遍历过程对每个顶点调用 + +### 图的存储结构 + +- 五种不同的存储结构 + + - 邻接矩阵 + + - 两个数组来表示图 + + - 一维数组存储图中顶点信息 + - 二维数组(称为邻接矩阵)存储图中的边或弧的信息 + + - 邻接表 + + - 数组与链表相结合的存储方法 + + - 十字链表 + + - 对于有向图,把邻接表与逆邻接表结合起来 + + - 邻接多重表 + - 边集数组 + + - 两个一维数组 + + - 一个存储顶点的信息 + - 一个存储边的信息,每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(wight)组成 + +### 图的遍历 + +- 定义 + + - 图中某一顶点触发访遍图中其余顶点,且使没一个顶点仅被访问一次 + +- 深度优先遍历、深度优先搜索(DFS) + + - 树的前序遍历 + +- 广度优先遍历、广度优先搜索(BFS) + + - 树的层序遍历 + +### 最小生成树 + +- 构造连通网的最小代价生成树称为最小生成树 +- 普里姆(Prim)算法 +- 克鲁斯卡尔(Kruskal)算法 + +### 最短路径 + +- 定义 + + - 对于非网图,指两顶点之间经过的边数最少的路径 + - 对于网图,指两顶点之间经过的边上权值之和最少的路径,路径上的第一个顶点是源点,最后一个顶点是终点 + +- 地杰斯特拉(Dijkstra)算法 +- 弗洛伊德(Floyd)算法 + +### 拓扑排序 + +- AOV 网 + + - 在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系 + +- 拓扑序列 +- 定义 + + - 对一个有向图构造拓扑序列的过程 + +- 拓扑排序算法 + +### 关键路径 + +- AOE 网 + + - 在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间 + +- 路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动 + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\2254.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\2254.md" new file mode 100644 index 0000000..f6095a1 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\2254.md" @@ -0,0 +1,95 @@ +# 算法4 + +## 第一章:基础 + +### 基础编程模型 + +### 数据与抽象 + +## 排序 + +### 排序就是将一组对象按照某种逻辑顺序重新排列的过程 + +### 初级排序算法 + +- 游戏规则 +- 选择排序 + + - 首先找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个最小就是自己和自己交换)。再次,在剩下的元素中找到最小的元素,将它和数组的第二个元素交换位置。如此往复,直到将整个数组排序。 + - 特点 + + - 运行时间和输入无关 + - 数据移动是最少的 + +- 插入排序 + + - 为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。 + - 部分有序 + + - 如果数组中倒置的数量小于数组大小的某个倍数,那么我们说这个数组是部分有序的 + + - 数组中每个元素距离它的最终位置都不远 + - 一个有序的大数组接一个小数组 + - 数组中只有几个元素的位置不正确 + - 插入排序对于部分有序的数组十分搞笑,也很适合小规模数组 + +- 希尔排序 + + - 基于插入排序的快速排序算法 + + - 对于大规模乱序数组插入排序很慢,因为他只会交换相邻的元素 + - 希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以便于对数组的局部进行排序,并且最终用插入排序将局部有序的数组排序 + + - 思想 + + - 使数组中任意间隔为 h 的元素都是有序的,称为 h 有序数组。 + - 一个 h 有序数组就是 h 个互相独立的有序数组编织在一起组成的一个数组 + + - 实现 + + - 对于每个 h,用插入排序将 h 个子数组独立地排序。 + - 在插入排序的代码中将移动元素的距离由 1 改为 h 即可,转化为一个类似于插入排序但使用不同增量的过程。 + +### 归并排序 + +- 递归式地将数组分成两半分别排序,然后将结果归并起来 + + - 原地归并的抽象方法 + + - 将两个不同的有序数组归并到第三个数组中 + - 左半边用尽,取右边的元素 + - 右半边用尽,取左边的元素 + - 右半边的当前元素小于左半边的当前元素,取右半边的元素 + - 右半边的当前元素大于等于左半边的当前元素,取左半边的元素 + + - 自顶向下的归并排序 + - 自底向上的归并排序 + + - 适合链表排序 + +### 快速排序 + +- 原地排序,只需要一个很小的辅助栈 + + - 将一个数组分成两个子数组,将两部分独立地排序。当两个子数组都有序时整个数组也就自然有序了。 + +### 优先队列 + +- 初级实现 + + - 无序数组 + - 有序数组 + - 链表 + +- 堆 + + - 当一颗二叉树的每个节点都大于等于它的两个子节点时,它被称为堆有序(最大堆) + - 堆实现 + + - 向上调整 + - 向下调整 + +## 查找 + +### 符号表(就是 Java 中的 map 键值对) + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\2254.xmind" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\2254.xmind" new file mode 100644 index 0000000..264f07d Binary files /dev/null and "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\2254.xmind" differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225\345\257\274\350\256\272.xmind" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225\345\257\274\350\256\272.xmind" new file mode 100644 index 0000000..b44bf70 Binary files /dev/null and "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\256\227\346\263\225\345\257\274\350\256\272.xmind" differ diff --git "a/\346\236\266\346\236\204/DDD.md" "b/\346\236\266\346\236\204/DDD.md" new file mode 100644 index 0000000..cfa44dc --- /dev/null +++ "b/\346\236\266\346\236\204/DDD.md" @@ -0,0 +1,148 @@ +# DDD + +## 第一部分 运用领域模型 + +### 模型的选择 + +- 模型和设计的核心互相影响。 +- 模型是团队所有成员使用的通用语言的中枢。 +- 模型是浓缩的知识。 + +### 软件的核心 + +- 为用户解决领域相关的问题的能力。 + +### 第一章 消化知识 + +- 有效建模的要素 + + - 模型和实现的绑定。 + - 建立了一种基于模型的语言。 + - 开发一个蕴含丰富知识的模型。 + - 提炼模型。 + - 头脑风暴和实验。 + +- 明确设计的优点 + + - 为了实现更明确的设计,程序员和其他各位相关人员都必须理解超订的本质,明白它是一个明确且重要的业务规则,而不只是一个不起眼的计算。 + - 程序员可以向业务专家展示技术工件,甚至是代码,但应该是领域专家(在程序员指导下)可以理解的,以便形成反馈闭环。 + +### 第二章 交流与语言的使用 + +- 2.1 模式:UBIQUITOUS LANGUAGE +- 2.2 「大声地」建模 +- 2.3 一个团队,一种语言 +- 2.4 文档和图 + + - 书面设计文档 + - 完全依赖可执行代码的情况 + +- 2.5 解释性模型 + +### 第三章 绑定模型和实现 + +- 3.1 模式:MODEL-DRIVEN DESIGN(模型驱动设计) + + - 基本要素 + + - 模型要支持有效的实现 + - 抽象出关键的领域知 + +- 3.2 建模范式和工具支持 +- 3.3 揭示主旨:为什么模型对用户至关重要 +- 3.4 模式:HANDS-ON MODELER(亲身实践的建模者) + +## 第二部分 模型驱动设计的构造块 + +### 第四章 分离领域 + +- 4.1 模式:LAYERED ARCHITECTURE + + - 用户界面层(表示层) + - 应用层 + - 领域层(模型层) + - 基础设施层 + +### 第五章 软件中所表示的模型 + +- 3 种模型元素模式 + + - ENTITY + - VALUE OBJECT + - SERVICE + +- 5.1 关联 + + - 规定一个遍历方向 + - 添加一个限定符,以便有效地减少多重关联 + - 消除不必要的关联 + +- 5.2 模式:ENTITY(又称为REFERENCE OBJECT) + + - 主要由标识定义的对象被称作 ENTITY + - ENTITY建模 + - 设计标识操作 + +- 5.3 模式:VALUE OBJECT + + - 用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象) + +- 5.4 模式:SERVICE + + - 特征 + + - 与领域概念相关的操作不是ENTITY或VALUE OBJECT的一个自然组成部分。 + - 接口是根据领域模型的其他元素定义的。 + - 操作是无状态的。 + +- 5.5 模式:MODULE(也称为PACKAGE) + +### 第六章 领域对象的生命周期 + +### 第七章 使用语言:一个扩展的示例 + +## 杂记 + +### 贫血模型与充血模型 + +- 贫血模型 + + - 领域对象的作用很简单,只有所有属性的get/set方式,以及少量简单的属性值转换,不包含任何业务逻辑,不关系对象持久化,只是用来做为数据对象的承载和传递的介质。 + - 真正的业务逻辑则由领域服务负责实现,此服务引入持久化仓库,在业务逻辑完成之后持久化到仓库中,并在此可以发布领域事件 + - 优点 + + - 结构简单,职责单一,相互隔离性好,使用单例模型提高运行性能 + + - 缺点 + + - 对象状态与行为分离,不能直观地描述领域对象。 + - 行为的设计主要考虑参数的输入和输出而非行为本身,不太具有面向对象设计的思考方式。 + - 行为间关联性较小,更像是面向过程式的方法,可复用性也较小。 + + - SpringBoot 采用单例模式,尽量不手动创建对象,对象无状态化,故较推荐使用贫血模型 + +- 充血模型 + + - 领域对象作用此领域相关行为,包含此领域相关的业务逻辑,同时也包含对领域对象的持久化操作。 + - 优点 + + - 对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高,更符合面向对象设计思想 + + - 缺点 + + - 对象属性中掺杂持久化仓库,不够纯粹,持久化操作是否属于业务逻辑有待求证。 + - 由于持久化仅需暴露接口,对业务逻辑与持久化操作的耦合度有一定降低。 + +- 充血模型2 + + - 了解决业务逻辑不纯粹问题,也有将持久化操作移出业务逻辑的作法。 + - 优点 + + - 保持了业务逻辑的纯粹性,去掉了持久化的入侵 + + - 缺点 + + - 降低了领域服务的自治性,破坏了行为逻辑的完整性,部分逻辑混入了应用层,尤其是领域事件的发布 + + - 前两种方式的折中,充分地做到了解耦,但也牺牲了部分内聚 + diff --git "a/\346\236\266\346\236\204/DDD.xmind" "b/\346\236\266\346\236\204/DDD.xmind" new file mode 100644 index 0000000..0662122 Binary files /dev/null and "b/\346\236\266\346\236\204/DDD.xmind" differ diff --git "a/\346\236\266\346\236\204/\346\267\261\345\205\245\346\265\205\345\207\272Istio\357\274\232Service Mesh\345\277\253\351\200\237\345\205\245\351\227\250\344\270\216\345\256\236\350\267\265 .xmind" "b/\346\236\266\346\236\204/\346\267\261\345\205\245\346\265\205\345\207\272Istio\357\274\232Service Mesh\345\277\253\351\200\237\345\205\245\351\227\250\344\270\216\345\256\236\350\267\265 .xmind" new file mode 100644 index 0000000..e080a79 Binary files /dev/null and "b/\346\236\266\346\236\204/\346\267\261\345\205\245\346\265\205\345\207\272Istio\357\274\232Service Mesh\345\277\253\351\200\237\345\205\245\351\227\250\344\270\216\345\256\236\350\267\265 .xmind" differ diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/SOA.xmind" "b/\350\256\276\350\256\241\346\250\241\345\274\217/SOA.xmind" new file mode 100644 index 0000000..3b68ac6 Binary files /dev/null and "b/\350\256\276\350\256\241\346\250\241\345\274\217/SOA.xmind" differ diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\345\256\236\347\216\260\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241.xmind" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\345\256\236\347\216\260\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241.xmind" new file mode 100644 index 0000000..13ca1ba Binary files /dev/null and "b/\350\256\276\350\256\241\346\250\241\345\274\217/\345\256\236\347\216\260\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241.xmind" differ diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 0000000..a6bbeef --- /dev/null +++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,537 @@ +# 设计模式 + +## 六个创建型模式 + +### 简单工厂模式-Simple Factory Pattern + +- 定义 + + - 定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。 + - 因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式 + +- 角色 + + - Factory(工厂角色) + + - 工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑; + - 工厂类可以被外界直接调用,创建所需的产品对象; + - 工厂类中提供了静态的工厂方法 factoryMethod(),它的返回类型为抽象产品类型Product。 + + - Product(抽象产品角色) + + - 它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法 + - 提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。 + + - Concreateproduct(具体产品角色) + + - 它是简单工厂模式的创建目标,所有被创建的对象都充 + - 当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在 + - 抽象产品中声明的抽象方法。 + +- 优缺点 + + - 优点 + + - 对象创建和使用的分离 + + - 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅「消费」产品 + + - 客户端不需要知道具体产品的类名 + + - 只需要知道具体产品类所对应的参数即可 + + - 根据「开放-封闭」原则,具体产品类的参数可以通过配置文件实现动态传参 + + - 缺点 + + - 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。 + - 势必会增加系统中类的个数 + - 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑 + - 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构 + +- 适用场景 + + - 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。 + - 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。 + +### 工厂方法模式-Factory Method Pattern + +- 定义 + + - 定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。 + - 工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。 + +- 结构及角色 + + - Product(抽象产品) + + - 定义产品或者业务行为的接口,是工厂方法模式所创建对象的父类型。 + + - ConcreteProduct(具体产品) + + - 也就是抽象产品或者业务行为的具体实现,不过这种具体的产品类型实例要由对应的具体工厂来创建。 + + - Factory(抽象工厂) + + - 抽象工厂中声明的就是工厂方法,可以理解为返回的是上面的抽象产品,抽象工厂是工厂方法模式的核心,所有创建对象的具体工厂都必须实现该抽象工厂。 + + - ConcreteFactory(具体工厂) + + - 抽象工厂的具体实现,实现抽象工厂中定义的工厂方法 + +- 优势 + + - 用户只需要关心产品对应的工厂,不需要关心产品的创建细节,甚至不需要知道具体的产品名称。 + - 符合面向对象编程中的多态定义 + - 完全符合「开放-封闭原则」 + +- 劣势 + + - 新增产品的时候还需要增加对应的工厂类,对于大型系统设计,类的数量指数级增加 + - 多层抽象固有的理解难度。 + +- 适用场景 + + - 客户端不知道他所需要的对象的类。 + - 利用面向对象和里氏代换原则,程序运行时,子类对象可以覆盖父类对象,从而对系统进行扩展。 + +### 抽象工厂模式-Abstract Factory Pattern + +### 单例模式-Singleton Pattern + +保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。 + +- 懒汉式单例 + + 顾名思义,就像你写代码的时候一定写过类似的「lazy=true」的代码,什么意思呢?就是这哥们比较懒,你让我干活,我才起床穿衣服给你干活,否则我猫着不出来活动。这种要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例。 + + - 第一次使用时创建实例 + - 无须一直占用系统资源 + - 必须考虑多线程问题 + +- 饿汉式单例 + + 顾名思义,就是它比较饥饿,在类初始化的时候你就杀愣的给人家的实例创建出来供别人使用。这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例。 + + - 类加载的时候即被创建 + - 无须考虑多线程,确保唯一性 + - 占用系统资源 + +- 并发条件下的懒汉式单例 + + - volatile关键字修饰静态变量 + + 被 volatile 修饰的成员变量可以确保多个线程都能够正确处理。 + + - 双重锁定 + + 如果 instance 为 null 并且这个有两个线程在调用 getInstance() 方法,他们都通过了第一重检查,然后因为锁的原因只能进去一个线程,另外一个被阻塞。如果这个时候没有第二重检查,那么第一个线程创建了实例,他出去的时候第二个线程进来还能创建新的实例,单例变成双例,那不扯淡呢吗?所以双重锁定必须加。 + +- 按需初始化单例(极致完美) + + 按需初始化(Initialization on Demand Holder,简称 IoDH)的技术,该技术解决了饿汉式单例不能实现延迟加载,解决了懒汉式单例因为线程安全控制带来的性能和代码繁琐的诟病! + + - 声明静态内部类初始化静态变量 + + 内部类中定义了一个 static 类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance() 方法没有任何线程锁定,因此其性能不会造成任何影响。 + + - 保证单例不被反射破坏 + + - 在私有构造中增加 instance 判断标识 + + - 保证序列化下单例不被破坏 + + - 在单例类中增加 readResolve() 方法并返回 instance 即可 + - readResolve() 方法与 writeReplace() 相对应,在反序列化之后(readObject() 之后)被调用,它会返回应该得到的没被 writeReplace() 替换之前的序列化对象 + +### 原型模式-Prototype Pattern + +- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 +- Java 的 Object 对象中提供了 clone() 方法,一般重写这个方法也就可以完成原型模式了。 + + - clone() + + - 浅拷贝、浅克隆 + + - 被赋值对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象 + + - 深拷贝、深克隆 + + - 把引用对象的变量指向复制到的新对象,而不是原有的被引用的对象 + - 一、构造函数,调用构造函数时进行深拷贝 + - 二、重载 clone() 方法 + - 三、序列化 + +- 优点优势 + + - 一般在初始化的信息不发生变化的情况下,克隆是最好的办法。既隐藏了对象创建的细节,又对性能是大大的提高。 + - 不用重新初始化对象,二十动态地获得对象运行时的状态 + +### 建造者模式-Builder Pattern + +- 讲一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 +- 用户只需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需知道了 +- 定义 + + - 讲一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 + +- 角色 + + - Builder + + - 是为创建一个 Product 对象的各个部件指定的抽象接口 + + - ConcreteBuilder + + - 具体的建造者,实现 Builder 接口,构造和装配各个部件。 + + - Product + + - 具体的产品、角色 + + - Director + + - 指挥者,用来根据客户端的需求构建对象,是构建一个使用 Builder 接口的对象 + +- 适用场景 + + - 主要用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。 + - 在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时使用的模式 + - 创建对象参数过多的时候 + - 对象的部分属性是可选择的时候 + - 对象创建完成后,就不能修改内部属性的时候 + +- 优劣势 + + - 好处是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以如果需要改变一个产品的内部表示,只需要再定义一个具体的建造者 + +- 建造者模式与工厂模式的区别 + + - 建造者模式,通过设置不同的可选参数,「定制化」的创建不同的对象 + - 工厂模式,是直接创建不同但是相关类型的对象 + +## 十一个行为型模式 + +### 职责链模式-Chain of Responsibility Pattern + +### 命令模式-Command Pattern + +### 解释器模式-Interpreter Pattern + +### 迭代器模式-Iterator Pattern + +### 中介者模式-Mediator Pattern + +### 备忘录模式-Memento Pattern + +### 观察者模式-Observer Pattern + +- 定义 + + - 定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。 + +- 角色及结构 + + - Subject(目标) + + - 目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法 notify。目标类可以是接口,也可以是抽象类或具体类。 + + - ConcreteSubject(具体目标) + + - 具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。 + + - Observer(观察者) + + - 观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update,因此又称为抽象观察者。 + + - ConcreteObserver(具体观察者) + + - 在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者 Observer 中定义的 update 方法。通常在实现时,可以调用具体目标类的 attach 方法将自己添加到目标类的集合中或通过 detach 方法将自己从目标类的集合中删除。 + +- 优点 + + - 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。 + - 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。 + - 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。 + - 观察者模式满足「开闭原则」的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。 + +- 缺点 + + - 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。 + - 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 + - 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 + +- 适用场景 + + - 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。 + - 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。 + - 需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象,可以使用观察者模式创建一种链式触发机制。 + +### 状态模式-State Pattern + +- 定义 + + - 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。其别名为状态对象(Objects for States)。 + +- 角色及结构 + + - Context(环境类) + + - 环境类又称为上下文类,它是拥有多种状态的对象。 + - 由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。 + - 在环境类中维护一个抽象状态类 State 的实例,这个实例定义当前状态,在具体实现时,它是一个 State 子类的对象。 + + - State(抽象状态类) + + - 它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。 + + - ConcreteState(具体状态类) + + - 它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。 + +- 优势 + + - 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来,消除庞大的条件分支语句。 + - 状态的集中管理,封装状态的转换规则,这样我们只需要注入不同状态的状态对象到环境类当中就能实现对象的不同行为。 + - 状态共享。减少系统对象。 + +- 劣势 + + - 有多少种状态就有多少个具体的状态类,系统开销大了 + - 状态对象与环境类对象构造混杂,复杂点状态对象行为会导致代码结构混乱。 + - 状态模式并不是完善的满足开放-封闭原则,无论是新增/修改状态都需要修改对应类的源码,不过总比错综复杂的条件分支语法强太多。 + +### 策略模式-Strategy Pattern + +- 定义 + + - 定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。 + - 所有这些算法完成的都是相同的工作,但是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。 + +- 角色及结构 + + - Context(环境类) + + - 环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。 + - 在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。就是我们所说的业务场景类。 + + - Strategy(抽象策略类) + + - 它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。 + - 环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。就是定义算法的接口。 + + - ConcreteStrategy(具体策略类) + + - 它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。就是具体的算法。 + +- 优势 + + - 算法重用 + - 消除条件判断语句 + - 完美符合「开放-封闭原则」 + +- 劣势 + + - 策略模式只适用于客户端知道所有的算法或行为的情况。因为客户端必须知道所有的策略类,并自行决定使用哪一个策略类。所以就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。 + - 会导致具体策略类累积的越来越多。细小的变化都需要新增一个策略类来实现。 + - 客户端只能使用一个策略,无法同时多个。 + +- 适用场景 + + - 某系统需要在多种算法中选择一种。 + - 某个对象有很多行为和选择 + +### 模板方法模式-Template Method Pattern + +- 定义 + + - 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。 + +- 角色及结构 + + - AbstractClass(抽象类) + + - 在抽象类中定义了一系列基本操作,这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。 + - 在抽象类中实现了一个模板方法,用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。 + + - ConcreteClass(具体子类) + + - 它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。 + +- 优势 + + - 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。 + - 鼓励我们使用继承实现代码复用 + - 通过抽象父类,不同的子类可以实现不同的行为,更换和新增子类方便,符合设计模式中的单一职责和开放-封闭原则。 + +- 劣势 + + - 如果父类基本方法变得越来越多并且基本方法是多变类型,会导致子类的细节实现也越来越多,从而导致系统更加臃肿,此时也需要更进一步的抽象。 + +- 适用场景 + + - 复杂算法分割,复杂业务流程分隔 + - 多个类都有共同的行为,可以提取抽象父类,在父类中直接声明并实现 + +### 访问者模式-Visitor Pattern + +## 七个结构型模式 + +### 适配器模式-Adapter Pattern + +- 定义 + + - 将一个类的接口转换成客户希望的另外一个接口 + - 主要目的是解决由于接口不能兼容而导致类无法使用的问题,适配器模式会将需要适配的类转换成调用者能够使用的目标接口 + +- Adapter 适配器模式使得原本由于接口(这里的接口指的是广义的接口,它可以表示一个方法或者方法的集合)不兼容而不能一起工作的那些类可以一起工作。 +- 角色结构 + + - Target(目标抽象类) + + - 目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。可以理解为它就是电源适配器,里面声明或者实现了将高电压转为低电压的过程。 + + - Adapter(适配器类) + + - 适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心。 + - 在对象适配器中它通过继承 Target 并关联一个 Adaptee 对象使二者产生联系。它可以理解为 Target 的具体实现的核心类,适配器模式就是通过这个适配器来协调适配者(220V 家庭用电)和客户端(笔记本电脑)之间的融合的。 + - Adapter 实现了 Target 接口,并包装了一个 Adaptee 对象。Adapter 在实现 Target 接口中的方法时,会将调用委托给 Adaptee 对象的相关方法,由 Adaptee 完成具体的业务。 + + - Adaptee(适配者类、需要适配的类) + + - 适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。 + - 有真正的业务逻辑,但是其接口不能被调用者直接使用 + +- 优势 + + - 目标类与适配者解耦,通过引用适配器,各自维护 + - 提高适配者的复用性 + - 可以方便的添加或者替换适配以适配不同的需求,符合开放 - 封闭原则 + - 一个适配器可以将多个不同的适配者适配到 target 目标类中 + - 可以适配适配者的子类,根据里氏代换原则,适配者的子类可以通过适配器来适配,毕竟适配器与适配者是关联关系。适配者在适配器中做声明和实例化。 + +- 适用场景 + + - 对接系统没有源代码 + - 对接的系统的接口不能符合当前系统的接口规范 + - 对接系统的接口需要复用,不能对其有侵入和耦合。 + +### 桥接模式-Bridge Pattern + +### 组合模式-Composite Pattern + +### 装饰模式-Decorator Pattern + +- 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活 +- Component:定义一个对象接口,可以给这些对象动态地添加职责 + + - ConcreteComponent:定义了一份具体的对象,可以给这个对象添加核心职责 + + - Operation() + + - Decorator:装饰抽象类,扩展 Component 类的功能,但是 Component 无须知道 Decorator 的存在 + + - ConcreteDecoratorA + - ConcreteDecoratorB + +- 装饰模式是为已有功能动态地添加更多功能的一种方式 +- 把类中的装饰功能从类中搬移取去除,简化原有的类。有效的把类的核心职责和装饰功能区分。 + +### 外观模式-Facade Pattern + +- 定义 + + - 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 + +- 角色及结构 + + - Facade(外观角色) + + - 在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任; + - 在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。 + + - SubSystem(子系统角色) + + - 软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能; + - 每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。 + +- 优势 + + - 对客户端屏蔽子系统,封装子系统,是客户端调用方便简单,减少客户端与子系统的关联,降低耦合。 + - 子系统的修改保持独立,某个子系统的修改不影响其他系统。这也是分层设计的体现。 + +- 劣势 + + - 对于限制客户端与子系统的交互并不完美,同时所有的业务逻辑全被外观类封装的话会影响系统的可变性以及灵活性。幸好外观模式不影响客户端可以直接调用子系统。 + - 违背开放-封闭原则 + +### 享元模式-Flyweight Pattern + +### 代理模式-Proxy Pattern + +- 为其他对象提供一种代理以控制对这个对象的访问。即给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。 +- 代理模式的结构及角色 + + - Subject(抽象主题角色) + + - 声明真实对象和代理对象的共同接口,保证在使用真实对象的地方都可以使用代理对象 + - 程序中的业务逻辑接口 + + - Proxy(代理主题角色) + + - 代理对象需要包含对真实对象的引用,从而可以在任何时候任何地点保证对真实对象的调用,甚至对真实对象的约束和修饰。 + - 实现了 Subject 接口的代理类,封装了 RealSubject 对象 + + - RealSubject(真实主题角色) + + - 实现真正的核心业务逻辑,客户端通过代理,代理再通过调用真实对象实现的核心操作完成业务逻辑 + - 实现了 Subject 接口的真正业务类 + - 在程序中不会直接调用 RealSubject 对象的方法,而是使用 Proxy 对象实现相关功能 + +- 代理模式的类型 + + - 远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。 + - 虚拟代理:根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象 + - 安全代理:用来控制真实对象访问时的权限。 + - 智能指引,是指当调用真实的对象时,代理处理另外一些事实。 + - 缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端 可以共享这些结果。 + +- 优势 + + - 代理模式可以实现「延迟加载」 + - 可以协调调用者与 RealSubject 之间的关系,一定程度上实现了解耦 + +## 七个面向对象设计原则 + +### 单一职责原则-Single Responsibility Principle + +- 就一个类而言,应该仅有一个引起它变化的原因。 +- 一个类应该只负责负责一个功能领域中的相应职责,不要多管闲事。 + +### 开闭原则-Open-Closed Principle + +- 一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。 + +### 里氏代换原则-Liskov Substitution Principle + +- 子类型必须能够替换掉它们的父类型。 + +### 依赖倒转原则-Dependency Inversion Principle + +- 抽象不应该依赖细节,细节应该依赖于抽象 +- 高层模块不应该依赖低层模块。两个都应该依赖抽象。 +- 要针对接口编程,不要对实现编程。 + +### 接口隔离原则-Interface Segregation Principle + +- 一个类对另一个类的依赖应该建立在最小的接口上 +- 说白了就是我们在设计接口时,不要设计出庞大臃肿的接口,因为实现这种接口时需要实现很多不必要的方法。 +- 尽量设计出功能单一的接口,这样也能保证实现类的职责单一。 + +### 合成复用原则-Composite Reuse Principle + +### 迪米特法则(Law of Demeter),也叫「最少知识原则」 + +- 如果两个类不彼此直接通信,那么这两个类就不应当发生直接的相互作用。 +- 一个软件实体应当尽可能少地与其他实体发生相互作用。 + diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.xmind" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.xmind" index f67716b..b520626 100644 Binary files "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.xmind" and "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217.xmind" differ