diff --git "a/MD/Java\345\237\272\347\241\200-IO.md" "b/MD/Java\345\237\272\347\241\200-IO.md" index 24334a0..b5c1ee2 100644 --- "a/MD/Java\345\237\272\347\241\200-IO.md" +++ "b/MD/Java\345\237\272\347\241\200-IO.md" @@ -1,26 +1,30 @@ +同步/异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 。所谓同步,就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果 + +阻塞/非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 + ## BIO Block-IO:InputStream和OutputStream,Reader和Writer。属于同步阻塞模型 同步阻塞:一个请求占用一个进程处理,先等待数据准备好,然后从内核向进程复制数据,最后处理完数据后返回 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/BIO.png) +![](../images/BIO.png) ## NIO NonBlock-IO:Channel、Buffer、Selector。属于IO多路复用的同步非阻塞模型 同步非阻塞:进程先将一个套接字在内核中设置成非阻塞再等待数据准备好,在这个过程中反复轮询内核数据是否准备好,准备好之后最后处理数据返回 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/NIO-1.png) +![](../images/NIO-1.png) IO多路复用:同步非阻塞的优化版本,区别在于IO多路复用阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的IO系统调用上。换句话说,轮询机制被优化成通知机制,多个连接公用一个阻塞对象,进程只需要在一个阻塞对象上等待,无需再轮询所有连接 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/NIO-3.png) +![](../images/NIO-3.png) 在Java的NIO中,是基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector用于监听多个通道的事件(比如:连接打开,数据到达) 因此,单个线程可以监听多个数据通道,Selector的底层实现是epoll/poll/select的IO多路复用模型,select方法会一直阻塞,直到channel中有事件就绪: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/NIO-4.png) +![](../images/NIO-4.png) 与BIO区别如下: @@ -31,11 +35,11 @@ IO多路复用:同步非阻塞的优化版本,区别在于IO多路复用阻 ## AIO Asynchronous IO:属于事件和回调机制的异步非阻塞模型 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/AIO.png) +![](../images/AIO.png) AIO得到结果的方式: * 基于回调:实现CompletionHandler接口,调用时触发回调函数 * 返回Future:通过isDone()查看是否准备好,通过get()等待返回数据 -但要实现真正的异步非阻塞IO,需要操作系统支持,Windows支持而Linux不完善 \ No newline at end of file +但要实现真正的异步非阻塞IO,需要操作系统支持,Windows支持而Linux不完善 diff --git "a/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" "b/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" index 48a4049..92e8d12 100644 --- "a/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" +++ "b/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" @@ -2,7 +2,7 @@ ### Java内存区域的分配 JVM虚拟机内存模型实现规范: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/JVM规范.png) +![](../images/JVM规范.png) 按线程是否共享分为以下区域: @@ -20,7 +20,7 @@ JVM虚拟机内存模型实现规范: 以HotSpot虚拟机实现为例,Java8中内存区域如下: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/JVM1.8.png) +![](../images/JVM1.8.png) 与规范中的区别: @@ -30,9 +30,15 @@ JVM虚拟机内存模型实现规范: ### Java对象不都是分配在堆上 #### 逃逸分析 -逃逸是指在某个方法之内创建的对象除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收。由于其被其它变量引用,由于无法回收,即称为逃逸。 +通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。 -逃逸分析技术可以分析出某个对象是否永远只在某个方法、线程的范围内,并没有“逃逸”出这个范围,逃逸分析的一个结果就是对于某些未逃逸对象可以直接在栈上分配提高对象分配回收效率,对象占用的空间会随栈帧的出栈而销毁。 +逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。 + +使用逃逸分析,编译器可以对代码做如下优化: + +1. 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。 +2. 将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。 +3. 分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。 ### 类加载机制 #### 加载过程 @@ -60,7 +66,7 @@ JVM虚拟机内存模型实现规范: 3. 为了实现代码热替换,OSGi是为了实现自己的类加载逻辑,用平级查找的逻辑替换掉了向下传递的逻辑。但其实可以不破坏双亲委派逻辑而是自定义类加载器来达到代码热替换。比如[这篇文章](https://www.cnblogs.com/pfxiong/p/4070462.html) ### 内存分配(堆上的内存分配) -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/堆的内存分配.png) +![](../images/堆的内存分配.png) #### 新生代 ##### 进入条件 优先选择在新生代的Eden区被分配。 @@ -85,8 +91,8 @@ GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引 老年代因为存活率高、没有分配担保空间,所以使用“标记-清理”或者“标记-整理”算法 复制算法:将可用内存按容量划分为Eden、from survivor、to survivor,分配的时候使用Eden和一个survivor,Minor GC后将存活的对象复制到另一个survivor,然后将原来已使用的内存一次清理掉。这样没有内存碎片。 -标记-清除:首先标记出所有需要回收的对象,标记完成后统一回收被标记的对象。会产生大量碎片,导致无法分配大对象从而导致频繁GC。 -标记-整理:首先标记出所有需要回收的对象,让所有存活的对象向一端移动。 +标记-清除:标记出存在引用链的对象,回收未被标记的对象。会产生大量碎片,导致无法分配大对象从而导致频繁GC。 +标记-整理:标记出存在引用链的对象,让所有存活的对象向一端移动。 #### Minor GC条件 当Eden区空间不足以继续分配对象,发起Minor GC。 @@ -122,6 +128,32 @@ G1将堆划分为多个大小固定的独立区域,根据每次允许的收集 参数:`-XX:+UseG1GC` +### G1的原理,相比CMS的优势 +G1的工作原理: +内存布局: +G1将堆内存划分为多个大小相等的区域(Region),每个区域可以是Eden、Survivor、老年代的一部分或整个Humongous对象(大对象)。这样的划分使得收集可以更细粒度地进行。 +并发标记-整理: +G1使用多阶段的并发标记过程来识别存活对象。首先进行初步标记,然后进行根搜索标记,最后进行重新标记,这些步骤尽可能与应用程序线程并发执行。 +基于目标的收集: +G1根据用户设定的暂停时间目标来决定收集哪些区域。它优先回收垃圾最多(即回收效益最高)的区域,以尽快满足内存需求,这就是“Garbage-First”命名的由来。 +混合收集周期: +G1执行混合垃圾收集,不仅回收年轻代,也回收一部分老年代区域,这样可以更均匀地分散垃圾回收的压力,减少老年代增长导致的Full GC风险。 +空间整合: +G1在回收过程中会进行空间整理,减少内存碎片,这是与CMS的一个重要区别,后者使用标记-清除算法,容易产生碎片。 + +相比CMS的优势: +更好的内存碎片管理: +G1通过复制和整理算法减少了内存碎片,提高了内存使用的效率,而CMS使用标记-清除算法后易造成碎片化。 +可预测的暂停时间: +G1允许用户设定暂停时间目标,通过动态调整收集策略来尽量满足这一目标,使得应用的响应时间更加可预测,适合对延迟敏感的服务。 +自动内存管理: +G1自动管理年轻代和老年代的大小,不需要手动配置比例,降低了调优难度。 +更大的堆支持: +G1设计之初就考虑了大容量堆的管理,能够有效管理数十GB甚至上百GB的堆内存,而CMS在大堆上的表现可能不佳。 +整体性能和吞吐量: +在很多场景下,G1能够提供与CMS相当甚至更好的吞吐量,同时保持较低的暂停时间。 + + ## Stop The World Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互 @@ -134,7 +166,7 @@ STW总会发生,不管是新生代还是老年代,比如CMS在初始标记 实现:volatile、synchronized、final、concurrent包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/j12.jpg) +![](../images/j12.jpg) 主内存:所有变量都保存在主内存中 工作内存:每个线程的独立内存,保存了该线程使用到的变量的主内存副本拷贝,线程对变量的操作必须在工作内存中进行 @@ -175,4 +207,18 @@ STW总会发生,不管是新生代还是老年代,比如CMS在初始标记 案例:https://www.wangtianyi.top/blog/2018/07/27/jvmdiao-you-ru-men-er-shi-zhan-diao-you-parallelshou-ji-qi/ -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ + +## java中的==和equals有什么区别 +==: + +1. ==操作符专门用来比较变量的值是否相同。如果比较的对象是基本数据类型,则比较数值是否相等;如果比较的是引用数据类型,则比较的是对象的内存地址是否相等。 +2. 因为Java只有值传递,所以对于==来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。 + +equals: + +1. equals方法常用来比较对象的内容是否相同。 +2. equals()方法存在于Object类中,而Object类是所有类的直接或间接父类。 +3. 未重写equals方法的类:Object中的equals方法实际使用的也是==操作符,比较的是他们的内存地址是否同一地址。 +4. 重写了equals方法的类:实现该类自己的equals方法比较逻辑(一般是比较对象的内容是否相同)。比如: +5. String:比较字符串内容,内容相同这相同; +6. Integer:比较对应的基本数据类型int的值是否相同(==操作符)。 diff --git "a/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" "b/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" index 0e747f9..a06a026 100644 --- "a/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" +++ "b/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" @@ -2,7 +2,7 @@ ### 线程的生命周期 新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/j13.png) +![](../images/j13.png) ### 问:你怎么理解多线程的 @@ -40,6 +40,42 @@ notifyAll:唤醒等待队列中等待该对象锁的全部线程,让其竞 **我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。** +#### 锁的种类 +* 公平锁/非公平锁 + +公平锁是指多个线程按照申请锁的顺序来获取锁 +ReentrantLock通过构造函数指定该锁是否是公平锁,默认是非公平锁。Synchronized是一种非公平锁 + +* 可重入锁 + +指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁 +ReentrantLock, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁 +Synchronized,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁 + +* 独享锁/共享锁 + +独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有 +ReentrantLock是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁 +读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的 +Synchronized是独享锁 + +* 乐观锁/悲观锁 + +悲观锁在Java中的使用,就是各种锁 +乐观锁在Java中的使用,是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新 + +* 偏向锁/轻量级锁/重量级锁 + +针对Synchronized的锁状态: +偏向锁是为了减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。指一段同步代码一直被一个线程所访问,在无竞争情况下把整个同步都消除掉 +轻量级锁是为了减少无实际竞争情况下,使用重量级锁产生的性能消耗。指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过CAS自旋的形式尝试获取锁,不会阻塞 +重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁 + +* 自旋锁/自适应自旋锁 + +指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU,默认自旋次数为10 +自适应自旋锁的自旋次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定,是虚拟机对锁状况的一个预测 + ### volatile 功能: @@ -47,7 +83,16 @@ notifyAll:唤醒等待队列中等待该对象锁的全部线程,让其竞 2. 禁止 JVM 进行的指令重排序。 ### ThreadLocal -使用`ThreadLocal userInfo = new ThreadLocal()`的方式,让每个线程内部都会维护一个ThreadLocalMap,里边包含若干了 Entry(K-V 键值对),每次存取都会先获取到当前线程ID,然后得到该线程对象中的Map,然后与Map交互。 +1. 定义:ThreadLocal变量每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。 +2. 原理:Thread线程类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有一个属于自己的ThreadLocalMap。ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。并发多线程场景下,每个线程Thread,在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而可以实现了线程隔离。 +3. Entry的Key为什么要设计成弱引用:如果Key使用强引用:当ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用的话,如果没有手动删除,ThreadLocal就不会被回收,会出现Entry的内存泄漏问题。如果Key使用弱引用:当ThreadLocal的对象被回收了,因为ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value则在下一次ThreadLocalMap调用set,get,remove的时候会被清除。 + + +1. 强引用:我们平时new了一个对象就是强引用,例如 Object obj = new Object();即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。 +2. 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。 +3. 弱引用:具有弱引用的对象拥有更短暂的生命周期。如果一个对象只有弱引用存在了,则下次GC将会回收掉该对象(不管当前内存空间足够与否)。 +4. 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。 + ### 线程池 #### 起源 @@ -67,6 +112,19 @@ new Thread弊端: * threadFactory:线程工厂,来创建线程 * rejectHandler:当拒绝任务提交时的策略(抛异常、用调用者所在的线程执行任务、丢弃队列中第一个任务执行当前任务、直接丢弃任务) +#### 线程池几个核心参数在CPU密集型、IO密集型业务场景的配置 +核心线程数(Core Pool Size) +CPU 密集型:CPU 密集型任务主要依赖处理器资源,因此推荐将核心线程数设置为等于 CPU 核心数或稍高于 CPU 核心数(例如 CPU核数 + 1),以充分利用硬件资源,减少上下文切换的开销。 +IO 密集型:IO 密集型任务在等待 IO 操作完成时不会占用 CPU,这时可以设置更多的核心线程,通常可以是 2 * CPU核数 + 1 或者更高,以便在等待 IO 期间能有更多线程准备处理后续的计算任务。 + +线程保持存活时间(Keep-Alive Time) +CPU 密集型:通常设置较短的保持存活时间,因为CPU密集型任务一旦完成,线程可能不需要再次激活,过多的空闲线程会浪费资源。 +IO 密集型:可以设置较长的保持存活时间,允许线程在等待下一次 IO 操作时保持一段时间的空闲状态,以备快速响应新的请求。 + +工作队列(Work Queue) +CPU 密集型:可以选择较小的有界阻塞队列,比如 SynchronousQueue,这可以防止过多的线程创建,保持核心线程始终忙碌。 +IO 密集型:可以使用较大的有界队列,如 LinkedBlockingQueue,允许更多的任务在等待,减少线程的创建和销毁。 + #### 创建线程的逻辑 以下任务提交逻辑来自ThreadPoolExecutor.execute方法: @@ -86,7 +144,7 @@ new Thread弊端: #### CountDownLatch 计数器闭锁是一个能阻塞主线程,让其他线程满足特定条件下主线程再继续执行的线程同步工具。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/countdownlatch.png) +![](../images/countdownlatch.png) 图中,A为主线程,A首先设置计数器的数到AQS的state中,当调用await方法之后,A线程阻塞,随后每次其他线程调用countDown的时候,将state减1,直到计数器为0的时候,A线程继续执行。 使用场景: @@ -128,7 +186,7 @@ public class CountDownLatchTest { #### CyclicBarrier 可以让一组线程相互等待,当每个线程都准备好之后,所有线程才继续执行的工具类 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/cyclicbarrier.png) +![](../images/cyclicbarrier.png) 与CountDownLatch类似,都是通过计数器实现的,当某个线程调用await之后,计数器减1,当计数器大于0时将等待的线程包装成AQS的Node放入等待队列中,当计数器为0时将等待队列中的Node拿出来执行。 @@ -190,4 +248,66 @@ public class PrintOddAndEvenShu { } ``` + +多线程交替打印alibaba: https://blog.csdn.net/CX610602108/article/details/106427979 +我自行实现的简单解法: +```java + +public class Test { + private int currentI = 0; + + private synchronized void printSpace(int inputLength) { + while (true) { + if (currentI == inputLength) { + currentI = 0; + System.out.print(" "); + this.notifyAll(); + } else { + try { + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + private synchronized void printLetter(int i, char c) { + while (true) { + if (i == currentI) { + currentI++; + System.out.print(c); + this.notifyAll(); + } else { + try { + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + private synchronized void print(String input) throws Exception { + Test test = new Test(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + int finalI = i; + Thread tt = new Thread(() -> test.printLetter(finalI, c)); + tt.start(); + } + Thread space = new Thread(() -> test.printSpace(input.length())); + space.start(); + space.join(); + } + + + public static void main(String[] args) throws Exception { + Test test = new Test(); + test.print("alibaba"); + } +} + +``` + 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" "b/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" index 9d04e51..deee610 100644 --- "a/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" +++ "b/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" @@ -9,23 +9,36 @@ |扩容之后容量为原来的1.5倍|无| ### HashMap -1. JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了红黑树(查找时间复杂度为 O(logn))来优化这个问题 +1. JDK 1.8 以前 HashMap 的实现是 数组+单链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了红黑树(查找时间复杂度为 O(logn))来优化这个问题 2. 为什么线程不安全?多线程PUT操作时可能会覆盖刚PUT进去的值;扩容操作会让链表形成环形数据结构,形成死循环 3. 容量的默认大小是 16,负载因子是 0.75,当 HashMap 的 size > 16*0.75 时就会发生扩容(容量和负载因子都可以自由调整)。 4. 为什么容量是2的倍数?在根据hashcode查找数组中元素时,取模性能远远低于与性能,且和2^n-1进行与操作能保证各种不同的hashcode对应的元素也能均匀分布在数组中 +### HashMap rehash过程: +1.7: +1. 空间不够用了,所以需要分配一个大一点的空间,然后保存在里面的内容需要重新计算 hash +2. 在准备好新的数组后,map会遍历数组的每个“桶”,然后遍历桶中的每个Entity,重新计算其hash值(也有可能不计算),找到新数组中的对应位置,以头插法插入新的链表 +3. 因为是头插法,因此新旧链表的元素位置会发生转置现象。元素迁移的过程中在多线程情境下有可能会触发死循环(无限进行链表反转),因此HashMap线程不安全 + +1.8: +1. 底层结构为:数组+单链表/红黑树。因此如果某个桶中的链表长度大于等于8了,则会判断当前的hashmap的容量是否大于64,如果小于64,则会进行扩容;如果大于64,则将链表转为红黑树 +2. java1.8+在扩容时,不需要重新计算元素的hash进行元素迁移。而是用原先位置key的hash值与旧数组的长度(oldCap)进行"与"操作。 +3. 如果结果是0,那么当前元素的桶位置不变。 +4. 如果结果为1,那么桶的位置就是原位置+原数组 长度 +5. 值得注意的是:为了防止java1.7之前元素迁移头插法在多线程是会造成死循环,java1.8+后使用尾插法 + ### ConcurrentHashMap原理 [http://www.jasongj.com/java/concurrenthashmap/](http://www.jasongj.com/java/concurrenthashmap/) HashTable 在每次同步执行时都要锁住整个结构。ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 将 hash 表分为 16 个桶(默认值) 最大并发个数就是Segment的个数,默认值是16,可以通过构造函数改变一经创建不可更改,这个值就是并发的粒度,每一个segment下面管理一个table数组,加锁的时候其实锁住的是整个segment #### Java7 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/j3.jpg) +![](../images/j3.jpg) ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。 #### Java8 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/j4.jpg) +![](../images/j4.jpg) 1. 为进一步提高并发性,放弃了分段锁,锁的级别控制在了更细粒度的table元素级别,也就是说只需要锁住这个链表的head节点,并不会影响其他的table元素的读写,好处在于并发的粒度更细,影响更小,从而并发效率更好 2. 使用CAS + synchronized 来保证实现put操作:如果Key对应的数组元素为null,则通过CAS操作将其设置为当前值。如果Key对应的数组元素(也即链表表头或者树的根元素)不为null,则对该元素使用synchronized关键字申请锁,然后进行操作。如果该put操作使得当前链表长度超过一定阈值,则将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)),插入操作完成之后如果所有元素的数量大于当前容量(默认16)*负载因子(默认0.75)就进行扩容。 diff --git "a/MD/Web\346\241\206\346\236\266-Spring.md" "b/MD/Web\346\241\206\346\236\266-Spring.md" index 2847f9a..ada11b3 100644 --- "a/MD/Web\346\241\206\346\236\266-Spring.md" +++ "b/MD/Web\346\241\206\346\236\266-Spring.md" @@ -30,9 +30,17 @@ Spring是个包含一系列功能的合集,如快速开发的Spring Boot,支 ### IOC(DI) -控制反转:原来是自己主动去new一个对象去用,现在是由容器工具配置文件创建实例让自己用,以前是自己去找妹子亲近,现在是有中介帮你找妹子,让你去挑选,说白了就是用面向接口编程和配置文件减少对象间的耦合,同时解决硬编码的问题(XML) +* 控制反转 -依赖注入:在运行过程中当你需要这个对象才给你实例化并注入其中,不需要管什么时候注入的,只需要写好成员变量和set方法 +由 Spring IOC 容器来负责对象的生命周期和对象之间的关系。IoC 容器控制对象的创建,依赖对象的获取被反转了 +没有 IoC 的时候我们都是在自己对象中主动去创建被依赖的对象,这是正转。但是有了 IoC 后,所依赖的对象直接由 IoC 容器创建后注入到被注入的对象中,依赖的对象由原来的主动获取变成被动接受,所以是反转 + +* 依赖注入 + +组件之间依赖关系由容器在运行期决定,由容器动态的将某个依赖关系注入到组件之中,提升组件重用的频率、灵活、可扩展 +通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现 + +注入方式:构造器注入、setter 方法注入、接口方式注入 ### Spring AOP #### 介绍 diff --git "a/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" "b/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" index 5c4afcb..a509eca 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" @@ -7,15 +7,57 @@ CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency( CAP理论就是说在分布式系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以考虑最差情况,分区容忍性是一般是需要实现的 -## 个人理解 -满足之后: -如果选择C,则需要将数据同步更新到所有节点上,每次写操作就都要等待全部节点写成功,会导致A的降低。 -如果选择A,就需要将数据复制到很多个节点,需要复制的数据很多,复制的过程缓慢,会导致C的降低 +虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。 -正常情况下,不存在CP还是AP的选择,可以做到CA,但如果网络出现分区(节点之间的网络连接不正常),就必须要为了满足P,而放弃C或A,放弃哪个?一般我们要保证的是系统不能挂掉(AP),然后通过同步数据的方式还原C,还可能让我们的系统挂一部分(CP),然后通过重启节点的方式还原A +BASE理论的核心思想是:BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)的缩写,它接受在短时间内可能存在的不一致性,但保证最终达到一致性。与ACID(原子性、一致性、隔离性、持久性)相比,BASE更注重系统的可用性和可伸缩性,牺牲了严格的即时一致性。 -对于不同业务也会需要考虑到不同的CAP选择,以电商网站为例,会员登录、个人设置、个人订单、购物车、搜索用AP,因为这些数据短时间内不一致不影响使用;后台的商品管理就需要CP,避免商品数量的不一致;支付功能需要CA,保证支付功能的安全稳定 +## 强一致如何实现 +什么是强一致性:在分布式存储系统中,强一致性是指系统保证任意时刻数据是一致的,即无论在任何节点上进行的操作,都能立即在所有节点上访问到最新的数据。 -BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 +分布式共识算法。其中,Paxos 和 Raft 是两个著名的共识算法,它们可以用于实现分布式系统的强一致性。这些算法通过选举和投票来确保各个节点的操作顺序一致,从而达到强一致性的要求。 +1. Paxos 算法通过多个阶段的提议、投票和决议过程来确保一个值被所有参与者接受。算法包括提议者(Proposer)、接受者(Acceptor)和学习者(Learner)的角色,并且使用一系列编号递增的消息来进行通信,确保在任何时候只有一个提议会被接受。 +2. Raft 使用了明确的领导者(Leader)角色,简化了状态机复制的过程。领导者负责处理所有的客户端请求并维护集群状态的一致性。它通过日志复制、心跳机制以及选举流程确保在任何时刻都有一个有效的领导者,并保持集群内的日志一致性。 +3. Raft 可以看作是对 Paxos 算法的一种工程化改进,它保留了 Paxos 对于分布式一致性问题的核心解决方案,同时通过对算法进行模块化和清晰化设计,极大地降低了开发者理解和实施分布式共识协议的难度 -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ + +## 数据一致性解决方案 +强一致性(Strong Consistency) +通过事务(如ACID事务)确保每次操作都看到最新的数据状态。这通常需要两阶段提交(2PC)或其他复杂的事务协议,但可能导致性能下降。 + +最终一致性(Eventual Consistency) +允许短暂的数据不一致,但保证在一段时间后所有副本间达到一致。这种模型在分布式系统中广泛使用,如Cassandra和Amazon DynamoDB。 + +分布式事务 +如两阶段提交(2PC)、三阶段提交(3PC)和多阶段提交(MPC)等协议,用于跨节点的事务协调。 + +复制策略 +同步复制:更新在所有副本之间同步完成,保证强一致性,但可能影响性能。 +异步复制:更新在主节点上完成,副本随后更新,牺牲一致性以换取性能。 + +PAXOS/Raft共识算法 +用于在分布式系统中选举领导者并达成一致性决策。 + +幂等性设计 +确保多次执行相同操作不会改变系统状态,有助于防止因重试或网络问题导致的数据不一致 + +## 高可用多活面试题 +解释什么是“多活”架构,并描述其在高可用性中的作用。 +多活架构是指在一个分布式系统中,多个数据中心或节点同时处于活动状态,每个节点都能独立处理请求,从而提高系统的可用性和容灾能力。当某个节点出现故障时,其他节点仍能继续提供服务。 + +描述一下典型的多活架构部署策略,例如如何处理数据同步和流量路由。 +多活架构通常采用数据同步机制(如主从复制、分布式数据库)保持数据一致性,同时使用智能DNS、负载均衡器或路由策略将流量分散到各个活节点。在故障发生时,可以快速切换流量路由,确保服务不中断。 + +如何在多活架构中实现故障检测和自动切换? +通过心跳检测、健康检查、Zookeeper或Consul等服务发现机制监控各个节点的状态,一旦检测到故障,立即触发流量切换策略,将流量导向其他正常运行的节点。 + +谈谈多活架构下的数据一致性挑战及解决方案 +数据一致性是多活架构中的主要挑战。可以采用异步复制、分布式事务(如2PC、Saga)、最终一致性等方法来平衡数据一致性和高可用性。 + +如何设计一个高可用多活系统?请简述关键要素。 +关键要素包括:负载均衡(如DNS轮询、硬件负载均衡器)、数据复制与一致性(如主从复制、分布式数据库)、故障检测与切换机制(如健康检查、心跳检测)、网络连通性保障(如SDN、专线连接)、自动化运维工具(如自动化部署、监控报警)。 + +多活架构面临的主要挑战有哪些?如何解决? +主要挑战包括数据一致性、网络延迟、故障检测与恢复、运维复杂度等。解决方案涉及采用合适的数据同步技术、优化网络架构(如使用CDN、专线)、实施自动化运维工具、以及建立完善的监控和告警体系。 + +在多活架构中,如何处理跨数据中心的事务? +可以采用分布式事务协调服务(如Seata、LRA),或者通过Saga模式、TCC(Try-Confirm-Cancel)模式来处理跨数据中心的长事务,确保事务的原子性和一致性。 diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\344\270\200\350\207\264\346\200\247hash.md" "b/MD/\345\210\206\345\270\203\345\274\217-\344\270\200\350\207\264\346\200\247hash.md" index 88b1da3..4e2b9c2 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-\344\270\200\350\207\264\346\200\247hash.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-\344\270\200\350\207\264\346\200\247hash.md" @@ -14,22 +14,22 @@ 一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 `0 ~ 2^32-1`。如下图: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash1.jpg) +![](../images/一致性hash1.jpg) 之后将各个节点散列到这个环上,可以用节点的 IP、hostname 这样的唯一性字段作为 Key 进行 `hash(key)`,散列之后如下: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash2.jpg) +![](../images/一致性hash2.jpg) 之后需要将数据定位到对应的节点上,使用同样的 `hash 函数` 将 Key 也映射到这个环上。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash3.jpg) +![](../images/一致性hash3.jpg) 这样按照顺时针方向就可以把 k1 定位到 `N1节点`,k2 定位到 `N3节点`,k3 定位到 `N2节点`。 ### 容错性 这时假设 N1 宕机了: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash4.jpg) +![](../images/一致性hash4.jpg) 依然根据顺时针方向,k2 和 k3 保持不变,只有 k1 被重新映射到了 N3。这样就很好的保证了容错性,当一个节点宕机时只会影响到少少部分的数据。 @@ -37,22 +37,22 @@ 当新增一个节点时: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash5.jpg) +![](../images/一致性hash5.jpg) -在 N2 和 N3 之间新增了一个节点 N4 ,这时会发现受印象的数据只有 k3,其余数据也是保持不变,所以这样也很好的保证了拓展性。 +在 N2 和 N3 之间新增了一个节点 N4 ,这时会发现受影响的数据只有 k3,其余数据也是保持不变,所以这样也很好的保证了拓展性。 ## 虚拟节点 到目前为止该算法依然也有点问题: 当节点较少时会出现数据分布不均匀的情况: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash6.jpg) +![](../images/一致性hash6.jpg) 这样会导致大部分数据都在 N1 节点,只有少量的数据在 N2 节点。 为了解决这个问题,一致哈希算法引入了虚拟节点。将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/一致性hash7.jpg) +![](../images/一致性hash7.jpg) 计算时可以在 IP 后加上编号来生成哈希值。 @@ -60,4 +60,15 @@ https://github.com/crossoverJie/JCSprout/blob/master/MD/Consistent-Hash.md +## Redis用了吗? +降低扩容缩容时传统哈希算法重建映射影响范围,一致性哈希不是唯一的解决办法。 + +redis 缓存采用了固定哈希槽的方式维护 Key 和存储节点的关系。每个节点负责一部分槽,槽的总数是固定的 16384,无论节点数怎么变, Key 进行哈希运算再对槽总数取模的值都是不变的。 + +插入数据时,先算出数据应该分配到哪个槽,再去查槽在哪个节点,这样就实现了把 Key 通过哈希算法均匀的分布到大量的槽上。槽分配到哪个节点是人为配置的,只要配置的槽均匀,最终节点上的 Key 就会均匀分布到物理节点,扩容缩容时也只需要迁移部分槽的数据。 + +在一致性哈希算法中,引入虚拟节点其实和固定哈希槽有异曲同工之妙,都是将最终存储的节点和数据的 Key 之间多做一层转换,避免节点总数的变动影响太多映射关系,虽然固定哈希槽不如一致性哈希加虚拟节点灵活,但是考虑到 redis 的存储的数据量级与多数分布式数据库相比差距很大, 固定哈希槽策略对于 redis 已经完全够用了。 + +一致性哈希算法的应用非常广泛,在分布式数据库 Cassandra 、缓存Memcache、分布式服务框架Dubbo中都有应用。 + 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\344\272\213\345\212\241.md" "b/MD/\345\210\206\345\270\203\345\274\217-\344\272\213\345\212\241.md" index fff0ea3..0272354 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-\344\272\213\345\212\241.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-\344\272\213\345\212\241.md" @@ -9,7 +9,7 @@ 如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/分布式事务1.png) +![](../images/分布式事务1.png) ### TCC 方案 TCC 的全称是:Try、Confirm、Cancel。 @@ -26,7 +26,7 @@ TCC 的全称是:Try、Confirm、Cancel。 但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码很难维护。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/分布式事务2.png) +![](../images/分布式事务2.png) ### 本地消息表 本地消息表其实是国外的 ebay 搞出来的这么一套思想。 @@ -42,7 +42,7 @@ TCC 的全称是:Try、Confirm、Cancel。 这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,会导致如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/分布式事务3.png) +![](../images/分布式事务3.png) ### 可靠消息最终一致性方案 这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。 @@ -56,7 +56,7 @@ TCC 的全称是:Try、Confirm、Cancel。 5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/分布式事务4.png) +![](../images/分布式事务4.png) ### 最大努力通知方案 这个方案的大致意思就是: diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\345\215\217\350\260\203\345\231\250.md" "b/MD/\345\210\206\345\270\203\345\274\217-\345\215\217\350\260\203\345\231\250.md" index 2c8d3e0..f82e197 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-\345\215\217\350\260\203\345\231\250.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-\345\215\217\350\260\203\345\231\250.md" @@ -1,22 +1,22 @@ ### 分布式协调 这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 立马就可以收到通知,完美解决。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/zookeeper1.png) +![](../images/zookeeper1.png) ### 分布式锁 举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/zookeeper2.png) +![](../images/zookeeper2.png) ### 元数据/配置信息管理 zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么? -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/zookeeper3.png) +![](../images/zookeeper3.png) ### HA高可用性 这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/zookeeper4.png) +![](../images/zookeeper4.png) https://github.com/doocs/advanced-java/blob/master/docs/distributed-system/zookeeper-application-scenarios.md diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\346\266\210\346\201\257\351\230\237\345\210\227.md" "b/MD/\345\210\206\345\270\203\345\274\217-\346\266\210\346\201\257\351\230\237\345\210\227.md" index 15eb1b2..02fd880 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-\346\266\210\346\201\257\351\230\237\345\210\227.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-\346\266\210\346\201\257\351\230\237\345\210\227.md" @@ -34,7 +34,7 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,刚消费 消息体通过hash分派到队列里,每个队列对应唯一一个消费者。比如下面的示例中,订单号相同的消息会被先后发送到同一个队列中: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/消息顺序性.jpg) +![](../images/消息顺序性.jpg) 在获取到路由信息以后,会根据MessageQueueSelector实现的算法来选择一个队列,同一个订单号获取到的肯定是同一个队列。 @@ -56,7 +56,7 @@ Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的,如果这上面有某个 partition 的 leader,那么此时会从 follower 中重新选举一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/kafka集群.png) +![](../images/kafka集群.png) https://github.com/doocs/advanced-java#%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97 https://dbaplus.cn/news-73-1123-1.html diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\351\224\201.md" "b/MD/\345\210\206\345\270\203\345\274\217-\351\224\201.md" index 02cb563..a1ced4a 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-\351\224\201.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-\351\224\201.md" @@ -71,7 +71,7 @@ public void lock(String key, String request, int timeout) throws InterruptedExce 缺点: 1. 单点故障问题 -2. 锁超时问题:如果A拿到锁之后设置了超时时长,但是业务还未执行完成且锁已经被释放,此时其他进程就会拿到锁从而执行相同的业务 +2. 锁超时问题:如果A拿到锁之后设置了超时时长,但是业务还未执行完成且锁已经被释放,此时其他进程就会拿到锁从而执行相同的业务。如何解决?Redission定时延长超时时长避免过期。为什么不直接设置为永不超时?为了防范业务方没写解锁方法或者发生异常之后无法进行解锁的问题 3. 轮询获取锁状态方式太过低效 ### 基于ZooKeeper diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" "b/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" new file mode 100644 index 0000000..25a8611 --- /dev/null +++ "b/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" @@ -0,0 +1,66 @@ +# 常见算法 +对于限流常见有两种算法: + +1. 漏桶算法 +2. 令牌桶算法 + +漏桶算法比较简单,就是将流量放入桶中,漏桶同时也按照一定的速率流出,如果流量过快的话就会溢出(漏桶并不会提高流出速率)。溢出的流量则直接丢弃。 + +如下图所示: + +![](../images/分布式-限流-2.png) + +漏桶算法虽说简单,但却不能应对实际场景,比如突然暴增的流量。 + +这时就需要用到令牌桶算法:令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有流量来时则取走一个或多个令牌。当桶中没有令牌则将当前请求丢弃或阻塞。 + +![](../images/分布式-限流-1.gif) + +相比之下令牌桶可以应对一定的突发流量. + +# RateLimiter实现 + +对于令牌桶的代码实现,可以直接使用Guava包中的RateLimiter。 + +```java +@Override +public BaseResponse getUserByFeignBatch(@RequestBody UserReqVO userReqVO) { + //调用远程服务 + OrderNoReqVO vo = new OrderNoReqVO() ; + vo.setReqNo(userReqVO.getReqNo()); + + RateLimiter limiter = RateLimiter.create(2.0) ; + //批量调用 + for (int i = 0 ;i< 10 ; i++){ + double acquire = limiter.acquire(); + logger.debug("获取令牌成功!,消耗=" + acquire); + BaseResponse orderNo = orderServiceClient.getOrderNo(vo); + logger.debug("远程返回:"+JSON.toJSONString(orderNo)); + } + + UserRes userRes = new UserRes() ; + userRes.setUserId(123); + userRes.setUserName("张三"); + + userRes.setReqNo(userReqVO.getReqNo()); + userRes.setCode(StatusEnum.SUCCESS.getCode()); + userRes.setMessage("成功"); + + return userRes ; +} +``` + +代码可以看出以每秒向桶中放入两个令牌,请求一次消耗一个令牌。所以每秒钟只能发送两个请求。按照图中的时间来看也确实如此(返回值是获取此令牌所消耗的时间,差不多也是每500ms一个)。 + +使用RateLimiter 有几个值得注意的地方: + +允许先消费,后付款,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。 + + +# 其他算法 +1. 固定窗口算法又叫计数器算法,是一种简单方便的限流算法。主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。虽然我们限制了 QPS 为 2,但是当遇到时间窗口的临界突变时,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 4 次。 +2. 滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。有效解决了窗口切换时可能会产生两倍于阈值流量请求的问题。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b39534226cf4981a4c4632f0cab335e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp) + + diff --git "a/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md" "b/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md" index bd3b963..a909bf8 100644 --- "a/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md" +++ "b/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md" @@ -4,7 +4,7 @@ 客户端应用进程向注册中心发起查询,来获取服务的位置。服务发现的一个重要作用就是提供一个可用的服务列表。 ## 原理 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/consul.jpg) +![](../images/consul.jpg) 1. 当User Service启动的时候,会向Consul发送一个POST请求,告诉Consul自己的IP和Port 2. Consul 接收到User Service的注册后,每隔10s(默认)会向User Service发送一个健康检查的请求,检验User Service是否健康(Consul其实支持其他健康检查机制) diff --git "a/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\351\205\215\347\275\256\344\270\255\345\277\203.md" "b/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\351\205\215\347\275\256\344\270\255\345\277\203.md" index 0939de5..640d453 100644 --- "a/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\351\205\215\347\275\256\344\270\255\345\277\203.md" +++ "b/MD/\345\276\256\346\234\215\345\212\241-\346\234\215\345\212\241\351\205\215\347\275\256\344\270\255\345\277\203.md" @@ -1,6 +1,6 @@ 我们的每个服务的配置文件都是在自身代码库中,当服务数量达到一定数量后,管理这些分散的配置文件会成为一个痛点。这节课我么就来解决配置文件管理的痛点 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/config.jpg) +![](../images/config.jpg) Spring Cloud Config的目标是将各个微服务的配置文件集中存储一个文件仓库中(比如系统目录,Git仓库等等),然后通过Config Server从文件仓库中去读取配置文件,而各个微服务作为Config Client通过给Config Server发送请求指令来获取特定的Profile的配置文件,从而为自身的应用提供配置信息。同时还提供配置文件自动刷新功能。 diff --git "a/MD/\345\276\256\346\234\215\345\212\241-\347\275\221\345\205\263.md" "b/MD/\345\276\256\346\234\215\345\212\241-\347\275\221\345\205\263.md" index 0306688..486fbe7 100644 --- "a/MD/\345\276\256\346\234\215\345\212\241-\347\275\221\345\205\263.md" +++ "b/MD/\345\276\256\346\234\215\345\212\241-\347\275\221\345\205\263.md" @@ -5,15 +5,15 @@ API网关。它是系统的单个入口服务器。它类似于面向对象的 ### 流量统一管理、路由 它用于解决微服务过于分散,没有一个统一的出入口进行流量管理、路由映射、监控、缓存、负载均衡的问题。 我们用两张图来解释: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/api-gateway-1.jpg) +![](../images/api-gateway-1.jpg) 如上图所示的mst-user-service, mst-good-service, mst-order-service,这些应用会需要一些通用的功能,比如Authentication, 这些功能过于分散,代码就需要在三个服务中都写一遍,因此需要有一个统一的出入口来管理流量,就像下图 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/api-gateway-2.jpg) +![](../images/api-gateway-2.jpg) ### 设备适配 还可以针对不同的渠道和客户端提供不同的API Gateway,对于该模式的使用由另外一个大家熟知的方式叫Backend for front-end, 在Backend for front-end模式当中,我们可以针对不同的客户端分别创建其BFF。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/api-gateway-3.jpg) +![](../images/api-gateway-3.jpg) ### 协议转换 客户端直接调用微服务可能会使用对网络不友好的协议。内部服务之间可能使用Thrift二进制RPC、gRPC协议。两个协议对浏览器不友好并且最好是在内部使用。应用应该在防火墙之外使用HTTP或者WebSocket之类的协议。 diff --git "a/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" "b/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" index 137d4bf..c2cebbb 100644 --- "a/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" +++ "b/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" @@ -33,6 +33,30 @@ MyISAM相对简单所以在效率上要优于InnoDB。如果系统插入和查 问:有个表特别大,字段是姓名、年龄、班级,如果调用`select * from table where name = xxx and age = xxx`该如何通过建立索引的方式优化查询速度? 答:由于mysql查询每次只能使用一个索引,如果在name、age两列上创建联合索引的话将带来更高的效率。如果我们创建了(name, age)的联合索引,那么其实相当于创建了(name, age)、(name)2个索引。因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。其次还要考虑该列的数据离散程度,如果有很多不同的值的话建议放在左边,name的离散程度也大于age。 +问:优化大数据量的分页查询? +1. 索引优化:确保用于排序和筛选的字段已经建立了索引 +2. 延迟关联:先获取主键,然后再根据主键获取完整的行数据。这样可以减少数据库在排序时需要处理的数据量。通过将 select * 转变为 select id,把符合条件的 id 筛选出来后,最后通过嵌套查询的方式按顺序取出 id 对应的行 +3. 业务限制:查询页数上限 +4. 使用其他数据结构如ES、分表 + +```sql +-- 优化前 +select * +from people +order by create_time desc +limit 5000000, 10; + +-- 优化后 +select a.* +from people a +inner join( + select id + from people + order by create_time desc + limit 5000000, 10 +) b ON a.id = b.id; +``` + #### 最左前缀匹配原则 定义:在检索数据时从联合索引的最左边开始匹配。一直向右匹配直到遇到范围查询(>/ 5 and d = 6,如果建立(a,b,c,d)索引,d是用不到索引的,如果建立(a,b,d,c)索引,则都可以用到,此外a,b,d的顺序可以任意调整 @@ -45,7 +69,14 @@ MyISAM相对简单所以在效率上要优于InnoDB。如果系统插入和查 4. 持久性(Durability):一个事务一旦提交,对数据库的修改应该永久保存。 不同事务隔离级别的问题: -[https://www.jianshu.com/p/4e3edbedb9a8](https://www.jianshu.com/p/4e3edbedb9a8) +1. 读未提交(Read Uncommitted):在这个级别下,一个事务可以读取到另一个事务尚未提交的数据变更,即脏读(Dirty Read)现象可能发生。这种隔离级别允许最大的并行处理能力,但并发事务间的数据一致性最弱。 +2. 读已提交(Read Committed):在这种级别下,一个事务只能看到其他事务已经提交的数据,解决了脏读的问题。但是,在同一个事务内,前后两次相同的查询可能会返回不同的结果,这被称为不可重复读(Non-repeatable Read)。 +3. 可重复读(Repeatable Read):这是MySQL InnoDB存储引擎默认的事务隔离级别。在这个级别下,事务在整个生命周期内可以看到第一次执行查询时的快照数据,即使其他事务在此期间提交了对这些数据的修改,也不会影响本事务内查询的结果,因此消除了不可重复读的问题。但是,它不能完全避免幻读(Phantom Read),即在同一事务内,前后两次相同的范围查询可能因为其他事务插入新的行而返回不同数量的结果。 + +为什么可重复读不能完全避免幻读: +1. 幻读的定义: 幻读是指在一个事务内,同一个查询语句多次执行时,由于其他事务提交了新的数据插入或删除操作,导致前后两次查询结果集不一致的情况。具体表现为:即使事务A在开始时已经获取了一个数据范围的快照,当它再次扫描这个范围时,发现出现了之前未读取到的新行,这些新行如同“幻影”一般突然出现。 +2. 可重复读与幻读的关系: 在可重复读隔离级别下,对于已存在的记录,MVCC确保事务能够看到第一次查询时的数据状态,因此不会发生不可重复读。但是,当有新的行被插入到符合事务查询条件的范围内时,MVCC本身并不能阻止这种现象的发生,因为新插入的行对于当前事务是可见的(取决于插入事务的提交时间点和当前事务的Read View)。 +3. 为了减轻幻读的影响,InnoDB在执行某些范围查询时会采用间隙锁(Gap Locks)或者Next-Key Locks来锁定索引区间,防止其他事务在这个范围内插入新的记录,但并非所有的范围查询都会自动加上这样的锁,而且这仅限于使用基于索引的操作,非索引字段的范围查询无法通过间隙锁完全避免幻读。 ## 锁表、锁行 1. InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁 @@ -53,13 +84,34 @@ MyISAM相对简单所以在效率上要优于InnoDB。如果系统插入和查 3. 行锁相对于表锁来说,优势在于高并发场景下表现更突出,毕竟锁的粒度小 4. 当表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁 -[https://segmentfault.com/a/1190000012773157](https://segmentfault.com/a/1190000012773157) - ### 悲观锁乐观锁、如何写对应的SQL -悲观锁:select for update -乐观锁:先查询一次数据,然后使用查询出来的数据+1进行更新数据,如果失败则循环 +悲观锁:在悲观锁机制下,事务认为在它执行期间数据一定会被其他事务修改,因此在读取数据时就立即对其加锁,阻止其他事务对同一数据进行操作,直到当前事务结束并释放锁。SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE +乐观锁:乐观锁并不在读取数据时立即加锁,而是在更新数据时检查自上次读取以来数据是否已被其他事务修改过。这通常通过在表中添加一个版本号字段或者时间戳字段来实现。 + +### 锁的种类 +按功能分类: +1. 共享锁:Shared Locks,简称S锁。在事务要读取一条记录时,需要先获取该记录的S锁。 +2. 独占锁:排他锁,英文名:Exclusive Locks,简称X锁。在事务要改动一条记录时,需要先获取该记录的X锁。 + +按粒度分类: +1. 表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般是做ddl处理时使用。另外表级别还有AUTO-INC锁:在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。这样一个事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的 +2. 行锁,也称为记录锁,顾名思义就是在记录上加的锁。锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强,MySQL一般都是用行锁来处理并发事务。下面是行锁的类型: + - Record Locks:记录锁,当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁; + - Gap Locks:是为了防止插入幻影记录而提出的,不允许其他事务往这条记录前面的间隙插入新记录。在REPEATABLE READ隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用MVCC方案解决,也可以采用加锁方案解决,但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读取操作时,那些幻影记录尚不存在,我们无法给这些幻影记录加上记录锁 + - Next-Key Locks:记录锁和一个gap锁的合体。锁住某条记录,又想阻止其他事务在该记录前面的间隙插入新记录 + - Insert Intention Locks:插入意向锁。一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的gap锁,如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交 + - 隐式锁:当事务需要加锁的时,如果这个锁不可能发生冲突,InnoDB会跳过加锁环节,这种机制称为隐式锁。隐式锁是InnoDB实现的一种延迟加锁机制,其特点是只有在可能发生冲突时才加锁,从而减少了锁的数量,提高了系统整体性能 + +### MVCC +MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种用于数据库管理系统中的事务处理机制,它在不使用加锁或者只对数据进行有限锁定的情况下,为并发事务提供了一种相对无冲突的访问方式。MVCC有效地避免了脏读、不可重复读等问题,并极大地提高了数据库系统的并发性能 + +原理: +1. 隐式字段:每行记录除了实际的数据列外,还包含额外的系统信息,例如DB_TRX_ID(最近修改该行事务ID)、DB_ROLL_PTR(回滚指针指向undo日志),以及DB_ROW_ID(行ID)等。这些隐藏字段用于跟踪数据的不同版本。 +2. 事务版本号:每个事务都有一个唯一的事务ID,在事务开始时分配。这个ID用来区分不同事务的操作时间点。 +3. Undo日志:当事务对某一行进行修改时,旧版本的数据会被存放在Undo日志中,形成历史版本链。这样当其他事务需要读取旧版本数据时,可以从Undo日志中获取。 +4. Read View:MVCC通过Read View来决定事务可见的行版本。每个读操作都会创建或复用一个Read View,根据Read View的规则判断当前事务能否看到某个版本的数据:如果被读取行的事务ID小于Read View的最低事务ID,则可见;如果大于等于Read View的最高事务ID且未提交,则不可见;否则,检查该行是否在Read View创建之后被其他已提交事务修改过。 +5. 非锁定读:在MVCC中,存在两种类型的读操作:快照读(Snapshot Read)和当前读(Current Read)。快照读就是基于MVCC实现的,它不会阻塞其他事务,并总是返回某一特定时间点的数据视图。而当前读则会获取最新的数据并加锁以确保一致性。 -[https://www.jianshu.com/p/f5ff017db62a](https://www.jianshu.com/p/f5ff017db62a) ### 索引 #### 原理 @@ -67,13 +119,19 @@ MyISAM相对简单所以在效率上要优于InnoDB。如果系统插入和查 非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,所以一个表中可以拥有多个非聚集索引。叶子结点包含索引字段值及指向数据页数据行的逻辑指针 #### 索引原理 -使用B+树来构建索引,为什么不用二叉树?因为红黑树在磁盘上的查询性能远不如B+树 - -红黑树最多只有两个子节点,所以高度会非常高,导致遍历查询的次数会多,又因为红黑树在数组中存储的方式,导致逻辑上很近的父节点与子节点可能在物理上很远,导致无法使用磁盘预读的局部性原理,需要很多次IO才能找到磁盘上的数据 - -但B+树一个节点中可以存储很多个索引的key,且将大小设置为一个页,一次磁盘IO就能读取很多个key,且叶子节点之间还加上了下个叶子节点的指针,遍历索引也会很快。 - -B+树的高度如何计算?在Linux里,每个页默认4KB,假设索引的是8B的long型数据,每个key后有个页号4B,还有6B的其他数据(参考《MySQL技术内幕:InnoDB存储引擎》P193的页面数据),那么每个页的扇出系数为4KB/(8B+4B+6B)=227,即每个页可以索引245个key。在高度h=3时,s=227^3=1100万。通常来说,索引树的高度在2~4。 +为什么使用B+树来构建索引? +1. b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素; +2. b+树查询必须查找到叶子节点,b树只要匹配到即可不用管元素位置,因此b+树查找更稳定 +3. 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历 +4. 红黑树最多只有两个子节点,所以高度会非常高,导致遍历查询的次数会多 +5. 红黑树在数组中存储的方式,导致逻辑上很近的父节点与子节点可能在物理上很远,导致无法使用磁盘预读的局部性原理,需要很多次IO才能找到磁盘上的数据。B+树一个节点中可以存储很多个索引的key,且将大小设置为一个页,一次磁盘IO就能读取很多个key +6. 叶子节点之间还加上了下个叶子节点的指针,遍历索引也会很快。 + +B+树的高度如何计算? +1. 假设B+树的高度为2的话,即有一个根结点和若干个叶子结点。这棵B+树的存放总记录数为=根结点指针数 * 单个叶子节点记录行数 +2. 假设一行记录的数据大小为1k,那么单个叶子节点可以存的记录数 =16k/1k =16. +3. 非叶子节点内存放多少指针呢?我们假设主键ID为bigint类型,长度为8字节(int类型的话,一个int就是32位,4字节),而指针大小是固定的在InnoDB源码中设置为6字节,假设n指主键个数即key的个数,n*8 + (n + 1) * 6 = 16K=16*1024B , 算出n约为 1170,意味着根节点会有1170个key与1171个指针 +4. 一棵高度为2的B+树,能存放1171* 16 = 18736条这样的数据记录。同理一棵高度为3的B+树,能存放1171 * 1171 * 16 = 21939856,也就是说,可以存放两千万左右的记录。B+树高度一般为1-3层,已经满足千万级别的数据存储。 #### 优化 如何选择合适的列建立索引? @@ -82,6 +140,48 @@ B+树的高度如何计算?在Linux里,每个页默认4KB,假设索引的 2. 离散度大(不同的数据多)的列使用索引才有查询效率提升 3. 索引字段越小越好,因为数据库按页存储的,如果每次查询IO读取的页越少查询效率越高 +### count1/count*/count字段 的区别 + +1. count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL +2. count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL +3. count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。 + +count(\*),自动会优化指定到那一个字段。所以没必要去count(1),用count(\*),sql会帮你完成优化的 因此:count(1)和count(\*)基本没有差别! + +### explain详解 +1. id:select 查询序列号。id相同,执行顺序由上至下;id不同,id值越大优先级越高,越先被执行。 +2. select_type:查询数据的操作类型,其值如下: + - simple:简单查询,不包含子查询或 union + - primary:包含复杂的子查询,最外层查询标记为该值 + - subquery:在 select 或 where 包含子查询,被标记为该值 + - derived:在 from 列表中包含的子查询被标记为该值,MySQL 会递归执行这些子查询,把结果放在临时表 + - union:若第二个 select 出现在 union 之后,则被标记为该值。若 union 包含在 from 的子查询中,外层 select 被标记为 derived + - union result:从 union 表获取结果的 select +3. table:显示该行数据是关于哪张表 +4. partitions:匹配的分区 +5. type:表的连接类型,其值,性能由高到底排列如下: + - system:表只有一行记录,相当于系统表 + - const:通过索引一次就找到,只匹配一行数据 + - eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常用于主键或唯一索引扫描 + - ref:非唯一性索引扫描,返回匹配某个单独值的所有行。用于=、< 或 > 操作符带索引的列 + - range:只检索给定范围的行,使用一个索引来选择行。一般使用between、>、<情况 + - index:只遍历索引树 + - ALL:全表扫描,性能最差 +注:前5种情况都是理想情况的索引使用情况。通常优化至少到range级别,最好能优化到 ref +6. possible_keys:显示 MySQL 理论上使用的索引,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。如果该值为 NULL,说明没有使用索引,可以建立索引提高性能 +7. key:显示 MySQL 实际使用的索引。如果为 NULL,则没有使用索引查询 +8. key_len:表示索引中使用的字节数,通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 显示的是索引字段的最大长度,并非实际使用长度 +9. ref:显示该表的索引字段关联了哪张表的哪个字段 +10. rows:根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,数值越小越好 +11. filtered:返回结果的行数占读取行数的百分比,值越大越好 +12. extra:包含不合适在其他列中显示但十分重要的额外信息,常见的值如下: + - using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。出现该值,应该优化 SQL + - using temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。出现该值,应该优化 SQL + - using index:表示相应的 select 操作使用了覆盖索引,避免了访问表的数据行,效率不错 + - using where:where 子句用于限制哪一行 + - using join buffer:使用连接缓存 + - distinct:发现第一个匹配后,停止为当前的行组合搜索更多的行 + ## 分区分库分表 分区:把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的,通过将不同数据按一定规则放到不同的区块中提升表的查询效率。 @@ -97,4 +197,23 @@ B+树的高度如何计算?在Linux里,每个页默认4KB,假设索引的 2. 跨库跨表的join问题。在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。 3. 额外的数据管理负担和数据运算压力。额外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算。 + +## MySQL数据库主从延迟同步方案 +什么事主从延迟:为了完成主从复制,从库需要通过 I/O 线程获取主库中 dump 线程读取的 binlog 内容并写入到自己的中继日志 relay log 中,从库的 SQL 线程再读取中继日志,重做中继日志中的日志,相当于再执行一遍 SQL,更新自己的数据库,以达到数据的一致性。主从延迟,就是同一个事务,从库执行完成的时间与主库执行完成的时间之差 + +1. 「异步复制」:MySQL 默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给客户端,并不关心从库是否已经接收并处理。这样就会有一个问题,一旦主库宕机,此时主库上已经提交的事务可能因为网络原因并没有传到从库上,如果此时执行故障转移,强行将从提升为主,可能导致新主上的数据不完整。 +2. 「全同步复制」:指当主库执行完一个事务,并且所有的从库都执行了该事务,主库才提交事务并返回结果给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。 +3. 「半同步复制」:是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库接收到并写到 Relay Log 文件即可,主库不需要等待所有从库给主库返回 ACK。主库收到这个 ACK 以后,才能给客户端返回 “事务完成” 的确认。这样会出现一个问题,就是实际上主库已经将该事务 commit 到了存储引擎层,应用已经可以看到数据发生了变化,只是在等待返回而已。如果此时主库宕机,可能从库还没写入 Relay Log,就会发生主从库数据不一致。为了解决上述问题,MySQL 5.7 引入了增强半同步复制。针对上面这个图,“Waiting Slave dump” 被调整到了 “Storage Commit” 之前,即主库写数据到 binlog 后,就开始等待从库的应答 ACK,直到至少一个从库写入 Relay Log 后,并将数据落盘,然后返回给主库 ACK,通知主库可以执行 commit 操作,然后主库再将事务提交到事务引擎层,应用此时才可以看到数据发生了变化。 +4. 一主多从:如果从库承担了大量查询请求,那么从库上的查询操作将耗费大量的 CPU 资源,从而影响了同步速度,造成主从延迟。那么我们可以多接几个从库,让这些从库来共同分担读的压力。 +5. 强制走主库:如果某些操作对数据的实时性要求比较苛刻,需要反映实时最新的数据,比如说涉及金钱的金融类系统、在线实时系统、又或者是写入之后马上又读的业务,这时我们就得放弃读写分离,让此类的读请求也走主库,这就不存延迟问题了。 +6. 并行复制:MySQL在5.6版本中使用并行复制来解决主从延迟问题。所谓并行复制,指的就是从库开启多个SQL线程,并行读取relay log中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。MySQL 5.7对并行复制做了进一步改进,开始支持真正的并行复制功能。MySQL5.7的并行复制是基于组提交的并行复制,官方称为enhanced multi-threaded slave(简称MTS),延迟问题已经得到了极大的改善。其核心思想是:一个组提交的事务都是可以并行回放(配合binary log group commit);slave机器的relay log中 last_committed相同的事务(sequence_num不同)可以并发执行。 + +强制走主库之后,如何在处理大数据量的时候保证实时写查: +1. 表结构与索引优化:确保表结构设计合理,尽量避免数据冗余和更新异常。根据查询条件创建合适的索引,特别是对于高频查询字段,可以显著提高读取速度。 +2. 分区表(Partitioning):对于非常大的表,可以考虑使用分区表功能,将大表物理分割成多个小表,根据时间、范围或其他业务逻辑进行划分,这样不仅能提高查询效率,还可以通过并行处理提高写入速度。 +3. 分库分表:当单表数据量过大时,采用水平拆分或垂直拆分策略将数据分布到多个数据库或表中,进一步提升读写性能。 +4. 查询优化:避免全表扫描,尽可能使用覆盖索引。编写高效的SQL语句,减少不必要的计算和排序操作。 +5. 缓存机制:在数据库层面,可以考虑使用内存表或者缓存技术(如Redis)来临时存储最近写入的数据,以便快速响应实时查询请求。 +6. 实时分析型数据库:对于大数据量且实时性要求极高的场景,可以考虑引入实时分析型数据库系统,例如ClickHouse、Druid等,它们设计之初就为实时写入和查询提供了高效的支持。 + 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/\346\225\260\346\215\256\345\272\223-Redis.md" "b/MD/\346\225\260\346\215\256\345\272\223-Redis.md" index 9f38256..8b86d69 100644 --- "a/MD/\346\225\260\346\215\256\345\272\223-Redis.md" +++ "b/MD/\346\225\260\346\215\256\345\272\223-Redis.md" @@ -5,14 +5,52 @@ Redis是一个键值对数据库,数据库中的键值对由字典保存。每 字典的键是一个字符串对象。字典的值则可以是包括【字符串、列表、哈希表、集合或有序集】在内的任意一种 Redis 类型对象。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/redis键空间.png) +![](../images/redis键空间.png) 上图展示了一个包含 number 、 book 、 message 三个键的数据库 —— 其中 number 键是一个列表,列表中包含三个整数值; book 键是一个哈希表,表中包含三个键值对; 而 message 键则指向另一个字符串: -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/redis数据类型.png) +![](../images/redis数据类型.png) 不同的数据类型的具体实现(压缩列表、跳表必看)请看: https://redisbook.readthedocs.io/en/latest/index.html#id3 +在线文档:http://118.25.23.115/ + +压缩链表原理: +在内存中是连续存储的,但是不同于数组,为了节省内存,ziplist的每个元素所占的内存大小可以不同 +ziplist将一些必要的偏移量信息记录在了每一个节点里,使之能跳到上一个节点或下一个节点 + +与跳表应用场景: +当zset满足以下两个条件的时候,使用ziplist: +1. 保存的元素少于128个 +2. 保存的所有元素大小都小于64字节 + +redis数据过期策略: +定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除 +惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除 +定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key + +内存淘汰策略: +no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错 +allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key +allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key +volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key +volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key +volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除 + +Redis 是单线程吗? +1. Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发生数据给客户端」这个过程是由一个线程(主线程)来完成的但是,Redis 程序并不是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)的: +2. Redis 在 2.6 版本,会启动 2 个后台线程,分别处理关闭文件、AOF 刷盘这两个任务; +3. Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key。 + +Redis 采用单线程为什么还这么快? +1. Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构 +2. Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题 +3. Redis 采用了I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。 + +Redis 6.0 之后为什么引入了多线程? +1. 随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。所以为了提高网络请求处理的并行度 +2. 对于读写命令,Redis 仍然使用单线程来处理,Redis 官方表示,Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上。 + ## 集群模式 来源: https://my.oschina.net/zhangxufeng/blog/905611 @@ -20,18 +58,18 @@ https://www.cnblogs.com/leeSmall/p/8398401.html https://docs.aws.amazon.com/zh_cn/AmazonElastiCache/latest/red-ug/CacheNodes.NodeGroups.html ### 主从 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/redis主从.png) +![](../images/redis主从.png) 用一个redis实例作为主机,其余的实例作为从机。主机和从机的数据完全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的同步和读取。因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离的目的。 问题是主从模式如果所连接的redis实例因为故障下线了,没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接。如果主节点由于故障下线了,那么从节点因为没有主节点而同步中断,因而需要人工进行故障转移工作。为了解决这两个问题,在2.8版本之后redis正式提供了sentinel(哨兵)架构。 ### 哨兵 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/redis哨兵.png) +![](../images/redis哨兵.png) 由Sentinel节点定期监控发现主节点是否出现了故障,当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。 ### 集群 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/redis集群.png) +![](../images/redis集群.png) redis主从或哨兵模式的每个实例都是全量存储所有数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。集群将数据分片存储,每组节点存储一部分数据,从而达到分布式集群的目的。 @@ -84,8 +122,22 @@ redis主从或哨兵模式的每个实例都是全量存储所有数据,浪费 ## 持久化 RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。 -AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。 +1. save:save 是需要占用主线程资源的,会阻塞主线程。 +2. bgsave:使用的是子进程写,不会占用主线程的资源(fork会阻塞主线程,但是copyonwrite过程是纳秒级别相对较快,aof是毫秒级别)。redis使用了操作系统的写时复制技术(COPY -ON-WRITE COW)bgsave子进程是由主进程fork生成的,可以共享主进程的内存,如果主进程有写入命令时候,主进程会把写入的那块内存页先复制一份给fork使用,这样就不影响主进程的写操作,可以继续修改原来的数据,避免了对正常业务的影响。 + + +AOF 持久化记录服务器执行的所有写操作命令,aof日志的重写是由后台程序bgrewriteaof子进程完成的,这也是为了避免阻塞主线程,导致数据性能下降。Redis 还可以在后台对 AOF 文件进行重写(rewrite重写机制就是把这条键值对的多次操作压缩成一次操作,这样就大大减少了aof日志文件的大小。),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。 +1. always 同步回写:每条命令执行完成后,回立即将日志写回磁盘。always回写回占用主线程资源,每次回写都是一个慢速的罗盘操作,基本上可以避免数据的丢失,会对主线程产生影响,如果你对redis数据的准确性要求非常高,而写入和读取占比不高的话,可以采用这种策略。 +2. everysec 每秒回写:每个命令执行完后回先将日志写入aof文件的内存缓冲区,每隔一秒把缓冲区中的命令写入磁盘。每秒回写采用一秒回写一次的策略,避免了同步回写的性能开销,虽然减少了对系统性能的影响,但是如果1秒内产生了大量的写操作,在aof缓冲区积压了很多日志,这时候还没来得及写入aof日志文件就发生了宕机,就会造成数据的丢失。 +3. no 操作系统控制的回写:每个写命令执行完后只是先把日志写入aof文件的内存缓冲区,何时回写磁盘由操作系统决定。采用操作系统控制的回写,在写完aof缓冲区后就可以继续执行命令,但是如果系统发生宕机了,也同样会造成数据的丢失。 + Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。 -但实际上持久化会对Redis的性能造成非常严重的影响,如果一定需要保存数据,那么数据就不应该依靠缓存来保存,建议使用其他方式如数据库。所以Redis的持久化意义不大。 +## 渐进式rehash +以下是哈希表渐进式 rehash 的详细步骤: +1. 为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。 +2. 在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。 +3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。 +4. 随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。 +渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/\347\247\222\346\235\200\346\236\266\346\236\204.md" "b/MD/\347\247\222\346\235\200\346\236\266\346\236\204.md" index 55d37a8..1c451c9 100644 --- "a/MD/\347\247\222\346\235\200\346\236\266\346\236\204.md" +++ "b/MD/\347\247\222\346\235\200\346\236\266\346\236\204.md" @@ -1,47 +1,59 @@ -## 解决思路 -尽量将请求拦截在数据库的上游,因为一旦大量请求进入数据库,性能会急剧下降 +## 业务上适当规避 +1. 根据某些规则对部分用户直接返回没抢到。比如有些用户曾经被系统识别为恶意用户、垃圾用户、僵尸用户,直接告诉用户已经抢完 +2. 分散不同客户端打开活动入口的时间。比如将1秒内的流量分散到10秒 -## 架构和实现细节 +## 技术上硬核抗压 +1. 限流策略。比如在压力测试中我们测到系统QPS达到了极限,那么超过的部分直接返回已经抢完,通过Nginx的lua脚本可以查redis看到QPS数据从而可以动态调节 +2. 异步削峰。对Redis中的红包预减数量,立即返回抢红包成功请用户等待,然后把发送消息发给消息队列,进行流量的第二次削峰,让后台服务慢慢处理 +3. 服务逻辑。比如业务逻辑是使用事务控制对数据库的创建订单记录,减库存的操作,那么创建操作要放到减库存操作之前,从而避免减数量update的行锁持有时间 +4. 机器配置。当然是服务器机器配置约高越好,数据库配置越猛越好,高并发抢红包主要是CPU与网络IO的负载较高,要选择偏向CPU与网络IO性能的机器 + +### 架构和实现细节 * 前端模块(页面静态化、CDN、客户端缓存) * 排队模块(Redis、队列实现异步下单) * 服务模块(事务处理业务逻辑、避免并发问题) -* 防刷模块(验证码、限IP) +* 防刷模块(验证码、限制用户频繁访问) -## 模块解析 -### 前端模块 +### 模块解析 +#### 前端模块 1. 页面静态化,将后台渲染模板的方式改成使用HTML文件与AJAX异步请求的方式,减少服务端渲染开销,同时将秒杀页面提前放到CDN 2. 客户端缓存,配置Cache-Control来让客户端缓存一定时间页面,提升用户体验 3. 静态资源优化,CSS/JS/图片压缩,提升用户体验 -### 排队模块 +#### 排队模块 1. 对Redis中的抢购对象预减库存,然后立即返回抢购成功请用户等待,这里利用了Redis将大部分请求拦截住,少部分流量进入下一阶段 2. 如果参与秒杀的商品太多,进入下一阶段的流量依然比较大,则需要使用消息队列,Redis过滤之后的请求直接放入到消息队列,让消息队列进行流量的第二次削峰 -### 服务模块 +#### 服务模块 1. 消息队列的消费者,业务逻辑是使用事务控制对数据库的下订单,减库存操作,且下订单操作要放到减库存操作之前,可以避免减库存update的行锁持有时间 -### 防刷模块 +#### 防刷模块 1. 针对恶意用户写脚本去刷,在Redis中保存用户IP与商品ID进行限制 2. 针对普通用户疯狂的点击,使用JS控制抢购按钮,每几秒才能点击一次 3. 在后台生成数学计算型的验证码,使用Graphics、BufferedImage实现图片,ScriptEngineManager计算表达式 -### 异常流程的处理 +#### 异常流程的处理 1. 如果在秒杀的过程中由于服务崩溃导致秒杀活动中断,那么没有好的办法,只能立即尝试恢复崩溃服务或者申请另寻时间重新进行秒杀活动 2. 如果在下订单的过程中由于用户的某些限制导致下单失败,那么应该回滚事务,立即告诉用户失败原因 -## 难点+坑+复盘优化 -### 难点 -1. 理解整个架构设计的思路,围绕这个思路进行思考有什么方式可以做到,在开发过程中多进行压力测试反馈优化 -2. 代码中异常情况的处理与业务上应急预案的准备 +### 总结 +#### 原则 +业务优化思路:业务上适当规避 +技术优化思路:尽量将请求拦截在数据库的上游,因为一旦大量请求进入数据库,性能会急剧下降 +架构原则:合适、简单、演化(以上内容是最终版本,初版可以说没有用到队列,直接使用缓存-数据库这样的架构) + +#### 难点 +1. 如何将高并发大流量一步步从业务和技术方面有条不紊地应对过来 +2. 如何在代码中处理好异常情况以及应急预案的准备 -### 坑 +#### 坑 1. 以上的解决方案能通过利用Redis与消息队列集群来承载非常高的并发量,但是运维成本高。比如Redis与消息队列都必须用到集群才能保证稳定性,会导致运维成本太高。所以需要有专业的运维团队维护。 2. 避免同一用户同时下多个订单,需要写好业务逻辑或在订单表中加上用户ID与商品ID的唯一索引;避免卖超问题,在更新数量的sql上需要加上>0条件 -### 优化 +#### 优化 1. 将7层负载均衡Nginx与4层负载均衡LVS一起使用进一步提高并发量 2. 以上是应用架构上的优化,在部署的Redis、消息队列、数据库、虚拟机偏向选择带宽与硬盘读写速度高的 3. 提前预热,将最新的静态资源同步更新到CDN的所有节点上,在Redis中提前加载好需要售卖的产品信息 -4. 使用分布式限流减少Redis访问压力,在Nginx中配置并发连接数与速度限制,但如果有很多不同的活动同时进行则不适用 +4. 使用分布式限流减少Redis访问压力,在Nginx中配置并发连接数与速度限制 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/\347\256\227\346\263\225-\344\272\214\345\217\211\346\240\221-\345\244\232\345\217\211\346\240\221\344\270\255\346\234\200\351\225\277\347\232\204\350\277\236\347\273\255\345\272\217\345\210\227.md" "b/MD/\347\256\227\346\263\225-\344\272\214\345\217\211\346\240\221-\345\244\232\345\217\211\346\240\221\344\270\255\346\234\200\351\225\277\347\232\204\350\277\236\347\273\255\345\272\217\345\210\227.md" new file mode 100644 index 0000000..6ce2af7 --- /dev/null +++ "b/MD/\347\256\227\346\263\225-\344\272\214\345\217\211\346\240\221-\345\244\232\345\217\211\346\240\221\344\270\255\346\234\200\351\225\277\347\232\204\350\277\236\347\273\255\345\272\217\345\210\227.md" @@ -0,0 +1,49 @@ +二叉树:https://cheonhyangzhang.gitbooks.io/leetcode-solutions/content/solutions-501-550/549-binary-tree-longest-consecutive-sequence-ii.html + +多叉树:https://gist.github.com/thanlau/34bd056dd0b1662d5fd483e157a27dfb +```java +/** + * Definition for a multi tree node. + * public class MultiTreeNode { + * int val; + * List children; + * MultiTreeNode(int x) { val = x; } + * } + */ +public class Solution { + /** + * @param root the root of k-ary tree + * @return the length of the longest consecutive sequence path + */ + int max = 0; + + public int longestConsecutive3(MultiTreeNode root) { + // Write your code here + if (root == null){ + return 0; + } + helper(root); + return max; + } + + public int[] helper(MultiTreeNode root) { + int up = 0; + int down = 0; + if (root == null){ + return new int[]{down, up}; + } + for ( MultiTreeNode node : root.children ){ + int[] h = helper(node); + if (node.val == root.val + 1){ + down = Math.max(down, h[0] + 1); + } + if (node.val + 1 == root.val){ + up = Math.max(up, h[1] + 1); + } + } + max = Math.max(max, down + up + 1); + return new int[]{down, up}; + } +} + +``` diff --git "a/MD/\347\256\227\346\263\225-\345\205\266\344\273\226-\344\272\214\345\215\201\350\277\233\345\210\266\347\233\270\345\212\240.md" "b/MD/\347\256\227\346\263\225-\345\205\266\344\273\226-\344\272\214\345\215\201\350\277\233\345\210\266\347\233\270\345\212\240.md" new file mode 100644 index 0000000..d8e0106 --- /dev/null +++ "b/MD/\347\256\227\346\263\225-\345\205\266\344\273\226-\344\272\214\345\215\201\350\277\233\345\210\266\347\233\270\345\212\240.md" @@ -0,0 +1,17 @@ +在二十进制中,我们除了使用数字0-9以外,还使用字母a-j(表示10-19),给定两个二十进制整数,求它们的和。 输入是两个二十进制整数,且都大于0,不超过100位; 输出是它们的和(二十进制),且不包含首0。我们用字符串来表示二十进制整数。 +``` +/* +二十进制相加 +sample +input + +1234567890+abcdefghij +99999jjjjj+9999900001 + +output + +bdfi02467j +iiiij00000 +*/ +``` + diff --git "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\216\222\345\272\217\347\256\227\346\263\225.md" "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\216\222\345\272\217\347\256\227\346\263\225.md" index aa6638b..f4d852c 100644 --- "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\216\222\345\272\217\347\256\227\346\263\225.md" +++ "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\216\222\345\272\217\347\256\227\346\263\225.md" @@ -1,7 +1,7 @@ ## 快速排序 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/j5.jpg) +![](../images/j5.jpg) -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/quicksort.gif) +![](../images/quicksort.gif) 首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序 @@ -13,9 +13,9 @@ 1. 根据初始数组去构造初始堆(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。 2. 每次交换第一个和最后一个元素,输出最后一个元素(最大值),然后把剩下元素重新调整为大根堆。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/j11.png) +![](../images/j11.png) -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/heapsort.gif) +![](../images/heapsort.gif) 堆排序过程的最好和最坏时间复杂度是O(nlog2n) diff --git "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\223\215\344\275\234\347\263\273\347\273\237.md" "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\223\215\344\275\234\347\263\273\347\273\237.md" new file mode 100644 index 0000000..ad29904 --- /dev/null +++ "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\346\223\215\344\275\234\347\263\273\347\273\237.md" @@ -0,0 +1,19 @@ +## 线程和进程区别 +1. 进程是资源分配的最小单位,线程是程序执行的最小单位 +2. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。线程是共享进程的数据空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多 +3. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。 + +## 硬链接与软链接的区别 +每个文件都是创建了一个指针指向inode(代表物理硬盘的一个区块),可以通过`ls -i`查看。 +硬链接是创建另一个文件,也通过创建指针指向inode。当你删除一个文件/硬链接时,它会删除一个到底层inode的指针。当inode的所有指针都被删除时,才会真正删除文件。 +软连接是另外一种类型的文件,保存的是它指向文件的全路径,访问时会替换成绝对路径 + +## 查看某个进程中的线程 +`ps -T -p ` + +## 查看某个文件夹中每个文件夹的大小 +`du --max-depth=1 -h` + +## CPU负载的含义 +一段时间内CPU正在处理和等待处理的进程总数与CPU最大处理进程数的比例。对于多核CPU来说最大LOAD是最大的核心数量。 +使用`top`和`top -Hp`查找到CPU占用比较大的线程,进而使用`jstack`来排查Java程序的问题 \ No newline at end of file diff --git "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" index e99034c..e1f12d9 100644 --- "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" +++ "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" @@ -19,7 +19,7 @@ HTTP是TCP/IP协议中的【应用层】协议。它不涉及数据包传输, ## TCP TCP是一种面向连接的、可靠的、基于字节流的TCP/IP协议中的【传输层】协议 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/tcp.png) +![](../images/tcp.png) ### 建立连接:三次握手 1. 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。 @@ -60,4 +60,28 @@ HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的 3. 密钥交换 - 使用RSA非对称公钥加密算法(客户端生成一个对称密钥,然后用SSL证书里带的服务器公钥将该对称密钥加密。随后发送到服务端,服务端用服务器私钥解密,到此,握手阶段完成。)或者DH交换算法在客户端与服务端双方确定将要使用的密钥。这个密钥是双方都同意的一个简单,对称的密钥。这个过程是基于非对称加密方式和服务器的公钥/私钥的。 4. 加密通信 - 在服务器和客户端加密实际信息是用到对称加密算法,用哪个算法在Hello阶段已经确定。对称加密算法是对于加密和解密都很简单的密钥。这个密钥是基于第三步在客户端与服务端已经商议好的。与需要公钥/私钥的非对称加密算法相反。 -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ +## socket线程安全? +总结来说,虽然系统级别的socket操作本身具备一定程度的并发安全性,但在多线程程序中对socket进行读写时仍需要开发者采取适当的同步策略以保证线程安全。 + +1. 系统调用级别:在操作系统层面,对同一个socket文件描述符(fd)进行读写操作的系统调用如send、recv、write和read等,通常是线程安全的。这意味着多个线程可以同时调用这些函数而不会导致内核数据结构的混乱。 +2. 应用程序级别:然而,在应用程序中直接对同一个socket进行并发读写时,并非完全线程安全。不同的线程如果不加控制地同时对同一socket进行读写,可能会遇到以下问题: + - 数据包边界混淆:TCP是流式传输协议,没有消息边界的概念,两个线程如果并发发送或接收,可能导致接收到的数据边界与发送时不一致。 + - 竞争条件:当多个线程尝试修改socket的状态或缓冲区时,可能会产生未定义的行为,例如,一个线程正在发送数据时,另一个线程关闭了socket,这可能导致不可预测的结果。 + - 同步问题:如果没有适当的锁或其他同步机制来保护共享状态,那么不同线程之间对于何时开始和结束I/O操作的协调就可能出现问题。 +3. 最佳实践:为了在多线程环境下正确且安全地使用socket,通常建议采取以下措施: + - 互斥访问:使用线程同步机制,如互斥锁(mutex),确保任何时候只有一个线程在执行读写操作。 + - 生产者-消费者模型:设计成一个线程专门负责接收数据并放入队列,另一个线程从队列取出数据并发送,通过队列实现线程间的通信和同步。 + - 事件驱动编程:在某些场合下,如使用异步I/O(如epoll/kqueue等)、IOCP(Windows平台)或者事件循环机制,可以避免直接的并发访问,从而在单个线程内处理多个socket连接,减少线程间同步开销。 + +## java socket跟socket有什么区别 +1. 抽象层次: + - 一般意义上的Socket:通常指的是计算机网络编程中的一个抽象概念,是操作系统提供的一种用于进程间通信的机制,通过它可以在网络中不同主机上的进程之间进行双向的数据传输。它是TCP/IP协议栈的一部分,在TCP或UDP等传输层协议之上提供接口。 + - Java Socket:在Java编程语言中,java.net.Socket和java.net.ServerSocket类是对底层操作系统Socket接口的封装,提供了面向对象的方式来创建、管理和使用网络连接。程序员可以通过Java Socket API实现跨网络的客户端-服务器通信。 +2. API形式: + - 通用Socket:在不同的编程语言或环境中,对Socket的操作可能需要直接调用系统API函数(如C语言中的socket()、bind()、listen()、accept()、send()、recv()等)。 + - Java Socket:Java为开发者提供了一套高级且易于使用的API,如Socket socket = new Socket(host, port)来建立到指定主机和端口的连接,以及socket.getInputStream()和socket.getOutputStream()来获取输入输出流以读写数据。 +3. 平台无关性: + - 通用Socket:直接操作系统提供的Socket API时,代码往往具有一定的平台相关性,即在不同的操作系统上可能需要采用不同的API及调用方式。 + - Java Socket:由于Java语言的跨平台特性,其封装的Socket API能够在支持Java的所有平台上运行,使得基于Java Socket编写的网络程序具有良好的可移植性。 + +综上所述,Java Socket是在Java编程环境下对通用Socket概念的具体实现和抽象,为开发者提供了一个更加便捷、安全且平台无关的方式来处理网络通信问题。 diff --git "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\350\256\276\350\256\241\346\250\241\345\274\217.md" index 598171e..34aaa51 100644 --- "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -9,6 +9,9 @@ http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern +## 状态模式 +https://www.cnblogs.com/xyzq/p/11090344.html + ## 装饰器模式 在保持原有功能接口不变的基础上动态扩展功能的模式。 Java IO包中就使用了该模式,InputStream有太多的实现类如FileInputStream,如果要在每个实现类上加上几种功能如缓冲区读写功能Buffered,则会导致出现ileInputStreamBuffered, StringInputStreamBuffered等等,如果还要加个按行读写的功能,类会更多,代码重复度也太高,你说改原来的接口也行啊,但是这样就是改变接口的内容了,现在我想做到不更改以前的功能,动态地增强原有接口。 @@ -17,14 +20,14 @@ Java IO包中就使用了该模式,InputStream有太多的实现类如FileInpu 缺点: 会引入很多小类,从而增加使用复杂度,多层装饰的时候很容易导致不知道如何使用 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/装饰器模式.png) +![](../images/装饰器模式.png) ## 策略模式 一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。 比如利用策略模式优化过多 if else 代码,将这些if else算法封装成一个一个的类,任意地替换 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/策略模式.png) +![](../images/策略模式.png) 整体思路如下: @@ -42,7 +45,7 @@ https://juejin.im/post/5c5172d15188256a2334a15d ## 观察者模式 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 -![](https://github.com/xbox1994/2018-Java-Interview/raw/master/images/观察者模式.jpg) +![](../images/观察者模式.jpg) 观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。 diff --git a/README.md b/README.md index 6476b67..c8e9559 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -本项目是本人于2018年参加BATJ等其他公司电话、现场面试之后总结出来的针对Java面试的知识点或真题,每个点或题目都是在面试中被问过的。 +本项目是本人参加BAT等其他公司电话、现场面试之后总结出来的针对Java面试的知识点或真题,每个点或题目都是在面试中被问过的。 除开知识点,一定要准备好以下套路: -1. **个人介绍**,需要准备1分钟和5分钟两个版本,包括学习经历、工作经历、项目经历、个人优势、一句话总结。一定要自己背得滚瓜烂熟,张口就来 +1. **个人介绍**,需要准备一个1分钟的介绍,包括学习经历、工作经历、项目经历、个人优势、一句话总结。一定要自己背得滚瓜烂熟,张口就来 2. **抽象概念**,当面试官问你是如何理解多线程的时候,你要知道从定义、来源、实现、问题、优化、应用方面系统性地回答 3. **项目强化**,至少与知识点的比例是五五开,所以必须针对简历中的两个以上的项目,形成包括【架构和实现细节】,【正常流程和异常流程的处理】,【难点+坑+复盘优化】三位一体的组合拳 4. **压力练习**,面试的时候难免紧张,可能会严重影响发挥,通过平时多找机会参与交流分享,或找人做压力面试来改善 @@ -9,50 +9,60 @@ 6. **重点针对**,面试官会针对简历提问,所以请针对简历上写的所有技术点进行重点准备 ### Java基础 -* [JVM原理](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/Java基础-JVM原理.md) -* [集合](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/Java基础-集合.md) -* [多线程](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/Java基础-多线程.md) -* [IO](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/Java基础-IO.md) -* [问题排查](http://www.wangtianyi.top/blog/2018/07/20/javasheng-chan-huan-jing-xia-wen-ti-pai-cha/?utm_source=github&utm_medium=github) +* [JVM原理](MD/Java基础-JVM原理.md) +* [集合](MD/Java基础-集合.md) +* [多线程](MD/Java基础-多线程.md) +* [IO](MD/Java基础-IO.md) +* [问题排查](https://www.wangtianyi.top/article/2018-07-20-javasheng-chan-huan-jing-xia-wen-ti-pai-cha/?utm_source=github&utm_medium=github) ### Web框架、数据库 -* [Spring](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/Web框架-Spring.md) -* [MySQL](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/数据库-MySQL.md) -* [Redis](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/数据库-Redis.md) +* [Spring](MD/Web框架-Spring.md) +* [MySQL](MD/数据库-MySQL.md) +* [Redis](MD/数据库-Redis.md) ### 通用基础 -* [网络通信协议](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/通用基础-网络通信协议.md) -* [排序算法](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/通用基础-排序算法.md) -* [常用设计模式](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/通用基础-设计模式.md) -* [从URL到看到网页的过程](http://www.wangtianyi.top/blog/2017/10/22/cong-urlkai-shi-,ding-wei-shi-jie/?utm_source=github&utm_medium=github) +* [操作系统](MD/通用基础-操作系统.md) +* [网络通信协议](MD/通用基础-网络通信协议.md) +* [排序算法](MD/通用基础-排序算法.md) +* [常用设计模式](MD/通用基础-设计模式.md) +* [从URL到看到网页的过程](https://www.wangtianyi.top/article/2017-10-22-cong-urlkai-shi-,ding-wei-shi-jie/?utm_source=github&utm_medium=github) ### 分布式 -* [CAP理论](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-CAP理论.md) -* [锁](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-锁.md) -* [事务](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-事务.md) -* [消息队列](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-消息队列.md) -* [协调器](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-协调器.md) -* [ID生成方式](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-ID生成方式.md) -* [一致性hash](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/分布式-一致性hash.md) +* [CAP理论](MD/分布式-CAP理论.md) +* [锁](MD/分布式-锁.md) +* [事务](MD/分布式-事务.md) +* [消息队列](MD/分布式-消息队列.md) +* [协调器](MD/分布式-协调器.md) +* [ID生成方式](MD/分布式-ID生成方式.md) +* [一致性hash](MD/分布式-一致性hash.md) +* [限流](MD/分布式-限流.md) ### 微服务 -* [微服务介绍](http://www.wangtianyi.top/blog/2017/04/16/microservies-1-introduction-to-microservies/?utm_source=github&utm_medium=github) -* [服务发现](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/微服务-服务注册与发现.md) -* [API网关](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/微服务-网关.md) -* [服务容错保护](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/微服务-服务容错保护.md) -* [服务配置中心](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/微服务-服务配置中心.md) -### 算法(头条必问) -* [数组-快速排序-第k大个数](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-数组-快速排序-第k大个数.md) -* [数组-对撞指针-最大蓄水](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-数组-对撞指针-最大蓄水.md) -* [数组-滑动窗口-最小连续子数组](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-数组-滑动窗口-最小连续子数组.md) -* [数组-归并排序-合并有序数组](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-数组-归并排序-合并有序数组.md) -* [链表-链表反转-链表相加](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-链表-反转链表-链表相加.md) -* [链表-双指针-删除倒数第n个](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-链表-双指针-删除倒数第n个.md) -* [二叉树-递归-二叉树反转](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-二叉树-递归-二叉树反转.md) -* [动态规划-连续子数组最大和](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-动态规划-连续子数组最大和.md) -* [数据结构-LRU淘汰算法](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/算法-数据结构-LRU淘汰算法.md) +* [微服务介绍](https://www.wangtianyi.top/article/2017-04-16-microservies-1-introduction-to-microservies/?utm_source=github&utm_medium=github) +* [服务发现](MD/微服务-服务注册与发现.md) +* [API网关](MD/微服务-网关.md) +* [服务容错保护](MD/微服务-服务容错保护.md) +* [服务配置中心](MD/微服务-服务配置中心.md) +### 算法 +* [数组-快速排序-第k大个数](MD/算法-数组-快速排序-第k大个数.md) +* [数组-对撞指针-最大蓄水](MD/算法-数组-对撞指针-最大蓄水.md) +* [数组-滑动窗口-最小连续子数组](MD/算法-数组-滑动窗口-最小连续子数组.md) +* [数组-归并排序-合并有序数组](MD/算法-数组-归并排序-合并有序数组.md) +* [数组-顺时针打印矩形](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a) +* [数组-24点游戏](https://leetcode.cn/problems/24-game/description/) +* [链表-链表反转-链表相加](MD/算法-链表-反转链表-链表相加.md) +* [链表-双指针-删除倒数第n个](MD/算法-链表-双指针-删除倒数第n个.md) +* [链表-双指针-重排链表](https://leetcode.cn/problems/reorder-list/description/) +* [二叉树-递归-二叉树反转](MD/算法-二叉树-递归-二叉树反转.md) +* [二叉树-递归-多叉树中最长的连续序列](MD/算法-二叉树-多叉树中最长的连续序列.md) +* [二叉树-trie树](https://leetcode.cn/problems/implement-trie-prefix-tree/description/) +* [动态规划-连续子数组最大和](MD/算法-动态规划-连续子数组最大和.md) +* [数据结构-LRU淘汰算法](MD/算法-数据结构-LRU淘汰算法.md) +* [其他-二十进制相加](MD/算法-其他-二十进制相加.md) +* [有序数组中位数](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/) +* [数组中的k个最小值](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/) ### 项目举例 -* [秒杀架构](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/秒杀架构.md) +* [秒杀架构](MD/秒杀架构.md) ### 系统设计 -* [系统设计-高并发抢红包](https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/系统设计-高并发抢红包.md) -* [系统设计-答题套路](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%E4%B8%80%E4%B8%AA%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98) -* [系统设计-在AWS上扩展到数百万用户的系统](https://www.wangtianyi.top/blog/2019/03/06/zai-awsshang-kuo-zhan-dao-shu-bai-mo-yong-hu-de-xi-tong/?utm_source=github&utm_medium=github) -* [系统设计-从面试者角度设计一个系统设计题](http://www.wangtianyi.top/blog/2018/08/31/xi-tong-she-ji-mian-shi-ti-zong-he-kao-cha-mian-shi-zhe-de-da-zhao/?utm_source=github&utm_medium=github) - -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ +* [系统设计-高并发抢红包](MD/系统设计-高并发抢红包.md) +* [系统设计-在AWS上扩展到数百万用户的系统](https://www.wangtianyi.top/article/2019-03-06-zai-awsshang-kuo-zhan-dao-shu-bai-mo-yong-hu-de-xi-tong/?utm_source=github&utm_medium=github) +* [系统设计-从面试者角度设计一个系统设计题](https://www.wangtianyi.top/article/2018-08-31-xi-tong-she-ji-mian-shi-ti-zong-he-kao-cha-mian-shi-zhe-de-da-zhao/?utm_source=github&utm_medium=github) +### 智力题 +* [概率p输出1,概率1-p输出0,等概率输出0和1](https://blog.csdn.net/qq_29108585/article/details/60765640) +* [判断点是否在多边形内部](https://www.cnblogs.com/muyefeiwu/p/11260366.html) diff --git a/images/pay_wx.png b/images/pay_wx.png new file mode 100644 index 0000000..0956481 Binary files /dev/null and b/images/pay_wx.png differ diff --git a/images/wechat_qrcode.jpg b/images/wechat_qrcode.jpg new file mode 100644 index 0000000..fd1a042 Binary files /dev/null and b/images/wechat_qrcode.jpg differ diff --git a/images/wxg.png b/images/wxg.png new file mode 100644 index 0000000..6f41830 Binary files /dev/null and b/images/wxg.png differ diff --git a/images/wxg2.jpg b/images/wxg2.jpg new file mode 100644 index 0000000..97a90a7 Binary files /dev/null and b/images/wxg2.jpg differ diff --git a/images/wxg2.png b/images/wxg2.png new file mode 100644 index 0000000..199aed7 Binary files /dev/null and b/images/wxg2.png differ diff --git "a/images/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201-1.gif" "b/images/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201-1.gif" new file mode 100644 index 0000000..b9d493b Binary files /dev/null and "b/images/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201-1.gif" differ diff --git "a/images/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201-2.png" "b/images/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201-2.png" new file mode 100644 index 0000000..679167c Binary files /dev/null and "b/images/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201-2.png" differ diff --git "a/images/\347\237\245\350\257\206\346\230\237\347\220\203.JPG" "b/images/\347\237\245\350\257\206\346\230\237\347\220\203.JPG" new file mode 100644 index 0000000..d6b59ee Binary files /dev/null and "b/images/\347\237\245\350\257\206\346\230\237\347\220\203.JPG" differ