From 5cea6bacfe84ec2de53500e822c2e8644fb37411 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Fri, 1 May 2020 20:29:39 +0800 Subject: [PATCH 01/80] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 182 ++++++++++++++++++++++---------------------- Rocket.md | 86 +++++++++++++++++++-- 2 files changed, 167 insertions(+), 101 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0d36467..3e7bcd3 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,8 +1,12 @@ - + + + + + + - @@ -79,26 +83,13 @@ - - - + - - - @@ -264,9 +255,21 @@ - - - + + + + + + + + + + + 1581156795852 + 1581270301073 @@ -590,28 +593,21 @@ - - 1585657673868 + + 1585377224497 - - 1585658308922 + + 1586355292587 - - 1585658338742 - - @@ -687,9 +683,9 @@ - - - @@ -700,92 +696,92 @@ - + - - + + - - + + - + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - + - + - + - + - + - + - + - + - + - + - - - - - + - + + + + + - + - + - + @@ -794,9 +790,9 @@ - + - + \ No newline at end of file diff --git a/Rocket.md b/Rocket.md index 9e5f8db..d0c9c8d 100644 --- a/Rocket.md +++ b/Rocket.md @@ -212,6 +212,16 @@ HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。 +### 动态年龄计算 + +Hotspot在遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值。 + +JVM引入动态年龄计算,主要基于如下两点考虑: + +1. 如果固定按照MaxTenuringThreshold设定的阈值作为晋升条件: a)MaxTenuringThreshold设置的过大,原本应该晋升的对象一直停留在Survivor区,直到Survivor区溢出,一旦溢出发生,Eden+Svuvivor中对象将不再依据年龄全部提升到老年代,这样对象老化的机制就失效了。 b)MaxTenuringThreshold设置的过小,“过早晋升”即对象不能在新生代充分被回收,大量短期对象被晋升到老年代,老年代空间迅速增长,引起频繁的Major GC。分代回收失去了意义,严重影响GC性能。 +2. 相同应用在不同时间的表现不同:特殊任务的执行或者流量成分的变化,都会导致对象的生命周期分布发生波动,那么固定的阈值设定,因为无法动态适应变化,会造成和上面相同的问题。 + + ### 常见的垃圾回收机制 1. 引用计数法:引用计数法是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。 @@ -270,7 +280,6 @@ HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分 双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。 - ### 双亲委派模型的"破坏" 一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些代码那该怎么办? @@ -318,21 +327,78 @@ HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分 * JConsole * VisualVM - -### JVM调优实战 - -1. 外部命令导致系统缓慢 + +### JVM常见参数 + +1. -Xms20M:表示设置JVM启动内存的最小值为20M,必须以M为单位 +2. -Xmx20M:表示设置JVM启动内存的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免JVM内存自动扩展。大的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高 +3. -verbose:gc:表示输出虚拟机中GC的详细情况 +4. -Xss128k:表示可以设置虚拟机栈的大小为128k +5. -Xoss128k:表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数是无效的 +6. -XX:PermSize=10M:表示JVM初始分配的永久代(方法区)的容量,必须以M为单位 +7. -XX:MaxPermSize=10M:表示JVM允许分配的永久代(方法区)的最大容量,必须以M为单位,大部分情况下这个参数默认为64M +8. -Xnoclassgc:表示关闭JVM对类的垃圾回收 +9. -XX:+TraceClassLoading表示查看类的加载信息 +10. -XX:+TraceClassUnLoading:表示查看类的卸载信息 +11. -XX:NewRatio=4:表示设置年轻代(包括Eden和两个Survivor区)/老年代 的大小比值为1:4,这意味着年轻代占整个堆的1/5 +12. -XX:SurvivorRatio=8:表示设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8 +13. -Xmn20M:表示设置年轻代的大小为20M +14. -XX:+HeapDumpOnOutOfMemoryError:表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照 +15. -XX:+UseG1GC:表示让JVM使用G1垃圾收集器 +16. -XX:+PrintGCDetails:表示在控制台上打印出GC具体细节 +17. -XX:+PrintGC:表示在控制台上打印出GC信息 +18. -XX:PretenureSizeThreshold=3145728:表示对象大于3145728(3M)时直接进入老年代分配,这里只能以字节作为单位 +19. -XX:MaxTenuringThreshold=1:表示对象年龄大于1,自动进入老年代,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。 +20. -XX:CompileThreshold=1000:表示一个方法被调用1000次之后,会被认为是热点代码,并触发即时编译 +21. -XX:+PrintHeapAtGC:表示可以看到每次GC前后堆内存布局 +22. -XX:+PrintTLAB:表示可以看到TLAB的使用情况 +23. -XX:+UseSpining:开启自旋锁 +24. -XX:PreBlockSpin:更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁 +25. -XX:+UseSerialGC:表示使用jvm的串行垃圾回收机制,该机制适用于单核cpu的环境下 +26. -XX:+UseParallelGC:表示使用jvm的并行垃圾回收机制,该机制适合用于多cpu机制,同时对响应时间无强硬要求的环境下,使用-XX:ParallelGCThreads=设置并行垃圾回收的线程数,此值可以设置与机器处理器数量相等。 +27. -XX:+UseParallelOldGC:表示年老代使用并行的垃圾回收机制 +28. -XX:+UseConcMarkSweepGC:表示使用并发模式的垃圾回收机制,该模式适用于对响应时间要求高,具有多cpu的环境下 +29. -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。 +30. -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开 + +### JVM调优目标-何时需要做jvm调优 + +1. heap 内存(老年代)持续上涨达到设置的最大内存值; +2. Full GC 次数频繁; +3. GC 停顿时间过长(超过1秒); +4. 应用出现OutOfMemory 等内存异常; +5. 应用中有使用本地缓存且占用大量内存空间; +6. 系统吞吐量与响应性能不高或下降。 + +### JVM调优实战 + +1. Major GC和Minor GC频繁 + + 首先优化Minor GC频繁问题。通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间来降低Minor GC的频率。例如在相同的内存分配率的前提下,新生代中的Eden区增加一倍,Minor GC的次数就会减少一半。 + + 扩容Eden区虽然可以减少Minor GC的次数,但会增加单次Minor GC时间么?扩容后,Minor GC时增加了T1(扫描时间),但省去T2(复制对象)的时间,更重要的是对于虚拟机来说,复制对象的成本要远高于扫描成本,所以,单次Minor GC时间更多取决于GC后存活对象的数量,而非Eden区的大小。因此如果堆中短期对象很多,那么扩容新生代,单次Minor GC时间不会显著增加。 + +2. 请求高峰期发生GC,导致服务可用性下降 + + 由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象,我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。 + 另外,类似的JVM是如何避免Minor GC时扫描全堆的? 经过统计信息显示,老年代持有新生代对象引用的情况不足1%,根据这一特性JVM引入了卡表(card table)来实现这一目的。卡表的具体策略是将老年代的空间分成大小为512B的若干张卡(card)。卡表本身是单字节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。如上图所示,卡表3被标记为脏(卡表还有另外的作用,标识并发标记阶段哪些块被修改过),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫描。 + +3. 外部命令导致系统缓慢 一个数字校园应用系统,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU使用率很高,并且系统占用绝大多数的CPU资 源的程序并不是应用系统本身。每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息,执行这个shell脚本是通过Java的 Runtime.getRuntime().exec()方法来调用的。这种调用方式可以达到目的,但是它在Java 虚拟机中是非常消耗资源的操作,即使外部命令本身能很快执行完毕,频繁调用时创建进程 的开销也非常可观。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一 样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执 行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。用户根据建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后, 系统很快恢复了正常。 + +4. STW过长的GC -2. 由Windows虚拟内存导致的长时间停顿 + 对于性能要求很高的服务,建议将MaxPermSize和MinPermSize设置成一致(JDK8开始,Perm区完全消失,转而使用元空间。而元空间是直接存在内存中,不在JVM中),Xms和Xmx也设置为相同,这样可以减少内存自动扩容和收缩带来的性能损失。虚拟机启动的时候就会把参数中所设定的内存全部化为私有,即使扩容前有一部分内存不会被用户代码用到,这部分内存在虚拟机中被标识为虚拟内存,也不会交给其他进程使用。 - 一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果 对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳 检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完 全无日志输出,处于停顿状态。 +5. 由Windows虚拟内存导致的长时间停顿 + 一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果 对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳 检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完 全无日志输出,处于停顿状态。 + 因为是桌面程序,所需的内存并不大(-Xmx256m),所以开始并没有想到是GC导致的 程序停顿,但是加入参数-XX:+PrintGCApplicationStoppedTime-XX:+PrintGCDateStamps- Xloggc:gclog.log后,从GC日志文件中确认了停顿确实是由GC导致的,大部分GC时间都控 制在100毫秒以内,但偶尔就会出现一次接近1分钟的GC。 从GC日志中找到长时间停顿的具体日志信息(添加了-XX:+PrintReferenceGC参数), 找到的日志片段如下所示。从日志中可以看出,真正执行GC动作的时间不是很长,但从准 备开始GC,到真正开始GC之间所消耗的时间却占了绝大部分。 - + 除GC日志之外,还观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源 管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它 的工作内存被自动交换到磁盘的页面文件之中了,这样发生GC时就有可能因为恢复页面文 件的操作而导致不正常的GC停顿。在Java的GUI程序中要避免这种现象,可以 加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。 ## Java基础 @@ -377,6 +443,10 @@ CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。 AQS内部有3个对象,一个是state(用于计数器,类似gc的回收计数器),一个是线程标记(当前线程是谁加锁的),一个是阻塞队列。 +AQS是自旋锁,在等待唤醒的时候,经常会使用自旋的方式,不停地尝试获取锁,直到被其他线程获取成功。 + +AQS有两个队列,同步对列和条件队列。同步队列依赖一个双向链表来完成同步状态的管理,当前线程获取同步状态失败后,同步器会将线程构建成一个节点,并将其加入同步队列中。通过signal或signalAll将条件队列中的节点转移到同步队列。 + ### 如何指定多个线程的执行顺序 1. 设定一个 orderNum,每个线程执行结束之后,更新 orderNum,指明下一个要执行的线程。并且唤醒所有的等待线程。 From 5d90c65c591dd6f8ce8a9bd58d3dba7ebe86994a Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Fri, 1 May 2020 20:30:34 +0800 Subject: [PATCH 02/80] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 8 ++++---- Rocket.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3e7bcd3..fa3c193 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -700,10 +700,10 @@ - + - + @@ -758,10 +758,10 @@ - + - + diff --git a/Rocket.md b/Rocket.md index d0c9c8d..be09645 100644 --- a/Rocket.md +++ b/Rocket.md @@ -383,14 +383,14 @@ JVM引入动态年龄计算,主要基于如下两点考虑: 由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象,我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。 另外,类似的JVM是如何避免Minor GC时扫描全堆的? 经过统计信息显示,老年代持有新生代对象引用的情况不足1%,根据这一特性JVM引入了卡表(card table)来实现这一目的。卡表的具体策略是将老年代的空间分成大小为512B的若干张卡(card)。卡表本身是单字节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。如上图所示,卡表3被标记为脏(卡表还有另外的作用,标识并发标记阶段哪些块被修改过),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫描。 -3. 外部命令导致系统缓慢 - - 一个数字校园应用系统,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU使用率很高,并且系统占用绝大多数的CPU资 源的程序并不是应用系统本身。每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息,执行这个shell脚本是通过Java的 Runtime.getRuntime().exec()方法来调用的。这种调用方式可以达到目的,但是它在Java 虚拟机中是非常消耗资源的操作,即使外部命令本身能很快执行完毕,频繁调用时创建进程 的开销也非常可观。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一 样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执 行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。用户根据建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后, 系统很快恢复了正常。 - -4. STW过长的GC +3. STW过长的GC 对于性能要求很高的服务,建议将MaxPermSize和MinPermSize设置成一致(JDK8开始,Perm区完全消失,转而使用元空间。而元空间是直接存在内存中,不在JVM中),Xms和Xmx也设置为相同,这样可以减少内存自动扩容和收缩带来的性能损失。虚拟机启动的时候就会把参数中所设定的内存全部化为私有,即使扩容前有一部分内存不会被用户代码用到,这部分内存在虚拟机中被标识为虚拟内存,也不会交给其他进程使用。 +4. 外部命令导致系统缓慢 + + 一个数字校园应用系统,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU使用率很高,并且系统占用绝大多数的CPU资 源的程序并不是应用系统本身。每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息,执行这个shell脚本是通过Java的 Runtime.getRuntime().exec()方法来调用的。这种调用方式可以达到目的,但是它在Java 虚拟机中是非常消耗资源的操作,即使外部命令本身能很快执行完毕,频繁调用时创建进程 的开销也非常可观。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一 样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执 行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。用户根据建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后, 系统很快恢复了正常。 + 5. 由Windows虚拟内存导致的长时间停顿 一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果 对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳 检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完 全无日志输出,处于停顿状态。 From 4a3bd02374745439443bfdeed72ada0ee0c93e31 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Wed, 6 May 2020 15:22:59 +0800 Subject: [PATCH 03/80] update --- .idea/workspace.xml | 48 +++++++++---------- README.md | 2 +- .../Solution.java" | 2 +- 3 files changed, 24 insertions(+), 28 deletions(-) rename "src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q736_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" => "src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q763_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" (94%) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index fa3c193..077affe 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,12 +1,8 @@ - - - - + - @@ -700,10 +696,10 @@ - + - + @@ -758,10 +754,10 @@ - + - + diff --git a/README.md b/README.md index cef71bb..cbc5038 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ * [q6_Z字形变换](/src/字符串操作/q6_Z字形变换) * [q14_最长公共前缀](/src/字符串操作/q14_最长公共前缀) -* [q736_划分字母区间](/src/字符串操作/q736_划分字母区间) +* [q763_划分字母区间](/src/字符串操作/q763_划分字母区间) ### 数字操作 diff --git "a/src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q736_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" "b/src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q763_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" similarity index 94% rename from "src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q736_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" rename to "src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q763_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" index de3e054..418d11b 100644 --- "a/src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q736_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" +++ "b/src/\345\255\227\347\254\246\344\270\262\346\223\215\344\275\234/q763_\345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264/Solution.java" @@ -1,4 +1,4 @@ -package 字符串操作.q736_划分字母区间; +package 字符串操作.q763_划分字母区间; import java.util.ArrayList; import java.util.List; From cde96ead44a57e816c75a828f305c2b21991966e Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Thu, 4 Jun 2020 15:05:34 +0800 Subject: [PATCH 04/80] update --- .idea/workspace.xml | 50 +++++++++++++------------------ Rocket.md | 72 +++++++++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 48 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 077affe..c22370a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -259,13 +259,8 @@ - - - 1581441609925 - 1581519773712 @@ -603,7 +598,14 @@ - - @@ -681,29 +682,22 @@ - - - - - - - - - - + - - + + - + @@ -730,22 +724,18 @@ - - - - @@ -754,10 +744,10 @@ - + - + @@ -766,10 +756,10 @@ - + - + diff --git a/Rocket.md b/Rocket.md index be09645..e47b9bd 100644 --- a/Rocket.md +++ b/Rocket.md @@ -2,7 +2,21 @@ ## ZooKeeper ### CAP定理: -一个分布式系统不可能同时满足以下三种,一致性(C:Consistency),可用性(A:Available),分区容错性(P:Partition Tolerance).在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。(Base理论CA强一致性和最终一致性) +一个分布式系统不可能同时满足以下三种,一致性(C:Consistency),可用性(A:Available),分区容错性(P:Partition Tolerance).在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。 + +### BASE理论 + +BASE理论是基本可用,软状态,最终一致性三个短语的缩写。BASE理论是对CAP中一致性和可用性(CA)权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求。 +1. 基本可用:基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。比如正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒。 +2. 软状态:软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。 +3. 最终一致性:最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 + +### ZooKeeper特点 + +1. 顺序一致性:同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 +2. 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 +3. 单一系统映像:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 +4. 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 ### ZAB协议: @@ -31,7 +45,7 @@ ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Z 1. 纯内存操作 2. 单线程操作,避免了频繁的上下文切换 3. 合理高效的数据结构 -4. 采用了非阻塞I/O多路复用机制 +4. 采用了非阻塞I/O多路复用机制(有一个文件描述符同时监听多个文件描述符是否有数据到来) ### Redis 的数据结构及使用场景 @@ -54,6 +68,10 @@ Redis 中数据过期策略采用定期删除+惰性删除策略 5. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。 6. 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。 +### Redis的set和setnx + +Redis中setnx不支持设置过期时间,做分布式锁时要想避免某一客户端中断导致死锁,需设置lock过期时间,在高并发时 setnx与 expire 不能实现原子操作,如果要用,得在程序代码上显示的加锁。使用SET代替SETNX ,相当于SETNX+EXPIRE实现了原子性,不必担心SETNX成功,EXPIRE失败的问题。 + ### Redis的LRU具体实现: 传统的LRU是使用栈的形式,每次都将最新使用的移入栈顶,但是用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改进。Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。 @@ -131,7 +149,7 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 * 最上层的服务类似其他CS结构,比如连接处理,授权处理。 * 第二层是Mysql的服务层,包括SQL的解析分析优化,存储过程触发器视图等也在这一层实现。 -* 最后一层是存储引擎的实现,类似于Java接口的实现,Mysql的执行器在执行SQL的时候只会关注API的调用,完全屏蔽了不同引擎实现间的差异。比如Select语句,先会判断当前用户是否拥有权限,其次到缓存(内存)查询是否有相应的结果集,如果没有再执行解析sql,优化生成执行计划,调用API执行。 +* 最后一层是存储引擎的实现,类似于Java接口的实现,Mysql的执行器在执行SQL的时候只会关注API的调用,完全屏蔽了不同引擎实现间的差异。比如Select语句,先会判断当前用户是否拥有权限,其次到缓存(内存)查询是否有相应的结果集,如果没有再执行解析sql,检查SQL 语句语法是否正确,再优化生成执行计划,调用API执行。 ### SQL执行顺序 @@ -512,8 +530,12 @@ HashSet的value存的是一个static finial PRESENT = newObject()。而HashSet ### 阻塞非阻塞与同步异步的区别 -1. 同步和异步关注的是消息通信机制,所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。 -2. 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 +1. 同步和异步关注的是消息通信机制,所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。 +2. 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。 + +### Java SPI + +由于双亲委派模型损失了一丢丢灵活性。就比如java.sql.Driver这个东西。JDK只能提供一个规范接口,而不能提供实现。提供实现的是实际的数据库提供商。提供商的库总不能放JDK目录里吧。Java从1.6搞出了SPI就是为了优雅的解决这类问题——JDK提供接口,供应商提供服务。编程人员编码时面向接口编程,然后JDK能够自动找到合适的实现。 ## Spring @@ -523,15 +545,7 @@ HashSet的value存的是一个static finial PRESENT = newObject()。而HashSet 2. 第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值对象也没有被初始化) 3. 第三级缓存:singletonFactories单例对象工厂缓存。 -### 创建Bean的整个过程 - -1. getBean方法肯定不陌生,必经之路,然后调用doGetBean,进来以后首先会执行transformedBeanName找别名,看你的Bean上面是否起了别名。然后进行很重要的一步,getSingleton,这段代码就是从你的单例缓存池中获取Bean的实例。那么你第一次进来肯定是没有的,缓存里肯定是拿不到的。也就是一级缓存里是没有的。那么它怎么办呢?他会尝试去二级缓存中去拿,但是去二级缓存中拿并不是无条件的,首先要判断isSingletonCurrentlyInCreation(beanName)他要看你这个对象是否正在创建当中,如果不是直接就退出该方法,如果是的话,他就会去二级缓存earlySingletonObjects里面取,如果没拿到,它还接着判断allowEarlyReference这个东西是否为true。它的意思是说,是否允许让你从单例工厂对象缓存中去拿对象。默认为true。好了,此时如果进来那么就会通过singletonFactory.getObject()去单例工厂缓存中去拿。然后将缓存级别提升至二级缓存也就早期暴露的缓存。 -2. getSingleton执行完以后会走dependsOn方法,判断是否有dependsOn标记的循环引用,有的话直接卡死,抛出异常。比如说A依赖于B,B依赖于A 通过dependsOn注解去指定。此时执行到这里就会抛出异常。这里所指并非是构造函数的循环依赖。 -3. beforeSingletonCreation在这里方法里。就把你的对象标记为了早期暴露的对象。提前暴露对象用于创建Bean的实例。 -4. 紧接着就走创建Bean的流程开始。在创建Bean之前执行了一下resolveBeforeInstantiation。它的意思是说,代理AOPBean定义注册信息但是这里并不是实际去代理你的对象,因为对象还没有被创建。只是代理了Bean定义信息,还没有被实例化。把Bean定义信息放进缓存,以便我想代理真正的目标对象的时候,直接去缓存里去拿。 -5. 接下来就真正的走创建Bean流程,首先走进真正做事儿的方法doCreateBean然后找到createBeanInstance这个方法,在这里面它将为你创建你的Bean实例信息(Bean的实例)。如果说创建成功了,那么就把你的对象放入缓存中去(将创建好的提前曝光的对象放入singletonFactories三级缓存中)将对象从二级缓存中移除因为它已经不是提前暴露的对象了。但是。如果说在createBeanInstance这个方法中在创建Bean的时候它会去检测你的依赖关系,会去检测你的构造器。然后,如果说它在创建A对象的时候,发现了构造器里依赖了B,然后它又会重新走getBean的这个流程,当在走到这里的时候,又发现依赖了A此时就会抛出异常。为什么会抛出异常,因为,走getBean的时候他会去从你的单例缓存池中去拿,因为你这里的Bean还没有被创建好。自然不会被放进缓存中,所以它是在缓存中拿不到B对象的。反过来也是拿不到A对象的。造成了死循环故此直接抛异常。这就是为什么Spring IOC不能解决构造器循环依赖的原因。因为你还没来的急放入缓存你的对象是不存在的。所以不能创建。同理@Bean标注的循环依赖方法也是不能解决的,跟这个同理。那么多例就更不能解决了。为什么?因为在走createBeanInstance的时候,会判断是否是单例的Bean定义信息mbd.isSingleton();如果是才会进来。所以多例的Bean压根就不会走进来,而是走了另一段逻辑,这里不做介绍。至此,构造器循环依赖和@Bean的循环依赖还有多例Bean的循环依赖为什么不能解决已经解释清楚。然后如果说,Bean创建成功了。那么会走后面的逻辑。 -6. 将创建好的Bean放入缓存,addSingletonFactory方法就是将你创建好的Bean放入三级缓存中。并且移除早期暴露的对象。 -7. 通过populateBean给属性赋值,我们知道,创建好的对象,并不是一个完整的对象,里面的属性还没有被赋值。所以这个方法就是为创建好的Bean为它的属性赋值。并且调用了我们实现的的XXXAware接口进行回调初始化,。然后调用我们实现的Bean的后置处理器,给我们最后一次机会去修改Bean的属性。 +三级缓存详解:[根据 Spring 源码写一个带有三级缓存的 IOC](https://zhuanlan.zhihu.com/p/144627581) ### Spring如何解决循环依赖问题 @@ -549,23 +563,38 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给 2. CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 3. 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。 +### @Transactional 在private上会生效么 + +@Transactional注解只能应用在public方法上。当标记在protected、private、package-visible方法上时,不会产生错误,但也不会表现出为它指定的事务配置。可以认为它作为一个普通的方法参与到一个public方法的事务中。 + ### Spring的的事务传播机制 -1. REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。 +1. REQUIRED(默认,常用):支持使用当前事务,如果当前事务不存在,创建一个新事务。eg:方法B用REQUIRED修饰,方法A调用方法B,如果方法A当前没有事务,方法B就新建一个事务(若还有C则B和C在各自的事务中独立执行),如果方法A有事务,方法B就加入到这个事务中,当成一个事务。 2. SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。 3. MANDATORY:强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。 -4. REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。 +4. REQUIRES_NEW(常用):创建一个新事务,如果当前事务存在,把当前事务挂起。eg:方法B用REQUIRES_NEW修饰,方法A调用方法B,不管方法A上有没有事务方法B都新建一个事务,在该事务执行。 5. NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。 6. NEVER:无事务执行,如果当前有事务则抛出Exception。 7. NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。 +### Spring中Bean的生命周期 + +1. 实例化 Instantiation +2. 属性赋值 Populate +3. 初始化 Initialization +4. 销毁 Destruction + ### Spring的后置处理器 -1. BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。 -2. InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。 +1. BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。(before和after两个回调中间只处理了init-method) +2. InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作(TargetSource的AOP创建代理对象就是通过该接口实现) 3. BeanFactoryPostProcessor:Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。 4. BeanDefinitionRegistryPostProcessor:继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前真执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。 +### Spring MVC的工作流程(源码层面) + +参考文章:[自己写个Spring MVC](https://zhuanlan.zhihu.com/p/139751932) + ## 消息队列 ### 为什么需要消息队列 @@ -768,6 +797,13 @@ void quick_sort(int a[], int low, int high){ * 降级:服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好。 * 限流:限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。 +### 负载均衡算法: + +1. 轮询 +2. 加权轮询 +3. 随机算法 +4. 一致性Hash + ### 常见的限流算法: 常见的限流算法有计数器、漏桶和令牌桶算法。漏桶算法在分布式环境中消息中间件或者Redis都是可选的方案。发放令牌的频率增加可以提升整体数据处理的速度,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度。而漏桶不行,因为它的流出速率是固定的,程序处理速度也是固定的。 From 30b2af7afe1026a6164f6d1fb60c118102987e35 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Thu, 4 Jun 2020 17:21:33 +0800 Subject: [PATCH 05/80] update --- .idea/workspace.xml | 40 ++++++++++++++++++---------------------- Rocket.md | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index c22370a..ab3d1c3 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -261,13 +261,7 @@ - - - 1581519773712 - 1581704713975 @@ -605,7 +599,14 @@ - @@ -690,14 +691,16 @@ - - + + - - + + + + @@ -744,14 +747,11 @@ - - + + - - - - + @@ -772,10 +772,6 @@ - - - - diff --git a/Rocket.md b/Rocket.md index e47b9bd..8ef6e5b 100644 --- a/Rocket.md +++ b/Rocket.md @@ -127,6 +127,15 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 3. 隔离性:同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。 4. 持久性:事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。 +### Mysql的存储引擎 + +1. InnoDB存储引擎:InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理(OLTP)的应用。其特点是行锁设计,支持外键,并支持非锁定锁,即默认读取操作不会产生锁。从Mysql5.5.8版本开始,InnoDB存储引擎是默认的存储引擎。 +2. MyISAM存储引擎:MyISAM存储引擎不支持事务、表锁设计,支持全文索引,主要面向一些OLAP数据库应用。InnoDB的数据文件本身就是主索引文件,而MyISAM的主索引和数据是分开的。 +3. NDB存储引擎:NDB存储引擎是一个集群存储引擎,其结构是share nothing的集群架构,能提供更高的可用性。NDB的特点是数据全部放在内存中(从MySQL 5.1版本开始,可以将非索引数据放在磁盘上),因此主键查找的速度极快,并且通过添加NDB数据存储节点可以线性地提高数据库性能,是高可用、高性能的集群系统。NDB存储引擎的连接操作是在MySQL数据库层完成的,而不是在存储引擎层完成的。这意味着,复杂的连接操作需要巨大的网络开销,因此查询速度很慢。如果解决了这个问题,NDB存储引擎的市场应该是非常巨大的。 +4. Memory存储引擎:Memory存储引擎(之前称HEAP存储引擎)将表中的数据存放在内存中,如果数据库重启或发生崩溃,表中的数据都将消失。它非常适合用于存储临时数据的临时表,以及数据仓库中的纬度表。Memory存储引擎默认使用哈希索引,而不是我们熟悉的B+树索引。虽然Memory存储引擎速度非常快,但在使用上还是有一定的限制。比如,只支持表锁,并发性能较差,并且不支持TEXT和BLOB列类型。最重要的是,存储变长字段时是按照定常字段的方式进行的,因此会浪费内存。 +5. Archive存储引擎:Archive存储引擎只支持INSERT和SELECT操作,从MySQL 5.1开始支持索引。Archive存储引擎使用zlib算法将数据行(row)进行压缩后存储,压缩比一般可达1∶10。正如其名字所示,Archive存储引擎非常适合存储归档数据,如日志信息。Archive存储引擎使用行锁来实现高并发的插入操作,但是其本身并不是事务安全的存储引擎,其设计目标主要是提供高速的插入和压缩功能。 +6. Maria存储引擎:Maria存储引擎是新开发的引擎,设计目标主要是用来取代原有的MyISAM存储引擎,从而成为MySQL的默认存储引擎。它可以看做是MyISAM的后续版本。Maria存储引擎的特点是:支持缓存数据和索引文件,应用了行锁设计,提供了MVCC功能,支持事务和非事务安全的选项,以及更好的BLOB字符类型的处理性能。 + ### 事务的并发问题 1. 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据 @@ -142,8 +151,21 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 | 可重复读 | 否 | 否 | 是 | | 串行化 | 否 | 否 | 否 | -在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决时不彻底的。 -通过索引加锁,间隙锁,next key lock可以解决幻读的问题。 +### MVCC多版本并发控制 + +MVCC在RR(可重复读)级别解决了可能出现不可重复读的问题,同时解决了读数据情况下的幻读问题(对于修改新增的操作依旧存在幻读问题)。它的实现原理主要是依赖记录中的 3个隐式字段,undolog指针 ,Read View 来实现的。 + +#### 隐式字段 + +每行记录除了我们自定义的字段外,还有数据库隐式定义的最近修改(修改/插入)事务ID;回滚指针,指向这条记录的上一个版本;隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引。 + +#### Read View + +Read View就是事务进行快照读操作的时候生产的读视图,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。 + +### Next-Key Lock + +InnoDB 采用 Next-Key Lock 解决幻读问题。在`insert into test(xid) values (1), (3), (5), (8), (11);`后,由于xid上是有索引的,该算法总是会去锁住索引记录。现在,该索引可能被锁住的范围如下:(-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, +∞)。Session A执行后会锁住的范围:(5, 8], (8, 11]。除了锁住8所在的范围,还会锁住下一个范围,所谓Next-Key。 ### Mysql的逻辑结构 @@ -158,18 +180,26 @@ SQL的执行顺序:from---where--group by---having---select---order by ### MVCC,redolog,undolog,binlog * undoLog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。 -* redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。 +* redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的ib_logfile0和ib_logfile1。为了得到更高的可靠性,用户可以设置多个的镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB存储引擎先写重做日志文件1,当达到文件的最后时,会切换至重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中。 * MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。 * binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复。 ### binlog和redolog的区别 1. redolog是在InnoDB存储引擎层产生,而binlog是MySQL数据库的上层服务层产生的。 -2. 两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句。而innodb存储引擎层面的重做日志是物理日志。 +2. 两种日志记录的内容形式不同。MySQL的binlog是逻辑日志,其记录是对应的SQL语句,对应的事务。而innodb存储引擎层面的重做日志是物理日志,是关于每个页(Page)的更改的物理情况。 3. 两种日志与记录写入磁盘的时间点不同,binlog日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。 4. binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redolog是循环使用。 5. binlog可以作为恢复数据使用,主从复制搭建,redolog作为异常宕机或者介质故障后的数据恢复使用。 +### InnoDB的关键特性 + +1. 插入缓冲:对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。 +2. 两次写:两次写带给InnoDB存储引擎的是数据页的可靠性,有经验的DBA也许会想,如果发生写失效,可以通过重做日志进行恢复。这是一个办法。但是必须清楚地认识到,如果这个页本身已经发生了损坏(物理到page页的物理日志成功页内逻辑日志失败),再对其进行重做是没有意义的。这就是说,在应用(apply)重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,这就是doublewrite。 +3. 自适应哈希索引:InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引。 +4. 异步IO:为了提高磁盘操作性能,当前的数据库系统都采用异步IO(AIO)的方式来处理磁盘操作。AIO的另一个优势是可以进行IO Merge操作,也就是将多个IO合并为1个IO,这样可以提高IOPS的性能。 +5. 刷新邻接页:当刷新一个脏页时,InnoDB存储引擎会检测该页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。这样做的好处显而易见,通过AIO可以将多个IO写入操作合并为一个IO操作,故该工作机制在传统机械磁盘下有着显著的优势。 + ### Mysql如何保证一致性和持久性 MySQL为了保证ACID中的一致性和持久性,使用了WAL(Write-Ahead Logging,先写日志再写磁盘)。Redo log就是一种WAL的应用。当数据库忽然掉电,再重新启动时,MySQL可以通过Redo log还原数据。也就是说,每次事务提交时,不用同步刷新磁盘数据文件,只需要同步刷新Redo log就足够了。 From 7e69818e7df7ab95f998845e7579053c83633361 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Thu, 4 Jun 2020 18:23:19 +0800 Subject: [PATCH 06/80] update --- .idea/workspace.xml | 30 +++++++++++++++--------------- Rocket.md | 9 ++++++++- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ab3d1c3..60d5fa7 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -261,14 +261,7 @@ - - - - 1581704713975 - 1581705212871 @@ -606,7 +599,14 @@ - - + - - + + - + @@ -747,11 +747,11 @@ - + - + diff --git a/Rocket.md b/Rocket.md index 8ef6e5b..b5ec954 100644 --- a/Rocket.md +++ b/Rocket.md @@ -165,7 +165,7 @@ Read View就是事务进行快照读操作的时候生产的读视图,在该 ### Next-Key Lock -InnoDB 采用 Next-Key Lock 解决幻读问题。在`insert into test(xid) values (1), (3), (5), (8), (11);`后,由于xid上是有索引的,该算法总是会去锁住索引记录。现在,该索引可能被锁住的范围如下:(-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, +∞)。Session A执行后会锁住的范围:(5, 8], (8, 11]。除了锁住8所在的范围,还会锁住下一个范围,所谓Next-Key。 +InnoDB 采用 Next-Key Lock 解决幻读问题。在`insert into test(xid) values (1), (3), (5), (8), (11);`后,由于xid上是有索引的,该算法总是会去锁住索引记录。现在,该索引可能被锁住的范围如下:(-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, +∞)。Session A(`select * from test where id = 8 for update`)执行后会锁住的范围:(5, 8], (8, 11]。除了锁住8所在的范围,还会锁住下一个范围,所谓Next-Key。 ### Mysql的逻辑结构 @@ -515,6 +515,13 @@ AQS有两个队列,同步对列和条件队列。同步队列依赖一个双 6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。 7. handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。 +### 线程池都有哪几种工作队列 + +1. ArrayBlockingQueue:底层是数组,有界队列,如果我们要使用生产者-消费者模式,这是非常好的选择。 +2. LinkedBlockingQueue:底层是链表,可以当做无界和有界队列来使用,所以大家不要以为它就是无界队列。 +3. SynchronousQueue:本身不带有空间来存储任何元素,使用上可以选择公平模式和非公平模式。 +4. PriorityBlockingQueue:无界队列,基于数组,数据结构为二叉堆,数组第一个也是树的根节点总是最小值。 + ### 线程池的拒绝策略 1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 From f8429463fc96420b3bbd8c089d6eb3e379f4bdae Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Fri, 5 Jun 2020 14:16:24 +0800 Subject: [PATCH 07/80] update --- .idea/workspace.xml | 28 +++++++++++++++------------- Rocket.md | 13 +++++++++---- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 60d5fa7..df9bbec 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -3,6 +3,7 @@ + @@ -691,11 +693,11 @@ - + - + @@ -747,11 +749,11 @@ - + - + diff --git a/Rocket.md b/Rocket.md index b5ec954..0eff616 100644 --- a/Rocket.md +++ b/Rocket.md @@ -437,11 +437,11 @@ JVM引入动态年龄计算,主要基于如下两点考虑: 4. 外部命令导致系统缓慢 - 一个数字校园应用系统,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU使用率很高,并且系统占用绝大多数的CPU资 源的程序并不是应用系统本身。每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息,执行这个shell脚本是通过Java的 Runtime.getRuntime().exec()方法来调用的。这种调用方式可以达到目的,但是它在Java 虚拟机中是非常消耗资源的操作,即使外部命令本身能很快执行完毕,频繁调用时创建进程 的开销也非常可观。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一 样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执 行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。用户根据建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后, 系统很快恢复了正常。 + 一个数字校园应用系统,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU使用率很高,并且系统占用绝大多数的CPU资 源的程序并不是应用系统本身。每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息,执行这个shell脚本是通过Java的 Runtime.getRuntime().exec()方法来调用的。这种调用方式可以达到目的,但是它在Java 虚拟机中是非常消耗资源的操作,即使外部命令本身能很快执行完毕,频繁调用时创建进程 的开销也非常可观。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。用户根据建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后,系统很快恢复了正常。 5. 由Windows虚拟内存导致的长时间停顿 - 一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果 对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳 检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完 全无日志输出,处于停顿状态。 + 一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳 检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完 全无日志输出,处于停顿状态。 因为是桌面程序,所需的内存并不大(-Xmx256m),所以开始并没有想到是GC导致的 程序停顿,但是加入参数-XX:+PrintGCApplicationStoppedTime-XX:+PrintGCDateStamps- Xloggc:gclog.log后,从GC日志文件中确认了停顿确实是由GC导致的,大部分GC时间都控 制在100毫秒以内,但偶尔就会出现一次接近1分钟的GC。 @@ -600,9 +600,14 @@ Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给 2. CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 3. 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。 -### @Transactional 在private上会生效么 +### @Transactional错误使用失效场景 -@Transactional注解只能应用在public方法上。当标记在protected、private、package-visible方法上时,不会产生错误,但也不会表现出为它指定的事务配置。可以认为它作为一个普通的方法参与到一个public方法的事务中。 +1. @Transactional 在private上:当标记在protected、private、package-visible方法上时,不会产生错误,但也不会表现出为它指定的事务配置。可以认为它作为一个普通的方法参与到一个public方法的事务中。 +2. @Transactional 的事务传播方式配置错误。 +3. @Transactional 注解属性 rollbackFor 设置错误:Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。 +4. 同一个类中方法调用,导致@Transactional失效:由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。 +5. 异常被 catch 捕获导致@Transactional失效。 +6. 数据库引擎不支持事务。 ### Spring的的事务传播机制 From fc8dd0cf5f22df167fab4f985e80f9d72eae3492 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Mon, 8 Jun 2020 12:24:18 +0800 Subject: [PATCH 08/80] add q78 --- .idea/workspace.xml | 118 ++++++++++-------- README.md | 1 + .../Solution.java" | 24 ++++ 3 files changed, 91 insertions(+), 52 deletions(-) create mode 100644 "src/\346\225\260\347\273\204\346\223\215\344\275\234/q78_\345\255\220\351\233\206/Solution.java" diff --git a/.idea/workspace.xml b/.idea/workspace.xml index df9bbec..f573a25 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,8 +2,9 @@ + - + - + + + - - - - - - - + + + @@ -263,14 +258,11 @@ - - - - 1581705262462 - 1581705659513 @@ -608,7 +600,14 @@ - @@ -693,54 +692,69 @@ - + - + - - - + + + - - + + + + - - + + + + - - + + + + - - + + + - - + + + + - - + + + + - - + + + + + + @@ -749,11 +763,11 @@ - + - + diff --git a/README.md b/README.md index cbc5038..6cea521 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ * [q54_螺旋矩阵](/src/数组操作/q54_螺旋矩阵) * [q73_矩阵置零](/src/数组操作/q73_矩阵置零) +* [q78_子集](/src/数组操作/q78_子集) * [q945_使数组唯一的最小增量](/src/数组操作/q945_使数组唯一的最小增量) ### 栈相关 diff --git "a/src/\346\225\260\347\273\204\346\223\215\344\275\234/q78_\345\255\220\351\233\206/Solution.java" "b/src/\346\225\260\347\273\204\346\223\215\344\275\234/q78_\345\255\220\351\233\206/Solution.java" new file mode 100644 index 0000000..d47219e --- /dev/null +++ "b/src/\346\225\260\347\273\204\346\223\215\344\275\234/q78_\345\255\220\351\233\206/Solution.java" @@ -0,0 +1,24 @@ +package 数组操作.q78_子集; + +import java.util.ArrayList; +import java.util.List; + +/** + * 向子集中添加子集合 o(n*2^n) + */ +public class Solution { + + public List> subsets(int[] nums) { + List> result = new ArrayList<>(); + result.add(new ArrayList<>()); + for (int i = 0; i < nums.length; i++) { + int size = result.size(); + for (int j = 0; j < size; j++) { + List temp = new ArrayList<>(result.get(j)); + temp.add(nums[i]); + result.add(temp); + } + } + return result; + } +} From ec326b54387ef8648d8609f2b7969a03cb6e9a8b Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 9 Jun 2020 00:24:53 +0800 Subject: [PATCH 09/80] update --- .idea/workspace.xml | 56 +++++++++++++++++++++++++-------------------- Rocket.md | 51 ++++++++++++++++++++++------------------- 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index f573a25..3ffc014 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,9 +2,8 @@ - - + @@ -660,7 +666,6 @@ - @@ -685,50 +690,51 @@ - - + - + - + - + - + - + - + - + - + - + @@ -763,11 +769,11 @@ - + - + diff --git a/Rocket.md b/Rocket.md index 0eff616..733e4ef 100644 --- a/Rocket.md +++ b/Rocket.md @@ -85,7 +85,7 @@ Redis中setnx不支持设置过期时间,做分布式锁时要想避免某一 ### 如何解决 Redis 缓存穿透问题 1. 在接口做校验 -2. 存null值(缓存击穿加锁) +2. 存null值(缓存击穿加锁,或设置不过期) 3. 布隆过滤器拦截: 将所有可能的查询key 先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。 ### Redis的持久化机制 @@ -151,22 +151,6 @@ Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚 | 可重复读 | 否 | 否 | 是 | | 串行化 | 否 | 否 | 否 | -### MVCC多版本并发控制 - -MVCC在RR(可重复读)级别解决了可能出现不可重复读的问题,同时解决了读数据情况下的幻读问题(对于修改新增的操作依旧存在幻读问题)。它的实现原理主要是依赖记录中的 3个隐式字段,undolog指针 ,Read View 来实现的。 - -#### 隐式字段 - -每行记录除了我们自定义的字段外,还有数据库隐式定义的最近修改(修改/插入)事务ID;回滚指针,指向这条记录的上一个版本;隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引。 - -#### Read View - -Read View就是事务进行快照读操作的时候生产的读视图,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。 - -### Next-Key Lock - -InnoDB 采用 Next-Key Lock 解决幻读问题。在`insert into test(xid) values (1), (3), (5), (8), (11);`后,由于xid上是有索引的,该算法总是会去锁住索引记录。现在,该索引可能被锁住的范围如下:(-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, +∞)。Session A(`select * from test where id = 8 for update`)执行后会锁住的范围:(5, 8], (8, 11]。除了锁住8所在的范围,还会锁住下一个范围,所谓Next-Key。 - ### Mysql的逻辑结构 * 最上层的服务类似其他CS结构,比如连接处理,授权处理。 @@ -181,7 +165,7 @@ SQL的执行顺序:from---where--group by---having---select---order by * undoLog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。 * redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的ib_logfile0和ib_logfile1。为了得到更高的可靠性,用户可以设置多个的镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB存储引擎先写重做日志文件1,当达到文件的最后时,会切换至重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中。 -* MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。 +* MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 * binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复。 ### binlog和redolog的区别 @@ -192,6 +176,10 @@ SQL的执行顺序:from---where--group by---having---select---order by 4. binlog不是循环使用,在写满或者重启之后,会生成新的binlog文件,redolog是循环使用。 5. binlog可以作为恢复数据使用,主从复制搭建,redolog作为异常宕机或者介质故障后的数据恢复使用。 +### Next-Key Lock + +InnoDB 采用 Next-Key Lock 解决幻读问题。在`insert into test(xid) values (1), (3), (5), (8), (11);`后,由于xid上是有索引的,该算法总是会去锁住索引记录。现在,该索引可能被锁住的范围如下:(-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, +∞)。Session A(`select * from test where id = 8 for update`)执行后会锁住的范围:(5, 8], (8, 11]。除了锁住8所在的范围,还会锁住下一个范围,所谓Next-Key。 + ### InnoDB的关键特性 1. 插入缓冲:对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。 @@ -275,15 +263,32 @@ JVM引入动态年龄计算,主要基于如下两点考虑: 1. 引用计数法:引用计数法是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。 2. 可达性分析算法:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。 +### CMS的执行过程 + +1. 初始标记(STW initial mark):这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。 +2. 并发标记(Concurrent marking):这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。 +3. 并发预清理(Concurrent precleaning):并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。 +4. 重新标记(STW remark):这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。 +5. 并发清理(Concurrent sweeping):清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。 +6. 并发重置(Concurrent reset):这个阶段,重置CMS收集器的数据结构状态,等待下一次垃圾回收。 + +### G1的执行过程 + +1. 标记阶段:首先是初始标记(Initial-Mark),这个阶段也是停顿的(stop-the-word),并且会稍带触发一次yong GC。 +2. 并发标记:这个过程在整个堆中进行,并且和应用程序并发运行。并发标记过程可能被yong GC中断。在并发标记阶段,如果发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,每个区域的对象活性(区域中存活对象的比例)被计算。 +3. 再标记:这个阶段是用来补充收集并发标记阶段产新的新垃圾。与之不同的是,G1中采用了更快的算法:SATB。 +4. 清理阶段:选择活性低的区域(同时考虑停顿时间),等待下次yong GC一起收集,对应GC log: [GC pause (mixed)],这个过程也会有停顿(STW)。 +5. 回收/完成:新的yong GC清理被计算好的区域。但是有一些区域还是可能存在垃圾对象,可能是这些区域中对象活性较高,回收不划算,也肯能是为了迎合用户设置的时间,不得不舍弃一些区域的收集。 + ### G1和CMS的比较 -1. CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低手机停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。 +1. CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。 2. CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。 3. CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。 4. G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。 5. 从JDK 9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。 6. G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。 -7. G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。 +7. G1 需要记忆集来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。(Card Table(CMS中)的结构是一个连续的byte[]数组,扫描Card Table的时间比扫描整个老年代的代价要小很多!G1也参照了这个思路,不过采用了一种新的数据结构 Remembered Set 简称Rset。RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。 这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。每个Region都有一个对应的Rset。) ### 哪些对象可以作为GC Roots @@ -447,7 +452,7 @@ JVM引入动态年龄计算,主要基于如下两点考虑: 从GC日志中找到长时间停顿的具体日志信息(添加了-XX:+PrintReferenceGC参数), 找到的日志片段如下所示。从日志中可以看出,真正执行GC动作的时间不是很长,但从准 备开始GC,到真正开始GC之间所消耗的时间却占了绝大部分。 - 除GC日志之外,还观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源 管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它 的工作内存被自动交换到磁盘的页面文件之中了,这样发生GC时就有可能因为恢复页面文 件的操作而导致不正常的GC停顿。在Java的GUI程序中要避免这种现象,可以 加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。 + 除GC日志之外,还观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源 管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它的工作内存被自动交换到磁盘的页面文件之中了,这样发生GC时就有可能因为恢复页面文件的操作而导致不正常的GC停顿。在Java的GUI程序中要避免这种现象,可以 加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。 ## Java基础 @@ -464,7 +469,7 @@ HashMap的环:若当前线程此时获得ertry节点,但是被线程中断 ### HashMap如果我想要让自己的Object作为K应该怎么办 1. 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; -2. 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性; +2. 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性(Java建议重写equal方法的时候需重写hashcode的方法) ### volatile @@ -503,7 +508,7 @@ AQS有两个队列,同步对列和条件队列。同步队列依赖一个双 ### 为什么要使用线程池 1. 减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 -2. 可以根据系统的承受能力,调整线程池中工作线程的数目,放置因为消耗过多的内存,而把服务器累趴下 +2. 可以根据系统的承受能力,调整线程池中工作线程的数目,放置因为消耗过多的内存,而把服务器累趴下。 ### 核心线程池ThreadPoolExecutor内部参数 From 3883f0520a9bbcec6f3f1539d00912cdc0887691 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 9 Jun 2020 00:59:44 +0800 Subject: [PATCH 10/80] update --- .idea/workspace.xml | 39 ++++++++++++++++++--------------------- Rocket.md | 10 ++++++++-- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3ffc014..7e70ea3 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -268,14 +268,7 @@ - - - - 1581851491537 - 1581855774959 @@ -613,7 +606,14 @@ - @@ -689,20 +689,20 @@ - - - + - + @@ -769,11 +769,11 @@ - + - + @@ -786,14 +786,11 @@ - - + + - - - - + diff --git a/Rocket.md b/Rocket.md index 733e4ef..cc7de70 100644 --- a/Rocket.md +++ b/Rocket.md @@ -2,7 +2,7 @@ ## ZooKeeper ### CAP定理: -一个分布式系统不可能同时满足以下三种,一致性(C:Consistency),可用性(A:Available),分区容错性(P:Partition Tolerance).在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。 +一个分布式系统不可能在满足分区容错性(P)的情况下同时满足一致性(C)和可用性(A:)。在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。 ### BASE理论 @@ -20,7 +20,7 @@ BASE理论是基本可用,软状态,最终一致性三个短语的缩写。B ### ZAB协议: -ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Zookeeper 集群刚刚启动或者Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 +ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Zookeeper 集群刚刚启动或者Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案(超过半数同意)来进行事务请求处理。 ### 选举算法和流程:FastLeaderElection(默认提供的选举算法) @@ -31,6 +31,12 @@ ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Z 4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 5. 服务器5启动,后面的逻辑同服务器4成为follower。 +### zk中的监控原理 + +zk类似于linux中的目录节点树方式的数据存储,即分层命名空间,zk并不是专门存储数据的,它的作用是主要是维护和监控存储数据的状态变化,通过监控这些数据状态的变化,从而可以达到基于数据的集群管理,zk中的节点的数据上限时1M。 + +client端会对某个znode建立一个watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。 + ## Redis ### 应用场景 From ca844d9f1d084fa0371b3379179f496dcc1441eb Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Tue, 9 Jun 2020 23:39:27 +0800 Subject: [PATCH 11/80] update --- .idea/workspace.xml | 56 +++++++++++++++++++++++++-------------------- Rocket.md | 35 ++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 27 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 7e70ea3..bdfb14d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,5 +1,23 @@ + + + + + + + + + + + + + + + + + - - - - - @@ -269,6 +268,7 @@ + 1581855774959 @@ -617,7 +617,7 @@ - + + + + + - + - + - - + + - + @@ -769,11 +768,11 @@ - + - + @@ -791,6 +790,10 @@ + + + + From 7a29a75b85ad9355e24e3c9f711bbb4b680e176d Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Tue, 16 Jun 2020 18:36:11 +0800 Subject: [PATCH 13/80] update --- .idea/workspace.xml | 78 +++++++++++++++++++++++++-------------------- Rocket.md | 45 +++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 36 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index bdfb14d..a3da4cc 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -19,7 +19,7 @@ - + + + + + + @@ -619,7 +626,7 @@ - - - + + - + @@ -800,10 +807,10 @@ - + - + diff --git a/Rocket.md b/Rocket.md index 434ea63..0440c9d 100644 --- a/Rocket.md +++ b/Rocket.md @@ -172,7 +172,6 @@ SQL的执行顺序:from---where--group by---having---select---order by * undoLog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。 * redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的ib_logfile0和ib_logfile1。为了得到更高的可靠性,用户可以设置多个的镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB存储引擎先写重做日志文件1,当达到文件的最后时,会切换至重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中。 * MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 -* binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复。 ### binlog和redolog的区别 @@ -740,29 +739,6 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 4. 信号量:不能传递复杂消息,只能用来同步。 5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。 -### 内存管理有哪几种方式 - -1. 块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。 -2. 页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。 -3. 段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上。 -4. 段页式管理:结合了段式管理和页式管理的优点。将程序分成若干段,每个段分成若干页。段页式管理每取一数据,要访问3次内存。 - -### 页面置换算法 - -1. 最佳置换算法OPT:只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。 -2. 先进先出置换算法FIFO:简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面。 -3. 最近最久未使用算法LRU:算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。 -4. 时钟算法clock(也被称为是最近未使用算法NRU):页面设置一个访问位,并将页面链接为一个环形队列,页面被访问的时候访问位设置为1。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面。 -5. 改进型Clock算法:在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问位和修改位都是0的页面,其次是访问位为0修改位为1的页面。 -6. LFU最少使用算法LFU:设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。 - -### 操作系统中进程调度策略有哪几种 - -1. 先来先服务调度算法FCFS:队列实现,非抢占,先请求CPU的进程先分配到CPU,可以作为作业调度算法也可以作为进程调度算法;按作业或者进程到达的先后顺序依次调度,对于长作业比较有利. -2. 最短作业优先调度算法SJF:作业调度算法,算法从就绪队列中选择估计时间最短的作业进行处理,直到得出结果或者无法继续执行,平均等待时间最短,但难以知道下一个CPU区间长度;缺点:不利于长作业;未考虑作业的重要性;运行时间是预估的,并不靠谱. -3. 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿. -4. 时间片轮转调度算法(可抢占的):按到达的先后对进程放入队列中,然后给队首进程分配CPU时间片,时间片用完之后计时器发出中断,暂停当前进程并将其放到队列尾部,循环 ;队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 - ### 死锁的4个必要条件 1. 互斥条件:一个资源每次只能被一个线程使用; @@ -794,15 +770,7 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 4. 服务端响应HTTP响应报文,报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。 5. 浏览器解析渲染 -### 计算机网络的五层模型 - -1. 应用层:为操作系统或网络应用程序提供访问网络服务的接口 ,通过应用进程间的交互完成特定网络应用。应用层定义的是应用进程间通信和交互的规则。(HTTP,FTP,SMTP,RPC) -2. 传输层:负责向两个主机中进程之间的通信提供通用数据服务。(TCP,UDP) -3. 网络层:负责对数据包进行路由选择和存储转发。(IP,ICMP(ping命令)) -4. 数据链路层:两个相邻节点之间传送数据时,数据链路层将网络层交下来的IP数据报组装成帧,在两个相邻的链路上传送帧(frame)。每一帧包括数据和必要的控制信息。 -5. 物理层:物理层所传数据单位是比特(bit)。物理层要考虑用多大的电压代表1 或 0 ,以及接受方如何识别发送方所发送的比特。 - -### tcp和 udp区别 +### tcp和udp区别 1. TCP面向连接,UDP是无连接的,即发送数据之前不需要建立连接。 2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。 @@ -811,7 +779,7 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 5. TCP首部开销20字节,UDP的首部开销小,只有8个字节。 6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。 -### tcp和 udp的优点 +### tcp和udp的优点 * TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。 * UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP。 @@ -867,50 +835,7 @@ void quick_sort(int a[], int low, int high){ } ``` -5. 堆排序:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。 -```java -public class Test { - - public void sort(int[] arr) { - for (int i = arr.length / 2 - 1; i >= 0; i--) { - adjustHeap(arr, i, arr.length); - } - for (int j = arr.length - 1; j > 0; j--) { - swap(arr, 0, j); - adjustHeap(arr, 0, j); - } - - } - - public void adjustHeap(int[] arr, int i, int length) { - int temp = arr[i]; - for (int k = i * 2 + 1; k < length; k = k * 2 + 1) { - if (k + 1 < length && arr[k] < arr[k + 1]) { - k++; - } - if (arr[k] > temp) { - arr[i] = arr[k]; - i = k; - } else { - break; - } - } - arr[i] = temp; - } - - public void swap(int[] arr, int a, int b) { - int temp = arr[a]; - arr[a] = arr[b]; - arr[b] = temp; - } - - public static void main(String[] args) { - int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1}; - new Test().sort(arr); - System.out.println(Arrays.toString(arr)); - } -} -``` +5. 堆排序:假设序列有n个元素,先将这n建成大顶堆,然后取堆顶元素,与序列第n个元素交换,然后调整前n-1元素,使其重新成为堆,然后再取堆顶元素,与第n-1个元素交换,再调整前n-2个元素...直至整个序列有序。 6. 希尔排序:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。 7. 归并排序:把有序表划分成元素个数尽量相等的两半,把两半元素分别排序,两个有序表合并成一个 From a497e67094f461ff147c679532772b525b28cc7e Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Mon, 22 Jun 2020 21:59:30 +0800 Subject: [PATCH 16/80] add q581 --- .idea/workspace.xml | 71 +++++++++++-------- README.md | 1 + .../Solution.java" | 32 +++++++++ 3 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 "src/\346\225\260\347\273\204\346\223\215\344\275\234/q581_\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204/Solution.java" diff --git a/.idea/workspace.xml b/.idea/workspace.xml index a3da4cc..65427de 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -20,8 +20,8 @@ + - + - @@ -97,7 +97,7 @@ - + - - @@ -271,14 +271,11 @@ - - - - 1581857522107 - 1581858072286 @@ -616,7 +613,14 @@ - @@ -712,42 +716,43 @@ - - + + + - + - + - + - + - + - + - + - + @@ -782,12 +787,12 @@ - + - + @@ -805,6 +810,10 @@ + + + + diff --git a/README.md b/README.md index 6cea521..0a53dee 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ * [q54_螺旋矩阵](/src/数组操作/q54_螺旋矩阵) * [q73_矩阵置零](/src/数组操作/q73_矩阵置零) * [q78_子集](/src/数组操作/q78_子集) +* [q581_最短无序连续子数组](/src/数组操作/q581_最短无序连续子数组) * [q945_使数组唯一的最小增量](/src/数组操作/q945_使数组唯一的最小增量) ### 栈相关 diff --git "a/src/\346\225\260\347\273\204\346\223\215\344\275\234/q581_\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204/Solution.java" "b/src/\346\225\260\347\273\204\346\223\215\344\275\234/q581_\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204/Solution.java" new file mode 100644 index 0000000..491b5ae --- /dev/null +++ "b/src/\346\225\260\347\273\204\346\223\215\344\275\234/q581_\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204/Solution.java" @@ -0,0 +1,32 @@ +package 数组操作.q581_最短无序连续子数组; + +import java.util.Arrays; + +/** + * 利用排序 o(n*log(n)) + */ +public class Solution { + + public int findUnsortedSubarray(int[] nums) { + if (nums == null || nums.length < 1) { + return 0; + } + + int[] cloneNums = nums.clone(); + Arrays.sort(nums); + + int begin = Integer.MAX_VALUE; + int end = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != cloneNums[i]) { + begin = Math.min(begin, i); + end = Math.max(end, i); + } + } + return Math.max(end - begin + 1, 0); + } + + public static void main(String[] args) { + new Solution().findUnsortedSubarray(new int[]{2, 6, 4, 8, 10, 9, 15}); + } +} From 4b28b6580fb9eff667fe3f6d555f8939c52958bf Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 23 Jun 2020 11:09:48 +0800 Subject: [PATCH 17/80] update --- .idea/workspace.xml | 36 +++++++++++++++++++----------------- Rocket.md | 4 ++++ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index fa90d3c..d10f85c 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -20,8 +20,8 @@ - + @@ -709,10 +711,10 @@ - + - + @@ -784,10 +786,10 @@ - - + + - + @@ -807,11 +809,11 @@ - - + + - + diff --git a/Rocket.md b/Rocket.md index 0440c9d..474124d 100644 --- a/Rocket.md +++ b/Rocket.md @@ -719,6 +719,10 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 4. 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉。 5. 系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。 +### 多线程和单线程 + +线程不是越多越好,假如你的业务逻辑全部是计算型的(CPU密集型),不涉及到IO,并且只有一个核心。那肯定一个线程最好,多一个线程就多一点线程切换的计算,CPU不能完完全全的把计算能力放在业务计算上面,线程越多就会造成CPU利用率(用在业务计算的时间/总的时间)下降。但是在WEB场景下,业务并不是CPU密集型任务,而是IO密集型的任务,一个线程是不合适,如果一个线程在等待数据时,把CPU的计算能力交给其他线程,这样也能充分的利用CPU资源。但是线程数量也要有个限度,一般线程数有一个公式:最佳启动线程数=[任务执行时间/(任务执行时间-IO等待时间)]*CPU内核数超过这个数量,CPU要进行多余的线程切换从而浪费计算能力,低于这个数量,CPU要进行IO等待从而造成计算能力不饱和。总之就是要尽可能的榨取CPU的计算能力。如果你的CPU处于饱和状态,并且没有多余的线程切换浪费,那么此时就是你服务的完美状态,如果再加大并发量,势必会造成性能上的下降。 + ### 进程的组成部分 进程由进程控制块(PCB)、程序段、数据段三部分组成。 From 6f403b5e5d6312519635cbd3f58afc14657d9887 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 23 Jun 2020 11:10:15 +0800 Subject: [PATCH 18/80] update --- .idea/workspace.xml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d10f85c..0c46b40 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -711,16 +711,16 @@ - - + + - - - + + + - + @@ -778,19 +778,20 @@ - - + + + - - + + - + @@ -809,11 +810,11 @@ - + - + From 4ef5993d49f634922cb972a76f22bf357e4cb306 Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 23 Jun 2020 11:13:22 +0800 Subject: [PATCH 19/80] update --- .idea/workspace.xml | 53 +++++++++++++++++++++++++-------------------- Rocket.md | 45 +++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0c46b40..2b70387 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -279,21 +279,7 @@ - - - - 1581858072286 - - - 1581858428131 - 1581954761859 @@ -624,7 +610,21 @@ - + @@ -711,16 +716,16 @@ - + - - + + - + @@ -787,11 +792,11 @@ - + - + @@ -810,11 +815,11 @@ - + - + diff --git a/Rocket.md b/Rocket.md index 474124d..58dfd49 100644 --- a/Rocket.md +++ b/Rocket.md @@ -839,7 +839,50 @@ void quick_sort(int a[], int low, int high){ } ``` -5. 堆排序:假设序列有n个元素,先将这n建成大顶堆,然后取堆顶元素,与序列第n个元素交换,然后调整前n-1元素,使其重新成为堆,然后再取堆顶元素,与第n-1个元素交换,再调整前n-2个元素...直至整个序列有序。 +5. 堆排序:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。 +```java +public class Test { + + public void sort(int[] arr) { + for (int i = arr.length / 2 - 1; i >= 0; i--) { + adjustHeap(arr, i, arr.length); + } + for (int j = arr.length - 1; j > 0; j--) { + swap(arr, 0, j); + adjustHeap(arr, 0, j); + } + + } + + public void adjustHeap(int[] arr, int i, int length) { + int temp = arr[i]; + for (int k = i * 2 + 1; k < length; k = k * 2 + 1) { + if (k + 1 < length && arr[k] < arr[k + 1]) { + k++; + } + if (arr[k] > temp) { + arr[i] = arr[k]; + i = k; + } else { + break; + } + } + arr[i] = temp; + } + + public void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } + + public static void main(String[] args) { + int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1}; + new Test().sort(arr); + System.out.println(Arrays.toString(arr)); + } +} +``` 6. 希尔排序:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。 7. 归并排序:把有序表划分成元素个数尽量相等的两半,把两半元素分别排序,两个有序表合并成一个 From 4d3e6d1352d74fc4932b940f02160aae4f5f67ee Mon Sep 17 00:00:00 2001 From: yuanguangxin <274841922@qq.com> Date: Tue, 23 Jun 2020 11:17:31 +0800 Subject: [PATCH 20/80] update --- .idea/workspace.xml | 12 ++++++------ Rocket.md | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2b70387..9e74fec 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -716,16 +716,16 @@ - + - - + + - + @@ -792,11 +792,11 @@ - + - + diff --git a/Rocket.md b/Rocket.md index 58dfd49..07932bc 100644 --- a/Rocket.md +++ b/Rocket.md @@ -2,7 +2,7 @@ ## ZooKeeper ### CAP定理: -一个分布式系统不可能在满足分区容错性(P)的情况下同时满足一致性(C)和可用性(A:)。在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。 +一个分布式系统不可能在满足分区容错性(P)的情况下同时满足一致性(C)和可用性(A)。在此ZooKeeper保证的是CP,ZooKeeper不能保证每次服务请求的可用性,在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。另外在进行leader选举时集群都是不可用,所以说,ZooKeeper不能保证服务可用性。 ### BASE理论 @@ -743,6 +743,29 @@ Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers 4. 信号量:不能传递复杂消息,只能用来同步。 5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。 +### 内存管理有哪几种方式 + +1. 块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。 +2. 页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。 +3. 段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上。 +4. 段页式管理:结合了段式管理和页式管理的优点。将程序分成若干段,每个段分成若干页。段页式管理每取一数据,要访问3次内存。 + +### 页面置换算法 + +1. 最佳置换算法OPT:只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。 +2. 先进先出置换算法FIFO:简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面。 +3. 最近最久未使用算法LRU:算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。 +4. 时钟算法clock(也被称为是最近未使用算法NRU):页面设置一个访问位,并将页面链接为一个环形队列,页面被访问的时候访问位设置为1。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面。 +5. 改进型Clock算法:在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问位和修改位都是0的页面,其次是访问位为0修改位为1的页面。 +6. LFU最少使用算法LFU:设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。 + +### 操作系统中进程调度策略有哪几种 + +1. 先来先服务调度算法FCFS:队列实现,非抢占,先请求CPU的进程先分配到CPU,可以作为作业调度算法也可以作为进程调度算法;按作业或者进程到达的先后顺序依次调度,对于长作业比较有利. +2. 最短作业优先调度算法SJF:作业调度算法,算法从就绪队列中选择估计时间最短的作业进行处理,直到得出结果或者无法继续执行,平均等待时间最短,但难以知道下一个CPU区间长度;缺点:不利于长作业;未考虑作业的重要性;运行时间是预估的,并不靠谱. +3. 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿. +4. 时间片轮转调度算法(可抢占的):按到达的先后对进程放入队列中,然后给队首进程分配CPU时间片,时间片用完之后计时器发出中断,暂停当前进程并将其放到队列尾部,循环 ;队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 + ### 死锁的4个必要条件 1. 互斥条件:一个资源每次只能被一个线程使用; From e93589cf5a7315082d421461c911fb6483d6741d Mon Sep 17 00:00:00 2001 From: yuanguangxin Date: Tue, 23 Jun 2020 11:18:27 +0800 Subject: [PATCH 21/80] add q581 --- .idea/workspace.xml | 82 +++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 65427de..4377c26 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -19,8 +19,7 @@ - - - - - - - - - - - - -