整型四个:
··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)
int是基本数据类型,变量中直接存放数值,变量初始化时值是0
Integer是引用数据类型,变量中存放的是该对象的引用地址,变量初始化时值时null
Integer是int类型的包装类,将int封装成Integer,符合java面向对象的特性,可以使用各种方法比如和其他数据类型间的转换
Integer和int的深入对比:
两个通过new生成的Integer对象,由于在堆中地址不同,所以永远不相等
int和Integer比较时,只要数值相等, 结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int
通过new生成的Integer对象和非通过new生成的Integer对象相比较时,由于前者存放在堆中,后者存放在Java常量池中,所以永远不相等
两个非通过new生成的Integer对象比较时,如果两个变量的数值相等且在-128到127之间,结果就相等。这是因为给Integer对象赋一个int值,java在编译时,int值在-128到127之间会自动调用静态方法ValueOf()(Integer i = Integer.valueOf(100)),根据java api中对Integer类型的valueOf的定义,对于-128到127之间的整数,会进行缓存,如果下次再赋相同的值会直接从缓存中取,就不会new了。即享元模式
三者底层都是char[]
存储数据,JDK1.9之后使用的是byte[] ,因为往往我们存储都是短字符串,使用byte[]这样更节约空间。
由于String底层的char[]有final
修饰,因此每次对String的操作都会在内存中开辟空间,生成新的对象,所以String不可变
StringBuilder和StringBuffer是可变字符串,没有final修饰,适合字符串拼接,另外StringBuffer是线程安全的,方法有synchronized
修饰,但是性能较低,StringBuilder是线程不安全的,方法没有synchronized修饰,性能较高
关键字:常量池,栈,堆
区别:
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”。
关键字:编译优化
创建了一个对象,因为相对于字符串常量相加的表达式,编译器会在编译期间进行优化,直接将其编译成常量相加的结果。
String s; 创建几个对象?
没有创建对象。
String a = “abc”; String b = “abc”; 创建了几个对象?
关键字:相同对象
创建了一个对象,只是在第一条语句中创建了一个对象,a和b都指向相同的对象"abc",引用不是对象
1、“=”比较对象比较的是地址。
2、对于Object对象中的“equals”方法使用的也是 “==” ,比较的是对象的地址,默认情况下使用对象的equals比较Object中的equals方法,也就是比较地址,如果要实现自己的比较方式需要覆写(重写)equals 方法。
3、对于包装类比如:Integer都是复写过equals方法,比较的是int 值。
1、当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰
2、finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下
3、finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc(GC是垃圾收集的意思(GabageCollection))启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
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)。
关键字:共同特征总结出来构造类
抽象 : 是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的属性和行为,并不关注这此行为的细节是什么 - 举例:定义一个person类,就是对“人”这种事物的抽象
关键字:
封装:对数据的访问只能通过已定义的接口,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口,比如在Java中,把不需要暴露的内容和实现细节隐藏起来,或者private修饰,然后提供专门的访问方法,如JavaBean。 - 生活举例:电脑主机就是把主板等封装到机壳,提供USB接口,网卡接口,电源接口等。 JavaBean就是一种封装。
继承:新类(子类,派生类)继承了原始类的特性,子类可以从它的父类哪里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
多态:多态是指允许不同类的对象对同一消息做出响应。对象的多种形态,当编译时类型和运行时类型不一样,就是多态,意义在于屏蔽子类差异
方法的覆盖是子类和父类之间的关系,方法的重载是同一个类中方法之间的关系。
覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。
覆盖要求参数列表相同;重载要求参数列表不同。
抽象类不能被实例化, 需要通过子类实例化
抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。
抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体
含有抽象方法的类必须申明为抽象类
抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
1、定义:定义接口使用interface,定义抽象类使用abstract class
2、元素:接口由全局常量,抽象方法,(java8后:静态方法,默认方法)。抽象类由构造方法,抽象方法,普通方法
3、关系:接口和类是实现关系,抽象类和类是继承关系
BIO (Blocking I/O):同步阻塞I/O 模式,以流的方式处理数据,数据的读取写入必须阻塞在一个线程内等待其完成。适用于连接数目比较小且固定的架构
NIO (New I/O):同时支持阻塞与非阻塞模式,以块的方式处理数据,适用于连接数目多且连接比较短(轻操作)的架构,比如聊天器
AIO ( Asynchronous I/O):异步非阻塞I/O 模型,适用于连接数目多且连接比较长(重操作)的架构
InputStream : 输入字节流, 也就是说它既属于输入流, 也属于字节流 ,
OutputStream: 输出字节流, 既属于输出流, 也属于字节流
Reader: 输入字符流, 既属于输入流, 又属于字符流
Writer: 输出字符流, 既属于输出流, 又属于字符流
文本用字符输入流,读图片用字节输入流
字节字符
字符流适用于读文本,字节流适用于读图片,视频,文件等。
字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
字节流默认不使用缓冲区;字符流使用缓冲区。
字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元
主要运用了俩个设计模式,适配器和装饰者模式
BufferedInputStream 带缓冲区的字节输入
BufferedOutputStream 带缓冲区的输出流
BufferedReader : 带缓冲区的字符输入流
BufferedWriter : 带缓冲区的字符输出流
需要一个FileInputStream指向读取的文件,然后把它包装到BufferInputStream,使用BufferInputStream#read方法去读byte[],然后创建一个FileOutputStream指向输出文件,然后把它包装到BufferOutputStream,使用BufferOutputStream#write方法写byte[]到另外一个文件
和文件拷贝思路一样,只不过读的时候需要使用BufferedReader和FileReader,使用readline来读 , 写的时候需要BufferedWriter和 FileWriter,用wite来写
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap
Collection接口
List:
ArrayList:底层数据结构是数组,查询性能高,增删性能低
Vector:底层数据结构是数组,查询性能高,增删性能低
LinkedList:底层数据结构是双向链表,查询性能低,增删性能高
Set:
HashSet:无序不重复的,使用HashMap的key存储元素,判断重复依据是hashCode()和equals()
TreeSet:有序不重复的,底层使用TreeMap的key存储元素,排序方式分为自然排序,比较器排序
Map接口
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是基于双链表实现的,开配的内存空间不要求连续,因此不支持索引,查找元素需要从头查找,因此性能差,但是添加删除只需要改变指针指向即可,性能高. LinkedList会增加内存碎片化,增加内存管理难度
根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用LinkedList
ArrayList是线程不安全的,Vector相反是线程安全的,方法加了同步锁,线程安全但是性能差,ArrayList底层数组容量不足时,会自动扩容0.5倍,Vector会自动扩容1倍
第一种方式,让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关键字修饰,保证了可见性
统计平局值:avg , 分组:group by
union 并集 , union all(允许重复并集)
SELECT 列名 FROM 表1 JOIN 表2 ON 条件 WHERE 条件 GROUP BY 列名 HAVING 条件 ORDER BY 列名 LIMIT
FROM --> ON --> JOIN --> WHERE --> GROUP BY --> HAVING --> ORDER BY --> LIMIT
员工表employee字段有: id, username, amount ,deptname .
select 部门名,count(id) from employee group by deptname
select 部门名,sum(amount) from employee group by deptname
200 成功返回状态
301 永久重定向,被请求的资源永久移动到新位置
302 临时重定向,被请求的资源临时移动到新的位置,项目中使用了oauth2,对目标资源访问无权限时就会见到,它是会重定向到授权地址
401 无权限访问
403 禁止访问,服务器已经接收到请求,但拒绝执行
404 找不到该资源
500 服务器内部错误 zuul找不到服务名就会见到
503 服务器内部错误 服务器维护或者过载
504 网关超时
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
过滤器:在请求发送之后,处理之前对请求的一次拦截,可以更改请求状态或者参数值等。
创建过滤器:实现filter接口,重写doFilter方法,最后在web.xml中配置过滤器
服务端的session id会自动写入客户端的cookie中,每次请求客户端回自动把cookie带入后台,后台自动根据cookie中的sessionid就能找到session
session和cookie都是为了弥补http协议的无状态特性,解决会话问题
session是以ConcurrentHashMap结构存储在服务器端,同时生成一个sessionid返回客户端并存放到cookie中
cookie是将数据存储在客户浏览器端
session占用服务器的性能,但安全性较高,使用cookie减轻服务器的压力,但有被用户篡改风险因此安全性较低
关键字:?占位符,预编译,无注入风险
statement的sql语句使用字符串拼接,很容易出错,而preparedStatement使用?作为占位符,不容易出错易于维护
statement不对sql语句作处理,直接交给数据库,而preparedStatement支持预编译,事先将编译好的sql语句放到数据库端,相当于缓存,因此效率更高
statement有sql注入风险,preparedStatement没有sql注入风险
https://blog.csdn.net/ming2316780/article/details/48544867
转发是一次请求,可以共享同一组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)重定向可以跨域访问
4个方面:存放参数的地方,发送的数据类型,发送的内容大小,用途
get把参数包含在url中,post是把参数放到request body中,所以post相对于get更安全,所以post发送的数据更大,get有url的长度限制,所以在restful中,get一般用于用户查询搜索数据,post一般用于用户添加或者修改数据
post能发送更多的数据类型,get只能发送ASCII字符(ASCLL-American Standard Code for Information Interchange)
动态网页技术标准(jsp全称JavaServer Pages),jsp的本质就是servlet,每个JSP文件都会被编译成一个Serverlet去执行,该Serverlet会对JSP中的动态内容进行替换,静态部分是标准的html,动态部分是java程序
轻量级,IOC,AOP
Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理
CoreContain核心容器模块:
Web模块
Web:提供面向web的基本功能和面向web的应用上下文
//Web-MVC:为web应用提供模型视图控制(MVC)
3.//Web-Socket:在 web 应用程序中提供客户端和服务器端之间通信的方式
数据/集成模块
其他模块
IOC(Inversion of Control)控制反转,把对象的创建,属性设置,初始化,销毁等工作交给Spirng的IOC容器去管理,解放程序员的劳动力。
对象被注册到Spring的IOC容器中,使用的时候从容器中获取即可,非常方便。
它通过依赖注入,将需要的外部资源注入到组件中,使用IOC使得对象之间的耦合度降低,降低维护难度,从而使得代码更加优雅
AOP,Aspect Oriented Programming 英文首字母缩写,意为面向切面编程,是Spring的核心思想之一
AOP是对OOP(面向对象编程)的一种补充,能够做到很多面向对象无法做到的事情,比如需要在所有方法执行前开启事务,打印日志,如果使用面向对象来编程,将会产生大量重复代码,而使用AOP,可以将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,一次解决这些问题。而这些重复的代码,一般统称为横切逻辑代码
使用AOP,在不改变原有业务逻辑的情况下,实现解耦合,避免横切逻辑代码重复
AOP的使用场景包括日志记录,性能统计,安全控制,事务处理,异常处理等等
它是基于动态代理实现的,分为JDK动态代理和CGLIB动态代理。JDK动态代理只支持实现了接口的类 ,CGLIB支持没有实现接口的类。Spring默认使用JDK动态代理,如果被代理类没有实现接口,会选择CGLIB动态代理
另外Spring的AOP还用到了执行链模式。
懒加载:需要使用对象的时候才创建,节省资源,但不利于提前发现错误
非懒加载,也叫迫切加载,容器启动时就创建对象,消耗资源,但有利于提前发现错误
spring中默认时迫切加载,即在项目启动时,spring会扫描符合条件的所有bean并将其初始化
如果需要懒加载,可以使用@Lazy注释或者xml中配置属性default-lazy-init=“true”
方式一:setter方式注入,通过反射调用无参构造方法生成对象,再通过对于的setter方法注入配置的值,支持注解和xml两种实现方式
方式二:构造器方式注入,通过反射调用有参构造方法生成对象,支持注解和xml两种实现方式
注解实现方式:@Autowired,它是默认按类型匹配的、@Resource,它是默认按名字匹配的
@Aspect:定义切面
@Pointcut:定义切点 = cn.xx.service.*
@Before:前置通知,在目标方法运行之前运行
@After:后置通知,在目标方法运行结束之后运行(无论方法正常结束还是异常结束)
@AfterReturning:返回通知,在目标方法正常返回之后运行
@AfterThrowing:异常通知,在目标方法出现异常以后运行
@Around:动态代理,手动推进目标方法运行
方式一:普通注册方式,直接通过class注册
方式二:简单静态工厂方式注册
方式三:简单实例工厂方式注册
方式四:FactoryBean方式注册
@Controller/@RestController 一般用于定义控制层的类
@Service 一般用于定义服务层的类
@Repository 一般用于定义持久层类
@Component 定义一般类
@Configuration 定义配置类
关键字:数量上:对象一个多个。调用时:只实例化一个bean,实例化新的bean。
单例和多例属于对象模式,单例模式指对象在整个系统中只存在一份,多例模式则可以有多个实例。
如果一个bean是单例模式的,在处理多次请求的时候,在ioc容器中只实例化一个bean,这个对象会被保存在一个ConcurrentHashMap中,当有请求来的时候,会先从map中查看,如果有就直接使用这个对象,没有才会实例化新的对象。
如果是多例(prototype)模式的bean,每次请求来的时候,会直接实例化新的bean,没有map缓存的过程。
在spring的ioc容器中的bean默认都是单例的,如果需要使用多例,可以通过修改scope属性:scope=“prototype”
这两者分别指的是多例和单例模式,singleton即单例模式,指对象在整个系统中只存在一份;prototype即多例模式系统中可以有多个实例。
如果一个bean是单例模式的,在处理多次请求的时候,在ioc容器中只实例化一个bean,这个对象会被保存在一个ConcurrentHashMap中,当有请求来的时候,会先从map中查看,如果有就直接使用这个对象,没有才会实例化新的对象。
如果是多例模式的bean,每次请求来的时候,会直接实例化新的bean,没有map缓存的过程。
在spring的ioc容器中的bean默认都是单例的,如果需要使用多例,可以指定scope属性:scope=“prototype”
BeanFactory接口是IOC容器的核心接口,定义了管理bean的最基本方法,比如实例化,配置,管理,获取bean的方法
ApplicationContext接口是BeanFactory接口的子接口,除了继承BeanFactory中所有管理bean的方法,还拥有环境、国际化、资源、事件等服务相关的接口
BeanFactory是延迟加载,ApplicationContext是迫切加载
事务传播行为指的是一个方法调用另外一个方法事务是怎么进行传递的在Spring中规定了7种类型的事务传播行为
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。- 默认 |
---|---|
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
重定向是指将用户从当前请求重新定向到一个视图页面,或者是一个handler处理请求,以前的request域中信息全部失效,同时地址栏会发生变化,它是客户端行为
转发是指将用户从当前请求转发给另一个视图页面或者handler处理请求,以前的request域可以共享,地址栏不会发生变化,它是服务器行为
springmvc默认是使用转发方式跳转的,且会默认经过视图解析器,我们也可以通过指定,转发时在返回值前面加"forward:“,重定向时在返回值前面加"redirect:”,且此时就不会再经过视图解析器了
第一种需求,后台接收前台页面返回的string类型时间,要转换成的Date类型数据,可以使用@DateTimeFormat注解来接收参数
第二种需求,后台将Date类型数据返回给前台页面,默认是返回时间戳,如果想要优雅的格式,可以在模型的Date字段或get方法上使用@JsonFormat注解
@Controller:用来标识一个类是控制器类
@RequestMapping:用来映射请求路径和参数
@ResponseBody:将返回值放到responsebody中,通常返回json或者xml格式数据
@RequestBody:将前台请求参数转换成对象
@PathVariable:接收路径参数,通常用在restful接口中
@RestController:@Controller和@ResponseBody的组合注解
@ControllerAdvice:运用aop的思想,对全局做一些处理,比如结合@ExceptionHandler做全局异常捕获
SpringMVC 的拦截器主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、判断登录等功能上
第1步,定义拦截器:可以实现 HandlerInterceptor 接口来自定义拦截器,接口定义了三个方法,preHandler方法是在请求到达处理器之前执行,postHandler方法是在请求经过处理器之后、解析试图之前执行,afterCompletion方法是在视图渲染之后、返回客户端之前执行
第2步,配置拦截器:在springmvc的配置文件xml中,配置所有拦截路径,以及需要放行的路径
HandlerInterceptor是接口,我们可以实现该接口来定义拦截器,HandlerInterceptorAdapter是抽象类,它实现了HandlerInterceptor接口的子接口AsyncHandlerInterceptor,我们可以继承该类来定义拦截器,它简化拦截器的实现,默认preHandler返回true
1.Http请求:客户端请求提交到DispatcherServlet-前端控制器
2.寻找处理器:由DispatcherServlet调用HandlerMapping-处理器映射器,根据url找到对应的的Handler
3.调用处理器:DispatcherServlet指定HandlerAdapter-处理器适配器去调用Handler
4.调用业务处理和返回结果:Handler调用业务逻辑处理完成后,返回ModelAndView
5.处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler-视图解析器,找到ModelAndView指定的视图
6.Http响应:将结果显示到客户端
在spring中,bean默认都是单例的,controller也是交给spring容器管理的一个bean,因此它也是单例的。
单例的好处是减少了创建对象和垃圾回收的时间,节省了内存资源,但同时单例会造成线程不安全的问题,因为当所有请求访问同一个controller实例,controller中的成员变量是所有线程公用的,某个线程如果修改了这个变量,别的请求再来拿这个变量就编程修改后的值了
要解决这个问题,最直接有效的方式就是不要在controller中定义成员变量,如果你非要定义成员变量,两种方式
第一种,可以给controller上加注解@Scope(“prototype”),将controller设置为多例模式,每次请求都重新实例化一个controller
第二种,使用ThreadLocal变量,让每一个线程都有自己独立的变量
@Getmapping是一个组合注解,即是@RequestMapping(method = RequestMethod.GET)的缩写,意思是只接收get请求的方法
@Requestmapping如果没有指定请求方式,可以接收get,put等各种类型的请求
Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效
它大量简化maven依赖,管理了大量的基础依赖
基于注解配置(JavaConfig),无需xml配置
内嵌Tomcat,部署流程简单
打包和部署更加灵活,允许独立运行
可以使用@ControllerAdvice注解,编写一个全局异常处理类,再自定义一个方法使用@ExceptionHandler来捕获具体的异常并作相应的处理
通常情况下后台向前台返回结果时,会把结果封装成包含有错误码,错误信息以及数据本身的json数据,因此我们可以使用自定义异常类,自定义枚举错误码,在捕获全局异常后,向前台返回一个包含错误码的信息
包含3个注解:@ComponentScan,@EnableAutoConfiguration,@SpringBootConfiguration
@SpringBootApplication是SprnigBoot项目的核心注解,目的是开启自动配置,并表示该类为主启动类。它包含三个子标签
3方面:管理jar包,管理依赖,维护依赖版本号
这是SpringBoot的父工程,它的作用是帮我们管理了很多的基础jar包,同时它继承了spring-boot-dependencies,在spring-boot-dependencies项目中通过管理了大量的依赖,同时通过维护了这些依赖的版本号
但是在项目中,还需要通过 去导入具体的依赖才能使用
此项目是Springboot和Springmvc整个的jar包,构建了web项目的基本环境,集成了日志,tomcat,springmvc,json支持等等
一个starter包中包含了SpringBoot的自动装配机制,可以自动初始化好一些必要的Bean。我认为一个Starter代表一套环境,比如:spring-boot-starter-web就把WEB环境所需要的包都导入了如Tomcat,log4j,springmvc,springweb,auto-config自动配置包等的呢个。然后它把SpringMVC需要的Bean都进行自动装备,比如前段控制器,编码过滤器等。
方式一:使用@Value读取配置文件
方式二:使用@ConfigurationProperties读取配置文件
日志级别从低到高分别为:
TRACE < DEBUG 如果设置为 WARN,则低于 WARN 的信息都不会输出 Spring中默认使用INFO级别输出到控制台 事务(transaction)是指业务逻辑上对数据库进行的一系列持久化操作,要么全部成功,要么全部失败。 在Springboot中,可以通过xml配置和注解配置 xml方式通过配置DataSourceTransactionManager和transactionManager实现 注解方式配置通过在主启动类上加上@EnableTransactionManagement开启事务管理器,在具体的实现层service类上加上@Transactional 实现事务 #{}能够防止SQL注入,因为底层使用PreparedStatement对象,预编译,性能较高 ${}不能防止SQL注入,因为底层使用Statement对象,不会预编译而是拼接字符串,性能较低 能使用#{}时尽量使用#{},如果需要动态传入表名或者字段名需要用 比 如 , 像 O R D E R B Y 时 只 能 使 用 {}比如,像 ORDER BY 时只能使用 比如,像ORDERBY时只能使用{} 关键字:提前加载,关联的数据 延迟加载,是先从单表查询,需要使用关联数据的时候才发起关联查询,不用的时候不查询关联的数据,又叫懒加载. 饥饿加载,是在查询时将关联的数据立即查询出来加载进内存,不管用不用 关键字:association,javaType;collection,ofType 单个关联对象用association,适用于多对一的关联查询,使用javaType来定义实体类型,集合用collection,适用于一对多的关联查询,使用ofType来定义集合的泛型类型 关键字:SqlSession;namespaces 缓存,是指将从数据库查询出的数据存放在缓存中,下次使用相同查询时不必再从数据库查询,而是直接从缓存中读取,从而减轻数据库查询的压力,提高性能 mybaits中的一级缓存,是SqlSession级别,默认开启,使用同一个SqlSession发送相同的SQL时命中;它的生命周期和SqlSession一致,当调用SqlSession.close()方法时会释放缓存 mybatis中的二级缓存,是namespace级别,默认不开启,执行同一个namespace的相同statement,发送相同的SQL时命中;它的生命周期是程序结束 当SQL中执行了update()、delete()、insert()操作,则缓存中的数据都会清空 关键字:动态代理 动态代理,赋值给mapper接口引用的对象其实是一个代理对象,这个代理对象是由 JDK 动态代理创建的。在解析mapper的时候,mybatis会通过java反射,获取到接口所有的方法,当调用接口中方法时,将通过接口全限定名+方法名对应找到映射文件中namespace和id匹配的sql,然后将执行结果返回 使用Mybatis的拦截器可以做到 if标签:条件判断 choose、when、otherwise标签:选择结构,类似java中的switch trim标签:对包含的内容加上前缀,后缀 where标签:主要是用来简化SQL语句中where条件判断的,能智能的处理and or,不必担心多余导致语法错误 foreach标签:遍历元素 关键词:map引用取值,#{param1}引用取值,@Param接口 方式一,可以使用map进行传参,SQL中使用map的key来引用取值 方式二,可以在SQL中使用#{param1},#{param2}…来引用取值,它是根据mapper接口对应方法中形参的顺序进行匹配的,不管接口方法的参数名字叫个啥,SQL都只能使用param1,param2,等来取值 方式三,可以使用@Param注解,给mapper接口方法的参数命名,在SQL中直接使用取的名字来引用 关键词:嵌套子查询:子sql查询关联对象,n+1次。JOIN连表:主对象和关联对象,1次 嵌套子查询,指的是在查询一个主对象的时候,使用单表查询,在resultmap中额外发送一个子sql查询关联对象,然后映射给主对象 连表join查询,指的是查询一个主对象的时候,使用join连表的方式把主对象和关联对象的数据一次性查出来,用resultmap映射结果 他们的区别,join连表查询只发一条sql就能把数据查询出来,嵌套子查询会有一个n+1的问题,就是说如果主查询出来n条数据,那么会额外发送n条子sql去查询对应的关联对象,加上主查询那1次,也就是n+1次,因此它的性能相对较低的,一般我们会使用join连表查询 关键字:动态管理连接申请,节省内存,提高效率 对数据库的操作都需要取得连接,使用完都需要关闭连接,如果每次操作需要打开关闭连接,这样系统性能很低下。连接池就可以动态的管理这些连接的申请,使用和释放,我们操作数据库只需要在连接池里获取连接,使用完放回连接池,这样大大节省了内存,提高效率。 数据库连接池的原理主要分为三部分 Redis是一种高性能的,开源的,C语言编写的非关系型数据库,可以对关系型数据库起到补充作用,同时支持持久化,可以将数据同步保存到磁盘 说Redis很快是相对于关系型数据库如mysql来说的,主要有以下因素 Redis存储形式是键值对,支持value形式包括String,List,Set,ZSet,Hash。 String可以用作缓存,计数器,防攻击,验证码、登录过期等,List可以用来做队列,秒杀等,Set可以用来去重 1.String 2.List 3.Set 4.ZSet 5.Hash 使用的是Springboot整合的redis,主要用来解决前后端分离后前后端会话问题,以及验证码的问题 通过对Redis持久化,把内存中的数据和命令,保存一份到磁盘中做备份,当Redis发生宕机,重启服务器的时候,会从磁盘重新加载备份的数据,从而解决数据丢失问题 将内存中的数据备份到磁盘的过程,就叫作持久化 Redis持久化主要有两种方式,RDB和AOF,可以通过修改redis.conf进行配置 RDB是记录数据快照,而AOF是记录写命令的 AOF和RDB各有所长 根据实际需要来选择,通常二者可以结合来使用 方式一:增加物理内存 方式二:使用淘汰策略,删掉一些老旧数据 方式三:集群 主要用做缓存,比如:验证码,分类缓存,数据字典缓存,权限数据缓存,登录信息缓存等。 String类型的存储结构用的比较多,并且使用了Json格式进行序列化。 Mysql的事务是基于日志,记录修改数据前后的状态来实现的,而Redis的事务是基于队列实现的 Mysql中的事务满足原子性:即一组操作要么同时成功,要么同时失败, Redis中的事务不满足原子性,即一组操作中某些命令执行失败了,其他操作不会回滚 因此对于比较重要的数据,应该存放在mysql中 关键字:发布订阅, Redis是使用发布订阅来实现广播的 订阅者通过 SUBSCRIBE channel命令订阅某个频道 , 发布者通过 PUBLISH channel message向该频道发布消息,该频道的所有订阅者都可以收到消息 关键字:快,在内存中,读写大于磁盘, 一个字,快。 我们一般会将经常查询的,不会经常改变的热点数据,保存到缓存中,提高响应速度 关键字:请-》缓存?-》客户端 -》无缓存-》数据库缓存-》客户端 1.客户端发起查询请求 2.判断缓存中是否有数据 3.返回数据给客户端 关键字:写,删,缓存 我们在代码中控制,如果数据库做的是写操作,直接把redis中的对应数据删除,下次查询数据会重新写入缓存。 我们的业务对一致性要求不是很高,因此采用了先操作mysql,后删除redis。在写数据库和删除缓存行代码之间如果有查询请求依然会查询到Redis中的老数据,但是这种情况非常极端,而且我们的业务也能容忍这种短暂的脏数据。 我还知道其他方案,比如延迟双删 , 使用阿里的canal组件监听Mysql事务日志自动同步Redis等。 @EnableCaching:打在主启动类上,开启缓存功能 @Cacheable:打在方法上,表示该方法会开启缓存,打在类上,表示类中所有的方法都开启缓存,方法的返回值会自动写入缓存。如果缓存中已经有数据,方法将不会被调用,而是拿着缓存数据直接返回给客户端。 @CacheEvict:搭载类或者方法上,会将缓存清除 @CachePut:更新缓存 @Caching:组合操作,要应用于方法的多个缓存操作 @CacheConfig:打在类上,共享的一些常见缓存设置 缓存击穿:缓存中没有,数据库中有的数据,由于某种原因比如缓存过期了,同时并发用户特别多,一时间都往数据库中读取数据 缓存穿透:客户端频繁请求一个缓存和数据库中都没有数据,导致数据库压力大。 缓存雪崩:缓存重启,或者大量key失效,导致大量并发打到数据库 list控制先进后出: lpush rpop ; 队列:list控制先进先出: lpush lpop 单个对象可以使用String,也可以使用hash 集合对象可以使用hash,以便可以快速的通过field来取值,获取列表可以通过hash的 vals 来获取,很方便 redis是keyvalue结构,value支持常用的存储方式有:string; list; set;zset ;hash 。 Zrevrangebyscore Srandmember 可以用来消峰,异步,解耦, 日志收集等 为了增强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。 RocketMQ将数据存在硬盘,RocketMQ的性能在所有的MQ中是比较高的,主要是因为RocketMQ使用了mmap零拷贝技术(内存映射技术),consumequeue中的数据是顺序存放的,还引入了PageCache的预读取机制,使得对 consumequeue文件的读取几乎接近于内存读取,即使在有消息堆积情况下也不会影响性能。 零拷贝:主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务 预读取算法:预测即将访问的页面,提前将页面批量读入内存缓存。 RocketMQ发送消息支持:同步,异步,单向消息 使用场景建议如下 Queue是如何分配给Consumer的,这对应了四种算法:平均分配策略,环形平均策略,一致性Hash策略,同机房策略。 平均分配性能比较高,一致性Hash性能不高,但是能减少Rebalance,如果Consumer数量变动频繁可以使用一致性Hash。 消息到消费分为:拉取式 pull ,和推送是 push 按照发送的顺序进行消费就是顺序消息,遵循(FIFO), 默认生产者以Round Robin轮询方式把消息发送到不同的Queue分区队列;消费者从多个队列中消费消息,这种情况没法保证顺序。 RocketMQ分为全局有序和部分有序:全局有序是一个topic下的所有消息都要保证顺序,如果要保证消息全局顺序消费,就需要保证使用一个队列存放消息,一个消费者从这一个队列消费消息就能保证顺序,即:单线程执行 部分顺序消息只要保证某一组消息被顺序消费,即:只需要保证一个队列中的消息有序消费即可。比如:保证同一个订单ID的生成、付款、发货消息按照顺序消费即可实现 可以设置消息的过期时间等级,可以用作VIP过期,支付超时,自动确认收货等。 是最终一致性的事务场景,比如:注册保存用户同时赠送积分,由于赠送积分这个动作可以接受延迟同步,采用最终一致性。 滴滴打车的费用结算,跨行转账,等场景都可以考虑最终一致性。 事务消息解决的就是,事务发送方执行本地事务和事务消息的原子性。 rabbitMQ消息队列可以用来 首先,RabbitMQ的消息确认机制,默认是自动签收,也就是说消息一旦被消费者接收,就自动签收,消息就从队列里清除了。因此对于重要的消息,不容丢失的数据,我们需要设置在消费完成后手动签收 其次,我们可以将消息持久化,避免消息在消费前MQ宕机,网络问题等造成的消息丢失 Fanout:广播,将消息交给所有绑定到交换机的队列 Direct:定向,把消息交给符合指定routing key的队列 Topic:通配符,把消息交给符合routing pattern的队列 分为消息发送和消息接收两个步骤 重复消费,一般时由于消费者消费成功后,在给MQ确认签收的时候出现了网络波动,MQ没有接到确认,就会继续给消费者投递之前的消息,造成消费者接收到了两条一样的消息。 我们可以通过实现消息的幂等性来避免这种情况,比如说让生产者给每个消息携带一个唯一的id,消费者获取消息后根据这个id去查询数据库,如果不存在就正常消费,如果存在了就证明该消息被消费过,直接丢弃 我们可以设置confirm回调和 returned 回调 比如说,可以在发送消息的时候,把消息详情包括交换机名,路由键,都保存到一个表中,状态设置为发送中,如果在confirm方法中ack为false,代表发送到交换机失败 ,就把这个记录状态修改为发送失败 然后我们创建一个定时任务定时扫表,去读取发送失败的数据并重新发送,为了优化性能,我们设置重试次数3次,如果3次都失败了,我们可以采取人工干预 Lucene是基于倒排索引原理来实现的 keyword:不分词,直接建立索引,支持模糊查询,精确查询,聚合查询 text:分词后建立索引,支持模糊查询,精确查询,不支持聚合查询 keyword通常用于通常用于存储年龄,性别,邮编,邮箱号码等等,直接将完整数据保存的场景 text通常存储全文搜索的数据,例如地址,文章内容的保存 ES是基于Lucene的开源搜索引擎,它解决了原生Lucene使用的不足,优化了Lucene的调用方式 传统搜索比如mysql的like关键字查询,它的搜索方式就是全文扫表,查询性能很低 ES是基于Lucene的全文检索引擎,它采用的是倒排索引结构,在存储时先对文档进行分词,再做一些标点符号去除,大小写时态转换等优化处理,最后按照字母顺序去重排序,形成一个倒排索引文档,我们在检索时,就可以通过二分查找的方式找到目标值 Index:索引库,包含有一堆相似结构的文档数据,类比Mysql中的数据库 Type:类型,它是index中的一个逻辑数据分类,类比Mysql中的表 Document:文档:是ES中的最小数据单元,通常用json结构标识,类比Mysql中的一行数据 Field:字段:类比Mysql中的一个列 从ES7.0开始,Type被干掉了,从此库表合一即一个Index中只有一个默认的Type TermQuery:匹配关键字查询(关键词不分词) MatchQuery:匹配关键字查询(关键字分词后) BooleanQuery:按条件查询 matchAllQuery:匹配所有文档查询 rangeQuery:查询指定范围内的数据 DSL是一种以json形式标识的,由ES提供的一种查询语言,它由两部分组成,DSL查询和DSL过滤。 DSL过滤类似于模糊查询,DSL查询类似于精确查询 term:不会对搜索词进行分词处理,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到 match:会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到 指标聚合,比如求和,求最大值,最小值,平均数 数量统计聚合,计算满足条件数据的总条数,相当于sql中的count 去重聚合,它会计算非重复的数据个数,相当于sql中的distinct 桶聚合,它会将某个field的每个唯一值当成一个桶,并计算每个桶内的文档个数,相当于sql中的group by 最高权值聚合,它会匹配每组前n条数据,相当于sql中的group by后取出前n条 使用HighlightBuilder对关键字作高亮处理,由于我们项目使用的是SpringBoot整合ES的jar包,结果没有进行高亮处理,我们使用ElasticsearchTemplate的queryForPage方法来获取结果,再手动进行分页封装返回前台 代码控制的,数据库做了写操作,直接更新ES中的数据,我知道可以通过 Logstash 中数据和ES的数据自动同步。也可以通过阿里的canal组件来同步。 我们使用的是spring-boot-start-data-elasticsearch这个库来操作ES,用在大数据的搜索场景,比如商品的发布,搜索功能。 关键字:每个服务器都能独立运行 关键字: 使用了集群后,解决高并发同时有一个新的问题,就是客户端的请求如何分配到多台服务。因此需要通过负载均衡器,比如Nginx,使用负载均衡算法比如轮询、权重、随机等等将请求路由到不同的服务器 分布式是将应用按照业务类型拆分成多个子应用,每个子应用部署在不同的服务器上单独运行,子应用之间通过API相互调用。 可以分散服务器压力解决高并发问题,同时可以解决单体应用代码臃肿、业务复杂、维护性差等等问题,但是不能防止单节点故障,比如一个子应用故障,整个应用就能不完整运行 如果一个任务由10个子任务组成,每个子任务单独执行需1小时,则在一台服务器上执行该任务需10小时。 采用分布式方案,提供10台服务器,每台服务器只负责处理一个子任务,不考虑子任务间的依赖关系,执行完这个任务只需一个小时。(这种工作模式的一个典型代表就是Hadoop的Map/Reduce分布式计算模型) 而采用集群方案,同样提供10台服务器,每台服务器都能独立处理这个任务。假设有10个任务同时到达,10个服务器将同时工作,10小时后,10个任务同时完成,这样,整身来看,还是1小时内完成一个任务! 集群是将一个应用程序复制多份,部署在多台服务器上,每个服务器中的程序都是完整的,可以独立运行 分布式是将一个应用程序拆分成多个子程序,分别部署在多台服务器上,每个服务器中的程序都是不完整的,所有服务器需要相互通信相互协调才能完成最终的业务 集群能解决高并发问题,同时能防止单节点故障,即一台服务器宕机不影响其他服务器的正常运行 分布式也能解决高并发问题,但不能防止单节点故障,即一台服务器宕机了,整体业务就无法完成 集群无法解决项目本身的代码臃肿、业务复杂等等问题,分布式能降低模块之间的耦合 实际应用中,我们可以将分布式和集群相结合,比如分布式某个子程序的负载很高,可以单独对这个子程序做集群 微服务也是一个分布式系统,它将单体应用进行细粒度拆分,形成多个微服务,每个服务独立运行,每个服务也都可以有自己的数据库,服务之间使用HTTP通信,互相协调完成整个系统的业务。 它的优点是服务之间解耦合,不同的服务可以有不同的编程语言,技术选型多元化,支持敏捷开发 他的缺点是分布式事务很复杂,部署麻烦,技术成本高,服务间通信对性能也有一定的损耗 CAP理论指的是,在一个分布式系统中,一致性,可用性,分区容错性,三个要素最多只能同时实现两点。 分区容错性是分布式系统的内在要求,因此我们通常会在一致性和可用性之间做取舍。 满足CP,也就是满足一致性和容错性,舍弃可用性,如果系统允许有段时间失效就可以考虑。常见的如Redis,Nacos,ZooKeeper 满足AP,也就是满足可用性和容错性,舍弃一致性,如果系统允许出现短暂时间的不一致可以考虑。常见的如MySQL,Eureka 关键字:实时同步,最终会同步 强一致性是指数据在多个副本中总数实时同步的,如果能容忍数据在多个副本中在一定的延迟时间内同步,则是弱一致性 最终一致性则不要求数据什么时候同步,但是最终会同步即可。通常情况下我们在分布式领域选择会牺牲了强一致性,会采用最终一致性 关键字:数据最终要保证一致性 Base指的是基本可用,软状态,最终一致性。它是对CAP中的AP的扩展,意思是说当出现故障部分服务不可用时,要保证核心功能可用,允许在一段时间内数据不一致,但最终要保证一致性。满足Base理论的事务也叫柔性事务 答案是属于。微服务的意思也就是将模块拆分成一个独立的服务单元通过接口来实现数据的交互。但是微服务不一定是分布式,因为微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。这也是分布式和微服务的一个细微差别。 关键字:Springcloud Netflix,Eureka,Ribbon/OpenFeign,Hystrix,Zuul,Config,Bus,Sleuth 我司正在使用的是第一代微服务方案,Springcloud Netflix全家桶。 它是使用Eureka做服务注册与发现,也就是解决服务之间通信问题, 使用Ribbon/OpenFeign做客户端的负载均衡,也就是解决将请求路由到微服务集群的问题, 使用Hystrix断路器的熔断、降级来解决单节点故障, 使用Zuul做服务网关,将它作为整个微服务的大门,来实现登录、权限检查等业务, 使用Config分布式配置中心,来统一管理配置所有微服务的配置文件, 使用Bus消息总线给各个微服务广播消息,可以实现各个微服务配置的自动刷新, 使用Sleuth链路追踪,来实时监控各个微服务建的调用关系,快速定位故障节点 关键字:Eureka,Ribbon/OpenFeign,Hystrix,Zuul,Config,Bus,Sleuth Eureka:做服务注册与发现,用来解决服务之间通信问题, Ribbon/OpenFeign:用做客户端的负载均衡,也就是解决将请求路由到微服务集群的问题, Hystrix:断路器,它的熔断、降级策略用来解决单节点故障, Zuul:做服务网关,它是整个微服务的大门,可以用来实现登录、权限检查等业务, Config:分布式配置中心,用来统一管理配置所有微服务的配置文件, Bus:消息总线,用来给各个微服务广播消息,可以实现各个微服务配置的自动刷新, Sleuth:链路追踪,用来实时监控各个微服务建的调用关系,快速定位故障节点 关键字:优点:无耦合,HTTP,扩展,敏捷开发。缺点:繁琐,部署,技术成本,性能损耗 微服务相对单体应用来说 优点 缺点 关键字:保存微服务的端口、ip。。 Eureka是一个服务注册与发现的组件,翻译成人话就是管理所有微服务的通讯录的组件。它包含注册中心,客户端两部分组成。客户端在启动的时候会向注册中心发送一条自我介绍信息,比如端口,ip等等,在注册中心就会保存一张所有微服务的通讯录。这就叫服务注册 关键字:根据微服务通讯录找到另一个微服务 微服务会定期的从客户端拉取一份微服务通讯录,到本地缓存起来,默认是30s一次。当一个微服务向另一个微服务发起调用,直接根据本地的通讯录找到对方的服务名,发送HTTP请求。这个就叫服务发现 关键字:剔除 微服务会定时(默认30s)发送心跳请求,告诉注册中心,自己还处于存活状态,那么服务中心就不会将其从清单中删除,否则,当微服务宕机或者网络故障等因素,没有在规定时间(默认90s)内提交心跳请求,注册中心就会将它从通讯录中删除。 关键字:修改时间,加快频率。熔断降级。集群 第一,可以修改注册中心剔除服务时间,同时加快服务续约心跳请求的频率 第二,可以使用Hystrix的熔断降级机制,当某个服务不可访问,快速失败,并返回托底数据 第三。重试,提供者集群 使用了ScheduledThreadPoolExecutor线程池定时任务来实现 服务发现是先判断是否开启了服务发现功能(默认是开启的),获取定时任务的间隔时间(默认是30s),然后初始化服务发现的定时任务,间隔时间可以在yml中修改 服务续约是先判断是否开启服务注册功能(默认是开启的),获取定时任务间隔时间(默认是30s),然后初始化心跳请求的定时任务,间隔时间可以在yml中修改 Ribbon是一个客户端负债均衡器,它可以按照负债均衡算法,向多个服务发起调用。当一个微服务有多个集群时,就可以使用它做请求负载均衡,通常结合RestTemplate来使用 消费者会30/次注册中心拉取服务注册清单缓存到本地,当消费者需要调用一组提供者集群服务时,Ribbon会根据提供者服务名,在本地缓存的服务地址清单里找到这一组服务的通讯地址,然后按照负债均衡算法(默认是轮询),选择其中的一个通讯地址,发起http调用服务。 Ribobn内部通过LoadBalancerInterceptor拦截RestTemplate发起的请求,然后交给RibbonLoadBalancerClient负载均衡客户端做负载均衡,RibbonLoadBalancerClient把选择服务的工作交给ILoadBalancer负载均衡器 ,ILoadBalancer会调用 IRule负载均衡算法类来选择服务。之后RibbonLoadBalancerClient把选择好的服务交给LoadBalancerRequest去发请求。 RoundRobinRule:简单轮询,ribbon默认规则 AvailabilityFilteringRule:忽略短路状态和并发过高的服务器 WeightedResponseTimeRule:根据服务器响应时间作为权重,响应时间越长权重越小 ZoneAvoidanceRule:根据区域选择 BestAvailableRule:忽略短路的服务器,选择并发较低的服务器 RandomRule:随机选择一个可用服务器 Retry:重试机制的选择逻辑 OpenFeign整合了Ribbon和Hystrix,屏蔽了Ribbon拼接URL,参数的细节,使用声明式编程,让服务调用变得更加简单,OpenFiegn底层也是走的Ribbon的负载均衡策略。推荐使用OpenFeign 首先,当程序启动时,@EnableFeignClient会扫描@FeignClient注解的接口,并交给Spring容器管理。 当发起请求时,会使用jdk动态代理,并为每个方法都生成相应的RequestTemplate,同时封装http信息,包括url和请求参数等, 最后把RestTemplate交个HttpClient发送请求,使用ribbon的负载均衡发起调用 在微服务系统中,各个服务之间是需要进行网络通信的,那么他们相互调用就得知道对方的通信地址。eureka就是专门来做做服务注册与发现,解决服务之间通信问题的 当一个微服务做了集群,也就是同一个服务名会对应多个地址,那么我们在调用的时候,应该调用哪一个就成了问题,Ribbon是一个负债均衡器,它可以按照负债均衡算法,向多个服务发起调用。当一个微服务有多个集群时,就可以使用它做请求的分发 在微服务系统中,服务数量很多,而每个服务都有自己的配置文件,管理起来很麻烦。用了配置中心就可以帮我们集中管理配置文件,它支持本地配置文件,也支持将配置文件放到远程仓库如git集中管理 自动注入的实例其实是一个jdk动态代理对象,Feign会为每个方法生成相应的requestTemplate,它根据服务名找到对应的服务,根据返回值类型、形参列表匹配相应的接口,然后封装url、请求参数,最后生成request请求,使用Ribbon负载均衡发起调用 关键字:熔断器,熔断降级 Hystrix意为熔断器,它可以将出现故障的服务,通过熔断、降级等手段隔离开,这样不影响整个系统的主业务。它可以防止由单节点异常导致整个微服务故障,如果遇到故障时,快速失败,熔断的同时可以返回拖底数据达到服务降级的目的 关键字:熔断:保护机制,短路,降级拖底数据。降级:不可以,返回拖底数据; 熔断,是对服务链路的一种保护机制,当链路上的某个服务不可访问时,服务就会触发降级返回拖底数据,同时当失败率到达一个阈值,就标记该服务为短路状态,当请求访问时直接熔断。直到检查到该服务能正常访问时,就快速恢复 降级,是当某个服务不可访问时,我们返回一些事先准备好的数据给客户端,比如说,友情提示服务暂不可用,请骚后重试,这样用户体验就上去了 关键字:限流,线程池隔离:当前请求。信号量隔离:记录。 指的是限制某一个分布式服务的资源使用,可以理解为限流,也就是限制某个服务的请求数量。它包括线程池隔离和信号量隔离 线程池隔离,是指用一个线程池来存储当前请求,可以通过设置线程池最大线程数和最大排队队列数来限制请求数量 信号量隔离:是指用一个计数器来记录当前有多少个线程在运行,请求进来计数器就增加1,超过最大信号量,就直接返回 关键字:同一个线程,开销大小 线程池方式是异步处理,它与调用线程不是同一个线程 信号量方式是同步处理,与调用线程是同一个线程 线程池方式由于需要排队,调度,线程切换,因此开销较大,信号量方式无需切换线程,开销较小 CAP理论指的是,一个分布式系统中,一致性,可用性,分区容错性,三个要素只能同时实现两点。Eureka选择的是AP,它是弱一致性的,保证了可用性和分区容错性,放弃了数据一致性。也就是说当多个Eureka之间不可通信时,需要保证服务可用,正常提供服务注册发现功能,但是网络恢复后最终还是会同步的。 为了防止服务被误删除,Eureka不会立即删除过时的服务数据。这种机制可能会导致客户端从注册中心获取到已经下线的服务并发起调用而导致错误,因此在开发阶段我们可以关闭自我保护机制。在生产环境中,我们需要打开自我保护,因为它可以防止因为网络波动,服务没有及时续约而造成的服务误删除问题。 比如在秒杀业务中,需要实时从redis中查询库存,通过设置hystrix的最大信号量,以此来防止redis雪崩。当并发过高,请求数超过最大信号量,触发降级,直接向客户端返回兜底数据:”活动太火爆啦,请骚后重试“ zuul按照执行顺序,分为pre前置过滤,route路由过滤,post后置过滤,error异常后过滤 正常流程是请求先经过前置过滤器,到达路由过滤器进行路由,路由到各种微服务执行请求,返回结果后经过后置过滤,返回用户 异常流程,如果再整个过程中出现异常,都会进入error异常过滤器,处理完毕后经过post过滤器返回用户,如果error自己出现异常,最终也会通过post过滤器返回用户,如果post过滤器出现异常,也会跳转到error过滤器,然后直接返回用户 可以通过继承ZuulFilter抽象类,自定义pre类型的过滤器,shouldFilter方法中可以定义需要放行的资源,run方法中检查请求头中的token信息,如果没有token,就响应到客户端未登录的信息,并组织filter继续往后执行 方式一:可以通过继承ZuulFilter抽象类自定义pre过滤器,加上限流算法,来实现 方式二:可以通过hystrix的资源隔离模式,设置线程池最大连接数或者最大信号量来实现 方式三:常用,Ratelimit,使用令牌桶算法。。。 GJZ:集中管理 在分布式系统中,服务数量很多,而每个服务都有自己的配置文件,管理起来很麻烦。配置中心是个好东西,可以帮我们集中管理配置文件,它支持本地配置文件,也支持将配置文件放到远程仓库如git集中管理。 第一步,导入eureka-server依赖,以及springboot的web环境依赖。 第二布,主启动类上打注解,@EnableEurekaServer,开启eureka服务端功能 第三步,yml配置文件中,配置注册中心的端口号,主机名,注册中心地址 第一步,导入ribbon依赖 第二部,给RestTemplate的Bean定义方法上,加上注解@LoadBalanced,让这个restTemplate有负载均衡的功能 第三步,修改restTemplate调用服务的url,将目标主机名换成目标服务名 第一步,导入openfeign依赖 第二部,主配置类加注解,@EnableFeignClients,开启feign支持 第三步,定义feign客户端接口,并加上注解@FeignClient(“目标服务名”),接口中定义方法,该方法与目标服务的对应方法的方法名,返回值类型,形参列表,url路径要一致 第一步,导入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依赖 第二步,主启动类上加注解@EnableZuulProxy,开启zuul功能 第三步,yml中配置,统一访问前缀prefix,禁用通过服务名方式访问服务ignoredServices,配置路由routes指定某个服务使用某个路径来访问 配置中心服务端配置: 第一步,导入config-server依赖 第二步,主启动类加注解,@EnableConfigServer,开启配置中心 第三步,配置文件中,配置远程仓库地址,仓库账号密码 客户端配置: 第一步,导入config-client依赖 第二步,创建bootstrap.yml配置文件,配置中心地址config.uri,要拉取的配置文件名name,环境名profile 前端门户系统:HTML + JQuery + CSS 前端管理系统:VUE + ElementUI 后端系统:基于SpringCloud微服务框架(Eureka+OpenFeign+Hystrix+Zuul+Config) +MyBatisPlus+SpringMVC+Redis+ElasticSearch+RabbitMQ+AlicloudOSS 浏览器发起的所有请求首先通过Nginx,通过负载均衡算法,路由给zuul集群,然后通过zuul前置过滤,作登录校验后,它会从配置中心拉取的通讯地址中,根据url匹配到对应的服务,然后使用ribbon发起restful调用。微服务间也可以通过feign相互调用,最终执行完任务,返回浏览器 Ribbon和Feign都是SpringCloud Netflix中实现负载均衡的组件,不同点在于 Ribbon是需要我们手动构建http请求,根据目标服务名通过负载均衡算法直接调用目标服务, Feign是采用接口的方式,将需要调用的目标服务方法定义成抽象方法,路径,服务名,形参列表,返回值类型需要保持一致。我们只需要调用接口中的方法就可以了。它会自动帮我们生成jdk动态代理,为每个方法生成RequestTemplate并封装url和请求参数,使用负载均衡算法发起调用 Ribbon的实现方式,一般配合RestTemplate发起http请求,我们需要在注册RestTemplate的Bean的方法上加@LoadBalanced,使它具有负载均衡的能力 Feign的实现方式,是在主启动类上加@EnableFeignClients,在客户端接口上加注解@FeignClient Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理。 Springboot是一个基于spring的框架,对spring做了大量简化,使开发流程更快,更高效。比如它大量简化maven依赖,基于注解配置(JavaConfig)无需XML,内嵌Tomcat,部署流程简单,打包和部署更加灵活,允许独立运行 SpringCloud是基于SpringBoot实现的,用于微服务架构中管理和协调服务的,它是一系列框架的有序集合,它为开发者提供了一系列工具,例如服务发现与注册,配置中心,网关,负载均衡,熔断器,链路追踪等等,让微服务架构落地变得更简单 分布式事务,指的是在分布式环境中,一个请求可能涉及到对多个数据库的写操作,要保证多数据库的一致性就需要用到分布式事务 常见的分布式事务解决方案,2PC,TCC,可靠消息最终一致性,最大努力通知 2PC,它将整个事务流程分为两个阶段,P指的是准备阶段,C指的是提交阶段。它是一个阻塞协议,不适用于并发较高,事务生命周期长的分布式事务。 TCC,它是基于补偿性事务的AP系统的一种实现,补偿也就是说先按照预定方案执行,如果失败了就走补偿方案。它可以自己定义数据操作的粒度,但是对应用的侵入性强,可以用在登录送积分,送优惠券等等场景 可靠消息最终一致性,指的是当事务发起方执行完本地事务后,就发出一条消息通知其他参与方,并且他们一定能接收到消息并处理事务。适合执行周期长,并且实时性要求不高的场景 最大努力通知,是在不影响主业务的情况下,尽可能的保证数据的一致性,它适用于一些最终一致性敏感度低的业务,比如支付结果通知 2PC,是将整个事务流程分为两个阶段,P指的是准备阶段,C指的是提交阶段。它常见的标准有XA,JTA,Seata 由DTP模型定义事务管理器TM和资源管理器RM之间通讯的接口规范叫做XA,它规定的交互方式是酱紫的:应用程序(AP)通过TM提交和回滚事务,TM通过XA接口来通知RM数据库事务的开始,结束,提交,回滚 2PC能保证分布式事务的原子性,但是也有很多缺陷 比如,在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好 比如,在第二阶段,如果事务协调者发出提交事务指令后宕机,一部分参与者收到消息提交了事务,另一部分没有收到消息没有提交事务,这就会导致数据不一致 再比如,在第二阶段,如果事务协调者发出提交事务指令后宕机,收到指令的参与者也宕机了,我们就不能确定事务的执行结果,究竟有没有提交 Seata是由阿里中间件团队发起的开源项目Fescar更名而来,是一个开源的分布式事务框架,它通过对本地关系数据库的分支事务协调,来驱动完成全局事务 Seata的主要优点是性能好,不会长时间占用链接资源,对业务零入侵 与传统的2PC的区别主要两方面 在架构层次方面,传统的2PC方案的RM本质就是数据库自身,而Seata的RM是以jar包形式作为中间件层部署在应用程序上 在两阶段提交上方面,传统2PC方案是在第二阶段完成才释放资源,而Seata是在第一阶段就将本地事务提交,提高了效率 TC:事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚 TM:事务管理器,TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令 RM:控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支事务的提交和回滚 TCC是基于补偿型事务的AP系统的一种实现。补偿指的先按照事先预定的方案去执行,如果失败了就走补偿方案 它的优点是异步执行效率高,它能对分布式事务中的各个资源分别锁定,分别提交与释放 它的缺点是对应用的侵入性强,改动成本高,实现难度大 SpringCloudAlibaba极简入门-分布式事务实战seata 假设有服务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依赖 第二步,yml中配置事务组名,同时需要添加配置文件file.conf,registry.conf,需要注意yml中事务组名与file.comf中的事务组名一致 第三步,配置DataSource,需要适用Seata对DataSource进行代理 第四步,数据库中添加undo log日志表 第五步,业务方法上加注解@GlobalTransactional(rollbackFor = Exception.class)注解 事务参与者: 不用框架就要自己实现,如果业务要求强一致性这个不太好做,需要协调多个数据库的同时提交和回滚.如果是业务不要求强一致性,我可以参照TCC思想 ,可以考虑自己实现异步写数据库方案,如果失败可以做补偿.当然这个要根据业务特性来,很多大公司都是自己封装事务框架. 分布式锁是在分布式/集群环境中解决多线程并发造成的一系列数据安全问题.所用到的锁就是分布式锁,这种锁需要被多个应用共享才可以,通常使用Redis和zookeeper来实现。 分布式锁常用的三种方案 基于数据库实现:通常基于主键,或者唯一索引来实现分布式锁,但是性能比较差,一般不建议使用 基于Redis :可以使用setnx来加锁 ,但是需要设置锁的自动删除来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合。 另外释放锁在finallly中调用del删除锁,而删除锁前需要判断该锁是否是当前线程加的锁以免误删除锁,需要通过get获取锁然后进行判断,但是需要保证get判断或和del删除锁的原子性,可以使用LUA脚本实现。 总之自己封装Redis的分布式锁是很麻烦的,我们可以使用Redissoin来实现分布式锁,Redissoin已经封装好了。 基于zookeeper : 使用临时顺序节点实现,线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁,后面的节点监听自己的上一个节点的删除事件,如果第一个节点被删除,释放锁第二个节点就成为第一个节点,获取到锁。 在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案。 可以使用setnx来加锁 ,但是需要设置锁的自动删除来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合。 添加锁和设置过期时间可以使用set命令进行组合,达到原子性加锁 需要用lua解决删除和判断锁的原子性,否则可能会删除掉别人的锁。 Redis集群环境中,redis节点挂掉可能会导致加锁失败,可以使用Redisson的红锁来解决。 自己封装Redis的分布式锁是很麻烦的,我们可以使用Redissoin来实现分布式锁,Redissoin已经封装好了 Redisson对分布式锁进行了封装,对于锁超时问题,它提供了看门狗进行锁时间的续期,底层使用了定时任务每10s检查一下,如果业务还未执行完成,未释放锁,就进行超时时间续期。 基于zookeeper : 使用临时顺序节点实现,线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁,后面的节点监听自己的上一个节点的删除事件,如果第一个节点被删除,释放锁第二个节点就成为第一个节点,获取到锁。 在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案。 秒杀的商品和库存是缓存到Redis的,库存使用信号量,做的是秒杀预减库存方案。用户发起秒杀,直接走Redis秒杀商品,满足资格就预减库存,然后预创订单写入Redis。整个秒杀流程是不做数据罗库的。 此时把订单号返回给客户端,用户带着订单号进入订单确认页面进行下单,用户确认下单,再把Redis中的预创订单写入订单数据,同时做库存同步。紧接着就是调用支付接口做支付。 我们用户量不是很大,我进去的时候是50都W的用户,要求的是能抗住5000的QPS,线上环境实际的QPS是3千多,具体的不清楚,因为是经理他们在看,我开发完这个功能在本地jemeter压测的能达到1500的吞吐量。在线上环境做做集群什么的还是很容易达到5千以上的。 第一个是纯Redis秒杀,库存和商品本身都是使用Redis缓存,库存使用过的是信号量来保证原子性,订单也是放到Redis,用户确认订单后才入库。基于纯Redis秒杀,然后再做做集群上几千是很容易的。 Lvs+Nginx集群+下游服务集群。如果流量再高,就使用CDN分流。 支付超时使用MQ延迟队列来处理,把消息投递到一个设置了过期时间的队列中,达到过期时间消息会被转发给另外一个“死信队列” 设置了过期时间的队列就是延迟队列,过期的消息叫着死信消息,存放死信消息的队列叫死信队列。 下单业务中用到了一个低劣,订单超时用到一个队列,支付结果处理用到一个队列。 预创订单号,前台通过这个订单号来进行下单。 表单重复提交一般都用令牌机制嘛,在上一个页面生成一个随机令牌存储到Redis,然后把令牌带入表单页面,提交订单时把令牌带到后台,后台先去Redis比对令牌,然后进行下单逻辑,下单后就把令牌从Redis删除。 如果是重复提交,那么Redis中已经没有令牌,就会下单失败。 Redisson分布式锁,信号量来保证库存不超卖,它也是一种分布式锁,它能够保证多线程在扣减信号量的时候程原子性减,然后保证不会加成负数。 这种就是要提高接口的响应速度,减少和数据库的交互,可以基于Redis优化,或者使用MQ异步方案进行处理。 一方面:提高并发数 1.多线程,尽量用线程池 (线程个数:CPU核数 / (1 - 阻塞系数(IO密集型接近1,计算密集型接近0))) 2.适当调整连接数(Tomcat,Redis,Mysql等连接数) 3.集群 二方面:提高接口响应速度 1.减少和数据库交互,使用Redis代替 2.使用异步方案,比如MQ 3.使用并发编程,多个线程同时工作 4.减少服务的调用链 5.实在要连数据库,考虑数据库优化 前端优化: 后端优化: 不同的平台接入流程是一样的,但是每个具体的步骤不同,可以使用模板模式+策略模式来实现。 你说的是空扫描和延迟问题吧,小项目数据量少到无所谓,如果是数据量很多,可以使用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.执行绑定逻辑时,根据手机号判断是否有本地账户,如果有就直接绑定,如果没有就自动注册再绑定,绑定成功后就默认登录 它主要是对登录和授权的流程做了封装,简化我们的代码量,也提供了很多功能,比如:认证授权结果处理,记住我等。总之用来是很简单的。 包括了web授权和方法授权,我们一般使用方法授权,注解的方式比较灵活。需要在配置类上打注解@EnableGlobalMethodSecurity(prePostEnabled = true)开启全局方法授权支持,然后再需要授权的方法是打授权注解@PreAuthorize 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方法进行授权 首先,请求会经过UsernamePasswordAuthenticationFilter拦截,请求的用户名密码会封装成UsernamePasswordAuthenticationToken,过滤器将token提交给认证管理器AuthenticationManager进行认证 然后,认证管理器调用AuthenticationProvider进行认证,AuthenticationProvider再调用UserDetailsService获取到数据库中存储的用户信息UserDetails,然后调用密码编码器对密码进行比较,认证成功后封装Authentication 再后来,请求回到UsernamePasswordAuthenticationFilter,调用SecurityContextHolder将Authentication对象封装成SecurityContext并保存到SecurityContextHolder中 最后,请求回到SecurityContextPersistenceFilter,它会调用SecurityContextRepository将SecurityContext对象存储起来,再清理掉SecurityContextHolder中的信息 非对称加密是一种算法,指的是加密和解密时使用不同的密钥,其中私钥不可公开,公钥可以公开。 数字签名就是在非对称加密的基础上,使用私钥加密,公钥解密,主要用来防止数据被篡改,实现安全传输的目的 oauth协议是一个安全的开放授权标准,与传统的授权方式相比,它不会使第三方触及到用户的账号信息,Oauth2有四种授权模式 一、授权码模式:它是功能最完整,流程最严密的授权模式 二、简化模式:它简化了授权码模式,跳过了授权码这个步骤 三、密码模式:通过用户名密码的方式来换取Token , 前三种都可以用作三方登录 四、客户端模式: 以客户端的名义直接向服务器申请Token,这种需要对客户端绝对信任才可以,比如系统内容获取Token 我们使用的是Spring Cloud Oauth2 ,它整合了SpringSecurity+Oauth2+JWT,主要分为两块配置:认证服务器-负责颁发token,资源服务-器负责校验Token和资源授权 我们项目是资源服务器去校验Token,也可以将Token校验工作交给网关统一校验,资源服务器只负责授权工作。 另外常见的授权方案还有,CAS单点登录,用户只用在某个服务上登录,访问其他服务时就不需要登录了,这就要求每个面向用户的服务都必须于认证服务交互,会产生大量重复的工作 分布式会话,它是将用户认证信息存储在共享容器比如redis中,通常会以会话作为key,当用户访问微服务时,就从redis中获取认证信息。这对安全存储有较高的要求,复杂度高 关键词:认证中心-》password -》httpclient ->security的认证流程-》oauth2 -》回令牌 -》带令牌 -》校验令牌 -》方法授权 一个字,安全,我们做了认证授权后,每次客户端访问资源服务器,都需要远程调用认证服务器进行token的校验和授权,才能访问到资源。这是很消耗性能的,因此我们考虑将签名信息直接保存到客户端,那就不需要每次都向认证服务器认证授权了。 但是这有有一个新的问题,这些敏感数据赤裸裸的存到客户端不安全!而JWT就能解决这个问题。它支持非对称加密算法对信息加密,保证了信息安全 另外,JWT以json对象的形式传递信息,解析更方便 可以在令牌中定义内容,方便扩展 首先,我们会在前端设置axios后置拦截,检查是否是token过期,判断一下如果返回401,就代表token过期了 然后从localStorage中获取刷新refresh_token,并发送请求获取新的token 后台接收到前台的刷新token请求,拼接完整的刷新token的url,发送http请求获取到新的token并返回客户端 客户端收到新的token就把旧的token覆盖掉,最后把之前的请求再重新发送一次 关键词:过期时间,ip, 然后,可以在token中加入客户身份标识,比如客户的ip地址,如果短时间内ip地址频繁变动,就标记为异常状态,并给用户发送信息,提示账户有风险 Token都能被盗,那这个就是用户自己的问题了。SpringBoot中如何管理事务 8-23
Mybatis部分
MyBatis中${}取值和#{}取值的区别8-23
MyBatis关联查询中,延迟加载和饥饿加载的区别8-24
MyBatis对象关联查询和集合关联查询怎么做8-24
MyBatis一级缓存和二级缓存的区别8-24
MyBaits的Mapper接口没有实现类为什么可以用@Autowired直接注入8-24
在MyBatis如何动态修改SQL 8-25
MyBatis的动态SQL标签有哪些? 8-25
Mybatis的mapper如何传递多个参数8-25
Mybatis,关联对象查询,使用嵌套子查询和JOIN连表有什么区别8-25
为什么要使用连接池 8-26
关键字:建立,管理,关闭
Redis部分
讲一下你理解的Redis,为什么Redis很快 8-26
你常用的Redis的数据存储结构有哪些,他们的使用场景分别是什么 8-26
Redis每种存储结构说 4 个命令吧 8-26
你们项目是怎么用Redis的 8-27
怎么防止Redis宕机数据丢失问题 8-27
Redis持久化是什么?有几种方式 8-27
Redis有了AOF持久化为什么还要RDB? 8-27
Redis内存不够了怎么办? 8-28
你们Redis用在哪些业务上?用的什么存储结构8-28
淘汰策略有哪些?你们用的哪种8-28
Redis事务和Mysql事务的区别8-28
使用Redis如何实现消息广播8-29
为什么要使用Redis做缓存 8-29
缓存它指的是将数据库的数据同步到内存中,客户端获取数据直接从内存中获取。由于内存读写速度大于磁盘,而使用缓存能减少磁盘读取,大大提高查询性能。缓存的执行流程 8-29
你们怎么保证Redis/Mysql的一致性 8-29
SpringCache常用注解 8-30
了解缓存击穿,穿透,雪崩吗?怎么处理? 8-30
Redis如何模拟队列和栈,用什么命令 8-30
Redis存储单个对象怎么存,存储对象集合怎么存 8-30
Redis的Value支持哪些存储结构 8-31
你们Redis用来做什么?使用的什么结构? 8-31
统计全国高考前20名用什么?8-31
从100个VIP用户中随机抽取5名怎么做?8-31
RocketMQ
MQ有哪些使用场景 8-16
说下一下RokcetMQ的架构8-16
RabbitMQ工作流程8-16
RocketMQ的数据是存储到内存还是硬盘,如果是硬盘那么怎么保证速度8-16
发送消息有几种方式
Queue的分配算法
Consumer消息的拉取模式有哪两种
如何保证消息的顺序
延迟消息有用过吗?怎么用?可以用在什么业务场景
RocketMQ事务消息的使用场景,举例说明
RocketMQ事务消息工作流程
RabbitMQ
RabbitMQ的使用场景
RabbitMQ如何防止消息丢失
RabbitMQ的交换机有哪几种
消息是如何从发送者到达消费者的(RabbitMQ工作流程)
如何防止消息重复消费
RabbitMQ消息投递失败,你们怎么处理
ElasticSearch
Lucene创建索引原理
ES的keyword和text区别
ES的优势
Lucene/ES为什么那么快(ES用到什么数据结构)
ES的分层结构,index下面是什么
讲几个ES中的查询对象:比如TermQuery
你简单描述一下DSL语法
你说一下 match和term的区别? 8-22
你使用过ES的哪些聚合查询?8-22
ES高亮怎么做的? 8-22
你们ES和数据库的数据一致性怎么做的8-22
你们项目怎么使用ES8-23
三.微服务部分
相关概念
什么是集群 8-23
集群是将应用复制成多个相同的应用,一起来工作,从而提高工作能力。即将多个应用程序分散在不同的服务器,每个服务器都独立运行相同的代码。可以分散服务器压力解决高并发的问题,同时也能预防单节点故障,即一台服务器故障不影响其他服务器正常运行,但没有解决单体应用代码臃肿,业务复杂,维护性差等等问题什么是负载均衡 8-23
什么是分布式 8-23
分布式集群举例
集群和分布式的区别,分别解决什么问题8-24
说一下你理解的微服务8-24
什么是CAP理论 , 哪些技术用到AP,哪些用到CP 8-24
什么是强一致性和最终一致性 8-24
什么是Base理论 8-25
分布式是否属于微服务?
SpringCloud
讲一下你们公司微服务解决方案8-25
说一说Spring Cloud有哪些常用组件8-25
Spring Cloud的优缺点?8-25
什么是服务注册 8-26
什么是服务发现 8-26
什么是服务续约 8-26
如果服务挂了,注册中心要等到90s后剔除,那么在剔除前的这段时间内,挂掉的服务有可能还是会被调用,怎么处理? 8-26
你知道EurekaClient服务发现和服务续约每隔30s做一次请求是用什么技术实现的吗? 8-27
Ribbon是什么,Ribbon的工作原理讲一下 8-27
说一下 Ribbon的工作原理 8-27
Ribbon有哪些负载均衡算法,怎么配置 8-27
OpenFeign和Ribbon的区别 8-28
OpengFiegn的工作流程 8-28
为什么要使用Eureka 为什么要使用Ribbon 为什么要使用config配置中心 8-28
为什么Feign的客户端接口没有写实现类也可以直接被依赖注入 8-28
介绍一下Hystrix 8-29
什么是熔断,什么是降级 8-29
什么是资源隔离? 8-29
资源隔离中信号量和线程池的区别? 8-29
对于CAP理论,Eureka选择的是AP还是CP?它保证了一致性还是可用性? 8-30
说一下Eureka的自我保护 8-30
你们项目是如何做服务降级的?8-30
Zuul有哪几类Filter,他们的执行顺序是怎么样的? 8-30
在Zuul中做登录检查如何实现?8-31
在Zuul中如何做限流?8-31
配置中心解决什么问题? 8-31
EureakServer的搭建流程 8-31
Ribbon的整合流程 9-1
Feign的整合流程 9-1
Hystrix的整合流程 9-1
Zuul的整合流程 9-1
ConfigServer的整合流程 9-2
你们微服务项目的技术栈描述一下 9-2
浏览器发起一个请求,在你的微服务项目中的怎么去执行的? 9-2
说下Ribbon和Feign的区别呢 9-2
Spring,SpringBoot和SpringCloud的关系以及区别 9-3
分布式事务
什么是分布式事务 9-3
分布式事务你知道哪些解决方案? 这些方案如何选型 9-3
什么是2pc 9-3
Seata相比传统2PC有什么区别,以及优点? 9-4
Seata的TC,TM,RM的含义,以及作用? 9-4
你知道TCC吗,它有什么样的优缺点? 9-4
解释一下Seata的工作原理 9-4
Seata有三个角色:
你能简单描述一下你在项目中是如何集成Seata的吗 9-5
没有Seata或者TCC这些事务框架,你可以怎么处理事务?9-5
分布式锁
你说一下什么是分布式锁 9-5
分布式锁有哪些解决方案 9-5
Redis如何实现分布式锁,用什么命令
Redis实现分布式锁可能会出现什么问题,如何解决
你项目中怎么使用分布式锁的
了解Redission的看门狗原理吗?
你在项目中如果使用ZK实现分布式锁的?
秒杀系统
秒杀的整体流程详细说一下
你们这个秒杀QPS是多少
你们是怎么保证这么高的QPS的
如果流量更高,比如:每秒10W请求,应该怎么处理
说一下支付超时处理方案?延迟队列和死信队列是什么意思?
整个秒杀流程你用到了哪些队列
秒杀成功,返回给用户的数据是什么?
怎么防止表达重复提交
你们怎么处理超卖
如果一个接口需要耗时1s,导致后续的请求堵塞,怎么处理
如何提高接口的qps
项目并发高处理过不过来怎么办
订单支付
点击确认订单背后的业务流程
你们使用支付平台的哪些接口
你接入不同的支付平台,支付方式也会有些不一样,可以使用什么设计模式
订单超时使用quartz来做是有性能问题的,怎么处理
如果延迟队列在退库存的时候,正好用户下单成功,怎么办?
如果用户正好在付款切付款成功了,这个时候订单自动超时了,怎么办
如果异步通知失败怎么办(掉单)
接口幂等一般怎么设计?
认证授权
讲一下你们的登录实现方案
三方登录流程讲一下
为什么要使用SpringSecurity
Security中怎么授权
说一下security中的的filter
说一下security的认证原理
非对称加密,什么是数字签名 9-14
Oauth2的四种授权模式 9-14
讲一下你们的微服务授权方案 你还知道有哪些方案吗? 9-14
讲一下你们微服务认证授权的整体流程 9-14
你们为啥要用JWT 9-15
Oauth2认证,如果Token过期了你们是怎么处理的 9-15
Oauth2认证,如果Token被盗了怎么办?9-15
首先,我们需要对token设置过期时间,这个时间可以根据需要设置短一点四.技能提升