Siwei Wang
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# 补充面试知识点 ## java基础 ### 两类相等 两个类是否相等,取决于他们是否由统一个类加载器来加载。如果他们来自不同的类加载器,哪么就算这两个类来自同一Class文件,他们也是不相等的。 ### try catch 当finally前有return语句时,finally代码块也会被执行。 当finally中有return则会直接输出,不会再去执行try...catch中的。 ### string为什么要设计成不可变的? > **1.可以缓存 hash 值()** > 因为 的 hash 值经常被使用,例如 用做 得 hash 值也不可变, > 因此只需要进行一次计算。 > **2.常量池优化** > 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回 > 缓存的引用。 > **3.线程安全** > String 不可变性天生具备线程安全,可以在多个线程中安全地使用。 ### 访问修饰符 ![](https://i.imgur.com/e5Ok5rN.png) ### static关键字 **static 关键字的主要用途就是方便在没有创建对象时调用方法和变量和优化程序性能** **初始化顺序为父类中的静态变量和静态代码块——子类中 的静态变量和静态代码块——父类中的实例变量和普通代码块——父类的构造函数——子类的实例变量 和普通代码块——子类的构造函数** ### 抽象类和接口 > 一个类可以实现多个接口但是一个类只能继承一个抽象类。 > 接口内所有变量必须只能由final private或static修饰。 > 接口默认被public修饰 **应用场景** ### final > final可以修饰类,变量,方法 > 当final修饰类的时候,这个类就不能被继承 > 当final修饰方法,方法不能被重写 > 当final修饰常量,常量不能变,如果final修饰引用常量,引用的内存地址不能变化。 ### 反射 > JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java语言的反射机制 > **Java获取Class对象的三种方式** > 1. 通过实例对象获取(实例对象.getClass();) > 2. 通过类名.class获取(类名.calss) > 3. 通过类的静态方法获取(Class.forName("全类名")) > **反射优缺点** > 优点:运行期类型的判断,动态加载类,提高代码灵活度。 > 缺点:性能比直接的java代码要慢很多。 > **反射应用场景** > 1. Java的很多框架都用到了反射,例如 Spring 中的xml的配置模式等 > 2. 动态代理设计模式也采用了反射机制 ### 枚举类一般在什么时候用 有多个常量的时候使用枚举类,一个类的对象时有限且固定的。使用枚举类的优点:更加直观、类型安全、利于表达 ### 面向对象五大基本原则是什么 > 1. **单一职责原则(Single-Resposibility Principle)** > 一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面 向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。 > 2. **开放封闭原则(Open-Closed principle)** > 软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。 > 3. **里氏替换原则 (Liskov-Substituion Principle)** > 子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才 能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严 格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一 约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。 > 4. **依赖倒置原则(Dependecy-Inversion Principle)** > 依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具 > 体,具体依赖于抽象。 > 5. **接口隔离原则(Interface-Segregation Principle)** > 使用多个小的专门的接口,而不要使用一个大的总接口 ### 为什么要重写hashcode > 第一,在hashset等集合中,不重写hashcode方法会导致其功能出现问题; > 第二,可以提高集合效率。 ### try catch finally的执行顺序 ![](https://i.imgur.com/3zQmD06.png) ### 常见的object方法 ![](https://i.imgur.com/Div8sSy.png) ### java中的io流以及异常处理 ![](https://i.imgur.com/4fIDCxZ.png) ## 数据结构 ### 集合关系类图 ![](https://i.imgur.com/bQbU4yn.png) ### 哪些集合是线程安全的 > vector > hash table > stack > conconrrenthashmap > enumeration ### ConcurrentHashMap底层实现 ![](https://i.imgur.com/n4U0aF4.png) ### ConcurrentHashMap的key,value是否可以为null,Hashmap中的key,value是否可以为null > ConcurrentHashMap的key和value为null会出现空指针异常,hashmap中key和value可以为null。 ### 为什么hashtable不能是null?而hashmap可以 ![](https://i.imgur.com/H1jVu8l.png) ### ConcurrenctHashMap迭代器是强一致性还是弱一致性 > ConcurrenctHashMap是弱一致性 ### 快速失败机制 “fail-fast”和安全失败机制“fail-safe" > **快速失败** Java的快速失败机制是Java集合框架中的一种错误检测机制,当多个线程同时对集合中的内容进行修改时可能就会抛出ConcurrentModificationException异常。其实不仅仅是在多线程状态下,在单线程中用增强for循环中一边遍历集合一边修改集合的元素也会抛出这个异常 > **安全失败** 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内 容,在拷贝的集合上进行遍历。所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会抛出 ConcurrentModificationException 异常。缺点是迭代器遍历的是开始遍历那一刻拿 到的集合拷贝,在遍历期间原集合发生了修改,迭代器是无法访问到修改后的内容。java.util.concurrent 包下的容器都是安全失败(concurrenthashmap) ,可以在多线程下并发使用。 ### array 和 arrayList的区别 > 1. Array可以包含基本类型和对象类型, ArrayList 只能包含对象类型。 > 2. Array 大小是固定的, ArrayList 的大小是动态变化的。( ArrayList 的扩容是个常见面试题) > 3. 相比于 Array , ArrayList 有着更多的内置方法,如 addAll() , removeAll() > 4. 对于基本类型数据,ArrayList 使用自动装箱来减少编码工作量;而当处理固定大小的基本数据 类型的时候,这种方式相对比较慢,这时候应该使用 Array ### vector和list区别 > vector和list区别 vector是线程安全的 list线程不安全 vector使用sytrolized 修饰,效率低 list相比vector效率更高 ### comparable和comparator 很多包装类都实现了 comparable 接口,像 Integer 、 String 等,所以直接调用 Collections.sort() 直接可以使用。如果对类里面自带的自然排序不满意,而又不能修改其源代码的情况下,使用 Comparator 就比较合适 ### hashmap #### jdk1.8 jdk1.7 hashmap的区别 ![](https://i.imgur.com/RifUOv2.png) #### put的流程 ![](https://i.imgur.com/yTKoAIp.png) #### hashmap是怎么解决hash冲突的 > **拉链法** > HasMap 中的数据结构为数组+链表/红黑树,当不同的key计算出的hash值相同时,就用链表的形式将Node结点(冲突的 key 及 key 对应的 value )挂在数组后面。 > **hash函数** > key的hash值经过两次扰动,key的hashCode值与key的hashCode值的右移16位进行异或,然后对数组的长度取余(实际为了提高性能用的是位运算,但目的和取余一样),这样做可以让hashCode 取值出的高位也参与运算,进一步降低hash冲突的概率,使得数据分布更平均。 > **红黑树** > 在拉链法中,如果hash冲突特别严重,则会导致数组上挂的链表长度过长,性能变差,因此在链表 长度大于8时,将链表转化为红黑树,可以提高遍历链表的速度。 ### lru缓存 LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。 ### 几种二叉树 #### 二叉搜索树 > 1. 任意节点左子树不为空,则左子树的值均小于根节点的值. > 2. 任意节点右子树不为空,则右子树的值均大于于根节点的值. > 3. 任意节点的左右子树也分别是二叉查找树. > 4. 没有键值相等的节点. > **缺点**:二叉树在查找数据时,时间复杂度最好情况是O(logn) ,最坏情况下时间复杂度O(n),如a图所示,二叉树退化成一个链表了,恰好选择了最小或者最大的节点做root,节点排在了一条直线上。因此,在二叉查找树的基础上,又出现了AVL树,红黑树,它们两个都是基于二叉查找树,只是在二叉查找树的基础上又对其做了限制. #### 二叉平衡树(AVL) > AVL、红黑树是对二叉搜索树的改进版本。平衡因子:**节点的左右子树深度之差。**在一棵平衡二叉树中,节点的平衡因子只能取 0 、1 或者 -1 ,分别对应着左右子树等高,左子树比较高,右子树比较高。AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入删除次数比较少,但查找多的情况。 > **缺点**:由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树.当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树 #### 红黑树 > 一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是red或black(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍。它是一种弱平衡二叉树(由于是弱平衡,可以推出,相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索、插入、删除操作较多的情况下,我们就用红黑树。 > 1. 高度始终保持在h = logn > 2. 红黑树的查找、插入、删除的时间复杂度最坏为O(log n) #### 红黑树和b+树区别 B和B+树主要用于数据存储在磁盘上的场景,比如数据库索引就是用B+树实现的。这两种数据结构的特点就是树比较矮胖,每个结点存放一个磁盘大小的数据,这样一次可以把一个磁盘的数据读入内存,减少磁盘转动的耗时,提高效率。而红黑树多用于内存中排序,也就是内部排序。 ## java并发编程 ### 什么时候使用多线程 为功能而线程化 > 分配不同的线程来完成应用程序的不同功能,这是最容易的方法,因为功能重叠的机会很罕见。 在一个应用程序中控制并发功能的执行是比较容易的。 即使在计算间没有直接的影响,功能之间的依赖性还会维持。 > > 多线程应用举例:为了简化代码,为下列部分设计不同的线程:输入、图形用户界面、计算和输出。 为性能而线程化 > 通过将执行在并行环境下的大量的计算分解开来进行应用程序的并行化,能够提高计算的性能。线程化是为了改善周转周期和吞吐量 。 > > 比如: 搜索太空实验室碎片, 把全部搜索区域分成多个分段,并安排一个工人去搜索一个分段 。 为缩短周转周期而线程化 > 用可能的最小的时间完成一个任务 > > 举例:安排一个饭桌时候的不同任务: > > 一个侍者摆放盘子。 > > 一个侍者折叠和放置餐巾。 > > 一个侍者摆放花和蜡烛。 > > 一个侍者摆放器皿、汤匙、刀子和叉子 > > 一个侍者放玻璃杯 为了吞吐量而线程化 > 在固定的时间内完成最多的任务 > 举例:安排一个饭局时候的不同任务 > 对多个侍者的安排: 每个桌子安排一个侍者。 一个侍者能摆放所有桌子的所有盘子;另一个可以摆放所有的玻璃杯;以此类推。 ### 线程安全是什么 > 当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类时线程安全的。 ### 保证线程安全的方法 > **1)同步代码块:** > 在代码块声明上 加上synchronized > synchronized (锁对象) { > 可能会产生线程安全问题的代码 > } > 同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。 > **(2)同步方法:** > 在方法声明上加上synchronized > public synchronized void method(){ > 可能会产生线程安全问题的代码 > } > 同步方法中的锁对象是 this > 静态同步方法: 在方法声明上加上static synchronized > 静态同步方法中的锁对象是 类名.class > **(3)同步锁** > Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。 > **4)锁类型** > 可重入锁:在执行对象中所有同步方法不用再次获得锁 > 可中断锁:在等待获取锁过程中可中断 > 公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利 > 读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写 ### synchronized和lock的区别 ![](https://i.imgur.com/sB4K5e3.png) ### CAS存在的问题 > ABA问题,他想把A转成B,但是其实已经从A转为B又转A了一次 > 解决方法:通过版本号来解决 > 循环开销太大 > 只能保证一个共享变量的原子性 ### volatile实现有序性的原理 > volatile是通过内存屏障来实现有序性的,内存屏障是cpu的指令,他的作用是对该指令前和后的一些操作产生一定的约束保证按顺序执行。 ### 插入内存屏障的策略 ![](https://i.imgur.com/vgveC6l.png) ### as if serial和happens before规则的区别 ![](https://i.imgur.com/pH1lq3q.png) ### 执行execute方法和submit方法的区别是什么 > 1.execute没有返回值,无法判断出线程是否执行成功 > 2.submit方法有返回值,获得future对象来判断线程是否成功 ### 多线程的优缺点 > 优点: > 提高系统的并发能力 > 适应现代开发模式 > 缺点: > 导致死锁 > 导致内存泄漏 > 上下文切换可能导致效率过低 ### threadLocal原理 > ThreadLocal使用了threadLocalmap来实现 ### threadlocal底层数据结构 > ThreadLocalMap内部维护了一个类似于map的结构,key为当前对象的thread对象,值为object对象。 ### Threadlocal内存泄漏的问题 > threadlocal Map的key如果没有被外部强引用,很可能GC的时候被回收,而他的value并不会被回收,所以会剩余大量没有key的value,导致内存泄漏。 > 但是其实threadlocalmap已经解决了这种问题,每次调用set,get,remove方法都会清空key为null的数据。 ### 内存溢出或错误怎么定位 > 是否App中的类中和引用变量过多使用了Static修饰 如public staitc Student s;在类中的属性中使用 static修饰的最好只用基本类型或字符串。如public static int i = 0; //public static String str; 是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象) > 是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象) 检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。 > 检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中 > 检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。 ### synchronized关键字 > 修饰实例方法:当前实例加锁 > 修饰静态方法:对当前class加锁 > 修饰代码块:对当前class加锁 ### countdownlatch ![](https://i.imgur.com/lkWac2r.png) ![](https://i.imgur.com/GG2u32r.png) ### juc包中的原子类是哪四类 1.基本类型 ![](https://i.imgur.com/sZillQu.png) 2.数组类型 ![](https://i.imgur.com/QLpOQ4N.png) 3.引用类型 ![](https://i.imgur.com/5bjoxUN.png) 4.对象的属性修改类型 ![](https://i.imgur.com/sMnRGH6.png) ### juc > 即java.util.concurrent的缩写,AQS框架是J.U.C中实现锁及同步机制的基础,下面我简单说一下AQS,AQS是一个制作锁的框架,比如rentancylock就是AQS弄出来的, 他的基本原理就是通过一个双向队列来对一个资源进行访问,保证一个资源同一时刻只被一个进程所访问,具体流程是:每有一个线程进来,他会先看当前这个资源是不是忙的状态,忙就把进程加入队列,不忙就分配给进程,然后锁住资源。同时这个队列中每个线程持有两个指针,一个是pre一个是next,整个队列维护两个指针一个是head一个是tail,通过指针来保证整个系统正常运行。 ### 创建线程的方式、Thread和Runnable创建线程有什么区别 > 实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖/实现Runnable接口中的run方法,将线程任务代码定义到run方法中。通过thread方式继承了thread方法,建立线程, 而runable 则是实现了runable接口去创建 另外两种: > 使用callable的call创建 > 使用excuter ### 创建线程池的方法: Excutors工厂方法创建 > new ThreadPoolExutor > 三个核心参数: > corePoolsize核心线程参数,最小可以同时运行的线程数 > maximumPoolsize线程中允许最大的工作线程数量 > workqueue拥塞队列、 ![](https://i.imgur.com/YEcYkAG.png) ### java默认的线程调度方式 > 抢占式调度 每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。 > 希望系统能给某些线程多分配一些时间,给一些线程少分配一些时间,可以通过设置线程优先级来完成。 ### 多线程交替打印?(syn,volatile维护版本号,lock,wait&notify,信号量...) ![](https://i.imgur.com/fDtA6jJ.png) ### sychronized 和lock的区别 > Sychronized 是非公平锁,lock可以设置为公平锁 > Sychronized 不可中断,lock可中断 > Sychronized 只能跟一个条件,lock可以跟多个condition ### sychronized锁升级过程 > 刚开始为无锁状态 > 有一个线程访问时升级为偏向锁 > 有锁竞争时升级为轻量级锁 > 自旋十次失败升级为重量级锁 ![](https://i.imgur.com/E3zaGNO.png) ## jvm ### 为什么要有两个survivor区 设置两个Survivor区最大的好处就是解决了碎片化 刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化 那么,顺理成章的,应该建立两块Survivor区,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。 ### CMS产生内存碎片怎么办 * 增大Xmx或者减少Xmn * 在应用访问量最低的时候,在程序中主动调用System.gc(),比如每天凌晨。 * 在应用启动并完成所有初始化工作后,主动调用System.gc(),它可以将初始化的数据压缩到一个单独的chunk中,以腾出更多的连续内存空间给新生代晋升使用。 * 降低-XX:CMSInitiatingOccupancyFraction参数以提早执行CMSGC动作,虽然CMSGC不会进行内存碎片的压缩整理,但它会合并老生代中相邻的free空间。这样就可以容纳更多的新生代晋升行为 ### Java定位到代码是jstack jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式: jstack [-l] pid 主要分为两个功能: a. 针对活着的进程做本地的或远程的线程dump; b. 针对core文件做线程dump。 jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的 ### Java堆的统计信息可以用jstat ### 堆内内存和对外内存 和堆内内存相对应,堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。 1、减少了垃圾回收 因为垃圾回收会暂停其他的工作。 2、加快了复制的速度 堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。 同样任何一个事物使用起来有优点就会有缺点,堆外内存的缺点就是内存难以控制,使用了堆外内存就间接失去了JVM管理内存的可行性,改由自己来管理,当发生内存溢出时排查起来非常困难。 ### 频繁发生fullgc full gc 触发条件是 老年代空间不足, 所以追因的方向就是导致 老年代空间不足的原因: 大量对象频繁进入老年代 + 老年代空间释放不掉 1. 系统并发高、执行耗时过长,或者数据量过大,导致 young gc频繁,且gc后存活对象太多,但是survivor 区存放不下(太小 或 动态年龄判断) 导致对象快速进入老年代 老年代迅速堆满 2. 发程序一次性加载过多对象到内存 (大对象),导致频繁有大对象进入老年代 造成full gc 3. 存在内存溢出的情况,老年代驻留了大量释放不掉的对象, 只要有一点点对象进入老年代 就达到 full gc的水位了 4. 元数据区加载了太多类 ,满了 也会发生 full gc 5. 堆外内存 direct buffer memory 使用不当导致 ### gcroot的对象 1. 本地方法栈jin本地方法引用的对象 2. 方法区常量引用的对象 3. 方法区静态变量引用的对象 4. 堆引用的对象 ### jvm调优 > **哪些情况需要gc调优** > 1. Heap内存(老年代)持续上涨达到设置的最大内存值; > 2. Full GC 次数频繁; > 3. GC 停顿时间过长(超过1秒); > 4. 应用出现OutOfMemory等内存异常; > 5. 应用中有使用本地缓存且占用大量内存空间; > 6. 系统吞吐量与响应性能不高或下降 > **四个步骤** > 1. 记录好日志; > 2. 对程序做好性能监控; > 3. 根据日志和性能监控数据修改程序; > 4. 使用专业工具通过不同的JVM参数进行压测并获得最佳配置。 > **可调优参数** > 可调优参数: -Xms:初始化堆内存大小,默认为物理内存的1/64(小于1GB)。 > -Xmx:堆内存最大值。默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。 > -Xmn:新生代大小,包括Eden区与2个Survivor区。 > -XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。 > -XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory异常可以上调这个值。 > -XX:+DisableExplicitGC:禁止运行期显式地调用System.gc()来触发fulll GC。 > 注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。 > -XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。 > -XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。 > -XX:ParallelGCThreads=8:新生代并行收集器的线程数。 > -XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。 > -XX:CMSFullGCsBeforeCompaction=4:指定进行多少次fullGC之后,进行tenured区 内存空间压缩。 > -XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。 > 在设置的时候,如果关注性能开销的话,应尽量把永久代的初始值与最大值设置为同一值,因为永久代的大小调整需要进行FullGC才能实现。 ### 类加载的过程 #### 1. Loading加载 通过类的全限定名(包名 + 类名),获取到该类的.class文件的二进制字节流 将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口 #### 2. Linking链接 链接是指将上面创建好的class类合并至Java虚拟机中,使之能够执行的过程,可分为验证、准备、解析三个阶段。 验证(Verify) 确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。 准备(Prepare) 为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了 解析(Resolve) 解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程 #### 3. initialization初始化 初始化就是执行类的构造器方法init()的过程。 这个方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。 若该类具有父类,jvm会保证父类的init先执行,然后在执行子类的init。 ### 类加载的机制 #### 全盘负责 当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。 #### 父类委托 先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。 #### 缓存机制 缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。 #### 双亲委派机制 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。 #### 为什么需要双亲委派 避免重复加载 + 避免核心类篡改 ### partialGC和fullGC区别 #### partialGC ##### youngGC只收集young gen的GC ##### oldGC只收集old gen的GC 只有CMS是这个模式 ##### mixedGC收集整个young还有部分old 只有G1是这个模式 #### fullGC收集整个堆 ### 判断 #### 判断对象是否死亡 ![](https://i.imgur.com/cfMqYHq.png) #### 判断废弃常量 ![](https://i.imgur.com/FevybDD.png) #### 判断无用类 ![](https://i.imgur.com/ARCeQVI.png) #### 常用的收集器 ![](https://i.imgur.com/BMsPPvT.png) ![](https://i.imgur.com/zqF8RuR.png) ![](https://i.imgur.com/USstv8y.png) ![](https://i.imgur.com/7KCRf8L.png) ![](https://i.imgur.com/Ep8tTxK.png) ![](https://i.imgur.com/ij1YZwt.png) ![](https://i.imgur.com/LDZIuoX.png) ![](https://i.imgur.com/ZcDICG0.png) #### 在gcroot上的就一定不被回收吗? > 不一定,如果是弱引用或者虚引用即使在gcroot上也会回收。 > 而且 > 即使在可达性分析 算法 中不可达的对象,也并非"非死不可"的,这时候他们暂时处在"缓刑"阶段。要宣告一个对象的真正死亡,至少要经历两次标记过程: 如果对象在进行可达性分析之后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被JVM调用过,虚拟机会将这两种情况都视为"没有必要执行",此时的对象才是真正"死"的对象。 > 如果这个对象被判定为有必要执行finalize()方法,那么将会被放置在一个叫做F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(这里所说的执行指的是虚拟机会触发finalize()方法)。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象在finalize()中成功拯救自己(只需要重新与引用链上的任何一个对象建立起关联关系即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量),那在第二次标记时它将会被移除出"即将回收"的集合;如果对象这时候还是没有逃脱,那基本上它就是真的被回收 > 一个GCRoot不可达的对象,不会马上被垃圾回收,首先还会判断是否包含了finalize方法,若是有那就先执行finalize方法,若是这样的对象比较多,那么这部分对象及时GCRoot不可达,变得没用了,也会留在内存中,影响程序的效率。 ### 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢? ![](https://i.imgur.com/VZ97QZw.png) 减少他outofMemory的错误 ### 垃圾回收算法针对的区域 一般新生代用标记复制方法,因为这里面每次都要回收大量的垃圾,所以付出少量的对象复制成本就可以完成回收 老生代存活率较高,可以选用标记清除或者标记整理的方法。 ### zgc垃圾处理器 > 减少了用户线程的停止时间 > **并发标记(Concurrent Mark**):并发标记是遍历对象图做可达性分析的阶段,前后也要经过类似于G1、Shenandoah的初始标记和最终标记(ZGC中就是名字不同而已)的短暂的停顿,整个标记阶段只会更新染色指针中的Marked 0、Marked 1标志位。 > **并发预备重分配(Concurrent Prepare for Relocate**):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。 > **并发重分配(Concurrent Relocate)**:重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。 ###守护线程和非守护线程的区别 唯一的区别之处就在虚拟机的离开,当JVM中所有的线程都是守护线程的时候,JVM就可以退出了(如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了);如果还有一个或以上的非守护线程则不会退出。(以上是针对正常退出,调用System.exit则必定会退出)。 ## 计算机网络 ### 七层架构涉及哪些协议 ![](https://i.imgur.com/W698yLJ.png) ### rst tcp ![](https://i.imgur.com/ddeqz2j.png) ### 滑动窗口的大小 用windows字段 ### 三次握手 四次挥手状态 ![](https://i.imgur.com/K0Co1x9.png) ![](https://i.imgur.com/l0cxXVE.png) ### 什么是syn攻击 ![](https://i.imgur.com/Q2OcTBl.png) ### tcp粘包问题 TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。 ### 如何解决粘包问题 以要避免粘包问题,就得明确两个包之间的边界: 对于定长的数据包,保证每次都按固定的大小读取即可; 对于变长的包,可在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置; 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是我们自己写的,只要保证分隔符不和正文冲突即可) ### tcp如何解决syn攻击 #### 增加积压队列 目标设备上的每个操作系统都具有一定数量的半开放连接。对大量SYN数据包的一个响应是增加操作系统允许的可能半开连接的最大数量。为了成功增加最大积压,系统必须预留额外的内存资源来处理所有新的请求。如果系统没有足够的内存来处理增加的积压队列大小,系统性能将受到负面影响,但仍然可能优于拒绝服务。 #### 回收最早的半开TCP连接 一旦积压已被填补,另一个缓解策略就是覆盖最早的半开式连接。这种策略要求合法连接可以在比可以填充恶意SYN数据包的积压时间更短的时间内完全建立。当攻击量增加时,或者如果积压量太小而不实际,这种特定的防御就会失败。 #### SYN cookie 这个策略涉及服务器创建一个cookie。为了避免在积压已经被填满的情况下连接丢失的风险,服务器使用SYN-ACK数据包对每个连接请求进行响应,然后从积压中删除SYN请求,从存储器中删除请求并使端口打开,准备建立新的连接。如果连接是合法请求,并且最终的ACK数据包从客户端计算机发送回服务器,则服务器将重建(有一些限制)SYN积压队列条目。尽管这种缓解措施确实丢失了有关TCP连接的一些信息,但是优于允许合法用户因攻击而发生拒绝服务。 ### close_wait 和time_wait > close_wait是被关的 > time_wait是关的那一方 > TIME_WAIT > TIME_WAIT 是主动关闭链接时形成的,等待2MSL时间,约4分钟。主要是防止最后一个ACK丢失。 由于TIME_WAIT 的时间会非常长,因此server端应尽量减少主动关闭连接 #### time_wait状态过多 ![](https://i.imgur.com/qPMkGDR.png) ![](https://i.imgur.com/R37LEjk.png) #### CLOSE_WAIT > CLOSE_WAIT是被动关闭连接是形成的。根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但如果服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。 #### 为什么需要 TIME_WAIT 状态? > 假设最终的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发最终的ACK,否则会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连接的两个方向(全双工关闭),client必须进入 TIME_WAIT 状态,因为client可能面 临重发最终ACK的情形。 > 为什么 TIME_WAIT 状态需要保持 2MSL 这么长的时间? > 如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被 丢弃。建立第二个连接的时候,不会混淆。 #### 为什么是2MSL MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。 在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。 #### TIME_WAIT 和CLOSE_WAIT状态socket过多 > 如果服务器出了异常,百分之八九十都是下面两种情况: > 1.服务器保持了大量TIME_WAIT状态 > 2.服务器保持了大量CLOSE_WAIT状态,简单来说CLOSE_WAIT数目过大是由于被动关闭连接处理不当导致的。 > 因为linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的通道就一直被占着,而且是“占着茅坑不使劲”,一旦达到句柄数上限,新的请求就无法被处理了,接着就是大量Too Many Open Files异常,Tomcat崩溃。 ### DNS的工作流程 ![](https://i.imgur.com/0rQjkuz.png) ### ARP的工作流程 ![](https://i.imgur.com/7GyGeNS.png) ### 路由器和交换机的区别 ![](https://i.imgur.com/VWJ9EUE.png) ### TIME_WAIT和CLOSE_WAIT的区别 ![](https://i.imgur.com/AjEbdjk.png) ### GET和POST区别 #### post的结构 1. application/x-www-form-urlencoded 数据量不大、数据层级不深的情况下强烈建议这种数据提交格式。 2. multipart/form-data 需要提交文件、非 ASCII 码的数据或者是二进制流数据 3. application/json 数据结构较复杂,层级较深的情况。 ![](https://i.imgur.com/DErmJkB.png) ### ip地址会不会重复? 会重复。 IP地址可以让服务器或者路由器自动指派,指派后的IP例如192.168.1.100.电脑已经使用,这时候有另一台电脑是已经设置成固定IP的,也是相同的192.168.1.100。 ... 就会造成IP重复。 ### Socket的连接过程 ![](https://i.imgur.com/WQwvZLJ.png) ![](https://i.imgur.com/fSzNIcS.png) ### TCP,如何保证可靠?如何拥塞控制?滑动窗口是干嘛的? > TCP主要有以下几种方式保证可靠 > 1.校验和,发送端和接收端每次都会计算一边校验和,如果不对的话会拒绝接受数据包。 > 2.序列号,每次ack都会附带下一次包的序列号,保证收到正确的包,如果包不对,拒收 > 3.超时重发,如果没有在一定时间内收到ack,发送端会重新发送数据包 > 4.流量控制,传输速率过高也会导致丢包,所以tcp通过滑动窗口来控制传输流量 > 5.拥塞控制,通过拥塞避免来控制在不同丢包率下的传输速度,保证数据在合适的传输速率下传输。 > **如何控制拥塞**: > 它分为以下四个部分 > 1.慢开始: > 当tcp开始通信时,拥塞窗口的大小会以指数增长 > 2.拥塞避免 > 为防止网络阻塞,会设置一个阈值,超过这个阈值,拥塞窗口的增长就会成为线性的,每次加1,当网络出现超时时,将阈值设置为出现超时的窗口大小的一半并且把窗口大小设置为1,。 > 3.快重传 > 如果不是出现的网络超时,而是一个数据包的丢失,接收端会快速发送上一个包的ack给发送端,发送方收到三个同样的ack以后,会对这个数据包进行重传 > 4.快恢复 > 收到3个ack的同时会把阈值和窗口大小调整为发生重传时窗口的一半,直接进行拥塞避免。 > 滑动窗口是干啥的 > 当接收方收到包后,会返回ack,并且把滑动窗空的大小返回给发送方,这样发送方可以通过滑动窗口的大小来确定下次传输的速度。 ### tcp keepalive 当客户端端等待超过一定时间后自动给服务端发送一个空的报文,如果对方回复了这个报文证明连接还存活着,如果对方没有报文返回且进行了多次尝试都是一样,那么就认为连接已经丢失,客户端就没必要继续保持连接了。如果没有这种机制就会有很多空闲的连接占用着系统资源。 **使用的场景** 一般我们使用KeepAlive时会修改空闲时长,避免资源浪费,系统内核会为每一个TCP连接 建立一个保护记录,相对于应用层面效率更高。 常见的几种使用场景: 检测挂掉的连接(导致连接挂掉的原因很多,如服务停止、网络波动、宕机、应用重启等) 防止因为网络不活动而断连(使用NAT代理或者防火墙的时候,经常会出现这种问题) TCP层面的心跳检测 KeepAlive通过定时发送探测包来探测连接的对端是否存活, 但通常也会许多在业务层面处理的,他们之间的特点: TCP自带的KeepAlive使用简单,发送的数据包相比应用层心跳检测包更小,仅提供检测连接功能 应用层心跳包不依赖于传输层协议,无论传输层协议是TCP还是UDP都可以用 应用层心跳包可以定制,可以应对更复杂的情况或传输一些额外信息 KeepAlive仅代表连接保持着,而心跳包往往还代表客户端可正常工作 ### TCP传输为什么是有序的 > TCP通过每次ack携带的seq来确定下次开始的位置,这样既有利于保证传输的可靠性,同时也实现了有序的传输 ### 链路层协议 > 点对点协议 以太网 异步传输模式 ### tcp三次握手以及seq段分别是什么 > tcp第一次握手,发送端发送syn字段给接收方,并且告知接收方自己的发送能力 > tcp第二次握手,接收方发送syn/ack回给发送方,并告知接受方自己的一个接受能力 > tcp第三次握手,接收方发送ack给接收方,确认接收到了发送端的信息 > seq是序号,标记从发送到接受的字节流,tcp根据seq实现自己的有序性。 ### ICMP属于哪一层 ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。 它属于网络层协议 ### ping: > 1.向目标主机发送多个ICMP回送请求报文 > 2.根据目的主机返回报文的时间和成功响应的次数估算出数据包往返时间和丢包率 ### HTTP 1.0, 1.1 ,2.0比较 > **HTTP 1.0** > 短连接:每次发送请求都要重新建立tcp请求,即三次握手,非常浪费性能 > 无host头域,也就是http请求头里的host > 不允许断点续传,而且不能只传输对象的一部分,要求传输整个对象 **HTTP 1.1** > 长连接,流水线,使用connection:keep-alive使用长连接,与http 2.0不同的是, > host头域 > 由于长连接会给服务器造成压力 **HTTP 2.0** > 头部压缩,双方各自维护一个header的索引表,使得不需要直接发送值,通过发送key缩减头部大小 > 多路复用,使用多个stream,每个stream又分帧传输,使得一个tcp连接能够处理多个http请求 > 可以使用服务端推送 **HTTP 3.0** > 基于google的QUIC协议,而quic协议是使用udp实现的 > 减少了tcp三次握手时间,以及tls握手时间 > 解决了http 2.0中前一个stream丢包导致后一个stream被阻塞的问题 > 优化了重传策略,重传包和原包的编号不同,降低后续重传计算的消耗 > 连接迁移,不再用tcp四元组确定一个连接,而是用一个64位随机数来确定这个连接 > 更合适的流量控制 ![](https://i.imgur.com/sNxv7DC.png) ![](https://i.imgur.com/G7A1E0p.png) ### http请求格式 HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。 ### http常用参数 ![](https://i.imgur.com/5rliPad.png) ### 对象头常用数据格式(http请求头部) ![](https://i.imgur.com/fPvMnsM.png) ### HTTP状态码列表: ![](https://i.imgur.com/IzHpWiW.png) ![](https://i.imgur.com/I7IRomI.png) ![](https://i.imgur.com/KecpidP.png) ### https证书颁发过程,CA证书 它是负责管理和签发证书的第三方机构,就好比例子里面的中介——C 公司。一般来说,CA必须是所有行业和所有公众都信任的、认可的。因此它必须具有足够的权威性。就好比A、B两公司都必须信任C公司,才会找 C 公司作为公章的中介。 **申请证书的过程** ![](https://i.imgur.com/TGhgdDm.png) https://www.cnblogs.com/handsomeBoys/p/6556336.html ## 操作系统 ### gc页中断 ![](https://i.imgur.com/CnTOJsn.png) ### 进程不共享页表, 线程共享页表 ### fork join fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程。 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。 然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。 相当于克隆了一个自己。 #### java中的fork和join ![](https://i.imgur.com/mHAJckr.png) ### 子网掩码的作用 子网掩码是一个32位地址,是与IP地址结合使用的一种技术。 它的主要作用有两个,一是用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在远程网上。 二是用于将一个大的IP网络划分为若干小的子网络。 使用子网是为了减少IP的浪费。 ### 协程 协程是一种自己开发的轻量级线程 > **协程的目的** > 在传统的J2EE系统中都是基于每个请求占用一个线程去完成完整的业务逻辑(包括事务)。所以系统的吞吐能力取决于每个线程的操作耗时。如果遇到很耗时的I/O行为,则整个系统的吞吐立刻下降,因为这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多线程处于空闲状态(等待该线程执行完才能执行),造成了资源应用不彻底。 > 最常见的例子就是JDBC(它是同步阻塞的),这也是为什么很多人都说数据库是瓶颈的原因。这里的耗时其实是让CPU一直在等待I/O返回,说白了线程根本没有利用CPU去做运算,而是处于空转状态。而另外过多的线程,也会带来更多的ContextSwitch开销。 > 对于上述问题,现阶段行业里的比较流行的解决方案之一就是单线程加上异步回调。其代表派是node.js以及Java里的新秀Vert.x。 > 而协程的目的就是当出现长时间的I/O操作时,通过让出目前的协程调度,执行下一个任务的方式,来消除ContextSwitch上的开销。 > **协程的特点** > 线程的切换由操作系统负责调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。 > 线程的默认Stack大小是1M,而协程更轻量,接近1K。因此可以在相同的内存中开启更多的协程。 > 由于在同一个线程上,因此可以避免竞争关系而使用锁。 > 适用于被阻塞的,且需要大量并发的场景。但不适用于大量计算的多线程,遇到此种情况,更好实用线程去解决。 > **协程的原理** > 当出现IO阻塞的时候,由协程的调度器进行调度,通过将数据流立刻yield掉(主动让出),并且记录当前栈上的数据,阻塞完后立刻再通过线程恢复栈,并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。比如Golang里的 go关键字其实就是负责开启一个Fiber,让func逻辑跑在上面。 > 由于协程的暂停完全由程序控制,发生在用户态上;而线程的阻塞状态是由操作系统内核来进行切换,发生在内核态上。 > 因此,协程的开销远远小于线程的开销,也就没有了ContextSwitch上的开销。 ### 漏桶算法 令牌桶算法 ![](https://i.imgur.com/gmU1MM3.png) ![](https://i.imgur.com/TfNefTR.png) ### 同步异步 阻塞非阻塞 > 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。 > 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 > 同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。 > 阻塞和非阻塞 > 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 > 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。 ### bio,nio,aio > 先说煮开水的例子 > BIO(同步阻塞) > 定义:客户端在请求数据的过程中,保持一个连接,不能做其他事情。 > 那么BIO存在两个问题: > 由于连接是双向的,“始终保持一个连接”,则说明,对于客户端和服务端而言,都需要一个线程来维护这个连接,如果服务端没有数据给客户端,则客户端需要一直等待,该连接也需要一直维持。假设一个连接需要5MB的内存,不考虑多任务的情况下,客户端总是要花费固定的5MB。那么对服务端,1个客户端建立连接则需要花5MB,10个就要50MB,1000个就要5GB。显然,阻塞给服务器带来的性能负担极大。 > 客户端不能做其他事情,只能等待该请求的完成,其本身的性能没有得到充分的释放,所以等待就是浪费时间。 > NIO(同步非阻塞) > 客户端发送一个请求,并建立一个连接,服务端接收到了。如果服务端没有数据,就告知客户端“没有数据”;如果有数据,则返回数据。客户端接到了服务端回复的“没有数据”就断开连接,过了一段时间后,客户端重新问服务端是否有数据。服务器重复以上步骤。 > AIO(异步非阻塞) > 客户端向服务端请求数据。服务端若有,则返回数据;若无,则告诉客户端“没有数据”。客户端收到“没有数据”的回复后,就做自己的其他事情。服务端有了数据之后,就主动通知客户端,并把数据返回去。 #### bio,nio,aio的应用场景 > > BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 > NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 > AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 ### select,epoll,poll > select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。 > > select 时间复杂度O(n) > 只是知道有IO事件发生了,但不知道是哪个流,只能无差别去轮询所有流,找出能读出数据、或者写入数据的流。 > poll 时间复杂度O(n) > poll本质上和select什么区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,但它没有最大连接数的限制,原因是它是基于链表来储存 > epoll 时间复杂度O(1) > epoll 可以理解为event poll,epoll会把哪个流发生了怎样的IO事件通知我们,所以我们说额epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作是有意义的 > **select** > select 通过一个内置数组来告诉系统哪些套接字被监控; > 每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态; > 需要遍历整个返回的数组来判断哪些套接字上可以读或者写; > **poll** > poll 通过一个变长的数组解决了 select 长度受限制的问题,结构体只需要一个拷贝到内核态,但是轮询的问题还未得到解决 > **epoll** > epoll 通过事件的形式,采用注册的方式来实现。epoll 内部通过一个红黑树来维护注册的事件,激活的事件被拷贝到一个数组中,返回给用户态,因此用户态获得的数组全部是已经激活的事件。 ### linux命令大全 > 查看目录与文件:ls 切换目录:cd > 显示当前目录:pwd 创建空文件:touch > 创建目录:mkdir 查看文件内容:cat > 分页查看文件内容:more 查看文件尾内容:tail > 拷贝:cp 剪切或改名:mv > 删除:rm 搜索文件:find > 显示或配置网络设备:ifconfig 显示网络相关信息:netstat > 显示进程状态:ps 查看目录使用情况:du > 查看磁盘空间使用情况:df 显示系统当前进程信息:top > 杀死进程:kill 压缩和解压:tar > 改变文件或目录的访问权限:chmod 文本编辑:vim ### 进程间的通信方式 ![](https://i.imgur.com/SDRebGO.png) ### 进程分配的资源 如CPU时间、内存、文件和I/O设备 ### 进程在内存中的模型是什么样的? (程序段 存储执行的文件 数据段 存储执行时的数据 PCB) #### 共享内存比管道和消息队列效率高的原因 > 共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。 > 因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其它需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。 > 因为系统内核没有对访问共享内存进行同步,您必须提供自己的同步措施。例如,在数据被写入之前不允许进程从共享内存中读取信息、不允许两个进程同时向同一个共享内存地址写入数据等。解决这些问题的常用方法是通过使用信号量进行同步。 > 共享内存块提供了在任意数量的进程之间进行高效双向通信的机制。每个使用者都可以读取写入数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆写内存空间等竞争状态的出现。不幸的是,Linux无法严格保证提供对共享内存块的独占访问,甚至是在您通过使用IPC_PRIVATE创建新的共享内存块的时候也不能保证访问的独占性。 同时,多个使用共享内存块的进程之间必须协调使用同一个键值。 ### 进程间的调度 ![](https://i.imgur.com/i2NzIjX.png) ### 常⻅的⼏种内存管理机制 ![](https://i.imgur.com/KObWibA.png) ### 快表和多级页表 > 在分页内存管理中,有很重要的两点: > 1.虚拟内存到物理地址的转换要快 > 2.解决虚拟地址空间大,页表也会很大的问题 > **快表** ![](https://i.imgur.com/AJ3xkhJ.png) > **多级页表** ![](https://i.imgur.com/Zd1FxKq.png) ### 分页机制和分段机制的共同点和区别 ![](https://i.imgur.com/VBbh9zS.png) ### ⻚⾯置换算法 ![](https://i.imgur.com/pqJ8J4P.png) ## mysql ### 数据库常用命令 ### 什么是索引 > 索引是对数据库表的一列或者多列的值进行排序一种结构,使用索引可以快速访问数据表中的特定信息。 ### 哈希索引和b+树索引的区别 1. 哈希索引不支持排序,因为哈希表是无序的。 2. 哈希索引不支持范围查找。 3. 哈希索引不支持模糊查询及多列索引的最左前缀匹配。 4. 因为哈希表中会存在哈希冲突,所以哈希索引的性能是不稳定的,而B+树索引的性能是相对稳定 的,每次查询都是从根节点到叶子节点 ### mysql主从复制的策略 主从同步事件有3种形式:statement、row、mixed。 statement:会将对数据库操作的sql语句写入到binlog中。 row:会将每一条数据的变化写入到binlog中。 mixed:statement与row的混合。Mysql决定什么时候写statement格式的,什么时候写row格式的binlog。 ### mysql主从复制 > MySQL 主从复制涉及到三个线程:一个在主节点的线程:log dump thread,从库会生成两个线程:一个 I/O 线程,一个 SQL 线程 > **主要过程**: MySQL 主从复制默认是 异步的模式。MySQL增删改操作会全部记录在 Binlog 中,当 slave 节点连接 master 时,会主动从 master 处获取最新的 Binlog 文件。并把 Binlog 存储到本地的 relay log 中,然后去执行 relay log 的更新内容。 > I/O线程复制到一半自己突然挂掉了呢?又或者复制到一半主库宕机了呢?如果和保证数据一致性的呢? > 我们上面提到过,有一个relay-log.info的文件,用于记录当前从库正在复制的binlog和写入的Relay Log的Pos,只要这个文件还在,那么当从库意外重启之后,就会重新读取文件,从上次复制的地方开始继续复制。这就跟Redis中的主从复制类似,双方要维护一个offset,通过对比offset,来进行psync增量数据同步。 > 主节点:binlog线程 > 从节点:io线程和sql线程 > ![](https://i.imgur.com/gka8FsQ.png) ### mysql语句执行过程 > 1. 客户端发送一条查询给服务器。 > 2. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。 > 3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。 > 4. MySQL根据优化器生成的执行计划,再调用存储引擎的API来执行查询。将结果返回给客户端。 ### 串行化的加锁方法 innodb下,串行化的读并不会锁表,而是根据where条件锁一个范围。 但锁一个范围并不是简单锁记录,因为涉及到Next-key lock。 ### mysql日志 > 1. 重做日志(redo log) > ![](https://i.imgur.com/U0L54c9.png) > 2. 回滚日志(undo log) > ![](https://i.imgur.com/MsLbBXe.png) > 3. 二进制日志(binlog) > ![](https://i.imgur.com/Z2prdAi.png) > 4. 错误日志(errorlog) > 5. 慢查询日志(slow query log) > 6. 一般查询日志(general log) > 7. 中继日志(relay log) ### 索引优缺点 > **优点**: > 大大加快数据检索的速度。 将随机I/O变成顺序I/O(因为B+树的叶子节点是连接在一起的) 加速表与表之间的连接 > **缺点**: > 从空间角度考虑,建立索引需要占用物理空间 > 从时间角度 考虑,创建和维护索引都需要花费时间,例如对数据进行增删改的时候都需要维护索引。 ### MySQL主要的索引类型主要有FULLTEXT,HASH,BTREE,RTREE。 > RTREE:空间数据索引,多用于地理数据的储存,优点在于范围查找 ### 什么字段适合建立索引 1、表的主键、外键必须有索引; 2、数据量超过300的表应该有索引; 3、经常与其他表进行连接的表,在连接字段上应该建立索引; 4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引; 5、索引应该建在选择性高的字段上; 6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引; ### 索引适用场景 索引优化 索引设计原则 ![](https://i.imgur.com/fNfXzY8.png) ### 如何优化查询过程中的数据访问? ![](https://i.imgur.com/gdZ6RBr.png) ### 大表数据结构查询如何优化 ![](https://i.imgur.com/y3m0Ibe.png) ![](https://i.imgur.com/bwliVcH.png) ### MVCC实现原理 查不到相应的事务版本号怎么办 > 1. 版本号 每个事务会有自增的版本号 > 具体针对操作 > SELECT > 作为查询的结果要满足两个条件: > 当前事务所要查询的数据行快照的创建版本号必须小于当前事务的版本号,这样做的目的是保 > 证当前事务读取的数据行的快照要么是在当前事务开始前就已经存在的,要么就是当前事务自 > 身插入或者修改过的。 > 当前事务所要读取的数据行快照的删除版本号必须是大于当前事务的版本号,如果是小于等于 > 的话,表示该数据行快照已经被删除,不能读取。 > INSERT > 将当前系统版本号作为数据行快照的创建版本号。 > DELETE > 将当前系统版本号作为数据行快照的删除版本号。 > UPDATE > 保存当前系统版本号为更新前的数据行快照创建行版本号,并保存当前系统版本号为更新后的数据 > 行快照的删除版本号,其实就是,先删除在插入即为更新。 > 2. 行记录隐藏的列 > 3. undo日志 MVCC做使用到的快照会存储在Undo日志中,该日志通过回滚指针将一个一个数据行的所有快照 连接起 ### 索引失效场景 ![](https://i.imgur.com/qeqY7Md.png) ### MySQL中的innoDB引擎的行锁模式以及其是如何实现的 ![](https://i.imgur.com/b9RQONW.png) ![](https://i.imgur.com/IXgXkFb.png) ### Mysql死锁 ![](https://i.imgur.com/T4DyiVo.png) ### Mysql in和exists ![](https://i.imgur.com/rWa1wPq.png) ### varchar和char的区别 ![](https://i.imgur.com/QOHymXm.png) ![](https://i.imgur.com/YM2yxUQ.png) ### limit分页优化 > 在LIMIT偏移量较大的时候,查询效率会变低,可以记录每次取出的最大ID,下次查询时可以利用ID进行查询 > 建立复合索引 ### 如何优化UNION查询 > 如果不需要对结果集进行去重或者排序建议使用UNION ALL,会好一些。 ### 如何优化WHERE子句 > 不要在where子句中使用!=和<>进行不等于判断,这样会导致放弃索引进行全表扫描。 不要在where子句中使用null或空值判断,尽量设置字段为not null。 > 尽量使用union all代替or > 在where和order by涉及的列建立索引 > 尽量减少使用in或者not in,会进行全表扫描 > 在where子句中使用参数会导致全表扫描 避免在where子句中对字段及进行表达式或者函数操作会导致存储引擎放弃索引进而全表扫描 ### sql语句执行顺序 ![](https://i.imgur.com/ui82K8O.png) ### 数据库自增id和uuid的区别 > **使用自增ID的好处:** > 1. 字段长度较uuid会小很多。 > 2. 数据库自动编号,按顺序存放,利于检索 > 3. 无需担心主键重复问题 > **使用自增ID的缺点:** > 1. 因为是自增,在某些业务场景下,容易被其他人查到业务量。 > 2. 发生数据迁移时,或者表合并时会非常麻烦 > 3. 在高并发的场景下,竞争自增锁会降低数据库的吞吐能力 > UUID:通用唯一标识码,UUID是基于当前时间、计数器和硬件标识等数据计算生成的。 > **使用UUID的优点** > 1. 唯一标识,不会考虑重复问题,在数据拆分、合并时也能达到全局的唯一性。 > 2. 可以在应用层生成,提高数据库的吞吐能力。 > 3. 无需担心业务量泄露的问题。 > **使用UUID的缺点:** > 1. 因为UUID是随机生成的,所以会发生随机IO,影响插入速度,并且会造成硬盘的使用率较低。 > 2. UUID占用空间较大,建立的索引越多,造成的影响越大。 > 3. UUID之间比较大小较自增ID慢不少,影响查询速度。 ### drop、delete和truncate的区别? ![](https://i.imgur.com/wW9jnrv.png) ### B+树为什么能减少io次数 * 相比于一点点的回表查询,这样一次就能找到。 * 范围查询时可以通过访问叶子节点的链表进行有序遍历,而不再需要中序回溯访问结点。 * 非叶子结点只存储关键字key,一方面这种结构相当于划分出了更多的范围,加快了查询速度,另一方面相当于单个索引值大小变小,同一个页可以存储更多的关键字,读取单个页就可以得到更多的关键字,可检索的范围变大了,相对 IO 读写次数就降低了。 ### 垂直分表 垂直分库 水平分表 水平分库 #### 垂直分表: > 将一个表按照字段分成多个表,每个表存储其中一部分字段。一般会将常用的字段放到一个表中,将不常用的字段放到另一个表中。 > **垂直分表的优势**: > 1. 避免IO竞争减少锁表的概率。因为大的字段效率更低,第一数据量大,需要的读取时间长。第二, 大字段占用的空间更大,单页内存储的行数变少,会使得IO操作增多。 > 2. 可以更好地提升热门数据的查询效率。 #### 垂直分库 > 按照业务对表进行分类,部署到不同的数据库上面,不同的数据库可以放到不同的服务器上面。 **垂直分库的优势** > 1. 降低业务中的耦合,方便对不同的业务进行分级管理。 > 2. 可以提升IO、数据库连接数、解决单机硬件资源的瓶颈问题。 > **垂直拆分(分库、分表)的缺点:** > 1. 主键出现冗余,需要管理冗余列 > 2. 事务的处理变得复杂 > 3. 仍然存在单表数据量过大的问题 #### 水平分表 > 在同一个数据库内,把同一个表的数据按照一定规则拆分到多个表中。 > **好处** > 1. 解决了单表数据量过大的问题 > 2. 避免IO竞争并减少锁表的概率 #### 水平分库 > 1. 解决了单库大数据量的瓶颈问题 > 2. IO冲突减少,锁的竞争减少,某个数据库出现问题不影响其他数据库(可用性),提高了系统的稳 定性和可用性 > **缺点:** > 分片事务一致性难以解决 跨节点JOIN性能差,逻辑会变得复杂 数据扩展难度大,不易维护 ## redis ### Redis集群方案 #### 客户端分区方案 客户端就已经决定数据会被存储到哪个redis 节点或者从哪个redis 节点 读取数据。其主要思想是采用 哈希算法 将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key会 映射到特定的 Redis 节点上。 #### 代理分区方案 客户端 发送请求到一个 代理组件,代理 解析 客户端 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。 #### 查询路由方案 客户端随机地 请求任意一个 Redis 实例,然后由 Redis 将请求 转发 给 正确 的 Redis 节点。Redis Cluster 实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向( redirected)到正确的 Redis 节点。 ### 数据分布 ![](https://i.imgur.com/IzqoC20.png) #### 节点取余分区 使用特定的数据,如 Redis 的 键 或 用户 ID,再根据 节点数量 N 使用公式:hash(key)% N 计算出 哈希值,用来决定数据 映射 到哪一个节点上。 ### redis常用数据结构 #### string ![](https://i.imgur.com/l97q45D.png) #### list ![](https://i.imgur.com/qlhsopM.png) #### hash ![](https://i.imgur.com/NEIM2bp.png) #### set ![](https://i.imgur.com/UlrsSHv.png) #### set内部结构 > redis的集合对象set的底层存储结构特别神奇,我估计一般人想象不到,底层使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。是不是觉得用hashtable存储set是一件很神奇的事情。 > set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下: > 结合对象保存的所有元素都是整数值 > 集合对象保存的元素数量不超过512个 #### sorted set ![](https://i.imgur.com/fjcwttA.png) #### redis跳表 ![](https://i.imgur.com/KNIWTEL.png) ### Redis 单线程模型详解(多路IO复用) ![](https://i.imgur.com/99m4bw3.png) ![](https://i.imgur.com/Zp3ZD6R.png) ### 过期的数据的删除策略了解么? ![](https://i.imgur.com/EXCcVjN.png) ### reids持久化 ![](https://i.imgur.com/IGh7h3u.png) ![](https://i.imgur.com/Rpb7A2G.png) ![](https://i.imgur.com/nDZ6zSR.png) ![](https://i.imgur.com/9z34qSO.png) ![](https://i.imgur.com/3VLhjpl.png) ### redis事务 由于redis不支持回滚,redis不具有原子性 ![](https://i.imgur.com/8Y2IfR0.png) ### 如何保证缓存和数据库数据的⼀致性? 会有问题,如果他还没来得及删除就从redis里面读,读到原值 ![](https://i.imgur.com/yNastn6.png) 可以用:先删redis再写mysql 问题:删完redis可能先去mysql读数据了,但是这个时候mysql里面还没有更新 解决:加分布式锁,给删过程加锁,直到写操作时(也就是更新操作时)解锁。 ### redis分布式锁 > 加锁命令:SETNX key value,当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。 > 解锁命令:DEL key,通过删除键值对释放锁,他会确定是不是同一个线程,不要误解锁。 > 锁超时:EXPIRE key timeout, 设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。 ### redis hash扩容 > ht[0],是存放数据的table,作为非扩容时容器。 > ht[1],只有正在进行扩容时才会使用,它也是存放数据的table,长度为ht[0]的两倍 > 扩容时,单线程A负责把数据从ht[0] copy到ht[1] 中。如果这时有其他线程 > 进行读操作:会先去ht[0]中找,找不到再去ht[1]中找。 > 进行写操作:直接写在ht[1]中。 > 进行删除操作:与读类似。 ### redis架构 #### 单机模式 #### 主从复制 > **全量同步** > Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下: > - 从服务器连接主服务器,发送SYNC命令; > - 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; > - 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; > - 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; > - 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; > - 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; > **增量同步** > Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 > 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。 > **Redis主从同步策略** > 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。 #### 哨兵模式 ![](https://i.imgur.com/yDSPTWO.png) > 监控 (哨兵可时刻监测Redis主节点和从节点的工作状态) > 通知 (当检测到某一个redis实例运行出错时,哨兵可通知系统管理员或其他计算机程序) > 自动故障转移 (automatic failover) (若主节点运行出错,哨兵可触发故障转移机制,将某个从节点选举为新的主节点,并通知应用程序连接到新的主节点) > 提供配置 > **好处** 多个哨兵一致同意某个主节点已经不可用时才会触发故障检测机制,从而减少“谎报军情”的情况 > 即使某些哨兵进程不可用,其余的哨兵也可以继续工作 #### 集群模式 Cluster 采用无中心结构,每个节点都可以保存数据和整个集群状态,每个节点都和其他所有节点连接。Cluster 一般由多个节点组成,节点数量至少为 6 个才能保证组成完整高可用的集群,其中三个为主节点,三个为从节点。三个主节点会分配槽,处理客户端的命令请求,而从节点可用在主节点故障后,顶替主节点。 ### redis缓存穿透 缓存击穿 缓存雪崩 #### 缓存雪崩 > 当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。 > 1、在原有的失效时间上加上一个随机值,比如1-5分钟随机。这样就避免了因为采用相同的过期时间导致的缓存雪崩。 > 2、使用熔断机制。当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。 > 3、提高数据库的容灾能力,可以使用分库分表,读写分离的策略。 > 4、为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群,提高Redis的容灾性。 #### 缓存穿透 > 如发送的请求传进来的key是不存在Redis中的,那么就查不到缓存,查不到缓存就会去数据库查询。假如有大量这样的请求,这些请求像“穿透”了缓存一样直接打在数据库上,这种现象就叫做缓存穿透 > 1、把无效的Key存进Redis中。如果Redis查不到数据,数据库也查不到,我们把这个Key值保存进Redis,设置value="null",当下次再通过这个Key查询时就不需要再查询数据库。这种处理方式肯定是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。 > 2、使用布隆过滤器。布隆过滤器的作用是某个 key 不存在,那么就一定不存在,它说某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回。 #### 缓存击穿 > 缓存击穿是一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增。这种现象就叫做缓存击穿。 > 解决方案: > 1、上面说过了,如果业务允许的话,对于热点的key可以设置永不过期的key。 > 2、使用互斥锁。如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求,防止数据库打死。当然这样会导致系统的性能变差。 ## rabbitmq ### 如果发不出去怎么办 启动事务模式,发不出去就会阻塞,直到发出去。 ### 什么时候需要rabbitmq #### 异步通信 将业务中属于非核心或不重要的流程部分,使用消息异步通知的方式发给目标系统,这样主业务流程无需同步等待其他系统的处理结果,从而达到系统快速响应的目的。 如网站的用户注册场景,在用户注册成功后,还需要发送注册邮件与注册短信,这两个流程使用RabbitMQ消息服务通知邮件发送系统与短信发送系统,从而提升注册流程的响应速度。 #### 错峰流控与流量削峰 在电子商务系统或大型网站中,上下游系统处理能力存在差异,处理能力高的上游系统的突发流量可能会对处理能力低的某些下游系统造成冲击,需要提高系统的可用性的同时降低系统实现的复杂性。电商大促销等流量洪流突然来袭时,可以通过队列服务堆积缓存订单等信息,在下游系统有能力处理消息的时候再处理,避免下游订阅系统因突发流量崩溃。消息队列提供亿级消息堆积能力,3天的默认保留时长,消息消费系统可以错峰进行消息处理。 另外,在商品秒杀、抢购等流量短时间内暴增场景中,为了防止后端应用被压垮,可在前后端系统间使用RabbitMQ消息队列传递请求。 #### 系统解耦 以电商秒杀、抢购等流量短时间内暴增场景为例,传统做法是,用户下单后,订单系统发送查询请求到库存系统,等待库存系统返回请求结果给订单系统。如果库存系统发生故障,订单系统获取不到数据,订单失败。这种情况下,订单系统和库存系统两个子系统高耦合。 引入RabbitMQ消息队列,当用户下单后,将消息写入到RabbitMQ消息队列中,然后返回用户下单成功。 库存系统订阅下单的消息,消费下单消息,然后进行库操作。即使库存系统出现故障,也不影响用户下单。 #### 高可用 镜像队列是开源RabbitMQ 2.6.0版本新增的一个功能,允许集群将队列镜像到其他代理上,当集群某一代理宕机后,队列能自动切换到镜像中的其他代理,保证服务的可用性。 普通队列,由于队列以及队列内容仅存储在单代理上,当该代理故障后,对应的队列不可用。 RabbitMQ引入镜像队列机制,将队列镜像到集群中的其他代理上,每一个镜像队列包含一个主队列和多个从队列,并分布在集群的不同代理上。 ### rabbitMQ工作模式 > 1. **simple模式** 最基本的收发 > 2. **work工作模式** 消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息 > 3. **publish/subscribe**发布订阅(共享资源) 1、每个消费者监听自己的队列;生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息 > 4. **Routing模式**消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息; > 5. **topic 主题模式** 交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费 ### 交换机类型(exchange) ![](https://i.imgur.com/sYooD4F.png) ### 消息丢失 > 三个方面来确认:生产者确认机制、消费者手动确认消息和持久化。 > 1. 生产者确认机制 只要消息成功发送到交换机之后,RabbitMQ就会发送一个ack给生产者(即使消息没 有Queue接收,也会发送ack)。如果消息没有成功发送到交换机,就会发送一条nack消息,提示发送失败。 > 2. 消费者手动确认消息 解决方法:消费者设置为手动确认消息。消费者处理完逻辑之后再给broker回复ack,表示消息已经成功消费,可 以从broker中删除。当消息者消费失败的时候,给broker回复nack,根据配置决定重新入队还是从broker移除, 或者进入死信队列。只要没收到消费者的 acknowledgment,broker 就会一直保存着这条消息,但不会 requeue,也不会分配给其他 消费者 > 3. 持久化 当发布一条消息到交换机上时,Rabbit会先把消息写入持久化日志,然后才向生产者发送响应。一旦从队列中消费 了一条消息的话并且做了确认,RabbitMQ会在持久化日志中移除这条消息。在消费消息前,如果RabbitMQ重启 的话,服务器会自动重建交换机和队列,加载持久化日志中的消息到相应的队列或者交换机上,保证消息不会丢失。 ### 消息重复 > 1. 生产时消息重复 丢弃重复消息 > 2. 消费时消息重复 发送消息时让每个消息携带一个全局的唯一ID,在消费消息时先判断消息是否已经被消费过,保证消息 消费逻辑的幂等性。具体消费过程为:消费者获取到消息后先根据id去查询redis/db是否存在该消息 如果不存在,则正常消费,消费完毕后写入redis/db 如果存在,则证明消息被消费过,直接丢弃 ### 消息堆积 检查消费者是否在线,检查消费端配置属性,是否未配置异常队列的监听器; 检查下生产端或者消费端服务的内存或者cpu是否正常,是否是因为内存溢出导致rabbit服务消息无法发送; 对于手动配置ack机制的,要检查配置是否确认ack,否则会造成消息积压; 开启了手动ack,消费速度跟不上生产速度,检查下消费端是否处理消息流程过⻓,比较耗时。 ### RabbitMQ如何保证有序消费消息 所谓有序指的是同一事物的多条消息让唯一的一个消费者消费,并保证消费顺序 造成消息乱序消费的原因有两种 ![](https://i.imgur.com/RROJhPw.png) ![](https://i.imgur.com/Sz3e4zf.png) 如何保证有序 ![](https://i.imgur.com/lZ0vfrh.png) ![](https://i.imgur.com/F3dMnUc.png) ## 项目问题 ### dubbo #### Dubbo是什么 Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成。 RPC 指的是远程调用协议,也就是说两个服务器交互数据。 #### Dubbo 和 Spring Cloud 有什么区别? #### Dubbo的核心功能 * Remoting:网络通信框架,提供对多种NIO框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。 * Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。 * Registry:服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。 #### Dubbo的核心组件 ![](https://i.imgur.com/YGWt4ve.png) #### Dubbo的架构设计 Dubbo框架设计一共划分了10个层: * 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。 * 配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心。 * 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton。 * 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心。 * 集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心。 * 监控层(Monitor):RPC调用次数和调用时间监控。 * 远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。 * 信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心。 * 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心。 #### Dubbo服务注册与发现的流程 * Provider(提供者)绑定指定端口并启动服务 * 指供者连接注册中心,并发本机IP、端口、应用信息和提供服务信息发送至注册中心存储 * Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心 * 注册中心根据 消费者所求服务信息匹配对应的提供者列表发送至Consumer应用缓存。 * Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。 * Provider 状态变更会实时通知注册中心、在由注册中心实时推送至Consumer #### dubbo注册中心集群挂掉,发布者和订阅者之间还能通信吗 可以的,启动dubbo时,消费者会从zookeeper拉取注册的生产者的地址接口等数据,缓存在本地。 每次调用时,按照本地存储的地址进行调用。 #### Dubbo使用的是什么通信框架 默认使用NIO Netty框架 #### Dubbo集群提供了哪些负载均衡策略? * Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀; * RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题; * LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求; * ConstantHash LoadBalance: 一致性Hash策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动; 缺省时为Random随机调用 #### dubbo和spring cloud的区别 最大的区别:Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC通信。 而SpringCloud是基于Http协议+Rest接口调用远程过程的通信,相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。 ![](https://i.imgur.com/WXanR1N.png) #### Dubbo的集群容错方案有哪些? * Failover Cluster 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。 * Failfast Cluster 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 * Failsafe Cluster 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 * Failback Cluster 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 * Forking Cluster 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。 * Broadcast Cluster 广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。 默认 Failover Cluster ### 秒杀 ![](https://i.imgur.com/ZNV3oxA.png) ### netty #### 什么是Netty Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。并发高,传输快,封装好 ### docker > cker的英文翻译是“搬运工”的意思,他搬运的东西就是我们常说的集装箱Container,Container 里面装的是任意类型的 App,我们的开发人员可以通过 Docker 将App 变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。 > 从概念上来看 Docker 和我们传统的虚拟机比较类似,只是更加轻量级,更加方便使,Docker 和虚拟机最主要的区别有以下几点: > 虚拟化技术依赖的是物理CPU和内存,是硬件级别的;而我们的 Docker 是构建在操作系统层面的,利用操作系统的容器化技术,所以 Docker 同样的可以运行在虚拟机上面。 > 我们知道虚拟机中的系统就是我们常说的操作系统镜像,比较复杂;而 Docker 比较轻量级,我们可以用 Docker 部署一个独立的 Redis,就类似于在虚拟机当中安装一个 Redis 应用,但是我们用 Docker 部署的应用是完全隔离的。 > 我们都知道传统的虚拟化技术是通过快照来保存状态的;而 Docker 引入了类似于源码管理的机制,将容器的快照历史版本一一记录下来,切换成本非常之低。 > 传统虚拟化技术在构建系统的时候非常复杂;而 Docker 可以通过一个简单的 Dockerfile 文件来构建整个容器,更重要的是 Dockerfile 可以手动编写,这样应用程序开发人员可以通过发布 Dockerfile 来定义应用的环境和依赖,这样对于持续交付非常有利。 > ### 微服务 ### fastdfs 上传: ![](https://i.imgur.com/i94MW4W.png) 下载: ![](https://i.imgur.com/z8BsPou.png) ### elasticsearch #### 脑裂问题 > 一个正常es集群中只有一个主节点,主节点负责管理整个集群,集群的所有节点都会选择同一个节点作为主节点所以无论访问那个节点都可以查看集群的状态信息。 而脑裂问题的出现就是因为从节点在选择主节点上出现分歧导致一个集群出现多个主节点从而使集群分裂,使得集群处于异常状态。 **网络原因**         内网一般不会出现此问题,可以监控内网流量状态。外网的网络出现问题的可能性大些。 **节点负载 **          > 主节点即负责管理集群又要存储数据,当访问量大时可能会导致es实例反应不过来而停止响应,此时其他节点在向主节点发送消息时得不到主节点的响应就会认为主节点挂了,从而重新选择主节点。 **回收内存 **          > 大规模回收内存时也会导致es集群失去响应。所以内网负载的可能性大,外网网络的可能性大。 > 再来看看 **ES(Elastic Search)主动选举机制**: elasticsearch集群一旦建立起来以后,会选举出一个master,其他都为slave节点。但是具体操作的时候,每个节点都提供写和读的操作。就是说,你不论往哪个节点中做写操作,这个数据也会分配到集群上的所有节点中。 这里有某个节点挂掉的情况,如果是slave节点挂掉了,那么首先关心,数据会不会丢呢?不会。如果你开启了replicate,那么这个数据一定在别的机器上是有备份的。别的节点上的备份分片会自动升格为这份分片数据的主分片。这里要注意的是这里会有一小段时间的yellow状态时间。 如果是主节点挂掉怎么办呢?当从节点们发现和主节点连接不上了,那么他们会自己决定再选举出一个节点为主节点。但是这里有个脑裂的问题,假设有5台机器,3台在一个机房,2台在另一个机房,当两个机房之间的联系断了之后,每个机房的节点会自己聚会,推举出一个主节点。这个时候就有两个主节点存在了,当机房之间的联系恢复了之后,这个时候就会出现数据冲突了。 **预防方案** > 1:角色分离 在es集群中配置2到3个主节点并且让它们只负责管理不负责存储,从节点只负责存储。另外从节点禁用自动发现机制并为其指定主节点,在elasticsearch.yml文件中。 > 主节点:node.master =true   node.data=false 从节点:node.master =false   node.data=ture discovery.zen.ping.multicast.enabled:false discovery.zen.ping.unicast.hosts:["host1", "host2:port"] > 2:参数配置 discovery.zen.ping_timeout:3 此参数指定从节点访问主节点后如果3秒之内没有回复则默认主节点挂了,我们可以适当的把它改大,这样可以减少出现脑裂的概率。 > discovery.zen.minimum_master_nodes:1   该参数的意思是,当具备成为主节点的从节点的个数满足这个数字且都认为主节点挂了则会进行选举产生新的主节点。 > 例如:es集群有三个从节点有资格成为主节点,这时这三个节点都认为主节点挂了则会进行选举,此时如果这个参数的值是4则不会进行选举。 >我们可以适当的把这个值改大,减少出现脑裂的概率,官方给出的建议是(n/2)+1,n为有资格成为主节点的节点数node.master=true。 ### jwt session cookie token #### jwt原理 一般认证流程: 1、用户向服务器发送用户名和密码。 2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。 3、服务器向用户返回一个 session_id,写入用户的 Cookie。 4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。 5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。 **不适合跨域,服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。** ![](https://i.imgur.com/uhb3ytX.png) ![](https://i.imgur.com/aaC0yX0.png) **cookie**:在第一次客户端发起访问时,服务端给客户端设置一个cookie,cookie存放在客户端浏览器里面,第二次客户端发起访问的时候,就可以去带着这个cookie去访问客户端。 cookie 放在http响应头里 **session**: session是基于cookie的,当客户端第一次发起访问的时候,服务端会创建一个session在自己这边,并且会把一个sessionID放在客户端的cookie里面,在客户再发起访问时,读取他的cookie获取sessionId来访问session **token**:在客户端访问服务端后,服务端会颁发一个token给客户端,这样下次客户端再次访问时就不需要输入用户名和密码了。 ### springboot怎么连接数据库 JDBC 连接数据库 1.属性配置文件(application.properties) 2.pom.xml 配置maven依赖 3.entity里面设置对应表名 ### restful风格 RESTful API 是应用程序接口 ( API ) 的一种架构风格,它使用 HTTP 请求来访问和使用数据。该数据可用于 GET、PUT、POST 和 DELETE 数据类型,这些数据类型是指有关资源的操作的读取、更新、创建和删除。 网站的 API 是允许两个软件程序相互通信的代码。API 阐明了开发人员编写从操作系统或其他应用程序请求服务的程序的正确方法。 ### RPC ![](https://i.imgur.com/pdKAiC2.png) ### 微服务 所以,现在主流的设计一般会采用微服务架构。其思路不是开发一个巨大的单体式应用,而是将应用分解为小的、互相连接的微服务。一个微服务完成某个特定功能,比如乘客管理和下单管理等。每个微服务都有自己的业务逻辑和适配器。一些微服务还会提供API接口给其他微服务和应用客户端使用。 ## 排序算法 ### 堆排序默认 默认是小根堆 大根堆实现 ![](https://i.imgur.com/noi9cQQ.png) ### 排序算法稳定 > 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,A1=A2,且A1在A2之前,而在排序后的序列中,A1仍在A2之前,则称这种排序算法是稳定的;否则称为不稳定的。 > 稳定也可以理解为一切皆在掌握中,元素的位置处在你在控制中.而不稳定算法有时就有点碰运气,随机的成分.当两元素相等时它们的位置在排序后可能仍然相同.但也可能不同.是未可知的. > **选择排序 (不稳定)** > 选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择中,如果当前元素比一个元素大,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。 > **堆排序 (不稳定)** > 堆的结构是节点i的孩子为 2i 和 2i+1 节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程,首先要根据floyd算法建堆,因此要从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2,...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。 > eg:{5A,6,5B,7,8} --> {8,7,5B,5A,6} ,两个5的顺序颠倒了。 > **插入排序 (稳定)** > 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。插入调用有序序列的search操作,该操作返回的是第一个大于该元素的位置,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。 > **希尔排序 (不稳定)** > 希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。 > **冒泡排序 (稳定)** > 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 > **快速排序 (不稳定)** > 快速排序有两个方向,当a[i] <= a[center_index],左边的i下标一直往右走,其中center_index是中枢元素的数组下标,一般取为数组第0个元素。 > 当a[j] > a[center_index],右边的j下标一直往左走。如果i和j都走不动了,i <= j,交换a[i] 和 a[j],重复上面的过程,直到i>j。交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱. > 比如序列为 5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。 > **归并排序 (稳定)** > 归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。 > **基数排序 (稳定)** > 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。 ### 约有序应该用什么 > 选择和冒泡 ### 时间空间复杂度,稳定性 ![](https://i.imgur.com/aX2TxOT.png) ### 原理以及源码 **冒泡排序** > 比较相邻的元素。如果第一个比第二个大,就交换它们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数; > 针对所有的元素重复以上的步骤,除了最后一个;重复步骤1~3,直到排序完成。冒泡排序是稳定排序。 **选择排序:** > 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。重复第二步,直到所有元素均排序完毕。用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的。不过,一般提到排序算法时,大家往往会默认是数组实现,所以选择排序是不稳定的。 **插入排序:** > 把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。 > 从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置。 > 重复上述过程直到最后一个元素被插入有序子数组中 **归并排序** > 该算法是采用分治法 > 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 > 分:先把原数组分割成最小单元 > 治:把两两单元合并,合并的时候使用两个指针,谁小放进去,移动指针。 ![](https://i.imgur.com/xB6ouhG.png) **快速排序** > 核心思想:每次取最左边为基准数,每次保证小于基准数的放在基准数左边,大于基准数的在基准数右边,对分割剩下的也是同样,最后到每个部分都是一个数的时候就完成了。(因为用了栈所以空间复杂度(logn)) > 设置两个指针i,j分别指向所在的起点和终点,一定是j先往左走,找到一个小于基准数的值,然后i往右走,找到一个大于基准数的值,交换这两个值,一直这样,直到i遇到j,此时交换基准数和该位置的值,分割成两个小数组,再对两个小数组进行操作 ![](https://codimd.s3.shivering-isles.com/demo/uploads/upload_e072477bf9a5898ad982d8b5106728dd.PNG) **堆排序** > 为什么空间复杂度o1?因为在原数组上构造堆。 > 核心思想:构造大根堆,把堆首与堆尾交换,堆的尺寸减1,调整堆,重复堆首与堆尾交换,直到堆的尺寸为1. **归并代码** public static void main(String[] args) { int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8}; mergeSort(arrays, 0, arrays.length - 1); System.out.println("公众号:Java3y" + arrays); } /** * 归并排序 * * @param arrays * @param L 指向数组第一个元素 * @param R 指向数组最后一个元素 */ public static void mergeSort(int[] arrays, int L, int R) { //如果只有一个元素,那就不用排序了 if (L == R) { return; } else { //取中间的数,进行拆分 int M = (L + R) / 2; //左边的数不断进行拆分 mergeSort(arrays, L, M); //右边的数不断进行拆分 mergeSort(arrays, M + 1, R); //合并 merge(arrays, L, M + 1, R); } } /** * 合并数组 * * @param arrays * @param L 指向数组第一个元素 * @param M 指向数组分隔的元素 * @param R 指向数组最后的元素 */ public static void merge(int[] arrays, int L, int M, int R) { //左边的数组的大小 int[] leftArray = new int[M - L]; //右边的数组大小 int[] rightArray = new int[R - M + 1]; //往这两个数组填充数据 for (int i = L; i < M; i++) { leftArray[i - L] = arrays[i]; } for (int i = M; i <= R; i++) { rightArray[i - M] = arrays[i]; } int i = 0, j = 0; // arrays数组的第一个元素 int k = L; //比较这两个数组的值,哪个小,就往数组上放 while (i < leftArray.length && j < rightArray.length) { //谁比较小,谁将元素放入大数组中,移动指针,继续比较下一个 if (leftArray[i] < rightArray[j]) { arrays[k] = leftArray[i]; i++; k++; } else { arrays[k] = rightArray[j]; j++; k++; } } //如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中(剩下的都是大数字) while (i < leftArray.length) { arrays[k] = leftArray[i]; i++; k++; } //如果右边的数组还没比较完,左边的数都已经完了,那么将右边的数抄到大数组中(剩下的都是大数字) while (j < rightArray.length) { arrays[k] = rightArray[j]; k++; j++; } } **快排代码** ![](https://codimd.s3.shivering-isles.com/demo/uploads/upload_07b227ee539bfd1b208d2d449261d77c.PNG) **堆排代码** public static void main(String[] args) { int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8}; mergeSort(arrays, 0, arrays.length - 1); System.out.println("公众号:Java3y" + arrays); } /** * 归并排序 * * @param arrays * @param L 指向数组第一个元素 * @param R 指向数组最后一个元素 */ public static void mergeSort(int[] arrays, int L, int R) { //如果只有一个元素,那就不用排序了 if (L == R) { return; } else { //取中间的数,进行拆分 int M = (L + R) / 2; //左边的数不断进行拆分 mergeSort(arrays, L, M); //右边的数不断进行拆分 mergeSort(arrays, M + 1, R); //合并 merge(arrays, L, M + 1, R); } } /** * 合并数组 * * @param arrays * @param L 指向数组第一个元素 * @param M 指向数组分隔的元素 * @param R 指向数组最后的元素 */ public static void merge(int[] arrays, int L, int M, int R) { //左边的数组的大小 int[] leftArray = new int[M - L]; //右边的数组大小 int[] rightArray = new int[R - M + 1]; //往这两个数组填充数据 for (int i = L; i < M; i++) { leftArray[i - L] = arrays[i]; } for (int i = M; i <= R; i++) { rightArray[i - M] = arrays[i]; } int i = 0, j = 0; // arrays数组的第一个元素 int k = L; //比较这两个数组的值,哪个小,就往数组上放 while (i < leftArray.length && j < rightArray.length) { //谁比较小,谁将元素放入大数组中,移动指针,继续比较下一个 if (leftArray[i] < rightArray[j]) { arrays[k] = leftArray[i]; i++; k++; } else { arrays[k] = rightArray[j]; j++; k++; } } //如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中(剩下的都是大数字) while (i < leftArray.length) { arrays[k] = leftArray[i]; i++; k++; } //如果右边的数组还没比较完,左边的数都已经完了,那么将右边的数抄到大数组中(剩下的都是大数字) while (j < rightArray.length) { arrays[k] = rightArray[j]; k++; j++; } } ## 海量数据处理 > 1. **海量日志数据,提取出某日访问百度次数最多的那个IP。** > 可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP。 > 2. **搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串** > 第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成排序;然后,第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。 即,借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比所以,我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万,N’为300万) > 5. **有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词** > 第一步、先对这批海量数据预处理,在O(N)的时间内用Hash表完成排序;然后,第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。 即,借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比所以,我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万,N’为300万)。 > 7. **有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序** > 顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。 找一台内存在2G左右的机器,依次对用hash_map(query, query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件 > 9. **给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url** > 如果允许有一定的错误率,可以使用Bloom filter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否与Bloom filter,如果是,那么该url应该是共同的url(注意会有一定的错误率)。 > 11. **在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数** > 采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。 > 13. **给40亿个不重复的unsigned int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中** > bitmap > 15. **怎么在海量数据中找出重复次数最多的一个** > 先做hash,然后求模映射为小文件,求出每个小文件中重复次数最多的一个,并记录重复次数。然后找出上一步求出的数据中重复次数最多的一个就是所求(具体参考前面的题) > 17. **上千万或上亿数据(有重复),统计其中出现次数最多的钱N个数据。** > hash_map/搜索二叉树/红黑树等来进行统计次数。然后就是取出前N个出现次数最多的数据 > 19. **一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析** > hash+堆 ## 分布式理论 ### 一致性哈希(一致性hash) #### 为什么要使用一致性哈希 比如,在分布式的存储系统中,要将数据存储到具体的节点上,如果我们采用普通的hash算法进行路由,将数据映射到具体的节点上,如key%N,key是数据的key,N是机器节点数,如果有一个机器加入或退出这个集群,则所有的数据映射都无效了,如果是持久化存储则要做数据迁移,如果是分布式缓存,则其他缓存就失效了。 #### 一致性哈希算法详解 ##### 成环 按照常用的hash算法来将对应的key哈希到一个具有2^32次方个节点的空间中,即0 ~ (2^32)-1的数字空间中。现在我们可以将这些数字头尾相连,想象成一个闭合的环形。 ##### 服务器映射 通过取余将服务器放在环上 ##### 数据映射 现在我们将objectA、objectB、objectC、objectD四个对象通过特定的Hash函数计算出对应的key值,然后散列到Hash环上,然后从数据所在位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。 #### 服务器的删除与添加 宕机后,收到影响的数据会顺时针重新分配 添加时,通过顺时针的法则,对受到影响的数据顺时针重新分配 #### 如何解决一致性哈希的平衡性问题(当服务器节点比较少的时候,会出现一个问题,就是此时必然造成大量数据集中到一个节点上面,极少数数据集中到另外的节点上面。) 用虚拟节点解决,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。 ### cap > **Zookeeper 选择 CP** > Zookeeper 保证 CP,即任何时刻对 Zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZK 正在选举或者 ZK 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZK 不能保证服务可用性。 > **Eureka 选择 AP** > Eureka 保证 AP,Eureka 在设计时优先保证可用性,每一个节点都是平等的。一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点。只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。 > **一致性**: 所有节点在同一时间的数据完全一致 > 强一致性:对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。 > 弱一致性:如果能容忍后续的部分或者全部访问不到,则是弱一致性。 > 最终一致性:如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。 > **可用性** > 可用性指“Reads and writes always succeed”,即服务在正常响应时间内一直可用。 > **分区容错性** > 分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。 ![](https://i.imgur.com/d7Mgk7j.png) ### BASE理论 **基本可用(Basically Available)** 假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言: 响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用的搜索引擎可以在2秒作用返回结果。 功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单。但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。 **软状态(Soft State)** 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时 **最终一致性(Eventually Consistent)** 上面说软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性,从而达到数据的最终一致性。这个时间期限取决于网络延时、系统负载、数据复制方案设计等等因素。 ### 思维题 ### 几种分布式算法 #### paxos ![](https://i.imgur.com/3Rm3Mb6.png) 非常难于理解 没有给工程实现提供一个好的基础 #### raft ![](https://i.imgur.com/CXNezlx.png) ## 大胖子的个人介绍 ### 自我介绍 面试官您好,我是郑勐,本科就读于北京交通大学通信工程专业,研究生就读于代尔夫特理工大学无线通信专业,主要有过两段项目经历,第一个是一个线上购物平台的开发,主要是一个后端的开发,这个项目是一个分布式的项目, 包括多个微服务, 主要的技术栈包括了 mysql redis等。第二个是用dapr框架的虚拟actor开发线上购物,主要负责的是对于订单和库存的开发,并且包括实现业务回滚等,主要就是这样谢谢。 ### 具体讲讲这两个项目 **电商系统:** 本系统分为三层,展示层,服务层和持久层, 展示层就包括后台管理页面呀,商城首页之类的,这一层没有提供服务,只是展示 那服务层怎么被展示层调用呢?我使用的dubbo加zookeeper,就是服务层把信息通过dubbo注册给zookeeper,展示层通过dubbo去zookeeper里获取服务的相关信息。 持久层就是存放数据库相关的东西,比如本项目中,主要用关系型数据库 mysql以及非关系型数据库redis,主要数据存储在mysql中,对于一些不是那么重要的信息,为了减轻并发压力和安全考虑放在redis缓存中。大概就是这样。 **讲讲你项目中的难点:** 一个难点就是redis相关的,就是当我在做压力测试的时候,可能在高并发的情况下会出现缓存击穿的问题,也就是redis的作用失效了,很多请求直接打在mysql里面,这种情况的发送是由于很多请求是请求数据库中没有的key,这就导致redis不能更新新的数据,后面的请求就会一直去mysql里面。 解决办法:一开始我用土办法,就是如果是没有的key,就更新redis,赋给一个空的字符串,这样就避免了多次对mysql的直接访问。 后面发现这样只能避免单个情况,在大量随机数据访问中表现依然不好,布隆过滤器可以解决这种问题,他其实是把所有的key加到布隆过滤器里面,所以一下子就知道这个请求的key是否存在。 还有就是雪崩问题,比如热点失效了,一时间很多请求打在MySQL上,这种情况可以通过随机在区间内给热点赋失效时间,另一个就是说让他永久有效。 再有一个就是高并发下的可能会出现不一致性的情况,这种情况就是加锁,当线程需要访问MySQL的时候加锁,这样就能保证同时间只有一个线程访问mysql。具体流程就是如果获得锁,在有效时间内去mysql里访问,如果完成访归还锁,或者超时归还锁,其他线程再去竞争锁。 **简单介绍一下你的一个模块:** 好的我想讲一讲项目里的商品详情模块, 我觉得这个模块的技术难点有两个,一个是在商品详情页面上,根据销售属性值的组合去动态切换sku, 另一个就是为了提高响应速度和对抗高并发性,使用redis数据库去做一个缓存的过程 首先第一个,如何动态的通过销售属性值动态切换sku,首先找到这个sku的销售属性,比如有两个销售属性,那么我是把销售属性A值的ID|销售属性B值的ID作为hashMap的一个key,它们对应的skuID作为一个value储存在哈希表里,然后转成JSON字符串存放在页面中。在用户选择完销售属性之后呢,跟据这个hash的组合找到对应的skuID,然后在前台重定向到指定skuID的页面。 第二个,如何通过redis做一个缓存。 我是把整个skuInfo作为一个对象储存到redis的一个hash里面,这个hash的key是“sku” + SKUID + “info”,然后把这个类的属性作为value放进去。当请求到来的时候先去看redis里面有没有这个key,如果没有再去数据库里面找,并且找到的存入redis里面,下次再有请求就可以直接从redis里面拿。 但是遇到了一个问题 就是当我在做压力测试的时候,可能在高并发的情况下会出现缓存击穿的问题,也就是redis的作用失效了,很多请求直接打在mysql里面,这种情况的发送是由于很多请求是请求数据库中没有的key,这就导致redis不能更新新的数据,后面的请求就会一直去mysql里面。 解决办法:一开始我用土办法,就是如果是没有的key,就更新redis,赋给一个空的字符串,这样就避免了多次对mysql的直接访问。 后面发现这样只能避免单个情况,在大量随机数据访问中表现依然不好,后来去csdn上查,有个大佬说用布隆过滤器可以解决这种问题,他就是可以排除掉那些格式不对的请求,相当于在进入redis缓存之前还要用布隆过滤器来判断是否是有效的请求。 还有就是雪崩问题,比如热点失效了,一时间很多请求打在MySQL上,这种情况可以通过随机在区间内给热点赋失效时间,另一个就是说让他永久有效。 再有一个就是高并发下的可能会出现不一致性的情况,这种情况就是加锁,当线程需要访问MySQL的时候加锁,这样就能保证同时间只有一个线程访问mysql。具体流程就是如果获得锁,在有效时间内去mysql里访问,如果完成访归还锁,或者超时归还锁,其他线程再去竞争锁。 **项目不是前后端分离吗?为什么使用了Thymeleaf** 因为对vue不是很熟,thymeleaf又比较好操作易上手,所以我基本上先用thymeleaf调对了再去想怎么改vue。 **你这个项目登录是如何做的呢?如何判断是否登录** 首先在所有web层中,只要有请求过来,就会被拦截下来,通过反射来判断这个请求要访问的服务是否是需要登陆的,如果不需要登录,就打到指定页面,如果需要登陆,就打到登录页面,并且把他一开始想要访问的页面的url写进url里面,登陆完成后再通过url跳回去。 关于具体的登录,首先login的时候他会把用户输入的账号和密码打包成一个对象,然后先去redis里面看有没有以“user”+密码+“info”为key的记录,如果有就拿出来,如果没有就去mysql里面拿,通过seletc去选择符合用户名和密码匹配的,如果有就拿出来并且把他写redis里面,如果没有就返回null。所以只要返回的不是null就是登陆成功,他会把用户的id和名称写到一个map中,然后通过jwt根据这个map和他的ip地址打成一个token,并且把它拿到redis里面存放,设置一个失效时间。如果登录不成功这个token就是null。 然后在被拦截下来被验证他的登录状态时,就会去redis里面找这个token,如果有的话就说明登录是成功的并且会把他的登陆状态设置为登陆成功,反之就设置为失败,在返回给拦截器,如果是登陆成功,就刷新redis中相应登陆的失效时间,如果不成功就返回给前台说登陆失败。 **dapr 虚拟Actor:** 这个项目是我研究生阶段一门课的项目,用到的技术栈有springBoot Dapr redis 等, 本项目通过运用dapr的 虚拟actor来实现微服务,把每一个微服务绑定到一个actor上。 本项目包含了三个微服务 用户,订单,库存, 我拿订单系统为例,如果我们要查询一个订单的信息,比如findorder服务, 我们会给调用actorId 等于orderId的actor,然后返回actor的具体属性。 我在这个项目中主要负责订单和库存的开发,同时为了保证原子性,我选择使用编排sagas模式,也就是没有中央协调员,简单的如果事务失败则回滚。 **具体讲讲怎么做的回滚(或者技术难点)** 比如用户在结算时,系统会调取userId的actor,读取他的余额,如果余额不足就直接抛状态码400,后续不进行,返回失败。 如果余额是够的,他才会进入库存模块看库存是不是充足的,并做减法,发现库存不够就开始回滚,把之前减去的库存加回来,再跑到用户模块恢复减去的钱,抛400,说明支付失败。 ## Spring ### springboot自动装配原理 SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。 ### Spring,SpringBoot,SpringCloud,SpringMVC * Spring(Core) 是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。 * Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个 MVC 框架。 * Spring Boot 是基于Spring的一套快速开发整合包,更专注于构建服务(基于springmvc之上)。 * Spring Cloud是搭建分布式系统所需的一系列框架的有序集合,更专注于协同管理服务(基于Spring Boot之上)。 ### Spring的简介 ![](https://i.imgur.com/pColZp7.png) ### Spring用了哪些设计模式 ![](https://i.imgur.com/aPR2ctK.png) ### Spring模块 ![](https://i.imgur.com/i0Ei6bZ.png) ### IOC ![](https://i.imgur.com/NAU51G3.png) ### AOP ![](https://i.imgur.com/0pYlL6X.png) ### SpringAOP和AspectJ AOP有什么区别 SpringAOP运行时增强 AspectJAOP编译时增强 ### Spring bean的生命周期 ![](https://i.imgur.com/vDis2vg.png) ### Spring Bean的作用域 Singleton,创建唯一的实例 Protocal 每次请求创建一个bean的实例 request 每次HTTP请求创建一个bean,在当次http request内有效 session 每次HTTP请求创建一个bean,在当此http session内有效 ### Spring中单例bean的线程安全 用ThreadLocal变量。 ### Component和bean的区别 ![](https://i.imgur.com/TMvtrKA.png) ### bean的注解 ![](https://i.imgur.com/A0jzj9L.png) ### Spring 是如何解决循环依赖的 singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。 earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。 singletonFactories: 三级缓存,存储 singletonFactory。 ### Spring如何避免循环依赖 Spring是通过递归的方式获取目标bean及其所依赖的bean的; Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。 结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性。 ### SpringIOC 初始化 ![](https://i.imgur.com/OBRPrGQ.png) ### Spring注解的原理(IOC的原理) spring IOC容器启动的时候,根据我们指定的路径下去扫描我们的类,或者是直接扫描当前路径下的类; 我们的程序启动,告诉容器路径,它去指定的地方去加载bean,利用java的反射将我们类new出来,其实这个过程就是“依赖注入”那么当我拿到这个对象的时候,通过getClass()方法得到它的类,再通过getAnnotations()方法得到这个类的注解; 这样我们就可以在加载bean的时候实现 ,通过反射实例化我们的bean,然后通过getAnnotations()方法得到类和方法上的注解,然后根据自己定义的规则去实现各个注解需要做什么事情。 ### spring boot里controller是单例模式吗?既然是单例模式多个请求发送给controller产生线程安全问题吗?怎么解决? 1、在Controller中使用ThreadLocal变量 2、在spring配置文件Controller中声明 scope=“prototype”,每次都创建新的controller ### bean的生命周期 ![](https://i.imgur.com/rBAgJFC.png) ### Spring常用的注解 ![](https://i.imgur.com/qvB9GKp.png) ## SpringCloud组件 ### zookeeper #### Zookeeper 是什么?能做什么? Zookeeper 是一个「开源的」,是用于维护配置信息,命名,提供「分布式」同步和提供组服务的集中式服务。 ![](https://i.imgur.com/3RmzezK.jpg) 可以基于 Zookeeper 实现诸如「数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列」等功能。 ![](https://i.imgur.com/p96KhsB.jpg) Zookeeper 最常用的一个使用场景就是作为「注册中心」,生产者将自己提供的服务注册到 Zookeeper,然后消费者从 Zookeeper 中「拿到生产者的服务列表信息」,然后再去「调用生产者」的内容数据,比如 「Dubbo,Kafka」 都是使用 Zookeeper 作为注册中心的。 #### 说说 Zookeeper 的数据结构吧 ![](https://i.imgur.com/OgLlpLz.jpg) ZooKeeper 提供的名称空间与标准文件系统的名称空间非常相似。名称是由斜杠(“ /”)分隔的一系列路径元素。ZooKeeper 命名空间中的每个 znode 均由路径标识。「每个 znode 都有一个父对象」,其路径是 znode 的前缀,元素少一个;此规则的例外是 root(“ /”),它没有父项。此外,与标准文件系统完全一样,「如果 znode 有子节点,则无法删除它」。 ZooKeeper 与标准文件系统之间的主要区别在于,「每个 znode 都可以具有与之关联的数据」(每个文件也可以是目录,反之亦然),并且 znode 限于它们可以拥有的数据量。ZooKeeper 旨在存储协调数据:状态信息,配置,位置信息等。这种元信息通常以千字节(如果不是字节)来度量。「ZooKeeper 具有1M的内置完整性检查,以防止将其用作大型数据存储」,但是通常,它用于存储小得多的数据。 ![](https://i.imgur.com/VZJo00U.png) 「Znode的三种类型:」 「持久节点」(persistent node)节点会被持久 「临时节点」(ephemeral node),客户端断开连接后,ZooKeeper 会自动删除临时节点 「顺序节点」(sequential node),每次创建顺序节点时,ZooKeeper 都会在路径后面自动添加上10位的数字,从1开始,最大是2147483647 (2^32-1) ![](https://i.imgur.com/149UhO7.png) 「Znode的四种形式:」 「持久节点」:如 create /test/a "hello",通过 create 参数指定为持久节点 「持久顺序节点」:通过 create -s 参数指定为顺序节点 「临时节点」:通过 create -e 参数指定为顺序节点 「临时顺序节点」:通过 create -s -e 参数指定为临时及顺序节点 #### znode里面都存储了什么 Znode包含了「存储数据(data)」、「访问权限(acl)」、「子节点引用(child)」、「节点状态信息(stat)」 ![](https://i.imgur.com/P56yJLn.png) 「data」: znode存储的业务数据信息 「acl」: 记录客户端对znode节点的访问权限,如IP等。 「child」: 当前节点的子节点引用 「stat」: 包含Znode节点的状态信息,比如事务id、版本号、时间戳等等。 #### Zookeeper 的系统架构又是怎么样的? ![](https://i.imgur.com/U0sZfY3.png) ![](https://i.imgur.com/GDbBcr5.png) ![](https://i.imgur.com/D4nzteK.png) #### 那你继续给我讲讲 ZAB 协议吧 ![](https://i.imgur.com/W5rgNOs.png) #### Zookeeper初始化是如何进行Leader选举的? ![](https://i.imgur.com/Wtk7g4m.png) #### 如果Leader挂了,进入崩溃恢复,怎么选举Leader? ![](https://i.imgur.com/8hLe3Mv.png) #### 说说Wather监听机制和它的原理? ![](https://i.imgur.com/di8nSsq.png) ![](https://i.imgur.com/MjwNY2C.png) #### Zookeeper有哪些特性呢? ![](https://i.imgur.com/BRVnCYq.png) ![](https://i.imgur.com/AaAeBYn.png) #### Zookeeper 如何识别请求的先后顺序? ![](https://i.imgur.com/78BDr6l.png) #### 选举 leader 后是怎么进行数据同步的 ![](https://i.imgur.com/mcKuYfN.png) ![](https://i.imgur.com/YIdlypK.png) ![](https://i.imgur.com/6s2sY2C.png) #### Zookeeper 会有数据不一致的情况发生吗? 还是会有的,因为 Zookeeper 采用的是「过半写」机制,意味着「3台服务器只要有两台写成功就代表整个集群写成功」,如果刚好有请求打在这台还「未写的服务器」上就查询不到该数据,就会有数据不一致的情况产生。 ### Nginx负载均衡算法 1. 轮询(默认):个请求按时间顺序逐一分配到不同的后端服务,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响。 2. weight(轮询权值):weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。或者仅仅为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。 3. ip_hash:每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。 每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 4. fair:比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间 来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块。 ### Eureka > > Eureka 就是一个服务发现框架。分为三个角色:服务提供者,服务消费者,服务中介。有以下概念: > > 1. 服务提供者: 就是提供一些自己能够执行的一些服务给外界。 > > 2. 服务消费者: 就是需要使用一些服务的“用户”。 > > 3. 服务中介: 其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务中介那里,而服务消费者如需要消费一些服务(使用一些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。 > > 5. 服务注册 Register:官方解释:当 Eureka 客户端向 Eureka Server 注册时,它提供自身的元数据,比如IP地址、端口,运行状况 指示符URL,主⻚等。 > > 8. 服务续约 Renew Eureka 客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。 正常情况下,如果 Eureka Server 在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。 > > 9. 获取注册列表信息 Fetch Registries:官方解释:Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从 而进行远程调用。 > > 10. 服务下线 Cancel: Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务 器的实例注册表中删除。 ### ribbon负载均衡算法 > > 1. RoundRobinRule :轮询策略。 Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider ,其 最多轮询 10 轮。若最终还没有找到,则返回 null 。 > > 2. RandomRule : 随机策略,从所有可用的 provider 中随机选择一个。 > > 3. RetryRule : 重试策略。先按照 RoundRobinRule 策略获取 provider ,若获取失败,则在指定的时限内 重试。默认的时限为 500 毫秒。 ### hystrix > 在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容 限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间 的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。 > 总体来说 Hystrix 就是一个能进行 熔断 和 降级 的库,通过使用它能提高整个系统的整体弹性 ### openfeign > 调用原来代码一样进行各个服务间的调用呢? 聪明的小朋友肯定想到了,那就用 映射 呀,就像域名和IP地址的映射。我们可以将被调用的服务代码映 > 射到消费者端,这样我们就可以 “无缝开发” 啦。OpenFeign 也是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了Ribbon。 ### Zuul 网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴 权、限流、 路由、监控等功能。 ## 设计模式 ### 工厂方法模式 有些对象我们可以在工厂里面生产,需要用时直接从工厂里面拿出来即可,而不用每次需要用的时候都去new对象,工厂方法模式又分为三种 普通工厂模式:就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。 多个工厂方法模式:是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。 静态工厂方法模式:将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。 ### 抽象工厂模式: 上面的工厂方法模式有一个缺点,就是类的创建依赖工厂类。如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,就不需要修改之前的工厂类代码。 ### 建造者模式 工厂模式提供的是创建单个类实例的模式,而建造者模式可以理解为是批量生产。 ### 单例模式 什么叫单例,就是保证一个类在内存中只有一个对象。 #### 懒汉式,线程不安全 > public class Singleton { > private static Singleton instance; > private Singleton (){} > > public static Singleton getInstance() { > if (instance == null) { > instance = new Singleton(); > } > return instance; > } > } 不安全原因: 我们假设有多个线程1,线程2都需要使用这个单例对象。而恰巧,线程1在判断完instance==null后突然交换了cpu的使用权,变为线程2执行,由于instance仍然为null,那么线程2中就会创建这个Singleton的单例对象。之后线程1拿回cpu的使用权,而正好线程1之前暂停的位置就是判断instance是否为null之后,创建对象之前。这样线程1又会创建一个新的Singleton对象。 #### 懒汉式,线程安全 > public class Singleton { > private static Singleton instance; > private Singleton (){} > public static synchronized Singleton getInstance() { > if (instance == null) { > instance = new Singleton(); > } > return instance; > } > } #### 饿汉式 > JVM在类的初始化阶段,会执⾏类的静态⽅法。在执⾏类的初始化期间,JVM会去获取Class对象的锁。这个锁可以同步多个线程对同⼀个类的初始化。 > 饿汉模式只在类加载的时候创建⼀次实例,没有多线程同步的问题。单例没有⽤到也会被创建,⽽且在类加载之后 就被创建,内存就被浪费了。 > public class Singleton { > private static Singleton instance = new Singleton(); > private Singleton (){} > public static Singleton getInstance() { > return instance; > } > } #### 双检锁/双重校验锁 singleton使⽤static修饰的原因:getSingleton为静态⽅法,因为静态⽅法的内部不能直接使⽤⾮静态变量,只有静 态成员才能在没有创建对象时进⾏初始化,所以返回的这个实例必须是静态的。 > public class Singleton { > private volatile static Singleton singleton; > private Singleton (){} > public static Singleton getSingleton() { > if (singleton == null) { > synchronized (Singleton.class) { > if (singleton == null) { > singleton = new Singleton(); > } > } > } > return singleton; > } > } **为什么两次判断 instance == null :** ![](https://i.imgur.com/pwY9AcJ.png) ![](https://i.imgur.com/bpfXqWF.png) #### 登记式/静态内部类 ![](https://i.imgur.com/ThijXER.png) ![](https://i.imgur.com/qSHYt36.png) > public class Singleton { > private static class SingletonHolder { > private static final Singleton INSTANCE = new Singleton(); > } > private Singleton (){} > public static final Singleton getInstance() { > return SingletonHolder.INSTANCE; > } > } #### 枚举 > public enum Singleton { > INSTANCE; > public void whateverMethod() { > } > } ### 适配器模式: 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。 ### 策略模式: 策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。 ### 模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。简单的说就是很多相同的步骤,只是在某一些地方有差别,那么就可以使用这种模式。 ## mybatis ### #{}和${}区别 ![](https://i.imgur.com/nMgy48s.png) ### 最佳实践中,通常⼀个 Xml 映射⽂件,都会写⼀个 Dao 接⼝与之对应,请问,这个 Dao 接⼝的⼯作原理是什么?Dao 接⼝⾥的⽅法,参数不同时,⽅法能重载吗? ![](https://i.imgur.com/jjMhISJ.png) ### Mybatis 是如何进⾏分⻚的?分⻚插件的原理是什么? ![](https://i.imgur.com/7aKmURG.png) ### Mybatis 执⾏批量插⼊,能返回数据库主键列表吗? ![](https://i.imgur.com/QbV56Gx.png) ### Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述⼀下动态 sql 的执⾏原理不? ![](https://i.imgur.com/WFrwKdc.png) ### Mybatis 是如何将 sql 执⾏结果封装为⽬标对象并返回的?都有哪些映射形式? ![](https://i.imgur.com/FBj4qEW.png) ### Mybatis 能执⾏⼀对⼀、⼀对多的关联查询吗?都有哪些实现⽅式,以及它们之间的区别。 ![](https://i.imgur.com/qnzSzV2.png) ![](https://i.imgur.com/lmelUgI.png) ### Mybatis 是否⽀持延迟加载?如果⽀持,它的实现原理是什么? ![](https://i.imgur.com/Tvpm9QJ.png) ### Mybatis 的 Xml 映射⽂件中,不同的 Xml 映射⽂件,id 是否可以重复? ![](https://i.imgur.com/XcbOVLy.png) ### Mybatis 中如何执⾏批处理? 使用BatchExcutor完成批处理 ### Mybatis 都有哪些 Executor 执⾏器?它们之间的区别是什么? ![](https://i.imgur.com/ujNrYHp.png) ### Mybatis 中如何指定使⽤哪⼀种 Executor 执⾏器? ![](https://i.imgur.com/UCGsf2R.png) ### Mybatis 是否可以映射 Enum 枚举类? ![](https://i.imgur.com/KCHRJjk.png) ### Mybatis 映射⽂件中,如果 A 标签通过 include 引⽤了 B 标签的内容,请问,B 标签能否定义在 A 标签的后⾯,还是说必须定义在 A 标签的前⾯? ![](https://i.imgur.com/bycdl0e.png) ### 简述 Mybatis 的 Xml 映射⽂件和 Mybatis 内部数据结构之间的映射关系? ![](https://i.imgur.com/y1hhKCL.png) ### 为什么说 Mybatis 是半⾃动 ORM 映射⼯具?它与全⾃动的区别在哪⾥? ![](https://i.imgur.com/6yYRKdp.png) ### es相关问题 #### node种类 **master node** 处理创建,删除索引等请求 / 决定分片⽚被分配到哪个节点 / 负责索引的创建与删除; 维护并且更新 Cluster State,且只能由 master node 维护,否则会造成集群状态不正常。 **data node** 保存分片数据。在数据扩展上起到了至关重要的作用(由 Master Node 决定如何把分片分发到数据节点上); **Master Eligible Nodes & 选主流程** ⼀个集群,⽀持配置多个 Master Eligible 节点。这些节点可以在必要时(如 Master 节点出现故障,网络故障时)参与选主流程,成为 Master 节点; 节点启动后,默认就是⼀个 Master eligible 节点,设置 node.master: false 禁止; 当集群内第⼀个 Master eligible 节点启动时候,它会将自己选举成 Master 节点; **Coordinating Node** 处理请求的节点,负责路由请求到正确的节点,如创建索引的请求需要路由到 Master 节点; 所有节点默认都是 Coordinating Node; 通过将其他类型(data node/master node/master eligible node)设置成 False,使其成为专门负责的协调的节点; #### 读写流程+搜流程 ##### 读流程 1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点) 2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard) 3)实际的node上的primary shard处理请求,然后将数据同步到replica node 4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端 ##### 写流程 查询,GET某一条数据,写入了某个document,这个document会自动给你分配一个全局唯一的id,doc id,同时也是根据doc id进行hash路由到对应的primary shard上面去。也可以手动指定doc id,比如用订单id,用户id。 1)客户端发送请求到任意一个node,成为coordinate node 2)coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡 3)接收请求的node返回document给coordinate node 4)coordinate node返回document给客户端 ##### 如何判断你把文件写到哪个从节点上了 你可以通过doc id来查询,会根据doc id进行hash,判断出来当时把doc id分配到了哪个shard上面去,从那个shard去查询 ##### es搜索数据过程 1)客户端发送请求到一个coordinate node 2)协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard也可以 3)query phase:每个shard将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排 序、分页等操作,产出最终结果 4)fetch phase:接着由协调节点,根据doc id去各个节点上拉取实际的document数据,最终返回给客户端 ## 华为复盘 ### 主管面 1. 如何理解狼性文化 ![](https://i.imgur.com/JJfLvnZ.png) ![](https://i.imgur.com/HzzVT4m.png) 2. 你了解数通这个部门吗 做数据通信方面的研发、测试.如华为的各系列路由器、交换机,目前华为数通产品线产品系列很全,包括从高端到低端的各个层次,仅次于Cisco和Juniper,位居全球第三. 华为北研所在数通方面主要是网络操作系统和防火墙产品的设计开发 3. 你了解华为吗 华为创立于1987年,是全球领先的ICT(信息与通信)基础设施和智能终端提供商。目前华为约有19.7万员工,业务遍及170多个国家和地区,服务全球30多亿人口。 华为是一家100%由员工持有的民营企业。华为通过工会实行员工持股计划,参与人数为121,269人,参与人仅为公司员工,没有任何政府部门、机构持有华为股权。 4. 如何看待加班 对于任何一家公司来说,通过加班赶工期和赶进度是很正常的事,就我自己而言,目前还未成家,有充裕的时间和精力投入工作中。但是我也会注意提升自身的业务水平,加强时间管理能力,提高工作效率,减少不必要的加班 5. 对哪个算法了解的比较深 回溯,因为我觉得虽然它是一个笨办法,但是通过减枝他也可以做到一定的效率,而且他有比较通用的结构,自己用的也就比较多。 6. 华为的工资分配 操作人员的固定收入是占年总收入的 90%,无股金; 专业技术人员的固定收入占年总收入的 60%,浮动收入占 25%,股金控制在15%; 中层管理人员的固定收入为年总收入的 50%,浮动收入为 30%,股金为 20%; 高层管理人员的固定收入占总收入的 40%,浮动收入为 20%,股金为 40%。 7. 数据存储与机器视觉产品线 是存储,属于IT领域,更广泛;机器视觉就是人工智能的视觉部分,主要是摄像头为核心。 ### 消毒 > public class Code_1 { > public static void main(String[] args){ > Scanner scanner = new Scanner(System.in); > String s = scanner.nextLine(); > String t = scanner.nextLine(); > String[] temp1 =s.split(" "); > String[] temp2 =t.split(" "); > int[] input1 = new int[temp1.length]; > int[] input2 = new int[temp2.length]; > for(int i = 0;i<temp1.length;i++){ > input1[i] = Integer.parseInt(temp1[i]); > } > for(int i = 0;i<temp2.length;i++){ > input2[i] = Integer.parseInt(temp2[i]); > } > Arrays.sort(input1); > Arrays.sort(input2); > int result = getRadius(input1,input2); > System.out.println(result); > } > private static int getRadius(int[] input1, int[] input2) { > List<Integer> result = new ArrayList<>(); > for(int i = 0;i<input1.length;i++){ > int temp =Integer.MAX_VALUE; > for(int j = 0;j<input2.length;j++){ > temp = Math.min(temp,Math.abs(input2[j]-input1[i])); > } > result.add(temp); > } > return Collections.max(result); > } > } ### 力扣1497 给你一个整数数组 arr 和一个整数 k ,其中数组长度是偶数,值为 n 。 现在需要把数组恰好分成 n / 2 对,以使每对数字的和都能够被 k 整除。 按照两数和的值从大到小排序,如果一样的按照第一个数的大小排列。 ![](https://i.imgur.com/9XZAgdL.png) ![](https://i.imgur.com/nb1Oq0V.png) ![](https://i.imgur.com/5HLKnJX.png) ### 算最大利润 https://blog.csdn.net/weixin_41896265/article/details/120391528?utm_source=app&app_version=4.5.0 18. 职业规划 基本到了五年的工作年头,必须要向全栈工程师的方向发展了。有些人在之前的三年里,除了完成工作,在空余时间基本不会研究别的东西,这些人基本已经被时代所淘汰。年纪大一些势必被更年轻的人给顶替;而有些人在三年里,除了完成基本的工作任务之外,阅读了很多号的技术书籍、记录自己的博客、逛Github学习新技术。如果你是做Java开发的,那一定要学习前端的知识体系,掌握前端的主流框架,如Vue、React。如果你是做前端开发的,一定要掌握一门后端编程语言,如Java、PHP、Python等。现在时代发展的都是需求全能型人才。对数据库设计架构和项目搭建具有基本的能力,对项目开发中的各种文档能够组织学习及阐述,能够拥有组织协调3-5人项目小组能力,对项目进度具有初步掌控能力,不断增强与上下级的沟通能力。 在提升技术的基础上增加一些除了技术之外的能力。从个人能力向团队组织能力转变。在沟通能力、协作能力和领导力上发力。 ### linux常用命令 #### 文件&目录操作 ![](https://i.imgur.com/KrV1gQA.png) #### 权限管理 ![](https://i.imgur.com/T7h0osa.png) #### 查找 ![](https://i.imgur.com/SvmcnM7.png) #### 压缩包管理 ![](https://i.imgur.com/CjBkLXs.png) #### 网络管理 ![](https://i.imgur.com/lM5bFOC.png) #### 用户管理 ![](https://i.imgur.com/362zm4Y.png) #### 其他 ![](https://i.imgur.com/szifYbS.png) #### vim ![](https://i.imgur.com/uFOzqIb.png)

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully