面试题大全1-javaSE部分-JavaEE&框架&中间件


文章目录


Java面试题大全

一.JavaSE 部分

基础部分

1、Java中基本数据类型有哪些?

整型四个:
··byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。 0111 1111

··short:16位,最大存储数据量是2^16-1

··int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

··long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
浮点型两个:

··float:32位,

··double:64位,
布尔型一个:
··boolean:只有true和false两个取值。
字符型一个:
··char:16位,存储Unicode码,用单引号赋值。可以存储单个字符’a’,特殊字符’/n’,可以当作int相加int sum=‘a’+‘b’(相当于sum为整数型的195)

2、Integer 和 int的区别

int是基本数据类型,变量中直接存放数值,变量初始化时值是0

Integer是引用数据类型,变量中存放的是该对象的引用地址,变量初始化时值时null

Integer是int类型的包装类,将int封装成Integer,符合java面向对象的特性,可以使用各种方法比如和其他数据类型间的转换

Integer和int的深入对比:

  1. 两个通过new生成的Integer对象,由于在堆中地址不同,所以永远不相等

  2. int和Integer比较时,只要数值相等, 结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int

  3. 通过new生成的Integer对象和非通过new生成的Integer对象相比较时,由于前者存放在堆中,后者存放在Java常量池中,所以永远不相等

  4. 两个非通过new生成的Integer对象比较时,如果两个变量的数值相等且在-128到127之间,结果就相等。这是因为给Integer对象赋一个int值,java在编译时,int值在-128到127之间会自动调用静态方法ValueOf()(Integer i = Integer.valueOf(100)),根据java api中对Integer类型的valueOf的定义,对于-128到127之间的整数,会进行缓存,如果下次再赋相同的值会直接从缓存中取,就不会new了。即享元模式

3、String和StringBuilder和StringBuffer区别

三者底层都是char[] 存储数据,JDK1.9之后使用的是byte[] ,因为往往我们存储都是短字符串,使用byte[]这样更节约空间。

由于String底层的char[]有final修饰,因此每次对String的操作都会在内存中开辟空间,生成新的对象,所以String不可变

StringBuilder和StringBuffer是可变字符串,没有final修饰,适合字符串拼接,另外StringBuffer是线程安全的,方法有synchronized修饰,但是性能较低,StringBuilder是线程不安全的,方法没有synchronized修饰,性能较高

4、String a= “A” 和 String a = new String(“A”) 创建字符串的区别

在这里插入图片描述

关键字:常量池,栈,堆
区别:
1、String a = “A”,"A"存在常量池中,而new String(“A”)存在堆中。
2、常量池中相同的字符串只会有一个,而每new一个对象就会在堆中新建一个对象,不管这个值是否相同
3、String a = “A”在编译阶段就会在内存中创建;String a = new String(“A”);是在运行时才会在堆中创建对象。


原答案:
​ String a = “A” 首先去常量池找 “A”,如果有,会把a指向这个对象的地址 ,如果没有则在栈中创建char型的值’A’,堆中创建一个String对象object,值为"A",接着object会被存放进字符串常量池中,最后将a指向这个对象的的地址
​ Sring a = new String(“A”) : 如果常量池中没有“A”就会走上面相同的流程先创建“A”,然后在堆中创建一个String对象,它的值共享栈中已有的char值“A”。

5、下面代码创建了几个对象

关键字:编译优化

  • String s = “a” +“b” + “c” + “d”;这条语句创建了几个对象?

创建了一个对象,因为相对于字符串常量相加的表达式,编译器会在编译期间进行优化,直接将其编译成常量相加的结果。

  • String s; 创建几个对象?

  • 没有创建对象。

  • String a = “abc”; String b = “abc”; 创建了几个对象?
    关键字:相同对象
    创建了一个对象,只是在第一条语句中创建了一个对象,a和b都指向相同的对象"abc",引用不是对象

6、== 和 equals 的区别是什么

​ 1、“=”比较对象比较的是地址。
​ 2、对于Object对象中的“equals”方法使用的也是 “==” ,比较的是对象的地址,默认情况下使用对象的equals比较Object中的equals方法,也就是比较地址,如果要实现自己的比较方式需要覆写(重写)equals 方法。
​ 3、对于包装类比如:Integer都是复写过equals方法,比较的是int 值。

7、final 和 finally 和 finalize 的区别

​ 1、当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰

​ 2、finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下

​ 3、finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc(GC是垃圾收集的意思(GabageCollection))启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。

8、JDK 和 JRE 有什么区别?

1、JRE(Java Runtime Enviroment) :是Java的运行环境,JRE是运行Java程序所必须环境的集合,包含JVM标准实现及 Java核心类库

2、JDK(Java Development Kit) :是Java开发工具包,它提供了:
1、Java的开发环境(提供了编译器javac等工具,用于将java文件编译为class文件)。
2、运行环境(提 供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。JDK是整个Java的核心,包括了Java运行环境(JRE),一堆Java工具tools.jar和Java标准类库 (rt.jar)。

9、面向对象四大特性

关键字:共同特征总结出来构造类
抽象 : 是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的属性和行为,并不关注这此行为的细节是什么 - 举例:定义一个person类,就是对“人”这种事物的抽象

关键字:
封装:对数据的访问只能通过已定义的接口,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口,比如在Java中,把不需要暴露的内容和实现细节隐藏起来,或者private修饰,然后提供专门的访问方法,如JavaBean。 - 生活举例:电脑主机就是把主板等封装到机壳,提供USB接口,网卡接口,电源接口等。 JavaBean就是一种封装。

继承:新类(子类,派生类)继承了原始类的特性,子类可以从它的父类哪里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。

多态:多态是指允许不同类的对象对同一消息做出响应。对象的多种形态,当编译时类型和运行时类型不一样,就是多态,意义在于屏蔽子类差异

10、方法覆盖和重载

方法的覆盖是子类和父类之间的关系,方法的重载是同一个类中方法之间的关系。
覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。
覆盖要求参数列表相同;重载要求参数列表不同。

11、普通类和抽象类

抽象类不能被实例化, 需要通过子类实例化
抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。
抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体
含有抽象方法的类必须申明为抽象类
抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类

12、接口和抽象类

1、定义:定义接口使用interface,定义抽象类使用abstract class

2、元素:接口由全局常量,抽象方法,(java8后:静态方法,默认方法)。抽象类由构造方法,抽象方法,普通方法

3、关系:接口和类是实现关系,抽象类和类是继承关系


IO流

你知道BIO,NIO,AIO么?讲一下你的理解

BIO (Blocking I/O):同步阻塞I/O 模式,以流的方式处理数据,数据的读取写入必须阻塞在一个线程内等待其完成。适用于连接数目比较小且固定的架构

NIO (New I/O):同时支持阻塞与非阻塞模式,以块的方式处理数据,适用于连接数目多且连接比较短(轻操作)的架构,比如聊天器

AIO ( Asynchronous I/O):异步非阻塞I/O 模型,适用于连接数目多且连接比较长(重操作)的架构

java 中四大基础流

InputStream : 输入字节流, 也就是说它既属于输入流, 也属于字节流 ,

OutputStream: 输出字节流, 既属于输出流, 也属于字节流

Reader: 输入字符流, 既属于输入流, 又属于字符流

Writer: 输出字符流, 既属于输出流, 又属于字符流

读文本用什么流,读图片用什么流

文本用字符输入流,读图片用字节输入流

字符流和字节流有什么区别

字节字符

字符流适用于读文本,字节流适用于读图片,视频,文件等。

字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。

字节流默认不使用缓冲区;字符流使用缓冲区。

字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元

BufferedInputStream 用到什么设计模式

主要运用了俩个设计模式,适配器和装饰者模式

带缓冲区的流

BufferedInputStream 带缓冲区的字节输入

BufferedOutputStream 带缓冲区的输出流

BufferedReader : 带缓冲区的字符输入流

BufferedWriter : 带缓冲区的字符输出流

怎么实现一张图片拷贝

需要一个FileInputStream指向读取的文件,然后把它包装到BufferInputStream,使用BufferInputStream#read方法去读byte[],然后创建一个FileOutputStream指向输出文件,然后把它包装到BufferOutputStream,使用BufferOutputStream#write方法写byte[]到另外一个文件

怎么实现文本拷贝

和文件拷贝思路一样,只不过读的时候需要使用BufferedReader和FileReader,使用readline来读 , 写的时候需要BufferedWriter和 FileWriter,用wite来写


集合篇

List和Set和Map的区别
  • List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
  • Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
  • Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap

说一下Java中的集合体系

Collection接口

List:

  • ArrayList:底层数据结构是数组,查询性能高,增删性能低

  • Vector:底层数据结构是数组,查询性能高,增删性能低

  • LinkedList:底层数据结构是双向链表,查询性能低,增删性能高

Set:

  • HashSet:无序不重复的,使用HashMap的key存储元素,判断重复依据是hashCode()和equals()

  • TreeSet:有序不重复的,底层使用TreeMap的key存储元素,排序方式分为自然排序,比较器排序

Map接口

  • HashMap:key的值没有顺序,线程不安全
  • TreeMap:key的值可以自然排序,线程不安全
  • HashTable:它的key和value都不允许为null,线程安全
  • Properties:它的key和value都是String类型的,线程安全
HashMap和HashTable的区别

HashMap和HashTable都是实现了Map接口的集合框架,他们的区别

  • HashTable是线程安全的,它的实现方法都加了synchronized关键字,因此它的性能较低

  • HashMap是线程不安全的,它实现方法没有加synchronized,因此它的性能较高

  • HashMap的key和value都允许为null,HashTable中的key和value都不能为null,如果不考虑线程安全,建议使用HashMap,如果需要考虑线程安全的高并发实现,建议使用ConcurrentHashMap
    -------------------------————————————————————————————————————————
    深层版:
    —相同点:
    1.HashMap和Hashtable都是java.util包下的类
    2.HashMap和Hashtable都实现了Map接口,存储方式都是key-value形式
    3.HashMap和Hashtable当然也都实现了serializable和Cloneable接口
    4.HashMap和Hashtable的负载因子都是0.75
    5.HashMap与Hashtable的部分方法相同,比如put , remove等等方法
    —不同点:
    1.HashMap是非线程安全的,Hashtable是线程安全的
    2.HashMap允许null作为键或值,Hashtable不允许–运行时NullPointerException
    3.HashMap添加元素使用的是自定义hash算法,Hashtable使用的是key的hashCode算法
    4.HashMap在数组+链表的结构中引入了红黑树,Hashtable没有
    5.HashMap初始容量为16,Hashtable初始容量为11
    6.HashMap扩容是当前容量翻倍,Hashtable是当前容量翻倍+1
    7.HashMap只支持Iterator遍历,Hashtable支持Iterator和Enumeration
    8.HashMap与Hashtable的部分方法不同,比如Hashtable有contains方法

ArrayList和LinkedList区别

都属于线性结构,ArrayList是基于数组实现的,开辟的内存空间要求联系,可以根据索引随机访问元素性能高,但是插入和删除元素性能差,因为这会涉及到移位操作

LinkedList是基于双链表实现的,开配的内存空间不要求连续,因此不支持索引,查找元素需要从头查找,因此性能差,但是添加删除只需要改变指针指向即可,性能高. LinkedList会增加内存碎片化,增加内存管理难度

根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用LinkedList

ArrayList和Vector区别

ArrayList是线程不安全的,Vector相反是线程安全的,方法加了同步锁,线程安全但是性能差,ArrayList底层数组容量不足时,会自动扩容0.5倍,Vector会自动扩容1倍

一个User的List集合,如何实现根据年龄排序

第一种方式,让User类实现Comparable接口,覆写compareTo方法,方法中自定义根据年龄比较的算法

第二种方式,调用Collections.sort方法,传入一个比较器,覆写compare方法,方法中自定义根据年龄比较的算法

哪些集合类是线程安全的?
  • Vector:就比Arraylist多了个 synchronized (线程安全),因为效率较低,现在已经不太建议使用。

  • hashTable:就比hashMap多了个synchronized (线程安全),不建议使用。

  • ConcurrentHashMap:它是HashMap的线程安全,支持高并发的版本

    在jdk1.7中,它是通过分段锁的方式来实现线程安全的。意思是将哈希表分成许多片段Segment,而Segment本质是一个可重入的互斥锁,所以叫做分段锁。

    在jdk1.8中,它是采用了CAS操作和synchronized来实现的,而且每个Node节点的value和next都用了volatile关键字修饰,保证了可见性

二.JavaEE&框架&中间件

数据库基础

平均值用什么,分组用什么

统计平局值:avg , 分组:group by

两个相同列的结果集求并集用什么

union 并集 , union all(允许重复并集)

完整查询SQL中的关键字的定义顺序

SELECT 列名 FROM 表1 JOIN 表2 ON 条件 WHERE 条件 GROUP BY 列名 HAVING 条件 ORDER BY 列名 LIMIT

完整的多表JOIN查询,SQL中关键字的执行顺序

FROM --> ON --> JOIN --> WHERE --> GROUP BY --> HAVING --> ORDER BY --> LIMIT

SQL面试题

员工表employee字段有: id, username, amount ,deptname .

  • 求每个部门总人数怎么做 ,

select 部门名,count(id) from employee group by deptname

  • 求每个部门总工资怎么做?

select 部门名,sum(amount) from employee group by deptname

Javaweb基础

常见Http状态码

200 成功返回状态

301 永久重定向,被请求的资源永久移动到新位置

302 临时重定向,被请求的资源临时移动到新的位置,项目中使用了oauth2,对目标资源访问无权限时就会见到,它是会重定向到授权地址

401 无权限访问

403 禁止访问,服务器已经接收到请求,但拒绝执行

404 找不到该资源

500 服务器内部错误 zuul找不到服务名就会见到

503 服务器内部错误 服务器维护或者过载

504 网关超时

Servlet的生命周期

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • Servlet 初始化后调用 init () 方法。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 销毁前调用 destroy() 方法。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
什么是过滤器?怎么创建一个过滤器

过滤器:在请求发送之后,处理之前对请求的一次拦截,可以更改请求状态或者参数值等。

创建过滤器:实现filter接口,重写doFilter方法,最后在web.xml中配置过滤器

讲一下Session的工作原理

服务端的session id会自动写入客户端的cookie中,每次请求客户端回自动把cookie带入后台,后台自动根据cookie中的sessionid就能找到session

Session和cookie有什么区别

session和cookie都是为了弥补http协议的无状态特性,解决会话问题

session是以ConcurrentHashMap结构存储在服务器端,同时生成一个sessionid返回客户端并存放到cookie中

cookie是将数据存储在客户浏览器端

session占用服务器的性能,但安全性较高,使用cookie减轻服务器的压力,但有被用户篡改风险因此安全性较低

6、说说preparedStatement和Statement的区别

关键字:?占位符,预编译,无注入风险

statement的sql语句使用字符串拼接,很容易出错,而preparedStatement使用?作为占位符,不容易出错易于维护

statement不对sql语句作处理,直接交给数据库,而preparedStatement支持预编译,事先将编译好的sql语句放到数据库端,相当于缓存,因此效率更高

statement有sql注入风险,preparedStatement没有sql注入风险

https://blog.csdn.net/ming2316780/article/details/48544867

7、请求转发和重定向的区别

转发是一次请求,可以共享同一组request和response,重定向是多次请求,不能共享同一组request和response

转发地址栏不会发生变化,重定向地址栏会发生变化

转发不能到外部资源,重定向可以到外部资源

如果我们需要数据共享,使用转发,如果需要访问内部资源(WEB-INF),使用转发,如果需要跨域到外部资源,必须使用重定向

结合现实理解:
重定向:

A到B家,找B帮忙办件事,B说:”这个事我办不了啊,你去找C吧“。然后把C的联系方式给了A,A到C家(可以跨域,地址栏改变)找C办这件事了。

请求转发:

A找B办件事,B说:“这个事我办不了啊,我想想办法吧”。然后A在B家(不可以跨域,地址栏不变)等了一会,B找C把A的事情办了。(A不知道B找了C)

区别:

1)请求转发发生在服务器端,由服务器(比如servlet)控制。

2)重定向发生在客户端,由客户(通常是浏览器)控制。

1)请求转发过程在同一个请求当中完成,只会返回一个响应。

2)重定向过程则发生在两个不同的请求中,会返回两个不同响应。

1)请求转发后,浏览器地址栏URL不会发生改变。

2)重定向后,浏览器地址栏URL变为新的URL(因为浏览器确实给新的URL发送了一个新的请求)。

1)请求转发是在web服务器内部进行的,不能跨域访问

2)重定向可以跨域访问

8、get和post请求的区别

4个方面:存放参数的地方,发送的数据类型,发送的内容大小,用途

get把参数包含在url中,post是把参数放到request body中,所以post相对于get更安全,所以post发送的数据更大,get有url的长度限制,所以在restful中,get一般用于用户查询搜索数据,post一般用于用户添加或者修改数据

post能发送更多的数据类型,get只能发送ASCII字符(ASCLL-American Standard Code for Information Interchange)

9、JSP的原理

动态网页技术标准(jsp全称JavaServer Pages),jsp的本质就是servlet,每个JSP文件都会被编译成一个Serverlet去执行,该Serverlet会对JSP中的动态内容进行替换,静态部分是标准的html,动态部分是java程序

Spring部分

介绍一下Spring

轻量级,IOC,AOP

​ Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理

说下Spring框架的组成
  1. CoreContain核心容器模块:

    1. spring-core:提供框架的基本组成部分,包括 IoC 和依赖注入功能
    2. spring-beans:提供 BeanFactory,工厂模式
    3. spring-ExpressionLanguage:提供表达式语言
    4. context:提供国际化,事件传播,资源加载等功能
  2. Web模块

    1. Web:提供面向web的基本功能和面向web的应用上下文

    2. //Web-MVC:为web应用提供模型视图控制(MVC)

    3.//Web-Socket:在 web 应用程序中提供客户端和服务器端之间通信的方式

    1. //Web-Portlet:模块提供了用于Portlet环境的MVC实现
  3. 数据/集成模块

    1. JDBC:包含了Spring对JDBC的支持(数据访问进行封装的所有类)
      1. //ORM:为对象-关系映射提供交互层
    2. //OXM:提供对Object/XML映射实现的抽象层
    3. //JMS:主要包含了一些制造、消费和消息的功能
    4. //Transaction:为实现特殊接口类以及所有的 POJO 支持编程式和声明式的事务管理
  4. 其他模块

    1. AOP:提供了面向切面编程相关实现
    2. Aspects:模块提供了与AspectJ的集成,是一个功能强大的AOP框架
    3. Instrumentation:提供了class instrumentation 的支持和类加载器classloader的实现
    4. Messaging:为 STOMP 提供支持
    5. Test:支持使用JUnit和TestNG对Spring组件进行测试
什么是Spring的IOC

IOC(Inversion of Control)控制反转,把对象的创建,属性设置,初始化,销毁等工作交给Spirng的IOC容器去管理,解放程序员的劳动力。

对象被注册到Spring的IOC容器中,使用的时候从容器中获取即可,非常方便。

它通过依赖注入,将需要的外部资源注入到组件中,使用IOC使得对象之间的耦合度降低,降低维护难度,从而使得代码更加优雅

你对AOP的理解

AOP,Aspect Oriented Programming 英文首字母缩写,意为面向切面编程,是Spring的核心思想之一

AOP是对OOP(面向对象编程)的一种补充,能够做到很多面向对象无法做到的事情,比如需要在所有方法执行前开启事务,打印日志,如果使用面向对象来编程,将会产生大量重复代码,而使用AOP,可以将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,一次解决这些问题。而这些重复的代码,一般统称为横切逻辑代码

使用AOP,在不改变原有业务逻辑的情况下,实现解耦合,避免横切逻辑代码重复

AOP的使用场景包括日志记录,性能统计,安全控制,事务处理,异常处理等等

AOP的原理(使用什么设计模式)

它是基于动态代理实现的,分为JDK动态代理和CGLIB动态代理。JDK动态代理只支持实现了接口的类 ,CGLIB支持没有实现接口的类。Spring默认使用JDK动态代理,如果被代理类没有实现接口,会选择CGLIB动态代理

另外Spring的AOP还用到了执行链模式。

Spring的Bean懒加载和非懒加载有什么区别

懒加载:需要使用对象的时候才创建,节省资源,但不利于提前发现错误

非懒加载,也叫迫切加载,容器启动时就创建对象,消耗资源,但有利于提前发现错误

spring中默认时迫切加载,即在项目启动时,spring会扫描符合条件的所有bean并将其初始化

如果需要懒加载,可以使用@Lazy注释或者xml中配置属性default-lazy-init=“true”

Spring的依赖注入方式有哪些

方式一:setter方式注入,通过反射调用无参构造方法生成对象,再通过对于的setter方法注入配置的值,支持注解和xml两种实现方式

方式二:构造器方式注入,通过反射调用有参构造方法生成对象,支持注解和xml两种实现方式

注解实现方式:@Autowired,它是默认按类型匹配的、@Resource,它是默认按名字匹配的

说一下定义切面相关的注解

@Aspect:定义切面

@Pointcut:定义切点 = cn.xx.service.*

@Before:前置通知,在目标方法运行之前运行

@After:后置通知,在目标方法运行结束之后运行(无论方法正常结束还是异常结束)

@AfterReturning:返回通知,在目标方法正常返回之后运行

@AfterThrowing:异常通知,在目标方法出现异常以后运行

@Around:动态代理,手动推进目标方法运行

Bean的四种注册方式

方式一:普通注册方式,直接通过class注册

方式二:简单静态工厂方式注册

方式三:简单实例工厂方式注册

方式四:FactoryBean方式注册

注册Bean的注解有哪些

@Controller/@RestController 一般用于定义控制层的类

@Service 一般用于定义服务层的类

@Repository 一般用于定义持久层类

@Component 定义一般类

@Configuration 定义配置类

单例多例的区别

关键字:数量上:对象一个多个。调用时:只实例化一个bean,实例化新的bean。

单例和多例属于对象模式,单例模式指对象在整个系统中只存在一份,多例模式则可以有多个实例。

如果一个bean是单例模式的,在处理多次请求的时候,在ioc容器中只实例化一个bean,这个对象会被保存在一个ConcurrentHashMap中,当有请求来的时候,会先从map中查看,如果有就直接使用这个对象,没有才会实例化新的对象。

如果是多例(prototype)模式的bean,每次请求来的时候,会直接实例化新的bean,没有map缓存的过程。

在spring的ioc容器中的bean默认都是单例的,如果需要使用多例,可以通过修改scope属性:scope=“prototype”

Bean被指定为prototype以及singleton有什么区别

这两者分别指的是多例和单例模式,singleton即单例模式,指对象在整个系统中只存在一份;prototype即多例模式系统中可以有多个实例。

如果一个bean是单例模式的,在处理多次请求的时候,在ioc容器中只实例化一个bean,这个对象会被保存在一个ConcurrentHashMap中,当有请求来的时候,会先从map中查看,如果有就直接使用这个对象,没有才会实例化新的对象。

如果是多例模式的bean,每次请求来的时候,会直接实例化新的bean,没有map缓存的过程。

在spring的ioc容器中的bean默认都是单例的,如果需要使用多例,可以指定scope属性:scope=“prototype”

BeanFactory和ApplicationContext有什么区别

BeanFactory接口是IOC容器的核心接口,定义了管理bean的最基本方法,比如实例化,配置,管理,获取bean的方法

ApplicationContext接口是BeanFactory接口的子接口,除了继承BeanFactory中所有管理bean的方法,还拥有环境、国际化、资源、事件等服务相关的接口

BeanFactory是延迟加载,ApplicationContext是迫切加载

Spring事务传播行为(机制)

事务传播行为指的是一个方法调用另外一个方法事务是怎么进行传递的在Spring中规定了7种类型的事务传播行为

PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。- 默认
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
事务失效的场景
  1. 未启用spring事务管理功能
  2. 方法不是public类型的
  3. 数据源未配置事务管理器
  4. 自身调用问题,比如:入口方法A没有事务,在方法内部调用该类中的B方法,B方法的事务会失效,因为入口方法没有事务就是在非代理情况下执行,那么B方法也会非代理执行。
  5. 异常类型错误
  6. 异常被吞了
  7. 业务和spring事务代码必须在一个线程中

SpringMVC部分

SpringMVC怎么样设定重定向和转发的

重定向是指将用户从当前请求重新定向到一个视图页面,或者是一个handler处理请求,以前的request域中信息全部失效,同时地址栏会发生变化,它是客户端行为

转发是指将用户从当前请求转发给另一个视图页面或者handler处理请求,以前的request域可以共享,地址栏不会发生变化,它是服务器行为

springmvc默认是使用转发方式跳转的,且会默认经过视图解析器,我们也可以通过指定,转发时在返回值前面加"forward:“,重定向时在返回值前面加"redirect:”,且此时就不会再经过视图解析器了

SpringMVC如何对时间格式的参数进行格式化

第一种需求,后台接收前台页面返回的string类型时间,要转换成的Date类型数据,可以使用@DateTimeFormat注解来接收参数

第二种需求,后台将Date类型数据返回给前台页面,默认是返回时间戳,如果想要优雅的格式,可以在模型的Date字段或get方法上使用@JsonFormat注解

SpringMVC常用的注解有哪些

@Controller:用来标识一个类是控制器类

@RequestMapping:用来映射请求路径和参数

@ResponseBody:将返回值放到responsebody中,通常返回json或者xml格式数据

@RequestBody:将前台请求参数转换成对象

@PathVariable:接收路径参数,通常用在restful接口中

@RestController:@Controller和@ResponseBody的组合注解

@ControllerAdvice:运用aop的思想,对全局做一些处理,比如结合@ExceptionHandler做全局异常捕获

如何定义SpringMVC的拦截器

SpringMVC 的拦截器主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、判断登录等功能上

第1步,定义拦截器:可以实现 HandlerInterceptor 接口来自定义拦截器,接口定义了三个方法,preHandler方法是在请求到达处理器之前执行,postHandler方法是在请求经过处理器之后、解析试图之前执行,afterCompletion方法是在视图渲染之后、返回客户端之前执行

第2步,配置拦截器:在springmvc的配置文件xml中,配置所有拦截路径,以及需要放行的路径

HandlerInterceptor和HandlerInterceptorAdapter的区别

HandlerInterceptor是接口,我们可以实现该接口来定义拦截器,HandlerInterceptorAdapter是抽象类,它实现了HandlerInterceptor接口的子接口AsyncHandlerInterceptor,我们可以继承该类来定义拦截器,它简化拦截器的实现,默认preHandler返回true

SpringMVC的执行原理 8-21

1.Http请求:客户端请求提交到DispatcherServlet-前端控制器

2.寻找处理器:由DispatcherServlet调用HandlerMapping-处理器映射器,根据url找到对应的的Handler

3.调用处理器:DispatcherServlet指定HandlerAdapter-处理器适配器去调用Handler

4.调用业务处理和返回结果:Handler调用业务逻辑处理完成后,返回ModelAndView

5.处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler-视图解析器,找到ModelAndView指定的视图

6.Http响应:将结果显示到客户端

SpringMVC的Controller是单例还是多例,有没有并发安全问题,如何解决8-21

在spring中,bean默认都是单例的,controller也是交给spring容器管理的一个bean,因此它也是单例的。

单例的好处是减少了创建对象和垃圾回收的时间,节省了内存资源,但同时单例会造成线程不安全的问题,因为当所有请求访问同一个controller实例,controller中的成员变量是所有线程公用的,某个线程如果修改了这个变量,别的请求再来拿这个变量就编程修改后的值了

要解决这个问题,最直接有效的方式就是不要在controller中定义成员变量,如果你非要定义成员变量,两种方式

第一种,可以给controller上加注解@Scope(“prototype”),将controller设置为多例模式,每次请求都重新实例化一个controller

第二种,使用ThreadLocal变量,让每一个线程都有自己独立的变量

RequestMapping 和 GetMapping有什么区别8-21

@Getmapping是一个组合注解,即是@RequestMapping(method = RequestMethod.GET)的缩写,意思是只接收get请求的方法

@Requestmapping如果没有指定请求方式,可以接收get,put等各种类型的请求

SpringBoot部分

相比Spring,Spring Boot有哪些优点8-21

Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效

它大量简化maven依赖,管理了大量的基础依赖

基于注解配置(JavaConfig),无需xml配置

内嵌Tomcat,部署流程简单

打包和部署更加灵活,允许独立运行

SpringBoot如何做全局异常处理8-22

可以使用@ControllerAdvice注解,编写一个全局异常处理类,再自定义一个方法使用@ExceptionHandler来捕获具体的异常并作相应的处理

通常情况下后台向前台返回结果时,会把结果封装成包含有错误码,错误信息以及数据本身的json数据,因此我们可以使用自定义异常类,自定义枚举错误码,在捕获全局异常后,向前台返回一个包含错误码的信息

@SpringBootApplication注解的含义8-22

包含3个注解:@ComponentScan,@EnableAutoConfiguration,@SpringBootConfiguration

@SpringBootApplication是SprnigBoot项目的核心注解,目的是开启自动配置,并表示该类为主启动类。它包含三个子标签

  • @ComponentScan注解:开启ioc自动扫描注解,默认扫描当前包及其子包中@Controller,@Service等,并把这些bean加载到ioc器中
  • @EnableAutoConfiguration注解:启用springboot自动配置,自动扫描所有classpath目录下面所有jar中的spring.factories文件实现配置类批量注册
  • @SpringBootConfiguration注解:标志该类为springboot配置类
spring-boot-starter-parent的作用8-22

3方面:管理jar包,管理依赖,维护依赖版本号

这是SpringBoot的父工程,它的作用是帮我们管理了很多的基础jar包,同时它继承了spring-boot-dependencies,在spring-boot-dependencies项目中通过管理了大量的依赖,同时通过维护了这些依赖的版本号

但是在项目中,还需要通过 去导入具体的依赖才能使用

spring-boot-starter-web的作用

此项目是Springboot和Springmvc整个的jar包,构建了web项目的基本环境,集成了日志,tomcat,springmvc,json支持等等

你知道spring-boot-starter吗 8-22

一个starter包中包含了SpringBoot的自动装配机制,可以自动初始化好一些必要的Bean。我认为一个Starter代表一套环境,比如:spring-boot-starter-web就把WEB环境所需要的包都导入了如Tomcat,log4j,springmvc,springweb,auto-config自动配置包等的呢个。然后它把SpringMVC需要的Bean都进行自动装备,比如前段控制器,编码过滤器等。

SpringBoot中如何读取配置8-23

方式一:使用@Value读取配置文件

方式二:使用@ConfigurationProperties读取配置文件

SpringBoot中日志的level有哪些8-23

日志级别从低到高分别为:

TRACE < DEBUG <INFO <WARN < ERROR

如果设置为 WARN,则低于 WARN 的信息都不会输出

Spring中默认使用INFO级别输出到控制台

SpringBoot中如何管理事务 8-23

事务(transaction)是指业务逻辑上对数据库进行的一系列持久化操作,要么全部成功,要么全部失败。

在Springboot中,可以通过xml配置和注解配置

xml方式通过配置DataSourceTransactionManager和transactionManager实现

注解方式配置通过在主启动类上加上@EnableTransactionManagement开启事务管理器,在具体的实现层service类上加上@Transactional 实现事务

Mybatis部分

MyBatis中${}取值和#{}取值的区别8-23

#{}能够防止SQL注入,因为底层使用PreparedStatement对象,预编译,性能较高

${}不能防止SQL注入,因为底层使用Statement对象,不会预编译而是拼接字符串,性能较低

能使用#{}时尽量使用#{},如果需要动态传入表名或者字段名需要用 比 如 , 像 O R D E R B Y 时 只 能 使 用 {}比如,像 ORDER BY 时只能使用 ORDERBY使{}

MyBatis关联查询中,延迟加载和饥饿加载的区别8-24

关键字:提前加载,关联的数据

延迟加载,是先从单表查询,需要使用关联数据的时候才发起关联查询,不用的时候不查询关联的数据,又叫懒加载.

饥饿加载,是在查询时将关联的数据立即查询出来加载进内存,不管用不用

MyBatis对象关联查询和集合关联查询怎么做8-24

关键字:association,javaType;collection,ofType

单个关联对象用association,适用于多对一的关联查询,使用javaType来定义实体类型,集合用collection,适用于一对多的关联查询,使用ofType来定义集合的泛型类型

MyBatis一级缓存和二级缓存的区别8-24

关键字:SqlSession;namespaces

缓存,是指将从数据库查询出的数据存放在缓存中,下次使用相同查询时不必再从数据库查询,而是直接从缓存中读取,从而减轻数据库查询的压力,提高性能

mybaits中的一级缓存,是SqlSession级别,默认开启,使用同一个SqlSession发送相同的SQL时命中;它的生命周期和SqlSession一致,当调用SqlSession.close()方法时会释放缓存

mybatis中的二级缓存,是namespace级别,默认不开启,执行同一个namespace的相同statement,发送相同的SQL时命中;它的生命周期是程序结束

当SQL中执行了update()、delete()、insert()操作,则缓存中的数据都会清空

MyBaits的Mapper接口没有实现类为什么可以用@Autowired直接注入8-24

关键字:动态代理

动态代理,赋值给mapper接口引用的对象其实是一个代理对象,这个代理对象是由 JDK 动态代理创建的。在解析mapper的时候,mybatis会通过java反射,获取到接口所有的方法,当调用接口中方法时,将通过接口全限定名+方法名对应找到映射文件中namespace和id匹配的sql,然后将执行结果返回

在MyBatis如何动态修改SQL 8-25

使用Mybatis的拦截器可以做到

MyBatis的动态SQL标签有哪些? 8-25

if标签:条件判断

choose、when、otherwise标签:选择结构,类似java中的switch

trim标签:对包含的内容加上前缀,后缀

where标签:主要是用来简化SQL语句中where条件判断的,能智能的处理and or,不必担心多余导致语法错误

foreach标签:遍历元素

Mybatis的mapper如何传递多个参数8-25

关键词:map引用取值,#{param1}引用取值,@Param接口

方式一,可以使用map进行传参,SQL中使用map的key来引用取值

方式二,可以在SQL中使用#{param1},#{param2}…来引用取值,它是根据mapper接口对应方法中形参的顺序进行匹配的,不管接口方法的参数名字叫个啥,SQL都只能使用param1,param2,等来取值

方式三,可以使用@Param注解,给mapper接口方法的参数命名,在SQL中直接使用取的名字来引用

Mybatis,关联对象查询,使用嵌套子查询和JOIN连表有什么区别8-25

关键词:嵌套子查询:子sql查询关联对象,n+1次。JOIN连表:主对象和关联对象,1次

嵌套子查询,指的是在查询一个主对象的时候,使用单表查询,在resultmap中额外发送一个子sql查询关联对象,然后映射给主对象

连表join查询,指的是查询一个主对象的时候,使用join连表的方式把主对象和关联对象的数据一次性查出来,用resultmap映射结果

他们的区别,join连表查询只发一条sql就能把数据查询出来,嵌套子查询会有一个n+1的问题,就是说如果主查询出来n条数据,那么会额外发送n条子sql去查询对应的关联对象,加上主查询那1次,也就是n+1次,因此它的性能相对较低的,一般我们会使用join连表查询

为什么要使用连接池 8-26

关键字:动态管理连接申请,节省内存,提高效率

对数据库的操作都需要取得连接,使用完都需要关闭连接,如果每次操作需要打开关闭连接,这样系统性能很低下。连接池就可以动态的管理这些连接的申请,使用和释放,我们操作数据库只需要在连接池里获取连接,使用完放回连接池,这样大大节省了内存,提高效率。

数据库连接池的原理主要分为三部分
关键字:建立,管理,关闭

  • 第一,连接池的建立,在系统初始化时建立几个连接对象以便使用。
  • 第二,连接池的管理,客户请求连接数据库时,首先查看连接池中是否有空闲连接,如果有直接分配,如果没有就等待,直到超出最大等待时间,抛出异常
  • 第三,连接池的关闭,当系统关闭时,连接池中所有连接关闭

Redis部分

讲一下你理解的Redis,为什么Redis很快 8-26

Redis是一种高性能的,开源的,C语言编写的非关系型数据库,可以对关系型数据库起到补充作用,同时支持持久化,可以将数据同步保存到磁盘

说Redis很快是相对于关系型数据库如mysql来说的,主要有以下因素

  • 第一,数据结构简单,所以速度快
  • 第二,直接在内存中读写数据,所以速度快
  • 第三,采用多路IO复用模型,减少网络IO的时间消耗,避免大量的无用操作,所以速度快
  • 第四,单线程避免了线程切换和上下文切换产生的消耗,所以速度快
你常用的Redis的数据存储结构有哪些,他们的使用场景分别是什么 8-26

Redis存储形式是键值对,支持value形式包括String,List,Set,ZSet,Hash。

String可以用作缓存,计数器,防攻击,验证码、登录过期等,List可以用来做队列,秒杀等,Set可以用来去重

Redis每种存储结构说 4 个命令吧 8-26

1.String

  • set key value 设置值
  • get key 取值
  • mset key value key value… 设置多个值
  • mget key key 获取多个值
  • incr key 将key中的值自增1
  • decre key 将key中的值自减1

2.List

  • lpush key value value… 从最左边设置值
  • rpush key value value… 从最右边设置值
  • lrange key start stop 查询key中指定区间的元素
  • lpop key 移出并返回key中最左边的元素
  • rpop key 移出并返回key中最右边的元素

3.Set

  • sadd key value value 添加元素
  • smembers key 返回集合key中的所有元素
  • srem key member 删除集合key中member元素
  • scard key 查询集合key中的元素数量

4.ZSet

  • zadd key score value (score value)… 添加元素
  • zcard key 查询集合key中元素数量
  • zcount key min max 返回有序集合key中score 在min和max之间的元素
  • zrange key start stop 返回有序集合key中索引在start和stop之间的元素

5.Hash

  • hset key field value 添加元素
  • hget key field 获取key集合中field键对应的值
  • hmset key field value (field value)… 添加元素并批量添加子键值对
  • hmget key field field 获取key集合中所有的子键值对
你们项目是怎么用Redis的 8-27

使用的是Springboot整合的redis,主要用来解决前后端分离后前后端会话问题,以及验证码的问题

怎么防止Redis宕机数据丢失问题 8-27

通过对Redis持久化,把内存中的数据和命令,保存一份到磁盘中做备份,当Redis发生宕机,重启服务器的时候,会从磁盘重新加载备份的数据,从而解决数据丢失问题

Redis持久化是什么?有几种方式 8-27

将内存中的数据备份到磁盘的过程,就叫作持久化

Redis持久化主要有两种方式,RDB和AOF,可以通过修改redis.conf进行配置

RDB是记录数据快照,而AOF是记录写命令的

Redis有了AOF持久化为什么还要RDB? 8-27

AOF和RDB各有所长

  • RDB是记录数据快照,它的优点是只产生一个持久化文件,体积相对较小,启动恢复速度快,备份方便,它的缺点是没办法做到数据百分百不丢失,因为它是每隔一定时间保存一次
  • AOF是记录写命令,它的优点是格式清晰,容易理解,数据更安全,采用append模式即使持久化过程中宕机,也不影响已经保存的数据,它的缺点是文件体积较大,恢复速度慢

根据实际需要来选择,通常二者可以结合来使用

Redis内存不够了怎么办? 8-28

方式一:增加物理内存

方式二:使用淘汰策略,删掉一些老旧数据

方式三:集群

你们Redis用在哪些业务上?用的什么存储结构8-28

主要用做缓存,比如:验证码,分类缓存,数据字典缓存,权限数据缓存,登录信息缓存等。

String类型的存储结构用的比较多,并且使用了Json格式进行序列化。

淘汰策略有哪些?你们用的哪种8-28
  • volatile-lru :从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  • allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集中任意选择数据淘汰
  • no-eviction:不使用淘汰
Redis事务和Mysql事务的区别8-28

Mysql的事务是基于日志,记录修改数据前后的状态来实现的,而Redis的事务是基于队列实现的

Mysql中的事务满足原子性:即一组操作要么同时成功,要么同时失败,

Redis中的事务不满足原子性,即一组操作中某些命令执行失败了,其他操作不会回滚

因此对于比较重要的数据,应该存放在mysql中

使用Redis如何实现消息广播8-29

关键字:发布订阅,

Redis是使用发布订阅来实现广播的

订阅者通过 SUBSCRIBE channel命令订阅某个频道 , 发布者通过 PUBLISH channel message向该频道发布消息,该频道的所有订阅者都可以收到消息

为什么要使用Redis做缓存 8-29

关键字:快,在内存中,读写大于磁盘,

一个字,快。
缓存它指的是将数据库的数据同步到内存中,客户端获取数据直接从内存中获取。由于内存读写速度大于磁盘,而使用缓存能减少磁盘读取,大大提高查询性能。

我们一般会将经常查询的,不会经常改变的热点数据,保存到缓存中,提高响应速度

缓存的执行流程 8-29

关键字:请-》缓存?-》客户端

​ -》无缓存-》数据库缓存-》客户端

1.客户端发起查询请求

2.判断缓存中是否有数据

  • 如果有,直接返回
  • 如果没有,就从数据库查询,再把数据同步到缓存

3.返回数据给客户端

你们怎么保证Redis/Mysql的一致性 8-29

关键字:写,删,缓存

我们在代码中控制,如果数据库做的是写操作,直接把redis中的对应数据删除,下次查询数据会重新写入缓存。

我们的业务对一致性要求不是很高,因此采用了先操作mysql,后删除redis。在写数据库和删除缓存行代码之间如果有查询请求依然会查询到Redis中的老数据,但是这种情况非常极端,而且我们的业务也能容忍这种短暂的脏数据。

我还知道其他方案,比如延迟双删 , 使用阿里的canal组件监听Mysql事务日志自动同步Redis等。

SpringCache常用注解 8-30

@EnableCaching:打在主启动类上,开启缓存功能

@Cacheable:打在方法上,表示该方法会开启缓存,打在类上,表示类中所有的方法都开启缓存,方法的返回值会自动写入缓存。如果缓存中已经有数据,方法将不会被调用,而是拿着缓存数据直接返回给客户端。

@CacheEvict:搭载类或者方法上,会将缓存清除

@CachePut:更新缓存

@Caching:组合操作,要应用于方法的多个缓存操作

@CacheConfig:打在类上,共享的一些常见缓存设置

了解缓存击穿,穿透,雪崩吗?怎么处理? 8-30

缓存击穿:缓存中没有,数据库中有的数据,由于某种原因比如缓存过期了,同时并发用户特别多,一时间都往数据库中读取数据

  • 解决方案:加互斥锁,只能允许一个线程访问数据库,然后其他线程就可以往内存中拿

缓存穿透:客户端频繁请求一个缓存和数据库中都没有数据,导致数据库压力大。

  • 解决方案:布隆过滤器来判断数据库中有没有这个key

缓存雪崩:缓存重启,或者大量key失效,导致大量并发打到数据库

  • 解决方案:为key设置不同的过期时间
Redis如何模拟队列和栈,用什么命令 8-30
  • list控制先进后出: lpush rpop ;

  • 队列:list控制先进先出: lpush lpop

Redis存储单个对象怎么存,存储对象集合怎么存 8-30

单个对象可以使用String,也可以使用hash

集合对象可以使用hash,以便可以快速的通过field来取值,获取列表可以通过hash的 vals 来获取,很方便

Redis的Value支持哪些存储结构 8-31

redis是keyvalue结构,value支持常用的存储方式有:string; list; set;zset ;hash 。

  • string:一般用来存储简单字符串,也可以存储对象或者集合,一般转JSON方式存储。
  • list和set:就是存储集合,一个有序可重复,一个无序不重复
  • zset:存储集合,支持排序,一般做排行榜什么的可以使用它
  • hash一般用来存储对象,当然集合也可以存,比如你需要存储一个商品列表,取的时候需要取到商品列表,同时也要方便取单个商品,那最好使用hash,把商品的ID作为hash的field,商品对象作为value。取列表的时候使用vals就可以,取单个对象使用get即可。
你们Redis用来做什么?使用的什么结构? 8-31
  • 登录信息login,使用的是String结构存储
  • 手机验证码code,使用的是String结构
  • 课程分类course_type ,使用的是String结构
  • 购物车保存,使用的是Hash结构
  • 数据字典等项目也是使用redis存储
  • 权限数据也是使用redis存储
统计全国高考前20名用什么?8-31

Zrevrangebyscore

从100个VIP用户中随机抽取5名怎么做?8-31

Srandmember

RocketMQ

MQ有哪些使用场景 8-16

可以用来消峰,异步,解耦, 日志收集等

  • 比如:秒杀,大促等高并发场景下使用MQ对请求进行排队,达到消峰的目的,防止服务器大流量打垮
  • 使用MQ对服务之间进行解耦和,同时也达到异步的效果,好处是服务消费者挂了,服务消费者依然可工作,同时也能提高系统的响应速度。
  • 还有异构系统之间做数据交互也可以使用MQ,通常使用JSON进行通信
  • 还有就是对大数据量的处理,你比如:日志收集也可以用到MQ。
说下一下RokcetMQ的架构8-16

为了增强Broker性能与吞吐量,Broker一般都是以集群形式出现的。各集群节点中可能存放着相同Topic的不同Queue。

不过,这里有个问题,如果某Broker节点宕机,如何保证数据不丢失呢?其解决方案是,将每个Broker集群节点进行横向扩展,即将Broker节点再建为一个HA集群,解决单点问题。

Broker节点集群是一个主从集群,即集群中具有Master与Slave两种角色。Master负责处理读写操作请求,Slave负责对Master中的数据进行备份。当Master挂掉了,Slave则会自动切换为Master去工作。所以这个Broker集群是主备集群。Consumer既可以从Master订阅消息,也可以从Slave订阅消息

一个Master可以包含多个Slave,但一个Slave只能隶属于一个Master。 Master与Slave 的对应关系是通过指定相同的BrokerName、不同的BrokerId 来确定的。BrokerId为0表示Master非0表示Slave。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。

RabbitMQ工作流程8-16
  1. 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
  2. Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
  3. 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
  4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
  5. Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息
RocketMQ的数据是存储到内存还是硬盘,如果是硬盘那么怎么保证速度8-16

RocketMQ将数据存在硬盘,RocketMQ的性能在所有的MQ中是比较高的,主要是因为RocketMQ使用了mmap零拷贝技术(内存映射技术),consumequeue中的数据是顺序存放的,还引入了PageCache的预读取机制,使得对 consumequeue文件的读取几乎接近于内存读取,即使在有消息堆积情况下也不会影响性能。

零拷贝:主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务

预读取算法:预测即将访问的页面,提前将页面批量读入内存缓存。

发送消息有几种方式

RocketMQ发送消息支持:同步,异步,单向消息

  • 同步消息: 发送者发送消息,需要等待结果的返回,才能继续发送第二条消息,这是一种阻塞式模型,虽然消息可靠性高,但是阻塞导致性能低
  • 异步消息:它是通过回调的方式来获取到消息的发送结果,消息可靠性高,性能也高
  • 单向消息: 单向发送是没有返回结果值的,可靠性不高

使用场景建议如下

  • 如果是比较重要的不可丢失的消息,且对时效性要去不高建议使用同步发送,如转账消息
  • 如果是不重要的可失败的消息,比如日志消息,建议使用单向发送
  • 如果对时效性要求比较高,且消息不能丢失,可以尝试使用异步发送
Queue的分配算法

Queue是如何分配给Consumer的,这对应了四种算法:平均分配策略,环形平均策略,一致性Hash策略,同机房策略。

  • 平均分配【默认】:根据 qeueuCount / consumerCount 作为每个消费者平均分配数量,如果多出来的queue就再依次逐个分配给Consumer。
  • 环形平均策略:根据消费者的顺序,一个一个的分配Queue即可类似于发扑克牌。
  • 一致性Hash策略 : 该算法将Consumer的Hash值作为节点放到Hash环上,然后将Queue的hash值也放入Hash环上,通过顺时针进行就近分配。
  • 同机房策略:该算法会根据queue的部署机房位置和consumer的位置,过滤出当前consumer相同机房的queue。然后按照平均分配策略或环形平均策略对同机房queue进行分配。如果没有同机房queue,则按照平均分配策略或环形平均策略对所有queue进行分配。

平均分配性能比较高,一致性Hash性能不高,但是能减少Rebalance,如果Consumer数量变动频繁可以使用一致性Hash。

Consumer消息的拉取模式有哪两种

消息到消费分为:拉取式 pull ,和推送是 push

  • Pull:拉取式,需要消费者间隔一定时间就去遍历关联的Queue,实时性差但是便于应用控制消息的拉取
  • Push:推送式,封装了Queue的遍历,实时性强,但是对系统资源占用比较多。
如何保证消息的顺序

按照发送的顺序进行消费就是顺序消息,遵循(FIFO), 默认生产者以Round Robin轮询方式把消息发送到不同的Queue分区队列;消费者从多个队列中消费消息,这种情况没法保证顺序。

RocketMQ分为全局有序和部分有序:全局有序是一个topic下的所有消息都要保证顺序,如果要保证消息全局顺序消费,就需要保证使用一个队列存放消息,一个消费者从这一个队列消费消息就能保证顺序,即:单线程执行

部分顺序消息只要保证某一组消息被顺序消费,即:只需要保证一个队列中的消息有序消费即可。比如:保证同一个订单ID的生成、付款、发货消息按照顺序消费即可实现

延迟消息有用过吗?怎么用?可以用在什么业务场景

可以设置消息的过期时间等级,可以用作VIP过期,支付超时,自动确认收货等。

RocketMQ事务消息的使用场景,举例说明

是最终一致性的事务场景,比如:注册保存用户同时赠送积分,由于赠送积分这个动作可以接受延迟同步,采用最终一致性。

滴滴打车的费用结算,跨行转账,等场景都可以考虑最终一致性。

RocketMQ事务消息工作流程

事务消息解决的就是,事务发送方执行本地事务和事务消息的原子性。

  1. 首先事务发起方:往MQ中发送一个事务消息-half半消息(准备消息),该消息不可被消费
  2. 然后事务发送方执行本地事务,并发送commit给MQ,如果是执行失败就会发送rollback给MQ
  3. MQ搜到commit指令,代表本地事务执行成功,之前的half消息就会成为可以被commit提交的消息,否则就是Rollback消息
  4. 事务参与者,也就是消费者会消费这个消息,然后执行相关的逻辑处理。如果是Rollback消息就不会被消费,而是丢弃
  5. 如果事务参与方并没有发送commit或者rollback指令MQ,MQ回调用事务发送方的回到方法来检查,我们需要去实现这个本地事务检查接口,通过返回commit或者rollback来告知MQ本地事务是否执行成功。

RabbitMQ

RabbitMQ的使用场景

rabbitMQ消息队列可以用来

  • 做任务的异步处理,提高程序的相应时间
  • 提高系统稳定性,通过手动确认机制,当消费者出现故障,只要没有确认签收,请求的数据都不会丢失可以再次处理
  • 服务解耦,生产者通过MQ与消费者交互
  • 消除峰值,通过异步处理,消息传到MQ直接返回,接着等待排队处理,避免了线路拥堵
RabbitMQ如何防止消息丢失

首先,RabbitMQ的消息确认机制,默认是自动签收,也就是说消息一旦被消费者接收,就自动签收,消息就从队列里清除了。因此对于重要的消息,不容丢失的数据,我们需要设置在消费完成后手动签收

其次,我们可以将消息持久化,避免消息在消费前MQ宕机,网络问题等造成的消息丢失

RabbitMQ的交换机有哪几种

Fanout:广播,将消息交给所有绑定到交换机的队列

Direct:定向,把消息交给符合指定routing key的队列

Topic:通配符,把消息交给符合routing pattern的队列

消息是如何从发送者到达消费者的(RabbitMQ工作流程)

分为消息发送和消息接收两个步骤

  • 消息发送:生产者和Broker建立TCP连接,创建信道。通过信道将消息发送给Broker,由Exchange将消息进行转发到指定的队列
  • 消息接收:消费者和Broker建立TCP连接 ,创建信道 ,然后监听指定的队列,当有消息到达队列时,Broker默认将消息推送给消费者,消费者就能接收到消息
如何防止消息重复消费

重复消费,一般时由于消费者消费成功后,在给MQ确认签收的时候出现了网络波动,MQ没有接到确认,就会继续给消费者投递之前的消息,造成消费者接收到了两条一样的消息。

我们可以通过实现消息的幂等性来避免这种情况,比如说让生产者给每个消息携带一个唯一的id,消费者获取消息后根据这个id去查询数据库,如果不存在就正常消费,如果存在了就证明该消息被消费过,直接丢弃

RabbitMQ消息投递失败,你们怎么处理

我们可以设置confirm回调和 returned 回调

比如说,可以在发送消息的时候,把消息详情包括交换机名,路由键,都保存到一个表中,状态设置为发送中,如果在confirm方法中ack为false,代表发送到交换机失败 ,就把这个记录状态修改为发送失败

然后我们创建一个定时任务定时扫表,去读取发送失败的数据并重新发送,为了优化性能,我们设置重试次数3次,如果3次都失败了,我们可以采取人工干预

ElasticSearch

Lucene创建索引原理

Lucene是基于倒排索引原理来实现的

  • 首先,将原文档进行分词处理,形成一个个单独的单词,
  • 然后取出标点符号以及停词,形成词元,
  • 再将词元做一些语言相关的处理,比如变成小写,转换时态,单复数形式等等,
  • 将得到的词创建一个字典,按照字母顺序排序,合并相同的词,最终生成一个倒排索引文档
ES的keyword和text区别

keyword:不分词,直接建立索引,支持模糊查询,精确查询,聚合查询

text:分词后建立索引,支持模糊查询,精确查询,不支持聚合查询

keyword通常用于通常用于存储年龄,性别,邮编,邮箱号码等等,直接将完整数据保存的场景

text通常存储全文搜索的数据,例如地址,文章内容的保存

ES的优势

ES是基于Lucene的开源搜索引擎,它解决了原生Lucene使用的不足,优化了Lucene的调用方式

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 支持实时分析搜索
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据
  • 通过简单的 RESTful API、可以跟各种语言的客户端甚至命令行进行交互
  • 上手非常容易,只需很少的学习就可以在生产环境中使用
Lucene/ES为什么那么快(ES用到什么数据结构)

传统搜索比如mysql的like关键字查询,它的搜索方式就是全文扫表,查询性能很低

ES是基于Lucene的全文检索引擎,它采用的是倒排索引结构,在存储时先对文档进行分词,再做一些标点符号去除,大小写时态转换等优化处理,最后按照字母顺序去重排序,形成一个倒排索引文档,我们在检索时,就可以通过二分查找的方式找到目标值

ES的分层结构,index下面是什么

Index:索引库,包含有一堆相似结构的文档数据,类比Mysql中的数据库

Type:类型,它是index中的一个逻辑数据分类,类比Mysql中的表

Document:文档:是ES中的最小数据单元,通常用json结构标识,类比Mysql中的一行数据

Field:字段:类比Mysql中的一个列

从ES7.0开始,Type被干掉了,从此库表合一即一个Index中只有一个默认的Type

讲几个ES中的查询对象:比如TermQuery

TermQuery:匹配关键字查询(关键词不分词)

MatchQuery:匹配关键字查询(关键字分词后)

BooleanQuery:按条件查询

matchAllQuery:匹配所有文档查询

rangeQuery:查询指定范围内的数据

你简单描述一下DSL语法

DSL是一种以json形式标识的,由ES提供的一种查询语言,它由两部分组成,DSL查询和DSL过滤。

DSL过滤类似于模糊查询,DSL查询类似于精确查询

你说一下 match和term的区别? 8-22

term:不会对搜索词进行分词处理,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到

match:会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到

你使用过ES的哪些聚合查询?8-22

指标聚合,比如求和,求最大值,最小值,平均数

数量统计聚合,计算满足条件数据的总条数,相当于sql中的count

去重聚合,它会计算非重复的数据个数,相当于sql中的distinct

桶聚合,它会将某个field的每个唯一值当成一个桶,并计算每个桶内的文档个数,相当于sql中的group by

最高权值聚合,它会匹配每组前n条数据,相当于sql中的group by后取出前n条

ES高亮怎么做的? 8-22

使用HighlightBuilder对关键字作高亮处理,由于我们项目使用的是SpringBoot整合ES的jar包,结果没有进行高亮处理,我们使用ElasticsearchTemplate的queryForPage方法来获取结果,再手动进行分页封装返回前台

你们ES和数据库的数据一致性怎么做的8-22

代码控制的,数据库做了写操作,直接更新ES中的数据,我知道可以通过 Logstash 中数据和ES的数据自动同步。也可以通过阿里的canal组件来同步。

你们项目怎么使用ES8-23

我们使用的是spring-boot-start-data-elasticsearch这个库来操作ES,用在大数据的搜索场景,比如商品的发布,搜索功能。

三.微服务部分

相关概念

什么是集群 8-23

关键字:每个服务器都能独立运行
集群是将应用复制成多个相同的应用,一起来工作,从而提高工作能力。即将多个应用程序分散在不同的服务器,每个服务器都独立运行相同的代码。可以分散服务器压力解决高并发的问题,同时也能预防单节点故障,即一台服务器故障不影响其他服务器正常运行,但没有解决单体应用代码臃肿,业务复杂,维护性差等等问题

什么是负载均衡 8-23

关键字:

使用了集群后,解决高并发同时有一个新的问题,就是客户端的请求如何分配到多台服务。因此需要通过负载均衡器,比如Nginx,使用负载均衡算法比如轮询、权重、随机等等将请求路由到不同的服务器

什么是分布式 8-23

分布式是将应用按照业务类型拆分成多个子应用,每个子应用部署在不同的服务器上单独运行,子应用之间通过API相互调用。

可以分散服务器压力解决高并发问题,同时可以解决单体应用代码臃肿、业务复杂、维护性差等等问题,但是不能防止单节点故障,比如一个子应用故障,整个应用就能不完整运行

分布式集群举例

如果一个任务由10个子任务组成,每个子任务单独执行需1小时,则在一台服务器上执行该任务需10小时。

采用分布式方案,提供10台服务器,每台服务器只负责处理一个子任务,不考虑子任务间的依赖关系,执行完这个任务只需一个小时。(这种工作模式的一个典型代表就是Hadoop的Map/Reduce分布式计算模型)

而采用集群方案,同样提供10台服务器,每台服务器都能独立处理这个任务。假设有10个任务同时到达,10个服务器将同时工作,10小时后,10个任务同时完成,这样,整身来看,还是1小时内完成一个任务!

集群和分布式的区别,分别解决什么问题8-24

集群是将一个应用程序复制多份,部署在多台服务器上,每个服务器中的程序都是完整的,可以独立运行

分布式是将一个应用程序拆分成多个子程序,分别部署在多台服务器上,每个服务器中的程序都是不完整的,所有服务器需要相互通信相互协调才能完成最终的业务

集群能解决高并发问题,同时能防止单节点故障,即一台服务器宕机不影响其他服务器的正常运行

分布式也能解决高并发问题,但不能防止单节点故障,即一台服务器宕机了,整体业务就无法完成

集群无法解决项目本身的代码臃肿、业务复杂等等问题,分布式能降低模块之间的耦合

实际应用中,我们可以将分布式和集群相结合,比如分布式某个子程序的负载很高,可以单独对这个子程序做集群

说一下你理解的微服务8-24

微服务也是一个分布式系统,它将单体应用进行细粒度拆分,形成多个微服务,每个服务独立运行,每个服务也都可以有自己的数据库,服务之间使用HTTP通信,互相协调完成整个系统的业务。

它的优点是服务之间解耦合,不同的服务可以有不同的编程语言,技术选型多元化,支持敏捷开发

他的缺点是分布式事务很复杂,部署麻烦,技术成本高,服务间通信对性能也有一定的损耗

什么是CAP理论 , 哪些技术用到AP,哪些用到CP 8-24

CAP理论指的是,在一个分布式系统中,一致性,可用性,分区容错性,三个要素最多只能同时实现两点。

分区容错性是分布式系统的内在要求,因此我们通常会在一致性和可用性之间做取舍。

满足CP,也就是满足一致性和容错性,舍弃可用性,如果系统允许有段时间失效就可以考虑。常见的如Redis,Nacos,ZooKeeper

满足AP,也就是满足可用性和容错性,舍弃一致性,如果系统允许出现短暂时间的不一致可以考虑。常见的如MySQL,Eureka

什么是强一致性和最终一致性 8-24

关键字:实时同步,最终会同步

强一致性是指数据在多个副本中总数实时同步的,如果能容忍数据在多个副本中在一定的延迟时间内同步,则是弱一致性

最终一致性则不要求数据什么时候同步,但是最终会同步即可。通常情况下我们在分布式领域选择会牺牲了强一致性,会采用最终一致性

什么是Base理论 8-25

关键字:数据最终要保证一致性

Base指的是基本可用,软状态,最终一致性。它是对CAP中的AP的扩展,意思是说当出现故障部分服务不可用时,要保证核心功能可用,允许在一段时间内数据不一致,但最终要保证一致性。满足Base理论的事务也叫柔性事务

分布式是否属于微服务?

答案是属于。微服务的意思也就是将模块拆分成一个独立的服务单元通过接口来实现数据的交互。但是微服务不一定是分布式,因为微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。这也是分布式和微服务的一个细微差别。

SpringCloud

讲一下你们公司微服务解决方案8-25

关键字:Springcloud Netflix,Eureka,Ribbon/OpenFeign,Hystrix,Zuul,Config,Bus,Sleuth

我司正在使用的是第一代微服务方案,Springcloud Netflix全家桶。

它是使用Eureka做服务注册与发现,也就是解决服务之间通信问题,

使用Ribbon/OpenFeign做客户端的负载均衡,也就是解决将请求路由到微服务集群的问题,

使用Hystrix断路器的熔断、降级来解决单节点故障,

使用Zuul做服务网关,将它作为整个微服务的大门,来实现登录、权限检查等业务,

使用Config分布式配置中心,来统一管理配置所有微服务的配置文件,

使用Bus消息总线给各个微服务广播消息,可以实现各个微服务配置的自动刷新,

使用Sleuth链路追踪,来实时监控各个微服务建的调用关系,快速定位故障节点

说一说Spring Cloud有哪些常用组件8-25

关键字:Eureka,Ribbon/OpenFeign,Hystrix,Zuul,Config,Bus,Sleuth

Eureka:做服务注册与发现,用来解决服务之间通信问题,

Ribbon/OpenFeign:用做客户端的负载均衡,也就是解决将请求路由到微服务集群的问题,

Hystrix:断路器,它的熔断、降级策略用来解决单节点故障,

Zuul:做服务网关,它是整个微服务的大门,可以用来实现登录、权限检查等业务,

Config:分布式配置中心,用来统一管理配置所有微服务的配置文件,

Bus:消息总线,用来给各个微服务广播消息,可以实现各个微服务配置的自动刷新,

Sleuth:链路追踪,用来实时监控各个微服务建的调用关系,快速定位故障节点

Spring Cloud的优缺点?8-25

关键字:优点:无耦合,HTTP,扩展,敏捷开发。缺点:繁琐,部署,技术成本,性能损耗

微服务相对单体应用来说

优点

  • 服务之间无耦合,代码简单方便开发维护,服务之间升级维护互不影响
  • 轻量级HTTP通信机制,不同的服务可以采用不同的编程语言
  • 有极强的扩展能力,业务量大的服务可以再次拆分服务,或者也可以集群部署
  • 支持时下流行的敏捷开发并做了优化

缺点

  • 分布式事务繁琐
  • 部署麻烦,开发人员的学习成本高
  • 技术成本高,开发人员需要花更多的时间学习相关技术
  • 微服务间的通信存在对性能的损耗问题
什么是服务注册 8-26

关键字:保存微服务的端口、ip。。

Eureka是一个服务注册与发现的组件,翻译成人话就是管理所有微服务的通讯录的组件。它包含注册中心,客户端两部分组成。客户端在启动的时候会向注册中心发送一条自我介绍信息,比如端口,ip等等,在注册中心就会保存一张所有微服务的通讯录。这就叫服务注册

什么是服务发现 8-26

关键字:根据微服务通讯录找到另一个微服务

微服务会定期的从客户端拉取一份微服务通讯录,到本地缓存起来,默认是30s一次。当一个微服务向另一个微服务发起调用,直接根据本地的通讯录找到对方的服务名,发送HTTP请求。这个就叫服务发现

什么是服务续约 8-26

关键字:剔除

微服务会定时(默认30s)发送心跳请求,告诉注册中心,自己还处于存活状态,那么服务中心就不会将其从清单中删除,否则,当微服务宕机或者网络故障等因素,没有在规定时间(默认90s)内提交心跳请求,注册中心就会将它从通讯录中删除。

如果服务挂了,注册中心要等到90s后剔除,那么在剔除前的这段时间内,挂掉的服务有可能还是会被调用,怎么处理? 8-26

关键字:修改时间,加快频率。熔断降级。集群

第一,可以修改注册中心剔除服务时间,同时加快服务续约心跳请求的频率

第二,可以使用Hystrix的熔断降级机制,当某个服务不可访问,快速失败,并返回托底数据

第三。重试,提供者集群

你知道EurekaClient服务发现和服务续约每隔30s做一次请求是用什么技术实现的吗? 8-27

使用了ScheduledThreadPoolExecutor线程池定时任务来实现

服务发现是先判断是否开启了服务发现功能(默认是开启的),获取定时任务的间隔时间(默认是30s),然后初始化服务发现的定时任务,间隔时间可以在yml中修改

服务续约是先判断是否开启服务注册功能(默认是开启的),获取定时任务间隔时间(默认是30s),然后初始化心跳请求的定时任务,间隔时间可以在yml中修改

Ribbon是什么,Ribbon的工作原理讲一下 8-27

Ribbon是一个客户端负债均衡器,它可以按照负债均衡算法,向多个服务发起调用。当一个微服务有多个集群时,就可以使用它做请求负载均衡,通常结合RestTemplate来使用

说一下 Ribbon的工作原理 8-27

消费者会30/次注册中心拉取服务注册清单缓存到本地,当消费者需要调用一组提供者集群服务时,Ribbon会根据提供者服务名,在本地缓存的服务地址清单里找到这一组服务的通讯地址,然后按照负债均衡算法(默认是轮询),选择其中的一个通讯地址,发起http调用服务。

Ribobn内部通过LoadBalancerInterceptor拦截RestTemplate发起的请求,然后交给RibbonLoadBalancerClient负载均衡客户端做负载均衡,RibbonLoadBalancerClient把选择服务的工作交给ILoadBalancer负载均衡器 ,ILoadBalancer会调用 IRule负载均衡算法类来选择服务。之后RibbonLoadBalancerClient把选择好的服务交给LoadBalancerRequest去发请求。

Ribbon有哪些负载均衡算法,怎么配置 8-27

RoundRobinRule:简单轮询,ribbon默认规则

AvailabilityFilteringRule:忽略短路状态和并发过高的服务器

WeightedResponseTimeRule:根据服务器响应时间作为权重,响应时间越长权重越小

ZoneAvoidanceRule:根据区域选择

BestAvailableRule:忽略短路的服务器,选择并发较低的服务器

RandomRule:随机选择一个可用服务器

Retry:重试机制的选择逻辑

OpenFeign和Ribbon的区别 8-28

OpenFeign整合了Ribbon和Hystrix,屏蔽了Ribbon拼接URL,参数的细节,使用声明式编程,让服务调用变得更加简单,OpenFiegn底层也是走的Ribbon的负载均衡策略。推荐使用OpenFeign

OpengFiegn的工作流程 8-28

首先,当程序启动时,@EnableFeignClient会扫描@FeignClient注解的接口,并交给Spring容器管理。

当发起请求时,会使用jdk动态代理,并为每个方法都生成相应的RequestTemplate,同时封装http信息,包括url和请求参数等,

最后把RestTemplate交个HttpClient发送请求,使用ribbon的负载均衡发起调用

为什么要使用Eureka 为什么要使用Ribbon 为什么要使用config配置中心 8-28

在微服务系统中,各个服务之间是需要进行网络通信的,那么他们相互调用就得知道对方的通信地址。eureka就是专门来做做服务注册与发现,解决服务之间通信问题的

当一个微服务做了集群,也就是同一个服务名会对应多个地址,那么我们在调用的时候,应该调用哪一个就成了问题,Ribbon是一个负债均衡器,它可以按照负债均衡算法,向多个服务发起调用。当一个微服务有多个集群时,就可以使用它做请求的分发

在微服务系统中,服务数量很多,而每个服务都有自己的配置文件,管理起来很麻烦。用了配置中心就可以帮我们集中管理配置文件,它支持本地配置文件,也支持将配置文件放到远程仓库如git集中管理

为什么Feign的客户端接口没有写实现类也可以直接被依赖注入 8-28

自动注入的实例其实是一个jdk动态代理对象,Feign会为每个方法生成相应的requestTemplate,它根据服务名找到对应的服务,根据返回值类型、形参列表匹配相应的接口,然后封装url、请求参数,最后生成request请求,使用Ribbon负载均衡发起调用

介绍一下Hystrix 8-29

关键字:熔断器,熔断降级

Hystrix意为熔断器,它可以将出现故障的服务,通过熔断、降级等手段隔离开,这样不影响整个系统的主业务。它可以防止由单节点异常导致整个微服务故障,如果遇到故障时,快速失败,熔断的同时可以返回拖底数据达到服务降级的目的

什么是熔断,什么是降级 8-29

关键字:熔断:保护机制,短路,降级拖底数据。降级:不可以,返回拖底数据;

熔断,是对服务链路的一种保护机制,当链路上的某个服务不可访问时,服务就会触发降级返回拖底数据,同时当失败率到达一个阈值,就标记该服务为短路状态,当请求访问时直接熔断。直到检查到该服务能正常访问时,就快速恢复

降级,是当某个服务不可访问时,我们返回一些事先准备好的数据给客户端,比如说,友情提示服务暂不可用,请骚后重试,这样用户体验就上去了

什么是资源隔离? 8-29

关键字:限流,线程池隔离:当前请求。信号量隔离:记录。

指的是限制某一个分布式服务的资源使用,可以理解为限流,也就是限制某个服务的请求数量。它包括线程池隔离和信号量隔离

线程池隔离,是指用一个线程池来存储当前请求,可以通过设置线程池最大线程数最大排队队列数来限制请求数量

信号量隔离:是指用一个计数器来记录当前有多少个线程在运行,请求进来计数器就增加1,超过最大信号量,就直接返回

资源隔离中信号量和线程池的区别? 8-29

关键字:同一个线程,开销大小

线程池方式是异步处理,它与调用线程不是同一个线程

信号量方式是同步处理,与调用线程是同一个线程

线程池方式由于需要排队,调度,线程切换,因此开销较大,信号量方式无需切换线程,开销较小

对于CAP理论,Eureka选择的是AP还是CP?它保证了一致性还是可用性? 8-30

CAP理论指的是,一个分布式系统中,一致性,可用性,分区容错性,三个要素只能同时实现两点。Eureka选择的是AP,它是弱一致性的,保证了可用性和分区容错性,放弃了数据一致性。也就是说当多个Eureka之间不可通信时,需要保证服务可用,正常提供服务注册发现功能,但是网络恢复后最终还是会同步的。

说一下Eureka的自我保护 8-30

为了防止服务被误删除,Eureka不会立即删除过时的服务数据。这种机制可能会导致客户端从注册中心获取到已经下线的服务并发起调用而导致错误,因此在开发阶段我们可以关闭自我保护机制。在生产环境中,我们需要打开自我保护,因为它可以防止因为网络波动,服务没有及时续约而造成的服务误删除问题。

你们项目是如何做服务降级的?8-30

比如在秒杀业务中,需要实时从redis中查询库存,通过设置hystrix的最大信号量,以此来防止redis雪崩。当并发过高,请求数超过最大信号量,触发降级,直接向客户端返回兜底数据:”活动太火爆啦,请骚后重试“

Zuul有哪几类Filter,他们的执行顺序是怎么样的? 8-30

zuul按照执行顺序,分为pre前置过滤,route路由过滤,post后置过滤,error异常后过滤

正常流程是请求先经过前置过滤器,到达路由过滤器进行路由,路由到各种微服务执行请求,返回结果后经过后置过滤,返回用户

异常流程,如果再整个过程中出现异常,都会进入error异常过滤器,处理完毕后经过post过滤器返回用户,如果error自己出现异常,最终也会通过post过滤器返回用户,如果post过滤器出现异常,也会跳转到error过滤器,然后直接返回用户

在Zuul中做登录检查如何实现?8-31

可以通过继承ZuulFilter抽象类,自定义pre类型的过滤器,shouldFilter方法中可以定义需要放行的资源,run方法中检查请求头中的token信息,如果没有token,就响应到客户端未登录的信息,并组织filter继续往后执行

在Zuul中如何做限流?8-31

方式一:可以通过继承ZuulFilter抽象类自定义pre过滤器,加上限流算法,来实现

方式二:可以通过hystrix的资源隔离模式,设置线程池最大连接数或者最大信号量来实现

方式三:常用,Ratelimit,使用令牌桶算法。。。

配置中心解决什么问题? 8-31

GJZ:集中管理

在分布式系统中,服务数量很多,而每个服务都有自己的配置文件,管理起来很麻烦。配置中心是个好东西,可以帮我们集中管理配置文件,它支持本地配置文件,也支持将配置文件放到远程仓库如git集中管理。

EureakServer的搭建流程 8-31

第一步,导入eureka-server依赖,以及springboot的web环境依赖。

第二布,主启动类上打注解,@EnableEurekaServer,开启eureka服务端功能

第三步,yml配置文件中,配置注册中心的端口号,主机名,注册中心地址

Ribbon的整合流程 9-1

第一步,导入ribbon依赖

第二部,给RestTemplate的Bean定义方法上,加上注解@LoadBalanced,让这个restTemplate有负载均衡的功能

第三步,修改restTemplate调用服务的url,将目标主机名换成目标服务名

Feign的整合流程 9-1

第一步,导入openfeign依赖

第二部,主配置类加注解,@EnableFeignClients,开启feign支持

第三步,定义feign客户端接口,并加上注解@FeignClient(“目标服务名”),接口中定义方法,该方法与目标服务的对应方法的方法名,返回值类型,形参列表,url路径要一致

Hystrix的整合流程 9-1
  • 第一步,导入hystrix依赖

  • 第二部,主启动类加注解,@EnableCircuitBreaker,开启熔断功能

  • 第三步,在需要开启熔断功能的方法上,加注解@HystrixCommand(fallbackMethod=“xxx”),xxx是降级方法

  • 第四步,定义降级方法,方法名需要和fallbackMethod的值一致,形参列表和返回值类型需要和目标方法一致

feign整合Hystrix:

  • 第一步,yml中配置,feign.hystrix.enable=true,开启hystrix功能

  • 第二部,@FeignClient标签中,定义fallback或者fallbackFactory,指定降级类

  • 第三步,

如果是fallback,就实现feign接口,并覆写接口中的方法作为降级方法

如果是fallbackFactory,就实现FallbackFactory接口,同时指定泛型为feign接口,覆写create方法,返回一个feign接口的匿名内部类,类中写降级方法

Zuul的整合流程 9-1

第一步,导入zuul依赖

第二步,主启动类上加注解@EnableZuulProxy,开启zuul功能

第三步,yml中配置,统一访问前缀prefix,禁用通过服务名方式访问服务ignoredServices,配置路由routes指定某个服务使用某个路径来访问

ConfigServer的整合流程 9-2

配置中心服务端配置:

​ 第一步,导入config-server依赖

​ 第二步,主启动类加注解,@EnableConfigServer,开启配置中心

​ 第三步,配置文件中,配置远程仓库地址,仓库账号密码

客户端配置:

​ 第一步,导入config-client依赖

​ 第二步,创建bootstrap.yml配置文件,配置中心地址config.uri,要拉取的配置文件名name,环境名profile

你们微服务项目的技术栈描述一下 9-2

​ 前端门户系统:HTML + JQuery + CSS

​ 前端管理系统:VUE + ElementUI

​ 后端系统:基于SpringCloud微服务框架(Eureka+OpenFeign+Hystrix+Zuul+Config)

+MyBatisPlus+SpringMVC+Redis+ElasticSearch+RabbitMQ+AlicloudOSS

浏览器发起一个请求,在你的微服务项目中的怎么去执行的? 9-2

浏览器发起的所有请求首先通过Nginx,通过负载均衡算法,路由给zuul集群,然后通过zuul前置过滤,作登录校验后,它会从配置中心拉取的通讯地址中,根据url匹配到对应的服务,然后使用ribbon发起restful调用。微服务间也可以通过feign相互调用,最终执行完任务,返回浏览器

说下Ribbon和Feign的区别呢 9-2

Ribbon和Feign都是SpringCloud Netflix中实现负载均衡的组件,不同点在于

Ribbon是需要我们手动构建http请求,根据目标服务名通过负载均衡算法直接调用目标服务,

Feign是采用接口的方式,将需要调用的目标服务方法定义成抽象方法,路径,服务名,形参列表,返回值类型需要保持一致。我们只需要调用接口中的方法就可以了。它会自动帮我们生成jdk动态代理,为每个方法生成RequestTemplate并封装url和请求参数,使用负载均衡算法发起调用

Ribbon的实现方式,一般配合RestTemplate发起http请求,我们需要在注册RestTemplate的Bean的方法上加@LoadBalanced,使它具有负载均衡的能力

Feign的实现方式,是在主启动类上加@EnableFeignClients,在客户端接口上加注解@FeignClient

Spring,SpringBoot和SpringCloud的关系以及区别 9-3

Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理。

Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效。比如它大量简化maven依赖,基于注解配置(JavaConfig)无需XML,内嵌Tomcat,部署流程简单,打包和部署更加灵活,允许独立运行

SpringCloud是基于SpringBoot实现的,用于微服务架构中管理和协调服务的,它是一系列框架的有序集合,它为开发者提供了一系列工具,例如服务发现与注册,配置中心,网关,负载均衡,熔断器,链路追踪等等,让微服务架构落地变得更简单

分布式事务

什么是分布式事务 9-3

分布式事务,指的是在分布式环境中,一个请求可能涉及到对多个数据库的写操作,要保证多数据库的一致性就需要用到分布式事务

分布式事务你知道哪些解决方案? 这些方案如何选型 9-3

常见的分布式事务解决方案,2PC,TCC,可靠消息最终一致性,最大努力通知

2PC,它将整个事务流程分为两个阶段,P指的是准备阶段,C指的是提交阶段。它是一个阻塞协议,不适用于并发较高,事务生命周期长的分布式事务。

TCC,它是基于补偿性事务的AP系统的一种实现,补偿也就是说先按照预定方案执行,如果失败了就走补偿方案。它可以自己定义数据操作的粒度,但是对应用的侵入性强,可以用在登录送积分,送优惠券等等场景

可靠消息最终一致性,指的是当事务发起方执行完本地事务后,就发出一条消息通知其他参与方,并且他们一定能接收到消息并处理事务。适合执行周期长,并且实时性要求不高的场景

最大努力通知,是在不影响主业务的情况下,尽可能的保证数据的一致性,它适用于一些最终一致性敏感度低的业务,比如支付结果通知

什么是2pc 9-3

2PC,是将整个事务流程分为两个阶段,P指的是准备阶段,C指的是提交阶段。它常见的标准有XA,JTA,Seata

由DTP模型定义事务管理器TM和资源管理器RM之间通讯的接口规范叫做XA,它规定的交互方式是酱紫的:应用程序(AP)通过TM提交和回滚事务,TM通过XA接口来通知RM数据库事务的开始,结束,提交,回滚

2PC能保证分布式事务的原子性,但是也有很多缺陷

比如,在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好

比如,在第二阶段,如果事务协调者发出提交事务指令后宕机,一部分参与者收到消息提交了事务,另一部分没有收到消息没有提交事务,这就会导致数据不一致

再比如,在第二阶段,如果事务协调者发出提交事务指令后宕机,收到指令的参与者也宕机了,我们就不能确定事务的执行结果,究竟有没有提交

Seata相比传统2PC有什么区别,以及优点? 9-4

Seata是由阿里中间件团队发起的开源项目Fescar更名而来,是一个开源的分布式事务框架,它通过对本地关系数据库的分支事务协调,来驱动完成全局事务

Seata的主要优点是性能好,不会长时间占用链接资源,对业务零入侵

与传统的2PC的区别主要两方面

在架构层次方面,传统的2PC方案的RM本质就是数据库自身,而Seata的RM是以jar包形式作为中间件层部署在应用程序上

在两阶段提交上方面,传统2PC方案是在第二阶段完成才释放资源,而Seata是在第一阶段就将本地事务提交,提高了效率

Seata的TC,TM,RM的含义,以及作用? 9-4

TC:事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚

TM:事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令

RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支事务的提交和回滚

你知道TCC吗,它有什么样的优缺点? 9-4

TCC是基于补偿型事务的AP系统的一种实现。补偿指的先按照事先预定的方案去执行,如果失败了就走补偿方案

它的优点是异步执行效率高,它能对分布式事务中的各个资源分别锁定,分别提交与释放

它的缺点是对应用的侵入性强,改动成本高,实现难度大

解释一下Seata的工作原理 9-4

SpringCloudAlibaba极简入门-分布式事务实战seata
Seata有三个角色:

  • TM任务管理器(Transaction Manager),负责开启,提交,回滚事务的发起,
  • TC事务协调器(Transaction Coordinator) ,接收TM的指令通知RM提交或者回滚事务
  • RM资源管理器(Resource Manager),控制着分支事务的提交和回滚

假设有服务A需要调用服务B,且两个服务都需要修改各自的数据库,A服务作为程序入口充当TM和RM,B服务控制着分支事务充当RM。

  • A服务的TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID

  • A服务的RM向TC注册分支事务,并将其纳入XID对应全局事务的管辖

  • A服务执行分支事务,写undolog日志,向TC上报事务状态

  • 当调用B服务时,B服务的RM向TC注册分支事务,该分支事务执行,然后写undolog,向TC上报事务状态

  • 服务执行完毕A服务的TM向TC发送commit或者rollback指令

  • TC接收到指令,向参与事务的RM发送指令

  • 事务参与者RM受到commit指令,删除undolog日志。 如果是rollback指令就根据undolog回滚

Seata
在这里插入图片描述

你能简单描述一下你在项目中是如何集成Seata的吗 9-5

事务协调器:安装并启动Seata客户端

主业务端:

  • 第一步,导入Seata依赖

  • 第二步,yml中配置事务组名,同时需要添加配置文件file.conf,registry.conf,需要注意yml中事务组名与file.comf中的事务组名一致

  • 第三步,配置DataSource,需要适用Seata对DataSource进行代理

  • 第四步,数据库中添加undo log日志表

  • 第五步,业务方法上加注解@GlobalTransactional(rollbackFor = Exception.class)注解

事务参与者:

  • 前四步与主业务端相同,第五步不需要了
没有Seata或者TCC这些事务框架,你可以怎么处理事务?9-5

不用框架就要自己实现,如果业务要求强一致性这个不太好做,需要协调多个数据库的同时提交和回滚.如果是业务不要求强一致性,我可以参照TCC思想 ,可以考虑自己实现异步写数据库方案,如果失败可以做补偿.当然这个要根据业务特性来,很多大公司都是自己封装事务框架.

分布式锁

你说一下什么是分布式锁 9-5

分布式锁是在分布式/集群环境中解决多线程并发造成的一系列数据安全问题.所用到的锁就是分布式锁,这种锁需要被多个应用共享才可以,通常使用Redis和zookeeper来实现。

分布式锁有哪些解决方案 9-5

分布式锁常用的三种方案

基于数据库实现:通常基于主键,或者唯一索引来实现分布式锁,但是性能比较差,一般不建议使用

基于Redis :可以使用setnx来加锁 ,但是需要设置锁的自动删除来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合。

另外释放锁在finallly中调用del删除锁,而删除锁前需要判断该锁是否是当前线程加的锁以免误删除锁,需要通过get获取锁然后进行判断,但是需要保证get判断或和del删除锁的原子性,可以使用LUA脚本实现。

总之自己封装Redis的分布式锁是很麻烦的,我们可以使用Redissoin来实现分布式锁,Redissoin已经封装好了。

基于zookeeper : 使用临时顺序节点实现,线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁,后面的节点监听自己的上一个节点的删除事件,如果第一个节点被删除,释放锁第二个节点就成为第一个节点,获取到锁。

在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案。

Redis如何实现分布式锁,用什么命令

可以使用setnx来加锁 ,但是需要设置锁的自动删除来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合。

Redis实现分布式锁可能会出现什么问题,如何解决

添加锁和设置过期时间可以使用set命令进行组合,达到原子性加锁

需要用lua解决删除和判断锁的原子性,否则可能会删除掉别人的锁。

Redis集群环境中,redis节点挂掉可能会导致加锁失败,可以使用Redisson的红锁来解决。

你项目中怎么使用分布式锁的

自己封装Redis的分布式锁是很麻烦的,我们可以使用Redissoin来实现分布式锁,Redissoin已经封装好了

了解Redission的看门狗原理吗?

Redisson对分布式锁进行了封装,对于锁超时问题,它提供了看门狗进行锁时间的续期,底层使用了定时任务每10s检查一下,如果业务还未执行完成,未释放锁,就进行超时时间续期。

你在项目中如果使用ZK实现分布式锁的?

基于zookeeper : 使用临时顺序节点实现,线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁,后面的节点监听自己的上一个节点的删除事件,如果第一个节点被删除,释放锁第二个节点就成为第一个节点,获取到锁。

在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案。

秒杀系统

秒杀的整体流程详细说一下

秒杀的商品和库存是缓存到Redis的,库存使用信号量,做的是秒杀预减库存方案。用户发起秒杀,直接走Redis秒杀商品,满足资格就预减库存,然后预创订单写入Redis。整个秒杀流程是不做数据罗库的。

此时把订单号返回给客户端,用户带着订单号进入订单确认页面进行下单,用户确认下单,再把Redis中的预创订单写入订单数据,同时做库存同步。紧接着就是调用支付接口做支付。

你们这个秒杀QPS是多少

我们用户量不是很大,我进去的时候是50都W的用户,要求的是能抗住5000的QPS,线上环境实际的QPS是3千多,具体的不清楚,因为是经理他们在看,我开发完这个功能在本地jemeter压测的能达到1500的吞吐量。在线上环境做做集群什么的还是很容易达到5千以上的。

你们是怎么保证这么高的QPS的

第一个是纯Redis秒杀,库存和商品本身都是使用Redis缓存,库存使用过的是信号量来保证原子性,订单也是放到Redis,用户确认订单后才入库。基于纯Redis秒杀,然后再做做集群上几千是很容易的。

如果流量更高,比如:每秒10W请求,应该怎么处理

Lvs+Nginx集群+下游服务集群。如果流量再高,就使用CDN分流。

说一下支付超时处理方案?延迟队列和死信队列是什么意思?

支付超时使用MQ延迟队列来处理,把消息投递到一个设置了过期时间的队列中,达到过期时间消息会被转发给另外一个“死信队列”

设置了过期时间的队列就是延迟队列,过期的消息叫着死信消息,存放死信消息的队列叫死信队列。

整个秒杀流程你用到了哪些队列

下单业务中用到了一个低劣,订单超时用到一个队列,支付结果处理用到一个队列。

秒杀成功,返回给用户的数据是什么?

预创订单号,前台通过这个订单号来进行下单。

怎么防止表达重复提交

表单重复提交一般都用令牌机制嘛,在上一个页面生成一个随机令牌存储到Redis,然后把令牌带入表单页面,提交订单时把令牌带到后台,后台先去Redis比对令牌,然后进行下单逻辑,下单后就把令牌从Redis删除。 如果是重复提交,那么Redis中已经没有令牌,就会下单失败。

你们怎么处理超卖

Redisson分布式锁,信号量来保证库存不超卖,它也是一种分布式锁,它能够保证多线程在扣减信号量的时候程原子性减,然后保证不会加成负数。

如果一个接口需要耗时1s,导致后续的请求堵塞,怎么处理

这种就是要提高接口的响应速度,减少和数据库的交互,可以基于Redis优化,或者使用MQ异步方案进行处理。

如何提高接口的qps

一方面:提高并发数

1.多线程,尽量用线程池 (线程个数:CPU核数 / (1 - 阻塞系数(IO密集型接近1,计算密集型接近0)))

2.适当调整连接数(Tomcat,Redis,Mysql等连接数)

3.集群

二方面:提高接口响应速度

1.减少和数据库交互,使用Redis代替

2.使用异步方案,比如MQ

3.使用并发编程,多个线程同时工作

4.减少服务的调用链

5.实在要连数据库,考虑数据库优化

项目并发高处理过不过来怎么办

前端优化:

  • 使用页面静态化技术由Nginx实现动静分离、
  • CDN加速加快响应速度、
  • 使用验证码使流量错峰等手段最大限度的降低并发

后端优化:

  • Nginx+LVS负载,也可以多机房部署,分流
  • 从架构上使用分布式、集群分散并发量,
  • 从数据结构上使用缓存如Redis减少数据读写时间,
  • 从处理方式上采用如RabitMQ队列实现异步响应,
  • 资源隔离比如使用Hystrix的信号量隔离来限流,同时做好备用方案比如Hystrix的熔断降级策略等等

订单支付

点击确认订单背后的业务流程
  • 用户确认好支付方式,收获地址,点击确认订单,会把商品编号,支付方式,搜索地址作为参数提交到后台
  • 后台做了参数校验后会去准备订单数据,如:计算价格,然后构建订单和订单明细
  • 然后保存订单和订单明细,同时需要扣减库存,以及保存支付单,该支付单用作支付申请用,也有可能还会做一些其他业务比如赠送积分什么的 , 然后返回结果,一般是返回订单号。
  • 然后页面发起支付请求,调用支付中心接口,支付接口根据支付单构建支付申请,此时会返回一个html表单
  • 把html表单交给浏览器去提交,就会进入收银台-也就是支付页面,用户登录自己的账号进行支付。
  • 在接下来就是异步回调处理了,需要做签名检查,参数核对比如金额核对,收款人,付款人核对
  • 再接着就是根据支付结果处理业务了,比如:修改业务单和支付单状态,保存支付流水等。
  • 我们的系统是把支付结果扔给MQ,其他微服务来消费然后做相关的业务处理。
你们使用支付平台的哪些接口
  • 支付接口
  • 订单查询接口- 根据订单号查询支付状态,对账的时候用
  • 下载账单接口 -也是用作对账,只是下载某个时间段的账单
  • 取消订单接口 - 我们平台订单自动取消的时候,也会调用支付平台的取消功能
  • 退款功能
你接入不同的支付平台,支付方式也会有些不一样,可以使用什么设计模式

不同的平台接入流程是一样的,但是每个具体的步骤不同,可以使用模板模式+策略模式来实现。

订单超时使用quartz来做是有性能问题的,怎么处理

你说的是空扫描和延迟问题吧,小项目数据量少到无所谓,如果是数据量很多,可以使用MQ延迟队列来解决

如果延迟队列在退库存的时候,正好用户下单成功,怎么办?

这就是 典型的分布式场景下,多个线程对同一个数据的并发操作,可以使用分布式锁来实现,我们使用的redission的RLOCK重入锁来实现的。

如果用户正好在付款切付款成功了,这个时候订单自动超时了,怎么办

这个加锁也不一定能解决,我们是在平台订单超时的时候会调用支付宝的取消订单接口尝试去取消订单。如果确实出现用户支付成功,但是平台订单自动超时了,那么我们会在异步回调中进行判断,然后调用支付平台的退款接口。

如果异步通知失败怎么办(掉单)

我们后天提供了一个对账功能,是根据订单流水号去支付平台查询订单的支付结果状态,根据这个支付结果重新走一遍异步通知需要处理的业务流程。

接口幂等一般怎么设计?

幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同 ,对于查询和删除操作天然就是幂等的。

对于插入操作我们可以找到数据的唯一标识进行查重,比如:保存一个用户我们可以判断用户的手机号是否已经存在了,如果已经存在就不要再重复保存。

对于修改才做也是同样的道理,比如:支付成功需要把某个订单状态修改为已支付,那么我们在修改之前应该根据唯一订单号查询这个订单的状态是否已经被修改,如果已经被修改就不要重复修改了。为了防止并发操作,可以把判断逻辑放到synchronized代码块中。

认证授权

讲一下你们的登录实现方案

我之前做过一个单体应用,使用过的是Redis来做登录,当用户第一次发起登录请求,后台生成一个token保存到Redis中

将生成的token返回给用户端

用户端使用用浏览器中的localStorage保存token

通过axios的拦截器,给每次请求的请求头都加上token

服务端收到token,就能在Redis中找到对应的数据

三方登录流程讲一下

1.用户发起微信登录请求

2.后端获取请求二维码的连接,重定向到扫码界面

3.用户使用微信扫一扫并同意授权

4.后端回调获取授权码,并将授权码作为参数,重定向到前端跳转页面

5.前端将授权码返回后端,后端根据授权码获取token

6.后端根据token获取openId

7.根据openId查询微信用户表

  • 如果查到有用户信息,且已关联本地账户,就默认登录
  • 如果有查到用户信息,但没有关联本地账户,就跳转本地账户绑定页面,
  • 如果没有查到用户信息,就向微信平台发起请求查询用户基本信息,添加到微信用户信息表,再跳转本地账户绑定页面

8.执行绑定逻辑时,根据手机号判断是否有本地账户,如果有就直接绑定,如果没有就自动注册再绑定,绑定成功后就默认登录

为什么要使用SpringSecurity

它主要是对登录和授权的流程做了封装,简化我们的代码量,也提供了很多功能,比如:认证授权结果处理,记住我等。总之用来是很简单的。

Security中怎么授权

包括了web授权和方法授权,我们一般使用方法授权,注解的方式比较灵活。需要在配置类上打注解@EnableGlobalMethodSecurity(prePostEnabled = true)开启全局方法授权支持,然后再需要授权的方法是打授权注解@PreAuthorize

说一下security中的的filter

SecurityContextPersistenceFilter:请求开始会从SecurityContextRepository中获取SecurityContext对象并设置给SecurityContextHolder,在请求完处理成后将SecurityContextHolder持有的SecurityContext再保存到配置好的SecurityContextRepository中,同时清除SecurityContextHolder中的SecurityContext

UsernamePasswordAuthenticationFilter:默认拦截“/login”登录请求,将请求中的认证信息包括用户名,密码封装成UsernamePasswordAuthenticationToken,然后调用AuthenticationManager的认证方法进行认证

BasicAuthenticationFilter:处理 HTTP 请求的 BASIC 授权标头,如果身份验证成功,就把生成的Authentication对象放入SecurityContextHolder。如果设置了记住我,下次访问就不会走这里来了

RememberAuthenticationFilter:记住我,调用RememberMeServices的autoLogin方法自动登录

AnonymousAuthenticationFilter:匿名filter,检测SecurityContextHolder有没有Authentication对象,如果没有,就会创建一个AnonymousAuthenticationToken并保存到SecurityContextHolder

ExceptionTranslationFilter:处理filter链中的所有AccessDeniedException和AuthenticationException

FilterSecurityInterceptor:继承自AbstractSecurityInterceptor,通过调用AccessDecisionManager.decide方法进行授权

说一下security的认证原理

首先,请求会经过UsernamePasswordAuthenticationFilter拦截,请求的用户名密码会封装成UsernamePasswordAuthenticationToken,过滤器将token提交给认证管理器AuthenticationManager进行认证

然后,认证管理器调用AuthenticationProvider进行认证,AuthenticationProvider再调用UserDetailsService获取到数据库中存储的用户信息UserDetails,然后调用密码编码器对密码进行比较,认证成功后封装Authentication

再后来,请求回到UsernamePasswordAuthenticationFilter,调用SecurityContextHolder将Authentication对象封装成SecurityContext并保存到SecurityContextHolder中

最后,请求回到SecurityContextPersistenceFilter,它会调用SecurityContextRepository将SecurityContext对象存储起来,再清理掉SecurityContextHolder中的信息

非对称加密,什么是数字签名 9-14

非对称加密是一种算法,指的是加密和解密时使用不同的密钥,其中私钥不可公开,公钥可以公开。

数字签名就是在非对称加密的基础上,使用私钥加密,公钥解密,主要用来防止数据被篡改,实现安全传输的目的

Oauth2的四种授权模式 9-14

oauth协议是一个安全的开放授权标准,与传统的授权方式相比,它不会使第三方触及到用户的账号信息,Oauth2有四种授权模式

一、授权码模式:它是功能最完整,流程最严密的授权模式

二、简化模式:它简化了授权码模式,跳过了授权码这个步骤

三、密码模式:通过用户名密码的方式来换取Token , 前三种都可以用作三方登录

四、客户端模式: 以客户端的名义直接向服务器申请Token,这种需要对客户端绝对信任才可以,比如系统内容获取Token

讲一下你们的微服务授权方案 你还知道有哪些方案吗? 9-14

我们使用的是Spring Cloud Oauth2 ,它整合了SpringSecurity+Oauth2+JWT,主要分为两块配置:认证服务器-负责颁发token,资源服务-器负责校验Token和资源授权

我们项目是资源服务器去校验Token,也可以将Token校验工作交给网关统一校验,资源服务器只负责授权工作。

另外常见的授权方案还有,CAS单点登录,用户只用在某个服务上登录,访问其他服务时就不需要登录了,这就要求每个面向用户的服务都必须于认证服务交互,会产生大量重复的工作

分布式会话,它是将用户认证信息存储在共享容器比如redis中,通常会以会话作为key,当用户访问微服务时,就从redis中获取认证信息。这对安全存储有较高的要求,复杂度高

讲一下你们微服务认证授权的整体流程 9-14

关键词:认证中心-》password -》httpclient ->security的认证流程-》oauth2 -》回令牌 -》带令牌 -》校验令牌 -》方法授权

  • 浏览器带着用户名和密码访问认证中心的登录方法
  • 登录方法会拼接一个基于password模式的获取Token的url
  • 使用httpclient把请求发出去,请求会到达认证服务器
  • 认证服务器会走security的认证流程完成认证,以及加载用户的权限
  • 然后认证服务器会走oauth2生成令牌,令牌会基于JWT令牌转换器加密
  • 最后把令牌返回给浏览器,浏览器把Token存储到LocalStorage中,当然设置到cookie中也可以
  • 接下来就是浏览器会向资源服务器发送请求,请求头会通过axios前置拦截添加上Token
  • 资源服务器会对Token进行校验,也是用令牌转换器解析Token
  • 然后拿到Token中的授权信息,进行方法授权,如果有权限就可以正常执行方法
你们为啥要用JWT 9-15

一个字,安全,我们做了认证授权后,每次客户端访问资源服务器,都需要远程调用认证服务器进行token的校验和授权,才能访问到资源。这是很消耗性能的,因此我们考虑将签名信息直接保存到客户端,那就不需要每次都向认证服务器认证授权了。

但是这有有一个新的问题,这些敏感数据赤裸裸的存到客户端不安全!而JWT就能解决这个问题。它支持非对称加密算法对信息加密,保证了信息安全

另外,JWT以json对象的形式传递信息,解析更方便

可以在令牌中定义内容,方便扩展

Oauth2认证,如果Token过期了你们是怎么处理的 9-15

首先,我们会在前端设置axios后置拦截,检查是否是token过期,判断一下如果返回401,就代表token过期了

然后从localStorage中获取刷新refresh_token,并发送请求获取新的token

后台接收到前台的刷新token请求,拼接完整的刷新token的url,发送http请求获取到新的token并返回客户端

客户端收到新的token就把旧的token覆盖掉,最后把之前的请求再重新发送一次

Oauth2认证,如果Token被盗了怎么办?9-15

关键词:过期时间,ip,
首先,我们需要对token设置过期时间,这个时间可以根据需要设置短一点

然后,可以在token中加入客户身份标识,比如客户的ip地址,如果短时间内ip地址频繁变动,就标记为异常状态,并给用户发送信息,提示账户有风险

Token都能被盗,那这个就是用户自己的问题了。

四.技能提升

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值