Java面试宝典-1

一、Java 基础

JDK 和 JRE 有什么区别?

JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK

== 和 equals 的区别是什么?

== 的作用

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

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

总结:
所有比较是否相等时,都是用equals 并且在对常量相比较时,把常量写在前面,因为使用object的equals object可能为null 则空指针

【代码示例】

String x = "string";
String y = "string";
String z = new String("string");
System. out. println(x==y); // true
System. out. println(x==z); // false
System. out. println(x. equals(y)); // true
System. out. println(x. equals(z)); // true

【代码解读】

因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String() 方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

不对,两个对象的 hashCode() 相同,equals() 不一定 true。

String str1 = "通话";
String str2 = "重地";
System. out. println(String. format("str1:%d | str2:%d",  str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));

【代码结果】

str1:1179395 | str2:1179395
     
false

【代码解读】
很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等

final 在 java 中有什么作用?

  1. 被fifinal修饰的类不可以被继承
  2. 被fifinal修饰的方法不可以被重写
  3. 被fifinal修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
  4. 被fifinal修饰的方法,JVM会尝试将其内联,以提高运行效率
  5. 被fifinal修饰的常量,在编译阶段会存入常量池中.

java 中的 Math.round(-1.5) 等于多少?

等于 -1,Math. round 四舍五入大于 0. 5 向上取整的。

String 属于基础的数据类型吗?

String 不属于基础类型,基础类型有 8 种:byte、boolear、char、short、int、float、long、double,String 属于对象。

java 中操作字符串都有哪些类?它们之间有什么区别?

操作字符串的类有:String、StringBuffer、StringBuilder。

String 和 StringBuffer、StringBuilder 的区别在于 :

String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,

而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

线程是否安全 性能 推荐使用场景
StringBuffer 线程安全 多线程环境
StringBuilder 非线程安全 单线程环境

String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

如何将字符串反转?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

【代码示例】

**// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba
**

String 类的常用方法都有那些

indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

抽象类必须要有抽象方法吗?

不需要,抽象类不一定非要有抽象方法。

【代码示例】

abstract class Cat {
    public static void sayHi() {
        System. out. println("hi~");
    }
}

普通类和抽象类有哪些区别?

  • 普通类不能包含抽象方法,抽象类可以包含抽象方法。
  • 抽象类不能直接实例化,普通类可以直接实例化。

抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

接口和抽象类有什么区别?

  • 抽象类:
  • 抽象方法,只有行为的概念,没有具体的行为实现。使用abstract关键字修饰,没有方法体。子类必须重写这些抽象方法
  • 包含抽象方法的类,一定是抽象类。
  • 抽象类只能被继承,一个类只能继承一个抽象类。
    接口:
  • 全部的方法都是抽象方法,属性都是常量
  • 不能实例化,可以定义变量。
  • 接口变量可以引用具体实现类的实例
  • 接口只能被实现,一个具体类实现接口,必须实现全部的抽象方法
  • 接口之间可以多实现
  • 一个具体类可以实现多个接口,实现多继承现象

java 中 IO 流分为几种?

  • 按功能来分:输入流(input)、输出流(output)

  • 按类型来分:字节流和字符流。

  • 字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据

BIO、NIO、AIO 有什么区别?

  • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。

  • NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

  • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO的操作基于事件和回调机制。

Files的常用方法都有哪些?

  • Files. exists():检测文件路径是否存在。
  • Files. createFile():创建文件。
  • Files. createDirectory():创建文件夹。
  • Files. delete():删除一个文件或目录。
  • Files. copy():复制文件。
  • Files. move():移动文件。
  • Files. size():查看文件个数。
  • Files. read():读取文件。
  • Files. write():写入文件。

二、容器

java 容器都有哪些?

  • Collection

    • List

      • ArrayList

      • LinkedList

      • Vector

      • Stack

    • Set

      • HashSet

      • LinkedHashSet

      • TreeSet

    • Map

      • HashMap

        • LindedHashMap
      • TreeMap

      • ConcurrentHashMap

      • Hashtable

Collection 和 Collections 有什么区别?

  • Collection 是一个集合接口,提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。
  • Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法: Collections.
    sort(list)

List、Set、Map 之间的区别是什么?

  • List、Set、Map 的区别主要体现在两个方面:元素是否有序、是否允许元素重复。

  • 结构特点

  • List 和 Set 是存储单列数据的集合, Map 是存储键和值这样的双列数据的集合; List 中存储的数据是有顺序,并且允许重复; Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的, Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制,所以对于用户来说 set 中的元素还是无序的);

  • 实现类

  • List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地 址的同时还存储下一个元素的地址。链表增删快,查找慢; ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除; Vector:基于数组实现,线程安全的,效率低)。
    Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null值和 null键; HashTable:线程安全,低效,不支持 null 值和 null 键; LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序; SortMap 接口: TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。

  • Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法; LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)

  • 区别
    0 List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素; Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复; Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现Java.util.Comparator接口来自定义排序方式。

  • 具体可以参考这篇文章:https://www.cnblogs.com/IvesHe/p/6108933.html

HashMap 和 Hashtable 有什么区别?

  • 存储:HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。

  • 线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。

  • 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap
    替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

如何决定使用 HashMap 还是 TreeMap?

对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择

说一下 HashMap 的实现原理?

HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。

说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList增删操作要影响数组内的其他数据的下标。
  • 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

如何实现数组和 List 之间的转换?

  • 数组转 List:使用 Arrays. asList(array) 进行转换。

  • List 转数组:使用 List 自带的 toArray() 方法

  • 【代码示例】

// list to array
List list = new ArrayList();
list. add("王磊");
list. add("的博客");
list. toArray();
// array to list
String[] array = new String[]{"王磊","的博客"};
Arrays. asList(array);

ArrayList 和 Vector 的区别是什么?

  • 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
  • 性能:ArrayList 在性能方面要优于 Vector。
  • 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而ArrayList 只会增加 50%。

Array 和 ArrayList 有何区别?

  • Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。

  • Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。

  • Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有ArrayList 有

在 Queue 中 poll()和 remove()有什么区别?

  • 相同点:都是返回第一个元素,并在队列中删除返回的对象。

  • 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常

Queue queue = new LinkedList();
queue. offer("string"); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());

哪些集合类是线程安全的?

Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。

迭代器 Iterator 是什么?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Java面试宝典-1_第1张图片
Iterator 怎么使用?有什么特点?

【代码示例】

List list = new ArrayList<>();
Iterator it = list. iterator();
while(it. hasNext()){
  String obj = it. next();
  System. out. println(obj);
}

Iterator 的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出ConcurrentModificationException 异常。

Iterator 和 ListIterator 有什么区别?

  • Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
  • Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
  • ListIterator 从 Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

怎么确保一个集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

List list = new ArrayList<>();
list. add("x");
Collection clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());

三、多线程

并行和并发有什么区别?

  • 并行:一个处理器同时处理多个任务。
  • 并发:多个处理器或多核处理器同时处理多个不同的任务。

线程和进程的区别?

一个程序下至少有一个进程,一个进程下至少有一个线程,一个进程下也可以有多个线程来增加程序的执行速度。

守护线程是什么?

守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。

创建线程有哪几种方式?

  • 创建线程有三种方式:
    • 继承 Thread 重新 run 方法;
    • 实现 Runnable 接口;
    • 实现 Callable 接口。

说一下 runnable 和 callable 有什么区别?

runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

线程有哪些状态?

线程的状态:

  • NEW 尚未启动
  • RUNNABLE 正在执行中
  • BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
  • WAITING 永久等待状态
  • TIMED_WAITING 等待指定的时间重新被唤醒的状态
  • TERMINATED 执行完成
新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值 
 
就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。 Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。 
 
运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
 
阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种: 
等待阻塞(o.wait->等待对列): 
运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。 
同步阻塞(lock->锁池) 
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。 
其他阻塞(sleep/join) 
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。 
 
线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
正常结束
1. run()或 call()方法执行完成,线程正常结束。异常结束
2. 线程抛出一个未捕获的 Exception 或 Error。调用 stop
3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

sleep() 和 wait() 有什么区别?

  • 类的不同:sleep() 来自 Thread,wait() 来自 Object。

  • 释放锁:sleep() 不释放锁;wait() 释放锁。

  • 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒

notify()和 notifyAll()有什么区别?

notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

线程的 run()和 start()有什么区别?

start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。

创建线程池有哪几种方式?

线程池创建有七种方式,最核心的是最后一种:

  • newSingleThreadExecutor():它的特点在于工作线程数目被限制为1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
  • newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
  • newFixedThreadPool(intnThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目nThreads;
  • newSingleThreadScheduledExecutor():创建单线程池,返回ScheduledExecutorService,可以进行定时或周期性的工作调度;
  • newScheduledThreadPool(intcorePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个
    ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
  • newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
  • ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装

线程池都有哪些状态?

  • RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
  • TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法
    terminated()。
  • TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

线程池中 submit()和 execute()方法有什么区别?

  • execute():只能执行 Runnable 类型的任务。

  • submit():可以执行 Runnable 和 Callable 类型的任务。

  • Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返回值。

在 java 程序中怎么保证多线程的运行安全?

  • 方法一:使用安全类,比如 Java. util. concurrent 下的类。

  • 方法二:使用自动锁 synchronized。

  • 方法三:使用手动锁 Lock。

【手动锁代码示例】

Lock lock = new ReentrantLock();
lock. lock();
try {
    System. out. println("获得锁");
} catch (Exception e) {
    // TODO: handle exception
} finally {
    System. out. println("释放锁");
    lock. unlock();
}

多线程锁的升级原理是什么?

在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,JVM 让其持有偏向锁,并将threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否尤其线程 id 一致,如果一致则可以直接使用,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,不会堵塞,执行一定次数之后就会升级为重量级锁,进入堵塞,整个过程就是锁升级的原理。

锁的升级的目的:在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,减低了锁带来的性能消耗。

锁升级是为了减低了锁带来的性能消耗。

什么是死锁?

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

怎么防止死锁?

  • 尽量使用 tryLock(long timeout, TimeUnitunit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
  • 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
  • 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
  • 尽量减少同步的代码块。

ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

ThreadLocal 的经典使用场景是数据库连接和 session 管理等。

说一下 synchronized 底层实现原理?

synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

synchronized 和 volatile 的区别是什么?

  • volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
  • volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
  • volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

synchronized 和 Lock 有什么区别?

  • synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

  • synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

synchronized 和 ReentrantLock 区别是什么?

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

主要区别如下:

ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。
volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。

说一下 atomic 的原理?

atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。


四、反射

什么是反射?

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

什么是 java 序列化?什么情况下需要序列化?

Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来。

以下情况需要使用 Java 序列化:

  • 想把的内存中的对象状态保存到一个文件中或者数据库中时候;
  • 想用套接字在网络上传送对象的时候;
  • 想通过RMI(远程方法调用)传输对象的时候。

动态代理是什么?有哪些应用?

动态代理是运行时动态生成代理类。

动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。

怎么实现动态代理?

JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。


五、对象拷贝

为什么要使用克隆?

克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。

如何实现对象克隆?

  • 实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
  • 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

深拷贝和浅拷贝区别是什么?

  • 浅克隆:当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
  • 深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将复制。

六、Java Web

jsp 和 servlet 有什么区别?

  • JSP 是 servlet 技术的扩展,本质上就是 servlet 的简易方式。servlet 和 JSP最主要的不同点在于,servlet 的应用逻辑是在 Java 文件中,并且完全从表示层中的 html 里分离开来,而 JSP 的情况是Java 和 html 可以组合成一个扩展名为 JSP 的文件。JSP 侧重于视图,servlet 主要用于控制逻辑。

jsp 有哪些内置对象?作用分别是什么?

JSP 有 9 大内置对象:

request:封装客户端的请求,其中包含来自 get 或 post 请求的参数;
response:封装服务器对客户端的响应;
pageContext:通过该对象可以获取其他对象;
session:封装用户会话的对象;
application:封装服务器运行环境的对象;
out:输出服务器响应的输出流对象;
config:Web 应用的配置对象;
page:JSP 页面本身(相当于 Java 程序中的 this);
exception:封装页面抛出异常的对象。

说一下 jsp 的 4 种作用域?

  • page:代表与一个页面相关的对象和属性。
  • request:代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web组件;需要在页面显示的临时数据可以置于此作用域。
  • session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。
  • application:代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web应用程序,包括多个页面、请求和会话的一个全局作用域。

session 和 cookie 有什么区别?

  • 存储位置不同:session 存储在服务器端;cookie 存储在浏览器端。

  • 安全性不同:cookie 安全性一般,在浏览器存储,可以被伪造和修改。

  • 容量和个数限制:cookie 有容量限制,每个站点下的 cookie 也有个数限制。

  • 存储的多样性:session 可以存储在 Redis 中、数据库中、应用程序中;而 cookie 只能存储在浏览器中。

【分享】

  • cookie 的总数量没有限制,但是每个域名的COOKIE 数量和每个COOKIE 的大小是有限制的!
  • IE 每个域名限制为50 个。
  • Firefox 每个域名cookie 限制为50 个。
  • Opera 每个域名cookie 限制为30 个。
  • Safari/webkit 貌似没有cookie 限制。但是假如cookie 很多,则会使header大小超过服务器的处理的限制,导致错误发生。
  • 不同浏览器间每个cookie 文件大小也不同
  • Firefox 和safari 是4097 个字节,包括名(name)、值(value)和等号。
  • Opera 是4096 个字节,包括:名(name)、值(value)和等号。
  • IE 是4095 个字节,包括:名(name)、值(value)和等号。

说一下 session 的工作原理?

session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。

如果客户端禁止 cookie 能实现 session 还能用吗?

可以用,session 只是依赖 cookie 存储 sessionid,如果 cookie 被禁用了,可以使用 url 中添加 sessionid 的方式保证 session 能正常使用。

spring mvc 和 struts 的区别是什么?

  • 拦截级别:struts2 是类级别的拦截;spring mvc 是方法级别的拦截。
  • 数据独立性:spring mvc 的方法之间基本上独立的,独享 request 和 response数据,请求数据通过参数获取,处理结果通过 ModelMap 交回给框架,方法之间不共享变量;而 struts2虽然方法之间也是独立的,但其所有 action 变量是共享的,这不会影响程序运行,却给我们编码和读程序时带来了一定的麻烦。
  • 拦截机制:struts2 有以自己的 interceptor 机制,spring mvc 用的是独立的 aop方式,这样导致struts2 的配置文件量比 spring mvc 大。
  • 对 ajax 的支持:spring mvc 集成了ajax,所有 ajax 使用很方便,只需要一个注解 @ResponseBody就可以实现了;而 struts2 一般需要安装插件或者自己写代码才行。

如何避免 sql 注入?

  • 使用预处理 PreparedStatement。

  • 使用正则表达式过滤掉字符中的特殊字符。

什么是 XSS 攻击,如何避免?

XSS 攻击:即跨站脚本攻击,它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码(css 代码、Javascript 代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户 cookie、破坏页面结构、重定向到其他网站等。

预防 XSS 的核心是必须对输入的数据做过滤处理。

什么是 CSRF 攻击,如何避免?

CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账等。

防御手段:

验证请求来源地址;
关键操作添加验证码;
在请求地址添加 token 并验证。

七、异常

throw 和 throws 的区别?

  • throw:是真实抛出一个异常。
  • throws:是声明可能会抛出一个异常。

final、finally、finalize 有什么区别?

  • final:是修饰符,如果修饰类,此类不能被继承;如果修饰方法和变量,则表示此方法和此变量不能在被改变,只能使用。

  • finally:是 try{} catch{} finally{} 最后一部分,表示不论发生任何情况都会执行,finally部分可以省略,但如果 finally 部分存在,则一定会执行 finally 里面的代码。

  • finalize: 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法。

try-catch-finally 中哪个部分可以省略?

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。

常见的异常类有哪些?

  • NullPointerException 空指针异常
  • ClassNotFoundException 指定类不存在
  • NumberFormatException 字符串转换为数字异常
  • IndexOutOfBoundsException 数组下标越界异常
  • ClassCastException 数据类型转换异常
  • FileNotFoundException 文件未找到异常
  • NoSuchMethodException 方法不存在异常
  • IOException IO 异常
  • SocketException Socket 异常

八、网络

** http 响应码 301 和 302 代表的是什么?有什么区别?**

  • 301:永久重定向。
  • 302:暂时重定向。
  • 它们的区别是,301 对搜索引擎优化(SEO)更加有利;302 有被提示为网络拦截的风险。

【分享】

  • 其他的一些常见的响应码可以参考之前的一篇文章:https://blog.csdn.net/u011665991/article/details/82458808

forward 和 redirect 的区别?

forward 是转发 和 redirect 是重定向:

地址栏 url 显示:foward url 不会发生改变,redirect url 会发生改变;
数据共享:forward 可以共享 request 里的数据,redirect 不能共享;
效率:forward 比 redirect 效率高。

简述 tcp 和 udp的区别?

tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输,而 udp 则常被用于让广播和细节控制交给应用的通信传输。

两者的区别大致如下:

tcp 面向连接,udp 面向非连接即发送数据前不需要建立链接;
tcp 提供可靠的服务(数据传输),udp 无法保证;
tcp 面向字节流,udp 面向报文;
tcp 数据传输慢,udp 数据传输快;

tcp 为什么要三次握手,两次不行吗?为什么?

如果采用两次握手,那么只要服务器发出确认数据包就会建立连接,但由于客户端此时并未响应服务器端的请求,那此时服务器端就会一直在等待客户端,这样服务器端就白白浪费了一定的资源。若采用三次握手,服务器端没有收到来自客户端的再此确认,则就会知道客户端并没有要求建立请求,就不会浪费服务器的资源

说一下 tcp 粘包是怎么产生的?

tcp 粘包可能发生在发送端或者接收端,分别来看两端各种产生粘包的原因:

  • 发送端粘包:发送端需要等缓冲区满才发送出去,造成粘包;
  • 接收方粘包:接收方不及时接收缓冲区的包,造成多个包接收。

OSI 的七层模型都有哪些?

  • 物理层:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。

  • 数据链路层:负责建立和管理节点间的链路。

  • 网络层:通过路由选择算法,为报文或分组通过通信子网选择最适当的路径。

  • 传输层:向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。

  • 会话层:向两个实体的表示层提供建立和使用连接的方法。

  • 表示层:处理用户信息的表示问题,如编码、数据格式转换和加密解密等。

  • 应用层:直接向用户提供服务,完成用户希望在网络上完成的各种工作。

get 和 post 请求有哪些区别?

  • get 请求会被浏览器主动缓存,而 post 不会。

  • GET使用URL或Cookie传参,而POST将数据放在BODY中。

  • get 传递参数有大小限制,而 post 没有。

  • post 参数传输更安全,get 的参数会明文限制在 url 上,post 不会。

说到get 和 post 请求有哪些区别,大多数同学都会想到这几个标准答案,但是这三个答案是否是正确的还是有待商榷。

1. GET使用URL或Cookie传参,而POST将数据放在BODY中

GET和POST是由HTTP协议定义的。在HTTP协议中,Method和Data(URL, Body, Header)是正交的两个概念,也就是说,使用哪个Method与应用层的数据如何传输是没有相互关系的。

HTTP没有要求,如果Method是POST数据就要放在BODY中。也没有要求,如果Method是GET,数据(参数)就一定要放在URL中而不能放在BODY中。

那么,网上流传甚广的这个说法是从何而来的呢?我在HTML标准中,找到了相似的描述。这和网上流传的说法一致。但是这只是HTML标准对HTTP协议的用法的约定。怎么能当成GET和POST的区别呢?

而且,现代的Web Server都是支持GET中包含BODY这样的请求。虽然这种请求不可能从浏览器发出,但是现在的Web Server又不是只给浏览器用,已经完全地超出了HTML服务器的范畴了。

2. GET方式提交的数据有长度限制,则POST的数据则可以非常大

先说结论:HTTP协议对GET和POST都没有对长度的限制。HTTP协议明确地指出了,HTTP头和Body都没有长度的要求。

首先是"GET方式提交的数据有长度限制",如果我们使用GET通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。

注意这个限制是整个URL长度,而不仅仅是你的参数值数据长度。

POST也是一样,POST是没有大小限制的,HTTP协议规范也没有对POST数据进行大小限制,起限制作用的是服务器的处理程序的处理能力。

当然,我们常说GET的URL会有长度上的限制这个说法是怎么回事呢?虽然这个不是GET和POST的本质区别,但是我们也可以说说导致URL长度限制的两方面的原因:

  • 浏览器。早期的浏览器会对URL长度做限制。而现在的具体限制是怎么样的,我自己没有亲测过,就不复制网上的说法啦。

  • 服务器。URL长了,对服务器处理也是一种负担。原本一个会话就没有多少数据,现在如果有人恶意地构造几个M大小的URL,并不停地访问你的服务器。服务器的最大并发数显然会下降。另一种攻击方式是,告诉服务器Content-Length是一个很大的数,然后只给服务器发一点儿数据,服务器你就傻等着去吧。哪怕你有超时设置,这种故意的次次访问超时也能让服务器吃不了兜着走。有鉴于此,多数服务器出于安全啦、稳定啦方面的考虑,会给URL长度加限制。但是这个限制是针对所有HTTP请求的,与GET、POST没有关系。

  • POST比GET安全,因为数据在地址栏上不可见

  • 这个说法其实也是基于上面的1,2两点的基础上来说的,我觉得没什么问题,但是需要明白为什么使用GET在地址栏上就不安全了,以及还有没有其他原因说明“POST比GET安全”。

  • 通过GET提交数据,用户名和密码将明文出现在URL上,因为登录页面有可能被浏览器缓存,其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-siterequest forgery攻击。

三、我的理解

“1. GET使用URL或Cookie传参,而POST将数据放在BODY中”,这个是因为HTTP协议用法的约定。并非它们的本身区别。

“2. GET方式提交的数据有长度限制,则POST的数据则可以非常大”,这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。也不是GET和POST本身的区别。

“3. POST比GET安全,因为数据在地址栏上不可见”,这个说法没毛病,但依然不是GET和POST本身的区别。

虽然这三点不是它们的本身区别,但至少是它们在使用上的区别,所以我在面试这个问题时,如果面试者能够回答上面三点我基本会给个及格分。那么你想不想要更高的分数?

四、终极区别

GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是。这个是它们本质区别,上面的只是在使用上的区别。

什么是幂等性?幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。

正因为它们有这样的区别,所以不应该且不能用get请求做数据的增删改这些有副作用的操作。因为get请求是幂等的,在网络不好的隧道中会尝试重试。如果用get请求增数据,会有重复操作的风险,而这种重复操作可能会导致副作用(浏览器和操作系统并不知道你会用get请求去做增操作)。

五、我的建议

如果面试官问你这个问题时,我建议你说出上面三点,同时要说明那三点是它们在使用上的区别,当然也要把它们的终极区别给说出来。

PS:曾经有一个研读了HTTP协议的人去一家公司面试,面试官问他这个问题时,他回答“GET是用于获取数据的,POST一般用于将数据发给服务器。其他GET和POST没什么区别”,于是被刷了。

因为有些面试官心中也只有那一个“标准答案”。

如何实现跨域?

实现跨域有以下几种方案:

  • 服务器端运行跨域 设置 CORS 等于 *;
  • 在单个接口使用注解 @CrossOrigin 运行跨域;
  • 使用 jsonp 跨域;

说一下 JSONP 实现原理?

ajax请求受同源策略影响,不允许进行跨域请求,而script标签src属性中的链接却可以访问跨域的js脚本,利用这个特性,服务端不再返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。

【分享】具体可以参考此文章:

  • https://blog.csdn.net/u011897301/article/details/52679486/
  • https://blog.csdn.net/u011897301/article/details/52679486/

九、设计模式

说一下你熟悉的设计模式?

一共23种设计模式!

  • 按照目的来分,设计模式可以分为创建型模式、结构型模式和行为型模式。
    • 创建型模式用来处理对象的创建过程;
    • 结构型模式用来处理类或者对象的组合;
    • 行为型模式用来对类或对象怎样交互和怎样分配职责进行描述。
  • 创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:
    • 工厂方法模式(Factory Method Pattern)
    • 抽象工厂模式(Abstract Factory Pattern)
    • 建造者模式(Builder Pattern)
    • 原型模式(Prototype Pattern)
    • 单例模式(Singleton Pattern)
  • 结构型模式用来处理类或者对象的组合,主要包含以下7种设计模式:
    • 适配器模式(Adapter Pattern)
    • 桥接模式(Bridge Pattern)
    • 组合模式(Composite Pattern)
    • 装饰者模式(Decorator Pattern)
    • 外观模式(Facade Pattern)
    • 享元模式(Flyweight Pattern)
    • 代理模式(Proxy Pattern)
  • 行为型模式用来对类或对象怎样交互和怎样分配职责进行描述,主要包含以下11种设计模式:
    • 责任链模式(Chain of Responsibility Pattern)
    • 命令模式(Command Pattern)
    • 解释器模式(Interpreter Pattern)
    • 迭代器模式(Iterator Pattern)
    • 中介者模式(Mediator Pattern)
    • 备忘录模式(Memento Pattern)
    • 观察者模式(Observer Pattern)
    • 状态模式(State Pattern)
    • 策略模式(Strategy Pattern)
    • 模板方法模式(Template Method Pattern)
    • 访问者模式(Visitor Pattern)

举两个单例模式,其他的自己网上学习吧
单例模式实现1:

public class Singleton {
    // 类共享实例对象
    private static Singleton singleton = null;
    // 私有构造方法
    private Singleton() {
        System.out.println("-- this is Singleton!!!");
    }
    // 获得单例方法
    public synchronized static Singleton getInstance() {
        // 判断 共享对象是否为null ,如何为null则new一个新对象
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

public class Singleton {
    // 类共享实例对象 实例化
    private static Singleton singleton = new Singleton();
    // 私有构造方法
    private Singleton() {
        System.out.println("-- this is Singleton!!!");
    }
    // 获得单例方法
    public static Singleton getInstance() {
        // 直接返回共享对象
        return singleton;
    }
}

简单工厂和抽象工厂有什么区别?

  • 工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:
    • 简单工厂模式(Simple Factory)
    • 工厂方法模式(Factory Method)
    • 抽象工厂模式(Abstract Factory)
  • 这三种模式从上到下逐步抽象,并且更具一般性。还有一种分类法,就是将简单工厂模式看为工厂方法模式的一种特例,两个归为一类。两者皆可,这本为使用《Java与模式》的分类方法
  • 在什么样的情况下我们应该记得使用工厂模式呢?大体有两点
    • 1.在编码时不能预见需要创建哪种类的实例。
    • 2.系统不应依赖于产品类实例如何被创建、组合和表达的细节
  • 具体的可以参考这篇文章:https://www.cnblogs.com/zhangchenliang/p/3700820.html

十、Spring/Spring MVC

为什么要使用 spring?

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。

  • Spring能有效地组织你的中间层对象,不管你是否选择使用了EJB。如果你仅仅使用了Struts或其他为J2EE的 API特制的framework,Spring致力于解决剩下的问题。
  • Spring能消除在许多工程中常见的对Singleton的过多使用。过多使用Singleton降低了系统的可测试性和面向对象的程度。
  • 通过一种在不同应用程序和项目间一致的方法来处理配置文件,Spring能消除各种各样自定义格式的属性文件的需要。曾经对某个类要寻找的是哪个魔法般的属性项或系统属性感到不解,为此不得不去读Javadoc甚至源编码?有了Spring,你仅仅需要看看类的JavaBean属性。Inversion ofControl的使用(在下面讨论)帮助完成了这种简化。
  • 通过把对接口编程而不是对类编程的代价几乎减少到没有,Spring能够促进养成好的编程习惯
  • Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring
  • 使用Spring构建的应用程序易于单元测试
  • Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码
  • Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适用于许多web应用。例如,Spring能使用AOP提供声明性事务管理而不通过EJB容器,如果你仅仅需要与单个数据库打交道,甚至不需要一个JTA实现。
  • Spring为数据存取提供了一个一致的框架,不论是使用的是JDBC还是O/R mapping产品(如Hibernate)。

解释一下什么是 aop?

AOP(Aspect-Oriented Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern)。

Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

解释一下什么是 ioc?

   IoC就是(Inversion of Control),控制反转。在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。这称为控制反转。

Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

spring 有哪些主要模块?

Spring有七大模块组成:
Java面试宝典-1_第2张图片

  • 核心容器(Spring Core)

核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。

  • 应用上下文(Spring Context)

Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

  • Spring面向切面编程(Spring AOP)

通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

  • JDBC和DAO模块(Spring DAO)

JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

  • 对象实体映射(Spring ORM)

Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

  • Web模块(Spring Web)

Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  • MVC模块(Spring Web MVC)

MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。

spring 常用的注入方式有哪些?

之前写过一篇文章 常用的注解,有需要的可以参考:https://blog.csdn.net/u011665991/article/details/82460263

  • @Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
  • @Scope注解 作用域
  • @Lazy(true) 表示延迟初始化
  • @Service用于标注业务层组件、
  • @Controller用于标注控制层组件(如struts中的action)
  • @Repository用于标注数据访问组件,即DAO组件。
  • @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
  • @Scope用于指定scope作用域的(用在类上)
  • @PostConstruct用于指定初始化方法(用在方法上)
  • @PreDestory用于指定销毁方法(用在方法上)
  • @DependsOn:定义Bean初始化及销毁时的顺序
  • @Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
  • @Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
  • @Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用
  • @Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
  • @PostConstruct 初始化注解
  • @PreDestroy 摧毁注解 默认 单例 启动就加载
  • @Async异步方法调用

依赖注入的方式有几种,各是什么

一、构造器注入

  • 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
  • 优点:
  • 对象初始化完成后便可获得可使用的对象。
  • 缺点:
  • 当需要注入的对象很多时,构造器参数列表将会很长;不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。

二、setter方法注入

  • IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。
  • 优点:
  • 灵活。可以选择性地注入需要的对象。
  • 缺点:
  • 依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。

三、接口注入

  • 依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象
  • 优点
  • 接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
  • 缺点:
  • 侵入行太强,不建议使用。
  • 什么是侵入行?
  • 如果类A要使用别人提供的一个功能,若为了使用这功能,需要在自己的类中增加额外的代码,这就是侵入性

spring 中的 bean 是线程安全的吗?

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。

最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。

spring 支持几种 bean 的作用域?

当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
  • session:对于每次HTTPSession,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
  • globalsession:每个全局的HTTPSession,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet
    context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。


spring bean 容器的生命周期是什么样的?

1、Spring 容器根据配置中的 bean 定义中实例化 bean。
2、Spring 使用依赖注入填充所有属性,如 bean 中所定义的配置。
3、如果 bean 实现BeanNameAware 接口,则工厂通过传递 bean 的 ID 来调用setBeanName()。
4、如果 bean 实现 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 setBeanFactory()。
5、如果存在与 bean 关联的任何BeanPostProcessors,则调用 preProcessBeforeInitialization() 方 法。
6、如果为 bean 指定了 init 方法(的 init-method 属性),那么将调用它。
7、最后,如果存在与 bean 关联的任何 BeanPostProcessors,则将调用 postProcessAfterInitialization() 方法。
8、如果 bean 实现DisposableBean 接口,当 spring 容器关闭时,会调用 destory()。
9、如果为bean 指定了 destroy 方法(的 destroy-method 属性),那么将调用它。


spring 自动装配 bean 有哪些方式?

  • Spring中bean有三种装配机制,分别是:
    • 在xml中显示配置;
    • 在java中显示配置;
    • 隐式的bean发现机制和自动装配。

有需要的可以参考:https://blog.csdn.net/beirdu/article/details/78768606

spring 事务实现方式有哪些?

事务:事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.比如,保证数据的运行不会说A给B钱,A钱给了B却没收到。

实现事务的三种方式:

1.aspectJ AOP实现事务:
2.事务代理工厂Bean实现事务:
3.注解方式实现事务:
在需要进行事务的方法上增加一个注解“@Transactional(rollbackFor = MyExepction.class )”


@Component, @Controller, @Repository,@Service的区别

  • @Component :这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
  • @Controller :这将一个类标记为 Spring Web MVC 控制器。标有它的Bean 会自动导入到 IoC 容器中。
  • @Service :此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用@Service 而不是 @Component,因为它以更好的方式指定了意图。
  • @Repository :这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。

说一下 spring 的事务隔离?

  • 事务特性(4种):
    • 原子性 (atomicity):强调事务的不可分割.
    • 一致性 (consistency):事务的执行的前后数据的完整性保持一致.
    • 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰
    • 持久性(durability) :事务一旦结束,数据就持久到数据库
  • 如果不考虑隔离性引发安全性问题:
    • 脏读 :一个事务读到了另一个事务的未提交的数据
    • 不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
    • 虚幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.
  • 解决读问题: 设置事务隔离级别(5种)
    • DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
    • 未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
    • 已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生
    • 可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生.
    • 串行化的 (serializable) :避免以上所有读问题.
    • Mysql 默认:可重复读
    • Oracle 默认:读已提交
    • Java面试宝典-1_第3张图片
      read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
      read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
      repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
      serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。
  • 事务的传播行为(7种)
  • PROPAGION_XXX :事务的传播行为
  • 保证同一个事务中
    • PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认)
    • PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
    • PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
  • 保证没有在同一个事务中
    • PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
    • PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
    • PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
    • PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

说一下 spring mvc 运行流程?
Java面试宝典-1_第4张图片
流程

  • 1、用户发送请求至前端控制器DispatcherServlet
  • 2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  • 3、处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给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响应用户

spring mvc 有哪些组件?

名称 简介
HandlerMapping 管理请求(request)和处理句柄(Handler)之间的映射关系
HandlerAdapter 处理句柄适配器,1.每个HandlerAdapter包装一个Handler,2.HandlerAdapter主要用于隔离调用者DispatcherServlet和各式各样的Handler
HandlerExceptionResolver 处理句柄异常解析器
ViewResolver 视图解析器:根据视图名称和Locale解析为具体View对象用来渲染页面
RequestToViewNameTranslator 请求到视图名称的翻译器:从request中获取视图名称,作为ViewResolver的补充
LocaleResolver Locale地区解析器处理本地化/国际化语言相关,结合上下文和当前请求分析得到应该使用的Locale地区
ThemeResolver 主题解析器,处理UI主题(look and feel)相关
MultipartResolver Multipart上传数据解析器
FlashMapManager 管理FlashMap结构的重定向参数

@RequestMapping 的作用是什么?

@RequestMapping

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。

【分享】具体可以参考文章:https://www.cnblogs.com/qq78292959/p/3760560.html

@Autowired 的作用是什么?

这个注解就是spring可以自动帮你把bean里面引用的对象的setter/getter方法省略,它会自动帮你set/get。



    
      
    

这样你在userService里面要做一个userDao的setter/getter方法。

但如果你用了@Autowired的话,你只需要在UserService的实现类中声明即可。

@Autowired
 
private IUserDao userdao;

十一、Spring Boot/Spring Cloud

什么是 spring boot?

SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring众多框架中所需的大量且繁琐的配置文件,所以 SpringBoot是一个服务于框架的框架,服务范围是简化配置文件

为什么要用 spring boot?

最明显的特点是,让文件配置变的相当简单、让应用部署变的简单(SpringBoot内置服务器,并装备启动类代码),可以快速开启一个Web容器进行开发

  • (1)一个简单的SpringBoot工程是不需要在pom.xml手动添加什么配置的,如果与其他技术合用,比如postMan(文档在线自动生成、开发功能测试的一套工具)、Swagger(文档在线自动生成、开发功能测试的一套工具),则需要在pom.xml中添加依赖,由程序自动加载依赖jar包等配置文件。

  • (2)我们之前在利用SSM或者SSH开发的时候,在resources中储存各种对应框架的配置文件,而现在我们只需要一个配置文件即可,配置内容也大体有服务器端口号、数据库连接的地址、用户名、密码。这样,虽然简单但在一定问题上而言,这也是极不安全的,将所有配置,放在一个文件里,是很危险的,但对于一般项目而言并不会有太大影响。

  • (3)在SpringBoot创建时会自动创建Bootdemo1Application启动类,代表着本工程项目和服务器的启动加载,在springBoot中是内含服务器的,所以不需手动配置Tomact,但注意端口号冲突问题。

spring boot 核心配置文件是什么?

核心配置文件是指在resources根目录下的application.properties或application.yml配置文件,读取这两个配置文件的方法有两种,都比较简单

spring boot 配置文件有哪几种类型?它们有什么区别?

这里我理解的是两种类型 application.properties和application.yml

application.yml配置结构

spring:
 application:
  name: wxxcx mvc:
  view:
    prefix: /WEB-INF/jsp/ suffix: jsp

application.properties配置结构

spring.application.name=wxxcx
server.port = 9000
server.context-path = /
server.tomcat.uri-encoding = UTF-8
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

spring boot 有哪些方式可以实现热部署?

SpringBoot热部署方式一共有两种,分别使用两种不同的依赖

SpringBoot 1.3后才拥有SpringBoot devtools热部署

①:spring-boot-devtools ②:Spring Loaded

方式一: 在项目的pom文件中添加依赖:



    org.springframework.boot
    spring-boot-devtools

方式二: 在项目的pom文件中添加依赖:


        
            
                
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                    
                    
                        org.springframework
                        springloaded
                        1.2.6.RELEASE
                    
                
            
        
    

添加完毕后需要使用mvn指令运行:

首先找到IDEA中的Edit configurations ,然后进行如下操作:(点击左上角的"+",然后选择maven将出现右侧面板,在红色划线部位输入如图所示指令,你可以为该指令命名(此处命名为MvnSpringBootRun))
    Java面试宝典-1_第5张图片


Spring Boot 有哪些优点?

Spring Boot 的优点有:

  • 减少开发,测试时间和努力。
  • 使用 JavaConfig 有助于避免使用 XML。
  • 避免大量的 Maven 导入和各种版本冲突。
  • 提供意见发展方法。
  • 通过提供默认值快速开始开发。
  • 没有单独的 Web 服务器需要。这意味着你不再需要启动 Tomcat,Glassfish或其他任何东西。
  • 需要更少的配置因为没有 web.xml 文件。只需添加用@ Configuration 注释的类,然后添加用 @Bean 注释的方法,Spring 将自动加载对象并像以前一样对其进行管理。您甚至可以将@Autowired 添加到 bean 方法中,以使 Spring 自动装入需要的依赖关系中。
  • 基于环境的配置使用这些属性,您可以将您正在使用的环境传递到应用程序:Dspring.profiles.active = {enviornment}。在加载主应用程序属性文件后,Spring 将在
    (application{environment} .properties)中加载后续的应用程序属性文件。

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 

什么是 Swagger?你用 Spring Boot 实现了它吗?

Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger 消除了调用服务时的猜测。


什么是 Spring Batch?

Spring Boot Batch 提供可重用的函数,这些函数在处理大量记录时非常重要,包括日志/跟踪,事务管理,作业处理统计信息,作业重新启动,跳过和资源管理。它还提供了更先进的技术服务和功能,通过优化和分区技术,可以实现极高批量和高性能批处理作业。简单以及复杂的大批量批处理作业可以高度可扩展的方式利用框架处理重要大量的信息。


什么是 WebSockets?

WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。

1、WebSocket 是双向的 -使用 WebSocket 客户端或服务器可以发起消息发送。 
2、WebSocket 是全双工的 -客户端和服务器通信是相互独立的。 
3、单个 TCP 连接 -初始连接使用 HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信 
4、Light -与 http 相比,WebSocket 消息数据交换要轻得多。

jpa 和 hibernate 有什么区别?

简单的可以理解为:

  • Hibernate是JPA规范的一个具体实现
  • hibernate有JPA没有的特性
  • hibernate 的效率更快
  • JPA 有更好的移植性,通用性

分享两篇文章供大家参考:

  • https://www.cnblogs.com/mosoner/p/9494250.html

  • http://baijiahao.baidu.com/s?id=1602675844799153392&wfr=spider&for=pc

什么是 spring cloud?

  • Spring Cloud是一个微服务框架,相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案。

  • Spring Cloud对微服务基础框架Netflix的多个开源组件进行了封装,同时又实现了和云端平台以及和SpringBoot开发框架的集成。

  • SpringCloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。

  • Spring Cloud 为开发者提供了快速构建分布式系统的工具,开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。

  • https://www.cnblogs.com/lexiaofei/p/6808152.html

  • https://blog.csdn.net/kkkloveyou/article/details/79210420

  • https://spring.io/projects/spring-cloud

spring cloud 断路器的作用是什么?

在分布式环境下,特别是微服务结构的分布式系统中, 一个软件系统调用另外一个远程系统是非常普遍的。这种远程调用的被调用方可能是另外一个进程,或者是跨网路的另外一台主机, 这种远程的调用和进程的内部调用最大的区别是,远程调用可能会失败,或者挂起而没有任何回应,直到超时。更坏的情况是, 如果有多个调用者对同一个挂起的服务进行调用,那么就很有可能的是一个服务的超时等待迅速蔓延到整个分布式系统,引起连锁反应, 从而消耗掉整个分布式系统大量资源。最终可能导致系统瘫痪。

断路器(Circuit Breaker)模式就是为了防止在分布式系统中出现这种瀑布似的连锁反应导致的灾难。

一旦某个电器出问题,为了防止灾难,电路的保险丝就会熔断。断路器类似于电路的保险丝, 实现思路非常简单,可以将需要保护的远程服务嗲用封装起来,在内部监听失败次数, 一旦失败次数达到某阀值后,所有后续对该服务的调用,断路器截获后都直接返回错误到调用方,而不会继续调用已经出问题的服务, 从而达到保护调用方的目的, 整个系统也就不会出现因为超时而产生的瀑布式连锁反应。

分享一篇文章供大家参考:http://www.cnblogs.com/chry/p/7278853.html

spring cloud 的核心组件有哪些?

Spring Cloud由众多子项目组成,如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等,满足了构建微服务所需的所有解决方案。

  • 服务发现——Netflix Eureka
  • 客服端负载均衡——Netflix Ribbon
  • 断路器——Netflix Hystrix
  • 服务网关——Netflix Zuul
  • 分布式配置——Spring Cloud Config
  • 分享一篇文章供大家参考:https://blog.csdn.net/springML/article/details/82492643

使用 Spring Cloud 有什么优势?

使用 Spring Boot 开发分布式微服务时,我们面临以下问题

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

SpringBoot和SpringCloud的区别?

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

微服务的优点缺点?说下开发项目中遇到的坑?

优点:

1.每个服务直接足够内聚,代码容易理解 
2.开发效率高,一个服务只做一件事,适合小团队开发 
3.松耦合,有功能意义的服务。 
4.可以用不同语言开发,面向接口编程。 
5.易于第三方集成 
6.微服务只是业务逻辑的代码,不会和HTML,CSS或其他界面结合.
7.可以灵活搭配,连接公共库/连接独立库

缺点:

1.分布式系统的责任性 
2.多服务运维难度加大。 
3.系统部署依赖,服务间通信成本,数据一致性,系统集成测试,性能监控。

十二、Hibernate

为什么要使用 hibernate?
为什么要使用Hibernate(即它的优点):

  1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
  2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作
  3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。
  4. hibernate映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系

什么是 ORM 框架?
ORM(Object Relation Mapping)对象关系映射
即通过类与数据库表的映射关系,将对象持久化到数据库中,

主要是解决对象与关系数据库存在的互不匹配的现象的技术
ORM的方法论基于3个核心原则
  简单:以最基本的形式建模数据
  传达型:数据库结构被任何人都能理解的语言文档化
  精确性:基于数据模型创建正确标准化了的结构

常用的ORM框架有:Hibernate(Nhibernate),iBATIS,mybatis,EclipseLink,JFinal

hibernate 中如何在控制台查看打印的 sql 语句?
在application.properties配置如下代码:

	spring.jpa.properties.hibernate.show_sql=true          //控制台是否打印
	spring.jpa.properties.hibernate.format_sql=true        //格式化sql语句
	spring.jpa.properties.hibernate.use_sql_comments=true  //指出是什么操作生成了该语句

hibernate 有几种查询方式?
HQL查询、SQL查询、QBC条件查询

HQL的具体分类

  • 属性查询
  • 参数查询、命名参数查询
  • 关联查询
  • 分页查询
  • 统计函数。

HQL和SQL的区别:HQL是面向对象查询操作的,SQL是结构化查询语言 是面向数据库表结构的
代码举例:

//HQL:  Hibernate Query Language. 面向对象的写法:
Query query = session.createQuery("from Students where name = ?");
query.setParameter(0, "酱油君");
Query.list();
//QBC:  Query By Criteria.(条件查询)
Criteria criteria = session.createCriteria(Student.class);
criteria.add(Restrictions.eq("name", "酱油君"));
List list = criteria.list();
//SQL:
SQLQuery query = session.createSQLQuery("select * from Students");
query.addEntity(Student.class);
List list = query.list();

hibernate 实体类可以被定义为 final 吗?
可以将Hibernate的实体类定义为final类,但这种做法并不好。

因为Hibernate会使用代理模式在延迟关联的情况下提高性能,如果你把实体类定义成final类之后,因为 Java不允许对final类进行扩展,所以Hibernate就无法再使用代理了,如此一来就限制了使用可以提升性能的手段。不过,如果你的持久化类实现了一个接口而且在该接口中声明了所有定义于实体类中的所有public的方法轮到话,你就能够避免出现前面所说的不利后果。

在 hibernate 中使用 Integer 和 int 做映射有什么区别?
在Hibernate中,如果将OID定义为Integer类型,那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的,如果将OID定义为了int类型,还需要在hbm映射文件中设置其unsaved-value属性为0。

hibernate 是如何工作的?

工作原理:
- 1.读取并解析配置文件
- 2.读取并解析映射信息,创建SessionFactory 
- 3.打开Session
- 4.创建事务Transation
- 5.持久化操作
- 6.提交事务
- 7.关闭Session
- 8.关闭SesstionFactory
- 资料:https://www.cnblogs.com/bile/p/4030575.html

get()和 load()的区别?

  • hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;
  • hibernate对于get方法,hibernate一定要获取到真实的数据,否则返回null。
    具体介绍:
  • 对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有就返回null。
  • load方法加载实体对象的时候,根据映射文件上类级别的lazy属性的配置(默认为true)。

分情况讨论:

  • (1)若为true,则首先在Session缓存中查找,看看该id对应的对象是否存在,不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
  • (2)若为false,就跟get方法查找顺序一样,只是最终若没发现符合条件的记录,则会抛出一个ObjectNotFoundException。

说一下 hibernate 的缓存机制?

  • 一级缓存:内部缓存存在Hibernate中,属于应用事物级缓存
  • 二级缓存:应用级缓存、分布式缓存。
  • 使用场景:数据不会被第三方修改、数据大小在可接受范围、数据更新频率低、同一数据被系统频繁使用、非关键数据
  • 引入第三方缓存(如ehcache等)

hibernate 对象有哪些状态?
hibernate里对象有三种状态:

  • Transient 瞬时 :对象刚new出来,还没设id,设了其他值。
  • Persistent 持久:调用了save()、saveOrUpdate(),就变成Persistent,有id
  • Detached 脱管 : 当session close()完之后,变成Detached。
  • 参考资料:https://blog.csdn.net/tantexian/article/details/50539665

在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?

  • openSession 每一次获得的是一个全新的session对象,而getCurrentSession获得的是与当前线程绑定的session对象;
  • openSession不需要配置,而getCurrentSession需要配置
* 如果使用的是本地事务(jdbc事务)
thread
* 如果使用的是全局事务(jta事务)
jta
  • openSession需要手动关闭,而getCurrentSession系统自动关闭
openSession出来的session要通过:session.close(),
而getSessionCurrent出来的session系统自动关闭,如果自己关闭会报错
采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession()创建的session则不会
采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession()创建的
	session必须手动关闭
  • Session是线程不同步的,要保证线程安全就要使用getCurrentSession
  • 下面这段代码运行后可比较它们的(1)
package cn.blog.test;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class Test {

    
//openSession与getCurrentSession对比

         public static void main(String[] args) {  
              
                Configuration configuration = new Configuration().configure();  
                SessionFactory sf = configuration.buildSessionFactory();  
                  
                Session sessionOpen1 = sf.openSession();  
                Session sessionOpen2 = sf.openSession();  
                  
                Session sessionThread1 = sf.getCurrentSession();  
                Session sessionThread2 = sf.getCurrentSession();  
                  
                System.out.println(sessionOpen1.hashCode() + "<-------->" + sessionOpen2.hashCode());  //每次创建都是新的session对象
                System.out.println(sessionThread1.hashCode() + "<-------->" + sessionThread2.hashCode());  //每次获得的是当前session
		}         
}

hibernate 实体类必须要有无参构造函数吗?为什么?
首先答案是肯定的。

原因
Hibernate框架会调用这个默认构造方法来构造实例对象,即Class类的newInstance方法 ,这个方法就是通过调用默认构造方法来创建实例对象的 。

当查询的时候返回的实体类是一个对象实例,是Hibernate动态通过反射生成的。反射的Class.forName(“className”).newInstance()需要对应的类提供一个无参构造方法,必须有个无参的构造方法将对象创建出来,单从Hibernate的角度讲 他是通过反射创建实体对象的 所以没有默认构造方法是不行的,另外Hibernate也可以通过有参的构造方法创建对象。

提醒一点
如果你没有提供任何构造方法,虚拟机会自动提供默认构造方法(无参构造器),但是如果你提供了其他有参数的构造方法的话,虚拟机就不再为你提供默认构造方法,这时必须手动把无参构造器写在代码里,否则new Xxxx()是会报错的,所以默认的构造方法不是必须的,只在有多个构造方法时才是必须的,这里“必须”指的是“必须手动写出来”。

原文链接:https://blog.csdn.net/qq_29645505/article/details/88370037


十三、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 的过 程)。

Mybaits 的优点

1、基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML标签,支持编写动态 SQL 语句,并可重用。 
2、与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接; 
3、很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要JDBC 支持的数据库 MyBatis 都支持)。 
4、能够与 Spring 很好的集成; 
5、提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。 

MyBatis 框架的缺点

1、SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL 语句的功底有一定要求。
2、SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis 与 Hibernate 有哪些不同

1、Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 Sql 语句。 
2、Mybatis 直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大。 
3、Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用 hibernate 开发可以节省很多代码,提高效率。 

mybatis 中 #{}和 ${}的区别是什么?

  1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.

  2. $将传入的数据直接显示生成在sql中。如:order by u s e r i d user_id userid,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.

  3. #方式能够很大程度防止sql注入。

  4. $方式无法防止Sql注入

  5. $方式一般用于传入数据库对象,例如传入表名.

  6. 一般能用#的就别用 . M y B a t i s 排 序 时 使 用 o r d e r b y 动 态 参 数 时 需 要 注 意 , 用 . MyBatis排序时使用order by 动态参数时需要注意,用 .MyBatis使orderby而不是#

分享一篇文章供大家参考:https://www.cnblogs.com/baizhanshi/p/5778692.html

mybatis 有几种分页方式?

  • 1、数组分页
  • 2、sql分页
  • 3、拦截器分页
  • 4、RowBounds分页
  • 当然现在也有很多分页插件:比如PageHelper等
  • 分享一篇文章供大家参考:https://www.cnblogs.com/baizhanshi/p/5778692.html
  • https://blog.csdn.net/chenbaige/article/details/70846902

RowBounds 是一次性查询全部结果吗?为什么?

是一次性查询全部结果,只不过会根据参数丢掉一部分

分享一篇文章供大家参考:https://blog.csdn.net/u010077905/article/details/38469653

mybatis 逻辑分页和物理分页的区别是什么?

  • 1:逻辑分页
    虽然看起来实现了分页的功能,但实际上是将查询的所有结果放置在内存中,每次都从内存获取。内存开销比较大,在数据量比较小的情况下效率比物理分页高;在数据量很大的情况下,内存开销过大,容易内存溢出,不建议使用
  • 2:物理分页
    这种分页方法从底层上就是每次只查询对应条目数量的数据,内存开销比较小,在数据量比较小的情况下效率比逻辑分页还是低,在数据量很大的情况下,建议使用物理分页

mybatis 是否支持延迟加载?延迟加载的原理是什么?

在mybatis 中默认没有使用延迟加载 ,但是通过配置 实现延迟加载

延迟加载的原理:动态代理:在Hibernate中,被动态代理的延迟对象是one方;many方还是第一个普通的一个对象;

  • 分享几篇文章供大家参考:http://www.cnblogs.com/trisaeyes/archive/2007/01/08/614996.html
  • https://www.cnblogs.com/llynic/p/6377783.html

说一下 mybatis 的一级缓存和二级缓存?

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的作用范围更大。

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。 
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置; 
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

分享几篇文章供大家参考:https://blog.csdn.net/weixin_36380516/article/details/73194758
https://zhidao.baidu.com/question/140731349194225845.html

mybatis 和 hibernate 的区别有哪些?

Hibernate 框架
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,建立对象与数据库表的映射。是一个全自动的、完全面向对象的持久层框架。

Mybatis框架
Mybatis是一个开源对象关系映射框架,原名:ibatis,2010年由谷歌接管以后更名。是一个半自动化的持久层框架。

2.1 开发方面
    在项目开发过程当中,就速度而言:

        hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发;

        Mybatis 属于半自动化,sql需要手工完成,稍微繁琐;

    但是,凡事都不是绝对的,如果对于庞大复杂的系统项目来说,发杂语句较多,选择hibernate 就不是一个好方案。

2.2 sql优化方面
    Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能;

    Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;

2.3 对象管理比对
    Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;

    Mybatis 需要自行管理 映射关系;

2.4 缓存方面   

相同点:
Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓 存方案,创建适配器来完全覆盖缓存行为。

不同点:
Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

比较:

  • Hibernate 具有良好的管理机制,用户不需要关注SQL,如果二级缓存出现脏数据,系统会保存,;
  • Mybatis 在使用的时候要谨慎,避免缓存CAche 的使用。

Hibernate优势

  • Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
  • Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
  • Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
  • Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。

Mybatis优势

  • MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
  • MyBatis容易掌握,而Hibernate门槛较高。

一句话总结

  • Mybatis:小巧、方便、高效、简单、直接、半自动化
  • Hibernate:强大、方便、高效、复杂、间接、全自动化

mybatis 有哪些执行器(Executor)?

Mybatis有三种基本的Executor执行器: SimpleExecutor、ReuseExecutor、BatchExecutor。

  • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
  • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
  • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
  • 作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

Mybatis中如何指定使用哪一种Executor执行器?
答:在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

mybatis 分页插件的实现原理是什么?

实现原理: 就是在StatementHandler之前进行拦截,对MappedStatement进行一系列的操作(大致就是拼上分页sql)

分享几篇文章供大家参考:https://blog.csdn.net/tuesdayma/article/details/80166361

mybatis 如何编写一个自定义插件?

因为本人目前没有写过自定义插件,所以网上找了一篇文章供大家分享

https://www.jianshu.com/p/96ddaec4aea7


十四、RabbitMQ

什么是 rabbitmq

采用 AMQP 高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦 

为什么要使用 rabbitmq

1、在分布式系统下具备异步,削峰,负载均衡等一系列高级功能; 
2、拥有持久化的机制,进程消息,队列中的信息也可以保存下来。 
3、实现消费者和生产者之间的解耦。 
4、对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作。 
5.可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单。 

rabbitmq 的使用场景有哪些?

  • 场景1:单发送单接收
  • 场景2:单发送多接收场景3:Publish/Subscribe
  • 场景3:Publish/Subscribe
  • 场景4:Routing (按路线发送接收)
  • 场景5:Topics (按topic发送接收)
  • 分享几篇文章供大家参考:https://www.cnblogs.com/luxiaoxun/p/3918054.html

rabbitmq 有哪些重要的角色?

  • none

    • 不能访问 management plugin
  • management

    • 1、用户可以通过AMQP做的任何事外加:

    • 2、列出自己可以通过AMQP登入的virtual hosts

    • 3、查看自己的virtual hosts中的queues, exchanges 和 bindings

    • 4、查看和关闭自己的channels 和 connections

    • 5、查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。

  • policymaker

    • 1、management可以做的任何事外加:
    • 2、查看、创建和删除自己的virtual hosts所属的policies和parameters
  • monitoring

    • 1、management可以做的任何事外加:
    • 2、列出所有virtual hosts,包括他们不能登录的virtual hosts
    • 3、查看其他用户的connections和channels
    • 4、查看节点级别的数据如clustering和memory使用情况
    • 5、查看真正的关于所有virtual hosts的全局的统计信息
  • administrator

    • 1、policymaker和monitoring可以做的任何事外加:
    • 2、创建和删除virtual hosts
    • 3、查看、创建和删除users
    • 4、查看创建和删除permissions
    • 5、关闭其他用户的connections

分享几篇文章供大家参考:https://www.cnblogs.com/luxiaoxun/p/3918054.html

rabbitmq 有哪些重要的组件?

Spring AMQP 是基于 Spring 框架的AMQP消息解决方案,提供模板化的发送和接收消息的抽象层,提供基于消息驱动的 POJO的消息监听等,很大程度上方便了我们进行相关程序的开发。

Message消息是当前模型中操纵的基本单位,它由Producer产生,经过路由转发被Consumer所消费。它是生产者和消费者发送和处理的对象。消息头是由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

Exchange用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

Exchange包含4种类型:Direct, Topic, Fanout, Headers。不同的类型,他们如何处理绑定到队列方面的行为会有所不同。

Direct类型: 允许一个队列通过一个固定的Routing-key(通常是队列的名字)进行绑定。 Direct交换器将消息根据其routing-key属性投递到包含对应key属性的绑定器上。
Topic类型: 支持消息的Routing-key用*或#的模式,进行绑定。*匹配一个单词,#匹配0个或者多个单词。例如,binding key *.user.# 匹配routing key为 usd.user和eur.user.db,但是不匹配user.hello。
Fanout类型:它只是将消息广播到所有绑定到它的队列中,而不考虑routing key的值。
Header类型: 它根据应用程序消息的特定属性进行匹配,这些消息可能在binding key中标记为可选或者必选。
Queue队列,它代表了Message Consumer接收消息的地方,它用来保存消息直到发送给消费者。Queue有以下一些重要的属性。

持久性:如果启用,队列将会在消息协商器(Broker)重启前都有效。
自动删除:如果启用,那么队列将会在所有的消费者停止使用之后自动删除掉自身。
惰性:如果没有声明队列,那么在执行到使用的时候会导致异常,并不会主动声明。
排他性:如果启用,队列只能被声明它的消费者使用。
Binding绑定,生产者发送消息到Exchange,接收者从Queue接收消息,而绑定(Binging)是生产者和消费者消息传递的重要连接,它是连接生产者和消费者进行信息交流的关键。

简单来说,RabbitMQ就是一个消息代理机制。它的工作就是接收和转发消息。你可以把它当作是一个邮局:寄信人把信件放入邮箱,邮递员就会把信件投递到你的收件人处。在这个比喻中,RabbitMQ就扮演着邮箱、邮局以及邮递员的角色。其中:

Exchange: 就是顺丰和韵达。
routingkey: 就是邮件地址的概念.
queue: 就是邮箱接收软件,但是可以接收多个地址的邮件,通过bind实现。
producer: 消息生产者,就是投递消息的程序。
consumer:消息消费者,就是接受消息的程序。
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

rabbitmq 中 vhost 的作用是什么?

vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制;

vhost通过在各个实例间提供逻辑上分离,允许你为不同应用程序安全保密地运行数据;

RabbitMQ的Vhost主要是用来划分不同业务模块。不同业务模块之间没有信息交互。

Vhost之间相互完全隔离,不同Vhost之间无法共享Exchange和Queue。因此Vhost之间数据无法共享和分享。如果要实现这种功能,需要Vhost之间手动构建对应代码逻辑。

分享几篇文章供大家参考:https://www.cnblogs.com/zhengchunyuan/p/9253725.html
https://www.rabbitmq.com/vhosts.html

rabbitmq 的消息是怎么发送的?

RabbitMQ事务和Confirm发送方消息确认

分享几篇文章供大家参考:https://www.cnblogs.com/vipstone/p/9295625.html

rabbitmq 怎么保证消息的稳定性?

正常情况下,如果消息经过交换器进入队列就可以完成消息的持久化,但如果消息在没有到达broker之前出现意外,那就造成消息丢失,有没有办法可以解决这个问题?RabbitMQ有两种方式来解决这个问题:

通过AMQP提供的事务机制实现;
使用发送者确认模式实现;
分享几篇文章供大家参考:https://www.cnblogs.com/vipstone/p/9350075.html

rabbitmq 怎么避免消息丢失?

消息持久化
ACK确认机制
设置集群镜像模式
消息补偿机制
分享几篇文章供大家参考:https://blog.csdn.net/u011665991/article/details/89946185

要保证消息持久化成功的条件有哪些?

声明队列必须设置持久化 durable 设置为 true.
消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。
消息已经到达持久化交换器。
消息已经到达持久化队列。

分享几篇文章供大家参考:https://blog.csdn.net/u011665991/article/details/89946411

rabbitmq 持久化有什么缺点?

持久化的缺地就是降低了服务器的吞吐量,
因为使用的是磁盘而非内存存储,
从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题。

rabbitmq 有几种广播类型?

三种广播模式:
 
1 fanout: 所有bind到此exchange的queue都可以接收消息
         (纯广播,绑定到RabbitMQ的接受者都能收到消息);
2 direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息;
3 topic:  所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息;

rabbitmq 怎么实现延迟消息队列?

通过消息过期后进入死信交换器,
再由交换器转发到延迟消费队列,实现延迟功能;
 
使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。

rabbitmq 集群有什么用?

集群主要有以下两个用途:
 
高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用;
高容量:集群可以承载更多的消息量。

rabbitmq 节点的类型有哪些?

磁盘节点:消息会存储到磁盘。
 
内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型

rabbitmq 集群搭建需要注意哪些问题?

各节点之间使用“--link”连接,此属性不能忽略。
 
各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证。
 
整个集群中必须包含一个磁盘节点。

rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?

不是,原因有以下两个:
 
存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,
这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;
 
性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,
那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。

rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?

如果唯一磁盘的磁盘节点崩溃了,不能进行以下操作:
 
不能创建队列
不能创建交换器
不能创建绑定
不能添加用户
不能更改权限
不能添加和删除集群节点
 
唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西

rabbitmq 对集群节点停止顺序有要求吗?

RabbitMQ 对集群的停止的顺序是有要求的,
 
应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。

Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别?

对于吞吐量来说kafka和RocketMQ支撑高吞吐,ActiveMQ和RabbitMQ比他们低一个数量级。对于延迟量来说RabbitMQ是最低的。
1.从社区活跃度 
按照目前网络上的资料,RabbitMQ 、activeM 、ZeroMQ 三者中,综合来,RabbitMQ 是首选。
2.持久化消息比较
ActiveMq 和RabbitMq 都支持。持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了,消息不会丢失的机制。
3.综合技术实现 
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。 
RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。当然ZeroMq 也可以做到,不过自己必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。
4.高并发 
毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的erlang 语言。
5.比较关注的比较, RabbitMQ 和 Kafka 
RabbitMq 比Kafka 成熟,在可用性上,稳定性上,可靠性上, RabbitMq 胜于 Kafka (理论上)。另外,Kafka 的定位主要在日志等方面,因为Kafka 设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以如果业务方面还是建议选择 RabbitMq 。还有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出来很多

如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决

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

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

消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧

十五、Kafka

Kafka 是什么
Kafka 是一种高吞吐量、分布式、基于发布/订阅的消息系统,最初由 LinkedIn 公司开发,使用Scala 语言编写,目前是 Apache 的开源项目。

1. broker: Kafka 服务器,负责消息存储和转发 
2. topic:消息类别, Kafka 按照 topic 来分类消息 
3. partition: topic 的分区,一个 topic 可以包含多个 partition, topic 消息保存在各个partition 上
4. offset:消息在日志中的位置,可以理解是消息在 partition 上的偏移量,也是代表该消息的唯一序号 
5. Producer:消息生产者 
6. Consumer:消息消费者 
7. Consumer Group:消费者分组,每个 Consumer 必须属于一个 group 
8. Zookeeper:保存着集群 broker、 topic、 partition 等 meta 数据;另外,还负责 broker 故障发现, partition leader 选举,负载均衡等功能

kafka 可以脱离 zookeeper 单独使用吗?为什么?

kafka 不能脱离 zookeeper 单独使用,
 
因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。

Zookeeper 对于 Kafka 的作用是什么?

Zookeeper 是一个开放源码的、高性能的协调服务,它用于 Kafka 的分布式应用。
Zookeeper 主要用于在集群中不同节点之间进行通信
在 Kafka 中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都可以从之前提交的偏移量中获取
除此之外,它还执行其他活动,如: leader 检测、分布式同步、配置管理、识别新节点何时离开或连 接、集群、节点实时状态等等。
数据传输的事务定义有哪三种?
和 MQTT 的事务定义一样都是 3 种。
(1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输
(2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输.
(3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的

kafka 有几种数据保留的策略?

kafka 有两种数据保存策略:
 
按照过期时间保留
按照存储的消息大小保留。

kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理?

这个时候 kafka 会执行数据清除工作,时间和大小不论那个满足条件,都会清空数据。

什么情况会导致 kafka 运行变慢?

cpu 性能瓶颈
磁盘读写瓶颈
网络瓶颈

使用 kafka 集群需要注意什么?

集群的数量不是越多越好,最好不要超过 7 个,
因为节点越多,消息复制需要的时间就越长,整个群组的吞吐量就越低。
集群数量最好是单数,因为超过一半故障集群就不能用了,设置为单数容错率更高。

讲一讲 kafka 的 ack 的三种机制

request.required.acks 有三个值 0 1 -1(all) 
0:生产者不会等待 broker 的 ack,这个延迟最低但是存储的保证最弱当 server 挂掉的时候就会丢数据。 
1:服务端会等待 ack 值 leader 副本确认接收到消息后发送 ack 但是如果 leader挂掉后他不确保是否复制完成新 leader 也会导致数据丢失。 
-1(all):服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出的ack,这样数据不会丢失

十六、Zookeeper

zookeeper 是什么?
ZooKeeper 是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

zookeeper 都有哪些功能?

1.统一命名服务
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的命名又便于识别和记住,或许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复的名称,就像数据库中产生的唯一的数字主键一样。
NameService已经是Zookeeper内置的功能,你只要调用Zookeeper的API就能实现,如调用create接口就可以很容易的创建一个目录节点。

2.配置管理
配置管理的在分布式应用环境下很常见,例如同一个应用系统需要多台PC Server运行,但是他们运行的应用系统的某些配置是相同的,如果要修改这个相同的配置项,那么就必须同时修改每台运行这个系统的PC Server这样非常麻烦而且容易出错。
像这样的配置信息完全可以交给Zookeeper来管理,将配置信息保存在Zookeeper的某个目录节点中,然后将所有需要修改的应用及其监控配置信息的状态,一旦配置信息发生变化,每台应用及其就会收到ZooKeeper的通知,然后从Zookeeper的通知,然后从Zookeeper获取新的配置信息应用到系统中。

3.集群管理
Zookeeper能够很容易的实现集群管理的功能,如有多台Server组成一个服务集群,那么必须要一个总管知道当前及权重每台机器的服务状态,一旦有机器不能提供服务,集群中其他集群必须知道,从而做出调整重新分配服务政策.同样增加集群的服务能力时,就会增加一台或者多台server,同样也必须让总管知道.
Zookeeper不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个’总管’,让这个总管来管理集群,这就是Zookeeper的另一个功能 Leader Election。

4.队列管理
(1)当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
(2)队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。

四种类型的数据节点 Znode

1、PERSISTENT-持久节点 
除非手动删除,否则节点一直存在于 Zookeeper 上 
2、EPHEMERAL-临时节点 
临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。 
3、PERSISTENT_SEQUENTIAL-持久顺序节点 
基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。 
4、EPHEMERAL_SEQUENTIAL-临时顺序节点 
基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。 

Watcher

客户端注册 Watcher 实现
1、调用 getData()/getChildren()/exist()三个 API,传入 Watcher 对象 
2、标记请求 request,封装 Watcher 到 WatchRegistration 
3、封装成 Packet 对象,发服务端发送 request 
4、收到服务端响应后,将 Watcher 注册到 ZKWatcherManager 中进行管理
5、请求返回,完成注册。
服务端处理 Watcher 实现
1、服务端接收 Watcher 并存储 
接收到客户端请求,处理请求判断是否需要注册 Watcher,需要的话将数据节点的节点路径和 
ServerCnxn(ServerCnxn 代表一个客户端和服务端的连接,实现了 Watcher 的 process 接口,此时可以看成一个 Watcher 对象)存储在WatcherManager 的 WatchTable 和watch2Paths 中去。
2、Watcher 触发 
以服务端接收到 setData() 事务请求触发 NodeDataChanged 事件为例: 
2.1 封装 WatchedEvent将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装成一个 WatchedEvent 对象 
2.2 查询 Watcher从 WatchTable 中根据节点路径查找 Watcher 
2.3 没找到;说明没有客户端在该数据节点上注册过 Watcher 
2.4 找到;提取并从 WatchTable 和 Watch2Paths 中删除对应 Watcher(从这里可以看出 Watcher 在服务端是一次性的,触发一次就失效了) 
3、调用 process 方法来触发 Watcher
这里 process 主要就是通过 ServerCnxn 对应的 TCP 连接发送 Watcher 事件通知
客户端回调 Watcher
客户端 SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher。客户端的 Watcher 机制同样是一次性的,一旦被触发后,该 Watcher 就失效了。 

Zookeeper 下 Server 工作状态

服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。
1、LOOKING:寻找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。 
2、FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。 
3、LEADING:领导者状态。表明当前服务器角色是 Leader。 
4、OBSERVING:观察者状态。表明当前服务器角色是 Observer。

zookeeper 是如何保证事务的顺序一致性的?
zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了
zxid,zxid 实际上是一个 64 位的数字,高 32 位是 epoch(时期; 纪元; 世; 新时代)用来标识 leader 周期,如果有新的 leader 产生出来,epoch会自增,低 32 位用来递增计数。当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。

ZAB 和 Paxos 算法的联系与区别?

相同点: 
1、两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行 
2、Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交 
3、 ZAB 协议中,每个 Proposal 中都包含一个 epoch 值来代表当前的 Leader周期,Paxos 中名字为 Ballot
不同点: 
ZAB 用来构建高可用的分布式数据主备系统( Zookeeper), Paxos 是用来构建分布式一致性
状态机系统。

zookeeper 有几种部署模式?
部署模式:单机模式、伪集群模式、集群模式

zookeeper 怎么保证主从节点的状态同步?

Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。
恢复模式:当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。
因此,选主得到的leader保证了同步状态的进行,状态同步又保证了leader和Server具有相同的系统状态,当leader失去主权后可以在其他follower中选主新的leader。

集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?

Zookeeper 本身也是集群,推荐配置不少于 3 个服务器。Zookeeper 自身也要保证当一个节点宕机 时,其他节点会继续提供服务。 
如果是一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,数据并不会丢失; 如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。 
ZK 集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在 ZK节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。 所以 3 个节点的 cluster 可以挂掉 1 个节点(leader 可以得到 2 票>1.5) ,2 个节点的 cluster 就不能挂掉任何 1 个节点了(leader 可以得到 1 票<=1) 

Zookeeper 工作原理(原子广播)

1. Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。 Zab 协议有两种模式,它们分别是恢复模式和广播模式。 
2. 当服务启动或者在领导者崩溃后, Zab 就进入了恢复模式,当领导者被选举出来,且大多数server 的完成了和 leader 的状态同步以后,恢复模式就结束了。
3. 状态同步保证了 leader 和 server 具有相同的系统状态
4. 一旦 leader 已经和多数的 follower 进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 zookeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。 Zookeeper服务一直维持在 
Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的followers 支持。 
5. 广播模式需要保证 proposal 被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了 zxid。 
6. 实现中 zxid 是一个 64 为的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个leader 被选出来,它都会有一个新的 epoch。低 32 位是个递增计数。 
7. 当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 server 都恢复到一个正确的状态

说一下 zookeeper 的通知机制?
客户端端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些客户端会收到 zookeeper 的通知,然后客户端可以根据 znode 变化来做出业务上的改变。

客户端注册监听他关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
client端会对某个znode建立一个watcher事件,当该znode发生变化时,zk会主动通知watch这个znode的client,然后client根据znode的变化来做出业务上的改变等。

watcher的特点:
轻量级:一个callback函数。
异步性:不会block正常的读写请求。
主动推送:Watch被触发时,由Zookeeper服务端主动将更新推送给客户端。
一次性:数据变化时,Watch只会被触发一次。如果客户端想得到后续更新的通知,必须要在 Watch 被触发后重新注册一个 Watch。
仅通知:仅通知变更类型,不附带变更后的结果。
顺序性:如果多个更新触发了多个Watch,那 Watch 被触发的顺序与更新顺序一致。

使用watch的注意事项:
由于watcher是一次性的,所以需要自己去实现永久watch
如果被watch的节点频繁更新,会出现“丢数据”的情况
watcher数量过多会导致性能下降

十七、MySql

数据库的三范式是什么?

第一范式(1st NF -列都是不可再分)
第一范式的目标是确保每列的原子性:如果每列都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式(1NF)
第二范式(2nd NF-每个表只描述一件事情)
首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。第二范式要求每个表只描述一件事情。
第三范式(3rd NF-不存在对非主键列的传递依赖)
第三范式定义是,满足第二范式,并且表中的列不存在对非主键列的传递依赖。除了主键订单编号外,顾客姓名依赖于非主键顾客编号。

一张自增表里面总共有 17 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

表类型如果是 MyISAM ,那 id 就是 18。
表类型如果是 InnoDB,那 id 就是 15。

MyISAM 只管记录,之前记录的是17,就算增删数据,当添加数据时,还是18
InnoDB 表只会把自增主键的最大 id 记录在内存中,所以重启之后会导致最大 id 丢失。

如何获取当前数据库版本?
使用 select version() 获取当前 MySQL 数据库版本。

说一下 ACID 是什么?

Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

char 和 varchar 的区别是什么?

char(n) :固定长度类型,比如订阅 char(10),当你输入"abc"三个字符的时候,它们占的空间还是 10 个字节,其他 7 个是空字节。
chat 优点:效率高;缺点:占用空间;适用场景:存储密码的 md5 值,固定长度的,使用 char 非常合适。

varchar(n) :可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。
所以,从空间上考虑 varcahr 比较合适;从效率上考虑 char 比较合适,二者使用需要权衡

float 和 double 的区别是什么?

float 最多可以存储 8 位的十进制数,并在内存中占 4 字节。
double 最可可以存储 16 位的十进制数,并在内存中占 8 字节。

mysql 的内连接、左连接、右连接有什么区别?

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

mysql 索引是怎么实现的?

索引是满足某种特定查找算法的数据结构,而这些数据结构会以某种方式指向数据,从而实现高效查找数据。 具体来说 MySQL 中的索引,不同的数据引擎实现有所不同,但目前主流的数据库引擎的索引都是 B+ 树实现的,B+ 树的搜索效率,可以到达二分法的性能,找到数据区域之后就找到了完整的数据结构了,所有索引的性能也是更好的

**怎么验证 mysql 的索引是否满足需求?**使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。
explain 语法:

explain select * from table where type=1

数据库事务

事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。事务是一个不可分割的工作逻辑单元事务必须具备以下四个属 性,简称 ACID 属性: 
原子性(Atomicity)
1. 事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行。
一致性(Consistency)
2. 当事务完成时,数据必须处于一致状态。
隔离性(Isolation)
3. 对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。
永久性(Durability)
4. 事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性

说一下数据库的事务隔离?
MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的,在文件的最后添加:

transaction-isolation = REPEATABLE-READ
可用的配置值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。

READ-UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)。
READ-COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读)。
REPEATABLE-READ:可重复读,默认级别,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读)。
SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

不可重复读 :是指在一个事务内,多次读同一数据。

幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了

说一下 mysql 常用的引擎?

InnoDB 引擎:InnoDB 引擎提供了对数据库 acid 事务的支持,并且还提供了行级锁和外键的约束,它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候,InnoDB 会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎是不支持全文搜索,同时启动也比较的慢,它是不会保存表的行数的,所以当进行 select count(*) from table 指令的时候,需要进行扫描全表。由于锁的粒度小,写操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率
MyIASM 引擎:MySQL 的默认引擎,但不提供事务的支持,也不支持行级锁和外键。因此当执行插入和更新语句时,即执行写操作的时候需要锁定这个表,所以会导致效率会降低。不过和 InnoDB 不同的是,MyIASM 引擎是保存了表的行数,于是当进行 select count(*) from table 语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以,如果表的读操作远远多于写操作时,并且不需要事务的支持的,可以将 MyIASM 作为数据库引擎的首选
mysql常用引擎包括:MYISAM、Innodb、Memory、MERGE
1. MYISAM:全表锁,拥有较高的执行速度,不支持事务,不支持外键,并发性能差,占用空间相对较小,对事务完整性没有要求,以select、insert为主的应用基本上可以使用这引擎 
2. Innodb:行级锁,提供了具有提交、回滚和崩溃回复能力的事务安全,支持自动增长列,支持外键约束,并发能力强,占用空间是MYISAM的2.5倍,处理效率相对会差一些 
3. Memory:全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在mysql重启时会丢失,默认使用HASH索引,检索效率非常高,但不适用于精确查找,主要用于那些内容变化不频繁的代码表 
4. MERGE:是一组MYISAM表的组合

说一下 mysql 的行锁和表锁?
MyISAM 只支持表锁,InnoDB 支持表锁和行锁,默认为行锁

表级锁:开销小,加锁快,不会出现死锁。锁定粒度大,发生锁冲突的概率最高,并发量最低
行级锁:开销大,加锁慢,会出现死锁。锁力度小,发生锁冲突的概率小,并发度最高

1、表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 
2、行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
3、页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般 

说一下乐观锁和悲观锁?

  • 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据
  • 悲观锁:每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放
  • 数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就不修改,这样就实现了乐观锁

mysql 问题排查都有哪些手段?

  • 使用 show processlist 命令查看当前所有连接信息
  • 使用 explain 命令查询 SQL 语句执行计划
  • 开启慢查询日志,查看慢查询的 SQL

如何做 mysql 的性能优化?

  • 为搜索字段创建索引
  • 避免使用 select *,列出需要查询的字段
  • 垂直分割分表
  • 选择正确的存储引擎

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 
1、Where 子句中:where 表之间的连接必须写在其他 Where 条件之前,那些可以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后。 
2、用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN。 
3、避免在索引列上使用计算 
4、避免在索引列上使用 IS NULL 和 IS NOT NULL 
5、对查询进行优化,尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 
6、尽量避免where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描
7、尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描 

InnoDB与MyISAM的区别

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要高
索引
索引(Index)是帮助 MySQL 高效获取数据的数据结构。常见的查询算法,顺序查找,二分查找,二叉排序树查找,哈希散列法,分块查找,平衡多路搜索树 B 树(B-tree),索引是对数据库表中一个或多个列的值进行排序的结构,建立索引有助于快速获取信息。
你也可以这样理解:索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,
索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库
mysql 有4种不同的索引:
主键索引(PRIMARY) 
唯一索引(UNIQUE) 
普通索引(INDEX) 
全文索引(FULLTEXT)

索引并非是越多越好,创建索引也需要耗费资源,一是增加了数据库的存储空间,二是在插入和删除时要花费较多的时间维护索引 
索引加快数据库的检索速度 
索引降低了插入、删除、修改等维护任务的速度 
唯一索引可以确保每一行数据的唯一性 
通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能 
索引需要占物理和数据空间

常见索引原则有

1. 选择唯一性索引,唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
2. 为经常需要排序、分组和联合操作的字段建立索引。 
3. 为常用作为查询条件的字段建立索引。 
4. 限制索引的数目:越多的索引,会使更新表变得很浪费时间。尽量使用数据量少的索引
5. 如果索引的值很长,那么查询的速度会受到影响。尽量使用前缀来索引 
6. 如果索引字段的值很长,最好使用值的前缀来索引。 
7. 删除不再使用或者很少使用的索引 
8. 最左前缀匹配原则,非常重要的原则。 
9. 尽量选择区分度高的列作为索引区分度的公式是表示字段不重复的比例
10. 索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。 
11. 尽量的扩展索引,不要新建索引

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

试述视图的优点?
(1) 视图能够简化用户的操作
(2) 视图使用户能以多种角度看待同一数据;
(3) 视图为数据库提供了一定程度的逻辑独立性;
(4) 视图能够对机密数据提供安全保护

触发器(一段能自动执行的程序)
触发器是一段能自动执行的程序,是一种特殊的存储过程,触发器和普通的存储过程的区别是:触发器是当对某一个表进行操作时触发。诸如: update、 insert、 delete 这些操作的时候,系统会自动调用执行该表上对应的触发器。 SQL Server 2005 中触发器可以分为两类: DML 触发器和DDL 触发器,其中 DDL 触发器它们会影响多种数据定义语言语句而激发,这些语句有 create、alter、 drop 语句。

存储过程(特定功能的 SQL 语句集)
一组为了完成特定功能的 SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编
译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。

存储过程优化思路

1. 尽量利用一些 sql 语句来替代一些小循环,例如聚合函数,求平均函数等。 
2. 中间结果存放于临时表,加索引。 
3. 少使用游标。 sql 是个集合语言,对于集合运算具有较高性能。而 cursors 是过程运算。比如对一个100万行的数据进行查询。游标需要读表 100 万次,而不使用游标则只需要少量几次读取。 
4. 事务越短越好。 sqlserver 支持并发操作。如果事务过多过长,或者隔离级别过高,都会造成并发操作的阻塞,死锁。导致查询极慢, cpu 占用率极地。
5. 使用 try-catch 处理错误异常。 
6. 查找语句尽量不要放在循环内

什么是通用 SQL 函数

1、CONCAT(A, B) – 连接两个字符串值以创建单个字符串输出。通常用于将两个或多个字段合并为一个字段。
2、FORMAT(X, D)- 格式化数字 X 到 D 有效数字。 
3、CURRDATE(), CURRTIME()- 返回当前日期或时间。 
4、NOW() – 将当前日期和时间作为一个值返回。 
5、MONTH(),DAY(),YEAR(),WEEK(),WEEKDAY()从日期值中提取给定数据。
6、HOUR(),MINUTE(),SECOND() – 从时间值中提取给定数据。 
7、DATEDIFF(A,B) – 确定两个日期之间的差异,通常用于计算年龄 
8、SUBTIMES(A,B) – 确定两次之间的差异。 
9、FROMDAYS(INT) – 将整数天数转换为日期值 

MySQL 数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化

1、设计良好的数据库结构,允许部分数据冗余,尽量避免 join 查询,提高效率。 
2、选择合适的表字段数据类型和存储引擎,适当的添加索引。 
3、MySQL 库主从读写分离。 
4、找规律分表,减少单表中的数据量提高查询速度。 
5、添加缓存机制,比如 memcached,apc 等。 
6、不经常改动的页面,生成静态页面。 
7、书写高效率的 SQL。比如 SELECT * FROM TABEL 改为 SELECT field_1,field_2, field_3 FROM TABLE. 

对于关系型数据库而言,索引是相当重要的概念,请回答有关索引的几个问题

1、索引的目的是什么? 
快速访问数据表中的特定信息,提高检索速度 
创建唯一性索引,保证数据库表中每一行数据的唯一性。 
加速表和表之间的连接 
使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间 
2、索引对数据库系统的负面影响是什么? 
负面影响: 
创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理空间,不光是表需要占用数据空间,每个索引也需要占用物理空间;当对表进行增、删、改、的时候索引也要动态维护,这样就降低了数据的维护速度。 
3、为数据表建立索引的原则有哪些? 
在最频繁使用的、用以缩小查询范围的字段上建立索引。 
在频繁使用的、需要排序的字段上建立索引 
4、什么情况下不宜建立索引? 
对于查询中很少涉及的列或者重复值比较多的列,不宜建立索引。 
对于一些特殊的数据类型,不宜建立索引,比如文本字段(text)等 

MONGODB

mongodb是什么?
MongoDB 是由 C++语言编写的,是一个基于分布式文件存储的开源数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。 MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

mongodb有哪些特点?

1.MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。 
2.你可以在 MongoDB 记录中设置任何属性的索引 (如: FirstName="Sameer",Address="8 Gandhi Road")来实现更快的排序。 
3.你可以通过本地或者网络创建数据镜像,这使得 MongoDB 有更强的扩展性。 
4.如果负载的增加(需要更多的存储空间和更强的处理能力),它可以分布在计算机网络中的其他节点上这就是所谓的分片。 
5.Mongo 支持丰富的查询表达式。查询指令使用 JSON 形式的标记,可轻易查询文档中内嵌的对象及数组。
6.MongoDb 使用 update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段。 
7.Mongodb 中的 Map/reduce 主要是用来对数据进行批量处理和聚合操作。 
8.Map 和 Reduce。 Map 函数调用 emit(key,value)遍历集合中所有的记录,将 key 与 value 传给 Reduce 函数进行处理。 
9.Map 函数和 Reduce 函数是使用 Javascript 编写的,并可以通过 db.runCommand 或 mapreduce 命令来执行 MapReduce 操作。
10. GridFS 是 MongoDB 中的一个内置功能,可以用于存放大量小文件。
11. MongoDB 允许在服务端执行脚本,可以用 Javascript 编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
 

MySQL与MongoDB之间最基本的差别是什么?
MySQL和MongoDB两者都是免费开源的数据库。MySQL和MongoDB有许多基本差别包括数据的表示(data representation),查询,关系,事务,schema的设计和定义,标准化(normalization),速度和性能。
通过比较MySQL和MongoDB,实际上我们是在比较关系型和非关系型数据库,即数据存储结构不同。


十八、Redis

redis 是什么?都有哪些使用场景?

Redis是一个基于内存的高性能开源的 key—value型 单线程 数据库,支持string、list、set、zset和hash类型数据。

  • 适用场景:
    • 对扩展性要求高的数据
    • 数据高并发的读写
    • 海量数据的读写
  • 不适场景:
    • 需要事务支持(非关系型数据库)
    • 基于sql结构化查询储存,关系复杂

redis 有哪些功能?

数据缓存功能
分布式锁的功能
支持数据持久化
支持事务
支持消息队列

具体参考文章:https://blog.csdn.net/sinat_34496643/article/details/80077319

redis 和 memecache 有什么区别?

  • redis相比memcached有哪些优势?
       (1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
       (2) redis的速度比memcached快很多
       (3) redis可以持久化其数据

  • Memcache与Redis的区别都有哪些?
       1)、存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,这样能保证数据的持久性。
       2)、数据支持类型 Memcache对数据类型支持相对简单。 Redis有复杂的数据类型。
       3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

redis 为什么是单线程的?

因为CPU并不Redis的瓶颈,内存和网路带宽才是。
redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

什么是缓存穿透?怎么解决?

 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决办法:

 1.布隆过滤 2. 缓存空对象. 将 null 变成一个值.

具体参考文章:https://blog.csdn.net/u011665991/article/details/89956398

redis 支持的数据类型有哪些?

redis支持丰富的数据类型,从最基础的string到复杂的常用到的数据结构都有支持:

  • string:最基本的数据类型,二进制安全的字符串,最大512M。
  • list:按照添加顺序保持顺序的字符串列表。
  • set:无序的字符串集合,不存在重复的元素。
  • sorted set:已排序的字符串集合。
  • hash:key-value对的一种集合。
  • bitmap:更细化的一种操作,以bit为单位。
  • hyperloglog:基于概率的数据结构。

这些众多的数据类型,主要是为了支持各种场景的需要,当然每种类型都有不同的时间复杂度。其实这些复杂的数据结构相当于之前在《解读REST》这个系列博客基于网络应用的架构风格中介绍到的远程数据访问(Remote Data Access = RDA)的具体实现,即通过在服务器上执行一组标准的操作命令,在服务端之间得到想要的缩小后的结果集,从而简化客户端的使用,也可以提高网络性能。比如如果没有list这种数据结构,你就只能把list存成一个string,客户端拿到完整的list,操作后再完整的提交给redis,会产生很大的浪费。

redis 支持的 java 客户端都有哪些?

Redisson,Jedis,lettuce等等,官方推荐使用Redisson。

jedis 和 redisson 有哪些区别?

Jedis api 在线网址:http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html
redisson 官网地址:https://redisson.org/
redisson git项目地址:https://github.com/redisson/redisson
lettuce 官网地址:https://lettuce.io/
lettuce git项目地址:https://github.com/lettuce-io/lettuce-core

  • 概念:
    • Jedis:是Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
    • Redisson:实现了分布式和可扩展的Java数据结构。
    • Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
  • 优点:
    • Jedis:比较全面的提供了Redis的操作特性
    • Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列

Lettuce:主要在一些分布式缓存框架上使用比较多

  • 可伸缩:
    • Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
    • Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
    • Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作

结论:

建议使用:Jedis + Redisson

怎么保证缓存和数据库数据的一致性?

对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。
定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。
给所有的缓存一个失效期。
分享几篇文章供大家参考:https://blog.csdn.net/u011665991/article/details/89954298

redis 持久化有几种方式?

  • RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
  • AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放
    AOF 日志中的写入指令来重新构建整个数据集。

补充:

通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。

如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。

如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。

RDB 优缺点

RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份redis中的数据。
RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

AOF 优缺点

AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志/merge/回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

RDB和AOF到底该如何选择

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

分享几篇文章供大家参考:https://blog.csdn.net/yinxiangbing/article/details/48627997

redis 怎么实现分布式锁?

官方推荐采用Redlock算法,即使用string类型,加锁的时候给的一个具体的key,然后设置一个随机的值;取消锁的时候用使用lua脚本来先执行获取比较,然后再删除key。具体的命令如下:

SET resource_name my_random_value NX PX 30000
 
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

redis 分布式锁有什么缺陷?

(一)缓存和数据库双写一致性问题
(二)缓存雪崩问题
(三)缓存击穿问题
(四)缓存的并发竞争问题

分享几篇文章供大家参考:

https://www.jianshu.com/p/6fba984cd9bd
https://www.cnblogs.com/linianhui/archive/2017/11/06/what-problem-does-redis-solve.html
https://blog.csdn.net/hcmony/article/details/80694560

redis 如何做内存优化?

关闭 Redis 的虚拟内存[VM]功能,即 redis.conf 中 vm-enabled = no
设置 redis.conf 中 maxmemory ,用于告知 Redis 当使用了多少物理内存后拒绝继续写入的请求,可防止 Redis 性能降低甚至崩溃
可为指定的数据类型设置内存使用规则,从而提高对应数据类型的内存使用效率
Hash 在 redis.conf 中有以下两个属性,任意一个超出设定值,则会使用 HashMap 存值
hash-max-zipmap-entires 64 表示当 value 中的 map 数量在 64 个以下时,实际使用 zipmap存储值
hash-max-zipmap-value 512 表示当 value 中的 map 每个成员值长度小于 512 字节时,实际使用
zipmap 存储值
List 在 redis.conf 中也有以下两个属性
	 - list-max-ziplist-entires 64
	 - list-max-ziplist-value 512
	 - 在 Redis 的源代码中有一行宏定义 REDIS-SHARED-INTEGERS = 10000 ,修改该值可以改变 Redis 存储数值类型的内存开销

redis 淘汰策略有哪些?

noeviction:当内存限制达到,谁也不删除,返回错误。
allkeys-lru:尝试回收最少使用的键,使得新添加的数据有空间存放。
volatile-lru:尝试回收最少使用的键,但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkey-random:回收随机的键,使得新添加的数据有空间存放。
volatile-random:回收随机的键,使得新添加的数据有空间存放,但仅限于过期集合的键。
volatile-ttl:回收在过期集合的键,并且优先回收存货时间较短的键,使得新添加的数据有空间存放。

redis 常见的性能问题有哪些?该如何解决?

Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

Master
   AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

Redis 与其他 key-value 存储有什么
不同Redis 有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。
Redis 的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
Redis 运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。
在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样 Redis可以做很多内部复杂性很强的事情。
同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

Redis 的数据类型?

Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 
zsetsorted set:有序集合)。 
我们实际项目中比较常用的是 string,hash 如果你是 Redis 中高级用户,还需要加上下面几种数据结构 HyperLogLog、Geo、Pub/Sub。 
如果你说还玩过 Redis Module,像 BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。 
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 有哪些好处

  1. 速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O1)
  2. 支持丰富数据类型,支持 string,list,set,Zset,hash 等
  3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  4. 丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题

缓存雪崩
我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库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出现问题,不去数据库查询,而是直接返回默认值给用户

redis 过期键的删除策略

1、定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。 
2、惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。 
3、定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期 
键,以及要检查多少个数据库,则由算法决定。 

Jedis 与 Redisson 对比有什么优缺点?
Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持;Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上

怎么理解 Redis 事务?
1)事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程
中,不会被其他客户端发送来的命令请求所打断。
2)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。


十九、JVM

说一下 jvm 的主要组成部分?及其作用?

  • 类加载器(ClassLoader)
  • 运行时数据区(Runtime Data Area)
  • 执行引擎(Execution Engine)
  • 本地库接口(Native Interface)
    组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能

说一下 jvm 运行时数据区?
不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分:

程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成
Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的
Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据

说一下堆栈的区别?

功能方面:堆是用来存放对象的,栈是用来执行程序的
共享性:堆是线程共享的,栈是线程私有的
空间大小:堆大小远远大于栈

队列和栈是什么?有什么区别?

  • 队列和栈都是被用来预存储数据的。
  • 队列允许先进先出检索元素,但也有例外的情况,Deque 接口允许从两端检索元素。
  • 栈和队列很相似,但它运行对元素进行后进先出进行检索

什么是双亲委派模型?

在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

类加载器分类:
启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库

其他类加载器:
扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库
应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类

说一下类加载的执行过程?
类装载分为以下 5 个步骤:

  • 加载:根据查找路径找到相应的 class 文件然后导入
  • 检查:检查加载的 class 文件的正确性
  • 准备:给类中的静态变量分配内存空间
  • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址
  • 初始化:对静态变量和静态代码块执行初始化工作
加载 
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,
作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。
验证 
这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备 
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 put static 指令是程序被编译后,存放于类构造器方法之中。 
但是注意如果声明为: 
public static final int v = 8080; 
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v 赋值为 8080。
解析 
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中
的:publicstaticintv=8080;
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。 

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:
1. CONSTANT_Class_info 
2. CONSTANT_Field_info 
3. CONSTANT_Method_info 
等类型的常量。
 
符号引用 
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。 

直接引用 
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
初始化 
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以 
外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。 

类构造器 
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子方法执行之前,父类的方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。注意以下几种情况不会执行

类初始化:
1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。 
2. 定义对象数组,不会触发该类的初始化。 
3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触
发定义常量所在的类。
4. 通过类名获取 Class 对象,不会触发类的初始化。 
5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初
始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。

怎么判断对象是否可以被回收?
一般有两种方法来判断:

  • 引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题
  • 可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的

java 中都有哪些引用类型?

JAVA 强引用
在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。 

JAVA软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。 

JAVA弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。 

JAVA虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

说一下 jvm 有哪些垃圾回收算法?

  • 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半
  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法

说一下 jvm 有哪些垃圾回收器?

  • Serial:最早的单线程串行垃圾回收器
  • Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案
  • ParNew:是 Serial 的多线程版本
  • Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量
  • Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法
  • CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统
  • G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项

详细介绍一下 CMS 垃圾回收器?
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器

CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低

新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1
    新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收

简述分代垃圾回收器是怎么工作的?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区
  • 清空 Eden 和 From Survivor 分区
  • From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor
      每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程

说一下 jvm 调优的工具?
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具

  • jconsole:用于对 JVM 中的内存、线程和类等进行监控;
  • jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等

常用的 jvm 调优的参数都有哪些?
-Xms2g:初始化推大小为 2g
-Xmx2g:堆最大内存为 2g
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合
-XX:+PrintGC:开启打印 gc 信息
-XX:+PrintGCDetails:打印 gc 详细信息

Java 中 WeakReference 与 SoftReference 的区别?
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

JRE、JDK、JVM 及 JIT 之间有什么不同

  • JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。
  • JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java编译器,它也包含 JRE。
  • JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。
  • JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

Java 中堆和栈有什么区别
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

栈
又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方法出口等信息.调用方法时执行入栈,方法返回式执行出栈. 

 本地方法栈
栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.

堆
JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所有的对象实例都放在这里,当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理 
方法区 又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7的永久代和1.8的元空间都是方法区的一种实现。 

堆和栈的区别
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连续,会有碎片。 

1、功能不同 
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。 

2、共享性不同 
栈内存是线程私有的。 
堆内存是所有线程共有的。 

3、异常错误不同
如果栈内存或者堆内存不足都会抛出异常。 
栈空间不足:java.lang.StackOverFlowError。
堆空间不足:java.lang.OutOfMemoryError。

4、空间大小 
栈的空间大小远远小于堆的

JVM 运行时内存
新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。

新生代
是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、 ServivorFrom、 ServivorTo 三个区。 
Eden 区 
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
ServivorFrom 
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。 
ServivorTo 
保留了一次 MinorGC 过程中的幸存者。 
MinorGC 的过程(复制->清空->互换) 
MinorGC 采用复制算法。 
1: eden、 servicorFrom 复制到 ServicorTo,年龄+1 
首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区); 
2:清空 eden、 servicorFrom 然后,清空 Eden 和 ServicorFrom 中的对象; 
3: ServicorTo 和 ServicorFrom 互换 最后, ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。 
老年代
主要存放应用程序中生命周期长的内存对象。 
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 
MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。 
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。 ajorGC 的耗时比较长,因为要扫描再回收。 MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。
永久代
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。 

二十、Memcached

Memcached 是什么,有什么作用?
Memcached 是一个开源的,高性能的内存绶存软件,从名称上看 Mem 就是内存的意思,而 Cache 就是缓存的意思。Memcached 的作用:通过在事先规划好的内存空间中临时绶存数据库中的各类数据,以达到减少业务对数据库的直接高并发访问,从而达到提升数据库的访问性能,加速网站集群动态应用服务的能力。

Memcached 服务分布式集群如何实现?
特殊说明:Memcached 集群和 web 服务集群是不一样的,所有 Memcached 的数据总和才是数据库的数据。每台 Memcached 都是部分数据。(一台 memcached 的数据,就是一部分 mysql 数据库的数据)

  • a、程序端实现
  • 程序加载所有 mc 的 ip 列表,通过对 key 做 hash (一致性哈希算法)
    例如:web1 (key)===>对应 A,B,C,D,E,F,G……若干台服务器。(通过哈希算法实现)
  • b、负载均衡器
  • 通过对 key 做 hash (一致性哈希算法)一致哈希算法的目的是不但保证每个对象只请求一个对应的服务器,而且当节点宕机,缓存服务器的更新重新分配比例降到最低。

Memcached 服务特点及工作原理是什么?

a、完全基于内存缓存的 
b、节点之间相互独立 
c、C/S 模式架构,C 语言编写,总共 2000 行代码。 
d、异步I/O 模型,使用 libevent 作为事件通知机制。 
e、被缓存的数据以 key/value 键值对形式存在的。 
f、全部数据存放于内存中,无持久性存储的设计,重启服务器,内存里的数据会丢失。 
g、当内存中缓存的数据容量达到启动时设定的内存值时,自动使用 LRU 算法删除过期的缓存数据。
h、可以对存储的数据设置过期时间,这样过期后的数据自动被清除,服务本身不会监控过期,而是在访问的时候查看 key 的时间戳,判断是否过期。 
j、memcache 会对设定的内存进行分块,再把块分组,然后再提供服务

memcached 是怎么工作的?
Memcached 的神奇来自两阶段哈希(two-stage hash)。Memcached 就像一个巨大的、存储了很多对的哈希表。通过 key,可以存储或查询任意的数据。
客户端可以把数据存储在多台 memcached 上。当查询数据时,客户端首先参考节点列表计算出 key 的哈希值(阶段一哈希),进而选中一个节点;客户端将请求发送给选中的节点,然后 memcached 节点通过一个内部的哈希算法(阶段二哈希),查找真正的数据(item)

memcached 的 cache 机制是怎样的?
Memcached 主要的 cache 机制是 LRU(最近最少用)算法 +超时失效。当您存数据到 memcached 中,可以指定该数据在缓存中可以呆多久 Which is forever,or some time in the future。如果 memcached 的内存不够用了,过期的 slabs会优先被替换,接着就轮到最老的未被使用的 slabs。

memcached 如何实现冗余机制?
不实现!我们对这个问题感到很惊讶。Memcached 应该是应用的缓存层。它的设计本身就不带有任何冗余机制。如果一个 memcached 节点失去了所有数据,您应该可以从数据源(比如数据库)再次获取到数据。您应该特别注意,您的应用应该可以容忍节点的失效。不要写一些糟糕的查询代码,寄希望于 memcached 来保证一切!如果您担心节点失效会大大加重数据库的负担,那么您可以采取一些办法。比如您可以增加更多的节点(来减少丢失一个节点的影响),热备节点(在其他节点 down 了的时候接管 IP),等等

memcached 能接受的 key 的最大长度是多少?
key 的最大长度是 250 个字符。需要注意的是,250 是 memcached 服务器端内部的限制,如果您使用的客户端支持”key 的前缀”或类似特性,那么 key(前缀+原始 key)的最大长度是可以超过 250 个字符的。我们推荐使用使用较短的 key,因为可以节省内存和带宽。

memcached 对 item 的过期时间有什么限制?
过期时间最大可以达到 30 天。memcached 把传入的过期时间(时间段)解释成时间点后,一旦到了这个时间点,memcached 就把 item 置为失效状态。这是一个简单但 obscure 的机制。

memcached 最大能存储多大的单个 item?
1MB。如果你的数据大于 1MB,可以考虑在客户端压缩或拆分到多个 key 中。
为什么单个 item 的大小被限制在 1M byte 之内?
啊…这是一个大家经常问的问题!
简单的回答:因为内存分配器的算法就是这样的。
详细的回答:Memcached 的内存存储引擎(引擎将来可插拔…),使用 slabs 来管理内存。内存被分成大小不等的 slabs chunks(先分成大小相等的 slabs,然后每个 slab 被分成大小相等 chunks,不同slab 的 chunk 大小是不相等的)。chunk的大小依次从一个最小数开始,按某个因子增长,直到达到最大的可能值。

memcached 与 redis 的区别

1、Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash等数据结构的存储。而 memcache 只支持简单数据类型,需要客户端自己处理复杂对象
2、Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用(PS:持久化在 rdb、aof)。 
3、由于 Memcache 没有持久化机制,因此宕机所有缓存数据失效。Redis 配置为持久化,宕机重启后,将自动加载宕机时刻的数据到缓存系统中。具有更好的灾备机制。
4、Memcache 可以使用 Magent 在客户端进行一致性 hash 做分布式。Redis 支持在服务器端做分布式(PS:Twemproxy/Codis/Redis-cluster 多种分布式实现方式) 
5、Memcached 的简单限制就是键(key)和 Value 的限制。最大键长为 250 个字符。可以接受的储存数据不能超过 1MB(可修改配置文件变大),因为这是典型 slab 的最大值,不适合虚拟机使用。而Redis 的 Key 长度支持到 512k。 
6、Redis 使用的是单线程模型,保证了数据按顺序提交。Memcache 需要使用cas 保证数据一致性。CAS(Check and Set)是一个确保并发一致性的机制,属于“乐观锁”范畴;原理很简单:拿版本号,操作,对比版本号,如果一致就操作,不一致就放弃任何操作cpu 利用。由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis 。
7、memcache 内存管理:使用 Slab Allocation。原理相当简单,预先分配一系列大小固定的组,然后根据数据大小选择最合适的块存储。避免了内存碎片。(缺点:不能变长,浪费了一定空间) 
memcached 默认情况下下一个 slab 的最大值为前一个的 1.25 倍。 
5、redis 内存管理: Redis 通过定义一个数组来记录所有的内存分配情况, Redis采用的是包装的 malloc/free,相较于 Memcached 的内存管理方法来说,要简单很多。由于 malloc 首先以链表的方式搜索已管理的内存中可用的空间分配,导致内存碎片比较多

二十一、Dubbo

Dubbo是什么

Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已成为 Apache 基金会孵化项目。 官网:http://dubbo.apache.org

Dubbo 支持分布式事务吗
目前暂时不支持,可与通过 tcc-transaction 框架实现
介绍:tcc-transaction 是开源的 TCC 补偿性分布式事务框架
Git 地址:https://github.com/changmingxie/tcc-transaction
TCC-Transaction 通过 Dubbo 隐式传参的功能,避免自己对业务代码的入侵。

在 Provider 上可以配置的 Consumer 端的属性有哪些
1)timeout:方法调用超时
2)retries:失败重试次数,默认重试 2 次
3)loadbalance:负载均衡算法,默认随机
4)actives 消费者端,最大并发调用限制

Dubbo有哪几种负载均衡策略,默认是哪种

  • Random LoadBalance 随机,按权重设置随机概率(默认)
  • RoundRobin LoadBalance 轮询,按公约后的权重设置轮询比率
  • LeastActive LoadBalance 最少活跃调用数,相同活跃数的随机
  • ConsistetHash LoadBalance 一致性Hash,相同参数的请求总是发到同一提供者

Dubbo服务之间的调用是阻塞的吗
默认是同步等待结果阻塞的,支持异步调用。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。

Dubbo 的整体架构设计有哪些分层

  • 接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业务设计对应的接口和实现
  • 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心
  • 服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和服务端的 Skeleton,以
    ServiceProxy 为中心,扩展接口为 ProxyFactory
  • 服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为
    RegistryFactory、Registry、RegistryService
  • 路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBlancce
  • 监控层(Monitor):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为
    MonitorFactory、Monitor 和 MonitorService
  • 远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、 Invoker 和 Exporter
  • 信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer
  • 网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec
  • 数据序列化层(Serialize):可复用的一些工具,扩展接口为 Serialization、ObjectInput、
    ObjectOutput 和 ThreadPool

Dubbo Monitor 实现原理
Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是先走 filter 链,然后才进行真正的业务逻辑处理。
默认情况下,在 consumer 和 provider 的 filter 链中都会有 Monitorfilter。

  • 1、MonitorFilter 向 DubboMonitor 发送数据
  • 2、DubboMonitor 将数据进行聚合后(默认聚合 1min 中的统计数据)暂存到
    ConcurrentMap statisticsMap,然后使用一个含有 3 个线程(线程名字:DubboMonitorSendTimer)的线程池每隔 1min 钟,调用 SimpleMonitorService 遍历发送 statisticsMap 中的统计数据,每发送完毕一个,就重置当前的 Statistics 的 AtomicReference
  • 3、SimpleMonitorService将这些聚合数据塞入 BlockingQueue queue 中(队列大写为 100000)
  • 4、SimpleMonitorService 使用一个后台线程(线程名为DubboMonitorAsyncWriteLogThread)将- queue 中的数据写入文件(该线程以死循环的形式来写)
  • 5、SimpleMonitorService 还会使用一个含有 1 个线程(线程名字:DubboMonitorTimer)的线程池每隔 5min 钟,将文件中的统计数据画成图表

Dubbo 用到哪些设计模式
Dubbo 框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。

工厂模式 
Provider 在 export 服务时,会调用 ServiceConfig的export方法。ServiceConfig中有个字段:
privatestaticfinalProtocolprotocol= 
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另 外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
装饰器模式 
Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
EchoFilter->ClassLoaderFilter->GenericFilter->ContextFilter-> 
ExecuteLimitFilter->TraceFilter->TimeoutFilter->MonitorFilter->
ExceptionFilter
更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。 
观察者模式 
Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。
动态代理模式 
Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类的主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。

二十二、Elasticsearch

elasticsearch 了解多少,说说 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段
是想了解之前公司接触的 ES 使用场景、规模,有没有做过比较大规模的索引设计、规划、调优。

比如:ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10 分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。

仅索引层面调优手段: 
1.1、设计阶段调优 
1、根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引;
2、使用别名进行索引管理; 
3、每天凌晨定时对索引做 force_merge 操作,以释放空间;
4、采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储; 
5、采取 curator 进行索引的生命周期管理; 
6、仅针对需要分词的字段,合理的设置分词器; 
7、Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等
1.2、写入调优
1、写入前副本数设置为 0; 
2、写入前关闭 refresh_interval 设置为-1,禁用刷新机制;
3、写入过程中:采取 bulk 批量写入; 
4、写入后恢复副本数和刷新间隔; 
5、尽量使用自动生成的 id。
1.3、查询调优
1、禁用 wildcard; 
2、禁用批量 terms(成百上千的场景); 
3、充分利用倒排索引机制,能 keyword 类型尽量 keyword;
4、数据量大时候,可以先基于时间敲定索引再检索;
5、设置合理的路由机制。
1.4、其他调优 
部署调优,业务调优等。 

elasticsearch 的倒排索引是什么

  • 传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。

  • 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。
    有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率

  • 学术的解答方式:

  • 倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。

加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。 
lucene 从 4+版本后开始大量使用的数据结构是 FST。FST 有两个优点: 
1、空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间; 
2、查询速度快。O(len(str))的查询时间复杂度。

elasticsearch 索引数据多了怎么办,如何调优,部署
索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。
如何调优,正如问题 1 所说,这里细化一下:

3.1 动态索引层面 
基于模板+时间+rollover api 滚动创建索引,举例:设计阶段定义:blog 索引的模板格式为:
blog_index_时间戳的形式,每天递增数据。
这样做的好处:不至于数据量激增导致单个索引数据量非常大,接近于上线 2 的32 次幂-1,索引存储达到了 TB+甚至更大。
一旦单个索引很大,存储等各种风险也随之而来,所以要提前考虑+及早避免。
3.2 存储层面 
冷热数据分离存储,热数据(比如最近 3 天或者一周的数据),其余为冷数据。对于冷数据不会再写入新数据,可以考虑定期 force_merge 加 shrink 压缩操作,节省存储空间和检索效率。
3.3 部署层面 
一旦之前没有规划,这里就属于应急策略。结合 ES 自身的支持动态扩展的特点,动态新增机器的方式可以缓解集群压力,注意:如果之前主节点等规划合理,不需要重启集群也能完成动态新增的。

elasticsearch 是如何实现 master 选举的

前置前提: 
1、只有候选主节点(master:true)的节点才能成为主节点。
2、最小主节点数(min_master_nodes)的目的是防止脑裂。

这个我看了各种网上分析的版本和源码分析的书籍,云里雾里。 

核对了一下代码,核心入口为 findMaster,选择主节点成功返回对应 Master,否则返回 null。选举流程大致描述如下: 
第一步:确认候选主节点数达标,elasticsearch.yml 设置的值 
discovery.zen.minimum_master_nodes;
第二步:比较:先判定是否具备 master 资格,具备候选主节点资格的优先返回;若两节点都为候选主节点,则 id 小的值会主节点。注意这里的 id 为 string 类型。题外话:获取节点 id 的方法。 
1GET /_cat/nodes?v&h=ip,port,heapPercent,heapMax,id,name 
2ip port heapPercent heapMax id name

详细描述一下 Elasticsearch 索引文档的过程

这里的索引文档应该理解为文档写入 ES,创建索引的过程。
文档写入包含:单文档写入和批量 bulk 写入,这里只解释一下:单文档写入流程。

  • 第一步:客户写集群某节点写入数据,发送请求。(如果没有指定路由/协调节点,请求的节点扮演路由节点的角色。)
  • 第二步:节点 1 接受到请求后,使用文档_id 来确定文档属于分片 0。请求会被转到另外的节点,假定节点 3。因此分片 0 的主分片分配到节点 3 上。
  • 第三步:节点 3 在主分片上执行写操作,如果成功,则将请求并行转发到节点 1和节点 2 的副本分片 上,等待结果返回。所有的副本分片都报告成功,节点 3 将向协调节点(节点 1)报告成功,节点 1 向请求客户端报告写入成功。
如果再问:第二步中的文档获取分片的过程? 
回答:借助路由算法获取,路由算法就是根据路由和文档 id 计算目标的分片 id 的过程。
1shard=hash(_routing)%(num_of_primary_shards)

详细描述一下 Elasticsearch 搜索的过程?
搜索拆解为“query then fetch” 两个阶段。
query 阶段的目的:定位到位置,但不取。

步骤拆解如下:

  • 1、假设一个索引数据有 5 主+1 副本共 10 分片,一次请求会命中(主或者副本分片中)的一个。
  • 2、每个分片在本地进行查询,结果返回到本地有序的优先队列中。
  • 3、第 2)步骤的结果发送到协调节点,协调节点产生一个全局的排序列表。fetch 阶段的目的:取数据。
    路由节点获取所有文档,返回给客户端。

Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法

  • 1、关闭缓存 swap;
  • 2、堆内存设置为:Min(节点内存/2, 32GB)
  • 3、设置最大文件句柄数;
  • 4、线程池+队列大小根据业务需要做调整;
  • 5、磁盘存储 raid 方式——存储有条件使用 RAID10,增加单节点性能以及避免单节点存储故障。

详细描述一下 Elasticsearch 更新和删除文档的过程。

  • 1、删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;
  • 2、磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del
    文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段。
  • 3、在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档 在.del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。

详细描述一下 Elasticsearch 索引文档的过程。
协调节点默认使用文档 ID 参与计算(也支持通过 routing),以便为路由提供合适的分片。
shard=hash(document_id)%(num_of_primary_shards)

  • 1、当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 Memory Buffer,然后定时(默认是每隔 1 秒)写入到 Filesystem Cache,这个从 MomeryBuffer 到 Filesystem Cache 的过程就叫做refresh;
  • 2、当然在某些情况下,存在 Momery Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是通过 translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;
  • 3、在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync将创建一个新的提交 点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
  • 4、flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默认为 512M)时
补充:关于 Lucene 的 Segement: 
1、Lucene 索引是由多个段组成,段本身是一个功能齐全的倒排索引。 
2、段是不可变的,允许 Lucene 将新的文档增量地添加到索引中,而不用从头重建索引。 
3、对于每一个搜索请求而言,索引中的所有段都会被搜索,并且每个段会消耗CPU 的时钟周、文件句柄和内存。这意味着段的数量越多,搜索性能会越低。 
4、为了解决这个问题,Elasticsearch 会合并小段到一个较大的段,提交新的合并段到磁盘,并删除那些旧的小段。

在并发情况下,Elasticsearch 如果保证读写一致?

  • 1、可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲
    突;
  • 2、另外对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。
  • 3、对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。

二十三、Linux

绝对路径用什么符号?当前目录、上层目录用什么表示?主目录用什么表示? 切换目录用什么命令?

绝对路径:如/etc/init.d 
当前目录和上层目录: ./ ../
主目录: ~/ 
切换目录: cd 

怎么查看当前进程?怎么执行退出?怎么查看当前路径?

查看当前进程: ps 
执行退出: exit 
查看当前路径: pwd

怎么清屏?怎么退出当前命令?怎么执行睡眠?怎么查看当前用户ID?查看指定帮助用什么命令?

清屏: clear 
退出当前命令: ctrl+c 彻底退出 
执行睡眠: ctrl+z 挂起当前进程 fg 恢复后台查看当前用户 id: ”id“:查看显示目前登陆账户的 uid 和gid 及所属分组及用户名 
查看指定帮助:如 man adduser 这个很全而且有例子; adduser --help 这个告诉你一些常用参数;
info adduesr;
Ls 命令执行什么功能?可以带哪些参数,有什么区别?
ls 执行的功能:列出指定目录中的目录,以及文件哪些参数以及区别: a 所有文件 l 详细信息,包括大小字节数,可读可写可执行的权限等

查看文件有哪些命令

vi 文件名 #编辑方式查看,可修改 
cat 文件名 #显示全部文件内容 
more 文件名 #分页显示文件内容 
less 文件名 #与 more 相似,更好的是可以往前翻页
tail 文件名 #仅查看尾部,还可以指定行数 
head 文件名 #仅查看头部,还可以指定行数

列举几个常用的Linux命令

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

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

Linux查看日志的命令有多种: tail、cat、tac、head、echo等,本文只介绍几种常用的方法。
1、tail 
最常用的一种查看方式 
命令格式: tail[必要参数][选择参数][文件] 
-f 循环读取 
-q 不显示处理信息 
-v 显示详细的处理信息 
-c<数目> 显示的字节数 
-n<行数> 显示行数 
-q, --quiet, --silent 从不输出给出文件名的首部 
-s, --sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒
例如:
Tail -n 10 test.log查询日志尾部最后10行的日志; 
Tail -n +10 test.log查询10行之后的所有日志; 
Tail -fn 10 test.log循环实时查看最后1000行记录(最常用的)
一般还会配合着grep搜索用,例如 :
tail-fn1000test.log|grep'关键字'
如果一次性查询的数据量太大,可以进行翻页查看,例如:
tail-n4700aa.log|more-1000可以进行多屏显示(ctrl+f或者空格键可以快捷键)
2、head 
跟tail是相反的head是看前多少行日志
head-n10test.log查询日志文件中的头10行日志; 
head-n-10test.log查询日志文件除了最后10行的其他所有日志;
head其他参数参考tail 
3、cat 
cat 是由第一行到最后一行连续显示在屏幕上
一次显示整个文件 :
$cat filename
从键盘创建一个文件 :
$cat > filename
将几个文件合并为一个文件:
$cat file1 file2 > file只能创建新文件,不能编辑已有文件
将一个日志文件的内容追加到另外一个 :
$cat -n textfile1>textfile2
清空一个日志文件:
$cat:>textfile2
注意:> 意思是创建,>>是追加。千万不要弄混了。 
cat其他参数参考tail 
4、more 
more命令是一个基于vi编辑器文本过滤器,它以全屏幕的方式按页显示文本文件的内容,支持vi中的关键字定位操作。more名单中内置了若干快捷键,常用的有H(获得帮助信息),Enter(向下翻滚一 行),空格(向下滚动一屏),Q(退出命令)。more命令从前向后读取文件,因此在启动时就加载整个文件。 
该命令一次显示一屏文本,满屏后停下来,并且在屏幕的底部出现一个提示信息,给出至今己显示的该文件的百分比:–More–(XX%) 
more的语法:more 文件名 
Enter 向下n行,需要定义,默认为1行 
Ctrl f 向下滚动一屏空格键向下滚动一屏 
Ctrl b 返回上一屏 
= 输出当前行的行号 
:f 输出文件名和当前行的行号
v 调用vi编辑器 
!命令调用Shell,并执行命令
q退出more
5、sed 
这个命令可以查找日志文件特定的一段 , 根据时间的一个范围查询,可以按照行号和时间范围查询按照行号
sed-n'5,10p'filename这样你就可以只查看文件的第5行到第10行。
按照时间段
sed-n'/2014-12-1716:17:20/,/2014-12-1716:17:36/p'test.log
6、less 
less命令在查询日志时,一般流程是这样的
lesslog.log 
shift+G命令到文件尾部然后输入?加上你要搜索的关键字例如?1213 
按n向上查找关键字 
shift+n反向查找关键字 
less与more类似,使用less可以随意浏览文件,而more仅能向前移动,不能向后移动,而且less在查看
之前不会加载整个文件。 
lesslog2013.log查看文件 
ps-ef|lessps查看进程信息并通过less分页显示 
history|less查看命令历史使用记录并通过less分页显示 
lesslog2013.loglog2014.log浏览多个文件
常用命令参数:
less与more类似,使用less可以随意浏览文件,而more仅能向前移动,不能向后移动,而且less在查看
之前不会加载整个文件。 
lesslog2013.log查看文件 
ps-ef|lessps查看进程信息并通过less分页显示 
history|less查看命令历史使用记录并通过less分页显示 
lesslog2013.loglog2014.log浏览多个文件 
常用命令参数: 
-b<缓冲区大小>设置缓冲区的大小 
-g只标志最后搜索的关键词 
-i忽略搜索时的大小写 
-m显示类似more命令的百分比 
-N显示每行的行号 
-o<文件名>将less输出的内容在指定文件中保存起来 
-Q不使用警告音 
-s显示连续空行为一行 
/字符串:向下搜索"字符串"的功能 
?字符串:向上搜索"字符串"的功能 
n:重复前一个搜索(与/或?有关) 
N:反向重复前一个搜索(与/或?有关)
b向后翻一页 
h显示帮助界面 
q退出less命令

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

History //所有的历史记录 
History |grepXXX//历史记录中包含某些指令的记录
History |more//分页查看记录 
History -c//清空所有的历史记录
!!重复执行上一个命令 
查询出来记录后选中:!323

建立软链接(快捷方式),以及硬链接的命令

软链接:ln-sslinksource 
硬链接:lnlinksource

目录创建用什么命令?创建文件用什么?复制文件用什么?

创建目录: mkdir 
创建文件:典型的如 touch,vi 也可以创建文件,其实只要向一个不存在的文件
输出,都会创建文件 
复制文件: cp 7. 文件权限修改用什么命令?格式是怎么样的? 
文件权限修改: chmod
格式如下: 
chmodu+xfile 给 file 的属主增加执行权限 chmod 751 file 给 file 的属主分配
读、写、执行(7)的权限,给 file 的所在组分配读、执行(5)的权限,给其他用户
分配执行(1)的权限 
chmodu=rwx,g=rx,o=xfile 上例的另一种形式 chmod =r file 为所有用户分配读权限 chmod444file 同上例 chmod a-wx,a+r file 同上例 
$ chmod -R u+r directory 递归地给 directory 目录下所有文件和子目录的属
主分配读的权限 

查看文件内容有哪些命令可以使用?

vi 文件名 #编辑方式查看,可修改 
cat 文件名 #显示全部文件内容 
more 文件名 #分页显示文件内容 
less 文件名 #与 more 相似,更好的是可以往前翻页
tail 文件名 #仅查看尾部,还可以指定行数 
head 文件名 #仅查看头部,还可以指定行数
随意写文件命令?怎么向屏幕输出带空格的字符串,比如”hello 
world”?
写文件命令:vi 
向屏幕输出带空格的字符串:echo hello world 

终端是哪个文件夹下的哪个文件?黑洞文件是哪个文件夹下的哪个命令?

终端 /dev/tty 
黑洞文件 /dev/null 

复制文件用哪个命令?如果需要连同文件夹一块复制呢?如果需要有提示功能呢?

cp cp -r ????

删除文件用哪个命令?如果需要连目录及目录下文件一块删除呢?删除空文件夹用什么命令?

rm rm -r rmdir 

Linux 下命令有哪几种可使用的通配符?分别代表什么含义?

? ”可替代单个字符。 
“*” 可替代任意多个字符。 
方括号“ [charset]” 可替代 charset 集中的任何单个字符,如 [a-z], [abABC]

用什么命令对一个文件的内容进行统计?(行号、单词数、字节数)

wc 命令 - c 统计字节数 - l 统计行数 - w 统计字数。

Grep 命令有什么用?如何忽略大小写?如何查找不含该串的行?

是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
grep [stringSTRING] filename grep [^string] filename

怎么使一个命令在后台运行?

一般都是使用 & 在命令结尾来让程序自动运行。(命令后可以不追加空格)

利用 ps 怎么显示所有的进程? 怎么利用 ps 查看指定进程的信息?

ps-ef(systemv输出)
ps-auxbsd格式输出 
ps-ef|greppid

哪个命令专门用来查看后台任务?

job -l

把后台任务调到前台执行使用什么命令?把停下的后台任务在后台执行起来用什么命令?

把后台任务调到前台执行 fg 
把停下的后台任务在后台执行起来 bg

Linux 中进程有哪几种状态?在 ps 显示出来的信息中分别用什么符号表示的?

1、不可中断状态:进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指进程不响应异步信号。 
2、暂停状态/跟踪状态:向进程发送一个 SIGSTOP 信号,它就会因响应该信号而进入 TASK_STOPPED 状态;当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。正被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。 
3、就绪状态:在 run_queue 队列里的状态 
4、运行状态:在 run_queue 队列里的状态 
5、可中断睡眠状态:处于这个状态的进程因为等待某某事件的发生(比如等待socket 连接、等待信号量),而被挂起 
6、zombie 状态(僵尸):父亲没有通过 wait 系列的系统调用会顺便将子进程的尸体(task_struct)
也释放掉 
7、退出状态 
D 不可中断 Uninterruptible(usually IO) 
R 正在运行,或在队列中的进程 
S 处于休眠状态 
T 停止或被追踪 
Z 僵尸进程 
W 进入内存交换(从内核 2.6 开始无效) 
X 死掉的进程

终止进程用什么命令? 带什么参数?

kill [-s <信息名称或编号>][程序]或 kill [-l <信息编号>]
kill-9 pid 

怎么查看系统支持的所有信号?

kill -l 

搜索文件用什么命令? 格式是怎么样的?

find <指定目录> <指定条件> <指定动作>
whereis 加参数与文件名 
locate 只加文件名 
find 直接搜索磁盘,较慢。 
find / -name "string*" 

查看当前谁在使用该主机用什么命令? 查找自己所在的终端信息用什么命令?

查找自己所在的终端信息:who am i 
查看当前谁在使用该主机:who 

使用什么命令查看用过的命令列表?

history 

使用什么命令查看磁盘使用空间?空闲空间呢?

df-hl 

使用什么命令查看网络是否连通?

Netstat

使用什么命令查看 ip 地址及接口信息?

Ifconfig

查看各类环境变量用什么命令

查看所有 env 
查看某个,如 home: env $HOME 

通过什么命令指定命令提示符?

\u:显示当前用户账号 
\h:显示当前主机名 
\W:只显示当前路径最后一个目录 
\w:显示当前绝对路径(当前用户目录会以~代替) 
$PWD:显示当前全路径 
$:显示命令行’$'或者’#'符号 
#:下达的第几个命令 
\d:代表日期,格式为 week day month date,例如:"MonAug1"
\t:显示时间为 24 小时格式,如:HH:MM:SS 
\T:显示时间为 12 小时格式 
\A:显示时间为 24 小时格式:HH:MM 
\v:BASH 的版本信息如 export PS1=’[\u@\h\w#]$‘ 

查找命令的可执行文件是去哪查找的? 怎么对其进行设置及添加?

whereis [-bfmsu][-B <目录 >...][-M <目录 >...][-S <目录 >...][文件 ...] 
补充说明: whereis 指令会在特定目录中查找符合条件的文件。这些文件的烈性应属
于原始代码,二进制文件,或是帮助文件。 
-b 只查找二进制文件。 
-B <目录> 只在设置的目录下查找二进制文件。 -f 不显示文件名前的路径名称。 
-m 只查找说明文件。 
-M <目录> 只在设置的目录下查找说明文件。-s 只查找原始代码文件。 
-S <目录> 只在设置的目录下查找原始代码文件。 -u 查找不包含指定类型的文件。 
w -h ich 指令会在 PATH 变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果。 
-n 指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名。 
-p 与-n 参数相同,但此处的包括了文件的路径。 -w 指定输出时栏位的宽度。 
-V 显示版本信息 

通过什么命令查找执行命令?

which 只能查可执行文件 
whereis 只能查二进制文件、说明文档,源文件等

怎么对命令进行取别名

aliasla='ls-a

du 和 df 的定义,以及区别?

du 显示目录或文件的大小 
df 显示每个<文件>所在的文件系统的信息,默认是显示所有文件系统。(文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如 i 节点,磁盘分布图,间接块,超级块等。这些数据对大多数用户级的程序来说是不可见的,通常称为 Meta Data。) du 命令是用户级的程序,它不考虑 Meta Data,而 df命令则查看文件系统的磁盘分配图并考虑 Meta Data。 
df 命令获得真正的文件系统数据,而 du 命令只查看文件系统的部分情况。

awk 详解。

awk'{pattern+action}'{filenames} 
#cat/etc/passwd|awk-F':''{print1"\t"7}'//-F的意思是以':'分隔root 
/bin/bash 
daemon/bin/sh搜索/etc/passwd有root关键字的所有行 
#awk-F:'/root/'/etc/passwdroot:x:0:0:root:/root:/bin/bash

当你需要给命令绑定一个宏或者按键的时候,应该怎么做呢?

可以使用 bind 命令,bind 可以很方便地在 shell 中实现宏或按键的绑定。 
在进行按键绑定的时候,我们需要先获取到绑定按键对应的字符序列。 
比如获取 F12 的字符序列获取方法如下:先按下 Ctrl+V,然后按下 F12 .我们就可
以得到 F12 的字符序列 ^[[24~。 
接着使用 bind 进行绑定
[root@localhost~]#bind‘”\e[24~":"date"'
注意:相同的按键在不同的终端或终端模拟器下可能会产生不同的字符序列。
【附】也可以使用 showkey -a 命令查看按键对应的字符序列。

如果你的助手想要打印出当前的目录栈,你会建议他怎么做?

使用 Linux 命令 dirs 可以将当前的目录栈打印出来。

你的系统目前有许多正在运行的任务,在不重启机器的条件下,有什么方法可以把所有正在运行的进程移除呢?

使用 linux 命令 ’disown -r ’可以将所有正在运行的进程移除。

bash shell 中的 hash 命令有什么作用?

linux 命令’hash’管理着一个内置的哈希表,记录了已执行过的命令的完整路径,用该命令可以打印出你所使用过的命令以及执行的次数。

哪一个 bash 内置命令能够进行数学运算

bash shell 的内置命令 let 可以进行整型数的数学运算

怎样一页一页地查看一个大文件的内容呢?

通过管道将命令”cat file_name.txt” 和 ’more’ 连接在一起可以实现这个需要
[root@localhost~]#cat file_name.txt | more

数据字典属于哪一个用户的?

数据字典是属于’SYS’用户的,用户‘SYS’ 和 ’SYSEM’是由系统默认自动创建的

怎样查看一个 linux 命令的概要与用法?假设你在/bin 目录中偶然看到一个你从没见过的的命令,怎样才能知道它的作用和用法呢?

使用命令 whatis 可以先出显示出这个命令的用法简要,比如,你可以使用 whatis zcat 去查看‘zcat’的介绍以及使用简要。
[root@localhost~]# whatiszcat 
zcat[gzip](1)–compressorexpandfiles

使用哪一个命令可以查看自己文件系统的磁盘空间配额呢

使用命令 repquota 能够显示出一个文件系统的配额信息
【附】只有 root 用户才能够查看其它用户的配额。

二十四、微服务

微服务

微服务,又称微服务架构,是一种架构风格,它将应用程序构建为以业务领域为模型的小型自治服务集合。 
通俗地说,你必须看到蜜蜂如何通过对齐六角形蜡细胞来构建它们的蜂窝状物。他们最初从使用各种材料的小部分开始,并继续从中构建一个大型蜂箱。这些细胞形成图案,产生坚固的结构,将蜂窝的特定部分固定在一起。这里,每个细胞独立于另一个细胞,但它也与其他细胞相关。这意味着对一个细胞的损害不会损害其他细胞,因此,蜜蜂可以在不影响完整蜂箱的情况下重建这些细胞。

微服务有哪些特点?

解耦 – 系统内的服务很大程度上是分离的。因此,整个应用程序可以轻松构建,更改和扩展 
组件化 – 微服务被视为可以轻松更换和升级的独立组件 
业务能力 – 微服务非常简单,专注于单一功能 
自治 – 开发人员和团队可以彼此独立工作,从而提高速度 
持续交付 – 通过软件创建,测试和批准的系统自动化,允许频繁发布软件 
责任 – 微服务不关注应用程序作为项目。相反,他们将应用程序视为他们负责的产品 
分散治理 – 重点是使用正确的工具来做正确的工作。这意味着没有标准化模式或任何技术模式。开发人员可以自由选择最有用的工具来解决他们的问题 
敏捷 – 微服务支持敏捷开发。任何新功能都可以快速开发并再次丢弃

微服务有哪些优点

独立开发 – 所有微服务都可以根据各自的功能轻松开发 
独立部署 – 基于其服务,可以在任何应用程序中单独部署它们 
故障隔离 – 即使应用程序的一项服务不起作用,系统仍可继续运行 
混合技术堆栈 – 可以使用不同的语言和技术来构建同一应用程序的不同服务
粒度缩放 – 单个组件可根据需要进行缩放,无需将所有组件缩放在一起

微服务架构如何运作

客户端 – 来自不同设备的不同用户发送请求。 
身份提供商 – 验证用户或客户身份并颁发安全令牌。 
API 网关 – 处理客户端请求。 
静态内容 – 容纳系统的所有内容。 
管理 – 在节点上平衡服务并识别故障。 
服务发现 – 查找微服务之间通信路径的指南。 
内容交付网络 – 代理服务器及其数据中心的分布式网络。
远程服务 – 启用驻留在 IT 设备网络上的远程访问信息。

微服务架构的优缺点是什么?
Java面试宝典-1_第6张图片

单片,SOA 和微服务架构有什么区别?

单片架构类似于大容器,其中应用程序的所有软件组件组装在一起并紧密封装。 
一个面向服务的架构是一种相互通信服务的集合。通信可以涉及简单的数据传递,也可以涉及两个或
多个协调某些活动的服务。 
微服务架构是一种架构风格,它将应用程序构建为以业务域为模型的小型自治服务集合。 

在使用微服务架构时,您面临哪些挑战?

开发一些较小的微服务听起来很容易,但开发它们时经常遇到的挑战如下。 
自动化组件:难以自动化,因为有许多较小的组件。因此,对于每个组件,我们必须遵循 Build, 
Deploy 和 Monitor 的各个阶段。 
易感性:将大量组件维护在一起变得难以部署,维护,监控和识别问题。它需要在所有组件周围具有
很好的感知能力。
配置管理:有时在各种环境中维护组件的配置变得困难。 
调试:很难找到错误的每一项服务。维护集中式日志记录和仪表板以调试问题至关重要。

SOA 和微服务架构之间的主要区别是什么?
Java面试宝典-1_第7张图片

什么是凝聚力?

模块内部元素所属的程度被认为是凝聚力。

什么是耦合?

组件之间依赖关系强度的度量被认为是耦合。一个好的设计总是被认为具有高内聚力和低耦合性。

什么是 REST / RESTful 以及它的用途是什么?

Representational State Transfer(REST)/ RESTful Web 服务是一种帮助计算机系统通过 Internet 进行通信的架构风格。这使得微服务更容易理解和实现。微服务可以使用或不使用 RESTful API 实现,但使用 RESTful API 构建松散耦合的微服务总是更容易。 

Spring Cloud 解决了哪些问题?

在使用 Spring Boot 开发分布式微服务时,我们面临的问题很少由 Spring Cloud解决。 
与分布式系统相关的复杂性 – 包括网络问题,延迟开销,带宽问题,安全问题。 
处理服务发现的能力 – 服务发现允许集群中的进程和服务找到彼此并进行通信。 
解决冗余问题 – 冗余问题经常发生在分布式系统中。 
负载平衡 – 改进跨多个计算资源(例如计算机集群,网络链接,中央处理单元)的工作负载分布。
减少性能问题 – 减少因各种操作开销导致的性能问题。 
 

在 Spring MVC 应用程序中使用 WebMvcTest 注释有什么用处?

在测试目标只关注 Spring MVC 组件的情况下,WebMvcTest 注释用于单元测试Spring MVC 应用程 序。在上面显示的快照中,我们只想启动 ToTestController。执行此单元测试时,不会启动所有其他控制器和映射。 

什么是不同类型的微服务测试?

在使用微服务时,由于有多个微服务协同工作,测试变得非常复杂。因此,测试分为不同的级别。
在底层,我们有面向技术的测试,如单元测试和性能测试。这些是完全自动化的。 
在中间层面,我们进行了诸如压力测试和可用性测试之类的探索性测试。 
在顶层,我们的验收测试数量很少。这些验收测试有助于利益相关者理解和验证软件功能。 

对 Distributed Transaction 有何了解?

分布式事务是指单个事件导致两个或多个不能以原子方式提交的单独数据源的突变的任何情况。在微服务的世界中,它变得更加复杂,因为每个服务都是一个工作单元,并且大多数时候多个服务必须协同工作才能使业务成功。 

什么是 Idempotence 以及它在哪里使用?

幂等性是能够以这样的方式做两次事情的特性,即最终结果将保持不变,即好像它只做了一次。 
用法:在远程服务或数据源中使用 Idempotence,这样当它多次接收指令时,它只处理指令一次。

什么是有界上下文?

有界上下文是域驱动设计的核心模式。DDD 战略设计部门的重点是处理大型模型和团队。DDD 通过将大型模型划分为不同的有界上下文并明确其相互关系来处理大型模型。

什么是客户证书?

客户端系统用于向远程服务器发出经过身份验证的请求的一种数字证书称为客户端证书。客户端证书在许多相互认证设计中起着非常重要的作用,为请求者的身份提供了强有力的保证。

PACT 在微服务架构中的用途是什么?

PACT 是一个开源工具,允许测试服务提供者和消费者之间的交互,与合同隔离,从而提高微服务集成的可靠性。 
微服务中的用法 
用于在微服务中实现消费者驱动的合同。 
测试微服务的消费者和提供者之间的消费者驱动的合同。 
查看即将到来的批次 

什么是 OAuth?

OAuth 代表开放授权协议。这允许通过在 HTTP 服务上启用客户端应用程序(例如第三方提供商 
Facebook,GitHub 等)来访问资源所有者的资源。因此,您可以在不使用其凭据的情况下与另一个站点共享存储在一个站点上的资源。 

什么是端到端微服务测试?

端到端测试验证了工作流中的每个流程都正常运行。这可确保系统作为一个整体协同工作并满足所有要求。 
通俗地说,你可以说端到端测试是一种测试,在特定时期后测试所有东西。

Container 在微服务中的用途是什么?

容器是管理基于微服务的应用程序以便单独开发和部署它们的好方法。您可以将微服务封装在容器映像及其依赖项中,然后可以使用它来滚动按需实例的微服务,而无需任何额外的工作。

什么是消费者驱动的合同(CDC)?

这基本上是用于开发微服务的模式,以便它们可以被外部系统使用。当我们处理微服务时,有一个特定的提供者构建它,并且有一个或多个使用微服务的消费者。通常,提供程序在 XML 文档中指定接口。但在消费者驱动的合同中,每个服务消费者都传达了提供商期望的接口。 

Web,RESTful API 在微服务中的作用是什么?

微服务架构基于一个概念,其中所有服务应该能够彼此交互以构建业务功能。因此,要实现这一点,每个微服务必须具有接口。这使得 Web API 成为微服务的一个非常重要的推动者。RESTful API 基于 Web 的开放网络原则,为构建微服务架构的各个组件之间的接口提供了最合理的模型。 

您对微服务架构中的语义监控有何了解?

语义监控,也称为综合监控,将自动化测试与监控应用程序相结合,以检测业务失败因素。

我们如何在测试中消除非决定论?

非确定性测试(NDT)基本上是不可靠的测试。所以,有时可能会发生它们通过,显然有时它们也可能会失败。当它们失败时,它们会重新运行通过。 从测试中删除非确定性的一些方法如下: 
1、隔离 
2、异步 
3、远程服务 
4、隔离 
5、时间 
6、资源泄漏 
 Mock 或 Stub 有什么区别?
存根 
一个有助于运行测试的虚拟对象。 
在某些可以硬编码的条件下提供固定行为。 
永远不会测试存根的任何其他行为。 
例如,对于空堆栈,您可以创建一个只为 empty()方法返回 true 的存根。因此,这并不关心堆栈中是否存在元素。 
嘲笑 
一个虚拟对象,其中最初设置了某些属性。 
此对象的行为取决于 set 属性。 
也可以测试对象的行为。 
例如,对于 Customer 对象,您可以通过设置名称和年龄来模拟它。您可以将 age设置为 12,然后测试 isAdult()方法,该方法将在年龄大于 18 时返回 true。因此,您的 Mock Customer 对象适用于指定的条件。

Docker 的目的是什么?

Docker 提供了一个可用于托管任何应用程序的容器环境。在此,软件应用程序和支持它的依赖项紧密打包在一起。因此,这个打包的产品被称为 Container,因为它是由 Docker 完成的,所以它被称为 Docker 容器!

什么是金丝雀释放?

Canary Releasing 是一种降低在生产中引入新软件版本的风险的技术。这是通过将变更缓慢地推广到一小部分用户,然后将其发布到整个基础架构,即将其提供给每个人来完成的。 

什么是持续集成(CI)?

持续集成(CI)是每次团队成员提交版本控制更改时自动构建和测试代码的过程。这鼓励开发人员通过在每个小任务完成后将更改合并到共享版本控制存储库来共享代码和单元测试。 

什么是持续监测?

持续监控深入监控覆盖范围,从浏览器内前端性能指标,到应用程序性能,再到主机虚拟化基础架构指标。 

架构师在微服务架构中的角色是什么?

微服务架构中的架构师扮演以下角色: 
决定整个软件系统的布局。 
帮助确定组件的分区。因此,他们确保组件相互粘合,但不紧密耦合。
与开发人员共同编写代码,了解日常生活中面临的挑战。 
为开发微服务的团队提供某些工具和技术的建议。 
提供技术治理,以便技术开发团队遵循微服务原则。

我们可以用微服务创建状态机吗?

我们知道拥有自己的数据库的每个微服务都是一个可独立部署的程序单元,这反过来又让我们可以创建一个状态机。因此,我们可以为特定的微服务指定不同的状态和事件。 
例如,我们可以定义 Order 微服务。订单可以具有不同的状态。Order 状态的转换可以是 Order 微服务中的独立事件。 

什么是微服务中的反应性扩展?

Reactive Extensions 也称为 Rx。这是一种设计方法,我们通过调用多个服务来收集结果,然后编译组合响应。这些调用可以是同步或异步,阻塞或非阻塞。Rx是分布式系统中非常流行的工具,与传统流程相反。希望这些微服务面试问题可以帮助您进行微服务架构师访谈。 
翻译来源: 
https://www.edureka.co/blog/interview-questions/microservices-interview-questions/ 

二十五、算法

数据结构

栈(stack)
栈( stack)是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶(top)。它是后进先出(LIFO)的。对栈的基本操作只有 push(进栈)和 pop(出栈)两种,前者相当于插入,后者相当于删除最后的元素。
队列(queue)
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
链表(Link)
链表是一种数据结构,和数组同级。比如, Java 中我们使用的 ArrayList,其实现原理是数组。而 LinkedList 的实现原理就是链表了。
链表在进行循环遍历时效率不高,但是插入和删除时优势明显。
散列表(Hash Table)
散列表(Hash table,也叫哈希表)是一种查找算法,与链表、树等算法不同的是,散列表算法在查找时不需要进行一系列和关键字(关键字是数据元素中某个数据项的值,用以标识一个数据元素)的比较操作。 
散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必须要在数据元素的存储位置和它的关键字(可用 key 表示)之间建立一个确定的对应关系,使每个关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定关键字在散列表中的位置即可。这种对应关系被称为散列函数(可用 h(key)表示)。 
用的构造散列函数的方法有:
1)直接定址法:取关键字或关键字的某个线性函数值为散列地址。
即: h(key) = key 或 h(key) = a * key + b,其中 a 和 b 为常数。
(2)数字分析法 
(3)平方取值法:取关键字平方后的中间几位为散列地址。 
(4)折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
(5)除留余数法:取关键字被某个不大于散列表表长 m 的数 p 除后所得的余数为散列地址,
即: h(key) = key MOD p p ≤ m
(6)随机数法:选择一个随机函数,取关键字的随机函数值为它的散列地址,
即: h(key) = random(key)
排序二叉树
首先如果普通二叉树每个节点满足:左子树所有节点值小于它的根节点值,且右子树所有节点值大于它的根节点值,则这样的二叉树就是排序二叉树。

插入操作
首先要从根节点开始往下找到自己要插入的位置(即新节点的父节点);具体流程是:新节点与当前节点比较,如果相同则表示已经存在且不能再重复插入;如果小于当前节点,则到左子树中寻找,如果左子树为空则当前节点为要找的父节点,新节点插入到当前节点的左子树即可;如果大于当前节点,则到右子树中寻找,如果右子树为空则当前节点为要找的父节点,新节点插入到当前节点的右子树即可。

删除操作
删除操作主要分为三种情况,即要删除的节点无子节点,要删除的节点只有一个子节点,要删除的节点有两个子节点。
1. 对于要删除的节点无子节点可以直接删除,即让其父节点将该子节点置空即可。 
2. 对于要删除的节点只有一个子节点,则替换要删除的节点为其子节点。 
3. 对于要删除的节点有两个子节点,则首先找该节点的替换节点(即右子树中最小的节点),接着替换要删除的节点为替换节点,然后删除替换节点。

查询操作 
查找操作的主要流程为:先和根节点比较,如果相同就返回,如果小于根节点则到左子树中归查找,如果大于根节点则到右子树中递归查找。因此在排序二叉树中可以很容易获取最大(最右最深子节点)和最小(最左最深子节点)值 
前缀树
前缀树(Prefix Trees 或者 Trie)与树类似,用于处理字符串相关的问题时非常高效。它可以实现快速检索,常用于字典中的单词查询,搜索引擎的自动补全甚至 IP 路由。 
红黑树
红黑树的特性
(1)每个节点或者是黑色,或者是红色。 
(2)根节点是黑色。 
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL 或NULL)的叶子节点! ] 
(4)如果一个节点是红色的,则它的子节点必须是黑色的。 
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 

左旋 
对 x 进行左旋,意味着,将“x 的右孩子”设为“x 的父亲节点”;即,将 x 变成了一个左节点(x成了为 z 的左孩子)!。因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”。
LEFT-ROTATE(T,x) 
y←right[x]//前提:这里假设x的右孩子为y。下面开始正式操作 
right[x]←left[y]//将“y的左孩子”设为“x的右孩子”,即将β设为x的右孩子 
p[left[y]]←x//将“x”设为“y的左孩子的父亲”,即将β的父亲设为x 
p[y]←p[x]//将“x的父亲”设为“y的父亲” 
ifp[x]=nil[T] 
thenroot[T]←y//情况1:如果“x的父亲”是空节点,则将y设为根节点 
elseifx=left[p[x]] 
thenleft[p[x]]←y//情况2:如果x是它父节点的左孩子,则将y设为“x的父节点 
的左孩子” 
elseright[p[x]]←y//情况3:(x是它父节点的右孩子)将y设为“x的父节点的右孩
子” 
left[y]←x//将“x”设为“y的左孩子” 
p[x]←y//将“x的父节点”设为“y”


右旋
对 x 进行右旋,意味着,将“x 的左孩子”设为“x 的父亲节点”;即,将 x 变成了一个右节点(x成了为 y 的右孩子)!因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。
RIGHT-ROTATE(T,y) 
x←left[y]//前提:这里假设y的左孩子为x。下面开始正式操作 
left[y]←right[x]//将“x的右孩子”设为“y的左孩子”,即将β设为y的左孩子 
p[right[x]]←y//将“y”设为“x的右孩子的父亲”,即将β的父亲设为y 
p[x]←p[y]//将“y的父亲”设为“x的父亲” 
ifp[y]=nil[T] 
thenroot[T]←x//情况1:如果“y的父亲”是空节点,则将x设为根节点 
elseify=right[p[y]] 
thenright[p[y]]←x//情况2:如果y是它父节点的右孩子,则将x设为“y的父节 
点的左孩子” 
elseleft[p[y]]←x//情况3:(y是它父节点的左孩子)将x设为“y的父节点的左孩
子” 
right[x]←y//将“y”设为“x的右孩子” 
p[y]←x//将“y的父节点”设为“x”

添加
第一步: 将红黑树当作一颗二叉查找树,将节点插入。 
第二步:将插入的节点着色为"红色"。 
根据被插入节点的父节点的情况,可以将"当节点 z 被着色为红色节点,并插入二叉树"划分为三种情况来处理。 
①情况说明:被插入的节点是根节点。 
处理方法:直接把此节点涂为黑色。 
②情况说明:被插入的节点的父节点是黑色。 
处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③情况说明:被插入的节点的父节点是红色。这种情况下,被插入节点是一定存在非空祖父节点
的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节
点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为 3 
种情况(Case)

Java面试宝典-1_第8张图片


第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
删除
第一步:将红黑树当作一颗二叉查找树,将节点删除。 
这和"删除常规二叉查找树中删除节点的方法是一样的"。分 3 种情况: 
①被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就 OK 了。 
②被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。 
③被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。 
第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正
该树,使之重新成为一棵红黑树。 
选择重着色 3 种情况。 
①情况说明: x 是“红+黑”节点。 
处理方法:直接把 x 设为黑色,结束。此时红黑树性质全部恢复。 
②情况说明: x 是“黑+黑”节点,且 x 是根。 
处理方法:什么都不做,结束。此时红黑树性质全部恢复。 
③情况说明: x 是“黑+黑”节点,且 x 不是根。 
处理方法:这种情况又可以划分为 4 种子情况。这 4 种子情况如下表所示:

Java面试宝典-1_第9张图片

位图
位图的原理就是用一个 bit 来标识一个数字是否存在,采用一个 bit 来存储一个数据,所以这样可以大
大的节省空间。 bitmap 是很常用的数据结构,比如用于 Bloom Filter 中;用于无重复整数的排序等等。 bitmap 通常基于数组来实现,数组中每个元素可以看成是一系列二进制数,所有元素组成更大的二进制集合。 
B-TREE
B-tree 又叫平衡多路查找树。一棵 m 阶的 B-tree (m 叉树)的特性如下(其中 ceil(x)是一个取上限的函数):
1. 树中每个结点至多有 m 个孩子; 
2. 除根结点和叶子结点外,其它每个结点至少有有 ceil(m / 2)个孩子; 
3. 若根结点不是叶子结点,则至少有 2 个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结
点,整棵树只有一个根节点); 
4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部结点或查询失败
的结点,实际上这些结点不存在,指向这些结点的指针都为 null); 
5. 每个非终端结点中包含有 n 个关键字信息: (n, P0, K1, P1, K2, P2, ......, Kn, Pn)。其中: 
a) Ki (i=1...n)为关键字,且关键字按顺序排序 K(i-1)< Ki。 
b) Pi 为指向子树根的接点,且指针 P(i-1)指向子树种所有结点的关键字均小于 Ki,但都大于 K(i-
1)。 
c) 关键字的个数 n 必须满足: ceil(m / 2)-1 <= n <= m-1。
一棵 m 阶的 B+tree 和 m 阶的 B-tree 的差异在于: 
1.有 n 棵子树的结点中含有 n 个关键字; (B-tree 是 n 棵子树有 n-1 个关键字) 
2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (B-tree 的叶子节点并没有包括全部需要查找的信息) 
3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。(B- 
tree 的非终节点也包含需要查找的有效信息)

算法

****数据里有{1,2,3,4,5,6,7,8,9},请随机打乱顺序,生成一个新的数组(请以代码实现)****

importjava.util.Arrays;
	//打乱数组 
	public class Demo1{
	//随机打乱 
		public static int[] srand(int[] a){
			int[]b=newint[a.length]; 
			for(int i=0;i<a.length;i++){
				//随机获取下标 
				int tmp=(int)(Math.random()*(a.length-i));//随机数:[0,a-length-1)
				b[i]=a[tmp]; 
				//将此时a[tmp]的下标移动到靠后的位置 
				int change=a[a.length-i-1]; 
				a[a.length-i-1]=a[tmp]; 
				a[tmp]=change;
			} 
			return b;
		} 
		
		public static void main(String[] args){
			Int[]a={1,2,3,4,5,6,7,8,9};
			System.out.println(Arrays.toString(srand(a)));
		}
			
	}
写出代码判断一个整数是不是2的阶次方(请代码实现,谢绝调用API方法)
importjava.util.Scanner;

	//判断整数是不是2的阶次方 
	public class Demo2{
		public static boolean check(int sum){
			boolean flag=true;//判断标志
			while(sum>1){
				if(sum%2==0){
					sum=sum/2;
				}else{
					flag=false;
					break;
				}
			} 
			returnflag;
		} 
		
		public static void main(String[] args){
			Scanners canner=new Scanner(System.in); 
			System.out.println("请输入一个整数:"); 
			intsum=scanner.nextInt(); 
			System.out.println(sum+"是不是2的阶次方:"+check(sum));
		}
	}
假设今日是201531日,星期日,请算出13个月零6天后是星期几,距离现在多少天(用代码实现,谢绝调用API方法)

importjava.util.Scanner;
//算出星期几 
public class Demo4{
	public static String[]week={"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};

	public static int i=0; 
	public static int[] monthday1={0,31,28,31,30,31,30,31,31,30,31,30,31}; 
	public static int[] monthday2={0,31,29,31,30,31,30,31,31,30,31,30,31}; 
	//查看距离当前天数的差值 
	public static String distance(int year,int month,in tday,int newMonth,int newDay){
		int sum=0;//设定初始距离天数
		if(month+newMonth>=12){
			if(((year+1)%4==0&&(year+1)%100!=0)||(year+1)%400==0){
				sum+=366+newDay; 
				for(int i=0;i<newMonth-12;i++){
					sum+=monthday1[month+i];
				}
			}else{
				sum+=365+newDay; 
				for(inti=0;i<newMonth-12;i++){
					sum+=monthday1[month+i];
				}
			}
		} else{
			for(inti=0;i<newMonth;i++){
				sum+=monthday1[month+i];
			} 
			sum+=newDay;
		} 
		returnweek[sum%7];
} 

public static void main(String[] args){
	Scanner scanner=new Scanner(System.in); 
	System.out.println("请输入当前年份"); 
	int year=scanner.nextInt(); 
	System.out.println("请输入当前月份"); 
	int month=scanner.nextInt(); 
	System.out.println("请输入当前天数"); 
	int day=scanner.nextInt(); 
	System.out.println("请输入当前是星期几:以数字表示,如:星期天为0"); 
	int index=scanner.nextInt(); 
	System.out.println("今天是:"+year+"-"+month+"-"+day+""+ 
	week[index]); 
	System.err.println("请输入相隔月份"); 
	int newMonth=scanner.nextInt(); 
	System.out.println("请输入剩余天数"); 
	int newDay=scanner.nextInt(); 
	System.out.println("经过"+newMonth+"月"+newDay+"天后,是"+ 
	distance(year,month,day,newMonth,newDay)); 
}
有两个篮子,分别为AB,篮子A里装有鸡蛋,篮子B里装有苹果,请用面向对象的思想实现两个篮子里的物品交换
//面向对象思想实现篮子物品交换
Public class Demo5{
	Public static void main(String[] args){
		//创建篮子 
		Basket A=new Basket("A");
		Basket B=new Basket("B");
		//装载物品 
		A.load("鸡蛋"); 
		B.load("苹果"); 
		//交换物品 
		A.change(B); 
		A.show();
		B.show();
	}
}

Class Basket{
	Public String name;//篮子名称 
	Private Goods goods;//篮子中所装物品
	Public Basket(String name){
	//TODOAuto-generatedconstructorstub 
		this.name=name; 
		System.out.println(name+"篮子被创建");
	} 

	//装物品函数 
	Public void load(String name){
		goods=new Goods(name); 
		System.out.println(this.name+"装载了"+name+"物品");
	} 

	Public void change(Basket B){
		System.out.println(this.name+"和"+B.name+"中的物品发生了交换");
		String tmp=this.goods.getName(); 
		this.goods.setName(B.goods.getName()); 
		B.goods.setName(tmp);
	}


	Public void show(){
		System.out.println(this.name+"中有"+goods.getName()+"物品");
	}

	Class Goods{
		Private String name;//物品名称
		Public String getName(){
			Return name;
		}
	}

	Public void setName(String name){
		this.name=name;
	}
	
	Public Goods(String name){
		//TODOAuto-generatedconstructorstub 
		this.name=name;
	}
}
二分查找
又叫折半查找,要求待查找的序列有序。每次取中间位置的值与待查关键字比较,如果中间位置的值比待查关键字大,则在前半部分循环这个查找的过程,如果中间位置的值比待查关键字小,则在后半部分循环这个查找的过程。直到查找到了为止,否则序列中没有待查的关键字。

public static int biSearch(int[] array,int a){
	intlo=0; 
	inthi=array.length-1;
	intmid; 
	while(lo<=hi){
		mid=(lo+hi)/2;//中间位置
		if(array[mid]==a){
			returnmid+1; 
		}else if(array[mid]<a){//向右查找
			lo=mid+1; 
		}else{//向左查找
			hi=mid-1;
		}
	} 
	return-1;
}
冒泡排序算法
1)比较前后相邻的二个数据,如果前面数据大于后面的数据,就将这二个数据交换。
(2)这样对数组的第 0 个数据到 N-1 个数据进行一次遍历后,最大的一个数据就“沉” 到数组第N-1 个位置。
(3N=N-1,如果 N 不为 0 就重复前面二步,否则排序完成。

public static void bubbleSort1(int[] a,int n){
	Int i,j; 
	for(i=0;i<n;i++){//表示n次排序过程。
		for(j=1;j<n-i;j++){
			if(a[j-1]>a[j]){//前面的数字大于后面的数字就交换
				//交换a[j-1]和a[j]
				Int temp; 
				temp=a[j-1]; 
				a[j-1]=a[j]; 
				a[j]=temp;
			}
		}
	}
}
插入排序算法
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。插入排序非常类似于整扑克牌。在开始摸牌时,左手是空的,牌面朝下放在桌上。接着,一次从桌上摸起一张牌,并将它插入到左手一把牌中的正确位置上。为了找到这张牌的正确位置,要将它与手中已有的牌从右到左地进行比较。无论什么时候,左手中的牌都是排好序的。如果输入数组已经是排好序的话,插入排序出现最佳情况,其运行时间是输入规模的一个线性函数。如果输入数组是逆序排列的,将出现最坏情况。平均情况与最坏情况一样,其时间代价是(n2)Public void sort(int arr[]){
	for(inti=1;i<arr.length;i++){
		//插入的数 
		intinsertVal=arr[i]; 
		//被插入的位置(准备和前一个数比较) 
		intindex=i-1; 
		//如果插入的数比被插入的数小 
		while(index>=0&&insertVal<arr[index]){
			//将把arr[index]向后移动
			arr[index+1]=arr[index];
			//让index向前移动 
			index--;
		} 
		//把插入的数放入合适位置 
		arr[index+1]=insertVal;
	}
}
快速排序算法
快速排序的原理:选择一个关键值作为基准值。比基准值小的都在左边序列(一般是无序的),比基准值大的都在右边(一般是无序的)。一般选择序列的第一个元素。
一次循环:从后往前比较,用基准值和最后一个值比较,如果比基准值小的交换位置,如果没有继续比较下一个,直到找到第一个比基准值小的值才交换。找到这个值之后,又从前往后开始比较,如果有比基准值大的,交换位置,如果没有继续比较下一个,直到找到第一个比基准值大的值才交换。

Public void sort(int[] a,int low,int high){
	intstart=low; 
	intend=high; 
	intkey=a[low];
	
	while(end>start){
		//从后往前比较 
		while(end>start&&a[end]>=key) 
		//如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较
		end--;
		if(a[end]<=key){
			inttemp=a[end];
			a[end]=a[start];
			a[start]=temp;
		} 
	}
	
	//从前往后比较 
	while(end>start&&a[start]<=key){
		//如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
		start++; 
		if(a[start]>=key){
			inttemp=a[start];
			a[start]=a[end]; 
			a[end]=temp;
		}
	} 
	
	//此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大
	//但是两边的顺序还有可能是不一样的,进行下面的递归调用
	
	//递归 
	if(start>low)sort(a,low,start-1);//左边序列。第一个索引位置到关键值索引-1 
	if(end<high)sort(a,end+1,high);//右边序列。从关键值索引+1到最后一个
}
希尔排序算法
基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序” 时,再对全体记录进行依次直接插入排序。
1. 操作方法:选择一个增量序列 t1, t2, …, tk,其中 ti>tj, tk=12. 按增量序列个数 k,对序列进行 k 趟排序; 
3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

Private void shellSort(int[] a){
	intdk=a.length/2;
	while(dk>=1){
		ShellInsertSort(a,dk);
		dk=dk/2;
	} 
}

Private void ShellInsertSort(int[] a,int dk){
	//类似插入排序,只是插入排序增量是1,这里增量是dk,把1换成dk就可以了
	for(inti=dk;i<a.length;i++){
		if(a[i]<a[i-dk]){
			intj; 
			intx=a[i];//x为待插入元素 
			a[i]=a[i-dk]; 
			for(j=i-dk;j>=0&&x<a[j];j=j-dk){
				//通过循环,逐个后移一位找到要插入的位置。
				a[j+dk]=a[j];
			} 
			a[j+dk]=x;//插入
		}
	}
}
归并排序算法
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
Public class MergeSortTest{
	Public static void main(String[] args){
		int[] data=new int[]{5,3,6,2,1,9,4,8,7};
		print(data); 
		mergeSort(data); 
		System.out.println("排序后的数组:"); 
		print(data);
	} 
	
	Public static void mergeSort(int[] data){
		sort(data,0,data.length-1);
	} 
	
	Public static void sort(int[] data,int left,int right){
		if(left>=right) 
		return; 
		//找出中间索引 
		int center=(left+right)/2; 
		//对左边数组进行递归 
		sort(data,left,center); 
		//对右边数组进行递归 
		sort(data,center+1,right); 
		//合并 
		merge(data,left,center,right);
		print(data);
	} 
}
/** 
*将两个数组进行归并,归并前面2个数组已有序,归并后依然有序 
* 
*@paramdata 
*数组对象 
*@paramleft 
*左数组的第一个元素的索引 
*@paramcenter 
*左数组的最后一个元素的索引,center+1是右数组第一个元素的索引 
*@paramright 
*右数组最后一个元素的索引 
*/ 
public static void merge(int[]data,int left,int center,int right){
	//临时数组 
	int[]tmpArr=newint[data.length];
	//右数组第一个元素索引 
	intmid=center+1; 
	//third记录临时数组的索引 
	intthird=left; 
	//缓存左数组第一个元素的索引 
	inttmp=left; 
	while(left<=center&&mid<=right){
		//从两个数组中取出最小的放入临时数组
		if(data[left]<=data[mid]){
			tmpArr[third++]=data[left++];
		}else{
			tmpArr[third++]=data[mid++];
		} 
	} 
	//剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
	while(mid<=right){
		tmpArr[third++]=data[mid++];
	} 
	while(left<=center){
		tmpArr[third++]=data[left++];
	} 
	//将临时数组中的内容拷贝回原数组中
	//(原left-right范围的内容被复制回原数组)
	while(tmp<=right){
		data[tmp]=tmpArr[tmp++];
	}
}

	//最后一步
	Public static void print(int[]data){
		for(int i=0;i<data.length;i++){
			System.out.print(data[i]+"\t");
		} 
		System.out.println();
	}
桶排序算法
桶排序的基本思想是:把数组 arr 划分为 n 个大小相同子区间(桶),每个子区间各自排序,最后合并。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。 
1.找出待排序数组中的最大值 max、最小值 min 
2.我们使用动态数组 ArrayList 作为桶,桶里放的元素也用 ArrayList 存储。桶的数量为 
(maxmin)/arr.length+1 
3.遍历数组 arr,计算每个元素 arr[i] 放的桶 
4.每个桶各自排序

public static void bucketSort(int[] arr){
	int max=Integer.MIN_VALUE; 
	int min=Integer.MAX_VALUE; 
	for(int i=0;i<arr.length;i++){
		max=Math.max(max,arr[i]);
		min=Math.min(min,arr[i]);
	} 
	//创建桶
	int bucketNum=(max-min)/arr.length+1; 
	ArrayList<ArrayList<Integer>> bucketArr=new ArrayList<>(bucketNum);
	for(int i=0;i<bucketNum;i++){
		bucketArr.add(newArrayList<Integer>());
	} 
	//将每个元素放入桶 
	for(int i=0;i<arr.length;i++){
		intnum=(arr[i]-min)/(arr.length); 
		bucketArr.get(num).add(arr[i]); 
	} 
	//对每个桶进行排序 
	for(int i=0;i<bucketArr.size();i++){
		Collections.sort(bucketArr.get(i));
	}
}
基数排序算法
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
Public class radixSort{
	inta[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,101,56,17,18,23,34,15,35,25,53,51};
	
	Public radixSort(){
		sort(a); 
		for(int i=0;i<a.length;i++){
			System.out.println(a[i]);
		}
	}
} 


public void sort(int[] array){
	//首先确定排序的趟数; 
	int max=array[0]; 
	for(int i=1;i<array.length;i++){
		if(array[i]>max){
			max=array[i];
		} 
	} 
	Int time=0; 
	//判断位数; 
	while(max>0){
		max/=10;
		time++;
	} 
	
	//建立10个队列; 
	List<ArrayList> queue=new ArrayList<ArrayList>();
	for(inti=0;i<10;i++){
		ArrayList<Integer> queue1=new ArrayList<Integer>();
		queue.add(queue1);
	} 
	
	//进行time次分配和收集; 
	for(inti=0;i<time;i++){
		//分配数组元素; 
		for(int j=0;j<array.length;j++){
			//得到数字的第time+1位数; 
			intx=array[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10,i);
			ArrayList<Integer> queue2=queue.get(x); 
			queue2.add(array[j]); 
			queue.set(x,queue2);
		} 
		intcount=0;//元素计数器;
		//收集队列元素; 
		for(intk=0;k<10;k++){
			while(queue.get(k).size()>0){
				ArrayList<Integer> queue3=queue.get(k);
				array[count]=queue3.get(0); 
				queue3.remove(0); 
				count++;
			}
		}
	}
}
剪枝算法
在搜索算法中优化中,剪枝,就是通过某种判断,避免一些不必要的遍历过程,形象的说,就是剪去了搜索树中的某些“枝条”,故称剪枝。应用剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
回溯算法
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。 
最短路径算法
从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径叫做最短路
径。解决最短路的问题有以下算法, Dijkstra 算法, Bellman-Ford 算法, Floyd 算法和 SPFA算法等
AES
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥
RSA
RSA 加密算法是一种典型的非对称加密算法,它基于大数的因式分解数学难题,它也是应用最广泛的非对称加密算法。 
非对称加密是通过两个密钥(公钥-私钥)来实现对数据的加密和解密的。公钥用于加密,私钥用于解密。
最小生成树算法
现在假设有一个很实际的问题:我们要在 n 个城市中建立一个通信网络,则连通这 n 个城市需要布置 n-1 一条通信线路,这个时候我们需要考虑如何在成本最低的情况下建立这个通信网? 
于是我们就可以引入连通图来解决我们遇到的问题, n 个城市就是图上的 n 个顶点,然后,边表示两个城市的通信线路,每条边上的权重就是我们搭建这条线路所需要的成本,所以现在我们有 n 个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。 
构造最小生成树有很多算法,但是他们都是利用了最小生成树的同一种性质: MST 性质(假设N=(V, 
{E})是一个连通网, U 是顶点集 V 的一个非空子集,如果(u, v)是一条具有最小权值的边,其中 u 属于 U, v 属于 V-U,则必定存在一颗包含边(u, v)的最小生成树),下面就介绍两种使用 MST 性质生成最小生成树的算法:普里姆算法和克鲁斯卡尔算法。
CRC
循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
MD5
MD5 常常作为文件的签名出现,我们在下载文件的时候,常常会看到文件页面上附带一个扩展名 
为.MD5 的文本或者一行字符,这行字符就是就是把整个文件当作原数据通过 MD5 计算后的值,我们下载文件后,可以用检查文件 MD5 信息的软件对下载到的文件在进行一次计算。两次结果对比就可以确保下载到文件的准确性。另一种常见用途就是网站敏感信息加密,比如用户名密码,支付签名等等。随着 https 技术的普及,现在的网站广泛采用前台明文传输到后台, MD5 加密(使用偏移量)的方式保护敏感数据保护站点和数据安全。 

更多算法练习
更多算法练习题,请访问https://leetcode-cn.com/problemset/algorithms/

你可能感兴趣的:(面试,java,spring)