1、java基础
1.1: java中的集合有哪些?详细说明
java 中的集合分为单列集合和双列集合,单列集合顶级接口为 Collection,双列集合顶级
接口为 Map。
Collection 的子接口有两个:List 和 Set。
Ø List 接口的特点:元素可重复,有序(存取顺序)。 list 接口的实现类如下:
Ø ArrayList:底层实现是数组,查询快,增删慢,线程不安全,效率高;
Ø Vector:底层实现是数组,查询快,增删慢,线程安全,效率低;【废弃】
Ø LinkedList:底层实现是链表,增删快,查询慢,线程不安全,效率高;
Ø Set 接口的特点:元素唯一,不可重复,无序。 Set 接口实现类如下:
Ø HashSet:底层实现 hashMap,数组+链表实现,不允许元素重复,无序。
Ø TreeSet:底层实现红黑二叉树,实现元素排序
Ø Map 接口的特点:key-value 键值对形式存储数据 Map 接口实现类如下:
Ø HashMap:底层数组+链表实现,线程不安全效率高;
Ø TreeMap:底层红黑二叉树实现,可实现元素的排序;
Ø LinkedHashMap:底层hashmap+linkedList 实现,通过 hashmap 实现 key-value 键值对存储,通过链表实现元素有序。
1.2: HashMap底层原理?
1、HashMap 底层是数组+链表(LinkedList)实现,hashMap 默认初始化容量为 16,也就
是说数组索引值为0-15,每个数组中存储一个链表。
2、当 hashmap 空间使用达到 0.75 后,会对数组进行扩容,新建数组,然后将元素拷
贝到新的数组中,每次扩容翻倍。
3、存储元素时,存储对象为 Map.Entry,entry对象包含四个信息,key、value、hash 值、
链表地址值,因为存储元素时,会根据hash 值%16 计算,元素存储数组中的索引位置。
4、但是有可能发生 hash 碰撞现象,即两个元素不相同,却有一样的 hash 值,这样的
话,就将元素在数组中存入链表中,以链表的形式进行元素的存储,第一个entry 存在链表顶端,再有hash 值一致的entry 存入,则链接在第一个元素之后。
5、put()方法存储元素时,根据 hash值定位数组,在链表中通过HashCode()和equals()
方法,定位元素的key,若没有一致的entry,则在链表中添加entry 对象,若找到一样的entry,则将oldValue返回,将新的value 存入到entry 中。
1.3: hashmap和hashtable的区别?
相同点:
1、二者都是 key-value 的双列集合;
2、底层都是通过数组+链表方式实现数据的存储;
不同点:
1、继承的父类不同
Hashtable 继承自 Dictionary 类,而 HashMap 继承自 AbstractMap类。但二者都
实现了Map 接口。
2、线程安全性不同
Hashtable 中的方法是 Synchronize 的,而 HashMap 中的方法在缺省情况下是非
Synchronize 的。在多线程并发
的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap
时就必须要自己增加同
步处理。
3、hashMap 允许 null 键和 null 值,只能有一个,但是 hashtable不允许。
4、HashMap 是 java 开发中常用的类,但是 Hashtable和 vector 一样成为了废弃类,
不推荐使用,因为有其他高效的方式可以实现线程安全,比如ConcurrentHashMap。
1.4 : ArrayList和Linkedlist区别?
相同点:
1、二者都是 List 接口的实现类,具有元素可重复,有序(存取顺序)特点;
2、二者都是线程不安全,效率高;
不同点:
1、数据结构:ArrayList底层数据结构是动态数组,LinkedList底层数据结构是双向链表;
2、随机访问效率:ArrayList比 LinkedList 在随机访问的时候效率要高,因为LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
3、增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比ArrayList 效率要高,
因为ArrayList 增删操作要影响数组内的其他数据的下标。
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用LinkedList。
1.5 : 并发编程的三要素是那些?你是怎么理解的?
1、原子性
原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操
作打断,要么就全部都不执行。
package demo2;
class Student{
int age = 0;
}
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
int a;
Student b;
@Override
public void run() {
System.out.println(name+" a:"+(++a));
System.out.println(name+" b:"+(++b.age));
}
}
class Test{
public static void main(String[] args) {
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
int num = 0;
Student stu = new Student();
t1.a = num;
t1.b = stu;
t2.a = num;
t2.b = stu;
t1.start();
t2.start();
}
}
2、可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他
线程可以立即看到修改的结果。
3、有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行。
1.6 : 创建线程的有哪些方式?
1、继承 Thread 类
2、实现 Runnable 接口
3、实现 Callable 接口
4、通过线程池获取线程对象,实现多线程
1.7 : Runnable和Callable的区别?
runnable 没有返回值,callable 可以拿到有返回值,callable可以看作是 runnable 的补充
1.8 : Java线程具有五中基本状态是那些?
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到
程序 start() 这个线程。
就绪状态:
当线程对象调用了 start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线
程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复
杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入
阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。TERMINATED 执行完成
1.9 : 什么是线程池?有哪几种创建方式?
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为: newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。(compiletable) newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ThreadPoolExecutor:最原始的创建线程池的方式
1.10 : 什么是乐观锁和悲观锁?
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
Java中的乐观锁基本都是通过CAS操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。
Java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。
1.11 : sleep方法和wait方法有什么区别?
类的不同:sleep() 来自 Thread,wait() 来自 Object。
释放锁:sleep() 不释放锁;wait() 释放锁。
用法不同:sleep() 时间到会自动恢复;wait() 可以使用notify()/notifyAll()直接唤醒
1.12 : ThreadLocal是什么?有什么用?
ThreadLocal 是一种多线程访问技术,用于隔离多线程。翻译为线程局部变量,即使用 ThreadLocal,它将为每一个线程生成特有的一份线程局部变量,多线程之间是无法相互访问。
1.13 : 为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用?
(1)为什么wait()必须在同步(Synchronized)方法/代码块中调用?
答:调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁。
(2)为什么notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用?
答:notify(),notifyAll()是将锁交给含有wait()方法的线程,让其继续执行下去,如果自身没有锁,怎么叫把锁交给其他线程呢;(本质是让处于入口队列的线程竞争锁)
1.14: 悲观锁和乐观锁的原理及应用场景
悲观锁: 顾名思义就是很悲观,每次拿数据都会认为别的线程会修改该数据,所以会给数据上锁; 这样抢到锁的线程运行,取到数据做操作, 这期间其他线程想要访问该数据时,都是阻塞block挂起状态,操作不了; 核心就是不支持多并发,是单线程操作,通过抢占时间片的方式来抢锁的使用权,把并发变成了串行。 共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。 应用场景: 悲观锁适用于多写的场景,保证线程安全和数据安全; mysql的行锁、表锁、读锁、写锁; java中的synchronized; 乐观锁 顾名思义就是很乐观,每次拿数据都认为别的线程不会修改数据,因此不会给数据上锁; 但会在数据更新时判断一下,在此期间其他线程有没有对该数据做更新,最终通过多个线程的逐一更新获取数据的最终值; 判断单一线程操作数据期间,其他线程有没有对该数据做修改用的是,version版本号机制、CAS算法。 乐观锁支持多线程并发,每个线程在不同的时间节点对数据做更新操作,每次更新时候都会判断其他线程是否对数据做了更新。 (1)version版本号机制 一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。 当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。 sql源码: update table set x=x+1, version=version+1 where id=#{id} and version=#{version}; (2)CAS算法机制 即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。 应用场景: 乐观锁适用于多读的场景,获取数据不再创建、销毁锁,减少了锁的开销,加大了数据的吞吐量, Redis等非关系型数据库 ps:Redis是单线程操作,把事务封闭在单一线程中,避免了线程的安全问题,所以里面没有加悲观锁; 不过对于依赖多个Redis操作的复合操作来说,还是需要加锁的,而且有可能是分布式锁,也可以用LUA脚本,用任务队列的方式解决多任务并发的问题。
1.15:jdbc事务与事务隔离级别
事务的四大特点(ACID) actomicity(原子性) 表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败 consistency(一致性) 表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态 isolation(隔离性) 事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。 durability(持久性) 持久性事务完成之后,它对于系统的影响是永久性的。
事务的隔离级别 Read Uncommitted(读取未提交内容) 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容) 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读) 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化) 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
1.16:Java 到底是值传递还是引用传递
Java 只支持值传递。 Java 程序员之所以容易搞混值传递和引用传递,主要是因为 Java 有两种数据类型,一种是基本类型,比如说 int,另外一种是引用类型,比如说 String或对象。 基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack(栈)中,而对象存储在 heap(堆)中。
1.17:介绍JVM的内存管理采用分代的策略及调优的参数
分代的策略 1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(命名为A和B)。当对象在堆创建时,将进入年轻代的Eden Space。垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制到Old Gen。同时,在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old Gen。扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和B Suvivor Space。这么做主要是为了减少内存碎片的产生。
我们可以看到:Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保尽量不存在内存碎片,采用空间换时间的方式来加速内存中不再被持有的对象尽快能够得到回收。
2)年老代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。年老代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边,也就是内存整理)。当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。
3)持久代(Perm Gen):持久代主要存放类定义、字节码和常量等很少会变更的信息。
JVM调优参数 JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。 jconsole:用于对 JVM 中的内存、线程和类等进行监控; jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
-Xms2g:初始化推大小为 2g; -Xmx2g:堆最大内存为 2g; -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4; -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2; –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合; -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合; -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合; -XX:+PrintGC:开启打印 gc 信息; -XX:+PrintGCDetails:打印 gc 详细信息。
1.18:介绍使用Java线程池优势及使用配置
线程池的优势 (1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗; (2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行; (3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。 (4)提供更强大的功能,延时定时线程池。 线程池的主要参数 corePoolSize(线程池基本大小) maximumPoolSize(线程池最大大小) keepAliveTime(线程存活保持时间) workQueue(任务队列) threadFactory(线程工厂) handler(线程饱和策略) 如何配置线程池
CPU密集型任务 尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。 IO密集型任务 可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。 混合型任务 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。 因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。 因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。
1.19:统计1亿个的IP中出现IP次数最多的10个IP
可先将1亿IP分到1000个小文件中去,再对每个小文件中的IP进行hashmap计数统计并按数量排序,最后归并或者最小堆依次处理每个小文件的top10以得到最后的结果。
1.20:lamda表达式如何遍历集合
List<String> items = new ArrayList<>(); items.add("A"); items.add("B"); items.add("C"); // Lambda 表达式遍历(JDK 1.8) System.out.println("\n第三种遍历方式:Lambda 表达式遍历 List 集合"); items.forEach(item->{ System.out.println(item); }); // Lambda 表达式遍历(JDK 1.8) System.out.println("\n第四种遍历方式:Lambda 表达式遍历 List 集合"); items.forEach(System.out::println);
1.21:多线程 synchronized 和 volatile的理解
1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
《Java编程思想》上说,定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性。 4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
5、当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,如Range类的lower和upper边界,必须遵循lower<=upper的限制。
6、使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。
2、spring +springMVC
2.1: 介绍一下spring
关于Spring的话,我们平时做项目一直都在用,不管是使用ssh还是使用ssm,都可以整合。Spring里面主要的就三点,也就是核心思想,IOC控制反转,DI依赖注入,AOP切面编程
我先来说说IOC吧,IOC就是spring里的控制反转,把类的控制权呢交给spring来管理,我们在使用的时候,在spring的配置文件中,配置好bean标签,以及类的全路径,如果有参数,然后在配置上相应的参数。这样的话,spring就会给我们通过反射的机制实例化这个类,同时放到spring容器当中去。
我们在使用的时候,需要结合DI依赖注入使用,把我们想使用的类注入到需要的地方就可以,依赖注入的方式有构造器注入、getset注入还有注解注入。我们现在都使用@autowired
或者@Resource
注解的方式注入。
然后就是AOP切面编程,他可以在不改变源代码的情况下对代码功能的一个增强。我们在配置文件中配置好切点,然后去实现切面的逻辑就可以实现代码增强,这个代码增强,包括在切点的执行前,执行中,执行后都可以进行增强逻辑处理,不用改变源代码,这块我们项目中一般用于权限认证、日志、事务处理这几个地方。
2.2 : AOP的实现原理
这块呢,我看过spring的源码,底层就是动态代理来实现的,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了 ,目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:
-
JDK 动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy 利用InvocationHandler 动态创建一个符合接口的的实例,生成目标类的代理对象。
-
如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。不过在我们的业务场景中没有代理过final的类,基本上都代理的controller层实现权限以及日志,还有就是service层实现事务统一管理
2.3 : 详细介绍下IOC容器
Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext
BeanFactory 是基础类型的 IoC 容器,提供了完整的 IoC 服务支持。简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。
他俩的主要区别在于,如果 Bean 的某一个属性没有注入,则使用 BeanFacotry 加载后,在第一次调用 getBean() 方法时会抛出异常,但是呢ApplicationContext 会在初始化时自检,这样有利于检查所依赖的属性是否注入。
因此,在实际开发中,通常都选择使用 ApplicationContext
2.4 : @Autowired和
@Resource`的区别
@Autowired
默认是按照类型注入的,如果这个类型没找到,会根据名称去注入,如果在用的时候需要指定名称,可以加注解@Qualifier("指定名称的类")
@Resource
注解也可以从容器中注入bean,默认是按照名称注入的,如果这个名称的没找到,就会按照类型去找,也可以在注解里直接指定名称@Resource(name="类的名称")
2.5 : springbean的生命周期
生命周期这块无非就是从创建到销毁的过程
spring容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。
而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。
整体来说就4个步骤:实例化bean,属性赋值,初始化bean,销毁bean
-
首先就是实例化bean,容器通过获取BeanDefinition对象中的信息进行实例化
-
然后呢就是属性赋值,利用依赖注入完成 Bean 中所有属性值的配置注入
-
接着就是初始化bean,如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
-
最后就是销毁bean,和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑
2.6 : springbean的作用域
Spring 容器中的 bean 可以分为 5 个范围:
(1)singleton:单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。 controller、service、dao层基本都是singleton的
(2)prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
(3)request:在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
(4)session:在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
(5)global-session:全局作用域,在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。
2.7 : 事务的传播特性
解读:事务的传播特性发生在事务方法与非事物方法之间相互调用的时候,在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务
属性名称 | 值 | 描 述 |
---|---|---|
PROPAGATION_REQUIRED | required | 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将创建新事务,默认就是这个 |
PROPAGATION_SUPPORTS | supports | 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将以非事务状态执行 |
PROPAGATION_MANDATORY | mandatory | 支持当前事务。如果 A 方法没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | requires_new | 将创建新的事务,如果 A 方法已经在事务中,则将 A 事务挂起 |
PROPAGATION_NOT_SUPPORTED | not_supported | 不支持当前事务,总是以非事务状态执行。如果 A 方法已经在事务中,则将其挂起 |
PROPAGATION_NEVER | never | 不支持当前事务,如果 A 方法在事务中,则抛出异常 |
PROPAGATION.NESTED | nested | 嵌套事务,底层将使用 Savepoint 形成嵌套事务 |
2.8 : 事务的隔离级别
Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog实现的。隔离级别有四种
-
Read uncommitted (读未提交):读未提交,允许另外一个事务可以看到这个事务未提交的数据,最低级别,任何情况都无法保证。
-
Read committed (读已提交):保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新,可避免脏读的发生。
-
Repeatable read (可重复读):保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新,可避免脏读、不可重复读的发生。
-
Serializable (串行化):一个事务在执行的过程中完全看不到其他事务对数据库所做的更新,可避免脏读、不可重复读、幻读的发生。
2.9 : spring中都用了哪些设计模式
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
2.10 : springMVC的执行流程
(1)用户发送请求至前端控制器 DispatcherServlet;
(2) DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取 Handle;
(3)处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter 处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)Handler 执行完成返回 ModelAndView;
(7) HandlerAdapter 将 Handler 执 行 结 果 ModelAndView 返 回 给DispatcherServlet;
(8)DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器进行解析;
(9)ViewResolver 解析后返回具体 View;
(10)DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet 响应用户。
2.11 : springMVC接收前台参数的几种方式
-
1、如果传递参数的时候,通过ur1拼接的方式,直接拿对象接收即可,或者string、 int
-
2、如果传递参数的时候,传到后台的是js对象,那么必须使用对象接收,并且加@requestBody
-
3、get的请求方式,所有的参数接收都使用普通对象或者string、int
-
4、在用form表单提交的时候,所有的参数接收都使用普通对象或者string、int
2.12 : springMVC中的常用注解
@RequestMapping:指定类或者方法的请求路径,可以使用method字段指定请求方式
@GetMapping、@PostMapping:规定了请求方式的方法的请求路径
@RequestParam:接收单一参数的
@PathVariable:用于从路径中接收参数的
@CookieValue:用于从cookie中接收参数的
@RequestBody:用于接收js对象的,将js对象转换为Java对象
@ResponseBody:返回json格式数据
@RestController:用在类上,等于@Controller+@ResourceBody两个注解的和,一般在前后端分离的项目中只写接口时经常使用,标明整个类都返回json格式的数据
3、mybatis 等持久层
3.1 : mybatis 的优缺点?
Mybatis是一个半ORM(对象关系映射)的持久层框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程,使用时直接编写原生态sql。
l 优点:
1:基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理,提供XML标签,支持编写动态SQL语句,并可重用;
2:很好的与各种数据库兼容;
3:提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护。
4:与JDBC相比,消除了JDBC大量冗余的代码,不需要手动开关连接,能够与Spring很好的集成
l 缺点:
1:SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求;
2:SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库;
3.2 : MyBatis 、Hibernate有哪些不同?
首先Hibernate是一个完全面向对象的持久层框架,mybatis是一个半自动化的持久层框架。
开发方面:hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发, Mybatis 属于半自动化,sql需要手工完成,稍微繁琐,但是如果对于庞大复杂的系统项目来说,复杂的sql语句较多,选择hibernate 就不是一个好方案。
sql优化方面:Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能, Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;
对象管理方面:Hibernate是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;Mybatis 需要自行管理映射关系。
缓存方面:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存,MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。
总之:Mybatis 小巧、方便、高效、简单、直接、半自动化;Hibernate 强大、方便、高效、复杂、间接、全自动化。
3.3 : 当实体类中的属性名和表中的字段名不一样 ,怎么办?
第一种方法:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致;
第二种方法:通过<resultMap>来映射字段名和实体类属性名的一一对应的关系;
第三种方法:在实体类通过@Column注解也可以实现;
3.4 : mybatis 如何执行批量插入?
第一种就是普通的xml中insert语句可以写成单条插入,在调用方循环N次;
第二种是xml中insert语句写成一次性插入一个N条的list,语法如下(核心便签 foreach collection***)
<insert id="insertBatch" > insert into person ( <include refid="Base_Column_List" /> ) values <foreach collection="list" item="item" index="index" separator=","> (null,#{item.name},#{item.sex},#{item.address}) </foreach> </insert>
3.5 : Mybatis有哪些动态sql?
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签:
trim、where 、 set 、foreach、if 、choose、when 、otherwise 、bind
3.6 : MyBatis有哪写标签?
除了常见的select|insert|updae|delete标签之外,还有<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态sql的9个标签,其中
<sql>为sql片段标签,通过
<include>标签引入sql片段,
<selectKey>`为不支持自增的主键生成策略标签。
3.7 :Mybatis的一级缓存、二级缓存说说?
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/>
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
3.8 : 谈谈 MyBatis框架及原理?
MyBatis的主要设计目的就是让我们对执行SQL语句时对输入输出的数据管理更加方便,所以方便地写出SQL和方便地获取SQL的执行结果才是MyBatis的核心竞争力;
他的执行流程包括
l 读取配置文件,配置文件包含数据库连接信息和Mapper映射文件或者Mapper包路径。
l 有了这些信息就能创建SqlSessionFactory,SqlSessionFactory的生命周期是程序级,程序运行的时候建立起来,程序结束的时候消亡
l SqlSessionFactory建立SqlSession,目的执行sql语句,SqlSession是过程级,一个方法中建立,方法结束应该关闭
l 当用户使用mapper.xml文件中配置的的方法时,mybatis首先会解析sql动态标签为对应数据库sql语句的形式,并将其封装进MapperStatement对象,然后通过executor将sql注入数据库执行,并返回结果。
l 将返回的结果通过映射,包装成java对象。
4、SpringBoot
4.1 : 什么是springboot
SpringBoot是Spring项目中的一个子工程,其实人们把Spring Boot 称为搭建程序的脚手架
。其最主要作用就是帮我们快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让我们关注与业务而非配置。
4.2 : 为什么要用springboot
Spring Boot 优点非常多,如: 一、独立运行 Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的 jar包就能独立运行,所有的依赖包都在一个jar包内。 二、简化配置 spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。 三、自动配置 Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。 四、无代码生成和XML配置 Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。 五、应用监控 Spring Boot提供一系列端点可以监控服务及应用,做健康检测
4.3 : springboot有哪些优点
-
减少开发,测试时间和努力。
-
使用 JavaConfig 有助于避免使用 XML。
-
避免大量的 Maven 导入和各种版本冲突。
-
通过提供默认值快速开始开发。
-
没有单独的 Web 服务器需要。这意味着你不再需要启动 Tomcat,Glassfish或其他任何东西。
-
需要更少的配置 因为没有 web.xml 文件。只需添加用@ Configuration 注释的类,然后添加用@Bean 注释的方法,Spring 将自动加载对象并像以前一样对其进行管理。您甚至可以将@Autowired 添加到 bean 方法中,以使 Spring 自动装入需要的依赖关系中。
-
基于环境的配置 使用这些属性,您可以将您正在使用的环境传递到应用程序:-Dspring.profiles.active = {enviornment}。在加载主应用程序属性文件后,Spring 将在(application{environment} .properties)中加载后续的应用程序属性文件。
application.yml
spring.active.profile=dev
application-dev.yml
application-test.yml
application-prod.yml
4.4 : Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。 @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 @ComponentScan:Spring组件扫描,从当前类所在的包以及子包扫描,之外的包扫描不到,所以我们在开发的时候,所有的类都在主类的子包下
4.5 : springboot项目有哪几种运行方式
-
打包用命令或者放到容器中运行
-
用 Maven/Gradle 插件运行
-
直接执行 main 方法运行
4.6 : 如何理解springboot中的starters?
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用Spring JPA访问数据库,只要加入springboot-starter-data-jpa启动器依赖就能使用了。Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
4.7 : springboot自动配置原理
@SpringBootApplication @SpringBootConfiguration: @Configuration @EnableAutoConfiguration: @AutoConfigurationPackage:将启动类所在的包及子包中所有的组件扫描到ioc容器 @Import(AutoConfigurationImportSelector.class):会加载META-INF/spring.factories文件, 并将该文件中的自动配置类加载到Spring容器中进行自动配置工作 @ComponentScan:自定要扫描的包
这个就得从springboot项目的核心注解@SpringbootApplication说起了,这个注解包含了三个注解,其中一个是@EnableAutoConfiguration注解,这个注解主要是开启自动配置的,这个注解会"猜"你将如何配置 spring,前提是你已经添加 了 jar 依赖项,比如项目中引入了 spring-boot-starter-web ,这个包里已经添加 Tomcat 和 SpringMVC,这个注解节就会自动假设您在开发一个 web 应用程序并添加相应的 spring 配置,springboot默认有一个spring-boot-autoconfigure包,大多数常用的第三方的配置都自动集成了,像redis、es等,这里边有一个META-INF/spring.factories
文件,这里边定义了所有需要加载的bean的全路径,spring会根据反射的原理,创建这些对象,放到IOC容器中,加载时需要的参数,通过JavaConfig的方式加载配置文件中的参数然后创建了对应的对象,这就是自动配置的原理
5、SpringCloud +Dubbox
5.1 : 什么是springcloud?谈一下你对springcloud的认识?
为微服务体系开发中的架构问题,提供了一整套的解决方案,它提供了微服务开发所需要的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。最重要的是,跟SpringBoot框架一起使用的话,会让开发微服务架构的云服务非常方便。
Spring Cloud是一个基于Spring Boot实现的云应用开发工具;Spring boot专注于快速、方便集成的单个个体,Spring Cloud是关注全局的服务治理框架;spring boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring boot来实现.
5.2 : 你项目总spring cloud 的核心组件用到哪些?详细说一下他们各自的作用及请求的流程?
6个核心组件 Eureka、ribbon、feign、hystrix、zuul、config+bus,详细信息及请求流程自我总结,参照之前发的各个组件的作用就完整请求的一套流程
5.3 : SpringCloud和Dubbo比较,谈谈个人看法?
dubbo由于是二进制的传输,占用带宽会更少
springCloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大
dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决
springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
dubbo的注册中心可以选择zk,redis等多种,springcloud的注册中心只能用eureka或者自研.
springCloud和Dubbo都是现在主流的微服务架构,SpringCloud是Apache旗下的Spring体系下的微服务解决方案,Dubbo是阿里系的分布式服务治理框架;从技术维度上,个人觉得其实SpringCloud远远的超过Dubbo,,Dubbo本身只是实现了服务治理,而SpringCloud现在以及后续有好多个子项目以后还可能会更多。服务的调用方式Dubbo使用的是RPC远程调用,而SpringCloud使用的是 Rest API,其实更符合微服务官方的定义,服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,而SpringCloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,SpringCloud还支持断路器等其他组件
5.4 : Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别?
1:ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的,
2:Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,但是查询到的数据并不是最新的,因为自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务,Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点,当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性),
3:Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪,
4:还有 Eureka本质上是一个工程,而ZooKeeper只是一个进程
5.5 : spring、springMVC、springboot、springcloud 的区别与联系?
Spring是一个生态体系(也可以说是一个技术体系)是集大成者,包括Spring Framework、Spring Boot、Spring Cloud等;
Springframework框架他们的基础都是Spring 的 ioc和 aop, ioc 提供了依赖注入的容器, aop解决了面向横切面的编程(比如日志、事务),然后在此两者的基础上实现了其他延伸产品的高级功能。
Spring 最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件。大家觉得挺好用,于是按照这种模式搞了一个 MVC框架(一些用Spring 解耦的组件),用来开发 web 应用。然后有发现每次开发都写很多样板代码,为了简化工作流程,于是开发出了一些“懒人整合包”(starter),这就是 SpringBoot,
所以简练的说,spring是一个引擎,springMVC是基于Spring的一个MVC框架,springboot是一套快速开发整合包,Spring Cloud事实上是一整套基于Spring Boot的微服务解决方案。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:注册中心、服务发现、限流、网关、等
spring是一个生态体系,springframework是一个一站式的轻量级的java开发框架,核心是控制反转(IoC)和面向切面(AOP),针对于开发的WEB层(springMVC)、业务层(IoC)、持久层(jdbcTemplate)等都提供了多种配置解决方案
6、nginx
6.1 : Nginx的一些特性?
反向代理、负载均衡、动静分离
反向代理,当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。
负载均衡
举个例子来说就是在配置三台nginx服务器,当在客户端访问nginx是刷新页面会出现三个不同的页面。
(1)Nginx的upstream目前支持以下几种方式的分配
1、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
2、weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
2、ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
3、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
4、url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
动静分离
这里就以Nginx作为代理服务器的同时,也使用其作为静态资源的服务器。
静态资源通过绝对路径去访问,放在nginx服务器当中。
动态资源通过url拼接字符串的方式去访问例如tomcat服务器。
6.2 :请解释Nginx如何处理HTTP请求?
Nginx使用反应器模式;
主事件循环等待操作系统发出准备事件的信号,这样数据就可以从套接字读取,在该实例中,读取到缓冲区并进行处理。单个线程可以处理数万个并发连接。
6.3 :使用“反向代理服务器”的优点是什么?
(1)提高访问速度
由于目标主机返回的数据会存在代理服务器的硬盘中,因此下一次客户再访问相同的站 点数据时,会直接从代理服务器的硬盘中读取,起到了缓存的作用,尤其对于热门站点 能明显提高请求速度。
(2)防火墙作用
由于所有的客户机请求都必须通过代理服务器访问远程站点,因此可在代理服务器上设 限,过滤某些不安全信息。
(3)通过代理服务器访问不能访问的目标站点
互联网上有许多开发的代理服务器,客户机可访问受限时,可通过不受限的代理服务器 访问目标站点,通俗说,我们使用的翻墙浏览器就是利用了代理服务器,可直接访问外网。
6.4 : 代理设计中的正向代理和反向代理?
正向代理
代理客户;
隐藏真实的客户,为客户端收发请求,使真实客户端对服务器不可见;
一个局域网内的所有用户可能被一台服务器做了正向代理,由该台服务器负责 HTTP 请求;
意味着同服务器做通信的是正向代理服务器;
反向代理
代理服务器;
隐藏了真实的服务器,为服务器收发请求,使真实服务器对客户端不可见;
负载均衡服务器,将用户的请求分发到空闲的服务器上;
意味着用户和负载均衡服务器直接通信,即用户解析服务器域名时得到的是负载均衡服务器的 IP ;
共同点
都是做为服务器和客户端的中间层
都可以加强内网的安全性,阻止 web 攻击
都可以做缓存机制,提高访问速度
区别
正向代理其实是客户端的代理,反向代理则是服务器的代理。
正向代理中,服务器并不知道真正的客户端到底是谁;而在反向代理中,客户端也不知道真正的服务器是谁。
作用不同。正向代理主要是用来解决访问限制问题;而反向代理则是提供负载均衡、安全防护等作用。
6.5 :Nginx常用命令?
nginx -s stop 快速关闭Nginx,可能不保存相关信息,并迅速终止web服务。
nginx -s quit 平稳关闭Nginx,保存相关信息,有安排的结束web服务。
nginx -s reload 因改变了Nginx相关配置,需要重新加载配置而重载。
nginx -s reopen 重新打开日志文件。
nginx -c filename 为 Nginx 指定一个配置文件,来代替缺省的。
nginx -t 不运行,而仅仅测试配置文件。nginx 将检查配置文件
的语法的正确性,并尝试打开配置文件中所引用到的文件。
nginx -v 显示 nginx 的版本。
6.6 : Nginx是如何实现高并发的?
异步,非阻塞,使用了epoll 和大量的底层代码优化。
举例:
如果一个server采用一个进程负责一个request的方式,那么进程数就是并发数。正常情况下,会有很多进程一直在等待中。而nginx采用一个master进程,多个woker进程的模式。
master进程主要负责收集、分发请求。每当一个请求过来时,master就拉起一个worker进程负责处理这个请求。
同时master进程也负责监控woker的状态,保证高可靠性
woker进程一般设置为跟cpu核心数一致。nginx的woker进程在同一时间可以处理的请求数只受内存限制,可以处理多个请求。
Nginx 的异步非阻塞工作方式正把当中的等待时间利用起来了。在需要等待的时候,这些进程就空闲出来待命了,因此表现为少数几个进程就解决了大量的并发问题。
7、redis
7.1 : redis的数据类型,以及每种数据类型的使用场景?
1.String
这个没啥好说的,最常规的 set/get 操作,Value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。我们项目用户登录获取短信验证码、还有首页广告位及banner图这些热点数据都是放的String类型
2.Hash
这里 Value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。
我在做单点登录的时候,就是用这种数据结构存储用户信息,以 CookieId 作为 Key,设置 30 分钟为缓存过期时间,能很好的模拟出类似 Session 的效果。
3.List
使用 List 的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。
4.Set
因为 Set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用 JVM 自带的 Set 进行去重?
因为我们的系统一般都是集群部署,使用 JVM 自带的 Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
5.Sorted Set
Sorted Set多了一个权重参数 Score,集合中的元素能够按Score 进行排列。可以做排行榜应用,取TOP N 操作。
Sorted Set
可以用来做延时任务。最后一个应用就是可以做范围查找。
7.2 : Redis持久化机制?
RDB 和AOF两种方式
RDB持久化机制
对Redis中的数据执行周期性的持久化
AOF机制
将每条写命令作为日志,以append-only模式写入一个日志文件,在Redis重启时,通过回放日志中的写入指令来重构整个数据,
我们项目用的是使用RDB的方式
7.3 : 缓存穿透、缓存击穿、缓存雪崩、缓存预热、缓存同步?
-
缓存穿透:
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空,这就相当于进行了两次无用的查询。像这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题
解决办法
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空,不管是数据不存在,还是系统故障,我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
-
缓存击穿:
是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个key不停进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞.
解决办法
1:设置热点key永不过期,2:设置redis分布式锁
-
缓存雪崩
是指在某一个时间段,缓存集中过期失效,比方说:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期,所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决办法:
一个简单方案就是缓存失效时间分散开,不设置固定的实效时间,采用随机失效的策略来解决。
最多的解决方案就是锁,或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上
-
缓存预热:
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
操作方式:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
-
缓存更新:
1、定时去清理过期的缓存;
2.、当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存
7.4: redis分布式锁
这个分布式锁这里,我们原来传统的项目都在单台服务器上部署用java里的锁synchronized这个同步锁就行,但是他这个是针对对象的锁,但是我们分布式的项目需要把项目部署到多台服务器上,每台服务器的对象都不同,所以就得考虑用分布式锁,这块实现起来也比较简单,其实这个锁就是redis中的一个key-value的一对值,在使用的时候吧,首先使用setnx方法进行尝试加锁,并可以设置过期时间,如果返回1则代表加锁成功,然后立即对这个锁设置一个实效时间,防止服务宕机,锁一致存在,在处理完业务逻辑之后,删除锁就行了,其他线程就可以获取锁进行业务了
7.5: redis主从复制
通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步,配置非常简单,只需要在从节点配置slave of
主节点的ip即可,如果有密码,还需要配置上密码,从节点只能读数据,不能写数据
全量同步主要发生在初次同步的时候,大概的步骤是
-
从服务器连接主服务器,发送SYNC命令;
-
主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
-
主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
-
从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
-
主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
-
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
还有就是增量同步,主要发生在redis的工作过程中,Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。x
7.6 : redis集群
Redis本身就支持集群操作redis_cluster,集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选举主数据库和从数据库,为了保证选举过程最后能选出leader,就一定不能出现两台机器得票相同的僵局,所以一般的,要求集群的server数量一定要是奇数,也就是2n+1台,并且,如果集群出现问题,其中存活的机器必须大于n+1台,否则leader无法获得多数server的支持,系统就自动挂掉。所以一般是3个或者3个以上的奇数节点。
Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控redis主、从数据库是否正常运行,主数据库出现故障自动将从数据库转换为主数据库
我们公司搭建的redis集群是用的ruby脚本配合搭建的,我们一共搭建了6台服务器,3主3备,他们之间通信的原理是有一个乒乓协议进行通信的,我再给你说下一他们往里存储数据的机制吧,其实这个redis搭建好集群以后每个节点都存放着一个hash槽,每次往里存储数据的时候,redis都会根据存储进来的key值算出一个hash值,通过这个hash值可以判断到底应该存储到哪一个哈希槽中,取的时候也是这么取的,这就是我了解的redis集群
7.7 :Redis如何实现微信步数排行榜功能
Redis几种数据结构 1.String 字符串类型 2.Hash (哈希) 3.链表 4.Set 集合 5.zset 有序集合 可以使用Redis的有序集合ZSET来实现 微信步数排行榜的需求: 排行榜是以日期为单位的,历史日期的排行榜是可以查看的 排行榜可能并不会显示所有好友的步数,比如我的微信有349位好友,但排行榜从来没有显示过这么多,假设最多只显示步数前200的好友 步数是异步更新的,所以每隔一段时间步数同步后,排行榜都会变化 排行榜中,好友头像和微信昵称可以理解为不变的(变动的几率小,就像热搜榜中的标题和Url),但步数和点赞数是可变的 使用Redis的ZSET数据结构 设置key时,基于微信号和日期,比如我的微信是zwwhnly,今天的日期是2020-06-01,那么key就可以设计为:StepNumberRanking:zwwhnly:20200601 设置value时,将好友的昵称作为成员member,将好友的步数作为分值score
7.8:redis 内存淘汰策略配置
所谓淘汰策略就是按配置淘汰众多redis中无效或弃用的key,以达到回收内存的目地
在配置文件有一行:
maxmemory-policy volatile-lru
以下是配置对应的策略:
noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
allkeys-lru:在主键空间中,优先移除最近未使用的key。(推荐)
volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。
allkeys-random:在主键空间中,随机移除某个key。
volatile-random:在设置了过期时间的键空间中,随机移除某个key。
volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
8、RabbitMQ
8.1 : rabbitmq的特性及为什么要使用?
特性:
-
消息确认机制 (自动确认和手动确认) 默认使用自动确认
-
自动确认: acknowledge='none'
-
手动确认: acknowledge='manual'
-
-
持久化
-
交换器持久化、队列持久化和消息的持久化
-
-
过期时间(TTL)
-
跟Redis的过期时间一样
-
为什么要使用:
-
异步处理 : 调用者无需等待
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间
-
应用程序解耦合 : 解决了系统之间耦合调用的问题
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
-
削峰 : 抵御洪峰流量,保护了主业务
并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常.
比如:
在下单的时候就会往数据库写数据,但是在高峰期时候,并发量会突然激增,这时候直接访问数据库数据库会卡死. 这时候可以通过mq将消息保存起来,然后按照自己的消费能力来消费,这样慢慢写入数据库。
8.2 :如何保证数据一定被发送?
使用本地消息表(mysql)和定时任务(quartz)和MQ的响应机制(ComfirmFallback,ReturnFallback)
在发送消息时,在本地的mysql数据库中进行记录一条待发送信息.一个定时任务不断检查,是否发送成功,如果发送成功,将记录状态修改.如果发送失败者再次发送
8.3: rabbitmq如何保证消息不丢失?保证数据一定会被消费,如何保证?(两者一个意思)
RabbitMQ中,消息丢失可以简单的分为三种:生产者丢失消息,消费者丢失消息和消息队列丢失消息
生产者丢失消息
解决方案: 从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息
消息队列丢失消息
解决方案: 消息持久化。durable设置为true
消费者丢失消息
解决方案:
设置为手动ack确认机制,当消费者出现异常或者服务宕机时,MQ服务器不会删除该消息,而是会把消息重发给绑定该队列的消费者,
消息不重复消费
解决方案:使用本地消息去重表,每次消费先到表中查询是否消费的标识,如果消费过就直接ack回滚。
8.4 : rabbitmq实现数据同步后,消费者挂掉这么办,回滚代码这么实现?
实现ReturnCallback接口重写returnedMessage方法,回滚发送方数据同步操作,具体回滚可以读取发送方记录表中业务ID然后进行回退操作。
8.5 : 消息堆积是如何产生,如何解决消息堆积?
当消息生产的速度长时间,远远大于消费的速度时。就会造成消息堆积,比如
-
生产者突然大量发布消息
-
消费者挂掉
解决
-
增加消费者的多线程处理
-
增加多个消费者
-
8.6: rabbitmq常用的消费模式?
RabbitMQ工作模式:
1、简单模式 HelloWorld
一个生产者、一个消费者
2、工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系)
3、发布订阅模式 Publish/subscribe
需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing
需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic
需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
8.7 : rabbitmq什么时候变成死信队列?
-
消息拒绝并且没有设置重新入队
-
消息过期
-
消息堆积,并且队列达到最大长度,先入队的消息会变成DL
9、ElaticSearch
9.1 : 为什么使用ElasticSearch?
在我们项目中主要是负责检索xxxxx信息。xxx信息构成是比较复杂的,并且数据量巨大,至少会有几十万,如果使用mysql做检索,效率会非常低,并且对mysql造成很大的压力。因此采用了ElasticSearch。
9.2 : ElasticSearch的数据如何进行同步的?
一共有两种情况的同步。
第一种:初始全量数据同步
我们在B端xxx页面上开放了一键导入功能,分批从mysql中取出,组装数据并保存到es中。
第二种:增量同步
在对xxx进行新增、修改、删除时,不仅更新mysql中的数据,还会同步ES中的数据,这一块我们采用的时RabbitMQ异步同步到ES中。
9.3 : ES的倒排索引了解吗,怎么理解的?
ES查询数据的方式就是依赖倒排索引,倒排索引,就是关键字与文档的映射。
查询数据时,根据关键字先扫描分词列表,然后根据匹配到的分词在映射查询到相关的文档数据。
9.4 : ES的内部存储结构了解吗?
了解,es内部默认就是分布式的存储结构。每当创建一个索引库的时候,需要指定它对应的分片数和副本数,分片就是把数据分布式的存放在分片上,所有分片的数据加起来就是整个索引库的数据量,分片目的时保证数据的高并发。副本数是指需要备份的数量,指定副本的目的就是保证数据的高可用。
9.5 : ES支持的查询方式有哪些?
我了解的ES支持简单搜索(简单搜索:通过url拼接查询条件,以get方式请求ES进行查询)跟DSL搜索,我们项目中采用的是DSL搜索。
DSL搜索类型:
-
match_all:全查
-
match:1个查询条件,1字段
-
multi_match:1个查询条件,多个字段(首页搜索采用这种方式)
-
bool:多个查询条件,多个字段
-
highlight:高亮查询
-
分页、分组
10、MySQL及数据库优化
10.1 : mysql索引都有哪些?如何创建索引?
MySQL的索引有两种分类方式:逻辑分类和物理分类。
按照逻辑分类,索引可分为:
主键索引:一张表只能有一个主键索引,不允许重复,不允许为null;
唯一索引:数据列不允许重复,允许为NULL值,一张表可有多个唯一索引,但是一个唯一索引只能包含
一列,比如身份证号码,卡号都可以作为唯一索引;
普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许NULL值插入;
全文索引:让搜索关键词更高效的一种索引;
按照物理分类,索引可分为:
聚集索引:一般是表中的主键索引,如果表中没有显示指定主键,则会选择表中的第一个不允许为NULL的唯一索引,
如果还是没有的画,就采用Innodb存储引擎为每行数据内置的6字节rowid作为聚集索引。每张表只有一个聚集索引,
因为聚集索引的兼职的逻辑顺序决定了表中相应行的物理顺序。聚集索引在精确查找和范围查找方面有良好的性能表
现(相对于普通索引和全表扫描),聚集索引就显得弥足珍贵,聚集索引选择还是要慎重(一般不会让没有语义的自增
id充当聚集索引);
非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同(非主键的那一列),一个表中可以拥有多个非聚集
索引;
创建主键索引
alter table t add primary key add(id
)
创建唯一索引
alter table t add unique(username
)
创建普通索引
alter table t add indexindex_name(username
)
创建全文索引
alter table t add fulltext (username
)
10.2 : mysql搜索引擎知道哪些?有什么区别?默认搜索引擎是什么?
我了解到的数据库搜索引擎有MyISAM、InnoDB、BDB、MEMORY等,对于 MySQL 5.5 及更高版本,默认的存储引擎是 InnoDB。在 5.5 版本之前,MySQL 的默认存储引擎是 MyISAM,我主要给您介绍下这两个的区别吧
• InnoDB 存储引擎:
o 支持自增长列(auto_increment),自增长列的值不能为空,如果在使用的时候为空的话就会从现有的最大值自动+1,如果有但是比现在的还大,则就保存这个值。
o 支持外键(foreignkey),外键所在的表称为子表而所依赖的表称为父表。
o 支持事务,回滚以及系统崩溃的修复能力,并且支持多版本并发控制的事务安全。
o 支持mvcc(多版本并发控制)的行级锁,就是通过多版本控制来实现的乐观锁
o 索引使用的是B+Tree
优缺点:InnoDB的优势在于提供了良好的事务处理、崩溃修复能力和并发控制。缺点是读写效率较差,占用的数据空间相对较大。
• MyISAM 存储引擎
不支持事务、支持表级锁
支持全文搜索
缓冲池只缓存索引文件,
不缓存数据文件 MyISAM 存储引擎表由数据文件(MYD)和索引文件( MYI)组成
我们项目中常用到的是innoDB,InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全,但是对比Myisam的存储引擎InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
10.3: mysql如何查看索引是否生效?
使用 explain 执行计划查看在sql前面加入关键字explain 查询出的结果查看type类型检查是否有执行索引
举例:EXPLAIN select * from table where id=2;我们一般优化sql语句的话,type级别都要至少达到ref级别,就是每次查询必须要使用索引
explain之后返回的列 type字段描述
type类型字段
All:最坏的情况,全表扫描
Index:和全表扫描一样。只是扫描表的时候按照索引次序进行而不是行。
主要优点就是避免了排序, 但是开销仍然非常大。
Range:范围扫描,一个有限制的索引扫描。key 列显示使用了哪个索引。
当使用=、 <>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,
用常量比较关键字列时,可以使用 range
Ref:一种索引访问,它返回所有匹配某个单个值的行。此类索引访问只有当使用非唯一性索引或唯一性索引非唯一性前缀时才会发生。
10.4: mysql查询语句优化?
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
• 应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行 全表扫描。
• 应尽量避免在 where 子句中对字段进行 null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:selectid from t where num=0
• 应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引 而进行全表扫描,如:select id from t where num=10 or num=20 ,可以使用可以这样查询: selectid from t where num=10 union all select id from t where num=20
• 以%开头的模糊查询也会导致全表扫描: select id from t where name like '%abc%',如果要提高效率的话,可以考虑全文检索来解决。
• in 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select idfrom t where num between 1 and 3
• 应尽量避免在 where 子句中对字段进行表达式操作,这将导致放弃使用索引 而进行全表扫描。如:selectid from t where num/2=100应改为: select id from t where num=100*2
• 应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而 进行全表扫描。
10.5 : mysql存储过程和视图?
存储过程:存储程序是被存储在服务器中的组合SQL语句,经编译创建并保存在数据库中,用户可通过存储过程的名字调用执行。存储过程核心思想就是数据库SQL语言层面的封装与重用性。使用存储过程可以较少应用系统的业务复杂性,但是会增加数据库服务器系统的负荷,所以在使用时需要综合业务考虑。
视图:视图本身是一张虚拟表,不存放任何数据。在使用SQL语句访问视图的时候,获取的数据是MySQL从其它表中生成的,视图和表在同一个命名空间(因为表和视图共享数据库中相同的名称空间,因此,数据库不能包含具有相同名称的表和视图)。视图查询数据相对安全,视图可以隐藏一些数据和结构,只让用户看见权限内的数据,使复杂的查询易于理解和使用。
10.6: mysql行转列与列转行?
mysql中行转列我们用的是内部函数实现过,函数是cast when then else end 这种
列转行使用 union 关键字 把多个select语句结果捏合到一个结果来实现
10.7: 分库分表中解释一下垂直和水平2种不同的拆分?
垂直拆分:是将单表,或者是有关联的表放在一个数据库,把原有的一个数据库拆分成若干个数据库。
水平拆分:是将一个很大的表,通过取模,按照日期范围等等拆分成若干个表.
10.8 : 分库分表中垂直分库方案会带来哪些问题?
垂直分库如果没有按照合理的业务逻辑去拆分,后期会带来跨库join,分布式事务等;
跨库join会导致查询性能低下,分布式事务下会导致因数据不一致造成很多脏数据的存在。
10.9 : 搭建mycat的核心配置文件有哪些?
schema.xml,配置逻辑库表,分片和读写分离
rule.xml,具体的分片规则和分片算法
server.xml,配置默认的数据库和用户,表权限
10.10 : 什么叫垂直切分?什么叫混合切分?项目中有没有可能只用水平切分?
垂直拆分是将同样的系统按照应用场景(调用方)进行拆分。
比如一个交易系统的支付模块,上游有用户支付和商家支付两个调用流程。按照垂直拆分的规则就可以将支付模块拆分为用户支付和商家支付。
混合切分:项目组中如果有水平切分,那项目组里的开发方式就叫混合切分。或者项目组里就是单纯的垂直切分。
11、分布式事务
11.1 :说一下产生分布式事务的场景?
-
跨数据库分布式事务
-
跨服务分布式事务
-
混合式分布式事务
11.2 : 分布式事务的解决方案?
1、上游服务保证必须发送成功
-
在系统A处理任务完成后,在本地记录待发送信息。一个定时任务不断检查,是否发送成功,如果发送成功,将记录状态修改
2、下游服务保证必须发送成功
-
虚拟机和队列进行持久化
-
消息发送成功后,使用手动ack确认机制
消息重试: 消息持久化后,如果消息没有成功消费,那么消息就会重新消费,直到下游消费者返回消费成功响应为止。
3、对消息做幂等
幂等的作用:防止消息重复消费
如何实现幂等:
使用本地去重表,读取记录表,如果有数据,代表来过,消费过,就直接返回,不用消费。如果没有数据,代表没有消费过, 消费消息,消费成功了还需在记录表中添加一条记录
11.3 : 事务的隔离级别?
-
读未提交:就是一个事务可以读取另一个未提交事务的数据
-
读已提交:就是一个事务要等另一个事务提交后才能读取数据
-
可重复读:就是在开始读取数据(事物开启)时,不再允许修改操作
-
串行化:事务串行化顺序执行,事务只能排序依次执行。不能并发
12、linux
查看僵尸进程
ps -ef | grep java (先查java进程ID)
kill -9 PID(生产环境谨慎使用)
查看日志
ps -ef | grep '日志关键字' --color
tail -n 10 test.log 查询日志尾部最后10行的日志;
tail -100f test.log 实时监控100行日志;
head -n 10 test.log 查询日志文件中的头10行日志;
head -n -10 test.log 查询日志文件除了最后10行的其他所有日志;