面试题有哪些-阿里云开发者社区

开发者社区> t4lankq4euxde> 正文

面试题有哪些

简介: Java面试题
+关注继续查看

Java基础

 Java语言有哪些特点?
    1、简单易学、有丰富的类库。
    2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)
    3、与平台无关性(JVM是Java跨平台使用的根本)
    4、可靠安全
    5、支持多线程

面向对象和面向过程的区别?
    面向过程
    | 是什么是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候调用则可。
    | 特点及应用性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。
    面向对象
    | 是什么是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。
    | 特点及应用面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。
    | ​但是性能上来说,比面向过程要低。

3八种基本数据类型的大小,以及他们的封装类?
    图示

    要点
        1.int是基本数据类型,Integer是int的封装类,是引用类型。
        | int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。
        2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。
        3.虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。
        | 在Java虚拟机中没有任何供boolean值专用的字节码指令,
        | Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,
        | 而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。
        | 这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字
        | 节。
        | ​使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
        | 


标识符的命名规则?
    标识符的含义
    | 是指在程序中,我们自己定义的:类的名字,方法名称以及变量名称等等,都是标识符。
    命名规则
    | (硬性要求)
    | ​标识符可以包含英文字母(大小写),0-9的数字,$以及_ ,
    | ​标识符不能以数字开头,
    | ​标识符不能是关键字。
    命名规范
    | (非硬性要求)
    | ​类名规范:首字符大写,后面每个单词首字母大写(大驼峰式),
    | ​变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式),
    | ​方法名规范:同变量名。

instanceof 关键字的作用?
    含义
    | instanceof 严格来说是Java 中的一个双目运算符,用来测试一个对象是否为一个类的实例。
    用法
    | 其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
    | 注意 
    | ​1,编译器会检查 obj 是否能转换成右边的class类型,
    | ​如果不能转换则直接报错,
    | ​如果不能确定类型,则通过编译,具体看运行时定。
    | 2,null 不是任何类的实例。

final有哪些用法?
    常见的五个用法
    两个重排序规则
    | 在构造函数内对一个final域的写入,
    | ​与随后把这个被构造对象的引用赋值给一个引用变量,
    | ​这两个操作之间不能重排序
    | ​
    | ​初次读一个包含final域的对象的引用,
    | ​与随后初次读这个final域,
    | ​这两个操作之间不能重排序.
    | 

static都有哪些用法?
    -
    | 1,静态变量 和 静态方法
    | 也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享.
    | 2,static也用于静态块
    | 用于初始化操作。
    | 3,此外static也多用于修饰内部类
    | 此时称之为静态内部类.
    | 4,静态导包
    | 即import static.import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名
    | ​

 equals与==的区别?
    ==
        概念
        | ==  比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
        | 
        特点
        | 1、比较的是操作符两端的操作数是否是同一个对象。
        |  2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。 
        | 3、比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true
        | ​如: int a=10 与 long b=10L 与 double c=10.0都是相同的(为true),因为他们都指向地址为10的堆。

    equals
        概念
        | equals用来比较的是两个对象的内容是否相等,
        | 
        特点
        | 1,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,
        | 2,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。

    总结
    | 所有比较是否相等时,都是用equals 。
    | ​并且在对常量相比较时,把常量写在前面,因为使用object的equals object可能为null 则空指针。
    | 在阿里的代码规范中只使用equals ,阿里插件默认会识别,并可以快速修改,推荐安装阿里插件来排查老代码使用“==”,替换成equals

Hashcode的理解?
    hashCode原理     地址值--哈希码--存储区域
        -
        | 于是有人发明了哈希算法来提高集合中查找元素的效率。
        | 1,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,
        | 2,可以将哈希码分组,每组分别对应某个存储区域,
        | 3,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。
        | 
        | hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。

     Hashcode的作用
        问题    equals比较大量数据比较慢
        | java的集合有两类,一类是List,还有一类是Set。
        | 当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。
        | ​但是如果元素太多,用这样的方法就会比较慢。
        | 
        解决     先hashCode再equals
        | 先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
        | ​如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
        | ​这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
        | 

    有没有可能两个不相等的对象有相同的hashcode
        -有可能
        | 在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值.

    hash冲突的三种处理方式
        拉链法
        | 每个哈希表节点都有一个next指针,
        | ​多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.
        | 
        开放地址法
        | 一旦发生了冲突,就去寻找下一个空的散列地址,
        | ​只要散列表足够大,空的散列地址总能找到,并将记录存入
        | 
        再哈希
        | 又叫双哈希法,
        | ​有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数
        | 计算地址,直到无冲突


a=a+b与a+=b有什么区别吗?

重载和重写的区别?
    重写(Override)
        概念
        | 从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。
        | ​子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,对方法体进行修改或重写,这就是重写。
        | 
        实现
            -
            -

        特点
        | 1.发生在父类与子类之间 。
        | ​2.方法名,参数列表,返回类型(除非子类中方法的返回类型是父类中返回类型的子类)必须相同 。
        | ​3.访问修饰符的限制一定要大于被重写方法的访问修饰符。
        | (public>protected>default>private) 
        | ​4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。


    重载(Overload)
        概念
        | 在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。
        | 同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
        | 
        | 
        实现

        特点
        |  1.重载Overload是一个类中多态性的一种表现 
        | 2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序) 
        | 3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
        | 


深浅拷贝的理解?
    | 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
    | 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
    | 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
    | ​
    | 最好是结合克隆以及原型模式联系在一起哈,记得复习的时候,把这几个联系起来的。
    | 

泛型的理解?
    概念
    | 意味着编写的代码可以被不同类型的对象所重用。
    | 
    优点
    | 以集合来举例,
    | 1,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合:
    | 2,我们可以通过规则按照自己的想法控制存储的数据类型。
    | 

String、String StringBuffer 和 StringBuilder之间的关系
    String
        概念
        | String是只读字符串,它并不是基本数据类型,而是一个对象。
        | 从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。
        | ​每次对String的更新操作都会生成新的String对象。
        | 
        特点
        | 每次+操作:隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法拼接+后面的字符。
        | 

    StringBuffer StringBuilder
        -概念
        | StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类
        特点
        | 1,关于使用
        | ​他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。
        | 2,关于同步
        | ​StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
        | ​StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
        | 


Java的四种引用,强弱软虚具体是什么?
    强引用
    | 普通的 new 对象
        使用最多的引用,在OOM时也不会被回收

    软引用
    | 软引用可以用SoftReference来描述 ,指的是那些有用但是不是必须要的对象。
    | ​系统在发生内存溢出前会对这类引用的对象进行回收
    | 
        在程序内存不足时,会被回收
        | 可用场景:
        | ​创建缓存的时候,创建的对象放进缓存中,
        | ​当内存不足时,JVM就会回收早先创建的对象。

    弱引用
    | .弱引用可以用WeakReference来描述,他的强度比软引用更低一点,
    | ​弱引用的对象下一次GC的时候一定会被回收,而不管内存是否足够。
    | 
        弱引用就是只要JVM垃圾回收器发现了它,就会将之回收

    虚引用
    | 虚引用也被称作幻影引用,是最弱的引用关系,可以用PhantomReference来描述,
    | 他必须和ReferenceQueue一起使用,
    | ​同样的当发生GC的时候,虚引用也会被回收。可以用虚引用来管理堆外内存。
    | 
        虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入ReferenceQueue中。
        | 注意
        | 其它引用是被JVM回收后才被传入ReferenceQueue中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。
        | ​还有就是,虚引用创建的时候,必须带有ReferenceQueue
        | 


Java自动装箱与拆箱?
    概念
    | 装箱就是自动将基本数据类型转换为包装器类型(int-->Integer)
    | 调用方法:Integer的valueOf(int) 方法
    | 拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)
    | ​调用方法:Integer的intValue()方法
    用法
    特点
        -128 至 127 会做缓存
            -
            -
            -


    相关面试题
        -
        -
        -


Java创建对象的方式?
    new 创建新的对象

    通过反射机制

    采用克隆机制

    通过反序列化机制


Object类的常用方法?
    图示

    clone  深浅拷贝的理解?
    | 保护方法,protected native Object clone() throws CloneNotSupportedException;
    | ​实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出
    | CloneNotSupportedException 异常
    | ​深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone 方法。
    | 
    finalize
    | 该方法和垃圾收集器有关系,
    | ​判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。
    | 
    equals
    | 该方法使用频率非常高。
    | ​一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。
    | ​子类一般都要重写这个方法。
    | 
    hashcode
    | 该方法用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法,
    | ​这个方法在一些具有哈希功能的 Collection 中用到。
    | ​
    | 一般必须满足obj1.equals(obj2)==true。可以推出obj1.hashCode()==obj2.hashCode(),
    | ​但是hashCode 相等不一定就满足 equals。
    | ​不过为了提高效率,应该尽量使上面两个条件接近等价。
    | ​
    | JDK 1.6、1.7 默认是返回随机数;
    | JDK 1.8 默认是通过和当前线程有关的一个随机数 + 三个确定值,
    | ​        运用 Marsaglia’s xorshiftscheme 随机数算法得到的一个随机数。
    | 
    wait 
    | 配合 synchronized 使用,wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。
    | ​
    | ​wait() 方法一直等待,直到获得锁或者被中断。
    | ​wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
    | ​
    | 调用该方法后当前线程进入睡眠状态,直到以下事件发生。
    | 1. 其他线程调用了该对象的 notify 方法;
    | 2. 其他线程调用了该对象的 notifyAll 方法;
    | 3. 其他线程调用了 interrupt 中断该线程;
    | 4. 时间间隔到了。
    | 此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。
    | 
    notifyAll
    | 配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。

序列化和反序列化?
    Java 序列化中如果有些字段不想进行序列化,怎么办?
        —
        | 对于不想进行序列化的变量,使用 transient 关键字修饰。
        | transient 关键字的作用是:
        | 阻止序列化:阻止实例中那些用此关键字修饰的的变量序列化;
        | 当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
        | transient 只能修饰变量,不能修饰类和方法。
        | 


异常
     Excption与Error包结构
        被检查异常 (CheckedException)
            概念
            | 定义:Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
            | 
            特点  Java编译器会检查它。
                | 要么通过throws进行声明抛出,
                | 要么通过try-catch进行捕获处理,否则不能通过编译。
                | 
                | 例如,CloneNotSupportedException就属于被检查异常。
                | 当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出
                | CloneNotSupportedException异常。
                | 被检查异常通常都是可以恢复的。

            常见被检查异常(编译时)
            | IOException
            | FileNotFoundException
            | SQLException
            | 

        运行时异常 (RuntimeException)
            概念
            | 定义:RuntimeException及其子类都被称为运行时异常。
            特点  Java编译器不会检查它
                | 也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。
                | 例如除数为零,索引下标越界,fail-fast异常-->失败机制

            常见的五种运行时异常
            | ClassCastException(类转换异常)
            | IndexOutOfBoundsException(数组越界)
            | NullPointerException(空指针异常)
            | ArrayStoreException(数据存储异常,操作数组是类型不一致)
            | BufferOverflowException
            | 

        错误 (Error)
            概念
            | 定义 : Error类及其子类。
            特点  编译器也不会对错误进行检查
                -
                | 当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。
                | 程序本身无法修复这些错误的。  
                | ​例如,VirtualMachineError就属于错误。
                | ​出现这种错误会导致程序终止运行。
                | 

            常见的错误
            | OutOfMemoryError、ThreadDeath。


    失败机制
    | 它是Java集合的一种错误检测机制。
        快速失败
        | fail-fast 机制是 Java 集合(Collection)中的一种错误机制。
        | fail-fast机制产生的ConcurrentModificationException异常(java.util包下面的所有的集合类都是快速失败的)
        | 当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
            -
            | 记住是有可能,而不是一定。
            | 例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出
            | ConcurrentModificationException 异常,从而产生fail-fast机制,这个错叫并发修改异常。
            解决策略
            | 建议使用“java.util.concurrent 包下的类”去取代“java.util 包下的类”。
            | ​
            | 可以这么理解:在遍历之前,把 modCount 记下来 expectModCount,后面 expectModCount 去和 modCount 进行比较,如果不相等了,证明已并发了,被修改了,于是抛出ConcurrentModificationException 异常。
            | 

        安全失败
        | Fail-safe,java.util.concurrent包下面的所有的类都是安全失败的,
        | ​在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException异常。
        | ​如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。
        | 

    OOM
        OutOfMemoryError异常
        | 除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。
            Java Heap 溢出
                -
                | java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
                | 
                | 出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse MemoryAnalyzer) 对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(MemoryLeak)还是内存溢出(Memory Overflow)。
                | ​
                | 如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
                | 如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。

            虚拟机和本地方法栈溢出
                -
                | -如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
                | 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
                | 

            运行时常量池溢出
                —
                | 异常信息:java.lang.OutOfMemoryError:PermGenspace
                | 如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。
                | ​该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;
                | ​否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
                | ​由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
                | 

            方法区溢出
                -
                | 方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
                | ​也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
                | 异常信息:java.lang.OutOfMemoryError:PermGenspace
                | 方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
                | 

            SOF(堆栈溢出StackOverflow)
                -
                | 定义当应用程序递归太深而发生堆栈溢出时,抛出该错误。
                | ​
                | 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
                | 栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。
                | 



    说说你平时是怎么处理 Java 异常的
        1,try-catch-finally
        | try 块负责监控可能出现异常的代码
        | catch 块负责捕获可能出现的异常,并进行处理
        | finally 块负责清理各种资源,不管是否出现异常都会执行
        | 其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程。
        | 
            图示

        2,使用自定义异常
        | 在开发过程中会使用到自定义异常,在通常情况下,程序很少会自己抛出异常,因为异常的类名通常也包含了该异常的有用信息,所以在选择抛出异常的时候,应该选择合适的异常类,从而可以明确地描述该异常情况,所以这时候往往都是自定义异常。
        | 自定义异常通常是通过继承 java.lang.Exception 类,
        | ​如果想自定义 Runtime 异常的话,可以继承 java.lang.RuntimeException 类,实现一个无参构造和一个带字符串参数的有参构造方法。
        | 在业务代码里,可以针对性的使用自定义异常。比如说:该用户不具备某某权限、余额不足等。
        | 

    try catch finally 结构
        try catch finally,try里有return,finally还执行么?
        | 执行,并且finally的执行早于try里面的return
        | 
        | 1、不管有木有出现异常,finally块中代码都会执行;
        | 2、当try和catch中有return时,finally仍然会执行;
        | 3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
        | 4,finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
        | ​


IO 相关
    Java 中 IO 流分为几种?
        按照流的流向分,可以分为输入流和输出流;
        按照操作单元划分,可以划分为字节流和字符流;
        按照流的角色划分为节点流和处理流。


反射
    概念
    | -反射机制是在运行时,
    | 对于任意一个类,都能够知道这个类的所有属性和方法;
    | 对于任意个对象,都能够调用它的任意一个方法。
    | 在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
    | 
    | 
    哪里会用到反射?
        -
        | 这就是反射。如hibernate,struts等框架使用反射实现的。

    反射的实现方式
    | 第一步:获取Class对象,有4中方法:
    |  1)Class.forName(“类的路径”);
    |  2)类名.class ;  每个类都有隐含的静态成员 class。
    | ​3)对象名.getClass()
    | ​ 4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
    | 
    实现Java反射的类
    | 1)Class:表示正在运行的Java应用程序中的类和接口
    | 注意:所有获取对象的信息都需要Class类来实现。 
    | ​2)Field:提供有关类和接口的属性信息,以及对它的动态访问权限。
    | ​ 3)Constructor:提供关于类的单个构造方法的信息以及它的访问权限 
    | ​4)Method:提供类或接口中某个方法的信息
    | 
    反射的优点和缺点
        —
        | 优点:
        | ​ 1)能够运行时动态获取类的实例,提高灵活性;
        | ​ 2)与动态编译结合;
        | ​缺点:
        | ​ 1)使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
        | ​  解决方案: 
        | ​    1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度; 
        | ​    2、多次创建一个类的实例时,有缓存会快很多 
        | ​    3、ReflectASM工具类,通过字节码生成的方式加快反射速度 
        | ​2)相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
        | 


集合容器

Array 数组
    概念
    | Array(数组)是基于索引(index)的数据结构。
    | ​数组初始化必须指定初始化的长度, 否则报错。
    | 
    特点
    | 1,它使用索引在数组中搜索和读取数据很快
    | Array获取数据的时间复杂度是O(1)
    | 2,但是要删除数据却是开销很大
    | ​因为这需要重排数组中的所有数据, (因为删除数据以后, 需要把后面所有的数据前移)
    有数组了为什么还要搞个 ArrayList 呢?
        -
        | -可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。
        | ​通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。


Collection
    List
        Vector
            是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

        ArrayList
        LinkedList

    Set
        hashSet
        TreeSet

    ArrayList 和 LinkedList 的区别有哪些?
        ArrayList
        | 优点: 查询效率高
        | ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
        | 缺点: 删除小效率低
        | ​因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低。
        | 
            特点
            | 1,是一种变长的集合类,基于定长数组实现,
            | 当加入数据达到一定程度后,会实行自动扩容,即扩大数组大小。
            | 2,在使用add方法时
            | 如果 add(o),添加到的是数组的尾部,如果要增加的数据量很大,应该使用 ensureCapacity()方法,该方法的作用是预先设置 ArrayList 的大小,这样可以大大提高初始化速度。
            | 如果使用 add(int,o),添加到某个位置,那么可能会挪动大量的数组元素,并且可能会触发扩容机制。
            | 3,线程不安全
            | 多个线程同时操作 ArrayList,会引发不可预知的异常或错误。
            | 4,可以被复制
            | ArrayList 里面的 clone() 复制其实是浅复制。
            | 

        LinkedList
        | 优点: 更新效率高
        | LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。
        | 对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
        | 缺点: 查询效率低
        | ​因为 LinkedList 要移动指针,所以查询操作性能比较低。
        | 
        总结
        | 1,当需要对数据进行对随机访问的时候,选用 ArrayList。
        | 如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。
        | 当然,绝大数业务的场景下,使用 ArrayList 就够了,但需要注意避免 ArrayList 的扩容,以及非顺序的插入。
        | 2,当需要对数据进行多次增加删除修改时,采用 LinkedList。
        | 

    HashSet和TreeSet有什么区别?


Map
    HashMap
        HashMap 中的 key 我们可以使用任何类作为 key 吗?
            -
            | 平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key。
            | 
            | 但是现在我们想使用某个自定义类作为 HashMap 的 key,那就需要注意以下几点:
            | 1,如果类重写了 equals 方法,它也应该重写 hashCode 方法。
            | 2,类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
            | 3,如果一个类没有使用 equals,你不应该在 hashCode 中使用它。
            | 4,咱们自定义 key 类的最佳实践是使之为不可变的
            | 这样,hashCode 值可以被缓存起来,拥有更好的性能。
            | ​不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。

        HashMap 的长度为什么是 2 的 N 次方呢?
            便于进行位运算
            | 为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。
            | 
            | 我们首先可能会想到%取模的操作来实现。
            | 
            | 取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作
            | (也就是说hash % length == hash &(length - 1)的前提是 length 是 2 的 n 次方)。
            | ​并且,采用二进制位操作&,相对于%能够提高运算效率。
            | 


    HashTbale
    comcurrentHashMap
    SynchronizedMap( hashMap,mutex)
    Hashtable 与 HashMap 的区别
        —不同
        | -1. 出生的版本不一样,
        | Hashtable 出生于 Java 发布的第一版本 JDK 1.0,
        | HashMap 出生于 JDK1.2。
        | 2.继承的类不同
        | HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。
        | Hashtable 继承Dictionary。
        | 3.是否线程安全
        | Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰,是线程安全。
        | HashMap 是非线程安全的。
        | 4.key - value 是否能为null
        | Hashtable 的 key 不能为 null,value 也不能为 null:
        |      这个可以从 Hashtable 源码中的 put 方法看到,判断如果 value 为 null 就直接抛出              空指针异常,在 put 方法中计算 key 的 hash 值之前并没有判断 key 为 null 的情况, 那说明,这时候如果 key 为空,照样会抛出空指针异常。
        | HashMap 的 key 和 value 都可以为 null。
        | 在计算 hash 值的时候,有判断,
        | 如果key==null,则其hash=0;至于 value 是否为 null,根本没有判断过。
        | 5.hash效率问题
        | Hashtable 直接使用对象的 hash 值。
        | hash 值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。
        | 然后再使用除留余数法来获得最终的位置。
        | 然而除法运算是非常耗费时间的,效率低。
        | HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,
        | 这样在取模运算时,不需要做除法,只需要做位运算。
        | 位运算比除法的效率要高很多。
        | 
        | 6.初始容量不同
        | Hashtable 的初始长度是 11,之后每次扩充容量变为之前的2n+1(n 为上次的长度)
        | 而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。
        | 
        | 
        相同
        | 1. 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8)。

    HashMap 与 ConcurrentHashMap 的异同

说说List,Set,Map三者的区别?
    -
    | List(对付顺序的好帮手):
    |  List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象;
    | Set(注重独一无二的性质):
    | 不允许重复的集合。不会有多个元素引用相同的对象。
    | Map(用Key来搜索的专家):
    | ​使用键值对存储。Map会维护与Key有关联的值。
    | ​两个Key可以引用相同的对象,但Key不能重复,
    | ​典型的Key是String类型,但也可以是任何对象。
    | 

红黑树

多线程 JUC

多线程基础
    简述线程、程序、进程的基本概念。

    什么是线程安全
        概念
        | 如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,
        | 那么你的代码就是线程安全的
        | 
        | 基本特征:
        | 原子性简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
        | 可见性是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
        | 有序性是保证线程内串行语义,避免指令重排等。
        | ​
        线程安全级别
            不可变
            | 像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个;
            | ​因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用。
            | 
            绝对线程安全
            | 不管运行时环境如何,调用者都不需要额外的同步措施。
            | ​要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有:
            | ​比方说CopyOnWriteArrayList,CopyOnWriteArraySet
            | 
            相对线程安全
            | 相对线程安全也就是我们通常意义上所说的线程安全,
            | ​像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此。
            | ​
            | ​如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
            | 
            线程非安全
            | 这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类


    多线程有什么用?
        发挥多核CPU优势
        | 现在的笔记本、台式机乃至商用的应用服务器至少也都是双核,4核、8核甚至16核的
        | ​
        | ​如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。
        | 
        | ​单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过
        | 线程之间切换得比较快,看着像多个线程"同时"运行罢了。
        | ​多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
        | 
        防止阻塞
        | 从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,
        | 反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。
        | 
        | 但是单核CPU我们还是要应用多线程,就是为了防止阻塞。
        | ​试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,
        | ​比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。
        | ​多线程可以防止这个问题,多条线程同时运行,
        便于建模
        | 这是另外一个没有这么明显的优点了。
        | 任务可分解
        | ​假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、
        | ​分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

    什么是多线程中的上下文切换?
        -
        | 在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。
        | 从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。
        | 
        | 在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。
        | PCB还经常被称作“切换桢”(switchframe)。
        | ​“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
        | 上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。

    什么是Daemon线程?它有什么意义?
        -
        | 所谓后台(daemon)线程,也叫守护线程,
        | ​是指在程序运行的时候在后台提供一种通用服务的线程,
        | ​并且这个线程并不属于程序中不可或缺的部分。
        | ​
        | 因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。
        | ​
        | 必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。
        | ​
        | ​注意:后台进程在不执行finally子句的情况下就会终止其run()方法。
        | 比如:JVM的垃圾回收线程就是Daemon线程,Finalizer也是守护线程。
        | 

    线程的生命周期

    死锁相关
        产生死锁的四个必要条件
            —
            | 1. 互斥条件:一个资源每次只能被一个线程使用
            | 2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
            | 3. 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺
            | 4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
            | 

        如何避免产生死锁
            | 1. 设置获取锁的超时时间
            | ​至少能保证最差情况下,可以退出程序,不至于一直等待导致死锁;
            | 2. 设置按照同一顺序访问资源
            | ​类似于串行执行;
            | 3. 避免事务中的用户交叉;
            | 4. 保持事务简短并在一个批处理中;
            | 5. 使用低隔离级别;
            | 6. 使用绑定链接。



如何使用线程
    说说Java中实现多线程有几种方法?及优缺点
        创建线程的常用四种方式
            | 
            | 要点
            | 1,通过继承Thread类或者实现Runnable接口、Callable接口都可以实现多线程;
            | 2,不过实现Runnable接口与实现Callable接口的方式基本相同;
            |          只是Callable接口里定义的方法返回值,可以声明抛出异常而已。
            |          因此将实现Runnable接口和实现Callable接口归为一种方式。

        采用实现Runnable、Callable接口的方式创建线程的优缺点?
            | 优点:
            | 1,线程类只是实现了Runnable或者Callable接口,还可以继承其他类。
            | 2,这种方式下多个线程可以共享一个target对象
            | 所以非常适合多个相同线程来处理同一份资源的情况,
            | 从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
            | 缺点:
            | 1,编程稍微复杂些
            | 如果需要访问当前线程,则必须使用Thread.currentThread()方法

        什么是Callable和Future?
            -
            | Callable接口类似于Runnable,从名字就可以看出来了,
            | ​但是Runnable不会返回结果,并且无法抛出返回结果的异常,
            | ​而Callable功能更强大一些,被线程执行后可以返回值,这个返回值可以被Future拿到,
            | ​也就是说,Future可以拿到异步执行任务的返回值。可以认为是带有回调的Runnable。
            | ​
            | Future接口表示异步任务,是还没有完成的任务给出的未来结果。
            | ​
            | ​所以说Callable用于产生结果,Future用于获取结果。
            | 

        采用继承Thread类的方式创建线程的优缺点?
            | 优点:
            | 1,编程简单
            | 如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获取当前线程
            | 缺点:
            | 1,因为线程类已经继承了Thread类,Java语言是单继承的,所以就不能再继承其他父类了。
            | 


    如何停止一个正在运行的线程?
        使用退出标志
        | 使线程正常退出,也就是当run方法完成后线程终止。
        使用stop方法强行终止
        | 但是不推荐,因为stop和suspend及resume一样都是过期作
        | 废的方法。
        | 
        使用Interruptf方法中断线程
            代码示例



    线程之间是如何进行通信的
        共享内存
            JMM内存模型
            | 在共享内存的并发模型里,线程之间共享程序的公共状态,
            | 线程之间通过写-读内存中的公共状态来隐式进行通信。
            | 
            | 典型的共享内存通信方式,就是通过共享对象进行通信。
            | 例如上图线程 A 与线程 B 之间如果要通信的话,那么就必须经历下面两个步骤:
            | 1. 线程 A 把本地内存 A 更新过得共享变量刷新到主内存中去。
            | 2. 线程 B 到主内存中去读取线程 A 之前更新过的共享变量。
            | 

        消息传递
            线程阻塞或者线程唤醒,或者用用阻塞队列。
            | 在消息传递的并发模型里,线程之间没有公共状态,
            | 线程之间必须通过明确的发送消息来显式进行通信。
            | 
            | 在 Java 中典型的消息传递方式,就是wait()和notify(),或者BlockingQueue。
            | 


    一些常用方法
        Thread 类中的start() 和 run() 方法有什么区别?
            -
            | start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,
            | 这和直接调用run()方法的效果不一样。
            | ​当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,
            | ​start()方法才会启动新线程。
            | 

        notify()和notifyAll()有什么区别?
            notify可能会导致死锁,而notifyAll则不会
            | 任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码
            | 使用notifyall,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,
            | ​而notify只能唤醒一个。
            | ​
            | wait() 应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。
            | ​
            | notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。
            | ​正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事
            | 项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中.

        sleep()和wait() 有什么区别?
            -
            | 1,所属类不同:
            | sleep()方法,是属于Thread类中的。
            | wait()方法,则是属于Object类中的。
            | 2,会不会释放锁
            | 在调用sleep()方法的过程中,线程不会释放对象锁:
            | sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,
            | 但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。
            | 当调用wait()方法的时候,线程会放弃对象锁:
            | ​进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
            | 

        为什么wait, notify 和 notifyAll这些方法不在thread类里面?
            -
            | 1,明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。
            | ​2,如果线程需要等待某些锁,那么调用对象中的wait()方法就有意义了。
            | ​3,如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。
            | ​
            | ​简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
            | 

        为什么wait和notify方法要在同步块中调用?
            -
            | 1. 只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。
            | 2. 如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。
            | 3. 还有一个原因是为了避免wait和notify之间产生竞态条件。
            | ​
            | wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。
            | ​
            | 在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。
            | ​
            | 调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。
            | ​调用notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。
            | ​这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。
            | 

        Thread类中的yield方法有什么作用?
            -礼让CPU执行权
            | Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。
            | ​它是一个静态方法,
            | ​而且只保证当前线程放弃CPU占用,
            | ​而不能保证使其它线程一定能占用CPU,
            | ​执行yield()的线程有可能在进入到暂停状态后马上又被执行。
            | 

        Java中interrupted 和 isInterruptedd方法的区别?
            -
            | 1,interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。
            | Java多线程的中断机制
            | ​是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。
            | ​
            | 当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。
            | ​而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。
            | 
            | ​简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。
            | ​无论如何,一个线程的中断状态有可能被其它线程调用中断来改变。
            | 



线程同步 安全问题
    volatile 是什么?可以保证有序性吗?
        volatile 是一个修饰符
        | 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,
        | 那么就具备了两层语义:
        | 1,保证了不同线程对这个变量进行操作时的可见性
        | 即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,
        | volatile关键字会强制将修改的值立即写入主存。
        | 2,禁止进行指令重排序。
        | 
        | 使用volatile 一般用于状态标记量和单例模式的双检锁。
        | 
        volatile 不是原子性操作
        volatile 保证部分有序性?
        | 当程序执行到volatile变量的读操作或者写操作时,
        | ​在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;
        | ​在其后面的操作肯定还没有进行;
        | 
            代码示例


    valotile 和 Synchronized 的区别
        -
        | 1,对变量的操作不同
        | volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
        | synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
        | 2,使用级别不同
        | volatile仅能使用在变量级别;
        | synchronized则可以使用在变量、方法、和类级别的
        | 3,可见性与原子性方面
        | volatile 仅能实现变量的修改可见性,并不能保证原子性;
        | synchronized则可以保证变量的修改可见性和原子性。
        | 4,线程是否阻塞
        | volatile不会造成线程的阻塞;
        | synchronized可能会造成线程的阻塞。
        | 5,是否被编译器优化
        | volatile标记的变量不会被编译器优化;
        | ​synchronized标记的变量可以被编译器优化。
        | 
        | 

    说一说自己对于 synchronized 关键字的了解
        概念 
        | 1,synchronized 解决的是多个线程之间访问资源的同步性,
        | synchronized可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
        | 
        | 2,另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下
        | 因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,
        | Java 的线程是映射到操作系统的原生线程之上的。
        | 如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,
        | 而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized 效率低的原因。
        | 
        | 3,庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化
        | ​所以现在的 synchronized 锁效率也优化得很不错了。
        | ​JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
        使用
        | 修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
        | 修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。
        | 所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象;
        | 因为访问静态 synchronized 方法占用的锁是当前类的锁,
        | 而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
        | 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
        | 
        | ​总结:
        | ​ 1,synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
        | 2,synchronized关键字加到实例方法上是给对象实例上锁。
        | ​3,尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
        | 

    Java中synchronized 和 ReentrantLock 的异同?
        相似点
        | 它们都是加锁方式同步,而且都是阻塞式的同步,
        | ​也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.
        | 
        不同点
        | 1,实现方式不同
        | Synchronized:是java语言的关键字,是原生语法层面的互斥,需要jvm实现。
        | ReentrantLock:是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
        | 2,Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。
        | 在执行monitorenter指令时,首先要尝试获取对象锁。
        | 如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,
        | 相应在执行monitorexit指令时会将锁计算器就减1,
        | 当计算器为0时,锁就被释放了。
        | 如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
        | 3,ReentrantLock类提供了一些高级功能
        | 1)等待可中断
        | 持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于
        | Synchronized来说可以避免出现死锁的情况。
        | 2)公平锁
        | 多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,
        | Synchronized锁非公平锁,
        | ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,
        | 但公平锁表现的性能不是很好。
        | 3)锁绑定多个条件
        | ​一个ReentrantLock对象可以同时绑定对个对象。
        | 

    SynchronizedMap和ConcurrentHashMap有什么区别?
        -
        | 1,同步的范围不一样,后者粒度更加细
        | ​SynchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步。
        | 而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。
        | ​
        | 所以,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。
        | ​同时,同步操作精确控制到桶,这样,即使在遍历map时,如果其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。

    有三个线程T1,T2,T3,如何保证顺序执行?
        -
        | 在多线程中有多种方法让线程按特定顺序执行,
        | 你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。
        | ​为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
        | 实际上先启动三个线程中哪一个都行,因为在每个线程的run方法中用join方法限定了三个线程的执行顺序。
        | 
        代码示例图


    AQS
        什么是AQS
        | AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。
        | ​
        | 如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,
        | ReentrantLock、CountDownLatch、Semaphore等等都用到了它。
        | ​
        | ​AQS实际上以双向队列的形式连接所有的Entry,
        | ​比方说ReentrantLock,所有等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。
        | ​
        | AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。
        | 
        了解Semaphore吗?
        | Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。
        | ​Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,
        | ​
        | 等到某个线程执行完毕这段代码块,下一个线程再进入。
        | ​由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。
        | 

    阻塞队列
        是什么
        | 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
        | 这两个附加的操作是:
        | ​在队列为空时,获取元素的线程会等待队列变为非空。
        | ​当队列满时,存储元素的线程会等待队列可用。
        | 
        实现原理
        | Java 5之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实现生产者消费者模式,主要的技术就是用好,wait ,notify,notifyAll,sychronized这些关键字。
        | 
        | 而在java 5之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。
        | 
        | BlockingQueue接口是Queue的子接口,它的主要用途并不是作为容器,而是作为线程同步的的工具,因此他具有一个很明显的特性,
        | ​当生产者线程试图向BlockingQueue放入元素时,如果队列已满,则线程被阻塞,
        | ​当消费者线程试图从中取出一个元素时,如果队列为空,则该线程会被阻塞,
        | ​
        | 正是因为它所具有这个特性,所以在程序中多个线程交替向BlockingQueue中放入元素,取出元素,它可以很好的控制线程之间的通信。
        | 
        分类
        | ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
        | LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
        | PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
        | DelayQueue:一个使用优先级队列实现的无界阻塞队列。
        | SynchronousQueue:一个不存储元素的阻塞队列。
        | LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
        | LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
        | 
        生产者消费者模型
        | 阻塞队列常用于生产者和消费者的场景,
        | 生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。
        | 阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
        | 
        | 阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,
        | ​读取数据的线程不断将数据放入队列,
        | ​然后解析线程不断从队列取数据解析。
        | ​

    乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
        概念
            -
            | 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
            | 传统的关系型数据库里边就用到了很多这种锁机制,
            | 比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
            | 再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。
            | 
            | 悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
            | 而乐观锁机制在一定程度上解决了这个问题。
            | 
            | 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁, 但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
            | 乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。
            | 在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

        乐观锁的实现
            —
            | 1,使用数据版本标识来确定读到的数据与提交时的数据是否一致。
            | 提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。
            | 2、java中的Compare and Swap即  CAS 
            | 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
            | 
            | 何谓数据版本?
            | 即为数据增加一个版本标识,在基于数据库表的版本解决方案中,
            | 一般是通过为数据库表增加一个“version”字段来实现。
            | ​读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
            | ​此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,
            | ​如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
            | ​
            CAS 
                CAS 原理    比较并交换
                | CAS叫做CompareAndSwap,比较并交换,
                | 主要是通过处理器的指令来保证操作的原子性,它包含三个操作数:
                | 
                | 1. 变量内存地址,V表示
                | 2. 旧的预期值,A表示
                | 3. 准备设置的新值,B表示
                | 当执行CAS指令时,只有当V等于A时,才会用B去更新V的值,否则就不会执行更新操作
                | 
                CAS 缺点
                    ABA问题
                    | ABA的问题指的是在CAS更新的过程中,当读取到的值是A,然后准备赋值的时候仍然是A,但是实际上有可能A的值被改成了B,然后又被改回了A,这个CAS更新的漏洞就叫做ABA。
                    | 只是ABA的问题大部分场景下都不影响并发的最终效果。
                    | 解决
                    | Java中有AtomicStampedReference来解决这个问题,他加入了预期标志和更新后标志两个字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。
                    | 
                    循环时间开销大
                    | 自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。
                    只能保证一个共享变量的原子性操作
                    | 只对一个共享变量操作可以保证原子性,但是多个则不行,
                    | ​多个可以通过AtomicReference来处理或者使用锁synchronized实现




    ThreadLocal原理
        图示
        -
        | ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离,
        | ​相比于synchronized的做法是用空间来换时间。
        | ​
        | ​ThreadLocal有一个静态内部类ThreadLocalMap,
        | ​ThreadLocalMap又包含了一个Entry数组,
        | ​Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,
        | ​Entry具备了保存key value键值对的能力。
        | ​
        | 弱引用的目的是为了防止内存泄露,如果是强引用那么ThreadLocal对象除非线程结束否则始终无法被回收,弱引用则会在下一次GC的时候被回收。
        | ​
        | 但是这样还是会存在内存泄露的问题,
        | ​假如key和ThreadLocal对象被回收之后,entry中就存在key为null,
        | ​但是value有值的entry对象,但是永远没办法被访问到,同样除非线程结束运行。
        | 但是只要ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出现这个问题的。
        | 
        | 


线程池相关
    常用的线程池有哪五个
        —
        | newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
        | newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
        | newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
        | newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
        | newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
        | 
        | 

    简述一下你对线程池的理解
        线程池如何使用
        线程池的好处
        | 1,降低资源消耗。
        | 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
        | 2,提高响应速度。
        | 当任务到达时,任务可以不需要等到线程创建就能立即执行。
        | 3,提高线程的可管理性。
        | ​线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
        | 
        线程池的启动策略

    线程池核心线程数怎么设置呢?
        分为CPU密集型和IO密集型
        | CPU密集型
        | 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,
        | 比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。
        | 一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
        | IO密集型
        | 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。
        | 因此在 I/O 密集型任务的应用中,我们可以多配置一些线程;
        | 具体的计算方法是:核心线程数 = CPU核心数量 *  2。
        | 

    Java线程池中队列常用类型有哪些?
        -
        | ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
        | LinkedBlockingQueue一个基于链表结构的阻塞队列,此队列按FIFO (先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。
        | SynchronousQueue一个不存储元素的阻塞队列。
        | PriorityBlockingQueue一个具有优先级的无限阻塞队列。基于最小二叉堆实现
        | DelayQueue只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
        | DelayQueue是一个没有大小限制的队列,
        | ​因此往队列中插入数据的操作(生产者)永远不会被阻塞,
        | ​而只有获取数据的操作(消费者)才会被阻塞。
        | 
        | 
        | 

    原理及核心参数
        核心参数
        | 1. 最大线程数maximumPoolSize
        | 2. 核心线程数corePoolSize
        | 3. 活跃时间keepAliveTime
        | 4. 阻塞队列workQueue
        | 5. 拒绝策略RejectedExecutionHandler
        | 
        原理 执行流程
        | 1. 当我们提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务
        | 2. 当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队
        | 3. 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被自动销毁
        | 4. 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理
        | 

    线程池的拒绝策略有哪四种
        —
        | 1. AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
        | 2. CallerRunsPolicy:只用调用者所在的线程来处理任务
        | 3. DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
        | 4. DiscardPolicy:直接丢弃任务,也不抛出异常
        | 

    Java线程池中submit() 和 execute()方法有什么区别?
        -
        | 1,两个方法都可以向线程池提交任务
        | 2,返回类型不同
        | ​execute()方法的返回类型是void,它定义在Executor接口中,
        | ​
        | 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中;
        | ​它扩展了Executor接口,
        | ​其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
        | 


JMM内存模型?
    图示、
        | 说了半天,到底工作内存和主内存是什么?
        | 主内存可以认为就是物理内存,Java内存模型中实际就是虚拟机内存的一部分。
        | 工作内存就是CPU缓存,他有可能是寄存器也有可能是L1\L2\L3缓存,都是有可能的。

    为什么需要JMM 
        | 随着CPU和内存的发展速度差异的问题,导致CPU的速度远快于内存,
        | 所以现在的CPU加入了高速缓存,
        | 高速缓存一般可以分为L1、L2、L3三级缓存。
        | 
        | 基于上面的例子我们知道了这导致了缓存一致性的问题,所以加入了缓存一致性协议,
        | 同时导致了内存可见性的问题,
        | 而编译器和CPU的重排序导致了原子性和有序性的问题,
        | 
        | JMM内存模型正是对多线程操作下的一系列规范约束,
        | 因为不可能让程序员的代码去兼容所有的CPU,通过JMM我们才屏蔽了不同硬件和操作系统内存的访问差异,这样保证了Java程序在不同的平台下达到一致的内存访问效果,同时也是保证在高效并发的时候程序能够正确执行。

    JMM是什么?
        -
        | JMM是定义程序中变量的访问规则
        | 1,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作.
        | 2,JMM需要提供原子性,可见性,有序性保证。volatile
        | 由于指令重排序,读写的顺序会被打乱,
        图示

    原子性
    | Java内存模型通过read、load、assign、use、store、write来保证原子性操作,
    | ​此外还有lock和unlock,
    | ​直接对应着synchronized关键字的monitorenter和monitorexit字节码指令。
    | 
    可见性
    | 可见性的问题在上面的回答已经说过,
    | ​Java保证可见性可以认为通过volatile、synchronized、final来实现。
    | 
    有序性
    | 由于处理器和编译器的重排序导致的有序性问题,
    | ​Java通过volatile、synchronized来保证。
    | 
    happen-before规则
    | 虽然指令重排提高了并发的性能,但是Java虚拟机会对指令重排做出一些规则限制,
    | ​并不能让所有的指令都随意的改变执行位置;主要有以下几点:
    | ​
    | 1. 单线程每个操作,happen-before于该线程中任意后续操作
    | 2. volatile写happen-before与后续对这个变量的读
    | 3. synchronized解锁happen-before后续对这个锁的加锁
    | 4. final变量的写happen-before于final域对象的读,happen-before后续对final变量的读
    | 5. 传递性规则,A先于B,B先于C,那么A一定先于C发生

JVM 虚拟机

知识点汇总图示
    -
    | 其中内存模型,类加载机制,GC是重点方面.
    | ​性能调优部分更偏向应用,重点突出实践能力.
    | ​编译器优化和执行模式部分偏向于理论基础,重点掌握知识点.
    | 
    学习目标
        -
        | 1,需了解内存模型各部分作用,保存哪些数据.
        | 2,类加载双亲委派加载机制,常用加载器分别加载哪种类型的类.
        | 3,GC分代回收的思想和依据以及不同垃圾回收算法的回收思路和适合场景.
        | 4,性能调优常有JVM优化参数作用,参数调优的依据,常用的JVM分析工具能分析哪些问题以及使用方法.
        | 5,执行模式解释/编译/混合模式的优缺点,Java7提供的分层编译技术,JIT即时编译技术,OSR栈上替换,C1/C2编译器针对的场景,C2针对的是server模式,优化更激进.新技术方面Java10的graal编译器
        | 6,编译器优化javac的编译过程,ast抽象语法树,编译器优化和运行器优化.
        | 阿里内部资料
        | 


基础概念
    什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
        | Java虚拟机是一个可以执行Java字节码的虚拟机进程。
        | Java源文件被编译成能被Java虚拟机执行的字节码文件。
        | 
        |  Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。
        | ​Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

    说说堆和栈的区别?
        -
        | 栈是运行时单位,
        | 代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;
        | 堆是存储单位,
        | 代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),
        | 所在区域不连续,会有碎片。
        | 
        | 1、功能不同
        | 栈内存用来存储局部变量和方法调用,
        | 而堆内存用来存储Java中的对象。
        | 无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
        | 2、共享性不同
        | 栈内存是线程私有的。
        | 堆内存是所有线程共有的。
        | 3、异常错误不同
        | 栈空间不足:java.lang.StackOverFlowError。
        | 堆空间不足:java.lang.OutOfMemoryError。
        | 4、空间大小不同
        | 栈的空间大小远远小于堆的。

    说说Java内存/对象 分配规则?
        Java对象结构?
            --
            | Java对象由三个部分组成:对象头、实例数据、对齐填充。
            | 
            | 对象头由两部分组成,
            | 第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、
            | 线程持有的锁、偏向线程ID(一般占32/64 bit)。
            | 第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。
            | 如果是数组对象,则对象头中还有一部分用来记录数组长度。
            | 实例数据
            | 用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
            | 对齐填充
            | ​JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
            | 

        说说Java对象创建过程?
            | 1.JVM遇到一条新建对象的指令时
            | 首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用
            | 然后加载这个类
            | 2.为对象分配内存。
            | 一种办法“指针碰撞”、
            | 一种办法“空闲列表”,
            | 最终常用的办法“本地线程缓冲分配(TLAB)”
            | 3.将除对象头外的对象内存空间初始化为0
            | 4.对对象头进行必要设置
                什么是TLAB?
                    | 这就是TLAB(Thread Local Allocation Buffer,本地线程分配缓存)。
                    | 可以把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存。
                    | 虚拟机通过-XX:UseTLAB设定它的。
                    | 



        分配规则
            | 对象优先分配在Eden区,
            | 如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
            | 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。
            | 这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
            | 长期存活的对象进入老年代。
            | 虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
            | 动态判断对象的年龄。
            | 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
            | 空间分配担保。
            | ​每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,
            | ​如果这个值大于老年区的剩余值大小则进行一次Full GC,
            | ​如果小于检查HandlePromotionFailure设置,
            | ​        如果true则只进行Monitor GC,
            | ​        如果false则进行Full GC。

        分配方式
            指针碰撞
                | 一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。
                | ​当类加载检查通过后,Java虚拟机开始为新生对象分配内存。
                | ​
                | ​如果Java堆中内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是指针碰撞。
                | 

            空闲列表
                | 如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,
                | ​虚拟机必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表。




 JVM 内存模型/ 组成部分
    组成部分
        图示

        运行时数据区组件
            概述
                图示

            程序计数器

            虚拟机栈
                图示


            本地方法栈

            堆

            方法区

            常见内存溢出异常
            | 除 PCR 都有异常

        类加载子系统
        执行引擎子系统
        本地接口组件

    运行原理


类加载
    类的生命周期?
        图示
        | 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
        | 连接,连接又包含三块内容:验证、准备、初始化。 
        | 初始化,为类的静态变量赋予正确的初始值
        | 使用,new出对象程序中使用
        | 卸载,执行垃圾回收
        | 
        | 

    类加载过程
        图示

        原理
            | 当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
            | 
            | 加载
            | 通过类的完全限定名,查找此类字节码文件,由类加载器完成加载
            | 利用字节码文件创建Class对象.
            | 加载完成后,Class对象还不完整,所以此时的类还不可用。
            | 
            | 链接阶段 :验证 -- 准备 -- 解析
            | 验证
            | 确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全.
            | 准备
            | 进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null).不包含final修饰的静态变量,因为final变量在编译时分配.
            | 解析
            | 将常量池中的符号引用替换为直接引用的过程.直接引用为直接指向目标的指针或者相对偏移量等.
            | 初始化
            | 主要完成静态块执行以及静态变量的赋值.
            | 先初始化父类,再初始化当前类.
            | 只有对类主动使用时才会初始化.
            | 
            | 初始化触发条件
            | 1,创建类的实例时,
            | 2,访问类的静态方法或静态变量的时候,
            | 3,使用Class.forName反射类的时候,
            | 4,或者某个子类初始化的时候.
            | ​
            | 在虚拟机的生命周期中
            | Java自带的加载器加载的类是不会被卸载的;
            | ​只有用户自定义的加载器加载的类才可以被卸。


    类加载器
         什么是类加载器?
            | 类加载器是一个用来加载类文件的类。
            | Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类
            | ​
            | Java 源代码通过 javac 编译器编译成类.class文件,交给JVM。
            | ​类加载器负责加载文件系统、网络或其他来源的类文件。

        类加载器分类
            根加载器
            | BootStrapt
            扩展类加载器
            | Extension
            系统加载器
            | System
            用户自定义加载器
            | java.lang.ClassLoader的子类


    双亲委派模型
        图示

        概念
            -
            | 加载器加载类时
            | 先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器.父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载.
            | 
            | 优点
            | 1. 避免类的重复加载
            | ​2. 避免Java的核心API被篡改


     tomcat 类加载机制
        图示

        说明
            | 当 tomcat启动时,会创建几种类加载器:
            | Bootstrap 引导类加载器
            | 加载 JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下)
            | System 系统类加载器
            | ​加载 tomcat 启动的类,
            | ​比如bootstrap.jar,通常在 catalina.bat 或者catalina.sh中指定。
            | ​位于CATALINA_HOME/bin下。
            | 



GC
    判断对象是否可以被回收
        Java对象的访问

        判断对象是否可用
            -
            | 引用计数:
            | 每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计
            | 数为0时可以回收。
            | 此方法简单,无法解决对象相互循环引用的问题。
            | 
            | 可达性分析(Reachability Analysis)
            | ​从GC Roots开始向下搜索,搜索所走过的路径称为引用链。
            | ​当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

        Java对象的四种引用 
        | 强软弱虚


    垃圾回收算法
        标记清除算法

        复制算法

        标记整理算法

        分代回收算法
            为什么要分代
                | 分代回收基于两个事实:
                | 1,大部分对象很快就不使用了。
                | 2,还有一部分不会立即无用,但也不会持续很长时间.。
                | ​
                | 把Java堆分为新生代和老年代,
                | ​这样就可以根据各个年代的特点采用最适当的收集算法。

            具体的分法
                图示
                    -
                    | 年轻代-> 标记-复制
                    | ​老年代-> 标记-清除

                年轻代
                | 复制算法  对象的存活率不高
                老年代
                | 标记整理 标记清除  对象的死亡率不高
                永久代/ 持久代
                    虚拟机为什么使用元空间替换了永久代?
                        方法区规范的不同实现
                        为什么使用元空间替换了永久代?
                        | 表面上看是为了避免OOM异常。
                        | ​因为通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,
                        | ​但是不是总能知道应该设置为多大合适, 如果使用默认值很容易遇到OOM错误。
                        | 
                        | ​当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制啦。
                            永久代的方法区,和堆使用的物理内存是连续的。
                            「永久代」是通过以下这两个参数配置大小的
                            | -XX:PremSize:设置永久代的初始大小
                            | -XX:MaxPermSize: 设置永久代的最大值,默认是64M
                            | 
                            | 对于「永久代」,如果动态生成很多class的话,就很可能出现「java.lang.OutOfMemoryError:PermGen space错误」,
                            | 因为永久代空间配置有限嘛。最典型的场景是,在web开发比较多jsp页
                            | 面的时候。
                            | 
                            | JDK8之后,方法区存在于元空间(Metaspace)。
                            | ​物理内存不再与堆连续,而是直接存在于本地内存中,
                            | ​理论上机器「内存有多大,元空间就有多大」。
                            | 
                            --
                            可以通过以下的参数来设置元空间的大小:
                            | -XX:MetaspaceSize,
                            | 初始空间大小,达到该值就会触发垃圾收集进行类型卸载,
                            | 同时GC会对该值进行调整:
                            | 如果释放了大量的空间,就适当降低该值;
                            | 如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
                            | -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
                            | -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集.
                            | -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
                            | 
                            | 






    垃圾收集器
        串行收集
        并行收集
        G1 垃圾收集器
            —
            | 1.9后默认的垃圾回收算法,
            | ​特点:
            | 1,保持高回收率的同时减少停顿.
            | ​2,采用每次只清理一部分,而不是清理全部的增量式清理;以保证停顿时间不会过长
            | 3,其取消了年轻代与老年代的物理划分,但仍属于分代收集器,
            | ​算法将堆分为若干个逻辑区域(region):
            | ​一部分用作年轻代,一部分用作老年代,还有用来存储巨型对象的分区.
            | ​
            | 4,同CMS相同,会遍历所有对象,标记引用情况,清除对象后会对区域进行复制移动,以整合碎片空间.
            | ​
            | 年轻代回收: 并行复制采用复制算法,并行收集,会StopTheWorld.
            | 老年代回收: 会对年轻代一并回收
            | 初始标记完成堆root对象的标记,会StopTheWorld.
            | ​
            | ​并发标记 GC线程和应用线程并发执行. 
            | ​最终标记 完成三色标记周期,会StopTheWorld. 
            | ​复制/清除 会优先对可回收空间加大的区域进行回收

        ZGC 垃圾收集器
            -

        说一下 JVM 有哪些垃圾回收器?
            如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
            | 
            | ​上图展示了7种作用于不同分代的收集器,
            | ​回收新生代的收集器包括Serial、PraNew、ParallelScavenge,
            | ​回收老年代的收集器包括Serial Old、Parallel Old、CMS,
            | ​回收整个Java堆的G1收集器。
            | ​不同收集器之间的连线表示它们可以搭配使用。
            | 
            不同收集器的详细介绍
                | Serial收集器(复制算法): 
                | 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
                | ParNew收集器 (复制算法): 
                | 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
                | Parallel Scavenge收集器 (复制算法): 
                | 新生代并行收集器,追求高吞吐量,高效利用 CPU。
                | 吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),
                | 高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
                | Serial Old收集器 (标记-整理算法): 
                | 老年代单线程收集器,Serial收集器的老年代版本;
                | Parallel Old收集器 (标记-整理算法):
                | 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
                | CMS(Concurrent Mark Sweep)收集器(标记-清除算法):
                | 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
                | G1(Garbage First)收集器 (标记-整理算法): 
                | Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,
                | G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。
                | 此外,G1收集器不同于之前的收集器的一个重要特点是:
                | G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
                | ZGC(Z Garbage Collector)
                | 是一款由Oracle公司研发的,以低延迟为首要目标的一款垃圾收集器。
                | 它是基于动态Region内存布局,(暂时)不设年龄分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的收集器。
                | 在JDK 11新加入,还在实验阶段,
                | 主要特点是:回收TB级内存(最大4T),停顿时间不超过10ms。
                | ​优点:低停顿,高吞吐量,ZGC收集过程中额外耗费的内存小。
                | ​缺点:浮动垃圾

            如何选择垃圾收集器?
                -
                | 从上面这些出发点来看,
                | ​我们平常的 Web 服务器,都是对响应性要求非常高的。
                | ​选择性其实就集中在CMS、G1、ZGC上。
                | ​而对于某些定时任务,使用并行收集器,是一个比较好的选择。
                | 



    垃圾回收时机
        Minor GC与Full GC分别在什么时候发生?
            | 新生代内存不够用时候发生MGC也叫YGC
            | ​JVM内存不够的时候发生FGC

         什么时候会触发FullGC
            | 除直接调用System.gc外,触发Full GC执行的情况有如下四种。
            | 1. 旧生代空间不足
            | 旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,
            | 当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,
            | 调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
            | 2. Permanet Generation空间满 
            | PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。
            | 如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息: java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
            | 3. CMS GC时出现promotion failed和concurrent mode failure
            | 对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。
            |  promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;
            | concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
            | 应对措施为:增大survivorspace、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。
            | 对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。
            | 4. 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
            | ​这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行MinorGC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
            | ​例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,
            | 则执行Full GC。当新生代采用PSGC时,方式稍有不同,PS GC是在Minor GC后也会检查,
            | ​例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。
            | 
            | ​除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。
            | ​可通过在启动时通过- java-Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间
            | ​或通过-XX:+DisableExplicitGC来禁止RMI调用System.gc。

        JVM的永久代中会发生垃圾回收么?
            | 垃圾回收不会发生在永久代,
            | ​如果永久代满了或者是超过了临界值,会触发完全垃圾回收(FullGC)。
            | ​
            | ​如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。
            | ​这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。
            | 
            | ​请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
            | 

        什么是Stop The World ?  什么是OopMap?什么是安全点?
            | STW
            | 进行垃圾回收的过程中,会涉及对象的移动。
            | 为了保证对象引用更新的正确性,必须暂停所有的用户线程,
            | 像这样的停顿,虚拟机设计者形象描述为「Stop The World」。也简称为STW。
            | 
            | 在HotSpot中,有个数据结构(映射表)称为「OopMap」。
            | 一旦类加载动作完成的时候,
            | HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,记录到OopMap。
            | 在即时编译过程中,
            | 也会在「特定的位置」生成 OopMap,记录栈上和寄存器里哪些位置是引用。
            | 
            | 这些特定的位置主要在:
            | 1.循环的末尾(非 counted 循环)
            | 2.方法临返回前 / 调用方法的call指令后
            | 3.可能抛异常的位置
            | 
            | 这些位置就叫作「安全点(safepoint)。」
            | ​用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,
            | ​而是必须是执行到安全点才能够暂停。
            | 



执行模式
    解释模式
    编译模式
        编译器优化
            数组范围检查消除
            公共子表达式消除
            方法内联
            逃逸分析
                对象一定分配在堆中吗?有没有了解逃逸分析技术?
                    | 「对象一定分配在堆中吗?」
                    | 不一定的,JVM通过「逃逸分析」,那些逃不出方法的对象会在栈上分配。
                    | 「什么是逃逸分析?」(Escape Analysis),
                    | 是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。
                    | 作用
                    | 通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上。
                    | 逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。
                    | 
                    | 当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。
                    | 也分为方法逃逸和线程逃逸
                    | 
                    | 通俗点讲,如果一个对象的指针被多个方法或者线程引用时,
                    | 那么我们就称这个对象的指针发生了逃逸。
                    | 
                    | 「逃逸分析的好处」
                    | 1,栈上分配,可以降低垃圾收集器运行的频率。
                    | 2,同步消除,如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。
                    | 3,标量替换,把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有,
                    | ​    一、减少内存使用,因为不用生成对象头。
                    | ​    二、程序内存回收效率高,并且GC频率也会减少。




    混合模式

性能调优
    常见调优工具有哪些
        | 常用调优工具分为两类,
        | jdk自带监控工具:jconsole和jvisualvm,
        | 第三方有:MAT(MemoryAnalyzer Tool)、GChisto。
        | 
        | jconsole,
        | Java Monitoring and Management Console是从java5开始,
        | 在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控jvisualvm,
        | jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
        | 
        | MAT,Memory Analyzer Tool,
        | 一个基于Eclipse的内存分析工具,是一个快速、功能丰富的
        | Java heap分析工具,
        | 它可以帮助我们查找内存泄漏和减少内存消耗
        | GChisto,
        | ​一款专业分析gc日志的工具

    JVM调优命令有哪些
        Sun JDK监控和故障处理命令有
        | jps,JVM Process Status Tool,
        | 显示指定系统内所有的HotSpot虚拟机进程。
        | jstat,JVM statistics Monitoring
        | 是用于监视虚拟机运行时状态信息的命令,
        | 它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
        | jmap,JVM Memory Map
        | 用于生成heap dump文件
        | jhat,JVM Heap Analysis Tool  命令是与jmap搭配使用,
        | 用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,
        | 生成dump的分析结果后,可以在浏览器中查看。
        | jstack,
        | 用于生成java虚拟机当前时刻的线程快照。
        | jinfo,JVM Configuration info 
        | ​这个命令作用是实时查看和调整虚拟机运行参数。
        | 

    你知道哪些JVM性能调优参数?
        简单版
        堆栈类
        垃圾收集器相关
        辅助信息相关


MySQL

SQL编程
基础知识
    数据库的三范式是什么
        | 第一范式:列不可再分.
        | ​第二范式:行可以唯一区分,主键约束.
        | ​第三范式:表的非主属性不能依赖与其他表的非主属性外键约束.
        | 
        | ​且三大范式是一级一级依赖的:
        | ​第二范式建立在第一范式上,第三范式建立第一第二范式上。
        | 

    int(11) 中的 11 代表什么涵义?
        | 不影响字段存储的范围,只影响展示效果。

    CHAR 和 VARCHAR 的区别?
        | CHAR 和VARCHAR 类型在存储和检索方面有所不同
        | CHAR 列长度固定为创建表时声明的长度,长度值范围是1 到255
        | ​当 CHAR 值被存储时,它们被用空格填充到特定长度,
        | ​检索CHAR 值时需删除尾随空格。
        | 

    MySQL数据库引擎有哪些
        如何查看mysql提供的所有存储引擎
        | mysql> show engines;
        常用引擎
        | MYISAM:
        | 全表锁,拥有较高的执行速度,不支持事务,不支持外键,并发性能差,占用空间相对较小,对事务完整性没有要求,以select、insert为主的应用基本上可以使用这引擎。
        | Innodb:
        | 行级锁,提供了具有提交、回滚和崩溃回复能力的事务安全,
        | 支持自动增长列,支持外键约束,并发能力强,
        | 占用空间是MYISAM的2.5倍,处理效率相对会差一些。
        | Memory:
        | 全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在
        | mysql重启时会丢失,默认使用HASH索引,检索效率非常高,
        | 但不适用于精确查找,主要用于那些内容变化不频繁的代码表
        | MERGE:
        | ​是一组MYISAM表的组合

    说说InnoDB与MyISAM的区别
        | 在 MySQL 5.1 及之前的版本中,MyISAM 是默认的存储引擎,
        | 而在 MySQL 5.5 版本以后,默认使用 InnoDB 存储引擎。
        | 
        | 1. InnoDB支持事务,MyISAM不支持,
        | 对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,
        | 所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
        | 2. InnoDB支持外键,而MyISAM不支持。
        | 对一个包含外键的InnoDB表转为MYISAM会失败;
        | 3. 索引类型不一样
        | InnoDB是聚集索引
        | 数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。
        | 但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。
        | 因此,主键不应该过大,因为主键太大,其他索引也都会很大。
        | 而MyISAM是非聚集索引,
        | 数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
        | 4,是否存储总行数
        | InnoDB不保存表的具体行数,执行select count() from table时需要全表扫描。
        | MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可。
        | 5. Innodb不支持全文索引,而MyISAM支持全文索引,
        | 查询效率上MyISAM要高;
        | 6,行锁表锁
        | MyISAM 不支持行级锁,换句话说,MyISAM 会对整张表加锁,而不是针对行;
        | InnoDB 支持行锁

    说说在 MySQL 中一条查询 SQL 是如何执行的?
        | 1. 取得链接,
        | 使用到 MySQL 中的连接器。
        | 2. 查询缓存,
        | key 为 SQL 语句,value 为查询结果,如果查到就直接返回。
        | 不建议使用次缓存,在 MySQL 8.0 版本已经将查询缓存删除,也就是说 MySQL 8.0 版本后不存在此功能。
        | 3. 分析器,
        | 分为词法分析和语法分析。此阶段只是做一些 SQL 解析,语法校验。
        | 所以一般语法错误在此阶段。
        | 4. 优化器,
        | 是在表里有多个索引的时候,决定使用哪个索引;
        | 或者一个语句中存在多表关联的时候(join),决定各个表的连接顺序。
        | 5. 执行器,
        | 通过分析器让 SQL 知道你要干啥,通过优化器知道该怎么做,于是开始执行语句。
        | 执行语句的时候还要判断是否具备此权限,
        | 没有权限就直接返回提示没有权限的错误;
        | 有权限则打开表,根据表的引擎定义,去使用这个引擎提供的接口,
        | ​获取这个表的第一行,判断 id 是都等于 1。
        | ​如果是,直接返回;
        | ​如果不是继续调用引擎接口去下一行,重复相同的判断,直到取到这个表的最后一行,最后返回。
        | 

    为什么 SELECT COUNT(*) FROM table 在 InnoDB 比 MyISAM 慢?
        -
        | 对于 SELECT COUNT(*) FROM table 语句,在没有 WHERE 条件的情况下,InnoDB 比 MyISAM 可
        | 能会慢很多,尤其在大表的情况下。
        | 因为,InnoDB 是去实时统计结果,会全表扫描;
        | 而 MyISAM内部维持了一个计数器,预存了结果,所以直接返回即可。
        | 

    主键和候选键有什么区别?
        | 表格的每一行都由主键唯一标识,一个表只有一个主键。
        | ​主键也是候选键。按照惯例,
        | ​候选键可以被指定为主键,并且可以用于任何外键引用。
        | 

    简单说一说drop、delete与truncate的区别
        -
        | delete和truncate只删除表的数据不删除表的结构
        | 速度:
        | 一般来说: drop> truncate >delete 
        | delete语句是dml
        | 这个操作会放到rollback segement中,事务提交之后才生效;如果有相应的trigger,执行的时候将被触发. 
        | truncate,drop是ddl
        | ​操作立即生效,原数据不放到rollback segment中,不能回滚.操作不触发trigger.

    什么是内联接、左外联接、右外联接?
        | 内联接(Inner Join):
        | 匹配2张表中相关联的记录。
        | 左外联接(Left Outer Join):
        | 除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。
        | 右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。
        | ​
        | ​在判定左表和右表时,要根据表名出现在Outer Join的左右位置关系。
        | 

    说说什么是 MVCC?
        概念
        | 多版本并发控制(MVCC=Multi-Version Concurrency Control)
        | 是一种用来解决读 - 写冲突的无锁并发控制。
        | ​也就是为事务分配单向增长的时间戳,为每个修改保存一个版本。
        | ​版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照(复制了一份数据)。
        | ​这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。
        | 
        MVCC 可以为数据库解决什么问题?
        | 提高了数据库并发读写的性能
        | ​在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作;
        | ​
        | ​同时还可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。
        | 
        实现原理
        | MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,
        | 它的实现原理主要是依赖记录中的 3 个隐式字段、undo 日志、Read View 来实现的。
        | 
        | 


数据库事务
    什么是事务?
    | 多条sql语句,要么全部成功,要么全部失败。
    事务特性  ACID?
        原子性
        | 组成一个事务的多个数据库操作是一个不可分割的原子单元,
        | ​只有所有操作都成功,整个事务才会提交。
        | ​任何一个操作失败,已经执行的任何操作都必须撤销,让数据库返回初始状态。
        一致性
        | 事务操作成功后,数据库所处的状态和它的业务规则是一致的。数据不会被破坏。如A转账100元给B,不管操作是否成功,A和B的账户总额是不变的。
        隔离性
        | 在并发数据操作时,不同的事务拥有各自的数据空间,
        | ​它们的操作不会对彼此产生干扰.
        持久性
        | 一旦事务提交成功,事务中的所有操作都必须持久化到数据库中。

    事务的隔离级别?
        -
        | 读未提交(Read Uncommitted):
        | 最低的隔离级别,允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
        | 读已提交(Read Committed):
        | 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
        | 可重复读(Repeated Read)   
        | 在同一个事务内的查询都是事务开始时刻一致的,Mysql的InnoDB默认级别。
        | 在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读。
        | 可串行化(Serializable):
        | 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
        | 
        | 这里需要注意的是:
        |  InnoDB 存储引擎在REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统是不同的。
        | 所以说:Innod的可重读 == Sql 标准的串行化
        | 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是
        | READ-COMMITTED(读取提交内容),但是你要知道的是InnoDB 存储引擎默认使用
        | REPEAaTABLE-READ(可重读)并不会有任何性能损失。
        | ​
        | InnoDB 存储引擎在分布式事务的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。
        | ​
        图示

    并发事务带来哪些问题?
        | 脏读(Dirty read): 
        |  当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
        | 丢失修改(Lost to modify): 
        | 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
        | 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
        | 不可重复读(Unrepeatableread): 
        | 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
        | 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
        | 
        | 不可重复读和幻读区别:
        | 不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,
        | 幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
        | ​


 MySQL 数据库的锁
    常见的锁
    | 关于 MySQL 的锁机制,可能会问很多问题,不过这也得看面试官在这方面的知识储备。
    | MySQL 中有共享锁和排它锁,也就是读锁和写锁。
    | 1. 共享锁:不堵塞,多个用户可以同一时刻读取同一个资源,相互之间没有影响。
    | 2. 排它锁:一个写操作阻塞其他的读锁和写锁,这样可以只允许一个用户进行写入,防止其他用户读取正在写入的资源。
    | 3. 表锁:系统开销最小,会锁定整张表,MyISAM 使用表锁。
    | 4. 行锁:容易出现死锁,发生冲突概率低,并发高,InnoDB 支持行锁(必须有索引才能实现,否则会自动锁全表,那么就不是行锁了)。
    | 
    锁升级
    | MySQL 行锁只能加在索引上,如果操作不走索引,就会升级为表锁。
    | 因为 InnoDB 的行锁是加在索引上的,如果不走索引,自然就没法使用行锁了,
    | 原因是 InnoDB 是将 primary key index和相关的行数据共同放在 B+ 树的叶节点。
    | InnoDB 一定会有一个 primary key,secondaryindex 查找的时候,也是通过找到对应的 primary,再找对应的数据行。
    | 当非唯一索引上记录数超过一定数量时,行锁也会升级为表锁。
    | 测试发现当非唯一索引相同的内容不少于整个表记录的二分之一时会升级为表锁。
    | 因为当非唯一索引相同的内容达到整个记录的二分之一时,索引需要的性能比全文检索还要大,查询语句优化时会选择不走索引,造成索引失效,行锁自然就会升级为表锁。
    | 

索引
    索引是什么
    | 官方介绍索引是帮助MySQL高效获取数据的数据结构。
    | ​更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。
    索引的储存位置
    | 一般来说索引本身也很大,不可能全部存储在内存中,
    | 因此索引往往是存储在磁盘上的文件中的
    | (可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中)。
    | 
    索引的分类
    | ​我们通常所说的索引,包括聚集索引、覆盖索引、组合索引、前缀索引、唯一索引等,
    | ​没有特别说明,默认都是使用B+树结构组织(多路搜索树,并不一定是二叉的)的索引。
        主键索引
        | 索引列中的值必须是唯一的,不允许有空值。
        唯一索引
        | 索引列中的值必须是唯一的,但是允许为空值。
        | 
        普通索引
        | MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值。
        全文索引
        | 只能在文本类型CHAR,VARCHAR,TEXT类型字段上创建全文索引。
        | 字段长度比较大时,如果创建普通索引,
        | 在进行like模糊查询时效率比较低,这时可以创建全文索引。
        | MyISAM和InnoDB中都可以使用全文索引
        | 
        空间索引
        | MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。
        | 
        前缀索引
        | 在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度,
        | ​但是数值类型不能指定。
        | 

    索引的优缺点
    索引的使用场景
        不应该使用的场景
        | 1. 经常增删改的列不要建立索引;
        | 2. 有大量重复的列不建立索引;
        | 3. 表记录太少不要建立索引。
        | 

    主键与索引有什么区别?
        | 主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键;
        | 主键不允许为空值,唯一索引列允许空值;
        | 一个表只能有一个主键,但是可以有多个唯一索引;
        | 主键可以被其他表引用为外键,唯一索引列不可以;
        | 主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构,两者有本质区别。
        | 


视图
    | 视图是一种虚拟的表,具有和物理表相同的功能。
    | 可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。
    | 

优化
    大表如何优化
        限定数据范围
        | 务必禁止不带任何限制数据范围条件的查询语句。
        | ​比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
        | 
        读写分离
        | 经典的数据库拆分方案,主库负责写,从库负责读;
        垂直分区
            概念
            | 根据数据库里面数据表的相关性进行拆分。
            | 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
            | 简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。
            | 
            优缺点
            | 垂直拆分的优点
            | 1,可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。
            | 2,此外,垂直分区可以简化表的结构,易于维护。
            | 垂直拆分的缺点
            | ​1,主键会出现冗余,需要管理冗余列,
            | ​2,并会引起Join操作,可以通过在应用层进行Join来解决。
            | ​3,此外,垂直分区会让事务变得更加复杂;
            | 

        水平分区
            概念 
            | 保持数据表结构不变,通过某种策略存储数据分片。
            | ​这样每一片数据分散到不同的表或者库中,达到了分布式的目的。
            | ​水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。
            | ​举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。·
            | 
            | 水平拆分可以支持非常大的数据量。
            | ​需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,
            | ​但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,
            | ​所以水平拆分最好分库。
            | 
            优缺点
            | 优点
            | ​水平拆分可以支撑非常大的数据量。


    SQL 优化手段有哪些
        | 1、查询语句中不要使用select *
        | 2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代
        | 3、减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代
        | 4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
        | 5、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
        | 6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,
        | ​如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0
        | 
        | 

    分库分表之后,id 主键如何处理?
        使用全局ID
        | 因为要是分成多个表之后,每个表都是从 1 开始累加,
        | ​这样是不对的,我们需要一个全局唯一的 id来支持。
        | 
        生成全局 id 有下面这几种方式
        | 1,UUID:
        | 不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯
        | 一的名字的标示比如文件的名字。
        | 2,数据库自增 id : 
        | 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式
        | 生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。
        | 3,利用 redis 生成 id :
        | 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系
        | 统更加复杂,可用性降低,编码更加复杂,增加了系统成本。
        | 4,Twitter的snowflake算法:
        | Github 地址:https://github.com/twitter-archive/snowflake
        | 美团的Leaf分布式ID生成系统:Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋
        | 势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据
        | 库、Zookeeper等中间件。感觉还不错。
        | 美团技术团队的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html。
        | 
        | 

    MySQL 如何做到高可用方案?
        | MySQL 高可用,意味着不能一台 MySQL 出了问题,就不能访问了。
        | 1. MySQL 高可用:分库分表,通过 MyCat 连接多个 MySQL
        | 2. MyCat 也得高可用:Haproxy,连接多个 MyCat
        | 3. Haproxy 也得高可用:通过 keepalived 辅助 Haproxy
        | 

    MySQL数据库服务器性能分析的方法命令有哪些?


框架

Spring
    什么是Spring?
        -概念
        | Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。
        | Spring是一个轻量级的IoC和AOP容器开源开发框架。
        | 是为Java应用程序提供基础性服务的一套框架,
        | 目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
        | 并通过POJO为基础的编程模型促进良好的编程习惯。
        组成
        | Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
        | ​Spring Core:核心类库,提供IOC服务;
        | Spring AOP:AOP服务;
        | Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;
        | Spring MVC:提供面向Web应用的Model-View-Controller实现。
        | Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
        | ​Spring ORM:对现有的ORM框架的支持;
        | 
        常见的配置方式
        | 基于XML的配置、
        | ​基于注解的配置、
        | ​基于Java的配置。
        | 

    Spring框架优点
        优点
        | 轻量Spring 是轻量的,基本的版本大约2MB。
        | 控制反转 IOC实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象。
        | 面向切面的编程(AOP)Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
        | 容器Spring 包含并管理应用中对象的生命周期和配置。
        | MVC框架Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
        | 事务管理Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务
        | (JTA)。支持声明式事务。
        | 异常处理Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛
        | 出的)转化为一致的unchecked 异常。
        | 

    常用注解
        Autowired和Resource关键字的区别?
            | 相同点
            | 1,都是做bean的注入时使用
            | 
            | 2,都可以写在字段和setter方法上
            | 两者如果都写在字段上,那么就不需要再写setter方法。
            | 不同点
            | 1,来源不同
            | @ Autowired为Spring提供的注解
            | 需要导入包org.springframework.beans.factory.annotation.Autowired;
            | @ Resource并不是Spring的注解
            | 它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
            | 2,用法不同
            | @ Autowired只按照byType注入。
            | 默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@ Qualifier注解一起使用。
            | @ Resource默认按照ByName自动注入
            | 由J2EE提供,需要导入包javax.annotation.Resource。
            | @ Resource有两个重要的属性:name和type,
            | 而Spring将@ Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。
            | 所以,如果使用name属性,则使用byName的自动注入策略,
            | 而使用type属性时则使用byType自动注入策略。
            | 如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
            | 
                代码示例
                    Autowired的使用
                    Resource的使用



        @ Resource装配顺序
            -
            | 1,如果同时指定了name和type,
            | 则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
            | 2,如果指定了name,
            | 则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
            | 3,如果指定了type,
            | 则从上下文中找到类似匹配的唯一bean进行装配,找不到或找到多个,都会抛出异常。
            | 4,如果既没有指定name,又没有指定type,
            | 则自动按照byName方式进行装配
            | 5,如果没有匹配,
            | ​则回退为一个原始类型进行匹配,如果匹配则自动装配。

        依赖注入的方式
            构造器注入
            | 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注
            | 入。
            | 优点:
            | 对象初始化完成后便可获得可使用的对象。
            | 缺点:
            | 1,不够灵活
            | 当需要注入的对象很多时,构造器参数列表将会很长;
            | 2,麻烦
            | 若有多种注入方式,每种方式只需注入指定几个依赖,
            | ​那么就需要提供多个重载的构造函数,
            | 
            setter方法注入
            |  IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给
            | 依赖类。
            | 优点:
            | 1,灵活。
            | 可以选择性地注入需要的对象。
            | 缺点:
            | 1,依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。
            | 
            接口注入
            | 依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖
            | 注入。该函数的参数就是要注入的对象。
            | 优点
            | 1,接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
            | 缺点
            | 1,侵入行太强,不建议使用。
            | 什么是侵入行?如果类A要使用别人提供的一个功能,若为了使用这功能,需要在自己的类中
            | 增加额外的代码,这就是侵入性。
            | ​
            | 


    AOP
        是什么
        | AOP(Aspect-Oriented Programming,面向切面编程)
        | ​能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任
        | ​(例如事务处理、日志管理、权限控制等)封装起来,
        | ​便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
        | 
        实现
        | Spring AOP是基于动态代理的,
        | JKD
        | 如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;
        | CGlib
        | ​而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
        | 

    Spring AOP和AspectJ AOP有什么区别?
        -
        | 1,Spring AOP是属于运行时增强,而AspectJ是编译时增强。
        | 2,Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
        | 3,Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。
        | 4,AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。
        | 最佳实践
        | 如果我们的切面比较少,那么两者性能差异不大。
        | ​但是,当切面太多的话,最好选AspectJ,它比SpringAOP快很多。

    在Spring AOP 中,关注点和横切关注的区别是什么?
        -
        | 关注点 
        | 是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
        | 横切关注点
        | 是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。
        | 连接点
        | 连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。
        | 切入点
        | ​是切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
        | 

    什么是通知呢?有哪些类型呢?
        -
        | 通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。
        | before:前置通知,在一个方法执行前被调用。
        | after:在方法执行之后调用的通知,无论方法执行是否成功。
        | after-returning:仅当方法成功完成后执行的通知。
        | after-throwing:在方法抛出异常退出时执行的通知。
        | around:在方法执行之前和之后调用的通知。
        | 

    IOC
        是什么
        | 1,IOC就是控制反转
        | 是指创建对象的控制权的转移。
        | 以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。
        | 对象与对象之间松散耦合,也利于功能的复用。
        | DI依赖注入,和控制反转是同一个概念的不同角度的描述,
        | 即应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
        | 2,IOC让对象的创建不用去new了,可以由spring自动生产
        | ​使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
        | 
        IOC 的三种注入方式
        | 构造器注入、setter方法注入、根据注解注入。

    Bean 的生命周期
        Servlet生命周期
        | 实例化,初始init,接收请求service,销毁destroy;
        流程图

        Bean的生命周期
        | 1,实例化Bean
        | 对于BeanFactory容器,
        | 当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
        | 对于ApplicationContext容器,
        | 当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
        | 2,设置对象属性(依赖注入)
        | 实例化后的对象被封装在BeanWrapper对象中,紧接着,
        | Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
        | 3,处理Aware接口
        | Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
        | ①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String
        | beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
        | ②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
        | ③如果这个Bean已经实现了ApplicationContextAware接口,会调用
        | setApplicationContext(ApplicationContext)方法,传入Spring上下文;
        | 4,BeanPostProcessor
        | 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
        | 5,InitializingBean 与 init-method
        | 如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
        | 6,如果这个Bean实现了BeanPostProcessor接口,
        | 将会调用postProcessAfterInitialization(Object obj, String s)方法;
        | 由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
        | 
        | 以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
        | 
        | 7,DisposableBean
        | 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
        | 8,destroy-method
        | 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
        | 

    Bean 的作用域
        Spring容器中的bean可以分为5个范围
        | 1,singleton:默认,
        | 每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护
        | 2,prototype
        | 为每一个bean请求提供一个实例。
        | 3,request  为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
        | 4,session 
        | 与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
        | 5,global-session:全局作用域  
        | ​global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。
        | ​如果你想要声明让所有的portlet共用全局的存储变量的话,
        | ​那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
        | 

    Spring基于xml 注入bean的几种方式?
        -
        | (1)Set方法注入;
        | (2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
        | (3)静态工厂注入;
        | (4)实例工厂;

    Spring 框架中的单例 Bean 是线程安全的么?
        -
        | 关于单例 Bean 的线程安全和并发问题,需要开发者自行去搞定。
        | 单例的线程安全问题,并不是 Spring 应该去关心的。
        | Spring 应该做的是,提供根据配置,创建单例 Bean 或多例 Bean 的功能。
        | 
        | 当然,但实际上,大部分的 Spring Bean 并没有可变的状态,所以在某种程度上说 Spring 的单例Bean 是线程安全的。
        | ​如果你的 Bean 有多种状态的话,就需要自行保证线程安全。
        | ​
        | ​最浅显的解决办法,就是将多态 Bean 的作用域(Scope)由 Singleton 变更为 Prototype。
        | 

    Spring框架中都用到了哪些设计模式?
        —
        | 简单工厂模式:Spring 中的 BeanFactory 就是简单工厂模式的体现。
        | 根据传入一个唯一的标识来获得 Bean 对象,但是在传入参数后创建还是传入参数前创建,要根据具体情况来定。
        | 工厂模式:Spring 中的 FactoryBean 就是典型的工厂方法模式,
        | 实现了 FactoryBean 接口的 bean是一类叫做 factory 的 bean。
        | 其特点是,spring 在使用 getBean() 调用获得该 bean 时,会自动调用该 bean 的 getObject() 方法,所以返回的不是 factory 这个 bean,而是这个 bean.getOjbect()方法的返回值。
        | 单例模式:在 spring 中用到的单例模式有:scope="singleton",注册式单例模式,
        | bean 存放于Map 中。bean name 当做 key,bean 当做 value。
        | 原型模式:在 spring 中用到的原型模式有:scope="prototype",
        | 每次获取的是通过克隆生成的新实例,对其进行修改时对原有实例对象不造成任何影响。
        | 迭代器模式:在 Spring 中有个 CompositeIterator 实现了 Iterator,
        | Iterable 接口和 Iterator 接口,这两个都是迭代相关的接口。
        | 可以这么认为,实现了 Iterable 接口,则表示某个对象是可被迭代的。
        | Iterator 接口相当于是一个迭代器,实现了 Iterator 接口,等于具体定义了这个可被迭代的
        | 对象时如何进行迭代的。
        | 代理模式:Spring 中经典的 AOP,就是使用动态代理实现的,
        | 分 JDK 和 CGlib 动态代理。
        | 适配器模式:Spring 中的 AOP 中 AdvisorAdapter 类,
        | 它有三个实现:
        | MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter。
        | Spring会根据不同的 AOP 配置来使用对应的 Advice,
        | 与策略模式不同的是,一个方法可以同时拥有多个Advice。
        | Spring 存在很多以 Adapter 结尾的,大多数都是适配器模式。
        | 观察者模式:Spring 中的 Event 和 Listener。
        | spring 事件:ApplicationEvent,
        | 该抽象类继承了EventObject 类,JDK 建议所有的事件都应该继承自 EventObject。
        | spring 事件监听器:ApplicationListener,
        | 该接口继承了 EventListener 接口,JDK 建议所有的事件监听器都应该继承EventListener。
        | 模板模式:Spring 中的 org.springframework.jdbc.core.JdbcTemplate 
        | 就是非常经典的模板模式的应用,里面的 execute 方法,把整个算法步骤都定义好了。
        | 责任链模式:DispatcherServlet 中的 doDispatch() 方法中
        | ​获取与请求匹配的处理器HandlerExecutionChain,this.getHandler() 方法的处理使用到了责任链模式。
        | ​

    Spring 中 ApplicationContext 和 BeanFactory 的区别
        -类图
        | 可以看到,ApplicationContext 继承了 BeanFactory,
        | BeanFactory 是 Spring 中比较原始的Factory,它不支持 AOP、Web 等 Spring 插件。
        | 而 ApplicationContext 不仅包含了 BeanFactory的所有功能,还支持 Spring 的各种插件,
        | 还以一种面向框架的方式工作以及对上下文进行分层和实现继承。
        | 
        | BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;
        | 而 ApplicationContext 面向使用Spring 的开发者,
        | 最佳实践
        | 相比 BeanFactory 提供了更多面向实际应用的功能,
        | 几乎所有场合都可以直接使用 ApplicationContext,而不是底层的 BeanFactory。
        | 
        包目录不同
        | spring-beans.jar 中 org.springframework.beans.factory.BeanFactory
        | spring-context.jar 中 org.springframework.context.ApplicationContext
        | 
        国际化支持
        | BeanFactory 是不支持国际化功能的,因为 BeanFactory 没有扩展 Spring 中 MessageResource
        | 接口。
        | ​相反,由于 ApplicationContext 扩展了 MessageResource 接口,因而具有消息处理的能力
        | (i18N)。
        | 
        底层资源的访问
        | ApplicationContext 扩展了 ResourceLoader(资源加载器)接口,从而可以用来加载多个
        | Resource,
        | ​而 BeanFactory 是没有扩展 ResourceLoader。
        | 
        对Web应用的支持
        | 与 BeanFactory 通常以编程的方式被创建,
        | ​
        | ​ApplicationContext 能以声明的方式创建,如使用ContextLoader。
        | 当然你也可以使用 ApplicationContext 的实现方式之一,以编程的方式创建 ApplicationContext实例。
        | 
        延时加载
        | 1. BeanFactroy 采用的是延迟加载形式来注入 Bean 的,
        | 即只有在使用到某个 Bean 时(调用getBean()),才对该 Bean 进行加载实例化。
        | 这样,我们就不能发现一些存在的 spring 的配置问题。
        | 而 ApplicationContext 则相反,它是在容器启动时,一次性创建了所有的 Bean。
        | ​这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误。
        | ​
        | 2. BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用。
        | ​两者之间的区别是:
        | ​BeanFactory 需要手动注册,
        | ​而ApplicationContext 则是自动注册。
        | 
        常用容器  加载配置文件
        | BeanFactory 类型的有 XmlBeanFactory,
        | 它可以根据 XML 文件中定义的内容,创建相应的Bean。
        | ApplicationContext 类型的常用容器有:
        | 1. ClassPathXmlApplicationContext:
        | 从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。
        | 2. FileSystemXmlApplicationContext:
        | 由文件系统中的 XML 配置文件读取上下文。
        | 3. XmlWebApplicationContext:
        | ​由 Web 应用的 XML 文件读取上下文。例如我们在 Spring MVC使用的情况。
        | 

    Spring 是怎么解决循环依赖的?
        -三级缓存,提前曝光。
        | 1. 首先 A 完成初始化第一步并将自己提前曝光出来
        | ​(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来;
        | ​
        | 2. 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
        | ​
        | 3. 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A)。这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories),通过
        | ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象。C 拿
        | 到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中;
        | ​
        | 4. 回到 B,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了。
        | 

    spring 事务的传播级别
        -Spring事务定义了7种传播机制:
        | 1. PROPAGATION_REQUIRED   默认
        | 若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。
        | 2. PAOPAGATION_REQUIRE_NEW:
        | 无论当前是否有事务,新老事务相互独立。
        | 外部事务抛出异常回滚不会影响内部事务的正常提交。
        | 3. PROPAGATION_NESTED:
        | 如果当前存在事务,则嵌套在当前事务中执行。
        | 如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
        | 4. PROPAGATION_SUPPORTS:
        | 支持当前事务,若当前不存在事务,以非事务的方式执行。
        | 5. PROPAGATION_NOT_SUPPORTED:
        | 以非事务的方式执行,
        | 若当前存在事务,则把当前事务挂起。
        | 6. PROPAGATION_MANDATORY:
        | 强制事务执行,若当前不存在事务,则抛出异常.
        | 7. PROPAGATION_NEVER:
        | ​以非事务的方式执行,如果当前存在事务,则抛出异常。
        | 

    Spring 事务实现方式
        —
        | 编程式事务管理:
        | 这意味着你可以通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
        | 声明式事务管理:
        | 这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML配置管理事务。
        | 

     Spring框架的事务管理有哪些优点
        -
        | 1,它为不同的事务API(如JTA, JDBC, Hibernate, JPA, 和JDO)提供了统一的编程模型。
        | ​2,它为编程式事务管理提供了一个简单的API,而非一系列复杂的事务API(如JTA).
        | ​3,它支持声明式事务管理。
        | ​4,它可以和Spring 的多种数据访问技术很好的融合。
        | 

    事务的三要素
        -
        | 数据源
        | 表示具体的事务性资源,是事务的真正处理者,如MySQL等。
        | 事务管理器
        | 像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等。
        | 事务应用和属性配置
        | ​像一个标识符,表明哪些方法要参与事务,如何参与事务,
        | ​以及一些相关属性如隔离级别、超时时间等。
        | 

    事务注解的本质是什么?
        -
        | 是什么
        | @ Transactional 这个注解仅仅是一些(和事务相关的)元数据,
        | 在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。
        | 作用
        | 一是表明该方法要参与事务,
        | 二是配置相关属性来定制事务的参与方式和运行行为。
        | 
        | 声明式事务主要是得益于Spring AOP。
        | 使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。
        | 如何使用
        | @ Transactional注解既可以标注在类上,也可以标注在方法上。
        | ​当在类上时,默认应用到类里的所有方法。
        | ​如果此时方法上也标注了,则方法上的优先级高。另外注意方法一定要是public的。
        | 


SpringMVC
    MVC模式
        概念
        | 是一种设计模式:
        | M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
        | V-View 视图(做界面的展示 jsp,html)
        | C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
        | 
        原理图


    SpringMVC 是什么
        -
        | springMVC是一个MVC的开源框架,
        | springMVC=struts2+spring,springMVC就相当于是Struts2加上spring的整合,
        | 
        | 但是这里有一个疑惑就是,springMVC和spring是什么样的关系呢?
        | 这个在百度百科上有一个很好的解释:
        | springMVC是spring的一个后续产品,其实就是spring在原有基础上,
        | 又提供了web应用的MVC模块,
        | ​可以简单的把springMVC理解为是spring的一个模块(类似AOP,IOC这样的模块),
        | ​网络上经常会说springMVC和spring无缝集成,其实springMVC就是spring的一个子模块,所以根本不需要同spring进行整合。
        | 

    SpringMVC 工作原理(执行流程)
        图示
            -
            -
            |  1、首先用户发送请求——>DispatcherServlet,
            | 前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
            |  2、DispatcherServlet——>HandlerMapping, 
            | HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略; 
            | 3、DispatcherServlet——>HandlerAdapter,
            | HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
            | 4、HandlerAdapter——>处理器功能处理方法的调用,
            | HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
            |  5、ModelAndView的逻辑视图名——> ViewResolver,
            |  ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术; 
            | 6、View——>渲染,
            | View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
            |  7、返回控制权给DispatcherServlet ,由DispatcherServlet返回响应给用户,到
            | 此一个流程结束。

        流程
        | 1、用户发送请求至前端控制器DispatcherServlet。
        | 2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
        | 3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
        | 4、DispatcherServlet调用HandlerAdapter处理器适配器。
        | 5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
        | 6、Controller执行完成返回ModelAndView。
        | 7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
        | 8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
        | 9、 ViewReslover解析后返回具体View。
        | 10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
        | 11,DispatcherServlet响应用户。
        | 

    SpringMVC 常用组件说明
        1、前端控制器DispatcherServlet    
        | 由框架提供
        | 整个流程控制的中心,接收请求,响应结果,相当于转发器,中央处理器,由它调用其它组件处理用户的请求,降低组件之间的耦合性,提高每个组件的扩展性。
        | ​
        | 用户请求到达前端控制器,它就相当于mvc模式中的c,
        | 
        2,处理器映射器HandlerMapping   
        | 由框架提供
        | 根据请求的url查找Handler 
        | ​HandlerMapping负责根据用户请求找到Handler即处理器,
        | ​
        | ​springmvc提供了不同的映射器实现不同的映射方式,
        | ​例如:配置文件方式,实现接口方式,注解方式等。
        | 
        3,处理器适配器HandlerAdapter
        | 由框架提供
        | 按照特定规则(HandlerAdapter要求的规则)去执行Handler 
        | 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,
        | 通过扩展适配器可以对更多类型的处理器进行执行。
        | 
        4,处理器Handler   即控制器方法
        | 编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler 。
        | 
        | Handler 是继DispatcherServlet前端控制器的后端控制器,
        | 在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
        | 由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发。
        | 
        5,视图解析器 View resolver
        | 进行视图解析,根据逻辑视图名解析成真正的视图(view)
        | 
        |  View Resolver负责将处理结果生成View视图,
        | 首先根据逻辑视图名解析成物理视图名即具体的页面地址,
        | 再生成View视图对象,
        | 最后对View进行渲染将处理结果通过页面展示给用户。 
        | 
        6,视图 View 
        | View是一个接口,实现类支持不同的View类型
        | 包括:jstlView、freemarkerView、pdfView等。
        | 
        | 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,
        | 需要由工程师根据业务需求开发具体的页面。
        | 

    SpringMVC 常用注解
        @ RequestMapping
        | 用于处理请求 url 映射的注解,可用于类或方法上。
        | ​用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
        @ RequestBody
        | 注解实现接收http请求的json数据,将json转换为java对象。
        @ ResponseBody
        | 注解实现将conreoller方法返回对象转化为json对象响应给客户。


Mybatis
    什么是Mybatis
        —
        | 1,Mybatis是一个半ORM(对象关系映射)框架,
        | 它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
        | 程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
        | 2,MyBatis 可以使用 XML 或注解来配置和映射原生信息,
        | 将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
        | 3,通过xml 文件或注解的方式将要执行的各种 statement 配置起来,
        | ​并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,
        | ​最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
        | 

    说说MyBatis的优点和缺点
        优点
        | (1)基于SQL语句编程,相当灵活,
        | 不会对应用程序或者数据库的现有设计造成任何影响,
        | SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;
        | 提供XML标签,支持编写动态SQL语句,并可重用。
        | (2)与JDBC相比,减少了50%以上的代码量,
        | 消除了JDBC大量冗余的代码,不需要手动开关连接;
        | (3)很好的与各种数据库兼容
        | 因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据
        | 库MyBatis都支持。
        | (4)能够与Spring很好的集成;
        | (5)提供映射标签,
        | ​支持对象与数据库的ORM字段关系映射;
        | ​提供对象关系映射标签,支持对象关系组件维护。
        | 
        缺点
        | (1)SQL语句的编写工作量较大,
        | 尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
        | (2)SQL语句依赖于数据库,
        | ​导致数据库移植性差,不能随意更换数据库。
        | 

    {}和${}的区别是什么?
        -

    当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
        1,通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
        第2种:通过来映射字段名和实体类属性名的一一对应的关系。

    Mybatis是如何进行分页的?分页插件的原理是什么?
        -
        | Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。
        | 可以在sql内直接拼写带有物理分页的参数来完成物理分页功能,
        | 也可以使用分页插件来完成物理分页,
        | 比如:MySQL数据的时候,在原有SQL后面拼写limit。
        | 原理
        | 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
        | 

    Mybatis是如何将sql执行结果  封装为 目标对象 并返回的?
        两种映射形式
        | 第一种是使用标签,
        | 逐一定义数据库列名和对象属性名之间的映射关系。
        | 第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
        | 有了列名与属性名的映射关系后,Mybatis通过反射创建对象,
        | ​同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
        | 

    Mybatis 批量插入
        首先,创建一个简单的insert语句
        然后在java代码中像下面这样执行批处理插入

    xml映射文件中的常用标签
        select|insert|updae|delete
        动态sql的9个标签

    MyBatis实现一对一有几种方式?
        —联合查询,嵌套查询
        | 联合查询
        | 是几个表联合查询,只查询一次,,
        | 通过在resultMap里面配置association节点配置一对一的类就可以完成;
        | 嵌套查询
        | ​是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,
        | ​也是通过association配置,
        | ​但另外一个表的查询通过select属性配置。
        | 

    Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
        -
        | Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,
        | association指的就是一对一,collection指的就是一对多查询。
        | 在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
        | 原理
        | 使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法
        | ​1,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,
        | ​2,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用
        | a.setB(b),于是a的对象b属性就有值了,
        | ​3,接着完成a.getB().getName()方法的调用。
        | ​这就是延迟加载的基本原理。
        | ​
        | 当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
        | 

    Mybatis 的缓存机制
        图示
            -

        一级缓存
            图示
            | 1. MyBatis 一级缓存的生命周期和 SqlSession 一致。
            | 2. MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺。
            | 3. MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement。
            | 
            概念
            | 在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的 SQL,
            | MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的 SQL 语句,
            | 会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
            | 
            | 每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。
            | ​当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,
            | ​在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,
            | ​如果缓存没有命中的话,查询数据库,结果写入 Local Cache,
            | ​最后返回结果给用户。具体实现类的类关系图如下图所示:
            | 

        二级缓存
            图示
            | 二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,
            | 即二级缓存被多个 SqlSession 共享,是一个全局的变量
            | 
            | 当开启缓存后,数据的查询执行的流程为:
            | 二级缓存 -> 一级缓存 -> 数据库
            | ​
            | 
            概念
            | 在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,
            | 如果多个 SqlSession之间需要共享缓存,则需要使用到二级缓存。
            | 
            | 开启二级缓存后,会使用 CachingExecutor 装饰Executor,
            | 进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询
            | 
            | 注意点
            | 1,MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
            | 2,在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本。
            | ​直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。
            | 


    JDBC编程步骤

    Mybatis 中的设计模式
    MyBatis 中比如 UserMapper.java 是接口,为什么没有实现类还能调用?、
        | 使用JDK动态代理+MapperProxy。
        | ​本质上调用的是MapperProxy的invoke方法。


SpringBoot
    为什么要用SpringBoot?
        | 一、独立运行
        | 内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,
        | Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
        | 二、简化配置
        | spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
        | 三、自动配置
        | Spring Boot能根据当前类路径下的类、jar包来自动配置bean,
        | 如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。
        | 四、无代码生成和XML配置
        | Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,
        | 这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。
        | 五、应用监控
        | Spring Boot提供一系列端点可以监控服务及应用,做健康检测。
        | 

    Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
        -启动类上面的注解 @ SpringBootApplication

    运行Spring Boot有哪几种方式?
        1)打包用命令或者放到容器中运行
        2)用 Maven/Gradle 插件运行
        3)直接执行 main 方法运行

    如何理解 Spring Boot 中的 Starters?
        Starters是什么
        | Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,
        | ​你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。
        | ​如你想使用Spring JPA访问数据库,只要加入spring-boot-starter-data-jpa启动器依赖就能使用了。
        | ​Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
        | 
        Starters命名
        | Spring Boot官方的启动器都是以spring-boot-starter-命名的,代表了一个特定的应用类型。
        | 第三方的启动器不能以spring-boot开头命名,它们都被Spring Boot官方保留。
        | 一般一个第三方的应该这样命名,像mybatis的mybatis-spring-boot-starter。
        | 
         springboot常用的 starter
        | spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp支持
        | spring-boot-starter-data-jpa 数据库支持
        | spring-boot-starter-data-redis redis数据库支持
        | spring-boot-starter-data-solr solr支持
        | mybatis-spring-boot-starter 第三方的mybatis集成starter
        | 
        Starters分类
            应用类启动器
            生产类启动器
            技术类启动器
            其他第三方启动器


    如何在Spring Boot启动的时候运行一些特定的代码?
        -
        | 如果你想在Spring Boot启动的时候运行一些特定的代码,
        | 1,你可以实现接口ApplicationRunner或者CommandLineRunner,
        | ​这两个接口实现方式一样,它们都只提供了一个run方法。
        | CommandLineRunner:启动获取命令行参数
        | 

     Spring Boot中的监视器是什么?
        -
        | Spring boot actuator是spring启动框架中的重要功能之一。
        | ​Spring boot监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。
        | ​
        | 有几个指标必须在生产环境中进行检查和监控。
        | ​即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。
        | ​监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态。
        | 

    如何使用Spring Boot实现异常处理?
        -
        | Spring提供了一种使用ControllerAdvice处理异常的非常有用的方法。
        | ​我们通过实现一个ControlerAdvice类,来处理控制器类抛出的所有异常。
        | 

    运行Spring Boot有哪几种方式?
        1)打包用命令或者放到容器中运行
        2)用 Maven/Gradle 插件运行
        3)直接执行 main 方法运行

     SpringBoot 实现热部署有哪几种方式?
        主要有两种方式
        | Spring Loaded
        | Spring-boot-devtools
        | 

     如何理解 Spring Boot 配置加载顺序?
        在 Spring Boot 里面,可以使用以下几种方式来加载配置。
        | 1)properties文件;
        | 2)YAML文件;
        | 3)系统环境变量;
        | 4)命令行参数;
        | 

    Spring Boot 的核心配置文件有哪几个?它们的区别是什么?
        application 和 bootstrap 配置文件
        | application 配置文件
        | 这个容易理解,主要用于 Spring Boot 项目的自动化配置。
        | bootstrap 配置文件
        | 有以下几个应用场景。
        | 1,使用 Spring Cloud Config 配置中心时,
        | 这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
        | 2,一些固定的不能被覆盖的属性;
        | 3,一些加密/解密的场景;

    如何集成 Spring Boot 和 ActiveMQ?
        | 对于集成 Spring Boot 和 ActiveMQ,我们使用 spring-boot-starter-activemq 依赖关系。
        | ​它只需要很少的配置,并且不需要样板代码。
        | 


SpringCloud
| 待补充
    什么是SpringCloud?
        一种分布式微服务的良好的解决方案
        | 基于 Spring Boot 的 Spring 集成应用程序,提供与外部系统的集成。
        | ​
        | ​Spring cloud Task,一个生命周期短暂的微服务框架,
        | ​用于快速构建执行有限数据处理的应用程序。
        | 
        | 

    什么是微服务?
        | 微服务架构是一种架构模式或者说是一种架构风格,
        | 它提倡将单一应用程序划分为一组小的服务
        | 每个服务运行在其独立的自己的进程中,服务之间相互协调、互相配合,为用户提供最终价值。
        | 
        | 服务之间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)
        | ​每个服务都围绕着具体的业务进行构建,并且能够被独立的构建在生产环境、类生产环境等。
        | ​
        | ​另外,应避免统一的、集中式的服务管理机制,
        | ​对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,
        | ​可以有一个非常轻量级的集中式管理来协调这些服务,
        | ​可以使用不同的语言来编写服务,也可以使用不同的数据存储。
        | 

    SpringCloud有什么优势
        | (1)解决分布式系统相关的复杂性
        | 这种开销包括网络问题,延迟开销,带宽问题,安全问题。
        | (2)服务发现
        | 服务发现工具管理群集中的流程和服务如何查找和互相交谈。
        | 它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
        | (3)解决冗余
        | 分布式系统中的冗余问题。
        | (4)负载平衡  
        | 负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,
        | 中央处理单元,或磁盘驱动器的分布。
        | (5)解决性能问题
        | 由于各种运营开销导致的性能问题。
        | (6)部署复杂性-Devops 技能的要求。
        | 

     什么是服务熔断?什么是服务降级?
        -
        | 熔断机制是应对雪崩效应的一种微服务链路保护机制。
        | 当某个微服务不可用或者响应时间太长时,会进行服务降级,进而熔断该节点微服务的调用,
        | 快速返回“错误”的响应信息。
        | 当检测到该节点微服务调用响应正常后恢复调用链路。
        | 
        | 在SpringCloud框架里熔断机制通过Hystrix实现,
        | Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内调用20次,
        | 如果失败,就会启动熔断机制。
        | 
        | 服务降级,熔断后返回一个缺省值;
        | 一般是从整体负荷考虑。就是当某个服务熔断之后,服务器将不再被调用,
        | 此时客户可以自己准备一个本地的fallback回调,返回一个缺省值。
        | 这样做,虽然水平下降,但好歹可用,比直接挂掉强。
        | 
        | Hystrix相关注解
        |  @ EnableHystrix:开启熔断 
        | @ HystrixCommand(fallbackMethod=”XXX”):
        | ​声明一个失败回滚处理函数XXX,当被注解的方法执行超时(默认是1000毫秒),
        | ​就会执行fallback函数,返回错误提示。

    SpringBoot和SpringCloud的关系?
        | 1,用途不同
        | SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
        | 它将SpringBoot开发的一个个单体微服务整合并管理起来,
        | 为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。
        | 2,具有依赖关系
        | SpringBoot可以离开SpringCloud独立使用开发项目,
        | ​但是SpringCloud离不开SpringBoot ,属于依赖的关系.
        | 
        | 

    负载均衡的意义是什么 ?
        概念
        | 在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。
        | 
        | 负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源的过载。
        | 
        | 使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性。
        | 
        | ​负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务器进程。
        | 
        | 

    说说 RPC 的实现原理
        | 首先需要有处理网络连接通讯的模块,
        | 负责连接建立、管理和消息的传输。
        | 其次需要有编解码的模块,
        | 因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。
        | 剩下的就是客户端和服务器端的部分,
        | ​服务器端暴露要开放的服务接口,
        | ​客户调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。
        | 
        | 

    eureka自我保护机制是什么?
        | 保护模式:短时间的过多实例丢失,会不再删除注册数据;
        | ​当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户
        | 端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。
        | 

    什么是Ribbon?
        | ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。
        | ​feign默认集成了ribbon。

    什么是feigin?它的优点是什么?
        | 1.feign采用的是基于接口的注解 
        | 2.feign整合了ribbon,具有负载均衡的能力
        |  3.整合了Hystrix,具有熔断的能力
        | 
        | 使用: 
        | 1.添加pom依赖。 
        | 2.启动类添加@ EnableFeignClients 
        | 3.定义一个接口@ FeignClient(name=“xxx”)指定调用哪个服务
        | 


分布式
    说说你对分布式事务的了解
        -
        | 分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,
        | 特别是在微服务架构中,几乎可以说是无法避免。
        | 首先要搞清楚:ACID、CAP、BASE理论。
        | 
        ACID 
        CAP
        | 一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。
        | 可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
        | 分区容忍性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
        | 
        BASE

    分布式幂等性如何设计?
        为什么要幂等性设计
        | 在高并发场景的架构里,幂等性是必须得保证的。
        | 防止重复支付
        | ​比如说支付功能,用户发起支付,如果后台没有做幂等校验,刚好用户手抖多点了几下,于是后台就可能多次受到同一个订单请求,不做幂等很容易就让用户重复支付了,这样用户是肯定不能忍的。
        | 
        解决方案

    你知道哪些分布式事务解决方案?
        -
        | 1. 二阶段提交(2PC)
        | 2. 三阶段提交(3PC)
        | 3. 补偿事务(TCC=Try-Confirm-Cancel)
        | 4. 本地消息队列表(MQ)
        | 5. Sagas事务模型(最终一致性)
        | 
        两阶段提交
            概念
            处理流程
            问题
            | 1) 性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
            | 2) 可靠性问题:如果协调者存在单点故障问题,或出现故障,提供者将一直处于锁定状态。
            | 3) 数据一致性问题:在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一
            | 致。
            | 优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证
            | 强一致)。
            | 缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
            | 

        三阶段提交
            概念
            | 三阶段提交是在二阶段提交上的改进版本,3PC最关键要解决的就是协调者和参与者同时挂掉的问
            | 题,所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交。
            | 
            处理流程
            详细描述
                | 阶段一
                | a) 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所
                | 有参与者答复。
                | b) 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否
                | 则反馈 no。
                | 阶段二
                | 协调者根据参与者响应情况,有以下两种可能。
                | 情况1:所有参与者均反馈 yes,协调者预执行事务
                | a) 协调者向所有参与者发出 preCommit 请求,进入准备阶段。
                | b) 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不
                | 提交事务)。
                | c) 各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。
                | 阿里内部资料
                | 情况2:只要有一个参与者反馈 no,或者等待超时后协调者尚无法收到所有提供者的反馈,即中断
                | 事务
                | a) 协调者向所有参与者发出 abort 请求。
                | b) 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事
                | 务。
                | 阶段三
                | 该阶段进行真正的事务提交,也可以分为以下两种情况。
                | 情况 1:所有参与者均反馈 ack 响应,执行真正的事务提交
                | a) 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。
                | b) 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
                | c) 各参与者向协调者反馈 ack 完成的消息。
                | d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
                | 情况2:只要有一个参与者反馈 no,或者等待超时后协调组尚无法收到所有提供者的反馈,即回滚
                | 事务。
                | a) 如果协调者处于工作状态,向所有参与者发出 rollback 请求。
                | b) 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
                | c) 各参与者向协调组反馈 ack 完成的消息。
                | d) 协调组收到所有参与者反馈的 ack 消息后,即完成事务回滚。
                | 

            优缺点
            | 优点:相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。
            | 避免了协调者单点问题。阶段 3 中协调者出现问题时,参与者会继续提交事务。
            | 缺点:数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 do commite 指令时,
            | 此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造
            | 成数据不一致。
            | 

        补偿事务
            TCC(Try Confirm Cancel)是服务化的二阶段编程模型,采用的补偿机制:
            | TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
            | 
            步骤  3个
            | Try 阶段
            | ​主要是对业务系统做检测及资源预留。
            | ​Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。
            | ​即:只要Try成功,Confirm一定成功。
            | ​
            | Cancel 阶段
            | ​主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
            | ​
            | 举个例子,假入你要向老田转账,思路大概是:
            | ​我们有一个本地方法,里面依次调用步骤:
            | ​ 1、首先在 Try 阶段,要先调用远程接口把你和老田的钱给冻结起来。
            | ​ 2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。 3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
            | 
            优缺点
            | 优点:
            | 性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。
            | 数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据
            | 的一致性。
            | 可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动
            | 管理器也变成多点,引入集群。、
            | 缺点:
            | ​TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开
            | 发成本。
            | 
            | 

        消息队列
             本地消息表

            MQ事务消息


        Sagas事务模型
            概念
            | Saga模式是一种分布式异步事务,一种最终一致性事务,是一种柔性事务
            实现 两种方式
                一、事件/编排Choreography:
                | 没有中央协调器(没有单点风险)时,
                | 每个服务产生并聆听其他服务的事件,并决定是否应采取行动。
                | 
                | 该实现第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件,当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有被任何Saga参与者听到都意味着事务结束。
                | 优点:
                | ​事件/编排是实现Saga模式的自然方式; 它很简单,容易理解,不需要太多的努力来构建,所
                | 有参与者都是松散耦合的,因为他们彼此之间没有直接的耦合。如果您的事务涉及2至4个步骤,则
                | 可能是非常合适的。
                | 
                    图示

                    处理流程说明
                        | 订单服务保存新订单,将状态设置为pengding挂起状态,并发布名为ORDER_CREATED_EVENT的
                        | 事件。
                        | 支付服务监听ORDER_CREATED_EVENT,并公布事件BILLED_ORDER_EVENT。
                        | 库存服务监听BILLED_ORDER_EVENT,更新库存,并发布ORDER_PREPARED_EVENT。
                        | 货运服务监听ORDER_PREPARED_EVENT,然后交付产品。最后,它发布
                        | ORDER_DELIVERED_EVENT。
                        | 最后,订单服务侦听ORDER_DELIVERED_EVENT并设置订单的状态为concluded完成。
                        | 假设库存服务在事务过程中失败了。进行回滚:
                        | 库存服务产生PRODUCT_OUT_OF_STOCK_EVENT
                        | 订购服务和支付服务会监听到上面库存服务的这一事件:
                        | ①支付服务会退款给客户。
                        | ②订单服务将订单状态设置为失败。
                        | 阿里内部资料
                        | 


                二、命令/协调orchestrator:
                | 中央协调器负责集中处理事件的决策和业务逻辑排序。
                | saga协调器orchestrator以命令/回复的方式与每项服务进行通信,告诉他们应该执行哪些操作。
                | 优点:
                | 避免服务之间的循环依赖关系,因为saga协调器会调用saga参与者,但参与者不会调用协调器。
                | 集中分布式事务的编排。
                | 只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性。
                | 在添加新步骤时,事务复杂性保持线性,回滚更容易管理。
                | 如果在第一笔交易还没有执行完,想改变有第二笔事务的目标对象,则可以轻松地将其暂停在协调
                | 器上,直到第一笔交易结束。
                | 
                    图示

                    执行流程
                        | 订单服务保存pending状态,并要求订单Saga协调器(简称OSO)开始启动订单事务。
                        | OSO向收款服务发送执行收款命令,收款服务回复Payment Executed消息。
                        | OSO向库存服务发送准备订单命令,库存服务将回复OrderPrepared消息。
                        | OSO向货运服务发送订单发货命令,货运服务将回复Order Delivered消息。
                        | OSO订单Saga协调器必须事先知道执行“创建订单”事务所需的流程(通过读取BPM业务流程XML配置
                        | 获得)。如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回
                        | 滚。当你有一个中央协调器协调一切时,回滚要容易得多,因为协调器默认是执行正向流程,回滚
                        | 时只要执行反向流程即可。
                        | 阿里内部资料
                        | 





    分布式ID生成有几种方案?
        UUID
            | 算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。
            | 优点:本地生成,生成简单,性能好,没有高可用风险
            | 缺点:长度过长,存储冗余,且无序不可读,查询效率低
            | 

        数据库自增ID
            | 使用数据库的id自增策略,
            | 如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
            | 优点:数据库生成的ID绝对有序,高可用实现方式简单
            | 缺点:需要独立部署数据库实例,成本高,有性能瓶颈
            | 

        批量生成ID
            | 一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。
            | 优点:避免了每次生成ID都要访问数据库并带来压力,提高性能
            | 缺点:属于本地生成策略,存在单点故障,服务重启造成ID不连续
            | 

        Redis 生成ID

        Twitter的snowflake算法(重点)
            图示
            组成
            | 1位符号位:
            | 由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。
            | 41位时间戳(毫秒级):
            | 需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41位毫秒时间戳最多可以使用(1 << 41) / (1000x60x60x24x365) = 69年。
            | 10位数据机器位:
            | 包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署1 << 10 = 1024s个节点。超过这个数量,生成的ID就有可能会冲突。
            | 12位毫秒内的序列:
            | 这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成1 << 12 = 4096个ID加起来刚好64位,为一个Long型。
            | 优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
            | 缺点:需要独立的开发和部署,依赖于机器的时钟
            | 

        百度UidGenerator
            | UidGenerator是百度开源的分布式ID生成器,基于于snowflake算法的实现,看起来感觉还行。不过,国内开源的项目维护性真是担忧。
            | 

        美团Leaf
            | Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,
            | ​但也需要依赖关系数据库、Zookeeper等中间件。
            | 


    常见负载均衡算法有哪些?

    你知道哪些限流算法?
        计数器(固定窗口)算法
            概念
            | 计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。
            | ​下一个周期开始时,进行清零,重新计数。
            | 
            图示
            问题

        滑动窗口算法
            概念
            | 滑动窗口算法是将时间周期分为N个小周期,
            | ​分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。
            | 
            图示

        漏桶算法
            概念
            | 漏桶算法是访问请求到达时直接放入漏桶,
            | ​如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。
            | ​漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。
            | 
            图示

        令牌桶算法
            概念
            | 令牌桶算法是程序以r(r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,
            | ​请求到达时向令牌桶请求令牌,
            | ​如获取到令牌则通过请求,否则触发限流策略
            | 
            图示


    数据库如何处理海量数据?
        | 水平分库/表,各个库和表的结构一模一样。
        | 垂直分库/表,各个库和表的结构不一样。
        | ​
        | 读写分离:主机负责写,从机负责读。
        | 

    如何将长链接转换成短链接,并发送短信?
        -
        | 短 URL 从生成到使用分为以下几步:
        | 1,有一个服务,将要发送给你的长 URL 对应到一个短 URL 上.
        | 例如www.baidu.com -> www.t.cn/1
        | 2,把短 url 拼接到短信等的内容上发送。
        | 3,用户点击短 URL ,浏览器用 301 / 302 进行重定向,访问到对应的长 URL。
        | 4,展示对应的内容。
        | 
        长链接和短链接如何互相转换?
        | 思路是建立一个发号器。
        | 每次有一个新的长 URL 进来,我们就增加一。
        | 并且将新的数值返回.
        | 第一个来的 url 返回"www.x.cn/0",第二个返回"www.x.cn/1"。
        | 
        长链接和短链接的对应关系如何存储?
        | 如果数据量小且 QPS 低,直接使用数据库的自增主键就可以实现。
        | ​还可以将最近/最热门的对应关系存储在 K-V 数据库中,这样子可以节省空间的同时,加快响应速度。
        | 

    如何提高系统的并发能力?
        | ·1,使用分布式系统。
        | 2,部署多台服务器,并做负载均衡。
        | 3,使用缓存(Redis)集群。
        | 4,数据库分库分表 + 读写分离。
        | 5,引入消息中间件集群。
        | 


Redis

为什么要用缓存
    | 使用缓存的目的就是提升读写性能。
    | 而实际业务场景下,更多的是为了提升读性能,带来更好的性能,带来更高的并发量。
    | Redis的读写性能比Mysql好的多,
    | ​我们就可以把Mysql中的热点数据缓存到Redis中,提升读取性能,同时也减轻了Mysql的读取压力。
    | 

什么是 Redis?
    | Redis 是一个开源(BSD 许可)、基于内存、支持多种数据结构的存储系统,可以作为数据库、缓存和消息中间件。
    | ​
    | ​它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、集合
    | (sets)、有序集合(sorted sets)等,
    | ​除此之外还支持 bitmaps、hyperloglogs 和地理空间(geospatial )索引半径查询等功能。
    | ​
    | 它内置了复制(Replication)、LUA 脚本(Lua scripting)、LRU 驱动事件(LRU eviction)、事务(Transactions)和不同级别的磁盘持久化(persistence)功能,并通过 Redis 哨兵(哨兵)和集群(Cluster)保证缓存的高可用性(Highavailability)。
    | 

使用 Redis有哪些优缺点?
    优点
    | 读取速度快,因为数据存在内存中,所以数据获取快;
    | 支持多种数据结构,包括字符串、列表、集合、有序集合、哈希等;
    | 支持事务,且操作遵守原子性,即对数据的操作要么都执行,要么都不支持;
    | 持久化存储:Redis 提供 RDB 和 AOF 两种数据的持久化存储方案,解决内存数据库最担心的万一 Redis 挂掉,数据会消失掉
    | 还拥有其他丰富的功能,队列、主从复制、集群、等功能。
    | 
    缺点
    | 容量取决于内存
    | 由于Redis是内存数据库,所以,单台机器,存储的数据量,跟随机器本身的内存大小。虽然Redis本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
    | 占用主机CPU,消耗带宽
    | 如果进行完整重同步,由于需要生成RDB文件,并进行传输,会占用主机的 CPU ,并会消耗现网的带宽。不过Redis2.8 版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
    | 加载数据时间比较久
    | 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。
    | ​在这个过程中,Redis不能提供服务。
    | 

为什么 使用Redis而不是用Memcache呢?
    | Redis和Memcache都是将数据存放在内存中,都是内存数据库。
    | ​不过Memcache还可用于缓存其他东西,例如图片、视频等等。
    | ​
    | Memcache仅支持key-value结构的数据类型,
    | ​Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,hash等数据结构的存储。
    | ​
    | 虚拟内存
    | ​Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
    | ​
    | 分布式
    | ​设定Memcache集群,利用magent做一主多从;
    | ​Redis可以做一主多从。都可以一主一从
    | 存储数据安全
    | ​Memcache挂掉后,数据没了;
    | ​Redis可以定期保存到磁盘(持久化)
    | ​
    | Memcache的单个value最大1m,Redis的单个value最大512m。
    | ​
    | 灾难恢复
    | ​Memcache挂掉后,数据不可恢复;
    | ​ Redis数据丢失后可以通过aof恢复
    | ​
    | Redis原生就支持集群模式,Redis3.0版本中,官方便能支持Cluster模式了,Memcached没有原生的集群模式,需要依赖客户端来实现,然后往集群中分片写入数据。
    | ​
    | Memcached网络IO模型是多线程,非阻塞IO复用的网络模型,原型上接近于nignx。而Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现类epoll,kqueue和select,更接近于Apache早期的模式。
    | 

说说Redis的线程模型
    -
    | Redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket ,根据 socket 上的事件来选择对应的事件处理器进行处理。
    | 
    文件事件处理器的结构包含 4 个部分
    | 1. 多个 socket 。
    | 2. IO 多路复用程序。
    | 3. 文件事件分派器。
    | 4. 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。
    | ​
    | 多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,
    | ​但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,
    | ​事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
    | 

Redis 为什么设计成单线程的?
    | 1,多线程处理会涉及到锁,
    | 并且多线程处理会涉及到线程切···换而消耗 CPU。
    | 采用单线程,避免了不必要的上下文切换和竞争条件。
    | 2,其次 CPU 不是 Redis 的瓶颈,
    | ​Redis 的瓶颈最有可能是机器内存或者网络带宽。
    | 

为什么Redis单线程模型效率也能那么高?
    | 1. C语言实现,效率高
    | 2. 纯内存操作
    | 3. 基于非阻塞的IO复用模型机制
    | 4. 单线程的话 就能避免多线程的频繁上下文切换问题
    | 5. 丰富的数据结构(全称采用hash结构,读取速度非常快,对数据存储进行了一些优化,比如亚索表,跳表等)

客户端与Redis的一次通信过程  重点
    图示
    | 1. 客户端Socket01向Redis的 Server Socket 请求建立连接,
    | 此时 Server Socket 会产生一个AE_READABLE事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的Socket01,并将该 Socket01的AE_READABLE事件与命令请求处理器关联。
    | 
    | 2. 假设此时客户端发送了一个 set key value 请求,
    | 此时Redis中的Socket01会产生AE_READABLE事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面Socket01的AE_READABLE事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取Socket01的 set key value 并在自己内存中完成 set key value 的设置。操作完成后,它会将Socket01的AE_WRITABLE事件与令回复处理器关联。
    | 3. 如果此时客户端准备好接收返回结果了,
    | ​那么Redis中的Socket01会产生一个AE_WRITABLE事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对Socket01输入本次操作的一个结果,比如ok,之后解除Socket01的AE_WRITABLE事件与命令回复处理器的关联。
    | 

为什么 Redis 需要把所有数据放到内存中?
    | Redis 将数据放在内存中有一个好处,那就是可以实现最快的对数据读取,
    | 如果数据存储在硬盘中,磁盘 I/O 会严重影响 Redis 的性能。
    | 而且 Redis 还提供了数据持久化功能,不用担心服务器重启对内存中数据的影响。其次现在硬件越来越便宜的情况下,Redis 的使用也被应用得越来越多,使得它拥有很大的优势。

Redis 的同步机制了解是什么?
    | Redis 支持主从同步、从从同步。
    | ​如果是第一次进行主从同步,主节点需要使用 bgsave 命令,
    | ​再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将RDB 镜像记载到内存中。
    | ​等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程。
    | 

pipeline 有什么好处,为什么要用 pipeline?
    | 使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,
    | 但是要求管道中执行的指令间没有因果关系。
    | 
    | 用 pipeline 的原因在于可以实现请求/响应服务器的功能,
    | ​当客户端尚未读取旧响应时,它也可以处理新的请求。
    | ​如果客户端存在多个命令发送到服务器时,那么客户端无需等待服务端的每次响应
    | 才能执行下个命令,只需最后一步从服务端读取回复即可。
    | 

说说Redis持久化机制
    | Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。
    | 当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
    | 实现:
    | 单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,
    | 然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
    | 
    | 当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
    | 

Redis持久化方式有哪些?以及有什么区别?怎么选择?
    RDB持久化方式
        | 是指用数据集快照的方式半持久化模式)记录redis数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
        | 优点:
        | 1,只有一个文件dump.rdb,方便持久化。
        | 2,容灾性好,一个文件可以保存到安全的磁盘。
        | 3,性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了Redis的高性能)
        | 4,相对于数据集大时,比AOF的启动效率更高。
        | 缺点:
        | 1,数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间Redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
        | 

     AOF=Append-only file持久化方式
        | 是指所有的命令行记录以Redis命令请求协议的格式完全持久化存储,保存为AOF文件。
        | 优点:
        | (1)数据安全,AOF持久化可以配置appendfsync属性,有 always,每进行一次命令操作就记录到AOF文件中一次。
        | (2)通过 append 模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。
        | (3)AOF机制的 rewrite 模式。AOF文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall)
        | 缺点:
        | ​(1)AOF文件比RDB文件大,且恢复速度慢。
        | ​(2)数据集大的时候,比RDB启动效率低。

    最佳实践  两者解合使用
    |  1,不要仅仅使用RDB,因为那样会导致你丢失很多数据。
    | 2.,也不要仅仅使用AOF,因为那样有两个问题,
    | 第一,你通过AOF做冷备没有RDB做冷备的恢复速度更快; 
    | 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备
    | 份和恢复机制的 bug 。
    | 3. Redis支持同时开启开启两种持久化方式,我们可以综合使用AOF和RDB。
    | 用AOF来保证数据不丢失,作为数据恢复的第一选择; 
    | 用RDB来做不同程度的冷备,
    | 在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。
    | 4. 如果同时使用RDB和AOF两种持久化机制,那么在Redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整。
    | 
    | 

怎么使用 Redis 实现消息队列?
    | 一般使用 list 结构作为队列,rpush生产消息,lpop消费消息。
    | 当lpop没有消息的时候,要适当sleep 一会再重试。
    | 
    | 面试官可能会问可不可以不用 sleep 呢?
    | list 还有个指令叫blpop,在没有消息的时候,它会阻塞住,直到消息到来。
    | 
    | 面试官可能还问能不能生产一次消费多次呢?
    | ​使用 pub / sub 主题订阅者模式,可以实现 1:N的消息队列。
    | ​
    | 面试官可能还问 pub / sub 有什么缺点?
    | ​在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等。
    | ​
    | 面试官可能还问Redis如何实现延时队列?
    | ​使用sortedset,拿时间戳作为 score ,消息内容作为 key 调用zadd来生产消息,
    | ​消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

说说你对Redis事务的理解
    什么是Reids 事务
    | Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位。
    | ​它可以保证一次执行多个命令,每个事务是一个单独的隔离操作,
    | ​事务中的所有命令都会序列化、按顺序地执行。
    | 
    注意点
    | 1,Redis 事务是不支持回滚的,不像 MySQL 的事务一样,要么都执行要么都不执行;
    | 2,Redis 服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。
    | 它的原理是先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。
    | ​直到事务命令全部执行完毕才会执行其他客户端的命令。
    | 
    为什么不支持回滚
    | Redis 的事务不支持回滚,但是执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序层面捕获并解决。
    | 但是如果出现其他问题,则依然会继续执行余下的命令。
    | 这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
    | 
    实现
    | 1)MULTI命令用于开启一个事务,
    | 它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有
    | 队列中的命令才会被执行。
    | ** 2)EXEC:执行所有事务块内的命令**
    | 返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。 
    | 3)通过调用DISCARD,
    | 客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。 
    | 4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
    | ​可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
    | 
    | 

为什么Redis的操作是原子性的,怎么保证原子性的?
    | 对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
    | Redis的操作之所以是原子性的,是因为Redis是单线程的。 Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
    | ​
    | ​多个命令在并发中也是原子性的吗?
    | ​不一定,将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.
    | 

什么是 bigkey?会存在什么影响?
    是什么
    | bigkey 是指键值占用内存空间非常大的 key。
    | ​例如一个字符串 a 存储了 200M 的数据。
    | 
    影响
    | 网络阻塞
    | 获取 bigkey 时,传输的数据量比较大,会增加带宽的压力。
    | 超时阻塞
    | 因为 bigkey 占用的空间比较大,所以操作起来效率会比较低,
    | 导致出现阻塞的可能性增加。
    | 导致内存空间不平衡
    | ​一个 bigkey 存储数据量比较大,同一个 key 在同一个节点或服务器中存储,会造成一定影响。

Redis集群相关
    Redis 常用的集群模式
        | Redis 集群架构是支持单节点单机模式的,
        | ​也支持一主多从的主从结构,
        | ​还支持带有哨兵的集群部署模式。
        | 

    集群的原理

    哈希槽
        | Redis 集群中内置了 16384 个哈希槽,
        | 当需要在 Redis 集群中放置一个 key-value 时,redis 先对key 使用 crc16 算法算出一个结果,
        | 然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0~16383 之间的哈希槽,
        | redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
        | 
        | Redis 集群并没有使用一致性 hash,而是引入了哈希槽的概念。Redis 集群有 16384(2^14)个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。
        | ​
        | 

    Redis Cluster集群方案什么情况下会导致整个集群不可用?


Redis 常见性能问题和解决方案有哪些?

 假设有很多 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?
    我们可以使用 keys 命令和 scan 命令,但是会发现使用 scan 更好。
    | 1. 使用 keys 命令
    | 直接使用 keys 命令查询,
    | 但是如果是在生产环境下使用会出现一个问题,keys 命令是遍历查询的,查询的时间复杂度为 O(n),数据量越大查询时间越长。
    | 而且 Redis 是单线程,keys 指令会导致线程阻塞一段时间,会导致线上 Redis 停顿一段时间,直到 keys 执行完毕才能恢复。这在生产环境是不允许的。
    | 除此之外,需要注意的是,这个命令没有分页功能,会一次性查询出所有符合条
    | 件的 key 值,会发现查询结果非常大,输出的信息非常多。
    | 所以不推荐使用这个命令。
    | 2. 使用 scan 命令
    | scan 命令可以实现和 keys 一样的匹配功能,
    | 但是 scan 命令在执行的过程不会阻塞线程,并且查找的数据可能存在重复,需要客户端操作去重。
    | 因为 scan 是通过游标方式查询的,所以不会导致Redis 出现假死的问题。
    | Redis 查询过程中会把游标返回给客户端,单次返回空值且游标不为 0,则说明遍历还没结束,客户端继续遍历查询。
    | scan 在检索的过程中,被删除的元素是不会被查询出来的,但是如果在迭代过程中有元素被修改,scan 不能保证查询出对应元素。
    | 相对来说,scan 指令查找花费的时间会比 keys 指令长。
    | 

如果有大量的 key 需要设置同一时间过期,一般需要注意什么?
    | 如果有大量的 key 在同一时间过期,那么可能同一秒都从数据库获取数据,给数据库造成很大的压力,导致数据库崩溃,系统出现 502 问题。
    | 也有可能同时失效,那一刻不用都访问数据库,压力不够大的话,那么 Redis 会出现短暂的卡顿问题。
    | 所以为了预防这种问题的发生,最好给数据的过期时间加一个随机值,让过期时间更加分散。
    | 

什么情况下可能会导致 Redis 阻塞?
    | 内部原因
    | 如果 Redis 主机的 CPU 负载过高,也会导致系统崩溃;
    | 数据持久化占用资源过多;
    | 对 Redis 的 API 或指令使用不合理,导致 Redis 出现问题。
    | 外部原因
    | 外部原因主要是服务器的原因,
    | ​例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、网络问题等。
    | 

Redis缓存刷新策略有哪些?

缓存和数据库谁先更新呢? 数据不一致的问题

Redis 如何解决 key 冲突?
    | Redis 如果 key 相同,后一个 key 会覆盖前一个 key。
    | 如果要解决 key 冲突,最好给 key 取好名区分开,可以按业务名和参数区分开取名,避免重复 key 导致的冲突。
    | 

Redis 报内存不足怎么处理?
    | 修改配置文件 redis.conf 的 maxmemory 参数,增加 Redis 可用内存;
    | 设置缓存淘汰策略,提高内存的使用效率;
    | 使用 Redis 集群模式,提高存储量。
    | 

怎么提高缓存命中率?
    | 提前加载数据到缓存中;
    | 增加缓存的存储空间,提高缓存的数据;
    | 调整缓存的存储数据类型;
    | 提升缓存的更新频率。
    | 

缓存雪崩
    | 我们可以简单的理解为:
    | 由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。
    | 从而形成一系列连锁反应,造成整个系统崩溃。
    | 解决办法
    | 大多数系统设计者考虑用加锁(最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
    | 还有一个简单方案就是将缓存失效时间分散开。
    | 

缓存穿透
    | 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
    | 解决办法
    | 最常见的则是采用布隆过滤器,
    | 将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
    | 另外也有一个更为简单粗暴的方法,
    | 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然
    | 把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
    | 
    | 5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。
    | 
    | 如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?
    | 对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。 Bitmap:典型的就是哈希表
    | 缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。
    | 布隆过滤器(推荐)就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
    | 它的优点是空间效率和查询时间都远远超过一般的算法,
    | 缺点是有一定的误识别率和删除困难。 
    | Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
    |  Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。
    | 只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
    | ** Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在**。
    | 

缓存预热
    | 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。
    | ​这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!
    | ​用户直接查询事先被预热的缓存数据!
    | ​解决思路: 
    | ​1、直接写个缓存刷新页面,上线时手工操作下;
    | ​ 2、数据量不大,可以在项目启动的时候自动进行加载;
    | ​ 3、定时刷新缓存;
    | 

缓存更新、失效策略,淘汰
    | 除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),
    | ​我们还可以根据具体的业务需求进行自定义的缓存淘汰,
    | ​
    | ​常见的策略有两种:
    | ​(1)定时去清理过期的缓存;
    | ​(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
    | 
    | ​两者各有优劣,
    | ​第一种的缺点是维护大量缓存的key是比较麻烦的,
    | ​第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!
    | ​
    | ​具体用哪种方案,大家可以根据自己的应用场景来权衡。
    | 

缓存降级
    | 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
    | 
    | 系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
    | 降级的最终目的是保证核心服务可用,即使是有损的。
    | 而且有些服务是无法降级的(如加入购物车、结算)。
    | 以参考日志级别设置预案:
    | (1)一般:
    | 比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
    | (2)警告:
    | 有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
    | (3)错误:
    | 比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
    | (4)严重错误:
    | 比如因为特殊原因数据错误了,此时需要紧急人工降级。
    | 
    | 服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,
    | 例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
    | 

热点数据和冷数据是什么
    | 热点数据,缓存才有价值,数据更新前至少读取两次,缓存才有意义。
    | 这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
    | 
    | 对于冷数据而言,
    | 大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。
    | 频繁修改的数据,看情况考虑使用缓存
    | 
    | 那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?
    | ​有!比如,这个读取接口对数据库的压力很大,
    | ​但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,
    | ​
    | ​比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,
    | ​但是又不断变化,此时就需要将

Memcache与Redis的区别都有哪些?
    | 1)、存储方式 
    | Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
    | Redis有部份存在硬盘上,redis可以持久化其数据 
    | 2)、数据支持类型
    |  memcached所有的值均是简单的字符串,
    | redis作为其替代者,支持更为丰富的数据类型,提供list,set,zset,hash等数据结
    | 构的存储 
    | 3)、使用底层模型不同
    | 它们之间底层实现方式以及与客户端之间通信的应用协议不一样。
    |  Redis直接自己构建了VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
    | 4). value 值大小不同:
    | Redis 最大可以达到 1gb;
    | memcache 只有 1mb。
    | ** 5.) redis的速度比memcached快很多**
    | ​ 6)Redis支持数据的备份,即master-slave模式的数据备份。

redis的数据类型,以及每种数据类型的使用场景
    | (一)String 
    | 这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。
    | 一般做一些复杂的计数功能的缓存。 
    | (二)hash 
    | 这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。
    | 博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。 
    | (三)list 
    | 使用List的数据结构,可以做简单的消息队列的功能。
    | 另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。
    | 本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。
    | LIST可以很好的完成排队,先进先出的原则。
    |  (四)set
    |  因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。
    | 为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公
    | 共服务,太麻烦了。
    | 另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,
    | 自己独有的喜好等功能。
    | ** (五)sorted** 
    | set sorted 
    | set多了一个权重参数score,集合中的元素能够按score进行排列。
    | 可以做排行榜应用,取TOP N操作。
    | 
    | 

redis的过期策略以及内存淘汰机制

Nginx

反向代理,负载均衡,动静分离
简述一下什么是Nginx,它有什么优势和功能?
    概念
    | Nginx是一个web服务器和方向代理服务器,
    | ​用于HTTP、HTTPS、SMTP、POP3和IMAP协议。
    | ​因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。
    | 
    功能
    | 也就是说Nginx本身就可以托管网站(类似于Tomcat一样)
    | 进行Http服务处理,也可以作为反向代理服务器、负载均衡器和HTTP缓存。
    | Nginx 解决了服务器的C10K(就是在一秒之内连接客户端的数目为10k即1万)问题。
    | ​它的设计不像传统的服务器那样使用线程处理请求,
    | ​而是一个更加高级的机制—事件驱动机制,是一种异步事件驱动结构。
    优点
    | 更快
    | 高可用性,用于反向代理,宕机的概率为0
    | 低内存消耗
    | 单机支持10万以上的并发连接
    | 热部署
    | 高扩展,跨平台
    | 最自由的BSD许可协议
    | 
    | 以上7个特点当然不是Nginx的全部,拥有无数个官方功能模块、第三方功能模块
    | 使得Nginx能够满足绝大部分应用场景,这些功能模块间可以叠加以实现更加强大、复杂的功能,
    | 有些模块还支持Nginx与Perl、Lua等脚本语言集成工作,大大提高了开发效率。
    | 这些特点促使用户在寻找一个Web服务器时更多考虑Nginx。
    | 选择Nginx的核心理由还是它能在支持高并发请求的同时保持高效的服务。
        更快
        | ​一方面,在正常情况下,单次请求会得到更快的响应;
        | ​另一方面,在高峰期(如有数以万计的并发请求),Nginx可以比其他Web服务器更快地响应请求。
        | 
        高可用性,用于反向代理,宕机的概率为0
        | 高可靠性是我们选择Nginx的最基本条件,
        | ​因为Nginx的可靠性是大家有目共睹的,很多家高流量网站都在核心服务器上大规模使用;
        | ​
        | Nginx的高可靠性来自于其核心框架代码的优秀设计、模块设计的简单性;
        | ​
        | ​另外,官方提供的常用模块都非常稳定,
        | ​每个worker进程相对独立,master进程在1个worker进程出错时可以快速“拉起”新的worker子进程提供服务。
        低内存消耗
        | 一般情况下,10 000个非活跃的HTTP Keep-Alive连接在Nginx中仅消耗2.5MB的
        | 内存,这是Nginx支持高并发连接的基础。
        | 
        单机支持10万以上的并发连接
        | 这是一个非常重要的特性!
        | ​随着互联网的迅猛发展和互联网用户数量的成倍增长,各大公司、网站都需要应付海量并发请求,一个能够在峰值期顶住10万以上并发请求的Server,无疑会得到大家的青睐。
        | 
        | ​理论上,Nginx支持的并发连接上限取决于内存,10万远未封顶。
        | ​当然,能够及时地处理更多的并发请求,是与业务特点紧密相关的。
        | 
        热部署
        |  master管理进程与worker工作进程的分离设计,
        | ​使得Nginx能够提供热部署功能,即可以在7×24小时不间断服务的前提下,升级Nginx的可执行文件。
        | ​当然,它也支持不停止服务就更新配置项、更换日志文件等功能。
        | 
        高扩展,跨平台
        |  Nginx的设计极具扩展性,它完全是由多个不同功能、不同层次、不同类型且耦合度极低的模块组成。
        | ​因此,当对某一个模块修复Bug或进行升级时,可以专注于模块自身,无须在意其他。
        | 
        | ​而且在HTTP模块中,还设计了HTTP过滤器模块:
        | ​一个正常的HTTP模块在处理完请求后,会有一串HTTP过滤器模块对请求的结果进行再处理。
        | ​这样,当我们开发一个新的HTTP模块时,不但可以使用诸如HTTP核心模块、events模块、log模块等不同层次或者不同类型的模块,还可以原封不动地复用大量已有的HTTP过滤器模块。
        | ​这种低耦合度的优秀设计,造就了Nginx庞大的第三方模块,当然,公开的第三方模块也如官方发布的模块一样容易使用。
        | 
        | ​ Nginx的模块都是嵌入到二进制文件中执行的,无论官方发布的模块还是第三方模块都是如此。
        | ​这使得第三方模块一样具备极其优秀的性能,充分利用Nginx的高并发特性,
        | ​因此,许多高流量的网站都倾向于开发符合自己业务特性的定制模块。
        | 
        最自由的BSD许可协议
        | 这是Nginx可以快速发展的强大动力。
        | ​BSD许可协议不只是允许用户免费使用Nginx,它还允许用户在自己的项目中直接使用或修改Nginx源码,然后发布。
        | ​这吸引了无数开发者继续为Nginx贡献自己的智慧。
        | 


Nginx是如何处理一个HTTP请求的呢?
    多进程机制
    | 1,服务器每当收到一个客户端时,就有服务器主进程( master process )生成一个子进程(worker process )出来和客户端建立连接进行交互,直到连接断开,该子进程就结束了。
    | 优点
    | 1,使用进程的好处是各个进程之间相互独立,
    | 不需要加锁,减少了使用锁对性能造成影响,同时降低编程的复杂度,降低开发成本。
    | 2,采用独立的进程,可以让进程互相之间不会影响,
    | 如果一个进程发生异常退出时,其它进程正常工作, master 进程则很快启动新的 worker 进程,确保服务不会中断,从而将风险降到最低。
    | 缺点
    | 1,是操作系统生成一个子进程需要进行内存复制等操作,在资源和时间上会产生一定的开销。
    | 2,当有大量请求时,会导致系统性能下降。
    | 
    异步非阻塞机制
    | 每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。
    | 1,当某个工作进程接收到客户端的请求以后,调用 IO 进行处理,
    | ​如果不能立即得到结果,就去处理其他请求(即为非阻塞);
    | ​2,而客户端在此期间也无需等待响应,可以去处理其他事情(即为异步)。
    | 3,当 IO 返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务去响应客户端请求。
    | 

列举一些Nginx的特性?
    |  反向代理/L7负载均衡器
    | 嵌入式Perl解释器
    | 动态二进制升级
    | 可用于重新编写URL,具有非常好的PCRE支持
    | 

在Nginx中,如何使用未定义的服务器名称来阻止处理请求?
    | 这里,服务器名被保留为一个空字符串,它将在没有“主机”头字段的情况下匹配请求,
    | ​而一个特殊的Nginx的非标准代码444被返回,从而终止连接。
    | ​
    | 一般推荐 worker 进程数与CPU内核数一致,
    | ​这样一来不存在大量的子进程生成和管理任务,避免了进程之间竞争CPU 资源和进程切换的开销。
    | ​而且 Nginx 为了更好的利用多核特性,提供了 CPU亲缘性的绑定选项,
    | ​我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来Cache 的失效。
    | ​
    | 对于每个请求,有且只有一个工作进程对其处理。
    | ​首先,每个 worker 进程都是从 master进程fork 过来。在 master 进程里面,先建立好需要 listen 的 socket(listenfd)之后,然后再 fork 出多个 worker 进程。
    | 
    | 所有 worker 进程的 listenfd 会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有
    | worker 进程在注册 listenfd 读事件前抢占 accept_mutex ,抢到互斥锁的那个进程注册 listenfd 读
    | 事件,在读事件里调用 accept 接受该连接。
    | 当一个 worker 进程在 accept 这个连接之后,就开始读取请求、解析请求、处理请求,产生数据
    | 后,再返回给客户端,最后才断开连接。这样一个完整的请求就是这样的了。我们可以看到,一个
    | 请求,完全由 worker 进程来处理,而且只在一个 worker 进程中处理。
    | 在 Nginx 服务器的运行过程中,主进程和工作进程需要进程交互。交互依赖于 Socket 实现的管道
    | 来实现。
    | 

请解释Nginx服务器上的Master和Worker进程分别是什么? 
    | 主程序 Master process 启动后,通过一个 for 循环来接收和处理外部信号;
    | 主进程通过 fork() 函数产生 worker 子进程,每个子进程执行一个 for循环来实现Nginx服务器
    | 对事件的接收和处理。
    | 

请解释代理中的正向代理和反向代理
    | 首先,代理服务器一般指局域网内部的机器通过代理服务器发送请求到互联网上的服务器,
    | 正向代理服务器一般作用在客户端。
    | 例如:GoAgent翻墙软件。我们的客户端在进行翻墙操作的时候,我们使用的正是正向代理,通过正向代理的方式,在我们的客户端运行一个软件,将我们的HTTP请求转发到其他不同的服务器端,实现请求的分发。
    | 
    | 反向代理服务器作用在服务器端,
    | 它在服务器端接收客户端的请求,然后将请求分发给具体的服务器进行处理,然后再将服务器的相应结果反馈给客户端。
    | 
    | Nginx就是一个反向代理服务器软件。
    | 
    | 客户端必须设置正向代理服务器,
    | 当然前提是要知道正向代理服务器的IP地址,还有代理程序的端口。
    | 反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特别的设置。
    | ​客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断何处(原始服务器)转交请求,并将获得的内容返回给客户端。

MQ

为什么要使用MQ
    解耦,异步,削峰
    | 1)解耦:
    | A 系统发送数据到 BCD 三个系统,通过接口调用发送。
    | 如果 E 系统也要这个数据呢?
    | 那如果 C 系统现在不需要了呢?
    | A 系统负责人几乎崩溃......A 系统跟其它各种乱七八糟的系统严重耦合,
    | A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。
    | 
    | 如果使用MQ
    | A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。
    | 如果新系统需要数据,直接从 MQ 里消费即可;
    | 如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。
    | 
    | 这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要
    | 考虑人家是否调用成功、失败超时等情况。
    | 
    | 就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻
    | 烦。
    | 但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦。
    | (2)异步:
    | A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。
    | 最终请求总延时是 3 + 300 +450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。
    | 用户通过浏览器发起请求。
    | 如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一
    | 个请求到返回响应给用户,总时长是 3 + 5 = 8ms。
    | (3)削峰:
    | ​减少高峰时期对服务器压力。
    | 

MQ有什么缺点
    | 系统可用性降低
    | 系统引入的外部依赖越多,越容易挂掉。万一 MQ 挂了,MQ 一挂,整套系统崩溃,就完了。
    | 系统复杂度提高
    | 硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?
    | 怎么保证消息传递的顺序性?问题一大堆。
    | 一致性问题 
    | ​A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;
    | ​但是问题是,要是BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,
    | ​咋整?你这数据就不一致了。
    | 
    | 

Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别?
MQ 如何保证高可用的?
    RabbitMQ 是比较有代表性的
    | 因为是基于主从(非分布式)做高可用性的,
    | 我们就以 RabbitMQ为例子讲解第一种 MQ 的高可用性怎么实现。
    | 
    RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。
    | 单机模式,
    | 就是 Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式;
    | 普通集群模式,
    | 意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。
    | 你创建的queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。
    | 你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
    | 这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。
    | 镜像集群模式:
    | 这种模式,才是所谓的 RabbitMQ 的高可用模式。
    | ​跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,
    | ​就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。
    | ​RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
    | ​这样的话,
    | ​好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。
    | ​坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!
    | ​RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。

如何保证消息的可靠传输?如果消息丢了怎么办
    解决方案
    数据的丢失问题,可能出现在生产者、MQ、消费者中
    | 生产者丢失:
    | 生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的
    | 此时可以选择用 RabbitMQ 提供的事务功能,
    | 就是生产者发送数据之前开启RabbitMQ 事务channel.txSelect,然后发送消息,
    | 如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;
    | 如果收到了消息,那么可以提交事务channel.txCommit。
    | 吞吐量会下来,因为太耗性能。
    | 所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启confirm模式,
    | 在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的 id,
    | 然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个ack消息,告诉你说这个消息 ok 了。
    | 如果 RabbitMQ 没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。
    | 而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,
    | 如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
    | 事务机制和cnofirm机制最大的不同在于,
    | 事务机制是同步的,你提交一个事务之后会阻塞在那儿,
    | 但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ 接收了之后会异步回调你一个接口通知你这个消息接收到了。
    | 所以一般在生产者这块避免数据丢失,都是用confirm机制的。
    | 
    | MQ中丢失:
    | 就是 RabbitMQ 自己弄丢了数据,这个你必须开启 RabbitMQ 的持久化,
    | 就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。
    | 
    | 设置持久化有两个步骤:
    | 1,创建 queue 的时候将其设置为持久化,这样就可以保证RabbitMQ 持久化 queue 的元数据,但是不会持久化 queue 里的数据。
    | 2,发送消息的时候将消息的 deliveryMode 设置为 2,就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
    | 必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。
    | 持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。
    | 注意,哪怕是你给RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。
    | 
    | 消费端丢失:
    | 你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,
    | RabbitMQ 认为你都消费了,这数据就丢了。这个时候得用 RabbitMQ 提供的ack机制
    | 简单来说,就是你关闭 RabbitMQ 的自动ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里ack一把。
    | ​这样的话,如果你还没处理完,不就没有ack?那RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。

如何保证消息的顺序性
    先看看顺序会错乱的场景
    | RabbitMQ:一个 queue,多个 consumer,这不明显乱了;
    解决办法

如何解决消息队列的延时以及过期失效问题?
    |  MQ中消息失效:假设你用的是 RabbitMQ,
    | RabbtiMQ 是可以设置过期时间的,也就是 TTL。
    | 如果消息在 queue 中积压超过一定的时间就会被RabbitMQ 给清理掉,这个数据就没了。
    | 那这就是第二个坑了。这就不是说数据会大量积压在 mq里,而是大量的数据会直接搞丢。
    | 
    | 我们可以采取一个方案,就是批量重导,
    | ​这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。
    | ​这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。
    | ​假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
    | 

消息队列满了以后该怎么处理?
有几百万消息持续积压几小时,说说怎么解决?
    | 消息积压处理办法:临时紧急扩容:
    | 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。新建一个
    | topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。然后写一个临时的分发
    | 数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀
    | 轮询写入临时建立好的 10 倍数量的 queue。接着临时征用 10 倍的机器来部署 consumer,每一
    | 批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资
    | 源扩大 10 倍,以正常的 10 倍速度来消费数据。等快速消费完积压数据之后,得恢复原先部署的
    | 架构,重新用原先的 consumer 机器来消费消息。
    | 
    | 

让你来设计一个消息队列,你会怎么设计
    | 比如说这个消息队列系统,我们从以下几个角度来考虑一下:
    | 首先这个 mq 得支持可伸缩性吧,
    | 就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?
    | 设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个
    | partition 放一个机器,就存一部分数据。
    | 如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
    | 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?
    | 那肯定要了,落磁盘才能保证别进程挂了数据就丢了。
    | 那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读
    | 写的性能是很高的,这就是 kafka 的思路。
    | 其次你考虑一下你的 mq 的可用性啊?
    | 这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。
    | 多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
    | 能不能支持数据 0 丢失啊?
    | ​可以的,参考我们之前说的那个 kafka 数据零丢失方案。
    | 

Linux

绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示?切换目录用什么命令?
    | 绝对路径:如/etc/init.d
    | 当前目录和上层目录: ./ ../
    | 主目录: ~/
    | 切换目录: cd
    | 

怎么查看当前进程?怎么执行退出?怎么查看当前路径?
    | 查看当前进程: ps
    | ps -l 列出与本次登录有关的进程信息;
    |  ps -aux 查询内存中进程信息;
    |  ps -aux | grep 查询进程的详细信息;
    |  top 查看内存中进程的动态信息;
    |  kill -9 pid 杀死进程。
    | 执行退出: exit
    | 查看当前路径: pwd
    | 

查看文件有哪些命令

列举几个常用的Linux命令
    | 列出文件列表:ls【参数 -a -l】
    | 创建目录和移除目录:mkdir rmdir
    | 用于显示文件后几行内容:tail,例如: tail -n 1000:显示最后1000行
    | 打包:tar -xvf
    | 打包并压缩:tar -zcvf
    | 查找字符串:grep
    | 显示当前所在目录:
    | ​pwd创建空文件:touch
    | 编辑器:vim vi
    | 

你平时是怎么查看日志的?
    tail

    head

    cat

    more

    sed

    less
        -

    一般查日志配合应用的其他命令

计算机网络篇

Forward 和 Redirect 的区别?
    | 浏览器 URL 地址:
    | Forward 是服务器内部的重定向,服务器内部请求某个 servlet,然后获取
    | 响应的内容,浏览器的 URL 地址是不会变化的;
    | Redirect 是客户端请求服务器,然后服务器给客户端返回了一个 302 状态码和新的 location,客户端重新发起 HTTP 请求,服务器给客户端响应 location 对应的 URL 地址,浏览器的 URL 地址发生了变化。
    | 数据的共享:
    | Forward 是服务器内部的重定向,request 在整个内部重定向过程中是不变的
    | request 中的信息在 servlet 间是共享的。
    | Redirect 发起了两次 HTTP 请求分别使用不同的request。
    | 请求的次数:
    | ​Forward 只有一次请求;
    | ​Redirect 有两次请求。

Get 和 Post 请求有哪些区别?
    | 用途:
    | get 请求用来从服务器获取资源
    | post 请求用来向服务器提交数据
    | 表单数据的提交方式:
    | get 请求直接将表单数据以name1=value1&name2=value2的形式拼接到 URL 上(http://www.baidu.com/action?name1=value1&name2=value2),多个参数参数值需要用 & 连接起来并且用?拼接到 action 后面;
    | post 请求将表单数据放到请求头或者请求的消息体中。
    | 传输数据的大小限制:
    | get 请求传输的数据受到 URL 长度的限制,而 URL 长度是由浏览器决定的;
    | post 请求传输数据的大小理论上来说是没有限制的。
    | 参数的编码:
    | get 请求的参数会在地址栏明文显示,使用 URL 编码的文本格式传递参数;
    | post 请求使用二进制数据多重编码传递参数。
    | 缓存:
    | get 请求可以被浏览器缓存被收藏为标签;
    | post 请求不会被缓存也不能被收藏为标签。
    | 

HTTP 响应码有哪些?分别代表什么含义?
    | 200:成功,Web 服务器成功处理了客户端的请求。
    | 301:永久重定向,当客户端请求一个网址的时候,Web 服务器会将当前请求重定向到另一个网址,搜索引擎会抓取重定向后网页的内容并且将旧的网址替换为重定向后的网址。
    | 302:临时重定向,搜索引擎会抓取重定向后网页的内容而保留旧的网址,因为搜索引擎认为重定向后的网址是暂时的。
    | 400:客户端请求错误,多为参数不合法导致 Web 服务器验参失败。
    | 404:未找到,Web 服务器找不到资源。
    | 500:Web 服务器错误,服务器处理客户端请求的时候发生错误。
    | 503:服务不可用,服务器停机。
    | 504:网关超时。

如何理解HTTP协议是无状态的?
    | HTTP协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。
    | 也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。
    | HTTP是一个无状态的面向连接的协议,
    | 无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
    | 

说一下HTTP和HTTPS的区别
    | 端口不同:
    | HTTP和HTTPS的连接方式不同,没用的端口也不一样,
    | HTTP是80,
    | HTTPS用的是443;
    | 消耗资源:
    | 和HTTP相比,HTTPS通信会因为加解密的处理消耗更多的CPU和内存资源。
    | 开销:
    | ​HTTPS通信需要证书,这类证书通常需要向认证机构申请或者付费购买。
    | 

HTTP1.0、HTTP1.1、HTTP2.0的关系和区别
    图示
    HTTP1.0
    | 1,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接(无连接),
    | 2,服务器不跟踪每个客户端也不记录过去的请求(无状态)。
    | 3,HTTP/1.0中默认使用Connection: close。
    | 
    HTTP1.1
    | 1,在HTTP/1.1中已经默认使用Connection: keep-alive,避免了连接建立和释放的开销,
    | ​2,但服务器必须按照客户端请求的先后顺序依次回送相应的结果,以保证客户端能够区分出每次请求的响应内容。
    | 3,通过Content-Length字段来判断当前请求的数据是否已经全部接收。
    | ​4,不允许同时存在两个并行的响应。
    | 
    HTTP2.0
    | HTTP 2.0 引入二进制数据帧和流的概念,
    | 1,其中帧对数据进行顺序标识,这样浏览器收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。
    | 2,同样是因为有了序列,服务器就可以并行的传输数据,这就是流所做的事情。
    | 
    | 流(stream)
    | 已建立连接上的双向字节流消息与逻辑消息对应的完整的一系列数据帧
    | 帧
    | HTTP2.0通信的最小单位,
    | 每个帧包含帧头部,至少也会标识出当前帧所属的流(stream id)。
    | 
        特性
        | 多路复用
        | 1、所有的HTTP2.0通信都在一个TCP连接上完成,
        | 这个连接可以承载任意数量的双向数据流。
        | 2、每个数据流以消息的形式发送,而消息由一或多个帧组成。
        | 这些帧可以乱序发送,然后再根据每个帧头部的流标识符(stream id)重新组装。
        | 
        | 举个例子,每个请求是一个数据流,数据流以消息的方式发送,而消息又分为多个帧,帧头部记录着stream id用来标识所属的数据流,不同属的帧可以在连接中随机混杂在一起。接收方可以根据stream id将帧再归属到各自不同的请求当中去。
        | 
        | 3、另外,多路复用(连接共享)可能会导致关键请求被阻塞。
        | HTTP2.0里每个数据流都可以设置优先级和依赖,
        | 优先级高的数据流会被服务器优先处理和返回给客户端,
        | 数据流还可以依赖其他的子数据流。
        | 4、可见,HTTP2.0实现了真正的并行传输,
        | 它能够在一个TCP上进行任意数量HTTP请求。
        | 而这个强大的功能则是基于“二进制分帧”的特性。
        | 
        | 头部压缩
        | 在HTTP1.x中,头部元数据都是以纯文本的形式发送的,
        | 通常会给每个请求增加500~800字节的负荷。
        | HTTP2.0使用encoder来减少需要传输的header大小,
        | 通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
        | 高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟。
        | 
        | 服务器推送
        | 服务器除了对最初请求的响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确的请求。
        | ​
        | 


说说HTTP、TCP、Socket 的关系是什么?
    HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。
    | HTTP 本身就是一个协议,
    | 是从 Web 服务器传输超文本到本地浏览器的传送协议。
    | HTTP的长连接和短连接本质上是TCP长连接和短连接。
    | 
    | TCP/IP 代表传输控制协议/网际协议,
    | 指的是一系列协议族。
    | IP协议主要解决网络路由和寻址问题;
    | TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。
    | 
    | Socket 是 TCP/IP 网络的 API ,
    | 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在Socket 接口后面。
    | 对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议。
    | 总结
    | 需要 IP 协议来连接网络
    | TCP 是一种允许我们安全传输数据的机制,
    | ​使用 TCP 协议来传输数据的 HTTP 是 Web 服务器和客户端使用的特殊协议。
    | HTTP 基于 TCP 协议,所以可以使用 Socket 去建立一个 TCP 连接。

说说TCP与UDP的区别,以及各自的优缺点
    | 1、
    | TCP面向连接(如打电话要先拨号建立连接):
    | UDP是无连接的,即发送数据之前不需要建立连接。
    | 2、
    | TCP提供可靠的服务。
    | 也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;
    | tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
    | UDP尽最大努力交付,即不保证可靠交付。
    | 3、
    | UDP具有较好的实时性,工作效率比TCP高,
    | 适用于对高速传输和实时性有较高的通信或广播通信。
    | 4,
    | 每一条TCP连接只能是点到点的;
    | UDP支持一对一,一对多,多对一和多对多的交互通信。
    | 5,
    | TCP对系统资源要求较多,
    | UDP对系统资源要求较少。
    | 

说说TCP 如何保证可靠性的?
    | 序列号和确认号机制:
    | TCP 发送端发送数据包的时候会选择一个 seq 序列号,接收端收到数据包后会检测数据包的完整性,如果检测通过会响应一个 ack 确认号表示收到了数据包。
    | 超时重发机制:
    | TCP 发送端发送了数据包后会启动一个定时器,如果一定时间没有收到接受端的确认后,将会重新发送该数据包。
    | 对乱序数据包重新排序:
    | 从 IP 网络层传输到 TCP 层的数据包可能会乱序,TCP 层会对数据包重新排序再发给应用层。
    | 丢弃重复数据:
    | 从 IP 网络层传输到 TCP 层的数据包可能会重复,TCP 层会丢弃重复的数据包。
    | 流量控制:
    | TCP 发送端和接收端都有一个固定大小的缓冲空间,
    | 为了防止发送端发送数据的速度太快导致接收端缓冲区溢出,发送端只能发送接收端可以接纳的数据,为了达到这种控制效果,TCP 用了流量控制协议(可变大小的滑动窗口协议)来实现。
    | 

TCP/Http 长连接和短连接
    说一下HTTP的长连接与短连接的区别
        | HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
        | 
        | 短连接
        | 在HTTP/1.0中默认使用短链接,
        | 也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
        | 如果客户端访问的某个HTML或其他类型的Web资源,如JavaScript文件、图像文件、CSS文件等。当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话.
        | 长连接
        | 从HTTP/1.1起,默认使用长连接,用以保持连接特性。
        | 使用长连接的HTTP协议,会在响应头加入这行代码: Connection:keep-alive
        | 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭。如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。
        | 
        | Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
        | 实现长连接需要客户端和服务端都支持长连接。
        | 

    长连接和短连接的优缺点?
        长连接 
        | 1,长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。
        | 对于频繁请求资源的客户来说,较适用长连接。
        | 
        | 1,不过这里存在一个问题,存活功能的探测周期太长,
        | 还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。
        | 2,servler 扛不住
        | 在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,
        | 这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;
        | 如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。
        短连接
        | 短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。
        | ​但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。

    说说长连接短连接的操作过程
        | 短连接的操作步骤是:
        | 建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连
        | 接长连接的操作步骤是:
        | ​建立连接——数据传输...(保持连接)...数据传输——关闭连接
        | 


TCP 为什么要三次握手,两次不行吗?为什么?
    - 一次连接请求和两次请求收到确认
    | TCP 客户端和服务端建立连接需要三次握手,
    | 
    | 首先服务端需要开启监听,等待客户端的连接请求,
    | 这个时候服务端处于“收听”状态;
    | 1,客户端向服务端发起连接,选择seq=x的初始序列号,此时客户端处于“同步已发送”的状态;
    | 2,服务端收到客户端的连接请求,同意连接并向客户端发送确认,
    | 确认号是ack=x+1,表示客户端可以发送下一个数据包序号从x+1开始,
    | 同时选择seq=y的初始序列号,此时服务端处于“同步收到”状态;
    | 客户端收到服务端的确认后,向服务端发送确认信息,
    | 确认号是ack=y+1表示服务端可以发送下一个数据包序号从y+1开始,
    | 此时客户端处于“已建立连接”的状态;
    | 服务端收到客户端的确认后,也进入“已建立连接”的状态。
    | 
    | 握手过程中传送的包里不包含数据,
    | 三次握手完毕后,客户端与服务器才正式开始传送数据。
    | ​理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
    | ​
    图示
    | 从三次握手的过程可以看出如果只有两次握手,
    | ​那么客户端的起始序列号可以确认,服务端的起始序列号将得不到确认。

TCP四次挥手
    图示
    四次挥手过程   两次关闭请求和两次请求收到确认
    | 与建立连接的“三次握手”类似,断开一个TCP连接则需要“四次握手”。
    | 第一次挥手:
    | 主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),
    | 但是,此时主动关闭方还可以接受数据。
    | 第二次挥手:
    | 被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
    | 第三次挥手:
    | 被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,
    | 也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
    | 第四次挥手:
    | ​主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,
    | ​至此,完成四次挥手。
    | 

说一下 TCP 粘包是怎么产生的?怎么解决粘包问题的?
    现象  图示

    原理
    |  TCP 传输数据基于字节流,
    | 从应用层到 TCP 传输层的多个数据包是一连串的字节流是没有边界的,而且 TCP 首部并没有记录数据包的长度,
    | 所以 TCP 传输数据的时候可能会发送粘包和拆包的问题;
    | 
    | 而 UDP 是基于数据报传输数据的,
    | UDP 首部也记录了数据报的长度,可以轻易的区分出不同的数据包的边界。
    | 
    产生的原因
    | 拆包
    | 1,TCP 发送缓冲区剩余空间不足以发送一个完整的数据包,将发生拆包;
    | 2,要发送的数据超过了最大报文长度的限制,TCP 传输数据时进行拆包;
    | 粘包
    | 1,要发送的数据包小于 TCP 发送缓冲区剩余空间,TCP 将多个数据包写满发送缓冲区一次发送出去,将发生粘包;
    | 2,接收端没有及时读取 TCP 发送缓冲区中的数据包,将会发生粘包。
    | 
    粘包拆包的解决方法:
    | 1,发送端给数据包添加首部,首部中添加数据包的长度属性,
    | 这样接收端通过首部中的长度字段就可以知道数据包的实际长度啦;
    | 2,针对发送的数据包小于缓冲区大小的情况,发送端可以将不同的数据包规定成同样的长度,不足这个长度的补充 0
    | 接收端从缓冲区读取固定的长度数据这样就可以区分不同的数据包;
    | 3,发送端通过给不同的数据包添加间隔符合确定边界
    | ​接收端通过这个间隔符合就可以区分不同的数据包。
    | 

说说TCP/IP四层网络模型
    图示
    | TCP/IP分层模型(TCP/IP Layening Model)
    | ​被称作因特网分层模型(Internet Layering Model)、因特网参考模型(Internet Reference Model)。
    | 
    --功能
    | 第一层网络接口层
    | 网络接口层包括用于协作IP数据在已有网络介质上传输的协议。
    | 协议:ARP,RARP
    | 第二层网间层
    | 网间层对应于OSI七层参考模型的网络层。
    | 负责数据的包装、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。
    | 本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),ICMP协议。
    | 第三层传输层
    | 传输层对应于OSI七层参考模型的传输层,
    | 它提供两种端到端的通信服务。
    | 其中TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务,
    | UDP协议(UseDatagram Protocol)提供不可靠的用户数据报服务。
    | 第四层应用层
    | 应用层对应于OSI七层参考模型的应用层和表达层。
    | 因特网的应用层协议包括
    | ​Finger、Whois、FTP(文件传输协议)、Gopher、HTTP(超文本传输协议)、
    | ​Telent(远程终端协议)、SMTP(简单邮件传送协议)、IRC(因特网中继会话)、
    | ​NNTP(网络新闻传输协议)等。
    | 

OSI 的七层模型都有哪些?
    图示

    OSI(Open System Interconnection)
    | 开放系统互连参考模型
    | ​是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系。
        应用层
        | 各种应用程序协议,
        | ​比如 HTTP、HTTPS、FTP、SOCKS 安全套接字协议、DNS 域名系统、GDP 网关发现协议等等。
        表示层
        | 加密解密、转换翻译、压缩解压缩,
        | ​比如 LPP 轻量级表示协议。
        会话层
        | 不同机器上的用户建立和管理会话,
        | ​比如 SSL 安全套接字层协议、TLS 传输层安全协议、RPC 远程过程调用协议等等。
        传输层
        | 接受上一层的数据,在必要的时候对数据进行分割,并将这些数据交给网络层,保证这些数据段有效到达对端,
        | ​比如 TCP 传输控制协议、UDP 数据报协议。
        网络层
        | 控制子网的运行:逻辑编址、分组传输、路由选择,
        | ​比如 IP、IPV6、SLIP 等等。
        数据链路层:
        | 物理寻址,同时将原始比特流转变为逻辑传输路线,
        | ​比如 XTP 压缩传输协议、PPTP 点对点隧道协议等等。
        物理层
        | 机械、电子、定时接口通信信道上的原始比特流传输,
        | ​比如 IEEE802.2 等等。

    OSI这样分层有什么好处?
    | 1. 人们可以很容易的讨论和学习协议的规范细节。
    | 2. 层间的标准接口方便了工程模块化。
    | 3. 创建了一个更好的互连环境。
    | 4. 降低了复杂度,使程序更容易修改,产品开发的速度更快。
    | 5. 每层利用紧邻的下层服务,更容易记住个层的功能。
    | 

说说域名解析详细过程?
    | 1. 浏览器访问www.baidu.com,询问本地 DNS 服务器是否缓存了该网址解析后的 IP 地址。
    | 2. 如果本地 DNS 服务器没有缓存的话,就去 root-servers. net 根服务器查询该网址对应的 IP 地址。
    | 3. 根服务器返回顶级域名服务器的网址 gtld-servers. net,然后本地 DNS 服务器去顶级域名服务器查询该网址对应的 IP 地址。
    | 4. 顶级域名服务器返回www.baidu.com主区域服务器的地址,然后本地 DNS 服务器去www.baidu.com主区域服务器查询此域名对应的 IP 地址。
    | 5. 本地 DNS 服务器拿到www.baidu.com解析后的 IP 地址后,缓存起来以便备查,然后把解析后的 IP 地址返回给浏览器。
    | 

 IP 地址分为几类,每类都代表什么,私网是哪些?
    公共地址 和 私有地址
    | ​公共地址可以在外网中随意访问,
    | ​私有地址只能在内网访问只有通过代理服务器才可以和外网通信。
    | 
    公共地址:
    私有地址
    其他

简单一次完整的 HTTP 请求所经历的步骤?
    概念
    | 
    | 
    图示

设计模式 23种

https://www.runoob.com/design-pattern/design-pattern-tutorial.html  

常用数据结构

https://www.runoob.com/data-structures/data-structures-tutorial.html

十大排序算法

https://www.runoob.com/w3cnote/ten-sorting-algorithm.html

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
3996 0
怎么设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程
6330 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
2156 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
3951 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
4962 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
5664 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
10711 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
3795 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
16265 0
1
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载