java中级开发面试总结

中级开发面试总结
分布式事务的四种解决方案
一、两阶段提交(2PC)
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。

  1. 运行过程
    1.1 准备阶段
    协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
    1.2 提交阶段
    如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
    需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
  2. 存在的问题
    2.1 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
    2.2 单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
    2.3 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
    2.4 太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
    二、补偿事务(TCC)
    TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
    1.Try 阶段主要是对业务系统做检测及资源预留
    2、Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
    3、Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用。
1、首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

三、本地消息表(异步确保)
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
1、在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
2、之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
3、在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

四、MQ 事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
以阿里的 RocketMQ 中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。 第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。

2.你对nginx了解吗
1、代理服务器
客户机在发送请求时,不会直接发送给目的主机,而是先发送给代理服务器,代理服务接受客户机请求之后,再向主机发出,并接收目的主机返回的数据,存放在代理服务器的硬盘中,再发送给客户机。
2、为什么要使用代理服务器
1)提高访问速度
由于目标主机返回的数据会存放在代理服务器的硬盘中,因此下一次客户再访问相同的站点数据时,会直接从代理服务器的硬盘中读取,起到了缓存的作用,尤其对于热门站点能明显提高请求速度。
2)防火墙作用
由于所有的客户机请求都必须通过代理服务器访问远程站点,因此可在代理服务器上设限,过滤某些不安全信息。
3)通过代理服务器访问不能访问的目标站点
互联网上有许多开发的代理服务器,客户机在访问受限时,可通过不受限的代理服务器访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,虽然不能出国,但也可直接访问外网。
3、什么是正向代理?什么是反向代理?
正向代理,架设在客户机与目标主机之间,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中。

反向代理服务器架设在服务器端,通过缓冲经常被请求的页面来缓解服务器的工作量,将客户机请求转发给内部网络上的目标服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器与目标主机一起对外表现为一个服务器。

反向代理有哪些主要应用?
现在许多大型web网站都用到反向代理。除了可以防止外网对内网服务器的恶性攻击、缓存以减少服务器的压力和访问安全控制之外,还可以进行负载均衡,将用户请求分配给多个服务器。

负载均衡的作用
负载均衡:分摊到多个操作单元上进行执行,减少单台服务器的负载压力,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡。

负载均衡算法概念
轮询法:将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。当其中一台服务连接不上后自动跳过连接下一个服务。
轮询法
1、在轮询中,如果服务器down掉了,会自动剔除该服务器
2、缺省配置就是轮询策略
3、此策略适合服务器配置相当,无状态且短平快的服务使用。

加权轮询法(权重):不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请求;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
加权轮询法(权重)
1、权重越高分配到需要处理的请求越多。
2、此策略可以与least_conn或者ip_hash结合使用。
3、此策略比较适合服务器硬件配置差别比较大的情况。

源地址哈希法(IP_hash):根据获取客户端的IP地址,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客户端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。

源地址哈希法(ip_hash)
1、在nginx 版本1.3.1 之前不能再ip_hash中使用权重(weight)。
2、ip_hash不能与backup同时使用。
3、此策略适合有状态服务,比如session。
4、当有服务需要剔除,必须手动down掉。

最小连接数法:由于后端服务器的配置不尽相同,对于请求的处理有快有慢,最小连接数法根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。

最小连接数(least_conn)
把请求转发给连接数较少的后端服务器。轮询算法是把请求平均的转发给各个后端,使他们的负载大致相同;但是有些请求占用的时间很长,会导致其所在的后端负载较高。这种情况下,最小连接数这种方式就可以达到更好的负载均衡效果

随机法:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。

加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

其他参数:
Fail_timeout:与max_fails结合使用
Max_fails:设置fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了。
Fail_time:服务器会被认为停机的时间长度,默认为10s.
Backup:标记该服务器为备用服务器,当主服务器停止时,请求会被发送到它这里。
Down:标记服务器永久停机了。
4.被static修饰后是一个对象还是不是一个对象,什么时候会被初始化
可以修饰成员变量、成员方法、初始化块、内部类,被static修饰的成员是类的成员,它属于类、不属于单个对象。”

1 调用某个类里的静态函数(静态方法)时,如果类里面的静态数据(也称为静态字段或静态变量)没有初始化,那么这个类的所有静态数据(变量)依据在类里面出现的次序初始化.
2 类实例化的时候,如果类里面的静态数据(也称为静态字段或静态变量)没有初始化,那么这个类的所有静态数据(变量)依据在类里面出现的次序初始化.

简单的说就是类里的静态函数首次被调用或类首次实例化时.
从执行顺序看,静态构造函数可以看成最后一个成员变量.在类里面静态变量初始化结束时开始执行.
5.什么是java虚拟机,JVM垃圾回收机制了解多少
Jvm垃圾回收机制:
1.1 引用计数法
引用计数法描述的算法为:给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”。
引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个比较好的算法。比如Python语言就是采用的引用计数法来进行内存管理的。
但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因是引用计数法无法解决对象的循环引用问题。
1.2 可达性分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。

在Java语言中,可作为GC Roots的对象包含以下几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈中(Native方法)引用的对象

将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种,这四种引用的强度依次递减。
强引用: 强引用指的是在程序代码之中普遍存在的,类似于"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象实例。

软引用: 软引用是用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。

弱引用: 弱引用也是用来描述非必需对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象。在JDK1.2之后提供了WeakReference类来实现弱引用。

虚引用: 虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用。

软引用用于缓存,弱引用用于LocalThread,虚引用用户jvm回收堆外内存

2、回收方法区
方法区(永久代)的垃圾回收主要收集两部分内容:废弃常量和无用类。
回收废弃常量和回收Java堆中的对象十分类似。

类需要同时满足下面三个条件才会被算是"无用的类"
1.该类的所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
2.加载该类的ClassLoader已被回收
3.该类对应的Class对象没有任何其他地方被引用,无法在任何地方通过反射访问该类的方法

3、垃圾回收算法
3.1 标记-清除算法
“标记-清除”算法是最基础的收集算法。算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象(标记过程参见1.2可达性分析)。
效率问题:标记和清除这两个过程的效率都不高
空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。
3.2 复制算法(新生代回收算法)
将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等的复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。
3.3 标记整理算法(老年代回收算法)
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。
针对老年代的特点,提出了一种称之为“标记-整理算法”。标记过程仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。

虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

java虚拟机的基本结构
1、类加载子系统:负责从文件系统或者网络中加载class信息,加载的信息放在一起称之为方法区的内存空间。
2、方法区:就是存放类信息、常量信息、常量池信息、字符串字面量和数字常量等。
3、java堆:在java虚拟机启动的时候建立java堆,它是java程序最主要的内存工作区域,几乎所有的对象实例都存放在java堆中,堆空间是所有线程共享的。
4、直接内存:java的NIO库允许java程序使用直接内存,从而提高性能,通常直接内存速度会优于java堆。读写频繁的场合可能会考虑使用。
5、java栈:每个虚拟机线程都有一个私有的栈,一个线程的java栈在线程创建的时候被创建,java栈中保存着局部变量、方法参数、还有java的调用方法和返回值等。
6、本地方法栈:与java栈很类似,最大不同是本地方法栈用于本地方法调用。java虚拟机允许java直接调用本地方法(通常本地方法为C语言编写)
7、垃圾回收系统:是java的核心,也是必不可少的,java有一套自己进行垃圾清理的机制,开发者无需手动清理。
8、PC寄存器:是每个线程私有的空间,java虚拟机会为每个线程创建PC寄存器,在任意时刻,一个java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,PC寄存器就会执行当前正在被执行的指令,如果是本地方法,则PC寄存器的值为undefined。寄存器存放如当前执行环境指针、程序计数器、操作栈指针、计算的变量指针等信息。
9、执行引擎:虚拟机最核心的就是执行引擎了,它负责执行虚拟机的字节码。一般用户先编译成机器码后执行。

6.重载和重写
重写:方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
重写 总结:
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载:
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
重载 总结:
1.重载Overload是一个类中多态性的一种表现
2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准

区别:
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

7.方法修饰符了解多少,权限范围
private(私有的)
private可以修饰成员变量,成员方法,构造方法,不能修饰类(此刻指的是外部类,内部类不加以考虑)。被private修饰的成员只能在其修饰的本类中访问,在其他类中不能调用(这里也可以看出为什么不能修饰class,因为private本来就是作用于类内部的东西),但是被private修饰的成员可以通过set和get方法向外界提供访问方式

default(默认的)
defalut即不写任何关键字,它可以修饰类,成员变量,成员方法,构造方法。被默认权限修饰后,其只能被本类以及同包下的其他类访问。

protected(受保护的):
protected可以修饰成员变量,成员方法,构造方法,但不能修饰类(此处指的是外部类,内部类不加以考虑)。被protected修饰后,只能被同包下的其他类访问。如果不同包下的类要访问被protected修饰的成员,这个类必须是其子类

public(公共的):
public是权限最大的修饰符,他可以修饰类,成员变量,成员方法,构造方法。被public修饰后,可以再任何一个类中,不管同不同包,任意使用。

8.什么是值传递,什么是引用传递,有什么区别
值传递是指基本数据类型在方法中的传递,引用传递是指引用数据类型在方法中的传递

值传递:方法调用时,实际参数把它的值传递给方法的形参,形参接收的只是原始值的一个副本,后续方法里对形参的修改不会影响原来的实参的值
引用传递:在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。

在java中,基本数据类型作为参数传递的时候叫做值传递,传的是值本身,
值传递:值在该方法中改变属于局部变量改变
当出了该方法就没有作用了(方法中的传递的基本数据类型就属于局部变量)

在java中,方法的传参,引用数据类型,传的是物理内存地址值,当方法中对数组内部的数值进行改变时,
并未改变该数组的物理内存地址,所以当返回到主方法中,数组还是指向原来的地址。
当传入arr数组在内存中的地址值后,堆内存中的连续地址发生改变,可以影响数组整体

特例:String /基本数据类型包装类,虽然都是引用数据类型,但是在发生传参的时候,它们传的是值
9.线程了解多不?
1、实现Runnable接口可以避免Java单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;适合多个相同程序代码的线程去处理同一资源的情况。
2、继承Thread类和实现Runnable方法启动线程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理机可用,则执行run方法。
3、实现Callable接口要实现call方法,并且线程执行完毕后会有返回值。其他的两种都是重写run方法,没有返回值。
1、并发和并行的区别
并发:多个线程同是争夺同一资源
并行:多个线程同是执行多个资源
2、wait和sleep的区别
1)来自不同的类
wait是Object类
sleep是Theard类
2)关于锁的释放
wait会释放锁
sleep会释放锁
3)使用的范围不同
wait必须在同步代码块中
sleep可以在任意地方

3、sychronized和lock的区别
1)sychronized是一个java的关键字,lock是一个java类
2)sychronized无法判断锁的状态,lock可以判断锁的状态
3)sychronized自动关闭锁,lock需要手动手动释放锁
4)sychronized会使线程阻塞,lock不一定会阻塞线程
5)可重入锁,不可以中断的,非公平;lock ,可重入锁,可以 判断锁,非公平(可以 自己设置)
6)synchronized 适合锁少量的代码同步问题,lock 适合锁大量的同步代码
4、volation和sychronized的区别

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

6、线程池的七大参数
1、corePoolSize 线程池核心线程数
创建线程池后,当有请求任务来之后,就会安排池中线程去执行请求任务,近似理解为今日当值线程。
当线程池中的线程数目达到了corePoolSize后,就会把任务放到缓存队列中;
2、maxmumPoolSize:
线程池能够容纳同时执行的最大线程数,此值必须大于等于1
3、keepAliveTime:多余空闲线程的存活时间,超时了没有人调用就会释放 。
当前线程池的数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
4、unit:keepAliveTime的时间单位
5、workQueue:任务队列,被提交但未被执行的任务
6、threadFactory:表示生成线程池中线程的线程工厂,用于创建线程,一般用默认的即可
7、handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maxmumPoolSize)

7、线程池的四大拒绝策略
1、new ThreadPoolExecutor.AbortPolicy() 线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常
2、 new ThreadPoolExecutor.CallerRunsPolicy() 由调用线程处理该任务,如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务
3、new ThreadPoolExecutor.DiscardPolicy() 丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
4、new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃队列最前面的任务,然后重新提交被拒绝的任务。
8、线程的几种状态

  1. 新建(NEW):新创建了一个线程对象。
  2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。
  5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

10.什么是死锁
是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
产生死锁的原因:
a. 竞争资源
系统中的资源可以分为两类:
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁。

产生死锁的必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
1、以确定的顺序获得锁
2、超时放弃
检测死锁
首先为每个进程和每个资源指定一个唯一的号码;
然后建立资源分配表和进程等待表。
解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
死锁检测
1、Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
在发生死锁时可以用jstack -l pid来观察锁持有情况
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep
root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
得到进程ID为21711,第二步找出该进程内最耗费CPU的线程,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,
2、JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。

11.hashmap了解多少
HashMap 的扩容机制与其他变长集合的套路不太一样,HashMap 按当前桶数组长度的2倍进行扩容,阈值也变为原来的2倍(如果计算过程中,阈值溢出归零,则按阈值公式重新计算)。扩容之后,要重新计算键值对的位置,并把它们移动到合适的位置上去。

HashMap的工作原理:
通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

1)HashMap允许NULL值,NULL键。
(2)不要轻易改变负载因子,负载因子过高会导致链表过长,查找键值对时间复杂度就会增高,负载因子过低会导致hash桶的数量过多,空间复杂度会增高
(3)Hash表每次会扩容长度为以前的2倍
(4)HashMap是多线程不安全的,我在JDK 1.7进行多线程put操作,之后遍历,直接死循环,CPU飙到100%,在JDK 1.8中进行多线程操作会出现节点和value值丢失,为什么JDK1.7与JDK1.8多线程操作会出现很大不同,是因为JDK 1.8的作者对resize方法进行了优化不会产生链表闭环。
(5)尽量设置HashMap的初始容量,尤其在数据量大的时候,防止多次resize
12.arrylist了解
(1)jdk1.8ArrayList默认容量是多大?
(2)ArrayList 扩容多大?
(3)ArrayList 是线程安全的吗?为什么?
(4)ArrayList 中 elementData 为什么使用 transient 修饰?
(5)ArrayList list = new ArrayList(20); 中的list扩充几次?
1.jdk1.8如果不给初始值容量的话,是默认给了一个空数组,也就是0,当在第一次添加时,会重新比较大小,给10的容量
2.ArrayList 扩容1.5倍,记住右移一位:newCapacity = oldCapacity + (oldCapacity >> 1)(位移运算
)3.ArrayList不是线程安全的,当我们add添加或者删除时,elementData[size++] = e; elementData[–size] = e; 这两个操作,并不是原子操作,都是分为两步操作
4.由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 transient 修饰,可以防止被自动序列化。
5.默认ArrayList的长度是10个,所以如果你要往list里添加20个元素肯定要扩充一次(newCapacity 扩充为原来的1.5倍,但和输入的minCapacity相比发现小于minCapacity,于是 newCapacity = minCapacity,所以只扩容一次,具体见扩容里的grow方法),但是这里显示指明了需要多少空间,所以就一次性为你分配这么多空间,也就是不需要扩充了!

ArrayList扩容的核心方法grow(),下面将针对三种情况对该方法进行解析:
1.当前数组是由默认构造方法生成的空数组并且第一次添加数据。此时minCapacity等于默认的容量(10)那么根据下面逻辑可以看到最后数组的容量会从0扩容成10。而后的数组扩容才是按照当前容量的1.5倍进行扩容;
2.当前数组是由自定义初始容量构造方法创建并且指定初始容量为0。此时minCapacity等于1那么根据下面逻辑可以看到最后数组的容量会从0变成1。这边可以看到一个严重的问题,一旦我们执行了初始容量为0,那么根据下面的算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。
3.当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE。这边也可以看到ArrayList允许的最大容量就是Integer的最大值(-2的31次方~2的31次方减1)。
//ArrayList扩容的核心方法,此方法用来决定扩容量
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

77、为什么扩容的时候比较慢
数据量大的时候扩容影响效率,每次扩容都会新new一个数据,把原来的数组,一个一个元素复制到新数组里边。
13.数据存储结构和链表有什么区别
一、存储单元的连续性不同
链式存储结在构计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。
顺序存储结构在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素。
二、优缺点不同
空间上
顺序比链式节约空间。是因为链式结构每一个节点都有一个指针存储域。
存储操作上:
顺序支持随机存取,方便操作
插入和删除上:
链式的要比顺序的方便(因为插入的话顺序表也很方便,问题是顺序表的插入要执行更大的空间复杂度,包括一个从表头索引以及索引后的元素后移,而链表是索引后,插入就完成了)
三、适用方向不同
链式存储适用于在较频繁地插入、删除、更新元素时,而顺序存储结构适用于频繁查询时使用。
3.java基本数据类型
Java中的数据类型分为两大类,基本数据类型和引用数据类型。
基本数据类型只有8种,可按照如下分类
①整数类型:long、int、short、byte
②浮点类型:float、double
③字符类型:char
④布尔类型:boolean

引用数据类型非常多,大致包括:
类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型
例如,String类型就是引用类型。
简单来说,所有的非基本数据类型都是引用数据类型。

区别:
1、存储位置
基本变量类型
在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
引用变量类型
只要是引用数据类型变量,其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址
ps:通过变量地址可以找到变量的具体内容,就如同通过房间号可以找到房间一般

2、传递方式
基本变量类型
在方法中定义的非全局基本数据类型变量,调用方法时作为参数是按数值传递的
引用变量类型
引用数据类型变量,调用方法时作为参数是按引用传递的,传递的是引用的副本(内存地址)
14.string,stringbuff,stringbuild区别
string stringbuff stringbuild的执行效率: stringbuild>stringbuff>string
String类是不可变类,任何对String的改变都会引发新的String对象的生成;
StringBuffer是可变类,任何对它所指代的字符串的改变都不会产生新的对象,线程安全的。
StringBuilder是可变类,线性不安全的,不支持并发操作,不适合多线程中使用,但其在单线程中的性能比StringBuffer高。
栈:存放基本类型的变量数据和对象的引用。像int a = 1; String str = “hello” ; String str1 = new String(“OK”) ; 栈中存放的是 a, 1, str, str1。
常量池:存放基本类型常量和字符串常量。
堆:存放所有new出来的对象。
栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
15.String能不能被继承
不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
public final class String implements java.io.Serializable, Comparable, CharSequence {
// 省略… 
}
16.包装类和基本数据类型的关系

包装类
包装类顾名思义就是将基本数据类型的所没有属性和方法包装到类中,实现对象化的交互。

基本数据类型
Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

区别:
包装类可以为null,而基本类型不可以。
这使得在实体类(POJO)中只能应用包装类型,而基本类型则不行。
实体类(POJO):简单无规则的Java对象,只有属性字段以及setter和getter方法。
只能使用包装类型的原因是:数据库的查询结果可能是null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型),就会抛出NullPointerException的异常。
包装类型可以用于泛型,而基本类型不可以。

基本类型比泛型更高效
基本数据类型的具体数值直接存储在栈中,而包装类型在栈中存储的只是堆中的引用。
相比于基本数据类型,包装类型要占用更多的内存空间。且两个包装类型的值相同,并不代表它们相等。
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a==b); //false
System.out.println(a.equals(b)); //true

自动装箱和自动拆箱
把基本类型转换成包装类型的过程叫做装箱。
反之,把包装类型转换成基本类型的过程叫做拆箱

17.单例模式
单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。
1、单例类只有一个实例对象;
2、该单例对象必须由单例类自行创建;
3、单例类对外提供一个访问该单例的全局访问点;

单例模式的应用场景:
在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
网站的计数器,一般也是采用单例模式实现,否则难以同步。

1、常见的单例模式有两种创建方式:所谓饿懒汉式与饿汉式
(1)懒汉式
  懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。

(2)饿汉式
  在加载类的时候就会创建类的单例,并保存在类中。

常见应用场景spring中的bean默认是单例模式
一、单例模式:在spring中其实是scope(作用范围)参数的缺省设定值
每个bean定义只生成一个对象实例,每次getBean请求获得的都是此实例
单例模式分为饿汉模式和懒汉模式
饿汉模式 spring singleton的缺省是饿汉模式:启动容器时(即实例化容器时),为所有spring配置文件中定义的bean都生成一个实例
懒汉模式 在第一个请求时才生成一个实例,以后的请求都调用这个实例
spring singleton设置为懒汉模式:

2、双重加锁机制
  何为双重加锁机制?
在懒汉式实现单例模式的代码中,有使用synchronized关键字来同步获取实例,保证单例的唯一性,但是上面的代码在每一次执行时都要进行同步和判断,无疑会拖慢速度,使用双重加锁机制正好可以解决这个问题:

懒汉式的双层检查,第二次判断原因:
线程1和线程2 同时判断到实例为null,开始竞争,线程1成功抢到锁,这个时候线程2还在cas自旋,线程1抢到了锁,之后进入代码块,实例化了对象返回,释放锁;线程2 此时cas获得了锁,进入代码块,判断实例是否为空,由于线程1已经实例化了,所以不再重复实例化了
二、另一种和singleton对应的scope值—prototype多实例模式
调用getBean时,就new一个新实例
singleton和prototype的比较
singleton:
xml配置文件:

测试代码:
ctx = new ClassPathXmlApplicationContext(“spring-hibernate-mysql.xml”);
DvdTypeDAO tDao1 = (DvdTypeDAO)ctx.getBean(“dvdTypeDAO”);
DvdTypeDAO tDao2 = (DvdTypeDAO)ctx.getBean(“dvdTypeDAO”);
运行:
true
com.terana.hibernate.impl.DvdTypeDAOImpl@15b0333
com.terana.hibernate.impl.DvdTypeDAOImpl@15b0333

说明前后两次getBean()获得的是同一实例,说明spring缺省是单例

prototype:

执行同样的测试代码
运行:
false
com.terana.hibernate.impl.DvdTypeDAOImpl@afae4a
com.terana.hibernate.impl.DvdTypeDAOImpl@1db9852
说明scope="prototype"后,每次getBean()的都是不同的新实例

18.spring常用注解
@Transaction注解(事务回滚)

1.@Bean注解
@Bean 标识一个用于配置和初始化一个由SpringIoc容器管理的新对象的方法,类似于XML配置文件的,一般与@Configration注解配合使用
注册bean
2.@Service注解
首先,在配置root-config.xml文件中加一行:
加上这一行以后,将自动扫描路径下面的包,如果一个类带了@Service注解,将自动注册到Spring容器,不需要再在applicationContext.xml文件定义bean了

类似作用的还包括@Component、@Repository、@Controller:
3.@Component注解
把普通pojo实例化到spring容器中,相当于配置文件中的
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类
4.@Repository注解
用于标注数据访问组件,即DAO组件,再稍微大点的项目里,使用xml的bean定义来配置会大大增加代码体积且不易维护,所以引入了自动扫描的机制,它的作用和在xml文件中使用bean节点配置组件时一样的。
5.@Controller注解
当组件属于控制层时,则使用@Controller注解
被Controller标记的类就是一个控制器,这个类中的方法,就是相应的动作
6.@Configration注解
@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器
需要注意的是:
@Configuration不可以是final类型;
@Configuration不可以是匿名类;
嵌套的configuration必须是静态类。
Bean注解主要用于方法上,有点类似于工厂方法,当使用了@Bean注解,我们可以连续使用多种定义bean时用到的注解,譬如用@Qualifier注解定义工厂方法的名称,用@Scope注解定义该bean的作用域范围,譬如是singleton还是prototype等。

使用bean
7.@Autowired 注解
它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。
@Autowired注解可用于为类的属性、构造器、方法进行注值。默认情况下,其依赖的对象必须存在(bean可用)如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualifier注解进行限定,指定注入的bean名称
8.@Resource注解
@Resource和@Autowired注解都是用来实现依赖注入的。只是@Autowired按byType自动注入,而@Resource默认按 byName自动注入
@Resource依赖注入时查找bean的规则
既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型进行进行查找,如果找到就注入。
只是指定了@Resource注解的name,则按name后的名字去bean元素里查找有与之相等的name属性的bean。
只指定@Resource注解的type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
既指定了@Resource的name属性又指定了type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
除此之外还有很多注解
9.@Override注解
标示当前的方法定义将覆盖超类中的方法他告诉我们同时也告诉编译器我们的这些方法肯定覆盖了类里面的方法,如果注释掉类里面的方法 那么就会报错,它需要全部覆盖某个接口的方法。
10.@ComponentScan注解
@ComponentScan告诉Spring 哪个packages 的用注解标识的类 会被spring自动扫描并且装入bean容器。
例如,如果你有个类用@Controller注解标识了,那么,如果不加上@ComponentScan,自动扫描该controller,那么该Controller就不会被spring扫描到,更不会装入spring容器中,因此你配置的这个Controller也没有意义。
11.@PropertySouce注解
通过@PropertySource注解将properties配置文件中的值存储到Spring的 Environment中,Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。
12.@ImportResource注解和@Import注解
@Import注解是引入带有@Configuration的java类。
@ImportResource是引入spring配置文件.xml
13.@RequestMapping
是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
14.@responseBody注解
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据
19.多态了解多少
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
多态的定义及实现
1.多态定义的构成条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

在继承中要构成多态的还有两个条件:
调用函数的对象必须是指针或者引用
被调用的函数必须是虚函数,且完成了虚函数的重写(虚函数:就是在类的成员函数的前面加virtual关键字)
虚函数的重写:派生类中有一个跟基类的完全相同虚函数,我们就称子类的虚函数重写了基类的虚函数,完全相同是指:函数名、参数、返回值都相同。另外虚函数的重写也叫作虚函数的覆盖
多态的好处:
1.提高了代码的可维护性
2.提高了代码的扩展性
多态的作用:
可以当做形式参数,可以接受任意子类对象
多态的弊端:
不能使用子类特有的属性和行为
多态分为编译时多态和运行时多态。
编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。
20.${},#{}
1)#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。#{}可以接收简单类型值或pojo属性值。如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

(2) 表 示 拼 接 s q l 串 , 通 过 {}表示拼接sql串,通过 sql{}可以将parameterType传入的内容拼接在sql中且不进行jdbc类型转换, 可 以 接 收 简 单 类 型 值 或 p o j o 属 性 值 , 如 果 p a r a m e t e r T y p e 传 输 单 个 简 单 类 型 值 , {}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值, pojoparameterType{}括号中只能是value。
21.ioc简单说一下
控制反转:
  在使用了Spring框架之后对象不在由调用者来创建,而是由Spring容器来创建
  Spring容器会负责控制程序之间的关系而不是由调用者的程序代码直接控制
  这样控制权由应用程序转移到了Spring容器,控制权发生了反转,这就是Spring控制反转
  获得依赖对象的过程被反转了,控制反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入
  于是"控制反转"也叫"依赖注入"(Dependency Injection)简写(DI)所谓的依赖注入,就是由IOC容器在运行期间
  动态的将某种依赖关系注入到对象之中
  所以,依赖注入(DI)和控制反转(IOC)是从不同角度的描述的同一件事情,就是指通过引入IOC容器
  利用依赖关系注入的方式,实现对象之间的解耦
ioc 的常见容器是:ApplicationContext 和 BeanFactory

IOC 是基于 java 的反射机制以及工厂模式实现的。

spring的容器
在 Spring 容器启动的时候,Spring 会把你在 application.xml 中配置好的 bean 都初始化,在你需要调用的时候,把已经初始化的 bean 分配给你要调用这些 bean 的类,而不用去创建一个对象的实例。

AOP(面向切面编程)
AOP (面向切面编程) 技术利用一种称为 “横切” 的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP采用的技术
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建 “方面”,从而使得编译器可以在编译期间织入有关 “方面” 的代码。这里静态织入的原理就是在编译期间,切面直接以字节码形式编译到目标字节码中.

21.@Resource、autowired和qualifier区别
@Autowired+@Qualifier == @Resource 。

@Autowired: 根据类型注入以这个demo为例,一开始根据类型找到AnimalService,然后找到它的子类型。这时候由于它的子类型有两个,它就不知道要取哪个,所以这时候就报错。
@Resource :默认根据名字注入,其次按照类型搜索既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型进行进行查找,如果找到就注入。只是指定了@Resource注解的name,则按name后的名字去bean元素里查找有与之相等的name属性bean。只指定@Resource注解的type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常。
@Autowired :@Qualifie(“dogServiceImpl”) 两个结合起来可以根据名字和类型注入

22.spring有几种配置方式

  1. 基于xml的配置

在Spring1.x时代,都是基于xml来进行配置,用xml文件来管理bean之间的关系

  1. 基于注解的配置
    在Spring2.x时代,Spring提供了声明bean的注解,大大减少了配置量
    Bean的定义:
    在bean的上面使用@Component或其子类(@Repository、@Service、@Controller)来定义bean

Bean的注入:(以下注解添加到bean的属性中)
@required注解:应用于bean的setter方法,表示被标注过的属性必须在XML文件中配置,否则会出现异常
@Autowire:在属性上使用@Autowire注解可以进行自动装配,减少代码
当需要创建多个相同类型的bean,并且只需要装配其中一个时,可以使用@Qualifier和@Autowire用来指定装载其中一个bean

  1. 基于Java的配置
    Spring3.0以后,提供了Java配置的能力,Spring4.x和SpringBoot都推荐使用Java配置
    首先要说明以下两个注解:

@Configuration,表示修饰的类可以作为bean的来源(通过注解来获取bean)
@Bean,表示实例化一个bean,等同于在xml里面添加一个bean 被@Bean修饰的方法名就是该bean的name

23.一般情况下spring容器的bean是单例还是多例的

Spring的bean默认都是单例的,某些情况下,单例是并发不安全的,以Controller举例,问题根源在于,我们可能会在Controller中定义成员变量,如此一来,多个请求来临,进入的都是同一个单例的Controller对象,并对此成员变量的值进行修改操作,因此会互相影响,无法达到并发安全(不同于线程隔离的概念,后面会解释到)的效果。
2.1 单例变原型
对web项目,可以Controller类上加注解@Scope(“prototype”)或@Scope(“request”),对非web项目,在Component类上添加注解@Scope(“prototype”)。
优点:实现简单;
缺点:很大程度上增大了bean创建实例化销毁的服务器资源开销。

Java作为功能性超强的编程语言,API丰富,如果非要在单例bean中使用成员变量,可以考虑使用并发安全的容器,如ConcurrentHashMap、ConcurrentHashSet等等等等,将我们的成员变量(一般可以是当前运行中的任务列表等这类变量)包装到这些并发安全的容器中进行管理即可。

spring bean作用域有以下5个:
singleton:单例模式,当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;
prototype:原型模式,每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;
(下面是在web项目下才用到的)
request:搞web的大家都应该明白request的域了吧,就是每次请求都新产生一个实例,和prototype不同就是创建后,接下来的管理,spring依然在监听;
session:每次会话,同上;
global session:全局的web域,类似于servlet中的application。

24.项目快速了一下
26.redis是用来干什么的
缓存穿透
描述:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

  1. 缓存空对象
    缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
    缓存空对象会有两个问题:第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
  2. 布隆过滤器拦截
    在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
    布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
    布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小

缓存击穿
描述:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

当前key是一个热点key(例如一个秒杀活动),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

解决方案:
1、设置热点数据永远不过期。
从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓

2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。
4、加互斥锁,
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)

分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。

“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

缓存雪崩
描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3.设置热点数据永远不过期。
4.可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
5.采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
6.缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)

Redis是Remote Dictionary Service的首字母缩写,即远程字典服务。是一个高性能的key-value形式的内存型数据库。以设置和读取一个256字节字符串为例,它的读取速度可高达11W次/s,写速度可达8.1W次/s。
Redis有5种基础数据结构,分别为:string,list,hash,set,sorted set

缓存
计数器
分布式id生成器
分布式锁
位操作数据统计
字符串操作
频率限制
消息队列
排行榜
延时任务
27.redis以什么类型存储,存储方式,设过期时间都是怎么定义的
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。

可靠性
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
互斥性。在任意时刻,只有一个客户端能持有锁。
不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

redis是键值对的数据库,有5中主要数据类型:
字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合(sortedset)。
string 普通key:value储存
set name xiaoli
get name—>xiaoli
list 有序可以重复
hash string类型 field和value映射
set 无序不可重复
Sorted set:有序不可重复
Redis具体可以用来储存哪些数据?
1:热点数据,列如,一般首页的整页的数据都进行缓存
2:分布式 Session 解决方案(cookie + redis),比如,七天免登陆
3:整表数据缓存(使用hash结构)
4:简单的发布订阅(list 结构实现),延时队列(sorted set,时间戳score)

Redis持久化储存方式有几种?
1:RDB方式,定期保存内存快照,适合大规模的数据恢复,但是他的完整性和一致性较差
说到内存快照保存过程就要先了解fork()函数用法:
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都,复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
redis正是利用了这个特性,利用fork()来得到一个当前时刻的内存快照,同时创建的这个子进程可以将这个快照写入临时文件中,而主进程可以继续接受新的写请求,完美实现了数据的一致性同时丝毫不影响主进程的业务。

2:AOF方式,将操作日志追加至AOF文件中,数据完整性比RDB高,但是同时,由于记录储存的内容多了,影响数据恢复的效率,因此要定期重写AOF文件,以减少文件冗余。

Expire
//把用户信息写入redis
jedisClient.set(REDIS_USER_SESSION_KEY + “:” + token,JsonUtils.objectToJson(user));

//设置session的过期时间 时间单位是秒
jedisClient.expire(REDIS_USER_SESSION_KEY + “:” + token, 1800);

// NX是不存在时才set, XX是存在时才set, EX是秒,PX是毫秒
jedisClient.set(key, value, “NX”, “EX”, expireSecond);

jedis.set(String key, String value, String nxxx, String expx, int time)
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间

执行上面的set()方法就只会导致两种结果:

  1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

28.mysql在用的时候有没有分库分表
29.zookeeper主要是做什么的,对应到spring cloud中和那个组件用途比较接近
ZooKeeper:
C(一致性)A(可用性)P(分区容错性)
zookeeper保证了cp(一致性、分区容错性),但是作为服务注册中心,我们可以容忍注册中心返回的是几分钟以前的注册信息。但是服务中心却必须保证可用性,
即服务注册中心对于高可用性的需求高于一致性。对于可用性,zookeeper有一个leader选举方案。当master主节点宕机与其他节点失去联系时,其他节点会重
新进行Leader选举,选出新的master节点。然而选举耗时过长,一般为30~120S,并且整个选举期间,整个zookeeper集群是无法使用的。

Eureka:
在Eureka的实现中,节点之间是相互对等的,部分注册节点宕机不会影响集群,即使集群中只有一个节点,也能正常提供服务,哪怕是所有节点宕机,Eureka的客户端也能从缓存服务调用信息,这就保证了我们微服务之间的互相调用足够健壮。

eureka保证了ap(可用性、分区容错性),eureka每一个节点都是平等的,几个节点宕机不会影响正常节点的工作。剩余的正常节点依旧可以提供服务注册和查询。
并且,当客户端向某节点注册服务时,注册失败或者超时,则会自动切换到其他节点。只要有一台eureka节点还正常工作,就能保证注册服务的可用。但是对于服
务信息的同步则不能保证一致性(不能保证强一致性,但是最终一致)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内85%的节点都没有正常心跳(不可用)
那么Eureka就认为客户端与注册中心之间出现了网络故障,此时会出现以下几种情况:
1、Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
2、Eureka仍然能够接收新服务的注册和查询请求,但是不会被同步到其他节点上(保证当前节点的可用性)
3、当网络稳定后,当前实例新注册的服务会被同步到其他节点

特点在于不用每次都去注册中心获取,而是会把信息存到本地缓存,从缓存中获取,性能特别快,另外它内部配有心跳检测机制,当注册中心信息改变,自动获得最新信息至本地,同时心跳机制保证分布式环境下,服务宕机后,自动从注册中心移除!

总结:
因此,Eureka能够保证注册中心的高可用性,而不会像zookeeper一样直接集群瘫痪
ZooKeeper基于CP,不保证高可用,如果zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将无法获得数据。Eureka基于AP,能保证高可用,即使所有机器都挂了,也能拿到本地缓存的数据。作为注册中心,其实配置是不经常变动的,只有发版(发布新的版本)和机器出故障时会变。对于不经常变动的配置来说,CP是不合适的,而AP在遇到问题时可以用牺牲一致性来保证可用性,既返回旧数据,缓存数据。
所以理论上Eureka是更适合作注册中心。而现实环境中大部分项目可能会使用ZooKeeper,那是因为集群不够大,并且基本不会遇到用做注册中心的机器一半以上都挂了的情况。所以实际上也没什么大问题。

30.git说一下你了解的,git关于版本管理,git命令
在实际的项目开发过程中,一个工程通常分为测试分支和线上分支,测试分支通常对应测试环境,线上分支的代码对应线上环境。使用git作为代码管理工具,可以方便地进行代码的迭代管理,多人协同开发,有利于提升开发效率。在使用git过程中,有一些经常使用的命令,熟练使用这些命令进行代码管理是程序开发过程中的基础。

git克隆指定分支代码:git clone -b 分支名 仓库地址
git branch -r #查看远程分支 或
git branch -a #查看所有分支
git pull origin online
git add ./* //添加本地所有更改的代码
git commit -m “说明本次改变的功能” //提交本次代码变更的注释
git reset --hard HEAD^ 回退到上个版本

git remote show [remote-name] 查看某个远程仓库的详细信息,比如要看所克隆的 origin 仓库,可以运行:git remote show origin
git fetch:相当于从远程获取最新版本到本地,不会自动merge
git fetch和git pull的区别:
从远程仓库 抓取master分支,但是不会merge
比较本地master分支与远程master分支的区别合并远程分支到本地

git stash // 把当前进度保存到暂存区
再输入命令git status 就会告诉你nothing to commit,这时我们就可以正常切换分支了,在另一个分支修改完成并提交之后,再切回到当前分支,可以使用下面的命令恢复最新进度到工作区:
git stash pop // 恢复最新的进度到工作区
31.linux常用命令
ls 查看目录中的文件
cd /home 进入’/home’
cd … 返回上一级目录
cd …/…返回上两级目录
mkdir dir1 创建dir1目录
rmdir dir1 删除dir1目录(只能删除空目录)
rm -f file1 删除文件file1
rm -rf /mulu 删除目录下面文件以及子目录下文件
cp /test1/file1 /test3/file2 将/test1目录下的file1复制到test3目录 文件名改为file2
mv /test1/file1 /test3/file2 将/test1目录下的file1复制到test3目录 文件名改为file2
mv * …/ 当前目录所有文件移动到上一级目录
Kill 命令杀掉进程 -9 强迫进程立即停止
Kill -9 [pid]
grep 是搜索关键字
Ps 查看进程
查看pid

ps -ef | grep tomcat

启动Tomcat,先cd到启动的.sh文件目录

cd /java/tomcat/bin
./startup.sh
停止Tomcat 服务命令
./shutdown.sh

查看测试项目日志
先cd到logs目录(里面有个xx.out文件)
然后 tail -f xx.out
Ctr+C停止实时的日志
查看最近1000行日志
tail -1000 xx.out
查询文件(知道名称)
find / -name tnsname.ora

查询端口是否被占用 netstat -anp | grep 端口号
$ sudo netstat -anp | grep 3306
Listen表示被占用
32.maven在项目中的作用
Maven是基于中央仓库的编译,即把编译所需要的资源放在一个中央仓库里,如jar,tld,pom,等。当编译的时候,maven会自动在仓库中找到相应的包,如果本地仓库没有,则从设定好的远程仓库中下载到本地。

Maven中的重要配置文件:pom.xml

作用:
1.Maven 统一集中管理好所有的依赖包,不需要程序员再去寻找。
2.对应第三方组件用到的共同 jar,Maven 自动解决重复和冲突问题。
3.Maven 作为一个开放的架构,提供了公共接口,方便同第三方插件集成。程序员可以将自己需要的插件,动态地集成到 Maven,从而扩展新的管理功能。
4.Maven 可以统一每个项目的构建过程,实现不同项目的兼容性管理。

33.定时任务组件,表达式
Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会结合Spring框架使用。
使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午8点执行一次、每个月最后
一天下午3点执行一次等。
Java中:
@Scheduled(cron = “0 30 6-22 ? * *”)

0 0 0 1 * ? 每月1号凌晨执行一次

提供Spring配置文件spring-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义
结构
  corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份(可为空)
0 0 12 ? * WED" 在每星期三下午12:00 执行

34.Poi
POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
使用java来读取表格中的内容 或者创建表格写数据
XSSFWorkbook来创建表格 读写表格
HSSF提供读写Microsoft Excel XLS格式档案的功能。
XSSF提供读写Microsoft Excel OOXML XLSX格式档案的功能。
HWPF提供读写Microsoft Word DOC格式档案的功能。
HSLF提供读写Microsoft PowerPoint格式档案的功能。
HDGF提供读Microsoft Visio格式档案的功能。
HPBF提供读Microsoft Publisher格式档案的功能。
HSMF提供读Microsoft Outlook格式档案的功能。
35.前端了解多少?

36.springcloud
核心注解:@SpringBootApplication 如果不加这个注解,程序是无法启动的。
可以代替核心注解:
@SpringBootConfiguration@EnableAutoConfiguration@ComponentScanpublic
SpringBootConfiguration 表示 Spring Boot 的配置注解,EnableAutoConfiguration 表示自动配置,ComponentScan 表示 Spring Boot 扫描 Bean 的规则,比如扫描哪些包。

Spring Cloud核心组件:Eureka
1)、Eureka服务端:也称服务注册中心,同其他服务注册中心一样,支持高可用配置。如果Eureka以集群模式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中其他分片会把它们的状态再次同步回来
2)、Eureka客户端:主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端想注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时,它也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期性地刷新服务状态
3)、Eureka Server的高可用实际上就是将自己作为服务向其他注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用效果。

1)、服务提供者
A.服务注册
服务提供者在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自己服务的一些元数据信息。Eureka Server接收到这个REST请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名
B.服务同步
两个服务提供者分别注册到了两个不同的服务注册中心上,也就是说,它们的信息分别被两个服务注册中心所维护。此时,由于服务注册中心之间因互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。通过服务同步,两个服务提供者的服务信息就可以通过这两台服务注册中心中的任意一台获取到
C.服务续约
在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server:“我还活着”,以防止Eureka Server的剔除任务将该服务实例从服务列表中排除出去,我们称该操作为服务续约

定义服务续约任务的调用间隔时间,默认30秒

eureka.instance.lease-renewal-interval-in-seconds=30

定义服务失效的时间,默认90秒

eureka.instance.lease-expiration-duration-in-seconds=90
2)、服务消费者
A.获取服务
当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑,Eureka Server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次

缓存清单的更新时间,默认30秒

eureka.client.registry-fetch-interval-seconds=30

B.服务调用
服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡
对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone,每个服务客户端需要被注册到一个Zone中,所以每个客户端对应一个Region和一个Zone。在进行服务调用的时候,优先访问同处一个一个Zone中的服务提供方,若访问不到,就访问其他的Zone
C.服务下线
当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(DOWN),并把该下线事件传播出去
3)、服务注册中心
A.失效剔除
Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去
B.自我保护
在服务注册中心的信息面板中出现红色警告信息:
该警告就是触发了Eureka Server的自我保护机制。Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况,Eureka Server会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是,在这段保护期间内实例若出现问题,那么客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请求重试、断路器等机制。

关闭保护机制,以确保注册中心可以将不用的实例正确剔除(本地调试可以使用,线上不推荐)

eureka.server.enable-self-preservation=false
二、Spring Cloud核心组件:Ribbon
Ribbon是一个基于HTTP和TCP的客户端负载均衡器,它可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到服务均衡的作用。当Ribbon和Eureka联合使用时,Ribbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来去定服务端是否已经启动。
在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心(比如Eureka)。在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用只需要如下两步:
服务提供者只需要启动多个服务实例并且注册到一个注册中心或是多个相关联的服务注册中心
服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
三、Spring Cloud核心组件:Fegin
Fegin的关键机制是使用了动态代理
1)、首先,对某个接口定义了@FeginClient注解,Fegin就会针对这个接口创建一个动态代理
2)、接着调用接口的时候,本质就是调用Fegin创建的动态代理
3)、Fegin的动态代理会根据在接口上的@RequestMapping等注解,来动态构造要请求的服务的地址
4)、针对这个地址,发起请求、解析响应
Fegin是和Ribbon以及Eureka紧密协作的
1)、首先Ribbon会从Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口
2)、然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
3)、Fegin就会针对这台机器,构造并发起请求

四、Spring Cloud核心组件:Hystrix
在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构更加不稳定。为了解决这样的问题,产生了断路器等一系列的服务保护机制。
在分布式架构中,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。
Hystrix使用舱壁模式实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。
五、Spring Cloud核心组件:Zuul
Spring Cloud Zuul通过与Spring Cloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。
对于路由规则的维护,Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射。
Zuul提供了一套过滤器机制,可以支持在API网关无附上进行统一调用来对微服务接口做前置过滤,已实现对微服务接口的拦截和校验。
六、小结
Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值