目录 不好使!点右边小目录定位;
Vector、ArryList、LinkedList 的区别与联系★
Hashtable与HashMap的区别?如何解决那个线程不安全的问题?★★
BigDecimal和float、double有什么区别?BigInteger和int、long有什么区别?
Java 中的 Math.round(-1.5) 等于多少?
Synchronized 和 Volatile有什么区别?★★
Statement 与 PreparedStatement 的异同?
事物的四种隔离级别是?(一致性和并发性:一致性越好,并发性越差) ★★
传统开发中数据库链接的问题?如何解决?实现方式? (你了解数据库连接池吗?)
一张表里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把mysql重启,再insert一条记录,这条记录的ID是18还是15 ?
当你的浏览器中地址栏输入地址并回车的一瞬间到页面能够展示回来,经历了什么?
GET请求与POST请求的区别? — 表单(form标签)提交的方式 ★★
拦截器(Interceptor)和过滤器(Filter)的区别?★
通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Mybatis的缓存机制?(一级、二级缓存) (装饰模式) ★
Mybatis 结果集的映射方式有几种,并分别解释每种映射方式如何使用?
简述Spring声明式事务中@Transaction中常用的事务属性有哪些?
mysql 与 redis 如何保持数据一致性(防止脏读)?(延迟双删) ★★
缓存击穿,缓存穿透,缓存雪崩 (缓存的过程中有没有遇到什么问题) ★★★ ★
nginx有一个master,有四个woker,每个woker支持最大的连接数1024,支持的最大并发数是多少?
为什么Nginx 在Linux系统上效率高 (IO多路复用 epoll) ★
SpringBoot 的核心注解?它主要由哪几个注解组成的 ★★★
Spring Boot 的配置文件有哪几种格式?它们有什么区别? (yml配置文件) ★
ReentrantLock 和 synchronized区别
什么是AQS? ReentrantLock加锁本质是什么? ★★★
什么是CAS?int++ 如何保证原子性?AtomicInteger 底层原理?★★★
Mysql 底层数据结构 (InnoDB存储引擎:B+树) ★★★ ★
索引概念 (分类、优点、回表、覆盖索引、最左匹配、索引下推) ★★
JavaSE
什么是多态?★★
父类引用指向子类对象,编译看左运行看右。
注意:多态是方法的多态,属性没有多态性。
重写和重载的区别?★★
重写 是一个面向对象的思想,先有继承后有多态
重载发生在本类,重写发生在父类与子类之间;
方法重写:方法名、返回值类型、参数列表必须相同
方法重载
方法名称相同。
参数列表不同(个数不同、或类型不同、参数排列顺序不同)
访问修饰符、方法的返回类型可以相同也可以不同。
仅仅返回类型不足以成为方法的重载。
值传递和引用传递的区别?
值传递是对于是对基本数据而言;引用传递是对于引用数据类型而言
值传递不会影响到实际参数,
引用传递直接传递引用地址(对象),会影响到实际参数。
HashMap底层原理?★★
Day23.Map集合、HashMap、Properties、TreeMap|Comparable_焰火青年·的博客-CSDN博客_string类型怎么排序
Vector、ArryList、LinkedList 的区别与联系★
vectory类:线程安全,效率低,底层synchronized
ArryList类:线程不安全,查询效率高,随机增删效率低。
LinkedList类:底层双向链表,线程不安全,查询效率低,随机增删效率高。
数组结构算法:插入和删除速度低,查询和更改较快。
链表结构算法:插入和删除操作速度快,查询和更改速度慢。
Hashtable与HashMap的区别?如何解决那个线程不安全的问题?★★
Hashtable 线程安全,HashMap线程不安全
解决安全:
1.并发容器类
CopyOnWriteArraySet、CopyOnWriteArrayList; ConcurrentHashMap
分段锁:sync锁node节点,避免无效的锁,只锁一小段
HashMap为什么不安全?
因为哈希碰撞导致哈希冲突,多线程可能会覆盖原有值2. Collections 的 synchronizedMap(Map<K,V> m) 。
BigDecimal和float、double有什么区别?BigInteger和int、long有什么区别?
在用C或者C++处理大数时感觉非常麻烦,但是在Java中有两个类BigInteger和BigDecimal分别表示大整数类和大浮点数类,至于两个类的对象能表示最大范围不清楚,理论上能够表示无线大的数,只要计算机内存足够大。这两个类都在java.math.*包中,因此每次必须在开头处引用该包。
BigInteger和BigDecimal是用对象表示数据的,其实底层是用字符串存储数据的,因此无法使用“算术运算符”进行算术运算,只能调用add等方法完成计算。
而float,double,int,long等是基本数据类型,可以直接用算术运算符运算,但是有存储范围有限以及精度的问题。
Java 中的 Math.round(-1.5) 等于多少?
Math.round(-1.5)的返回值是-1。四舍五入的原理是在参数上加0.5然后做向下取整
System.out.println(Math.round(1.4)); //1 System.out.println(Math.round(1.5)); //2 System.out.println(Math.round(1.6)); //2 System.out.println(Math.round(-1.4)); //-1 System.out.println(Math.round(-1.5)); //-1 System.out.println(Math.round(-1.6)); //-2
String 常用方法?
substring (int beginIndex) //从beginIndex处开始截取
replace(CharSequence target, CharSequence replacement) 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
replaceAll(String regex, String replacement)
split(String regex, int limit) 字符串。
toLowerCase()
使用默认语言环境的规则将此String
中的所有字符都转换为小写。
toUpperCase()
使用默认语言环境的规则将此String
中的所有字符都转换为大写
Object 中有哪些方法?
wait、notify
getclass
tostring
equals
hashCode
clone
接口和抽象类的区别?★
类只能单实现,接口可以多继承!
A:成员的区别
抽象类:
构造方法:有构造方法,用于子类实例化使用。
成员变量:可以是变量,也可以是常量。
成员方法:可以是抽象的,也可以是非抽象的。接口:
构造方法:没有构造方法
成员变量:只能是常量。默认修饰符:public static final
成员方法:jdk1.7只能是抽象的。默认修饰符:public abstract (推荐:默认修饰符请自己永远手动给出)
jdk1.8可以写以default和static开头的具体方法当需要为一些类提供公共的实现代码时,应优先考虑抽象类
当注重代码的扩展性跟可维护性时,应当优先采用接口。
jdk1.8 有哪些新特性?有关注后续更新吗?
Stream流、函数式接口、lamdba表达式,方法引用
解决空指针异常的类,Optional
HashMap红黑树
永久代->元空间
新的日期API后续:对垃圾回收 GC 修改较大:ZGC,可伸缩低延迟垃圾收集器。
String 能被继承吗?为什么用final修饰?
不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
- 为了效率,禁止被继承和重写;String 类是最常用的类之一,
- 为了安全。String 类中有native关键字修饰的调用系统级别的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。
sleep() 和 wait() 有什么区别?
sleep和wait都会导致当前线程进入阻塞状态,被挂起。
sleep不释放锁,睡眠时间到自动醒来,回到就绪状态
wait会释放锁,要通过notify()或notifyAll()唤醒,回到就绪状态sleep是在Thread类中声明的一个静态方法,Thread.sleep(毫秒)
wait是在Object类中声明的非静态的方法,必须锁对象调用
run() 和 start()的区别?
1. start方法用来启动相应的线程;
2. run方法只是thread的一个普通方法,在主线程里执行;
3. 需要并行处理的代码放在run方法中,start方法启动霞新城后自动调用run方法;
4. run方法public的访问权限,返回类型为void;
1、start方法启动了一个新的线程,而run方法不能启动一个新线程,还是在main线程下运行,程序依然是主线程一个线程在运行。
2、调用start方法可以启动线程,而run方法只是thread的一个普通方法还是在主线程中执行。
3、通过start()方法来启动的新线程,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。start()不能被重复调用。而run方法能被重复调用,因为它就是一个普通的成员方法。
4、start方法在执行线程体中代码时,在不执行完的情况下可以进行线程切换,而run方法不能,run方法只能进行顺序执行。
讲一下你比较熟悉的设计模式 ★★
单例模式:线程池、连接池
代理模式:AOP
工厂模式:IOC
模板模式:Spring
观察者模式(主题模式):spring常用
Spring:模板、工厂、单例、动态代理
多线程相关的设计模式 ★
1、Thread Pool模式(线程池)
这个模式就是为减少开启与关闭线程带来的开销。但是在使用的时候还是有很多需要注意的地方,例如死锁、线程泄漏之类的问题,导致可用线程越来越少。
2、Producer-Consumer模式(生产者-消费者)
在多线程编程中经常有一个生成数据以及一个消费数据的两个模块,这两者的速率通常是不相等的,所以为了避免等待,该模式中引入了通道的概念。
生产者将产生的数据放入一个队列,消费者则从队列中拿数据。并且生产者与消费者是完全解耦的。
3、Thread Specific Storage模式(线程特有存储)
通过每个线程独立储存数据来避免竞争。也是一种很好的无锁线程安全实现方式,但是要注意内存泄漏,当线程销毁之后,保证数据也能够被及时销毁。
在Java当中直接使用 ThreadLocal 来应用该模式。
4、观察者模式
观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
5、Pipeline模式(流水线)
在执行任务的时候,我们通常可以将任务分解成不同的阶段,在一些阶段当中或许只有一个线程参与,而在另一些阶段中则有多个线程进行参与。
我们可以通过配置文件的方式来配置我们的自定义流水线。
需要注意的是,流水线由于独立出很多资源,所以一定会有额外的开销,所以要注意管线的深度,保证不要得不偿失。
在始终只有一个线程参与的情况下,也可以将Pipeline看做是Serial Thread Confinement模式。
6、Two-phase Termination模式(两阶段终止)
在终止线程的时候我们或许会丢掉很多东西,比如一些状态,或者还有一些必要执行的东西还未执行完。倘若直接中断线程的话会引起很多问题,所以在终止线程之前我们或许需要等待这些操作。
例如在Java的标准库中,线程池就会等待目前仍在执行的线程,执行完全之后就会完全关闭线程池。后面的Thread Pool模式中也会用到。
改进版本中也有用多个通道进行消费,减少锁竞争,再者也可以用工作窃取的方式进行负载均衡。
单例模式,饿汉式、懒汉式如何实现?★★
保证一个类仅有一个对象,并提供一个它的全局访问点。
Controller 是单例的,IoC在程序执行时就创建好了对象;线程池。
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
饿汉式(Hungry)
私有构造器,提供方法获取唯一的实例
缺陷:效率问题,建好了不用浪费内存 =》 懒汉式,大部分架构都参考的设计模式,第一次使用时进行初始化。 (如集合第一次添加数据时)
DCL设计模式:双重检测锁模式!
懒汉式(LazyMan) =》多线程出问题 =》 DCL懒汉式 -- 双重检测锁模式 =》通过反射、序列化破坏单例模式 =》枚举式(EnumSingle)
线程的生命周期 (线程的状态转换)
Volatile 关键字作用
可用来修饰变量,多线程中
1. 线程间可见 2. 防止指令重排
但不能保证原子性:上锁
Synchronized 和 Volatile有什么区别?★★
1. volatile本质是告诉JVM当前变量在PC寄存器中的值是不确定的,需要从主存中读取,synchronized则是可以锁定变量操作,只有当前线程可以访问操作该变量,其他线程被阻塞住。
2. Volatile是轻量级的synchronized,因为它不会引起上下文的切换和调度,所以Volatile性能更好。
3. Volatile 只能修饰变量,synchronized 可以修饰方法,静态方法,代码块。
4. Volatile 不能保证原子性;而synchronized 可以保证原子性。
5. Volatile 不会造成线程阻塞,synchronized 可能会造成线程阻塞。(因为volatile只是将当前变量的值及时告知所有线程,而synchronized是锁定当前变量不让其它线程访问)。
6. Volatile 标记的变量不会被编译器优化,synchronized 的操作可以被编译器优化。
synchronized 和 lock 锁有什么区别?
1. synchronized是同步阻塞,采用的是悲观并发策略;
lock是同步非阻塞,采用的是乐观并发策略(底层基于volatile关键字和CAS算法实现)
2. sync 是关键字,lock 是 java.util.concurrent.Locks 是下的接口
3. sync 会自动释放锁,lock需要手动上锁、手动释放锁
4. lock 提供了更多的实现方法,而且 可响应中断
5. sync 是非公平锁,lock可自己设置
悲观锁和乐观锁的怎么实现?
悲观锁:select...for update是MySQL提供的实现悲观锁的方式。
悲观锁:上锁所有update扫描过的行
例如:select price from item where id=100 for update
此时在items表中,id为100的那条数据就被我们锁定了,其它的要执行select price from items where id=100 for update的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。MySQL有个问题是select...for update语句执行中所有扫描过的行都会被锁上,因此在MySQL中用悲观锁务必须确定走了索引,而不是全表扫描,否则将会将整个数据表锁住。
乐观锁:版本号机制
乐观锁相对悲观锁而言,它认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回错误信息,让用户决定如何去做。
利用数据版本号(version)机制是乐观锁最常用的一种实现方式。一般通过为数据库表增加一个数字类型的 “version” 字段,当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值+1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据,返回更新失败。
在 java 程序中怎么保证多线程的运行安全?
线程的安全性问题体现在:
原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
有序性:程序执行的顺序按照代码的先后顺序执行
导致原因:
缓存导致的可见性问题
线程切换带来的原子性问题
编译优化带来的有序性问题
解决办法:
synchronized、Lock:原子、可见、有序性。
volatile:可见、有序性。
常见状态码
200:成功
302:重定向
400:请求语法错误,不能被解析
403:权限不足 (服务器收到请求,但是拒绝提供服务)
404:找不到页面
500:服务求内部错误 (BUG)
503:服务器未响应 (项目已经起了,但是没有初始化好)
IO 流最关键的一步
(1)创建源
(2)选择流
(3)操作流
(4)关闭流(释放资源)
for 和 forEach (增强for) 的区别
本质区别
for 本质是循环,forEach本质是迭代。
for循环是js提出时就有的循环方法。forEach是ES5提出的,挂载在可迭代对象原型上的方法,例如Array Set Map。forEach是一个迭代器,负责遍历可迭代对象。遍历:指的对数据结构的每一个成员进行有规律的且为一次访问的行为。
迭代:迭代是递归的一种特殊形式,是迭代器提供的一种方法,默认情况下是按照一定顺序逐个访问数据结构成员。迭代也是一种遍历行为。
其他区别
1. 参数:forEach 的参数。
2. 中断循环:for循环可以中断,forEach不可中断。
3. 删除元素:forEach 删除自身元素,index不可被重置。在 forEach 中我们无法控制 index 的值,它只会无脑的自增直至大于数组的 length 跳出循环。
4. 起点:for 循环可以控制循环起点。
Mysql | JDBC
Statement 与 PreparedStatement 的异同?
.二者的关系:接口与子接口。
Statement 需要拼写sql语句,并且存在sql注入问题:用户名和密码错误时,也可以操作数据库。
Statement 无法操作Blob类型数据,实现批量插入(Batch)时,效率较低。
PreparedStatement 通过使用占位符(?) 预编译Sql语句,解决了这些问题。
JDBC执行过程
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.建立链接
String url = "jdbc:mysql://localhost:3306/atguigu";
Connection conn = DriverManager.getConnection(url, "root", "******");
//3.准备sql
String sql = "SELECT * FROM temp";
//4.创建命令发送器
PreparedStatement pes = conn.prepareStatement(sql);
//5.执行sql, 获取结果集 //查询为executeQuery
ResultSet res = pes.executeQuery();
//6.处理结果集 并展示内容
while (res.next()){
String name = res.getString("name"); //通过字段名获取
int age = res.getInt(2); //通过下标获取
Object gender = res.getObject(3); //使用getObject 建议
System.out.println("name:"+name+"age:"+age+"gender:"+gender);
}
事务的四大属性?(ACID属性) ★
(1)原子性(Automicity)
事务中的操作要么都发生,要么都不发生。 事务中的全部操作在数据库中是不可分割的(2)一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致。(如A给B转账100,总金额还是100)(3)隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。事物间相互不影响(4)持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
事物(并发)操作中可能出现的问题?(针对隔离性) ★
脏读:一个事务读取了另一个事务未提交数据;
不可重复读:同一个事务中前后两次读取同一条记录不一样。因为被其他事务修改了并且提交了。(同一事务中查询的数据应保持一致性)
幻读:一个事务读取了另一个事务新增、删除的记录情况,记录数不一样,像是出现幻觉。
事物的四种隔离级别是?(一致性和并发性:一致性越好,并发性越差) ★★
隔离级别 | 描述 |
---|---|
read-uncommitted | 允许A事务读取其他事务未提交和已提交的数据。会出现脏读、不可重复读、幻读问题 |
read-committed | 只允许A事务读取其他事务已提交的数据。可以避免脏读,但仍然会出现不可重复读、幻读问题 |
repeatable-read 默认! | 确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读。但是幻读问题仍然存在。 |
serializable | 确保事务可以从一个表中读取相同的行,相同的记录。在这个事务持续期间,禁止其他事务对该表执行插入、更新、删除操作。 |
Spring 默认 repatable - read
事物的传播行为 ★★
名称 | 含义 |
---|---|
REQUIRED(默认) | 有事物加入,没事物新建 |
REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
NESTED | 如当前存在事务,则在嵌套事务内执行。如当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
传统开发中数据库链接的问题?如何解决?实现方式? (你了解数据库连接池吗?)
使用数据库连接池
提高程序响应速度:连接池提前进行初始化,减少了数据库连接初始化和释放过程的时间开销(减少创建连接的时间)
资源重用:降低了资源的消耗(可以重复使用已经提供的链接)
链接数有限:便于链接的管理
实现方式:Druid 德鲁伊
连接池的原理
- 连接池维护着两个容器空闲池和活动池
- 空闲池用于存放未使用的连接,活动池用于存放正在使用的连接,活动池中的连接使用完之后要归还回空闲池
- 当Java程序需要连接时,先判断空闲池中是否有连接,如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用
- Java程序需要连接时,如果空闲池中没有连接了,则先判断活动池的连接数是否已经达到了最大连接数,如果未达到最大连接数,则会新创建一个连接放置到活动池,供Java程序使用
- 如果空闲池中没有连接了,活动池中的连接也已经达到了最大连接数,则不能新创建连接了,那么此时会判断是否等待超时,如果没有等待超时则需要等待活动池中的连接归还回空闲池
- 如果等待超时了,则可以采取多种处理方式,例如:直接抛出超时异常,或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用
数据库设计三大范式
1NF:原子性,确保表中每一列数据不可再分
2NF:确保列数据要跟主键关联,不能出现部分依赖。(每行数据唯一性)
3NF:保证每一列数据都要跟主键直接关联,不能出现传递依赖。(与外键关联的属性芳芳到关联表中)
设计表:故意添加冗余字段,保证读的效率,牺牲写的效率
limit 分页公式、总页数公式 (ES同样适用)
limit分页公式
(1)limit分页公式:curPage是当前第几页;pageSize是一页多少条记录
limit (curPage-1)*pageSize,pageSize
(2)用的地方:sql语句中
select * from student limit(curPage-1)*pageSize,pageSize;总页数公式
(1)总页数公式:totalRecord是总记录数;pageSize是一页分多少条记录
int totalPageNum = (totalRecord +pageSize - 1) / pageSize;
(2)用的地方:前台UI分页插件显示分页码
(3)查询总条数:totalRecord是总记录数
SELECT COUNT(*) FROM tablename
数据库的 trucate 和 delete 的区别?
delete和truncate都是对表的数据进行操作,delete是对表中的一条或多条数据进行操作,而truncate是对表中的所有数据记录进行操作,一般我们用delete叫做删除表,用truncate叫做截断表。
1、delete
不能回滚、空间不能回收、工作效率低
2、truncate
能回滚、空间能回收、工作效率高
tuncate、delete、drop
tuncate 删除所有表数据,删除后 从1开始,不能回滚
drop 表数据和表结构一起删除,
delete 删除表数据,可以回滚 (dml 数据操作语言)truncate、drop 是数据库定义语言(ddl 数据定义语言),不能回滚。
一张表里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把mysql重启,再insert一条记录,这条记录的ID是18还是15 ?
表的类型是InnoDB:不重启18,重启15;InnoDB表只把自增主键的最大ID记录到内存中
表的类型是MylSAM:18。MylSAM将自增主键的最大ID记录到数据文件中。
in 和 or,哪个效率比较高?
in,or会导致索引失效
JavaWeb
当你的浏览器中地址栏输入地址并回车的一瞬间到页面能够展示回来,经历了什么?
....
GET请求与POST请求的区别? — 表单(form标签)提交的方式 ★★
get:请求能够携带的参数较少,大小有限制,会在浏览器的URL地址栏显示数据类容,不安全,但高效。
数据在地址栏,不安全。
容量大小有限(4k)。
效率相对较高。
post:请求能够携带的参数没有限制,大小没有限制,不会在浏览器的URL地址栏显示数据内容,安全,但不高效。
数据在请求体,安全。
没有容量限制。
效率相对较低。
如果没有特别指定post请求,则默认为get请求。
请求转发和重定向的区别? ★
- 重定向会由浏览器发起新的请求,而请求转发不会发起新的请求。
- 重定向可以访问任意互联网资源,而请求转发只能访问本项目资源。
- 重定向不能访问本项目的WEB-INF内的资源,而请求转发可以访问本项目的WEB-INF内的资源。
- 发起重定向的资源和跳转到的目标资源没在同一次请求中,所以重定向不能在实现request域对象中的数据共享;而发起请求转发的资源和跳转到的目标资源在同一次请求中,所以请求转发可以实现request域对象中的数据共享。
- 请求转发url地址栏不变,而重定向会发生变化
拦截器(Interceptor)和过滤器(Filter)的区别?★
1. 拦截器基于反射机制,过滤器基于函数回调。
2. 拦截器不依赖servlet容器,过滤器依赖servlet容器(SpringMVC)。
3. 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4. 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
5. 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
过滤器 - 拦截器 - 执行 - 拦截器 - 过滤器
什么是 Thymeleaf?请写出常用指令及作用
服务器模板引擎, 它的主要作用是在静态页面上渲染显示动态数据,简化视图层(MVC)操作。th:text=”${xx}”:修改标签文本值 th:value=”${value}”:修改标签属性值
th:href=”@{/}”:动态获取应用名(上下文路径)
th:if=${}、th:switch\th:case :分支
th:each=”e:${list}”::迭代
th:fragment=”name”\th:insert=”xx:name” 代码片段
${param.key}:获取请求参数
${#requesdt.xxx}使用内置对象
servlet 生命周期
实例化:servlet容器创建servlet对象。默认创建servlet实例的时机:当我们发送servlet对应的请求时(在使用时创建)。类似单例模式中的懒加载方式。希望容器一旦启动,就自动创建servlet实例通过load-on-startup=1设置,正数数值越低优先级别越高,优先实例化
初始化:servlet实例一旦创建,就开始初始化一些参数配置,我们可以做一些参数配置,比如编码,可以在web.xml或注解中配置
就绪状态:当发送对应的servlet请求时,会调用service()方法,注意此时不会重新创建servlet实例,也不会调用init()方法
销毁状态:调用了destroy()方法后,当前servlet实例将会被标记为回收垃圾,会对servlet实例进行清除处理
Vue
第一次页面加载会触发哪几个钩子?
Day53.Vue框架: 绑定元素、条件 | 列表渲染、事件驱动、侦听属性、生命周期_焰火青年·的博客-CSDN博客
第一次初始化会触发beforeCreate(初始化前), created(初始化后), beforeMount(挂在前), mounted(挂载后)
说出几种vue当中的常用指令和它的用法?
绑定元素属性:v-bind:标签属性名=”数据模型key”\ :属性名=”数据模型key”\{{msg}}
双向绑定:v-model:value=”数据模型key”\ v-model=”数据模型key”
条件渲染:v-if=”?” | v-eles-if=”?”
列表渲染:v-for=”e in key”\{{e}}
事件驱动:v-on:事件名=”函数名”\@事件名=”函数名”
取消默认行为:Event.preventDefault 阻止事件冒泡:stop Propagation
属性侦听:watch:{侦听的函数}
v-if 与 v-show 的区别:
v-if是动态加载,v-show会全部加载出来
SSM框架
MyBatis
是什么:持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注sql本身。
1. 如何配置1对n?多对1? 在实体类增加List属性,封装n的数据
2. 两个重要的配置文件
映射文件:aseAttrInfoMapper.xml 配置方法的名称以及方法的实现
核心配置文件:mybatis-cfg.xml 配置的数据源以及读取那个配置映射文件
<mappers> <mapper resource=/com/atguigu/mapper/BaseAttrInfoMapper.xml /> <mapper resource=/com/atguigu/mapper/BaseAttrInfoMapper.xml /> <mapper package = "com.atguigu.mapper"/> </mappers>
mybatis 默认有个规定:
BaseAttrInfoMapper.java 文件与 BaseAttrInfoMapper.xml 通常在一起 (可以包扫描)BaseAttrInfoMapper.java 中定义接口,
BaseAttrInfoMapper.xml 是接口的实现类spring boot 整合mybatis-plus!
mybatis-plus:3. mybatis 在传递多个参数的时候,建议使用@Param()注解标识,用#{}填充占位符,
4. mybatis 的动态sql 标签
<if> : 进行条件的判断
<where>:自动添加where,处理 and、or 连接符
<trim>:在SQL语句前后添加前缀,后缀
<set>: 修改操作时出现的逗号问题
<choose> <when> <otherwise>:类似于switch,所有的条件中选择其一
<foreach>:循环遍历;可通过拼装SQL完成批量操作
5. 全注解开发可读性差,不容易维护,开发的时候建议写 xxx.xml
6. 有关于mybatis 的相关源码:底层源码用的cglib动态代理
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.java);7. Mybatis如何生成自增主键值
a. 实体类id上加入注解属性 @TableId(type = IdType.AUTO) (Pkys)
b. xml 在<insert>标签中使用 useGeneratedKeys 和 keyProperty 两个属性来获取自动生成的主键值
Mybatis 在项目中如何使用 ★★
1. 导入依赖
2. 配置文件 配置数据源
3. @MapperScan
4. 继承Mapper 接口
MyBatis-Plus 使用 ★
如何使用?
1、导入依赖,如果有注释掉之前mybatis的依赖
2、如果MyBatis plus有自定义mapper文件,地址配置的由mybatis-mapper-locations改为mybatis-plus.mapper-locations
3、给实体类加注解,建立表与实体类关系@TableName("order_info") 指定类绑定的数据库 (声明在类)
@TableId(type = IdType.ASSIGN_ID) 主键,配置主键策略
@TableField(fill = FieldFill.INSERT) 由MP实现自动填充
@Version MP实现乐观锁 ,还需要注册 OptimisticLockerInterceptor 乐观锁插件
@TableLogic 逻辑删除(软删除)
value = "" 未删除的值,默认值为0
delval = "" 删除后的值,默认值为14、Dao层继承BaseMapper<T>、Service接口继承IService<T>、
Service实现继承ServiceImpl<mapper, T>,AutoWhere注入即可调用方法5、条件构造器 QueryWrapper,分页插件 PaginationInterceptor
#{}和${}的区别是什么?为什么还要用${}? ★
1、${} 底层使用 Statement。Statement 需要拼写sql语句,并且存在sql注入问题
2、#{} 底层使用 PreparedStatement。PreparedStatement 通过使用占位符(?) 预编译Sql语句安全,速度快,避免了注入、字符串拼接。
在SQL语句中,数据库表的表名不确定,需要外部动态传入,此时不能使用#{},因为数据库不允许表名位置使用问号占位符,此时只能使用${}
排序字段,也需要用到 order by ${}
如何获取自增主键? ★★
1. 在<insert>标签中使用 useGeneratedKeys 和 keyProperty 两个属性来获取自动生成的主键值。
示例:
- <insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
- insert into names (name) values (#{name})
- </insert>
2. Mybatis-Plus : 不需要处理? 插入成功自动封装进实体类
实体类 主键字段ID中配置参数 @TableId (type = IdType.AUTO)
3.Dk-Mybatis:
通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Mapper 接口的工作原理是JDK动态代理
Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象 MappedProxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。
Mapper接口里的方法,是不能重载的,因为映射文件中使用 全限定名+方法名 的保存和寻找策略。
一个类的全限定名是将类全名的.全部替换为/,例如com/itheima/dao/IUserDao.xml
简述Mybatis提供的两级缓存,以及缓存的查找顺序?
缓存目的:减少对数据库的访问次数,提高查询效率,减少系统开销
一级缓存:默认自动开启,针对ton规格Session
二级缓存:默认关闭,针对同一个SqlSessionFactory(一个工厂内创建的所有Sqssion)
二级缓存的数据需要实现序列化,SqlSession关闭的时候,一级缓存的内容会放入二级缓存缓存失效的情况:执行了增伤改操作、提交时了事物、手动清空缓存
查询顺序:
- 先查询二级缓存、
- 二级缓存未命中,查询一级缓存
- 一级缓存未命中、查询数据库
- 查询数据库的结果,放入一级缓存
SqlSession关闭之前,以及缓存中的数据存入二级缓存
Mybatis的缓存机制?(一级、二级缓存) (装饰模式) ★
缓存机制只针对于sql查询语句,减少对硬盘(数据库)的访问次数(IO操作),提高查询效率,减少系统开销,解决了高并发系统的性能问题。
一级缓存:作用范围是同一个SqlSessio。默认开启。
二级缓存:作用范围是同一个SqlSessioinFactory,默认关闭。( (由这一个SqlSessionFactory) 创建的所有SqlSession可以共享二级缓存中的数据)。)
查询的顺序是:
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存。
如果一级缓存也没有命中,则查询数据库。
查询数据库的结果,会立刻放入一级缓存 (不会立刻放入二级缓存)SqlSession关闭的时候,一级缓存中的内容会被存入二级缓存
执行增删改操作、提交事务会清空缓存。
缓存底层采用了装饰模式设计模式。Cache是缓存的顶级接口
Cache内部有一个Map,存储缓存的信息;key为sql语句,value为查询结果
所有的装饰类中必须有一个Cache的引用,说明对谁进行装饰。(类似包装流内的节点流)
PerpetualCache 是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过 PerpetualCache 来操作缓存数据的。
PerpetualCache 根据调用者不同,来区分不同级别缓存。
- 一级缓存:由BaseExecutor调用PerpetualCache
- 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰
Mabatis底层原理? 四大对象
Mabatis底层有四大对象,它们之间有不同的分工。
Executor、StatementHandler、ParameterHandler、ResultSetHandler。
①Executor
执行器,由它来调度 StatementHandler。
②StatementHandler
使用数据库的 Statement 、PreparedStatement 执行操作
③ParameterHandler
处理SQL参数;比如查询的where条件、和insert、update的字段值的占位符绑定。
④ResultSetHandler(只针对查询操作)
处理结果集ResultSet的封装返回。将数据库字段的值赋给实体类对象的成员变量。
Mybatis底层四大步骤:
BaseExecutor 父类
SimpleExecutor 子类
Statement JDBC
查询
query()
doQuery()
executeQuery()
DML
update()
doUpdate()
executeUpdate()
第一步:获取数据库连接
Connection connection = getConnection(statementLog);
第二步:获取Statement、PreparedStatement
Statement stmt = handler.prepare(connection, transaction.getTimeout());
第三步:处理参数 (SELECT和DML),给SQL语句中的占位符赋值 ParameterHanlder
handler.parameterize(stmt);
第四步:执行操作
return handler.update(stmt);//insert update delete
return handler.query(stmt, resultHandler); //select 会涉及到结果集ResultHandler
Mybatis 结果集的映射方式有几种,并分别解释每种映射方式如何使用?
自动映射:数据库列名与Java类属性名相同时,会自动匹配 (可以通过起别名的方式)
手动映射:使用ResultMap,sql标签中指明resultMap=“xxx”,多表查询时会使用
驼峰命名:添加全局配置,mapUnderscoreToCamelCase设置为true,满足驼峰命名会自动映射
Spring (粘合剂)
谈谈你对Spring的理解
开源框架,粘合剂,两大核心思想:IOC、AOP;极大的简化了开发
什么是IOC?如何理解?★★★
回答一:
IOC (Inversion of Control) 控制反转。设计思想|设计模式,由容器将设计好的对象交给容器控制,而非对象内部直接new。将ew对象的控制权从应用的代码本身转移到了外部XML文件(IoC容器)。
对象之间解耦。利于后期维护和修改。
回答二:
IOC设计模式是抽象工厂模式的升级,抽象工厂是从工厂类中获取同一接口的不同实现,但耦合代码还是存在的。IOC将耦合代码移出去,通过配置XML文件的方式代替耦合的代码,在容器启动的时候,IOC就会根据配置的XML对象生成依赖对象并注入(DI),所以在使用IOC模式后,使抽象工厂的内部耦合代码转移到了外部XML配置文件,从而实现真正的代码解耦
什么是DI?
IoC是一种控制反转思想,DI是它的一种具体实现方式。
DI(Dependency Injection)依赖注入,Spring框架的核心之一。实现IOC的依赖注入策略,依赖对象通过注入进行创建,由IOC容器负责创建和注入。
注入:将依赖关系的类放在IOC容器中,IOC就会根据依赖的关系NEW对象的实例。比如MVC模式
结论:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。
什么是AOP?如何理解? ★★★
AOP (Aspect Oriented Programming) 面向切面编程。符合开闭原则
通过预编译方式和运行期间动态代理,在不修改源代码的情况下,给程序动态统一的添加功能的一种技术,简称AOP。是spring框架的一个重要内容,可以说是对OOP (面向对象编程) 的补充和完善。AOP底层原理是动态代理技术。(JDK、CGLIB)
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法 (共同方法解耦)。
Java提供2中动态代理的方式:JDK动态代理基于接口实现;cglib基于继承实现
Day69.Spring: 使用注解开发、代理模式、AOP、切面、通知、AOP对IOC的影响_焰火青年·的博客-CSDN博客
spring中,bean的生命周期 ★★★
前置处理器 后置处理器,还有很多扩展方法。
可以指定配置指定的初始化 和销毁方法 <init-method> <destroy-method>
注意,Bean是在ioc容器初始化时创建的
其中红框标记四个阶段的是基本阶段。
MyBeanPostProcessor是针对所有Bean的扩展阶段,需定义BeanPostProcessor实现类并配置。
未进行标记的是只针对当前Bean的阶段,需要当前Bean实现相应的接口
AOP会调BeanPostProcessor
Bean的作用域有哪些?
Singleton 单例的
Prototype 原型的
Request 请求域
Session 会话域
Spring中常用的设计模式 ★★
(1)代理模式——spring 中两种代理方式,若目标对象实现了若干接口,spring 使用jdk 的java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口,spring 使用 CGLIB 库生成目标类的子类。
(2)单例模式——在 spring 的配置文件中设置 bean 默认为单例模式。
(3)模板方式模式——用来解决代码重复的问题。
比如:RestTemplate、JmsTemplate、JpaTemplate
(4)工厂模式——在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例。
@Resource、@Autoware、@Value的区别
@Resource:Javax的注解 默认按照ByName注入
@Autoware:Spring 的注解 默认按照ByType注入
@Value(“${xxxx}”):从配置文件读取值
Spring 常用注解 ★★
Spring 如何进行全局异常处理?★★
@ControllerAdvice spring AOP面向切面编程,对Controller进行切面环绕。作用:全局异常处理、全局数据预处理、全局数据绑定
@ExceptionHandler (Exception.class) 异常拦截器(自定义异常处理器),需要结合@ControllerAdvice一起使用 统一异常处理
如何解决spring 循环依赖?Spring三级缓存?★
先解释什么是循环依赖:1. 自己auto自己 2. A auto B ,B auto A。
Spring 中使用三级缓存来解决。提前引用
在B注入属性A时,三级缓存(工厂池)会创建提前引用,放入二级缓存(半成品池中)
二级缓存,就可以解决循环依赖问题;三级是因为动态代理。
A初始化,需要初始化B,将A放入二级缓存 (提前引用);
B初始化,需要A,从二级缓存中取出。
A初始化完成之后,B中的A就是一个完整的对象了。
但是因为AOP 动态代理 实现会调用 BeanPostProcessor
请描述一下Spring 的事务管理
(1)声明式事务管理:@Transactional
(2)编程式事物管理:在代码中显式挪用 beginTransaction()、commit()、rollback()等事务治理相关的方法。Spring 对事物的编程式管理有基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理两种方式。
哪些情况会导致事物失效? ★
1. 非运行时异常不会回滚 所以要加参数 指定回滚异常类型
@Transactional(rollbackFor = Throwable.class)
Transactional 默认捕获 ERROR 或 RuntimeException 及其子类
2. 不是public方法
3. final 修饰的方法
4. 没有被Spring 管理
3. try...catch...进行了异常处理
4. 跨了线程的事务
SpringMVC
是什么: 基于spring的一个框架,实际上就是spring的一个模块,专门做web开发,是servlet的一个升级
简述SpringMVC 的工作原理(底层) ★★★
生活:主公向军师发起一个请求,军师调度各个部门,接收反馈执行流程。最终汇报结果?
- DispatcherServlet 接收请求
- DispatcherServlet 调用 HandlerMapping,将请求映射为HandlerExecutionChain(处理器执行链)
- DispatcherServlet 调用HandlerAdapter(处理器适配器),调用处理器执行Handler、拦截器方法,返回ModelAndView对象
- DispatcherServlet 调用 ViewResolver(视图解析器),将逻辑视图解析为物理视图,返回View对象
- DispatcherServlet 调用View根据Model模型数据进行视图渲染
- DispatcherServlet 响应
- 前端控制器(DisatcherServlet):接收请求,响应结果,返回可以是json,String等数据类型,也可以是页面(Model)。
- 处理器映射器(HandlerMapping):根据URL去查找处理器,一般通过xml配置或者注解进行查找。
- 处理器(Handler):就是controller控制器,由程序员编写。
- 处理器适配器(HandlerAdapter):可以将处理器包装成适配器,这样就可以支持多种类型的处理器。
- 视图解析器(ViewResovler):进行视图解析,返回view对象(常见的有JSP,FreeMark等)。
SpringMVC常用注解 ★★
@RequestMapping 映射请求路径
@GetMapping、PostMapping、@PutMapping、@DeleteMapping 处理对应类型请求
@RequestParam 将请求参数绑定到控制器方法参数上
@RequestBody 获取请求体中的数据,接收JSON数据 (底层杰克逊)
@RequestHeader 获取请求头中的数据
@CookiValue 获取请求中COokie数据
@PathVariable 映射URL绑定的占位符,绑定到xxx传参中,一般与@GetMapping一起使用
@ExceptionHandler 声明异常处理类
什么是跨域问题?如何解决?
为了防止跨站攻击,浏览器对于ajax请求的一种安全限制:只能是与当前页域名相同的路径,这能有效的阻止跨站攻击。
跨域问题:请求的域名、端口、协议不同都会引发跨域问题 (http和https)
解决跨域问题:
1. jsonp(杰森):最早的解决方式, 只支持 GET 请求
2. nginx:反向代理,但是 在nginx.conf 配置不易理解,语义不清,不方便后期维护
3. springMVC提供@CrossOrigin 注解
4. cors:跨域资源共享 (原理:预检请求,预检响应)
简述Spring声明式事务中@Transaction中常用的事务属性有哪些?
只读:readOnly
超时:timeout
回滚异常类型:rollbackFor
事物隔离级别:lsolation
什么是 Restful风格
RESTful架构,是目前非常流行的一种互联网软件架构。
- 网络上的所有事物都被抽象为资源
- 每个资源都有一个唯一的资源标识符
- 同一个资源具有多种表现形式(xml,json等)
- 对资源的各种操作不会改变资源标识符
- 所有的操作都是无状态的
如果一个架构符合REST原则,就称它为RESTful架构。
Tomcat 如何优化?
TomCat默认并发500,可以进行调整连接数和并发数;服务器带宽
server.xml,配置Connector、Executor设置
Linux
是什么:操作系统,常用于搭建服务器
目录结构
Linux一切皆文件,Linux中没有盘符的概念,使用一个/代表整个所有目录和文件的根目录。
/opt:这是给主机额外安装软件所摆放的目录。
/root:该目录为系统管理员,也称作超级权限者的用户主目录。
/home:存放普通用户的主目录,在Linux中每个用户都有一个自己的目录,
/usr(unix software resource):这是一个非常重要的目录,用户的很多应用程序和文件都放在这个目录下,类似与windows下的program files目录。
/usr/loca:这是另一个给主机额外安装软件所摆放的目录。一般是通过编译源码方式安装的程序。
/etc(等等,感觉应该是conf):所有的系统管理所需要的配置文件和子目录
Linux 常用命令 ★★
ls、ll:查看文件 (-a 查看隐藏)
vim、vi、cat:编写、查看文件内容
ps -ef | grep xxx:查看进程,kill pid 杀死进程
rm:删除文件
cp:复制文件
mv:移动、重命名文件
tar:解压文件
systemctl status (start、stop、restart) 查看、启动、停止、重启服务
ifconfig:查看网络状态
yum install:安装软件
Linux命令中的管道要使用什么符号?它是如何工作的?
答案:管道符号是“|“,表示前面命令的输出作为后面命令的输入。
Linux 如何查看日志?★
1. 命令格式: tail [必要参数] [选择参数] [文件]
2. cat -n filename |grep “关键字
3. vim、vi
Redis
是什么:Key-value,NoSQL数据库,通常作为缓存、分布式锁使用
特点:单线程,原子性,先进先出 (因为CPU不是Redis的瓶颈,完全基于内存),不需要各种锁的性能消耗。IO多路复用
1.完全基于内存 2.数据额结构简单(没有主外键之类的) 3.采用单线程
适用场景:缓存、分布式锁
为什么速度快?为什么原子性?为什么单线程?
为什么速度快:基于内存、单线程、IO复用 非阻塞IO
为什么原子性:Redis 是单线程的,操作是串行化的
为什么单线程:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽(因为要进行主从复制)。单线程可以保证原子性;
复制延时:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
Redis特点、Redis适用场景 ★
1、Redis 是单线程 + 多路IO复用技术 非阻塞IO
多路IO复用:使用单独一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
2、单线程保证其原子性
3、完全基于内存,读写速度快
4、持久化,集群部署
5、支持过期时间,支持事务,消息订阅
坑补充:Redis6后 支持IO多线程,并不意味着失去原子性。读取、输入为多线程,Redis内部仍然为单线程
什么是多路IO复用?★★
多个IO请求复用一个线程 -- 网络连接在中间层注册,等待事件通知
select、poll模型采用轮询机制
epoll基于事件驱动,引入了一个事件收集器(selector),Redis、Nginx都采用epoll,
Linux支持epoll,并提供了相应函数:epoll_create来创建事件收集器的内存空间,epoll_wait监听事件的就绪状态,epoll_ctl用于事件地址注册。
redis五种数据类型及其适用场景 (基础) ★★
1. string:kv缓存、分布式锁、计数器 (decr 自增)
2. hash:键值对集合,存储对象;方便属性修改 (hset) 修改时不反序列化其他字段
跟string的区别在于不用反序列化,直接修改某个字段。
3. list:有序列表;可存储粉丝列表、评论列表、消息队列 (支持范围查询lrange、如分页)
4. set:无序集合,自动去重;分布式全局去重;共同好友 (sinter 取交集)
5. zset:评分,按分数排序;排行榜
mysql 与 redis 如何保持数据一致性(防止脏读)?(延迟双删) ★★
延迟双删:在写操作之前删除Redis缓存,在写操作之后延迟一段时间再删除Redis缓存。
设置多少毫秒?!
考虑Mysql 主从复制。 从机同步 也需要时间!
Redis事务的特点 ★
1、单独的隔离操作 2、没有隔离级别的概念 3、不保证原子性
Redis事务的三个命令:Multi(排队)、Exec(执行)、discard(放弃)
理解悲观锁和乐观锁及其特点 ★
1、悲观锁:安全、性能低
认为每次拿数据的时候都会进行修改,所有的获取数据的操作都会上锁
2、乐观锁:安全、性能高
认为每次拿数据的时候都认为不会进行修改,所以不会上锁,而是在更新时判断一下是否有人进行更新数据,使用版本号机制。
乐观锁适用于多读的应用类型,可以提高吞吐量
Redis中通过WATCH key [key ...] 实现乐观锁的效果
允许多个线程都上乐观锁,偏重于读(谁先修改版本,谁执行成功)。非关系型数据库(Redis)
Redis事务和MySQL事务的区别
MySQL事务遵守严格的ACID特征,而Redis 设计更多的是追求简单与高性能,所以事务不会也没有必要受制于传统 ACID 的束缚。
- Redis 具备了一定的原子性,但不支持回滚,严格意义上无法保证原子性。
- Redis 不支持回滚,也就无法保证业务上的数据一致性。
- Redis 具备隔离性,但是没有隔离级别。
- Redis 通过一定策略可以保证持久性。Redis 是否具备持久化,取决于 Redis 的持久化模式比如RDB、AOF及其策略设置。
Redis 有哪些持久化机制?★★★
RDB:每隔一段时间将数据快照保存到硬盘上。
AOF:每隔一段时间将生成数据的命令保存到硬盘上,
Redis RDB持久化流程 ★★
问题一:子进程具体是如何持久化的?
Redis会单独创建(fork)一个子进程来进行持久化,先将内存中的数据集的快照写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件
优点:安全;
缺点:最后一次持久化后的数据可能丢失(五分钟一次持久化,期间异常关机了)。
问题二:什么是fork
- Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程.
- 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
问题3:什么是快照?
Snapshot;即写即拷(copy-on-write 写时复制)
拍摄快照是很快,和原来的内容是相同的,没有进行复制操作
后面当进行各种操作时,如果数据发生了变化,就复制变化的数据一份。快照中存储旧的数据。变化越多,复制操作越多。恢复快照时,直接执行快照数据即可。
Redis AOF 持久化流程 ★★
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
(1)客户端的请求写命令会被append追加到AOF缓冲区内;
(2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
(3)AOF文件大小超过重写策略(自动重写) 或手动重写(bgrewriteaof) 时,会对aof文件 rewrite重写,压缩AOF文件容量;
(4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
Redis RDB和AOF持久化的比较 ★
RDB的优点和缺点
优点:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用 (最后一次可能数据丢失)
- 节省磁盘空间 (快照)
- 恢复速度快 (存储的是数据,而非操作)
缺点:
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
AOF 的优缺点
优点:
- 备份机制更稳健,丢失数据概率更低。
- 可读的日志文本,通过操作AOF稳健,可以处理误操作。
缺点:
- 比起RDB占用更多的磁盘空间。
- 恢复备份速度要慢。
- 每次读写都同步的话,有一定的性能压力。
- 存在个别Bug,造成恢复不能。
到底该用RDB还是AOF
1. 如果对数据不敏感,可以选单独用RDB。
2. 如果对数据敏感,同时使用RDB和AOF
(AOF不要单独使用,只有AOF重写aof文件无法进行)
3. 如果只是做纯内存缓存,可以都不用。
Redis 的主从复制、哨兵原理 ★★★
主从与集群是两个不同的概念
主从:纵向扩容。解决备份,减少主机压力
集群:水平扩容。减少主机缓存压力。
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。优点:读写分离,性能扩展;容灾快速恢复,提高可用性。
1、一主二仆:主机同步数据,slave只读不可写,master宕机 slave原地待命;
2、薪火相传 (上下级):主机同步数据,主从机不可写,主从机宕机主机数据无法同步。
3、反客为主:
可以使用指令 slaveof no one 手动将从机变为主机。其他没有变为 master 的 slave 也需要重新手动指定 master(重新拜大哥)
4、哨兵模式(sentinel) (自动版的反客为主):
master宕机后,哨兵自动从slave中选举新的master,在master宕机再重新启动后自动建
立主从关系(原主机变从机)。
为保证效率,Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
redis - 哨兵 主从复制原理:
1. 从机发送复制请求 sync 异步命令
2. 全量复制:主机进行bgsave: 拍个快照把当前所有状态全都发过去
3. 异步增量复制:后续将写入或修改命令 发送给从机!所以网络宽带和内存的大小可能是redis的瓶颈。
Redis 集群 三主三从 ★★★
水平扩容。减少主机缓存压力。
Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
官方推荐使用6实例,3个主节点,3个从节点。当主节点宕机时,集群可以选举出对应的从节点成为新的主节点,继续对外服务,保证服务的高可用性。
注意:
1、当集群内一个Master以及其对应的Slave同时宕机,集群将无法提供服务。
2、当存活的主节点数小于总节点数的一半时,整个集群就无法提供服务了。
底层:
Redis 集群包含 16384 个插槽(hash slot)与 key进行运算:crc32 算法 获取到一个值,根据这个值决定在那一组节点上存放数据。
Redisson 分布式锁 ★★★
Redis 分布式锁,电商方式
1、setnx key value:容易出现死锁;需要清空锁
2、设置锁并加默认的过期时间:多线程误删锁
expire key timeout 不具备原子性!
setex key timeout value --- key 与 过期时间具有原子性!redis 2.6.12 版本以后:setnx + setex 命令整合
set key value ex/px timeout nx/xx; 具有原子性
NX :键不存在时,才对键进行设置操作;XX :键已经存在时,才对键进行设置操作。3、设置一个uuid 防止误删锁:删除锁缺乏原子性
4、使用lua 脚本保证删除锁具有原子性:集群情况下锁不住资源
// 这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除 String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript defaultRedisScript = new DefaultRedisScript<>(); defaultRedisScript.setResultType(Long.class); defaultRedisScript.setScriptText(scriptText); // 第一个参数:defaultRedisScript 第二个参数:键值 第三个参数:口令串 this.redisTemplate.execute(defaultRedisScript, Arrays.asList("lock"),uuid);
5、redisson:Redisson 提供了使用Redis的最简单和最便捷的方法,Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
优化:
AOP + 分布式锁,减少代码冗余
根据事物注解联想
单节点:redisson - lock
集群:redisson - redLock
Redisson 的看门狗机制? ★
指定解锁时间带来的问题:如果业务执行的时间超过指定时间,redis会自动解锁;当前业务执行完后又要解锁,可能会解锁到另一条线程加的锁,所以自己指定的解锁时间一定大于业务执行的时间!
看门狗原理
1、如果我们指定了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们制定的时间,不会自动续期;
2、如果我们未指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动再续成30秒;
自动续期时间:internalLockLeaseTime 【看门狗时间 30s】 / 3, 10s
缓存击穿,缓存穿透,缓存雪崩 (缓存的过程中有没有遇到什么问题) ★★★ ★
缓存穿透:查询一个不存在的数据,由于缓存不存在,直接去查询数据库,但是数据库也无此记录,所以我们没有将null写入缓存。但这导致每次请求都会访问数据库,别人可以利用不存在的 key频繁攻击我们的应用。
追问:UUID随机怎么办?
解决方案:缓存null对象 或 使用布隆过滤(防止uuid随机穿透)
缓存雪崩:Redis宕机;或在设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:
1. 事前:redis高可用,主从+哨兵;事中:本地缓存ehcache+限流组件Hystrix;防止宕机;事后:redis持久化 快速恢复缓存数据
2. 原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存击穿:是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到DB,我们称为缓存击穿。
解决方案:在分布式的环境下,应使用分布式锁来解决,分布式锁的实现方案有多种,比如使用Redis的setnx、使用Zookeeper的临时顺序节点等来实现
布隆过滤器优缺点? ★★
底层就是一个二进制数据 默认0,能够判断一个元素在集合中是否存在,
一个元素一定不存在 或者 可能存在,存在一定的误判率{可通过代码调节}
优点:
1. 保密性好:存储的数据是0或1
2. 空间效率和查询时间 O(M) M:数组长度,O(K) K:hash 函数个数
3. 占用空间小
缺点:
误判率、删除困难
原理
存储数据:
1. 通过k个kash 函数计算hash值,对应二进制数组下标
2. 将二进制数组中的 0 改为 1
获取数据:
1. 通过k个kash 函数计算hash值,对应二进制数组下标
2. 如果对应的下标都是1 说明可能存在,如果是0说明一定不存在
原因:可能发生 hash碰撞 (冲突)
误判率和什么有关:hash函数个数、数据长度、数据规模
如何使用:
1. redisson 整合好了布隆过滤器2. 设置一些参数,在redis 中
3. 初始化设置:
数据规模,误判率!4. 保存sku的时候,直接将skuId 添加到布隆过滤器!
5. 在获取数据时判断布隆过滤器中是否有该商品!
如何保证Redis中的数据都是热点数据? ★★★
我们可以设置Redis内存淘汰策略
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
数据淘汰策略:
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
说说Redis 哈希槽的概念?
redis集群,通过hash决定放入哪个节点中,类似HashMap
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群共有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽 (三个节点,16384/3)。
Redis 在项目中的应用
Redis一般来说在项目中有几方面的应用
1. 作为缓存,将热点数据进行缓存,减少和数据库的交互,提高系统的效率
2. 作为分布式锁的解决方案,解决缓存击穿等问题
3. 作为消息队列,使用Redis的发布订阅功能进行消息的发布和订阅
具体的使用场景要结合项目去说,比如说项目中有哪些场景用到Redis来作为缓存,以及分布式锁等等。
为什么不用RabbitMQ做消息队列? 因为RabbitMQ 接收消息是抢的
Redis 发布与订阅模式
由于我们的秒杀服务时集群部署service-activity的,我们面临一个问题?RabbitMQ 如何实现对同一个应用的多个节点进行广播呢?
RabbitMQ 只能对绑定到交换机上面的不同队列实现广播,对于同一队列的消费者他们存在竞争关系,同一个消息只会被同一个队列下其中一个消费者接收,达不到广播效果;
我们目前的需求是定时任务发送消息,我们将秒杀商品导入缓存,同事更新集群的状态位,既然RabbitMQ 达不到广播的效果,我们就放弃吗?当然不是,我们很容易就想到一种解决方案,通过redis的发布订阅模式来通知其他兄弟节点,这不问题就解决了吗?
Redis 与 Memcache区别:
Redis 与 Memcache区别:
1、Memcache 数据只在缓存中,Redis 可以进行持久化操作 (RDB、AOF)
2、Memcache 只支持字符串类型,Redis多数据类型(string、list、set、hash、zset...)
3、命令机制:多线程加锁。Redis 单线程 + 多路io复用
Maven
自动化构建(build)工具,专注服务于Java平台的项目构建和依赖管理
功能:依赖,继承,聚合 {packaging - pom,jar,war}
仓库:依赖jar ---> 本地仓库 ---> [私服{自己搭建的服务器}] ---> 远程中央仓库下载
settings.xml,一劳永逸
<localRepository>D:\repository-spring</localRepository>
Maven的坐标
[1]group Id(分组名):公司或组织的域名倒序+当前项目名称
[2]artifact Id(构建名):当前项目的模块名称
[3]version(版本):当前模块的版本
[4]packaging:项目类型
<exclusions>排除依赖
Git
是什么:分布式版本控制系统
1、问题:公共类或公共方法,两人同时修改同一个文件,一前一后提交就会报冲突错误。
解决办法:IDE一般会对比本地文件和远程分支文件,远程分支上文件的内容手工修改到本地文件,然后再提交冲突文件与远程分支文件一致,这样才可以消除冲突,然后再提交自己修改的部分。特别要注意下,修改本地冲突文件使其与远程仓库的文件保持一致后,需要提交后才能消除冲突,否则无法继续提交。必要时可与同事交流,消除冲突。
(1) 通过git stash命令,把工作区的修改提交到栈区;
目的:是保存工作区的修改; 通过git
(2) pull命令,拉取远程分支上的代码并合并到本地分支;
目的:消除冲突; 通过git stash
(3)pop命令,把保存在栈区的修改部分合并到最新的工作空间中;
2.问题:git fetch和git pull命令之间的区别
git fetch branch是把名为branch的远程分支拉取到本地;
而git pull branch是在fetch的基础上,把branch分支与当前分支进行merge;因此pull = fetch + merge。
3.git的整个原理图
Git的常用命令 ★
git init 初始化本地库
git add 文件名 添加到暂存区
git commit 提交到本地库
git checkout 切换分支
git branch -a
git clone 远程地址 克隆
git push 分支 推送到远程库
git pull 从远程库拉取
如何解决冲突问题?★★★ ★
git stash 将代码存入get栈,隐藏起来
git pull 拉取代码
git stash pop 恢复之前缓存的工作目录和同事协商,解决冲突。即可
在push 之前,先pull拉取最新代码!
git 为什么要先commit,然后pull,最后再push?而不是commit然后直接push?
答:这个先 commit 再 pull 最后再push 的情况就是为了应对多人合并开发的情况,
1.commit 是为了告诉 git 我这次提交改了哪些东西,不然你只是改了但是 git 不知道你改了,也就无从判断比较;
2.pull是为了本地 commit 和远程commit 的对比记录,git 是按照文件的行数操作进行对比的,如果同时操作了某文件的同一行那么就会产生冲突,git 也会把这个冲突给标记出来,这个时候就需要先把和你冲突的那个人拉过来问问保留谁的代码,然后在 git add && git commit && git pull 这三连,再次 pull 一次是为了防止再你们协商的时候另一个人给又提交了一版东西,如果真发生了那流程重复一遍,通常没有冲突的时候(也就是没有修改同一行)就直接给你合并了,不会把你的代码给覆盖掉
Dubbo RPC 远程调用
Dubbo 是阿里巴巴开源的一个基于 Java 的 RPC 框架。默认使用dubbo协议
Dubbo提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现 (注册中心Zookeeper)。Dubbo官方推荐 使用Zookeeper作为服务注册中心。
先说明角色
节点 | 角色说明 |
---|---|
Consumer | 需要调用远程服务的服务消费方 |
Registry | 注册中心 |
Provider | 服务提供方 |
Container | 服务运行的容器 |
Monitor | 监控中心 |
调用关系说明 ★★
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。(长链接,心跳机制)
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败 (重试次数默认两次),再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
RPC 是什么
RPC (Remote Procedure Call),远程过程调用,它相对应的是本地过程调用。
RPC 对比的是本地过程调用,是用来作为分布式系统之间的通信,它可以用 HTTP 来传输,也可以基于 TCP 自定义协议传输。
TCP 和 HTTP的区别?
http协议是应用层协议,TCP(使用三次握手四次挥手策略) 处于传输层
http的任务是与服务器交换信息,它不管怎么连到服务器和保证数据正确的事情。而tcp的任务是保证连接的可靠,它只管连接,它不管连接后要传什么数据。
http协议是建立在tcp之上的,http是无状态的短链接,而tcp是有状态的长链接。
您应该问TCP和UDP的区别?RPC 和 HTTP 就不是一个层级的东西,没有可比性
HTTP:超文本传输协议 TCP:传输控制协议
SpringCloud 与 Dubbo的区别?
1. Dubbo + zk 生态没有 Cloud 好,很多组件需要使用别人的。版本兼容问题
2. Dubbo RPC远程过程调用,实体类需要序列化。;OpenFeign 使用HTTP 协议+动态代理。
3. Dubbo 相对来说更便宜,Dubbo更贵。
4. Dubbo 性能好,Feign灵活性好
缺点:springBoot 与 dubbo 的版本一定要确认好!调试匹配很麻烦;实体类需要实现序列化
两者的生态对比:Dubbo
Dubbo | Feign(Http调用) | |
---|---|---|
传输协议 | TCP | TCP |
开发语言 | java | 不限 |
性能 | 好 | 一般 |
灵活性 | 一般 | 好 |
Nginx
是什么:高性能的HTTP和反向代理web服务器,
特点:
1.占有内存少,并发能力强(十分强大)。
作用:反向代理、负载均衡、动静分离、统一访问入口
什么是负载均衡?
在负权沉重时由多台服务器分担负载,我们可以将服务器集群部署,然后由负载均衡服务器通过负载均衡策略将用户的请求分发到不同的服务器。来提高网站、应用、数据库或其他服务的性能以及可靠性。
反向代理的策略
Nginx默认匹配策略是轮询!Dubbo框架默认匹配策略是随机访问!
轮询(默认)、weight (加权轮询)、ip_hash(固定)、 fair(根据相应时间)
你对反向代理的理解?
我们的GateWay网关是集群的,但是我们访问需要一个统一的请求地址,就把同一个地址映射到IP上
Nginx 工作原理 ★
2-3 Nginx优势多路IO复用_哔哩哔哩_bilibili
Nginx 同redis类似都采用了 IO多路复用机制 ,每个worker都是一个独立的进程,但每个进程里只有一个主线程,通过 异步非阻塞 的方式来处理请求, 即使是千上万个请求也不在话下。每个worker的线程可以把一个cpu的性能发挥到极致。所以worker数量和服务器的cpu数相等是最为适宜的。设少了会浪费cpu,设多了会造成cpu频繁切换上下文带来的损耗。
异步非阻塞。io多路复用
如何设置权重?
后面加一个关键字 weight
Nginx 权重:server 192.000... weight=1 增加被访问的概率
向Nginx发送请求,占用了woker的几个连接数?
动态资源请求:占用四个连接数。静态资源请求:占用两个连接数。
因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接.
nginx有一个master,有四个woker,每个woker支持最大的连接数1024,支持的最大并发数是多少?
总连接数:4*1024 = 4096
静态请求需要两个连接数 = 4096/2 = 2048
动态请求需要四个连接数 = 4096/4 = 1024
为什么Nginx 在Linux系统上效率高 (IO多路复用 epoll) ★
因为Nginx 使用io多路复用 epoll模型,而Linux支持epoll,提供了相应函数epoll_create来声明事件收集器的内存空间
SpringBoot (脚手架)
对Spring框架进行了进一步的封装,新一代JavaEE的开发标准。
简化配置、自动装配,开箱即用,约定大于配置! 习惯优于配置
谈谈你对Spring Boot的理解? (优点) ★
Spring Boot是Spring项目中的一个子工程,和Spring框架 同属于spring的产品。其最主要作用就是帮助开发人员快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让开发人员关注业务而非配置。
优点:独立运行、简化配置、自动配置和无需部署war文件。简化开发,提高整体生产力
SpringBoot 的核心注解?它主要由哪几个注解组成的 ★★★
SpringBoot 的核心注解是@SpringBootApplication(),它也是启动类使用的注解,主要包含了 3 个注解:
@SpringBootConfiguration:它组合了 @Configuration 注解,声明配置类。
@EnableAutoConfiguration:具有打开自动配置的功能,也可以关闭某个自动配置的选项。
@ComponentScan:用于Spring组件扫描,自动注入。 当前启动类 所在包及其子包下的Spring注解。
SpringBoot 自动装配的原理是什么?★★★
SpringBoot启动的时候会扫描@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中所有的自动配置类(128个),并对其进行加载,而这些自动配置类的类名都是以AutoConfiguration结尾来命名的,它实际上就是一个javaConfig形式的Spring容器配置类,它们都有一个@EnableConfigurationPerperties的注解,通过这个注解启动XXXProperties命名的类去加载全局配置中的属性,如server.port,而XXXProperties通过@ConfigurationProperties注解将全局配置文件中的属性与自己的属性进行绑定。
Spring Boot 自动配置原理是什么?
@EnableAutoConfiguration注解、 @Configuration注解和 @ConditionalOnClass注解组成了Spring Boot自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。具体是通过maven读取每个starter中的spring.factories文件,该文件配置了所有需要被创建在spring容器中的bean。首先要激活@EnableAutoConfiguration注解,会扫描项目中引入的依赖,判断核心的类是否存在,如果存在就载入这个类,128个自动装配类。
jdbc redis mongo nacos es 等等。
简单来说,Spring Boot通过@EnableAutoConfiguration注解开启自动配置,对jar包下的spring.factories文件进行扫描,这个文件中包含了可以进行自动配置的类,当满足@Condition注解指定的条件时,便在依赖的支持下进行实例化,注册到Spring容器中。
SpringBoot 如何关闭数据源(排除)自动配置?★
核心配置类@SpringBootApplication 注解加入参数 exclude
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
SpringBoot 配置加载顺序? ★★
1.properties文件 2.YAML文件 3.系统环境变量 4.命令行参数
生效优先级:命令行参数 > 系统环境变量 > YAML文件 > properties文件
application.properties和bootstrap.properties有何区别 ?
bootstrap比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效, 而且boostrap 里面的属性不能被覆盖;
application用于 spring boot 项目的自动化配置。在项目初始化时,要保证先从配置中心(Nacos)进行配置拉取,拉取配置之后,才能保证项目的正常启动,
Spring Boot 的配置文件有哪几种格式?它们有什么区别? (yml配置文件) ★
主要有.properties 和 .yml格式,它们的区别主要是书写格式不同。另外,.yml 格式不支持 @PropertySource 注解导入配置。
YAML 是一种可读的数据序列化语言,它通常用于配置文件。
优点:配置有序,简洁方便。支持数组,数组中的元素可以是基本数据类型或者对象
开启SpringBoot特性有哪几种方式?
1、继承spring-boot-starter-parent项目
2、导入spring-boot-dependencies项目依赖
8. Spring Boot 有哪几种读取配置的方式?
使用@Value注解加载单个属性值
使用@ConfigurationProperties注解可以加载一组属性的值,针对于要加载的属性过多的情况,比@Value注解更加简洁
9. Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
Spring Boot 支持 Java Util Logging, Log4j2, Logback 作为日志框架,如果使用 Starters 启动器,Spring Boot 将使用 Logback 作为默认日志框架,推荐的日志框架是Log4j2。
10. Spring Boot 可以兼容老 Spring 项目吗?
可以兼容,使用 @ImportResource 注解导入老 Spring 项目配置文件。
12. 什么是 JavaConfig?
JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯 Java 方法,有助于避免使用 XML 配置。
22. 如何在自定义端口上运行 Spring Boot 应用程序
可以在 application.properties 配置文件中指定端口,比如server.port = 8090
23. 如何实现 Spring Boot 应用程序的安全性?
为了实现 Spring Boot 的安全性,可以使用 spring-boot-starter-security 依赖,添加安全配置和重写WebSecurityConfigurerAdapter 配置类的方法。
RabbitMQ
消息队列(MQ)全称为Message Queue, 是一种应用程序对应用程序的通信方法。用于软件之间通信的中间件(消息中间件)。消费并不需要确保提供方存在,实现了服务之间的高度解耦。
队列的主要作用是消除高并发访问高峰,加快网站的响应速度。异步、解耦、消峰填谷。
6种模式:简单模式,work模式 ,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;暂不作介绍);
RabbitMQ提供了5种模式:简单模式,work模式 ,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式。
如何保证消息不丢失?(消息投递可靠性) ★★★
一、开启事务,效率低不常用
二、消息确认机制,常用
1. 保证消息持久化:
Exchange:声明exchange时设置持久化(durable = true)并且不自动删除(autoDelete = false)
Queue:声明queue时设置持久化(durable = true)并且不自动删除(autoDelete = false)
message:消息默认持久化的,发送消息时通过设置deliveryMode=2持久化消息
2. 发送确认:发送消息的时候,rabbitmq显式告知我们消息是否已成功发送。
配置文件中设置:
publisher-confirms-type: correlated // 交换机的确认
publisher-returns: true // 队列的确认消息发送确认类实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback 接口
未到交换机或队列就会执行重写方法
如果消息未到达交换机或队列,采用重试机制 + Mq消息表
3. 手动消费确认:
消费者处理成功:channel.basicAck
消费者处理失败:channel.basicnack配置文件中设置:
listener:
simple:
acknowledge-mode: manual
prefetch: 1RabbitMQ提供transaction事务和confirm模式来确保生产者不丢消息;
confirm模式用的居多:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;
rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;
消息重试机制 ★★★
消息发送确认类实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback 接口
消息未到交换机会执行confirm();消息未到队列会执行returnedMessage()
1. 实体类实现CorrelationData,封装了交换机、路由键、消息、重试次数,Id作为key
2. 发送消息时候将CorrelationData 写入Redis,Id作为key;
3. 发送消息时携带CorrelationData,否则获取不到Id爆异常
4. 如果消息未到交换机或队列,根据Id查询缓存获取CorrelationData,
5. 判断重试次数,次数++重新写入缓存
如何防止消息重复消费?(如何保证消息幂等性) ★★★
幂等性:相同的消息只处理一次
1. 使用数据(业务字段)方式,通过消息表记录消费状态
2. 使用redis,setnx 模式,类似分布式锁;setnx key value,key 不存在时才设置value。
监听到消息后,设置setnx 返回Boolean判断是否存储成功。
如果消费失败怎么办? 删除缓存key
1.设置value状态、value 0或1判断状态; 2.删除缓存key
如何发送延迟消息?延迟消息如何取消? (定时取消订单)
1. 死信队列 (了解)
死信交换机就是一种普通的交换机。
有两个交换机:普通交换机 死信交换机
有两个队列:队列1:设置延迟时间;队列2:真正要监听的队列特点:队列特点先进先出,无法根据时间先后顺序消费消息。
2. 延迟插件 x-delayed-message
可按照时间先后顺序消费消息
总是会走returnedMessage方法() 不会使用一个单独的队列来存储消息!
在交换机中设置的!
一个交换机一个队列:延迟消息如何取消?
不取消!
通过业务字段,取消预约后做一个标记,消费的时候判断这个标记判断是否消费。
如何解决消息积压问题
提高消费能力!
1. 多开启一些消费者;
2. 修改架构,路由模式 改为 topic 通配符模式(白天开新的节点去消费,晚上再去同步更新一下那些数据?)
2. 如果是代码有问题。先临时多启动几个消息队列,定位原因把它解决掉,晚上改好了BUG再去同步更新。
如何保证消息的顺序性 ★★
a. 什么时候会发生?
1.一个队列有多个消费者去消费;由于消费者的消费能力不同,导致消费顺序错误2.一个队列对应一个消费者,但消费者内部有多线程操作,可能发生消费顺序错误
b. 解决方案1. 一个队列对应一个消费者
2.在消费者内部做一个队列进行排序
如何使用RabbitMQ解决分布式事务? ★
分布式事务:不同的服务操作不同的数据源(库或表),保证数据一致性的问题。
解决:采用RabbitMQ消息最终一致性的解决方案,解决分布式事务问题。
分布式事务场景:
1、电商项目中的商品库和ES库数据同步问题。
2、电商项目中:支付----à订单---à库存,一系列操作,进行状态更改等。
在互联网应用中,基本都会有用户注册的功能。在注册的同时,我们会做出如下操作:
收集用户录入信息,保存到数据库向用户的手机或邮箱发送验证码等等…
如果是传统的集中式架构,实现这个功能非常简单:开启一个本地事务,往本地数据库中插入一条用户数据,发送验证码,提交事物。
但是在分布式架构中,用户和发送验证码是两个独立的服务,它们都有各自的数据库,那么就不能通过本地事物保证操作的原子性。这时我们就需要用到 RabbitMQ(消息队列)来为我们实现这个需求。
在用户进行注册操作的时候,我们为该操作创建一条消息,当用户信息保存成功时,把这条消息发送到消息队列。验证码系统会监听消息,一旦接受到消息,就会给该用户发送验证码。
Rabbit 应用场景 ★★★
1. 商品上架:后台管理系统上下架按钮 -》发送一个msg ---> service-list 监听消息 调用list模块upperGoods
2. 取消订单:下单的时候有个过期时间 expireTime 给的是24小时rabbit发送一个延迟消息:orderId,到了消息出队时:监听这个消息,进而判断这个订单的支付状态
3. 分布式事务:
XA 两阶段提交协议
TCC
seata 强一致性
mq 弱一致性 数据最终一致性!
场景:
订单: 成功
支付: 成功
库存: 失败 --- 成功!
记录当前那个订单减库存失败;后续补货 采用mq 形式 发送一个消息,通知订单模块,支付模块。 更新状态!
rocketMQ 相关问题: 顺序消费、重复消费、消息丢失
一文理清RocketMQ顺序消费、重复消费、消息丢失问题_Darren i的博客-CSDN博客_rocketmq消息重复消费
SpringCloud
SpringCloud 是 基于SpringBoot 实现的!
分布式一定是集群的(防止单点故障)。 而集群并不一定就是分布式的。
是什么:微服务系统架构的一站式解决方案 Netflix
微服务、分布式概念、微服务架构
注册中心:Eureka
负载均衡:Ribbon
声明式调用远程方法:OpenFeign
熔断、降级、监控:Hystrix
网关:Gateway
链路跟踪:Sleuth
服务注册和配置中心:Spring Cloud Alibaba Nacos
熔断、降级、限流:Spring Cloud Alibaba Sentinel
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、默认使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会在Eureka Server发送心跳(默认周期30秒,zookeeper为10秒)。如果Eureka Server在多个心跳周期内没有收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移出(默认90秒)
分布式 微服务架构的四个核心问题:
【狂神说Java】SpringBoot最新教程IDEA版通俗易懂_哔哩哔哩_bilibili
1、这么多服务,客户端如何去访问?
2、这么多服务,服务之间如何进行通信?
3、这么多服务,如何有效治理?
4、服务挂了宕机了,怎么办?
架构技术选型:三套解决方案!
1、SpriongCloud,微服务框架一站式解决方案!
新一代Api网关 Gateway 网关 (zuul 以前的)
OpenFeign --> HttpClient -->HTTP的通信方式,声明式调用远程方法 同步并阻塞
注册与发现 Nacos、Eureka
熔断机制 Hystrix
2018年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节
2、Apache Dubbo zookeeper,半自动,需要整合别人的
API网关:没有!要么找第三方组件,要么自己实现
Dubbo (RPC远程过程调用) RPC通信框架 使用二进制传输 异步非阻塞
服务注册与发现,zookeeper:动物园管理者 (Hadoop,Hive)
熔断机制:没有 借助了Hystrix
缺点:springBoot 与 dubbo 的版本一定要确认好!调试匹配很麻烦;实体类需要实现序列化
3、SpringCloud Alibaba 一站式解决方案!
服务注册和配置中心:Nacos (Nacos配置中心,本质是将配置写在数据库里)
熔断、降级、限流:Sentinel
Nacos 与 Eureka区别 ★★
1. Nacos可作为配置中心。
2. 心跳检测机制时间间隔
Eureka:client 每隔 30秒 向Eureka上报健康状态;90秒没收到会从注册表中剔除;再通过60秒下限。nacos:client 每隔 5秒 向nacos 上报健康状态;nacos15秒为不健康;30秒将实例删除。
3. CAP理论:C一致性,A高可用,P分区容错性
eureka只支持AP
nacos支持CP和AP两种
Nacos作为配置中心,本质是将配置写在数据库里
分布式架构有什么好处?
1. 重复功能或模块抽取为服务,提高开发效率。
2. 可重用性高。
3. 可维护性高。
模块化、组件化程度更高,让项目更容易开发、维护和分工。也是高内聚、低耦合的一种体现。分布式系统中的每一个组件可以单独部署到一个Tomcat服务器上,独占软硬件资源,在必要的时候还可以将某一个组件配置集群,所以分布式架构能够提升性能。
什么是微服务? (优缺点)
模块化,功能化!解决高访问高并发
微服务架构是一种架构模式,各模块独立开发、独立部署、独立运行。进程间的访问。架构要考虑可靠性、可用性、高性能。
其实和 SOA 架构类似,微服务是在 SOA (分布式架构)上做的升华,“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
答案二
简单来说,微服务架构风格[1]是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级(restful风格)通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。
优点:
1. 服务拆分粒度更细,有利于提高开发效率,工作分配。
2. 可以针对不同服务制定对应的优化方案。
3. 适用于互联网时代,产品迭代周期更短。
4. 利于维护。
5. 利于扩展。
缺点:
1.. 粒度太细导致服务太多,维护成本高 (服务器多,集群部署)。
2. 分布式系统开发的技术成本高,对团队的挑战大。
如何解决分布式事务问题 ★★
本地事物:由内存总线和数据库的链接保证本地事物原子性,但是分布式情况下,由于网络的不可靠性,.会产生分布式事务问题;
所以我们需要一个协调器,去统一各个节点下的状态(要么同时成功要么同时失败);在互联网业务场景性能要求>一致性要求,在产生失败的情况其他节点要么回退要么重试,这两种方案都可以采用MQ 去保证最终一致性。(异步、解耦、堆积能力、支持重试->死信队列);
用本地事物去保证,写入一张消息表,再通过定时任务不断轮询消息表进行重试,达到上限后人工兜底。
最好的方案还是在做服务拆分时,把原子性的操作放在单进程、但数据库去执行,用本地事物保证ACID。
【项目亮点】我在生产环境到底如何解决分布式事务!极端情况挑战与兜底!_哔哩哔哩_bilibili
1. 基于XA协议的两段式提交(2PC) - 强一致性
引入两个角色:协调者(Coordinator)来统一掌控所有 参与者(Participant)
分为两个阶段:
(1)投票阶段(voting phase):参与者将操作结果通知协调者 (举例开会发邮件,需要回复)
(2)提交阶段(commit phase):收到参与者的通知后,协调者再向参与者发出通知,根据反馈情况决定各参与者是否要提交还是回滚;
缺点:一个模块出现问题(没有反馈),所有模块都会阻塞。2PC遵循强一致性,效率很低2. 代码补偿事务(TCC) - 最终一致性
TCC是Try ( 尝试 ) — Confirm(确认) — Cancel ( 取消 ) 的简称:
3. 本地消息表(异步确保)- 最终一致性
订单系统新增一条消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到 MQ,库存系统去消费 MQ。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
发生异常不会回滚,而会存储到消息表中
4. MQ 事务消息有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
定时任务发送流程:发送half message(半消息),执行本地事务,发送事务执行结果
定时任务回查流程:MQ服务器回查本地事务,发送事务执行结果
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 目前主流MQ中只有RocketMQ支持事务消息(需要买阿里的服务)。
5. Seata
Seata 是阿里开源的一个分布式事务框架,能够让大家在操作分布式事务时,像操作本地事务一样简单。一个注解搞定分布式事务。
对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
高性能:减少分布式事务解决方案所带来的性能消耗
介绍一下 SpringCloud 中的服务熔断机制
微服务系统中由于调用链很长,所以一处出现问题或超时,会逐渐蔓延到整个系统。
为了避免这样的问题,熔断机制要求给被调用的方法准备备用方案,一旦目标方法调用失败则调用备用方法返回相同类型的返回值。备用方法要求入参、返回值和原方法一致。
Feign 工作原理 (动态代理、HTTP请求) ★★
Fein 原理 Restful HTTP请求 + JDK动态代理,
通过动态代理在本地实例化远程接口->封装Request对象并进行编码->发送请求并对获取结果进行解码。
要使用Feign就必须在启动类上加上注解 @EnableFeignCleints,这个注解就相当于Feign组件的一个入口,序启动后,会进行包扫描,扫描所有被@FeignCleint 注解修饰的接口,通过JDK底层动态代理来为远程接口创建代理实例,并且注册到IOC容器中。
注意:调用接口@PathVariable @RequestBody注解不能省,请求路径要写全
为什么说 Ribbon 是一种客户端负载均衡?
因为是使用 Ribbon 时是从 consumer 出发在 Eureka 中查询对应的微服务信息,决定
从集群中访问哪一个实例。
Nginx 网关与 gateway 网关的区别
首先这两种网关的定义不一样
nginx 用户访问的总入口,也就是前端页面的容器,流量网关 (针对全局)
gateway的定义是针对每一个业务微服务来得,属于业务网关 (针对微服务架构)
对于具体的后端业务应用或者是服务和业务有一定关联性的策略网关就是上图左边的架构模型——业务网关。 业务网关针对具体的业务需要提供特定的流控策略、缓存策略、鉴权认证策略等等。
与业务网关相反,定义全局性的、跟具体的后端业务应用和服务完全无关的策略网关就是上图右边所示的架构模型——流量网关。流量网关通常只专注于全局的Api管理策略,比如全局流量监控、日志记录、全局限流、黑白名单控制、接入请求到业务系统的负载均衡等,有点类似防火墙。Kong 就是典型的流量网关。
这里需要补充一点的是,业务网关一般部署在流量网关之后、业务系统之前,比流量网关更靠近业务系统。通常API网指的是业务网关。 有时候我们也会模糊流量网关和业务网关,让一个网关承担所有的工作,所以这两者之间并没有严格的界线。
nginx与gateway的区别:
nginx是用C语言写的,自定义扩展的话,要么写C要么写lua
gateway是java语言的一个框架,可以在框架上进行代码的扩展与控制,例如:安全控制,统一异常处理,XXS,SQL注入等;权限控制,黑白名单,性能监控,日志打印等;
gateway的主要功能有,路由,断言,过滤器,利用它的这些特性,可以做流控。
nginx做网关,更多的是做总流量入口,反向代理,负载均衡等,还可以用来做web服务器。
CAP定理
C:一致性 (Consistency)
强一致:在任意时刻,所有节点中的数据是一样的。 (如银行)
弱一致:最终一致性就属于弱一致性。 (大部分)
A:可用性 (Availability)
系统提供的服务必须一直处于可用的状态,每次只要收到用户的请求,服务器就必须给出回应。 时刻给用户提供可用的状态,即使服务器宕机
P:分区容错性 (Partition tolerance)
在分布式系统中,不同的节点分布在不同的子网络中,由于一些特殊的原因,这些子节点之间出现了网络不通的状态,但他们的内部子网络是正常的。从而导致了整个系统的环境被切分成了若干个孤立的区域。这就是分区
Nacos 如何使用?
安装服务器
1. 导入依赖 nacos-discovery 、nacos-config
2. 在启动类上加入注解@EnabkeDiscoveryClient
3. 配置文件 配置访问的地址,路径
Sentinel 如何使用?★★
1. 引入依赖
2. 导入sentinel配置,链接客户端
3. feign配置,激活sentinel组件
4. Feign接口,参数指定熔断类
5. 启动类加入激活注解@EnableFeignClients("feign接口所在包名")
控制台可以设定流量控制(QPS)、降级规则(异常比例)、预热
可以将配置持久化到Nacos:鸡肋! sentinel 配置更新后进行无法同步
添加依赖、配置datasource
Gateway路由匹配规则有哪些?
1. 通过请求路径匹配(Path)
2. 通过请求方式匹配(Method)
3. 通过请求参数匹配(QueryParam)
4. 通过 Header 属性匹配(Header)
5. 通过请求 ip 地址进行匹配(RemoteAddr)
6. 通过Cookie匹配(Cookie)
7. 通过Host匹配(Host)
8. 通过时间匹配(datetime)
Docker
容器虚拟化技术 带着环境安装
镜像、:带着环境的的安装包
容器、:根据镜像生成容器,
仓库。:
海:宿主机,主机环境。集装箱:容器
docker file 了解过吗?
制作镜像需要使用,我们一般是拉取。
docker 和 k8s(kubernetes) 的区别?
k8s是一个开源的容器集群管理系统,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。
docker是容器层面的。docker目前主要包含了dockerd和containerd两个组件。其中containerd才是真正干活的。
k8s是容器编排层面的,可以对接不同的容器层。也就是说,k8s可以对接docker,也可以对接更简洁的containerd,或者其它。
MongoDB
文档型NoSQL数据库,使用K-V键值对方式存储数据
优点:
1、对数据库高并发读写。
2、对海量数据的高效率存储和访问。
3、对数据库的高可扩展性和高可用性。
缺点:
1、数据库事务一致性需求
2、数据库的写实时性和读实时性需求
3、对复杂的SQL查询,特别是多表关联查询的需求
ElasticSearch
Elasticsearch(ES) 是一个分布式、RESTful 风格的搜索和数据分析引擎。全文搜索引擎。ES天生支持集群。
分片与副本:数据会存放到不同的分片(sharding)上,每一个分片都会有一个或多个备份(副本 replicas)
SearchSourceBuilder:本质套娃,按照语句套娃存,按照结果套娃取
聚合查询aggs;组合查询bool(
must: 各个条件都必须满足,所有条件是and的关系
should: 各个条件有一个满足即可,即各条件是or的关系
must_not: 不满足所有条件,即各条件是not的关系
filter: 与must效果等同,但是它不计算得分(_score为0),效率更高点。
)排序(sort)、分页查询(from、size)、scoll分页
多字段匹配:multi_match; 关键字精确查询:term
为什么要使用Elasticsearch?
通过倒排索引实近实时搜索,通过集群分片、副本提高性能/吞吐量,实现高可用;
系统中的数据,随着业务的发展,时间的推移,将会非常多,而业务中往往采用模糊查询进行数据的搜索,而模糊查询会导致查询引擎放弃索引,导致系统查询数据时都是全表扫描,在百万级别的数据库中,查询效率是非常低下的,而我们使用ES做一个全文索引,将经常查询的系统功能的某些字段,比如说电商系统的商品表中商品名,描述、价格还有id这些字段我们放入ES索引库里,可以提高查询速度。
如何保证ES高可用?(两个属性: 分片 副本) ★★
每个ES都会有分片,每个分片都有副本;分片和副本可以保证ES集群的高可用
如何防止脑裂?(待补充)
ES集群如何防止脑裂:可在配置文件中添加配置,明确指定当前节点
原因:网络异常,其他节点认为老大寄了
如何进行分词
ik分词器:1.基于字典 2.基于机器学习;有个错字修正中文不好使;有多种分词策略
扩展分词:IK分词器通过自定义配置 设置分词,重新更新之后,就可以自己公司名
JVM
谈谈JVM的理解 ★★★ ★
Java能够实现跨平台,得益于JVM虚拟机
Java能跨平台的原因:JAVA程序不是直接在平台上运行的,是在java虚机(JVM)上进行的
Day125.JVM:栈、堆、GC 垃圾回收机制_焰火青年·的博客-CSDN博客
入口:类加载器 =》双亲委派 =》内部结构:本地方法栈、接口、库 =》 Java栈 =》 堆、方法区=》 堆存在99%垃圾回收 =》新生 老年 永久代 =》伊甸 幸存1、2 =》垃圾判定:引用计数法(不再使用)、可达性分析算法 =》出口:执行引擎 =》
类加载器:将Class加载入内存,双亲委派(类重复加载、安全),沙箱安全机制
本地方法栈:Native修饰的方法,需要操作硬件,Java无能为力
本地方法接口:
本地方法库:C++实现,.DLL
栈:实例对象的地址;栈帧(局部变量表,操作数栈,动态链接,返回地址)
程序计数器:指向下一条指令的指针,可以忽略不计
方法区:类信息(元数据模板)、静态常量、Integer-128-127
堆:实例对象的数据;
新生区:伊甸区、幸存者一、幸存者二、轻GC,TO区总为空;8:1:1
老年区:新生区存不下,或15次GC之后
对象头(哈希码、GC分代年龄、锁状态标志、线程持有的锁)
第一部分用于存储对象自身的运行时数据,
第二部分是类型指针
jdk7 变为堆存储静态变量、字符串常量池
jdk8 去永久代变元空间:
元空间指的是本地的物理内存 RAM
一、新生区垃圾回收:复制 - 清除 - 交换
1. 伊甸区满了,程序继续创建对象,发生轻GC,将存活的对象放入From区
2. 伊甸园又满了,此时会扫描伊甸区 + From区,将存活的对象存入To 区,此时From区空
3. From,To 交换
垃圾回收算法:
复制算法 -- from,to
优点:效率高,没有没存碎片
缺点:浪费内存空间,1/2
二、养老区垃圾回收:标记清除 或 标记清除-压缩混合使用
养老区满了,此时会发送Full GC,如果回收不了对象,就会OMM。
如何判断对象可回收?
1. 引用计数法:使用一个变量记录这个对象被引用的次数,有用用次数+1,没有-1
但是 Java 不可用,无法解决循环引用的问题
2. 可达性分析算法:判断这个对象是否与 GC Roots 有关联,没有初步判断可以回收。
引用链:对象与GC Roots 相连的那个线
如何理解这个对象可回收需要至少标记两次?
1. 判断这个对象是否与 GC Roots 有关联
2. 判断这个对象是否实现了 finalize() 方法;(对象回收之前一定会执行这个方法)
有,会将这个对象放入一个队列,并将开一个低优先级的线程处理。
没有,直接回收
内存调优;调优工具
Xmx最大内存;Xms初始化内存;Xmn设置新生区
栈和堆的区别?
1. 栈管运行,堆管存储!2. 栈没有GC行为
3. 堆效率低,栈效率高
4. 堆线程共享,栈线程私有
垃圾回收算法?如何判断对象可回收? ★★★ ★
1、复制算法 (Copying):实现简单、效率高,不产生内存碎片;浪费一半内存空间
2、标记清除 (Mark-Sweep):节省内存;效率问题
3、标记压缩 (Mark-Compact):没有内存碎片,节省内存;效率低
4、GC 分代收集算法 (Generational-Collection):年轻代使用复制算法;老年代使用标记清除、标记压缩如何判断对象可回收?
1. 引用计数法:使用一个变量记录这个对象被引用的次数,有用用次数+1,没有-1
但是 Java 不可用,无法解决循环引用的问题
2. 可达性分析算法:判断这个对象是否与 GC Roots 有关联,没有初步判断可以回收。
引用链:对象与GC Roots 相连的那个线
如何理解这个对象可回收需要至少标记两次?
1. 判断这个对象是否与 GC Roots 有关联
2. 判断这个对象是否实现了 finalize() 方法;(对象回收之前一定会执行这个方法)
有,会将这个对象放入一个队列,并将开一个低优先级的线程处理。
没有,直接回收
为什么新生代用复制算法? 因为新生代绝大部分对象都是垃圾!!
垃圾回收器?
收集器 | 线程 | 算法 | 优势 | 缺点 |
---|---|---|---|---|
Serial 收集器/Serial Old 收集器 | 单线程 - 串行 | 复制算法/标记整理算法 | 简单高效 | stop-the-world |
ParNew 收集器/Parallel Old 收集器 | 多线程-并行 | 复制算法/标记整理算法 | 响应优先 | stop-the-world |
Parallel Scavenge 收集器 (新生代) | 多线程-并行 | 复制算法 | 吞吐量优先 | 无法与CMS收集器配合使用 |
CMS收集器 (老年代) | 多线程-并行 | 标记-清除 | 响应优先 | 对CPU资源非常敏感 无法处理浮动垃圾 |
G1 收集器 (新生代 + 老年代) | 多线程-并行 | 标记-整理+复制 |
什么是GC根(GC Roots)?
可达性分析算法中
判断这个对象是否与 GC Roots 有关联,没有初步判断可以回收。
引用链:对象与GC Roots 相连的那个线
GC根
通过一系列"GC Roots"对象作为起始点,开始向下搜索,搜索所走过和路径称为引用链, 当一个对象到GC Roots没有任何引用链相连时(从GC Roots到这个对象不可达),则证明该对象是不可用的。
哪些对象可以作为GC Roots? ★★
栈里面的 :引用的对象,方法使用到的参数、局部变量等。
堆里面的:类静态属性引用对象 (静态变量)
方法区的:字符串常量池
在java中,可作为GC Roots的对象有:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
2.方法区中的类静态属性引用的对象;
3.方法区中常量引用的对象;
4.本地方法栈中JNI(即一般说的Native方法)中引用的对象
类的加载过程 ★★
(1)加载:load
将class文件加载到内存
(2)链接:link
① 验证:校验被加载的class文件的合法性,并且不会危害虚拟机的自身安全
(所有的java的字节码文件 cafebaby 开头)。② 准备:为 类变量(成员变量) 分配内存 (方法区中) 并设置默认值 (0,null,false),为静态常量赋初始值 (常量池中)。
③ 解析:把字节码中的符号引用 (类似全类名) 替换为对应的直接地址引用
(3)初始化:initialize
1. 静态成员变量 显示赋值语句
2. 静态代码块内容
双亲委派机制 ★★★
不同的类通过不同的类加载器完成加载
① 避免类的重复加载
② 保护程序安全,防止核心类库被随意篡改类加载器的分类:
(1)引导(启动)类加载器(Bootstrap ClassLoader)C++编写 又称为根类加载器
它负责加载jre/lib/rt.jar核心类库 ($JAVA_HOME中jre/lib/rt.jar里所有的class),它本身不是Java代码实现的(HotSpot VM 中C++实现的),也不是ClassLoader的子类,获取它的对象时往往返回null。
(2)扩展类加载器 (Extension ClassLoader) Java
它负责加载jre/lib/ext扩展库 ($JAVA_HOME中jre/lib/*.jar ),它是ClassLoader的子类,Java代码编写。
(3)应用程序类加载器 (Application ClassLoader) Java
也称为系统类加载器System Class Loader,它负责加载项目的classpath路径下的类,它是ClassLoader的子类,Java代码编写。(加载自己写的类)
(4)自定义类加载器。(略)
jdk8中有什么更新?
1. JVM 堆 去永久代变元空间:元空间指的是本地的物理内存 RAM
2. Stream流式编程:函数式接口、Lambda表达式、方法引用
3. 接口增强:静态方法、默认方法
4. Optional类:解决空指针
5. 新的时间和日期API
ReentrantLock 和 synchronized区别
二者都是独占锁
(1) synchronized加锁和解锁的过程自动进行,ReentrantLock 上锁解锁需要手动进行,比较灵活,
(2) synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3) synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。
如何进行JVM调优?堆参数调优? ★★
1.7、1.8参数调优:-Xms(初始堆内存大小) -Xmx(最大堆内存大小)
建议设置成一样。不需要重新分隔设计堆区大小,从而提高性能。
-Xss(栈初始大小)
为什么效率会变高:内存变大,重GC次数变少
JVM 的常用参数调优?
-Xms10g :JVM启动时申请的初始堆内存值
-Xmx20G :JVM可申请的最大Heap值
-Xmn3g : 新生代大小,一般设置为堆空间的1/3 1/4左右,新生代大则老年代小
-Xss :Java每个线程的Stack大小
-XX:PermSize :持久代(方法区)的初始内存大小
-XX:MaxPermSize : 持久代(方法区)的最大内存大小
-XX:SurvivorRatio : 设置新生代eden空间和from/to空间的比例关系,关系(eden/from=eden/to)
-XX:NewRatio : 设置新生代和老年代的比例老年代/新生代调试跟踪参数配置
-XX:+PrintGC :打印GC日志
我收藏了一篇文章,调优的时候会根据那个去调
JVM调优工具有哪些?★★
我们一般使用JDK自带的命令行,或图形化工具。bin目录下
命令行下:
-jps:查看正在运行的java进程
-jstat:查看JVM统计信息
-jinfo:实时查看和修改JVM配置参数
-jmap:导出内存映射文件&内存使用情况
图形化工具:
VisualVM :分析实例对象、分析内存泄漏、OOM、查看内存快照
JConsole (相对简单)
如何解决内存泄漏、内存溢出问题?★★★
一、内存溢出:新建对象在内存中存不下了。
1. 循环创建对象,导致堆内存溢出。
2. 递归调用没有出口,导致栈内存溢出。
3. 局部变量过大,导致栈内存溢出。
3. 真的不够用了,通过参数 -Xmx -Xms | -Xss 修改堆、栈内存大小 内存泄漏可能是内存溢出产生的原因。
二、内存泄漏:程序执行完成了,但对象没有被回收。
1. 严格来讲:有些对象已经不再使用,但由于指针的存在还是能被GCRoots连接起来,所以可达性分析算法 中该对象不会被回收。
2. 宽泛意义来讲:长生命周期对象引用了短生命周期对象,如静态调用,内部类调外部类。
解决方案:
Dump内存快照,用 VisualVM 或其他工具检测分析,找到问题对象,将其=null (消除指针)。
ArrayList中cliear()方法就是这样做的
2. 不要让Bean生命周期过长
3. 重写equals方法,查的,原理未知。
防止:
1. 单例算一种内存泄漏
2. 资源类未关闭;锁未解锁
3. 集合容器中的内存泄露
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
如何Dump 内存快照?★★
获取内存详情:jmap -dump:format=b,file=e.bin pid 这种方式可以用 jvisualvm.exe 进行内存分析,或者采用 Eclipse Memory Analysis Tools (MAT)这个工具
获取内存dump: jmap -histo:live pid 这种方式会先出发fullgc,所有如果不希望触发fullgc 可以使用jmap -histo pid
第三种方式:jdk启动加参数: -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=/httx/logs/dump 这种方式会产生dump日志,再通过jvisualvm.exe 或者Eclipse Memory Analysis Tools 工具进行分析
使用VisualVM,查看内存快照 ,分析OOM或内存溢出
什么是对象头?(拔高)
在 HotSpot 虚拟机中,一个对象的存储结构分为3块区域:对象头(Header)、实例数据(Instance Data) 和 对齐填充(Padding);
对象头(Header):包含两部分:第一部分用于存储对象自身的运行时数据,
如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit,官方称为 ‘Mark Word’;
第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例,另外,如果是Java数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以;
JUC
线程和进程 | 并行和并发
每一个程序都有一个进程,操作系统动态执行的基本单元;
一个进程中可以包含若干个线程;线程是资源调度的最小单位
并行和并发
并行:同一时间多个线程在执行,同一时刻多个线程在访问同一个资源, (烧水泡面)
并发:同一时间多个线程在做同一件事 (秒杀、春运抢票)
wait / sleep的区别
wait 释放锁,sleep 不释放锁
wait是Object的方法,sleep是Thread
wait 一般搭配 notify 使用
相同点:在哪睡的在哪醒
ReentrantLock和synchronized区别
二者都是独占锁,可重入锁
1. ReentrantLock 需要手动解锁,synchronized自动解锁
2. ReentrantLock 更灵活
3. synchronized不可响应中断,ReentrantLock可以响应中断。(一个线程获取不到锁就一直等着)
创建线程有几种方式?用那种? ★★
继承Thread抽象类;
实现Runnable接口:
java5以后:1
实现JUC 的Callable接口
通过线程池 (ThreadPoolExecutor) (常用)
工具类线程池最大容量为Integ 的最大值,会产生OOM!
一池一线程池,固定大小线程池,可扩容线程池
自定义线程池七大核心参数:核心线程容量、最大线程容量、空闲线程存活时间、时间单位、任务队列(阻塞队列)、线程工厂、拒绝策略
创建线程池的四种方式(连击)? ★★★
1.一池一线程 2.固定大小的线程池 3. 可扩容线程池 通
过工具类创建,最大线程个数是Integer.MAX_VALUE;会发生OOM
自定义线程池7个核心参数;
1.核心线程数 2.最大线程数 3.空闲线程存活时间 4.时间单位 5. 阻塞队列 6.线程工厂
7. 拒绝策略
核心线程个数如何设置? io密集型:2n cpu 密集型:n+1 n: 内核数
你工作用了么? 用! 你们线上核心线程个数是多少? 33 65(阿里云)
public ThreadPoolExecutor(int corePoolSize, //核心线程数(常备线程) int maximumPoolSize, //最大线程数 long keepAliveTime, //多余空闲线程数的存活时间 TimeUnit unit, //存活时间单位 BlockingQueue<Runnable> workQueue, //任务队列 提交但未执行 ThreadFactory threadFactory, //线程工厂 RejectedExecutionHandler handler //拒绝策略 ) { ..... }
线程池拒绝策略有哪些? ★★
1. 抛出异常
2. 由调用者所在的线程执行
3. 抛弃等待时间最久的任务
4. 直接丢弃
线程池能够处理最大任务是多少?
最大线程数+阻塞队列个数
线程池工作原理:拒绝策略jdk4种,可以自定义拒绝策略!
线程池底层原理 (线程池工作原理) ★★★
自定义线程池中的阻塞队列有哪些? ★
BlockingQueue接口主要有以下7个实现类:
- ArrayBlockingQueue: 由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue: 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
- PriorityBlockingQueue: 支持优先级排序的无界阻塞队列。
- DelayQueue: 使用优先级队列实现的延迟无界阻塞队列。
- SynchronousQueue: 不存储元素的阻塞队列,也即单个元素的队列。
- LinkedTransferQueue: 由链表组成的无界阻塞队列。
- LinkedBlockingDeque: 由链表组成的双向阻塞队列。
你们都服务器线程池设置多大?怎么设置的,依据是什么? ★
CPU密集型 我们是:CPU核心数 + 1
IO密集型 我们是:CPU核心数 * 2如何理解CPU与IO密集型?
计算量大 CPU,
不怎么耗CPU 如请求、上传文件、网络传输 IO密集型线上核心线程个数是多少? 我们用的阿里云服务器,32 64
线程间通信 母题 (生产消费模式) ★★
两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z,要求用线程间通信
1、简化问题:两个线程操作一个初始值为0的变量,实现一个线程对变量增加1,一个线程对变量减少1,交替10轮。
2、实现方案-线程间通信模型:
生产者+消费者
通知等待唤醒机制 (wait、notify)
3、多线程编程模板中:判断、干活、通知;线程 操作 资源类;高内聚低耦合
什么是AQS? ReentrantLock加锁本质是什么? ★★★
AQS 是基于 volatile 和 cas 操作实现的。
抽象队列同步器。内部维护了一个队列用来存储线程,又有一个volitile 修饰的变量 state,来决定线程怎么处理 (这也是上锁的本质),通过改变state0:1,实现上锁解锁
通过AQS可以实现独占锁 (一个线程可获取的锁,ReentrantLock 可重入锁),也可以实现共享锁 (多个线程可获取的锁Semaphore(信号量)/CountDownLatch(倒计时线程控制)等)
什么是CAS?int++ 如何保证原子性?AtomicInteger 底层原理?★★★
自旋锁:比较并交换,比较内存值,相等则交换。
CAS是解决多线程并发安全问题的一种乐观锁算法,保证原子性。1.原子性 2.有序性 3.可见性
AtomicInteger 原子类底层:CAS + volatile + native
CAS为什么效率高:因为各个线程没有阻塞 (无锁、轻量级锁)
CAS的实现:AtomicInteger 、JMM内存模型
Lock 锁底层 大量使用了CAS自旋锁
CopyOnWrite 写时拷贝技术? ★★★ ★
所有读的线程可以并发去读(共享锁),所有写的线程可以单独去写(独占锁)。
CopyOnWrite容器(简称COW容器)即写时拷贝的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWriteArraySet、CopyOnWriteArrayList; ConcurrentHashMap将数据存储在 ConcurrentHashMap,工作内存-->比Redis还快
优点:保证高效的读取(共享),安全的写入(独占) (用于读多写少的并发场景:搜索框黑名单)
缺点:占用内存难问题,只能保证最终一致;
CopyuOnWrite 底层:并发写时拷贝出一个,然后替换指针;写时拷贝
ConcurrentHashMap 底层:sync + CAS + Volatile (控制初始化长度)
JDK1.8之前 锁的分段技术
分段锁:锁node节点,避免无效的锁,只锁一小段
JDK1.8之后 之后实现了粒度更细的加锁;锁住hash槽中第一个元素
1. 减少内存开销
2. 获得JVM支持(synchronized 底层JVM)
JMM 内存模型 ★★
Java内存模型(JMM)就是一种符合内存模型规范的,,屏蔽了各种硬件和操作系统的访问差异,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制与规范。
内存模型的三大特性:原子性、可见性、有序性
原子性:即不可分割性。
1. 使用Lock 或 sychronized 来变成一个原子操作。
2. 使用 JUC 下的原子类:AtomicInteger、AtomicLong、AtomicReference等。可见性:每个线程都有自己的工作内存,所以当某个线程修改完某个变量之后,在其他的线程中,未必能观察到该变量已经被修改。在 Java 中 volatile、synchronized 和 final 实现可见性。volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。
有序性:单线程是有序的,多线程是无序的。JMM的工作内存和主内存之间存在延迟,并且 java会对一些指令进行重新排序 (指令重排)。
1. volatile 关键字本身就包含了禁止指令重排序的语义
2. synchronized 则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入
Volatile 关键字作用? ★★★
1、保证其有序性,指令执行的顺序与程序指明的顺序一致
2、保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。
注意:无法保证原子性,需要 synchronized
底层是内存屏障:一条CPU指令
Callable接口与runnable接口的区别? ★
Callable 有返回值,可以抛出异常,
不同点:
- 具体方法不同:一个是run,一个是call
- Runnable没有返回值;Callable可以返回执行结果,是个泛型
- Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛
- 它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果 (FutureTask)。
Lock锁的本质 是什么?★★★
Lock 锁的底层 使用 CAS 自旋锁 使用了 (AQS 用到了);通过 CAS,修改这个值。
内部维护了一个 volatile 修饰的变量,本质对这个变量+1 -1,后面有一个双线链表用于存储等待中的线程 (AQS)
创建lock可以加参数,设置公平锁
Mysql高级
Mysql 底层数据结构 (InnoDB存储引擎:B+树) ★★★ ★
Log(n)的匹配效率,想要达到必须用索引! 索引本质是一种数据结构,B+数。
二叉树 -> 平衡二叉树 -> 红黑树 -> B树 -> B+树
Day130.MySQL高级:Liunx安装、三大范式、InnoDB、数据结构、B+树_焰火青年·的博客-CSDN博客
Mysql 为何高效?底层使用什么引擎?B+树特点 .....
为什么不用Hash表结构存储?
因为Hash无法做到连续读取!B+数底叶子结点通过指针相连,成一个双向链表。
MySAM 与 InnoDB的区别
InnoDB 存储引擎
- 支持事物, 行级锁,外键
- 5.5之后,默认采用InnoDB引擎。
- 底层使用 B+树 数据结构存储
MyISAM 存储引擎
- 不支持外键,行锁,不支持事物;极其重要
- 适合海量的低价值数据,不需要改的数据。(如操作日志、用户行为)
- 5.5之前默认的存储引擎
- 底层使用 B树 数据结构存储
InnoDB表只把自增主键的最大ID记录到内存中
MylSAM将自增主键的最大ID记录到数据文件中。
为什么使用数据索引能提高效率?
索引的本质是一种数据结构,InnoDB中底层是B+数,可以达到logn的时间复杂度
而全表扫描只有On。
并且B+数 底层存储数据是连续的,通过指针形成了一个链表,范围查询速度快。
什么是聚簇索引和非聚簇索引?什么是回表?★★★
Mysql InnoDB存储引擎总中每一份数据都会绑定一个索引(有主键用主键,没有主键用唯一键,都没有用一个64的uid ),而数据只有一份,和数据放在一起的称为聚簇索引,非聚簇索引叶子节点中不再存储数,而是存储 字段与主键id(索引列值),所以需要回表操作;二次查找,先找到主键值,再通过主键值找到数据行的数据页。
还有一种情况:select的字段就是 主键索引!不用产生回表。
索引概念 (分类、优点、回表、覆盖索引、最左匹配、索引下推) ★★
回表:一个概念,去表中(磁盘中)查询数据,效率低;需要根据覆盖索引查询到的id值再回到主键索引里,再次查询。
覆盖索引:查询数据就是索引,无需回表,效率高。我们可以根据覆盖索引进行优化。
最左匹配原则:最左优先,以最左边的为起点任何连续的索引都能匹配上,
B+树的节点存储引擎顺序是从左到右存储,在检索匹配的时候也要满足从左到右匹配。
索引下推:索引下推是数据库检索数据过程中为减少回表次数而做的优化。复合查询中减少回表次数。
索引的底层实现是什么?什么情况下会索引失效?★★★
InnoDB 存储引擎是用 B+Tree 实现其索引结构
1. 全盘扫描
2. 最左前缀法则:索引中,用第一个索引列去匹配。
因为在建立多值非聚簇索引时,会按照第一个字段分配Page的顺序
3. like 以通配符%开头
4. 范围条件右边的列
5. 计算、函数
6. 不等于(!= 或 <>)
7. is not null无法使用索引,is null可使用索引
8. or 会导致索引失效
9. 类型转换导致
失效条件:
1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
2.对于多列索引,不是使用的第一部分,则不会使用索引
3.like查询以%开头
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引
6. 组合索引要遵循 最左匹配原则
Mysql 如何强制使用一个索引? ★
-- 强制使用索引 如下,其中,xxx 为索引名
select * from '表名' force index(xxx) ;
Mysql 如何调优? ★★
一句话:索引优化,就是想办法提高索引的命中率
调优工具(语句)
1、Show Profiles Mysql:查看sql的执行周期 1/3
show profiles; 查看sql执行信息,分析 网络异常、还是硬件不够、或是sql需要优化
show profile cpu,block io for query 9; 查看执行计划 (具体sql)
2、Explain 索引优化分析:查看当前sql索引引用情况
Explain select * from t1,t2,t3;
3、Optimizer Trace 优化追踪器 (大招)
可以查看sql执行,经历了什么;索引优化、命中过程4、慢Sql日志
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,有主键用主键,没有主键用唯一键,都没有用一个64的uid
Explain 如何查看SQl好坏? ★★
会有很多数值,用户展示当前sql的索引引用情况,如用可能用的索引,实际用到哪个索引,实际使用索引长度,最重要的就是Type。
B+树索引和哈希索引的区别
1. B+ 树,底层存储数据是连续的,范围查询速度快,支持通过索引排序
2. B+ 树 不会产生Hash冲突
Mysql 主从复制原理 ★★★
两个日志、两个线程
二进制日志 binlog日志偏移量
1. 主库对外提供数据的增删改查服务,主库中涉及到数据的修改都会写binlog
2. 从库用来数据的同步和备份,slave会开启一个IO线程读取binlog日志,写到中继日志当中
3. 在开启一个IO线程,读取中继日志,将数据、权限、表结构相关的修改同步到从库里面
undo log:数据修改之前
redo log:数据修改之后
MyCat 数据库中间件 (如何保证Mysql高可用) ★
Mycat是数据库中间件。
可以在读写操作之间加入一个组件,把它们包容起来;通过虚拟数据库链接真实数据库
读写分离、数据分片、多数据源整合
如何搭建Mysql集群、分库分表、读写分离 ★★
达到500W、2G容量会降低,Mysql的瓶颈,单表超过200W,
垂直分库 水平分表
修改 schema.xml,设置虚拟数据库,连接真实读、写数据库
虚拟数据库:TESTDB
恶心!
是如何进行分库分表的?(待补充)
阿里:单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
一个master 三个slave,数据量有点大,单机大概5、6百万条,修改的不是很多,但是读的挺多,我们的架构比较简单,直接读数据库,最简单的方法就是多搞几个可读服务器,
进行分库分表。提高sql性能;限制单表数据量
MyISAM 和 InnoDB的区别
1. MyISAM 不支持外键,行锁,不支持事物;InnoDB 支持
2. MyISAM效率相对较高
Mysql8是否支持缓存,为什么?
不支持,因为缓存命中率比较低,所以Mysql8取消了缓存
锁机制 ★★
可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
公平锁:按照申请锁的顺序去获得锁,线程会直接进入队列,先进先出。
连锁:若干锁同时上锁成功才算成功。new RedissonMultiLock(lock...)
红锁:大部分节点上加锁就算成功。new RedissonRedLock(lock...)
读写锁:读读可并发,其他都不可并发
信号量(Semaphore):多线程访问多资源,控制资源量;单资源加锁即可
闭锁(CountDownLatch倒计时线程控制):倒计时线程控制;减少计数为0时执行。
MinIo 分布式文件存储系统
minio 开源的分布式文件存储系统,市场占用率高;
特点:性能高;可扩展;sdk;有界面;防止丢失数据;
存储机制 纠删码:在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据!
纠删码是一种恢复丢失和损坏数据的数学算法。
项目相关
项目如何进行服务治理?
Nacos--作为注册中心和配置中心,实现服务注册发现和服务健康监测及配置信息统一管理
心跳监测机制。
服务注册方法:以Java nacos client v1.0.1 为例子,服务提供每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。
项目是如何优化的?
TomCat默认并发500,可以进行调整连接数和并发数;服务器带宽
使用各种中间件
1. 架构优化:单体架构-垂直架构-rpc-soa-分布式微服务;时间成本(学习-代码重构)
2. 增加服务器:各种集群
mysql集群:mycat 垂直分库(按照业务) 水平分表(安装某个字段)
Redis主从复制+集群
3. 减少io:减少用户访问次数 -- redis缓存;
索引优化:explain: all --> index ---> 【range ---> ref】---> er_ref ---> const --->sysetm
单表:全值匹配、最佳左前缀、否定会导致索引失效 != <> is not null、范围右边会失效、like
关联:被驱动表建立索引,不用内连接
子查询:改成 A left join B where A.id is null;
分组-排序:无过滤不索引;顺序错,必排序;方向反,必排序
建议:不要写 * ,尽量使用覆盖索引;减少回表,尽量使用索引下推
4. 减少同步:减少同步操作 -- mq异步解耦
5. redis优化
项目有没有什么问题:公司硬盘加密,中文乱码,换个盘符就好了
分布式锁 ★★
多请求并发,需要分布式锁,普通锁只能解决本地事物。
1. 基于数据库实现分布式锁;
2. 基于缓存(Redis等)实现分布式锁;
3. 基于Zookeeper实现分布式锁;我们是用Redssion ,底层LUA脚本,保证 释放锁操作 具有原子性。
分布式事务 ★★★ ★
分布式事务,就是把各种业务看做一个整体,在我们的项目中支付-减库存-修改订单状态
MQ消息表
发送消息减库存失败后,进行消息重试;
重试后还是失败,MQ消息表记录当前那个订单减库存失败;
后续进行补货时,采用mq 形式 发送一个消息,通知订单模块、支付模块修改状态达到最终一致性。
结局方案:
1. 2PC 两段式提交 强一致性 (基于XA协议) 两段式提交,引入一个协调者。
(1)投票阶段(voting phase):参与者将操作结果通知协调者
(2)提交阶段(commit phase):协调者根据反馈情况决定各参与者是否要提交还是回滚;
缺点:一个模块出现问题(没有反馈),所有模块都会阻塞。2PC遵循强一致性,效率很低。2. TCC 代码补偿事务 最终一致性 三段式提交 本质还是两段式
TCC是Try ( 尝试 ) — Confirm(确认) — Cancel ( 取消 ) 的简称:
3. 本地消息表(异步确保)- 最终一致性
订单系统新增一条消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到 MQ,库存系统去消费 MQ。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
发生异常不会回滚,而会存储到消息表中
4. MQ 事务消息 - 最终一致性有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
定时任务发送流程:发送half message(半消息),执行本地事务,发送事务执行结果
定时任务回查流程:MQ服务器回查本地事务,发送事务执行结果
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 目前主流MQ中只有RocketMQ支持事务消息(需要买阿里的服务)。
5. Seata 阿里框架 有几种模式(XA、AT)
Seata 是阿里开源的一个分布式事务框架,能够让大家在操作分布式事务时,像操作本地事务一样简单。一个注解搞定分布式事务。
对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
高性能:减少分布式事务解决方案所带来的性能消耗
Seata 如何使用?
1. 准备相关Mysql表,用于记录回滚日志
2. 安装Seata TC服务器,
3. 引入依赖,配置文件进行配置,
4. 加入@GlobalTransactional 注解,会自动开启全局事务,并且记录日志 (AT模式)
你们都服务器线程池设置多大?怎么设置的,依据是什么? ★
CPU密集型 我们是:CPU核心数 + 1
IO密集型 我们是:CPU核心数 * 2如何理解CPU与IO密集型?
计算量大 CPU,
不怎么耗CPU 如请求、上传文件、网络传输 IO密集型
IO密集型,CPU密集型,从阿里云租的服务器,16核,线程池设置的32
个人隐私怎么 脱密 加密处理的?
就是通过一个工具类。通过正则匹配;
并发量多大?
我自己的接口 在我自己电脑 用 jmeter 测试过,并发大概6、700. (与CPU\配置都有关系)
这是给甲方开发的项目,正式的运维数据我们是拿不到的。
在测试环境下,这个项目 6、700是没问题的
项目 Redis集群是如何配置的?
测试环境三主三从,实际生产环境应该是同步的。 (不是我做的,不清楚)
你们项目都有哪些分支?★★
主分支(master)、开发分支(dev)、版本分支、测试分支(test)、
用户测试环境(uat) 用于甲方验收
项目是如何部署的?
Jenkins 自动化部署
如何解决高并发?
1. 首先网络带宽需要支持; 1M带宽想要解决并发,肯定不现实
2. 硬件;处理器硬盘越大 效率越高
3. 代码层面
4. 架构 微服务可以添加机器 横向拓展
如何解决Token盗用?
同一台机器就认为是同一个人,盗用token,没有意义。而不同IP会重新登录
PostMan,就是盗用token
如何进行内网穿透
1、内网穿透
外网请求怎么请求到内网地址
解决方案:
1. 在公司中解决 ★★
找运维人员解决,登录交换机控制台,添加内外网的映射关系
例如:192.168.140.100:12345 => 192.168.43.86:8160
2. 个人解决:修改系统配置文件host,添加域名和ip映射
优点:简单
缺点:只能使用80端口,(只能配置ip映射不能配置端口映射)
3. 个人解决:使用内网穿透工具
优点:可以配置ip映射,也可以配合端口映射
缺点:花钱
4. 个人解决:在外网主机上发布web服务,做请求重定向到内网
优势:可以配置ip映射,也可以配合端口映射
缺点:内网的接口url,端口不能修改
5. 个人解决:由微信提供内网穿透,原理和方式 (4) 一致
如何解决超卖问题?(库存只剩1件商品,多个用户同时下单)
我们采用的是下订单不减库存,只验证库存,支付成功后再去扣减库存,如果库存扣减失败,通知后台进行补货,如果这个商品不能补货,人工客户介入,和买家进行沟通,给予退款或相应补偿。
如果想实现不超卖,就得在下单时进行库存锁定,(可以使用数据库锁方式)然后减库存操作。分布式事务 -- MQ -- 最终一致性
项目中如何防止超卖的?项目中什么时候锁定库存的?
a. 商品详情页 校验库存,有货and无货
b. 购物车中每个购物项最大只能购买200件商品
c. 下单是否需要锁定库存下单时锁库存:用户是上帝 -- 京东
下单时不锁库存:用户是韭菜 -- 淘宝
减库存失败不退款!
订单--支付--库存 看做一个事务, mq 解决分布式事务,保证最终一致性
spu&sku什么结构存储的 -- 表结构
sku 有一些基本信息 单独存放一个 sku表
平台属性、销售属性、图片、海报、分类分开存储直接通过skuid关联,或通过一张中间表
商品表结构:
商品表是不存储具体的值的,因为有很多一对多关系。
通过存储 那张表的表主键id(品牌表、三级分类id),或者spu_id被那张表存储(图片、海报、销售属性、值),进行关联。
商品表只存储,商品名、描述信息
spu_info 商品表 tm_id -> 关联品牌表, 品牌表通过 中间表 关联三级分类表
spu_info 商品表 通过主键 -> 海报表、图片表spu_sale_attr: 颜色、尺码、样式
spu_sale_attr_value: 红色、l、羽绒马甲
你们服务降级怎么处理的
通过Sentinel; @SentinelResource 自定义降级方法;服务出现了问题就会走默认实现。我们可以通过Sentinel设置一些参数
关于权限 Spring Security 框架
Day91.Spring Security框架: 认证与授权、Spring Session集成--Session共享_焰火青年·的博客-CSDN博客
需要一张权限表
1. 引入依赖、添加配置
2. WebSecurityConfigurerAdapter 切面类,获取用户角色对应的权限集合过滤
3. 加入注解 @EnableGlobalMethodSecurity(prePostEnabled = true) ,开启Controller权限 细粒度 控制
4. Controller 添加 注解即可
@PreAuthorize("hasAuthority('role.assgin') or hasRole('admin')")
微服务调用的时候 cookie 无法传递 怎么解决?★★
Openfein 无法携带请求,在gateway加入拦截器,手动放入请求头。
业务相关
第三方对接,常用的加密方式?
OAuth 2.0 单点登录,传递给对方token,反过来调用接口获取信息
或接收对方token,调用对方接口获取信息
我们的开放平台 Open:
给出对方AppId、AppKey,将json参数加密后作为data,然后通过参数生成authorization
可在页面配置AppId的数据(地市、学校)权限、接口权限
第三方对接给出的接口:
方式1:签名校验、AppId、AppKey
将请求参数(json) 进行SM4加密
通常用时间戳、uuid、加密后参数,用约定好的方式进行拼接,进行Sm3加密生成签名
放在请求头、或json中
方式2:非对称加密 公钥、私钥
分配给平台公钥、私钥,对方的公钥
签名、传参解密、手机号返回加密使用对方的公钥 (使用对方的公钥加密。字符串在转换为字节流时使用的编码格式为UTF-8。),对方用私钥进行加密、解密。
返回参数加密、生成签名,使用自己的私钥,对方用分配的公钥解密。
双方均会进行校验。传参与返回中包含签名sign、业务流水号(uuid)
签名的源数据通过报文中除signature之外的其他非null字段按照字段名排序后拼接为字符串再使用UTF-8编码转换为字节流。签名使用SM2withSM3算法。将签名结果转为十六进制字符串即为报文中signature字段的值。
手机号、身份证等隐私返回时用对方公钥进行加密
关于非对称加密:公钥、私钥 一个公钥对应一个私钥。 密钥对中,让大家都知道的是公钥,不告诉大家,只有自己知道的,是私钥。 如果用其中一个密钥加密数据,则只有对应的那个密钥才可以解密。 如果用其中一个密钥可以进行解密数据,则该数据必然是对应的那个密钥进行的加密。
私钥加密,持有私钥或公钥才可以解密
公钥加密,持有私钥才可解密
常用的加密方式?
对称加密(AES)、非对称加密(SM2withSM3)、不可逆加密(MD5)
对称加密,如AES
优势:算法公开、计算量小、加密速度快、加密效率高
缺陷:双方都使用同样密钥,安全性得不到保证
非对称加密,如RSA
私钥加密,持有私钥或公钥才可以解密
公钥加密,持有私钥才可解密
优点:安全,难以破解
缺点:算法比较耗时
不可逆加密,如MD5,SHA
基本原理:加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。一、 AES 高级加密标准
分配appId、appKey,可加密可解密
最常见的对称加密算法 (加密和解密需要使用同一个密钥)
通常aes128 后再转换为BASE64
二、 MD5 信息摘要算法:不可逆,通常用于密码加密
特点
1.长度固定(MD5的固定长度为128比特,16字节,通常用他的16进制字面值输出他,是一个长度为32位的字符串。)
2. 不可逆(从结果无法反推原始数据)
3. 具有高度的离散性(输出的16字节数据,没有任何规律可言,无法预测结果)
4. 抗碰撞性(在原始数据固定的情况下,几乎不会出现两个数据的MD5相同)
MD5应用场景
1. 用户密码保护:在保存用户密码时,不记录密码本身,只记录密码的MD5结果(即使数据库被盗也无法反推出明文)
2. 文件完整性校验:先在发送端计算一次文件的MD5,并把结果发送给接收端,接收端在接受文件后也计算一次MD5,两次结果一致文件完整。
3. 云盘秒传:云盘上传时计算MD5,并在自己的数据库中搜索一下 MD5是否存在,存在则使用已有的文件就可以了,从而实现云盘秒传。
4. 数字签名:发布程序时同时发布其MD5,下载后比较MD5是否相同,就可知道程序是否被篡改。
电商模块相关
商品详情 业务流程
商品详情页展示了数据的基本信息,切换颜色 尺码等等操作,加入购物车。
一、它的具体执行链是这样的
用户点击商品发起一个请求,请求传递到gateway 网关 统一入口,
gateway 过滤请求 - web-all 页面渲染 - service-item 数据汇总 - service-product 数据查询
二、获取数据
1. 商品所在的三级分类数据:category3Id
2. Sku基本信息 和商品图片列表 (skuInfo + skuImageList)
3. 获取skuPrice 实时价格
4. 获取商品所有的销售属性 + 销售属性值 和对应的选中状态 (判断是否存在skuId)
查询销售属性 join 销售属性值
left join 查询 skuId对应的销售属性值Id是谁 标识出本商品对应的销售属性。
spuSaleAttr: 销售属性表
spuSaleAttrValue: 销售属性值表
skuSaleAttrValue: skuId 与 销售属性值Id 的关联表
5. 实现商品切换:获取所有销售属性 可能存在的组合。
通过 group_concat 函数将销售属性值与skuId 分组 拼接,成Json字符串集合,传递到前端进行校验,我们组合了所有的可能性。
前端切换销售属性 组装数据后,会判断是否存在对应skuId,如果存在重新发起请求。不存在修改状态。 (页面通过销售属性值切换不同的skuId)
group_concat(sale_attr_value_id order by id separator '|')根据Id分组,属性值排序;通过sku筛选过滤,可以获得所有属性拼接可能的json串6. 海报信息
7. 规格参数
四、后续优化:Redis + redssion -- 缓存穿透、缓存击穿、缓存雪崩
由于详情页的高访问量,为了提高系统响应速度减少数据库IO,我们加入了Redis 缓存优化。又因为缓存击穿等问题,引入了Redisson分布式锁; 为了解耦便于后续开发,使用了AOP思想,提成了一个注解,直接在方法上加入注解指定key即可,程序会先查询Redis,再上锁查数据库。
1. 自定义注解,绑定切面类 @GmallCache
2. 切面编写分布式锁业务逻辑
3. 直接在方法上使用注解,指定key即可
2、布隆过滤器
分布式锁无法解决缓存随机穿透,引入布隆过滤器
底层二进制数组,用0和1表示 数据一定不存在或可能存在
由于获取数据都是串行的,引入了异步编排技术和线程池技术
3、CompletableFuture 多线程异步编排 jdk1.8
提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性;
速度提高了20ms
五、热度排名 (Redis 计数器)
用户每点击一次,redis记录热度+1,每到100更新ES
if (hotScore%100==0){ // 更新es Optional<Goods> optional = goodsRepository.findById(skuId); Goods goods = optional.get(); goods.setHotScore(Math.round(hotScore)); goodsRepository.save(goods); }
ES全文检索 业务流程
检索条件:分类数据、品牌、平台属性、热度、价格、商品名称
全文检索数据获取以及数据展示
1. 使用的es 的高级客户端 动态生成dsl 语句
2. 将获取到结果集封装到自定义好的vo中
3. 将后台获取的数据在web-all 进行存储
logstash 日志收集框架 kibana 可视化组件
为什么使用es?因为mysql 索引失效 where skuName liek "%?%"
所以使用使用企业级搜索引擎 倒排索引
怎么使用的?
1. 根据实际业务创建索引库,页面需要展示的数据项,与查询项,共同组成索引库
es 7.8.0 通过使用注解的方式启动项目时会自动创建索引库
// ES <实体类、主键类型> public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> { }
title --- skuName 配置中文分词器 ik_max_word 最大分词
price 最新价格
defaultImage 默认图片
tmId 品牌id
attrsList ... 数据类型 是nested ,允许数据彼此独立的检索和查询
category*Id 分类数据2. 上架:根据业务获取数据将其放入索引库;后续通过MQ连接后台管理
调用es 的api:save(goods); CompletableFuture 异步优化
3. 根据用户可能输入的检索条件进行查询
使用高级客户端RestHighLevelClient ,编写业务逻辑,java 动态生成dsl
判断用户的检索条件,查询、过滤、分页、排序、高亮、聚合searchRequest、searchSourceBuilder 调用
使用聚合函数的目的: 去重显示、根据聚合的数据进行过滤;
结果从聚合中获取结果集,封装为自定义Vo类返回
es 集群几个节点?至少三个节点
为了尽量保持集群高可用, 至少需要三台机器搭建集群;A网络波动,BC自动选举master
es-cluster 集群那么你是如何防止脑裂的?
原因:网络异常
在配置文件中添加配置,明确指定通过配置 minimum_master_nodes 来防止出现脑裂
ES数据同步问题怎么处理?
1. 修改Mysql的时候 直接修改ES。不好,耦合了
2. 使用MQ进行消息通知,最终一致
3. 在商品数据修改前 先下架,下架了 ES里就没这个数据了,修改后,再重新上架,ES会重新添加。 好处:强一致。
单点登录
单点登录就是访问项目时,用户只需要登录一次,就可以去访问所以内容。我们是采用网关自定义全局过滤器、token认证以及redis来实现, 登录业务,用接收的用户名密码(密码采用MD5+密码盐的形式存储)核对后台数据库,核对通过,用uuid (JWT?) 生成token,将用户id加载到写入redis,redis的key为token,value为用户id。登录成功返回token与用户名和昵称,将token与用户信息记录到cookie里面重定向用户到之前的来源地址。用户再次访问其他需要登录的内容时通过网关过滤器进行拦截校验。具体业务参考以下流程图:
如何保证只能电脑手机一个登录? token覆盖
token续签,在Redis重新设置过期时间
购物车 业务流程
功能:Redis 增删改查
1. 购物车数据存在redis中,使用 hash 类型存储。好处是效率高,缺点是数据可能会丢失。Hash的好处是修改数据时不用全部序列化。
持久化技术使用 ADB + ROF;RDB默认开启。数据可能会丢失,考虑业务场景可以容忍。
key = user:userId:cart,field = skuId,value = cartInfo。
然后购物车有四个基本功能:增删改查,
一、 新增
添加的时候用户有两种状态,已登录和未登录,通过请求中是否有userId进行判断。
登录后会生成uuid作为token,将token作为key,userId作为value存入Redis,并将token存入cookie。每次请求会拿token查缓存。登录后Gateway网关的过滤器会将userId放入请求头。
如果没有userId,前端在商品详情页点击加入购物车按钮时生成的随机数,作为用户的临时id,存储在cookie中。如果为临时id,缓存时效14天。
二、 查看
查看的时候会进行合并操作。将临时购物车与登录购物车合并。
判断用户的登录状态,未登录直接返回未登录购物车数据,如果已登录,则判断是否存在临时购物车,存在就进行购物车合并,然后再删除未登录购物车数据。
用户登录了,还有未登录购物车,触发购物车合并。
合并操作:
1. 判断购物车中是否有该商品,有则数量相加,没有则添加商品项,数量小于等于200。
2. 设置合并后的选中状态,有一方选中即为选中,加入购物车默认为选中状态。
3. 删除临时购物车 hdel key
购物车按照修改时间排序。
查询实时价格,根据升降进行提示(价格实时获取)。
无库存不会提示,下单时会校验。
三、 修改
Hash类型不用全部序列化数据
hget key field;
hset key field vlaue;四、 删除
根据skuid去操作redis中的hash的field删除
hdel key field
1、cookie被禁了能添加吗?
未登录:不能了,没有临时用户id了。
已登录:都不能登录了。2、PC(电脑)和客户端(手机APP)购物车数据一样吗?
未登录:不一样
已登录:是一样的。
首先判断用户是否登录
登录后,Gateway的fliter会将userId存如请求头中
合并购物车:
订单 业务流程
整个订单模块有结算页、下单、对接支付服务和对接库存管理系统等功能,当用户发起结算请求时,由于用户在没有登录的情况下也可以点击结算,所以我们要先判断用户是否登录,只有已登录的情况下,才能跳转到结算页面,在结算页,用户可以选择收货地址,给用户展示订单信息,用户选择支付方式,提供了微信支付和支付宝支付,确认订单信息,然后提交数据到后台,生成对应的订单表、订单详情表和订单物流表(当订单生成的时候,我们要调用对应的库存系统针对订单的商品数量进行验库存,还要进行验价格)。当订单创建成功之后,自动跳转到成功页面(将订单数据和到期时间传递过去)。
这块我们设置的订单的有效时间为24小时(这个时间可以自己定,只要合理就行),因为我们利用延时队列实现定时消息发送,消费者到时间后监听到消息,进行订单校验,如果订单是未支付状态,把订单状态修改为关闭订单。
订单的主要状态有 未支付、已支付、待发货、已收货、待评价、已完成、已关闭、已拆单等。
可以进行下单结算,下单后会清空购物车,重定向到支付页面,选择支付方式。
支付成功后发送消息,修改订单状态,发送消息扣减库存,订单改为该发货。后续库存打包完成,改为已发货。
一、订单结算页回显
order远程调用user、cart模块,回显当前用户信息、收货地址,选中的购物车项信息(送货清单)
二、 订单提交
封装订单信息,存储到Mysql
1. 流水号防止重复提交:
展示订单结算页时生成uuid作为流水号 (隐藏域),结算页存一份 ,redis存一份 "tradeNo:" + userId
每次用户提交订单时比较页面与缓存的流水号,相等则可以提交,并删除流水号。
2. 通过HttpClient 发送Restful请求 校验库存系统;库存不足无法提交
3. 校验实时价格;查询最新价格与购物车价格进行比对,根据价格变动进行提示,
优化:
4. CompletableFuture 异步编排;因为每个购物项需要遍历校验,所以声明了一个集合存储CompletableFuture,多任务组合将集合转为数组。
5. 有效期24小时 延迟消息 - 基于RabbitMQ延迟插件
在生成订单时,发送延时消息,设置消息24小时后触发,然后监听到这个消息后,进行订单的取消。
(延迟消息有两种实现方式:死信队列、延迟插件)
三、删除订单 (RabbitMQ)
发送延迟消息(默认24小时),监听器收到延迟消息后,修改订单状态。
基于插件实现延迟消息(消息暂存在交换机中)。
删除 orderInfo paymentInfo Alipay(支付宝)
只有 用户扫码未支付才能取消订单 -- 查询用户支付记录
为了防止关闭订单的一瞬间,用户进行了扫码操作,层级判断。
订单的主要状态有 未支付、已支付、待发货、已收货、待评价、已完成、已关闭等。
如果出现二维码不扫码, 不能关闭支付宝交易记录. pamentInfo orderInfo;
但仍然可以扫码生成支付宝订单支付if(订单过期时间<二维码有效期){
二维码有效期 = 订单时间;
}四、拆单业务
因为商品可能属于不同的商家,属于不同仓库 , 属于不同物流。都可能触发拆单。
前端将库存对应的 skuId,根据仓库ID将订单拆为多份,传递JSON到后端
什么时候拆单?
第一种: 下单的时候拆单
第二种: 支付的时候拆单 --- 采用
拆单之后对用户与商家是否有影响?没有1. 先获取到原始订单
2. [{"wareId":"1","skuIds":["2","10"]},{"wareId":"2","skuIds":["3"]}] 参数变为能操作的对象。
3. 获取子订单 根据原始订单给子订单赋值
4. 更改原始订单状态五、查看订单
用户可以点击我的订单获取我的订单列表
两张表的多表联查:orderInfo,orderDetail;pageParam,userId,返回分页对象
RabbitMQ
1. 如何保证消息不丢失 (保证消息持久化(参数)、发送确认(配置)、手动消费确认(配置))
2. 消息重试机制 - 借助Redis实现
(CorrelationData、消息发送确认类实现RabbitTemplate.ConfirmCallback, RabbitTemplate .ReturnCallback)
a. 实体类实现CorrelationData,封装了交换机、路由键、消息、重试次数、是否延迟消息等,Id作为key
b. 发送消息时候将CorrelationData 写入Redis,Id作为key;
c. 发送消息时携带CorrelationData,否则获取不到Id爆异常
3. 如何保证消息幂等性
a.使用数据(业务字段)方式,通过消息表记录消费状态
b.使用redis,setnx 模式,类似分布式锁;setnx key value,key 不存在时才设置value。
如果消费失败怎么办? 删除缓存key
1.设置value状态、value 0或1判断状态; 2.删除缓存key
4. 如何发送延迟消息(定时取消订单)
5. 如何解决消息积压问题
6. 如何保证消息的顺序性
超卖问题(库存只剩1件商品,多个用户同时下单)
我们采用的是下订单不减库存,只验证库存,支付成功后再去扣减库存,如果库存扣减失败,通知后台进行补货,如果这个商品不能补货,人工客户介入,和买家进行沟通,给予退款或相应补偿。
如果想实现不超卖,就得在下单时进行库存锁定,(可以使用数据库锁方式)然后减库存操作。分布式事务 -- MQ -- 最终一致性
项目中如何防止超卖的?项目中什么时候锁定库存的?
a. 商品详情页 校验库存,有货and无货
b. 购物车中每个购物项最大只能购买200件商品
c. 下单是否需要锁定库存下单时锁库存:用户是上帝 -- 京东
下单时不锁库存:用户是韭菜 -- 淘宝
减库存失败不退款!
订单--支付--库存 看做一个事务, mq 解决分布式事务,保证最终一致性
支付模块
接收到回调要做的事情:
- 验证回调信息的真伪
- 验证用户付款的成功与否
- 把新的支付状态写入支付信息表{paymentInfo}中。
- 通知电商其他模块
- 给支付宝返回回执。
- 支付宝支付需要的参数?
公共的参数:
支付网关\appID\请求数据类型(json)\编码\公钥和私钥\同步回调\异步回调
支付接口的参数:
订单交易编号\金额\标题\销售产品码
退款接口:
订单交易编号或者支付宝的交易号\退款金额
- 支付锁库存吗?
不锁,支付 用户在付钱呢,不能因为其他问题导致用户付钱失败.
- 支付日志表(信息表) 记录了什么东西? (主要是看表里的字段)
作用:为了和支付宝那边 对账
订单交易编号\金额\支付类型、支付宝交易号、回调时间和回调内容、支付状态
判断题
以下代码的运行结果?
public static void main(String[] args) {
Integer[] datas = {1,2,3,4,5};
List<Integer> list = Arrays.asList(datas);
list.add(5);
System.out.println(list.size());
//运行异常,不允许添加元素
}
算法题
求质数
@Test
public void test(){
int sum = 0;
System.out.println("101-200之间的素数有:");
for (int i = 101; i <= 200; i++) {
boolean flag = true;
for (int j = 2; j <= Math.sqrt(i); j++) { //开平方根
if (i % j == 0) {
flag = false;
break;
}
}
if (flag) {
sum++;
System.out.println(i);
}
}
System.out.println("总共有:"+sum);
}
用100元钱买100支笔,其中钢笔3元/支,圆珠笔2元/支,铅笔0.5元/支,问钢笔、圆珠笔和铅笔可以各买多少支?请写main方法打印需要买的数目。
public static void main(String[] args) {
double money = 100;
double pPrice = 3;
double yPrice = 2;
double qPrice = 0.5;
int count = 100;
for (int pen = 1; pen <= money / pPrice; pen++) {
for (int yuan = 1; yuan <= money / yPrice; yuan++) {
for (int qian = 1; qian <= money / qPrice; qian++) {
if (pen + yuan + qian == count && pen * pPrice + yuan * yPrice + qian * qPrice == money) {
System.out.println("购买" + pen + "支钢笔," + yuan + "支圆珠笔," + qian + "支铅笔");
}
}
}
}
}
有n步台阶,一次只能上1步或2步,共有多少种走法?
//答案一:递归
public static int f(int n) {
if (n <= 2)
return n;
int x = f(n - 1) + f(n - 2);
return x;
}
//答案二:不用递归
public static int f(int n) {
if (n <= 2)
return n;
int first = 1, second = 2;
int third = 0;
for (int i = 3; i <= n; i++) {
third = first + second;
first = second;
second = third;
}
return third;
}
//我的答案
public static void main(String[] args) {
System.out.println(MyMethod(3));
}
public static int MyMethod(int s){
int sum =s; //
int count = 0;
if(sum==1){
return 1;
}
//注意:int类型相除会省略小数位,由于此处除数为2,所以可行
for (int i = 0; i <= sum/2; i++) {
for (int j = 0; j <= sum/1; j++) {
if (i * 2 + j * 1 == sum) {
count++;
System.out.println(i + " -- " + j);
}
}
}
return count;
}
求1+2!+3!+...+20!的和
//方式1
public static void main(String[] args) {
long sum = 0;
for (int i = 1; i <= 20; i++) {
long temp = 1;
for (int j = 1; j <=i; j++) {
temp *= j;
}
sum += temp;
}
System.out.println("sum = " + sum);
}
//方式2
public static void main(String[] args) {
long sum = 0;
for (int i = 1; i <= 20; i++) {
sum += jieCheng(i);
}
System.out.println("sum = " + sum);
}
public static long jieCheng(int n){
long temp = 1;
for (int j = 1; j <=n; j++) {
temp *= j;
}
return temp;
}