Java面试

Java知识块

Array

0、新建

int[] arr = {1,2,4, …};int[] arr;
arr = new int[]{1,2,3, …};int[] arr1= new int[3];//所有元素初始化为0int[][] arr = {{1,2,3},{4,5,6},{7,8,9}}; //每个子数组元素个数不要求均相同
int[][] arr = new int[m][n]; //其中n可以省略,在创建的时候可以指定
int[][][] arr = new int[m][n][q]; //同样其中n、q可以省略

集合 Collection

数组不能满足我们的需求之后,产生了集合的概念。集合根据元素是否重复分为List和Set两类接口。在List中,又根据是否需要线程安全分为Vector和ArrayList、LinkedList。在Set中,又根据是否需要插入和遍历顺序一致分为LinkedHashSet和TreeSet、HashSet(其中TreeSet需要对元素排序,HashSet不需要)。

Collection共有特点:

1、在使用泛型前可以存放Object所有子类,使用泛型之后只能存放该类型
2、可以使用迭代器Iterator(Collection继承Iterable),元素删除后需要重新获取迭代器。

Comparator和Comparable的区别

List

1、ArrayList

1、ArrayList是底层为Object[]数组。
2、初始默认容量10,每次扩容为原来的1.5倍。容量向右位移一位+原本容量
3、优点:检索效率高。
4、缺点:随机增删效率低,无法存放大数据量。
5、非线程安全的
6、数组(Array)和列表(ArrayList)的区别:Array可以存放基本或者引用对象,ArrayList只能引用对象。Array大小固定的,ArrayList可以变动。

2、LinkedList

1、LinkedList是底层为双向链表。有first和last变量分别表示头一个和最后一个Node节点。
2、和ArrayList相比,无初始容量,first和last都为null
3、优点:随机增删效率高,适用于大量数据和频繁增删的情况
4、缺点:不能随机存取,只能顺序

Set

不能存放相同元素,存放元素必须重写equals方法
1、HashSet

1、Hashset底层采用HashMap实现,元素无序且唯一
2、实际放入的是HashMap的key部分
3、对元素时间复杂度为O(1)

2、TreeSet

1、TreeSet底层调用的TreeMap
2、实现SortedSets接口的实现类
3、元素会自动按照大小顺序排序,存放对象所属类实现了Comparable接口
4、对元素时间负复杂度为O(logn)

Map

Map是以k-v键值对的形式存放数据,key不能重复。
遍历:1、先获取所有的key,再逐个遍历。2、转换成Set调用迭代器实现。

1、HashMap

1、HashMap底层是数组、链表、红黑树(数组超过64,链表节点超过8时转红黑树,红黑树节点小于6转链表)
2、key的特点和HashSet对应,无序不可重复
3、默认初始化容量是16,加载因子0.75.扩容是左移一位。扩容为2倍因为n-1&hash 得到的值来确认key的位置,从而减少hash碰撞增加效率。
4、key存放的对象必须重写equls、hashCode方法
5、key可以为null(但只能有一个),value也可以为null
6、HashMap扩容机制:默认大小16,加载因子0.75,扩容策略是原容量*2。元素迁移是要么在原位置,要么在(原位置+原容量)位置。

2、HashTable

1、Hashtable的key和value都不能为null,HashMap的都可以
2、Hashtable的方法都带有synchronized,线程安全,但效率低
3、Hashtable底层也是哈希表数据结构,Hashtable初始化容量是11,默认加载因子是0.75,扩容是原容量*2+1

3、TreeMap

1、底层是红黑树实现的
2、会根据放入元素的key进行排序。默认是根据字典顺序排序,也可以通过实现Comparator接口自定义排序。
3、实现了NavigableMap接口,支持导航方法

4、ConcurrentHashMap

0、JDK1.8之前 数组+链表实现,JSK1.8之后数组+链表+红黑树实现
1、是线程安全的HashMap
2、相比于HashTable的所put/get都加锁,JDK1.8之前ConcurrentHashMap采用分段segment加锁:给大数组segment加锁,segment下面有小数组,小数组挂载链表。JDK1.8之后使用CAS+volatile或synchronized实现的,没有元素就CAS创建,有元素就synchronized对节点及后面的链表加锁。(好处是加锁频率更低)
3、ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速(这一点在桶更多时表现得更明显些)。只有在size之类操作的时候才锁定全表。
4、ConcurrentHashMap使用的迭代器也换成了弱一致迭代器,当iterator被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据。

5、HashMap和HashTable区别
HashMap 允许 key 和 value 为 null,Hashtable 不允许。
HashMap 的默认初始容量为 16,Hashtable 为 11。
HashMap 的扩容为原来的 2 倍,Hashtable 的扩容为原来的 2 倍加 1。
HashMap 是非线程安全的,Hashtable是线程安全的。
HashMap 的 hash 值是位运算 效率更高,Hashtable 直接使用 hashCode取模。
HashMap 没有 Hashtable 中的 contains 方法。
HashMap 继承自 AbstractMap 类,Hashtable 继承自 Dictionary 类。

6、ConcurrentHashMap详解
锁方面:由分段锁(Segment继承自ReentrantLock)升级为CAS+synchronized实现;
数据结构层面:将Segment变为了Node,每个Node独立,原来默认的并发度16,变成了每个Node都独立,提高了并发度;
hash冲突:1.7中发生hash冲突采用链表存储,1.8中先使用链表存储,后面满足条件后会转换为红黑树来优化查询;
查询复杂度:1.7中链表查询复杂度为O(N),1.8中红黑树优化为O(logN));

7、ConcurrentHashMap扩容

8、有没有有顺序的Map实现类,如果有,他们是怎么保证有序的
LinkedHashMap,TreeMap便是有顺序的map实现类。LinkedHashMap继承于HashMap。
LinkedHashMap保证有序的结构是双向链表,TreeMap保证有序的结构是红黑树。
1)HashMap中k的值没有顺序,常用来做统计。
2)LinkedHashMap,它内部有一个链表,保持Key插入的顺序。迭代的时候,也是按照插入顺序迭代,而且迭代比HashMap快。
3)TreeMap的顺序是Key的自然顺序(如整数从小到大),也可以指定比较函数。但不是插入的顺序。

9、Iterator和fail-fast
每次我们尝试获取下一个元素的时候,Iterator fail-fast属性检查当前集合结构里的任何改动。如果发现任何改动,它抛出ConcurrentModificationException。

9.1、fail-fast和fail-safe
fail-fast:表示快速失败,在集合遍历过程中,一旦发现容器中的数据被修改了。会立刻抛出ConcurrentModificationException 异常。
fail-safe:表示失败安全,也就是在这种机制下,出现集合元素的修改,不会抛出 ConcurrentModificationException。原因是采用了安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有内容,再在拷贝的集合上遍历。例如ConcerrentHashMap。

10、Map接口提供了哪些不同的集合视图
a、Set keyset()
b、Collection values()
c、Set<Map.Entry<K,V>> entrySet():返回map中包含所有映射的集合视图。

队列

Java中LinkedList实现了Deque(双端队列)接口,可以当做队列使用

入队:add(E e)、offer(E e)、offerLast(E e)、offerFirst(E e) 插入队首、
出队:poll()、remove() 无元素抛异常
获取队头元素:peek()、element() 无元素抛异常

线程

状态:新建、就绪、运行、阻塞、完成
线程涉及到的一些方法

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;需要在synchronized代码块中使用,否则抛出IllegalMonitorStateException。
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
  • yield():线程执行 yield()方法后转入就绪(ready)状态;

值得注意的是,wait/notify并不会等概率的唤起线程,

线程实现方式:继承Thread、实现Runable接口、线程池

1、线程池 ThreadPoolExecutor类
事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
优势:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

1.1、提交任务时线程池已满
LinkedBlockingQueue无界队列情况下,继续添加任务到阻塞队列
ArrayBlockingQueue有界队列情况下,先尝试增加线程数量,不行则采用拒绝策略。

1.2、使用ThreadPoolExecutor

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),new ThreadPoolExecutor.DiscardPolicy());

2、线程的sleep()方法和yield()方法有什么区别
线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态。
sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
PS:都不会释放锁

3、sleep() 和 wait() 有什么区别
wait来自object类, sleep来自线程类
wait会释放锁, sleep不会释放锁
wait必须在同步代码块中,sleep可以在任何地方睡
wait不需要捕获异常,sleep需要捕获异常

4、线程的 join() 方法的作用
用于等待当前线程终止。如果一个线程A执行了 threadB.join() 语句,其含义是:当前线程A等待 threadB 线程终止之后才从 threadB.join() 返回继续往下执行自己的代码。

5、编写多线程程序有几种实现方式?
继承 Thread 类;
实现 Runnable 接口;
实现 Callable 接口
注意:
Thread 其实也是实现了 Runable 接口。Runnable接口无返回不能抛出异常,Callable 有返回值可以抛出异常。

5.1、Thread 和 Runnable 的区别
Thread 是一个类,Runnable 是接口。
Runnable 表示一个线程的顶级接口,Thread 类其实是实现了 Runnable 这个接口,我们在使用的时候都需要实现 run方法
Runnable 表示线程要执行的任务,因此在线程池里面,提交一个任务传递的类型是 Runnable。

6、Thread 调用 start() 方法和调用 run() 方法的区别
run():普通的方法调用,在主线程中执行,不会新建一个线程来执行。
start():新启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 CPU 时间片,就开始执行 run() 方法。

7、synchronized 和 Lock 的区别
从功能上来说,lock和synchronized都是用来解决线程安全问题的工具。
1)Lock 是一个接口,有很多实现类 例如ReentrantLock;synchronized 是 Java 中的关键字,synchronized 是内置的语言实现,通过修饰方法、类、变量控制锁的粒度;
2)Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;synchronized 不需要手动获取锁和释放锁,在发生异常时,会自动释放锁,因此不会导致死锁现象发生;
3)Lock 的使用更加灵活,可以有响应中断、有超时时间等;而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,直到获取到锁;Lock提供了公平锁和非公平锁两种,synchronized 只有非公平锁。
4)在性能上,随着近些年 synchronized 的不断优化,Lock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官方推荐尽量使用 synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。

7.1、ReentrantLock (实现Lock接口)——mark
它是可重入的排他锁
锁实现机制:依赖AQS;synchronized依赖监视器
灵活性:支持响应中断,超时等;synchronized不灵活
释放锁:必须显式调用unlock()释放;synchronized自动释放
锁类型:公平锁和非公平锁;synchronized非公平锁
条件队列:可以关联多个;synchronized关联一个

7.2、ReentrantLock如何实现锁公平和非公平
ReentrantLock默认采用了非公平的策略来实现锁的竞争逻辑。ReentrantLock内部采用了AQS实现锁竞争,没有竞争到锁的线程会进入AQS同步队列(FIFO双向链表)。实现公平锁就是线程竞争锁之前判断队列是否有线程等待,有就加入队列;实现非公平锁是无论队列中是否有线程等待,都去竞争锁。
之所以ReentrantLock和synchronized都是非公平锁,是因为按照公平策略去阻塞线程加入等待队列,然后再唤醒队列里面的线程会涉及到内核态的切换,对性能影响交大。

8、线程池的核心属性有哪些?
hreadFactory(线程工厂):用于创建工作线程的工厂。
corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。
maximumPoolSize(最大线程数):线程池允许开启的最大线程数。
handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:1)线程池运行状态不是 RUNNING;2)线程池已经达到最大线程数,并且阻塞队列已满时。
keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。

8.1、线程池选择
高并发、任务执行时间短:线程池数量略大于CPU数量,减少线程上下文切换。
低并发、任务执行时间长:IO密集型(性能瓶颈在IO操作不占CPU)使用大数量线程池;计算密集型,小数量线程池。
高并发、业务执行时间长:性能瓶颈不在于线程池。思考引入缓存、拆分业务等。

8.2、线程池如何知道一个线程的任务已经执行完毕
当我们把一个任务交给线程池去处理的时候,线程池会调度工作线程来执行这个任务的run方法,run方法正常结束也就意味着任务完成了。所以线程池中的工作线程通过同步调用run方法并且等待run方法返回后在统计完成数量。
如果在线程池外部去获取线程池内部任务执行状态,可以通过isTerminal方法,submit方法(主要)

9、Threadlocal
Threadlocal是一种线程隔离机制,提供了在多线程环境下对共享变量访问的安全性。一般情况使用加锁解决,但是会导致性能下降。Threadlocal在每个线程中都有一个容器ThreadLocalMap来存放共享变量副本,每个线程只对自己变量副本进行更新操作。
并且,ThreadLocalMap中的key是弱引用key

使用ThreadLcoal输出 abc efg abd
不使用输出 abc efg efg

import java.util.Comparator;
import java.util.PriorityQueue;public class ATest {private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();private static String s = "abc";public static void main(String[] args) throws InterruptedException {//创建第一个线程Thread threadA = new Thread(() -> {//            threadLocal.set(s);
//            System.out.println("线程A本地变量中的值为:" + threadLocal.get());
//            String s = threadLocal.get();
//            s = "efg";
//            threadLocal.set(s);
//            System.out.println("线程A本地变量中的值修改后为:" + threadLocal.get());System.out.println(s);s="efg";System.out.println(s);});//创建第二个线程sThread threadB = new Thread(() -> {//            threadLocal.set(s);
//            System.out.println("线程B本地变量中的值为:" + threadLocal.get());System.out.println(s);});//启动线程A和线程BthreadA.start();Thread.sleep(1000);threadB.start();}
}

9.1、Threadlocal会出现内存泄漏吗
存在内存泄漏。主要原因是ThreadLocalMap的key是一个弱引用,当成员变量没有其他强引用关系的时候,这个时候对象会被GC回收的,从而导致key变成null,造成这块内存无法被访问。
解决方法是:扩大成员变量Threadlocal的作用域避免被GC回收;使用完调用remove方法移除对应数据

10、保证线程安全的方式
线程安全就是多线程访问时,不管通过任何的方式调用以及线程如何进行交替,都能得到预期的结果。线程安全问题具体表现在三方面:原子性、有序性、可见性。
具体实现方式:
a、使用线程安全的类
b、使用synchronized同步代码块或lock锁
c、多线程并发情况下,线程共享的变量改为方法局部级变量
d、使用threadlocal

10.1、伪共享内存
CPU在向内存发起IO操作的时候,一次性会读取64个字节数据作为一个缓存行,缓存到CPU高速缓存中。缓存行可以有效减少CPU和内存交互的次数。当多个线程同时修改同一个缓存行里面的多个独立变量的时候,基于缓存一致性协议,就会影响彼此的性能,这就是伪共享问题。(例如CPU0要修改X,CPU1要修改Y,X和Y在同一缓存行,每个CPU都需要竞争缓存行的所有权。一旦某个CPU上的线程获得了所有权,其他CPU中的缓存行就会失效)

11、CAS
比较并交换compare and swap,是乐观锁使用的机制。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。实际上在CAS执行过程中也会存在因为不是原子操作而导致的问题,所以在底层还是使用了Lock指令对缓存加锁。
CAS存在的问题:
ABA问题(来回变换):使用版本号解决
循环时间长开销大
只能保证一个共享变量的原子操作

12、结束线程
使用线程对象.interrupt()方法

13、start()和run()
调用start()才是多线程并发,run()还是顺序执行的
JVM执行start()方法是另起一个线程执行run()

14、多线程上下文切换
CPU切换到另一个就绪的线程,需要保留当前线程的运行时位置,同时要加载需要恢复的线程的环境信息

15、线程中怎么处理不可控制的异常
多线程的run()方法是不支持throws语句的。所以当线程对象的run()方法抛出非运行异常时,我们必须捕获并且处理它们。可以使用UncaughtExceptionHandler接口的uncaughtException()方法。将这个接口的实现类对象setUncaughtExceptionHandler到线程中。
ps:如果没有捕获到异常,线程就会停止。

16、线程之间如何通信
共享内存volatile和消息传递wait notify BlockingQueue

17、CompletableFuture
是基于事件驱动的异步回调类。当使用异步线程去执行一个任务的时候,我们希望在执行这个任务结束以后触发的一个后续动作。CompletableFuture 提供了 5 种不同的方式,把多个异步任务组成一个具有先后关系的处理链,然后基于事件驱动任务链的执行。
thenCombine:把两个任务并行执行,当两个任务都执行结束以后触发事件回调
thenCompose:把两个任务串行执行,结束后触发回调
thenAccept:第一个任务结束后触发第二个任务,并且第一个任务的执行结果作为第二个任务的参数。task2无返回值
thenApply:与thenAccept一样,但task2有返回值
thenRun:task1执行完后触发一个实现类Runnable接口的task2

产生死锁的原因:
竞争不可剥夺资源、线程之间推进非法
产生死锁的必要条件:
互斥、请求和保持、不可剥夺、环路
预防死锁:
破坏请求、破坏请求保持、剥夺资源、破坏环路(有序分配)

如何检测死锁?
从产生死锁的四个必要条件出发。互斥、请求和保持、不剥夺、环路

银行家算法

1、synchronized与java.util.concurrent.locks.Lock
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

2、Java实现读写锁
参考链接

异常

1、运行时异常与受检异常(手动处理)

2、Error和Exception区别
Error 表示系统级的错误和程序不必处理的异常,恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况。
Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。

3、Exception分类
Exception直接子类:编译时异常(受检异常)。编译时异常必须在编译前先处理,如果不处理编译器报错。
RuntimeException子类:运行时异常。(RuntimeException继承了Exception)
区别:
编译时异常一般发生的概率比较高。对于一些发生概率高的异常,需要在编译时预先对其处理。
运行时异常一般发生的概率很低。你可以预先处理,也可以不处理。

4、自定义异常
参考

IO

1、实现序列化
实现Serializable接口

1.1、某些字段不想被序列化
使用 transient 关键字修饰

2、几种流
字节流、字符流。字节流继承于InputStream OutputStream,字符流继承于InputStreamReader OutputStreamWriter。

3、BIO、NIO、AIO
前提概念:同步IO与非同步IO、阻塞IO与非阻塞IO

BIO:同步阻塞,数据的读取写入必须阻塞在一个线程内等待其完成
NIO:同步非阻塞,一个线程不断的轮询数据读写的状态,从而进行下一步操作
AIO:异步非阻塞,无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。JDK1.7之后

区别
IO:面向流,阻塞IO
NIO:面向缓冲区,非阻塞IO

4、select epoll
参考

反射

通过对编译之后生成的.class字节码文件进行反编译,找到类、类中方法、属性。

1、反射创建实例对象方式
通过已知对象s1.getClass();
通过类名Student.class();
通过Class.forName(“包名.类名”);
通过以上三种方法便可得到一个Class类,一个Class类对应着該类的.class字节码,但此时不能直接通过new来创建对象。可以通过getConstructors() 获取该类的构造器。在使用构造器类的newInstance()方法便可以得到該类的对象。

2、动态代理
在程序执行过程中使用JDK反射机制,创建代理类对象,并动态指定要代理的目标类。
代理类要完成的功能:1、调用目标方法,执行目标方法的功能。2、功能增强

Java零散

1、正则表达式
用Pattern类表示正则表达式对象,它提供了API进行各种正则表达式操作。
正则表达式通常用在查找符合某些复杂规则的字符串。

2、&和&&区别
&:用在按位与,逻辑与
&&:带有短路的与运算。举例:username != null &&!username.equals(“”)。

3、String 和StringBuffer的区别
这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。
StringBuilder不是线程安全,StringBuffer是线程安全的

4、为什么重写equals还要重写hashcode
HashMap中,如果要比较key是否相等,要同时使用这两个函数。自定义类的hashcode码为默认的内存地址,会导致相同key对象的hashcode不同。
如何重写hashcode方法:参考文章

5、Synchronized和lock区别
synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;
而Lock是一个接口,Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

6、synchronized
修饰实例方法:作用于当前对象加锁
修饰代码块:给某一变量加锁,执行完代码块后释放
修饰静态方法:线程想要执行对应代码需要拿到类锁参考文章

7、final、finally,finalize
final 表示属性需要初始化并且不可修改,修饰的方法不可覆盖,修饰的类不可继承,final。
finally是异常处理语句结构的一部分,表示总是执行。try中有return时,先执行finally中的语句再return。try和finally中同时用return时,只执行finally的return。
finalize()是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源。

7.1、finally什么情况下不会执行
a、在进入try之前程序就抛出异常而终止了
b、在try或者catch中有System.exit(0)退出了

8、如何通过反射创建对象?
通过类对象调用newInstance()方法,例如:String.class.newInstance()
通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance(“Hello”);

9、Java中实现单例模式方法
懒汉式、饿汉式、双重校验锁、登记式/静态内部类、枚举

//枚举实现
public  enum Singleton{INSTANCE;public void doSomething{//}
} //静态内部类实现
public class Singleton{private Singleton(){}//private static class SingletonHandler{private static final Singletion INSTANCE=new Singleton();}public static final Singletion getSingletion(){return SingletonHandler.INSTANCE;}
}

10、静态代码块
1)静态代码块优先于main函数
2)静态变量只会初始化(执行)一次。
3)当有父类时,对象完整的初始化顺序为:父类的静态数据->子类的静态数据->父类的构造方法->子类的构造方法。

11、抽象类和接口的关系
抽象类降低了接口的实现难度
抽象类只能单继承,接口可以多实现。
抽象类可以有构造方法,接口中不能有构造方法。
抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final)
抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。
抽象类中的抽象方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类型。

12、自定义类 实例对象的比较
实现Comparable<T>接口 ,重写public int compareTo(T t)方法

13、为什么 char 数组比 String 更适合存储密码?
由于字符串在 Java 中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它,并且为了可重用性,会存在 String 在字符串池中, 它很可能会保留在内存中持续很长时间,从而构成安全威胁。
由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。
由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[],你就可以将所有元素设置为空白或零。

14、什么是函数式编程
首先,函数式接口:有且只有一个未实现的方法的接口,一般通过FunctionalInterface这个注解来表明某个接口是一个函数式接口。
使用lambda表达式来实现这些接口

15、a=a+b 和 a+=b区别
+=操作符会进行隐式自动类型转换

byte a = 127;
byte b = 127;
a=a+b;//报错
a+=b;//-2,转二进制后理解

16、static
修饰静态变量、静态方法、静态代码块(多用于初始化)、静态内部类

17、新建String

String str1="abc";//放入常量池
String str2=new String("def");//放入堆内存

18、SPI
是 Service Provider Interface ,它是 JDK 内置的一种动态扩展点的实现。就是我们可以定义一个标准的接口,然后第三方的库里面可以实现这接口。程序在运行的时候,会根据配置信息动态加载第三方实现的类,从而完成功能的动态扩展。
有一个非常典型的实现案例,就是数据库驱动java.jdbc.Driver。JDK里面只定义了数据库驱动Driver他是一个接口,具体的实现是由第三方数据库厂商来实现的。在程序运行的时候,会根据我们声明的驱动类型,来动态加载对应的扩展实现,从而完成数据库的连接。

JVM

JDK

JDK包含JRE包含JVM
JDK是Java标准的开发包,包括了Java编译器、Java运行时环境、以及常用的Java类库等。
JDK将java文件编译class文件,运行在JVM上。

为什么要使用字节码
Java中,JVM可以执行的代码叫做字节码,它不面向任何特定的处理器,只面向虚拟机。Java
语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。(主要是提升效率和可移植性)

JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

作用:
程序计数器:线程私有。一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。

Java虚拟机栈:线程私有。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈:线程私有。本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

Java堆:线程共享。对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

方法区:与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不同的实现可以放在不同的地方。

运行时常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

双亲委派机制

Java中的所有类,都需要由类加载器装载到JVM中才能运行。
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
过程:Application ClassLoader—Extension ClassLoader—Bootstrap ClassLoader
优点:避免重复加载 、 避免核心类篡改

垃圾回收机制GC

Java中垃圾回收机制是用来帮助程序员解决内存分配和垃圾回收的问题。首先就是判定哪些对象是可以回收的垃圾,Java采用的是可达性分析法,通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象经历至少两次标记后,基本就是可以回收的垃圾对象了。
垃圾收集算法:标记-回收、复制、标记-整理、分代。
垃圾收集器:当前使用的是G1。对象主要分配在新生代的伊甸园和From区,如果内存不足就会触发GC,伊甸园和From区存活的对象就会到To区,如果To区也存放不下,就会到老年代。每GC一次,对象就+1岁,对象15岁时进入老年代。也可能在空间不足时提前进入。

1、垃圾收集算法
1)标记 - 清除算法
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2)复制算法
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
3)标记 - 整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
4)分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。
一般是把Java堆分为新生代1/3和老年代2/3,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
在老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清理或者标记—整理算法来进行回收。

2、Minor GC与Full GC分别在什么时候发生?
新生代内存不够用时候发生minor GC也叫young GC,JVM内存不够的时候发生full GC

2.1、什么时候触发full gc
System.gc()
老年代空间不足
堆伸缩

3、JVM的内存结构,Eden和Survivor比例
8:1:1

4、一次完整的GC流程
一个对象出生在Eden区。当进行minor GC之后,该对象仍然存活。年龄加1,然后转到了Survivor的from区。在一次minor GC之后,在Eden区和Survivor from区存活的对象将被复制到Survivor to区,然后from和to区颠倒,在15次minor GC之后,该对象仍然存活。该对象将进入到老年代区。

5、新生代、老年代存储哪些东西
方法new一个对象,就会进入新生代
新生代中经历了N次GC仍然存活的对象进入老年代、大对象直接进入老年代、servivor空间不足时一些对象直接进入老年代。

6、可达性算法中,哪些可以作为GC Roots对象
a、虚拟机栈帧引用的对象
b、方法区静态成员引用对象
c、方法区常量引用对象
d、本地方法栈引用对象

JVM调优

1、有哪些JVM性能调优
设定堆内存大小
设定新生代大小
设定垃圾回收器

2、如果生成环境CPU占用过高,怎么排查
top -H 获取占用CPU最高的线程,转化为16进制
使用 jstack获取应用栈信息,搜索这个16进制

JVM零散

1、内存泄漏
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是Java中可能出现内存泄露的情况

2、数据存放位置
基本数据类型会存放在栈内存中
引用数据类型会存放在堆内存中,栈内存中引用指向堆内存中对象的地址。

JUC

JUC是java.util.concurrent包的简称,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!

1、并发和并行
并发:短时间段内 交替运行
并行:时间段内 同时一起运行

2、线程的六个状态
新生、运行、阻塞、等待、超时等待、终止

3、锁
传统synchronized
Lock 接口
两者区别参考前文

4、线程通讯机制
volatile 共享变量、wait/notify、Lock/Condition机制、管道

4.1、volatile
volatile有两个作用:保证多线程环境下共享变量可见性、防止多个指令之间的重排序。
可见性是指某一线程对共享变量修改时,其他线程会立即看到修改之后的值。本质上是:CPU层面使用了高速缓存来解决内存IO效率,但是带来了缓存一致性的问题。所以对于volatile关键字修饰的共享变量,JVM会自动增加一个Lock锁(指令级别的锁)。
指令重排序在多线程情况下会导致可见性问题。如果变量增加了volatile,则在编译的时候不会触发编译器优化,同时在JVM中会插入内存屏障避免重排序。

5、并发编程三要素
原子性:不可分割的操作,要么都成功要么都失败
有序性:程序执行的顺序和代码的顺序保持一致
可见性:一个线程对共享变量进行修改,另外的线程能立马看到

6、实现可见性(线程A修改变量能被其他线程感知到)的方法有哪些
volatile:告诉JVM当前变量在寄存器中的值是不确定的,需要读取内存。但不具有原子性。还会禁止进行指令重排序
synchronized/lock:锁定变量不可被其他线程访问
final

7、如果你提交任务时,线程池队列已满,这时会发生什么
如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务。
如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是 AbortPolicy

7.1、阻塞队列的有界和无界
阻塞队列是一种特殊的队列,它在普通队列的基础上提供了额外两个功能:当队列为空时阻塞消费者线程,唤醒生产者线程;当队列满了的时候阻塞生产者线程,唤醒消费者线程。
阻塞队列通常情况下是有界的,比如ArrayList之类的队列。无界队列并不是真正的没有限制,他只是元素存储量很大,它默认的长度是Integer.Max_value,所以我们感受不到它。无界队列存在比较大的潜在风险。

8、自旋锁
与互斥锁比较类似,都是为了解决对某项资源的互斥使用。在任何时刻,最多只能有一个持有者。
对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,而是一直循环在那里看是否该自旋锁的持有者已经释放了锁。锁一旦被释放,就会被等待的线程立即获取,而不需要经过唤醒和上下文切换。
自旋锁是一种比较低级的资源保护方式比较适用于保护访问耗时较短的资源。信号量和互斥锁适合于保持时间较长的情况

9、Atomic
Atomic的类通过CAS的方式来保证操作的原子性。

10、AQS
是多线程同步器,Lock、CountDownLatch都用到了AQS。本质上来说AQS提供了两种锁机制,分别是排他锁共享锁。
底层:
AQS内部由两个核心部分组成:一个volatile修饰的state变量(作为竞争条件),一个双向链表维护线程等待队列。
多个线程通过对这个state共享变量进行修改来实现竞态条件,竞争失败的线程加入到队列并阻塞,抢占到竞态资源的线程释放后,后续线程按序实现唤醒。

JDBC

1、JDBC提供了Statement、PreparedStatement 和 CallableStatement三种方式来执行查询语句
Statement、PreparedStatement和CallableStatement都是接口(interface)。 Statement继承自Wrapper、PreparedStatement继承自Statement、CallableStatement继承自PreparedStatement。
区别:
Statement接口提供了执行语句和获取结果的基本方法;普通的不带参的查询SQL;支持批量更新,批量删除;
PreparedStatement接口添加了处理 IN 参数的方法; 可变参数的SQL,编译一次,执行多次,效率高; 安全性好,有效防止Sql注入等问题;
CallableStatement接口添加了处理 OUT 参数的方法;支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;

2、数据库连接池
应用程序每次向数据库发起 CRUD 操作都需要创建连接,在数据库访问量较大的情况下,频繁的创建连接会带来较大的性能开销。
连接池就是在程序启动时初始化一部分连接保持在连接池里面,当应用需要使用连接的时候,直接从连接池获取。避免了每次建立和释放连接的开销。
关键参数:初始化连接数、最大连接数、最大空闲连接数、最小空闲连接数、最大等待时间

Servlet

1、servlet如何处理多个请求
Servlet容器默认是采用单实例多线程的方式处理多个请求的
Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;

2、doGet/doPost与Http协议有关,是在 javax.servlet.http.HttpServlet 中实现的

3、servlet实例创建
创建Servlet的实例是由Servlet容器来完成的,且创建Servlet实例是在初始化方法init()之前

4、

框架

动态代理

1、概念
为某些对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或不能直接引用另一个对象,而代理对象可以在客户类和目标类之间起到中介作用。动态代理是在程序执行过程中使用JDK反射机制,创建代理类对象,并动态指定要代理的目标类。

2、作用
功能增强。在原有功能上增加了额外功能。
控制访问。例如商家不让用户访问厂家。

3、动态代理实现方式
JDK动态代理通过invoke()方法调用、CGlib

MyBatis

1、#{}${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
处理#{}时,会将sql中的#{}整体替换为占位符(即:?),调用PreparedStatement的set方法来赋值。这样就不会有注入攻击的风险。

2、当实体类中的属性名和表中的字段名不一样
a、在xml的sql语句中设置别名
b、在mybatis的核心配置文件中设置全局配置信息mapUnderscoreToCamelCase为true,将表中字段的下划线自动转换为驼峰
c、resultType换为resultMap,在resultMap中配置字段名和属性值的对应关系

3、通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应, 请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法, 参数不同时,方法能重载吗?
接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。
是不能重载的。

4、MyBatis编程步骤
创建SqlSessionFactory
通过SqlSessionFactory创建SqlSession
通过SqlSession执行数据库操作
调用session.commit()提交事务
调用session.close()关闭会话

5、在mapper中如何传递多个参数
a、顺序传参
b、@Param注解传参
c、map传参
d、java bean传参

6、MyBatis二级缓存
一级缓存是默认开启的,它在一个sqlSession会话里面的所有查询操作都会保存到缓存中,一般来说一个请求中的所有增删改查操作都是在同一个sqlSession里面的,所以我们可以认为每个请求都有自己的一级缓存,如果同一个sqlSession会话中2 个查询中间有一个 insert 、update或delete 语句,那么之前查询的所有缓存都会清空。
二级缓存是全局的,也就是说;多个请求可以共用一个缓存,二级缓存需要手动开启。缓存会先放在一级缓存中,当sqlSession会话提交或者关闭时才会将一级缓存刷新到二级缓存中;开启二级缓存后,用户查询时,会先去二级缓存中找,找不到在去一级缓存中找;

Spring

理解:Spring是一个IOC和AOP容器框架,主要用于简化企业级应用的开发。Spring容器使用了工厂模式创建所需要的对象。ApplicationContext就是IOC容器。

0、Spring启动容器
初始化Spring容器:创建工厂、注解配置读取器、classpath路径扫描器
将配置类的BeanDefinition注册到容器中
调用refresh()方法刷新容器

1、自动配置方式
byName:根据Bean的名字进行自动装配。
byType:根据Bean的类型进行自动装配。

1.1、有两个id相同的Bean会报错吗
首先如果使用XML配置文件,xml不能存在两个id相同的bean,否则spring容器启动报错
两个不同的配置文件中,存在两个id相同的bean。IOC容器会在加载Bean的时候默认多个bean进行覆盖。Spring 3.x之后,Spring IOC只会注册第一个声明的Bean实例。这个错误发生在依赖注入阶段。

2、IoC和DI
IoC叫控制反转,DI是依赖注入

3、依赖注入的方式
set注入,构造器注入,接口注入

4、AOP
面向切面编程(适用于权限检查,日志记录,性能分析,事务管理,异常管理之类和业务无关的需求)
实现原理:使用动态代理来扩展现有类和方法。如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

4.1 Spring AOP和AspectJ AOP
Spring AOP是属于运行时增强,而AspectJ是编译时增强。Spring AOP基于动态代理,AspectJ基于字节码操作。
Spring AOP已经集成AspectJ了,可以通过@AspectJ来使用

4.2、Spring通知有哪些类型
前置通知:在join point之前执行的通知,但此通知不能阻止连接点的执行除非抛出异常
返回后通知:在join point正常执行后执行的通知,例如没有抛出异常、正常返回
抛出异常后通知:在方法抛出异常后通知
后通知:当某个join point退出执行后通知(无论是否正常执行完退出)
环绕通知:包围一个join point的通知,如方法调用。环绕通知可以在方法调用前后完成自定义行为,它可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

4.3、Spring通知执行顺序
around before advice
before advice
被修饰方法执行
around after advice
after advice
after returning advice

5、Spring怎么解决循环依赖
Spring中设计了三级缓存来解决循环依赖d的问题。当调用getBean方法的时候,Spring先从一级缓存中去寻找Bean,如果没有则去二级缓存中寻找。而如果一、二级都没有找到,意味着该目标Bean没有实例化。于是Spring容器会实例化目标bean。然后将目标Bean放入二级缓存中,同时加上标记是否存在循环依赖。如果不存在循环依赖才真正放入二级缓存,否则将其标记存在循环依赖,然后等待下一次轮询赋值,也就是@Autowired,等@Autowired注解赋值完成之后,会将目标bean放入一级缓存。Spring一级缓存存放所有成熟的bean。
三级缓存是用来存储代理Bean。当调用getBean方法时,发现Bean需要通过代理工厂来创建,此时会将创建好的实例保存到三级缓存,最终赋值好之后放入一级缓存。

6、Spring事务
Spring支持编程式事务管理和声明式事务管理。
a、编程式事务管理使用TransactionTemplate
b、声明式事务管理建立在AOP之上,本质是通过AOP功能对方法前后进行拦截,将事务处理的功能编织到拦截的方法中。优点是不用业务代码中掺杂事务管理代码。

6.1、事务传播特性——mark
当多个事务同时存在时:
a、PROPAGATION_REQUIRED:当前没有事务,就创建事务;当前存在事务,就加入事务
b、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入事务;如果不存在,则以非事务执行
c、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入事务;如果不存在,则抛出异常
d、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务
e、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
f、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
g、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

7、spring有哪儿几种方式获取bean context——mark
ApplicationContext为应用上下文。
方法一:在初始化时保存ApplicationContext对象
方法二:通过Spring提供的utils类获取ApplicationContext对象
方法三:继承自抽象类ApplicationObjectSupport
方法四:继承自抽象类WebApplicationObjectSupport
方法五:实现接口ApplicationContextAware
方法六:通过Spring提供的ContextLoader

8、spring生命周期——mark
实例化bean、设置对象属性(依赖注入)、处理Aware接口(设置beanName,factory等)、initializingBean(调用所在类的init()方法)、BeanPostProcessor(bean自定义设置)、DisposableBean(清理)、destroy-method(bean配置了destroy()方法就会调用)

8.1、Spring容器的bean什么时候被实例化
a、使用BeanFactory作为Spring Bean工厂,则所用bean都在第一次被使用时实例化
b、使用ApplicationContext作为Spring Bean 工厂,如果是singleton并lazy-init=false的bean,则在ApplicationContext启动时实例化;如果是singleton并lazy-init=true或prototype的bean的bean,则在第一次被使用时实例化。

9、Spring中有几种配置Bean方式
基于xml配置、基于注解配置、基于Java配置

10、BeanFactory和ApplicationContext——mark
都可以当做Spring的容器。
BeanFactory是Spring中比较原始的Factory、无法支持Spring中许多插件,如AOP、Web应用等。
ApplicationContext由BeanFactory派生而来,具有BeanFactory所有功能。
此外,在创建bean的时候也有区别,参考8.1

11、Bean是线程安全的吗
不是线程安全的,当多个请求于同一服务时,容器会给每一个请求分配一个线程,这是多线程并发会执行该请求对应的业务逻辑,会造成线程不安全,Spring没有对单例bean进行多线程封装,需要开发者自行处理。

12、Spring如何处理线程并发问题
在Spring中,绝大部分Bean都可以声明为Singleton作用域,因为Spring堆一些Bean中非线程安全状态采用ThreadLocal进行处理。

13、Spring Bean有哪些作用域scope——mark
singleton:这种bean范围是默认的,不管接受多少请求,每个容器只用一个bean实例。
prototype:为每个bean请求提供一个实例
request:在请求bean范围内会每个来自客户端的网络请求都创建一个实例,请求完成后销毁
Session:与request类似
global-session:在所有portlet共用全局

14、Spring有哪些事件
上下文更新事件:ConfigurableApplicationContext接口refresh方法时触发
上下文开始事件:ConfigurableApplicationContext接口start方法触发
上下文停止事件:ConfigurableApplicationContext接口stop方法触发
上下文关闭事件:ApplicationContext被关闭时触发
请求处理事件:web中一个http请求结束时触发
PS:当一个Bean实现了ApplicationListener接口时,当一个ApplicationEvent被发布后,bean会自动被通知。

15、@Resource 和 @Autowired区别
在Spring中两个注解都是用来实现Bean的依赖注入,区别在于:
@Autowired是Spring里面提供的注解,默认根据类型来实现Bean的依赖注入。@Autowired注解里面有required属性默认值是true,表示强制要求bean实例注入,如果没有则报错。其次,由于@Autowired是根据类型注入,当存在同类型多个bean时也会提示一个错误。
@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能。@Resource支持ByName和ByType两种注入方法。

SpringMVC

1、工作原理
DispatcherServlet收到请求后—找到处理该请求的Handler—Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet

2、常用注解
@Controller 指示是控制器类
@RequestParam 请求参数赋值给形参
@PathVarible 获取请求URL中的参数

3、Controller中为什么不能设置全局变量(方法外的属于类的变量)
因为controller是默认单例模式,高并发下全局变量会出现线程安全问题。
方法一:改为局部变量
方法二:使用ThreadLocal
方法三:Controller类上加@Scope("session")设置为会话级别

4、拦截器和过滤器
SpringMVC拦截器是依赖JDK反射实现的,主要进行请求拦截,通过Handler进行处理的时候拦截分为:preHandle(处理前拦截)、preHandle(处理完成后,返回结果前拦截)、afterCompletion(返回后拦截)。Spring中有多个拦截器时,按照配置的先后顺序进行拦截
Servlet过滤器是基于函数回调实现的,主要针对URL地址做编码、过滤掉没用的参数、安全校验。

此外 拦截器可以使用Spring 容器中的bean,而过滤器不行。

SpringBoot

1、SpringBoot缓存机制

2、SpringBoot中的start parent
springboot项目都有一个parent,主要作用是定义:Java编译版本、字符编码、执行打包的配置、依赖继承等

3、SpringBoot打包的jar和普通的jar区别
SpringBoot打包的jar包不能被其他项目引入而使用其中的类

4、约定优于配置——mark
约定优于配置是一种软件设计的范式,核心思想是减少开发人员对配置项的维护,从而更专注于业务逻辑。
传统的Spring框架开发web应用,需要做很多业务无关的配置,比如:jar包依赖、web.xml维护、Dispatch-Servlet.xml 配置项维护、应用部署到Web容器、第三方组件配置到Spring IOC容器中。而在SpringBoot中,我们不需要再去做这些繁琐的配置,SpringBoot已经帮我们完成了。Spring Boot约定优于配置的体现:
Spring Boot Starter 启动依赖,它能帮我们管理所有jar包依赖
如果应用了Spring MVC相关的jar,Spring Boot会自动内置Tomcat容器运行web应用
SpringBoot自动装配机制 可以实现Bean自动装配
默认加载application.properties配置文件

5、SpringBoot自动装配原理
自动装配就是把第三方组件的Bean装载到Spring IOC容器里面。在SpringBoot项目中,只需要@SpringBootApplication注解就可以实现自动装配。它是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。
引入Starter启动依赖组件的时候,这个组件必须包含@Configuration配置类,在这个配置类里通过@Bean声明需要装配到Spring IOC容器的对象。这个类是在第三方jar包里面的,然后通过SpringBoot约定优于配置思想,将这个配置类的全路径放在spring.factories文件中,这样SpringBoot就能知道第三方jar包里面配置类的位置。

6、SpringBoot注解
启动@SpringBootApplication、加载配置文件SpringBootConfiguration、开启自动配置EnableAutoConfiguration、组件扫描ComponentScan
参考文章

Spring Cloud & RPC

1、RPC 的工作原理
RPC 是一个分布式计算的 CS 模式,总是由 Client 向 Server 发出一个执行若干过程请求,Server 接受请求,使用者客户端提供的参数,计算完成之后将结果返回给客户端。
Provider启动后通过Register把服务的ID,IP地址、端口信息等注册到RPC框架注册中心;Consumer想要调服务的时候,通过Provider注册时到服务的ID去注册中心查找在线可供调用的服务,返回IP列表;Consumer根据一定策略从IP列表中选取真正调用服务;最后RPC框架提供监控功能,监控服务健康状况

2、服务提供者挂了,注册中心如何知道服务不可用了
服务掉线分为主动下线心跳检测

3、如果注册中心挂了,比如你用的是 Zookeeper,如果 Zookeeper 挂了,那服务之间还能相互调用吗
首先注册中心挂掉也要分两种情况,如果数据库挂了,ZK 还是能用的,因为 ZK 会缓存注册机列表在缓存里。
其次 ZK 本身就是一个集群的,一台机器挂了,ZK 会选举出集群中的其他机器作为 Master 继续提供服务,如果整个集群都挂了也没问题,因为调用者本地会缓存注册中心获取的服务列表。省略和注册中心的交互,Consumer 和 Provider 采用直连方式,这些策略都是可配置的。

4、已经有 http 协议接口,或者说 RestFul 接口,为什么还要使用 RPC 技术
在接⼝不多的情况下,使用 http 确实是一个明智的选择:开发简单、测试也比较直接、部署方便,利用现成的 http 协议进行系统间通讯。
但是如果业务真的慢慢做大,系统也慢慢扩大,RPC 框架的好处就显示出来 了,⾸先 RPC 支持长链接,通信不必每次都要像 http 一样去重复 3 次握⼿,减少了网络开销。
其次就是 RPC 框架一般都有注册中心模块,有完善的监控管理功能,服务注册发现、服务下线、服务动态扩展等都方便操作,服务化治理效率大大提高。
相比 http 能减少网络传输字节数,降低网络开销(握手)提高性能。实现更大的吞吐量和并发数,但是需要更多的关注底层复杂的细节, 对开发人员的要求也高,增加开发成本。

4.1、HTTP和RPC区别——mark
a、http是应用层的超文本传输协议,主要服务于网页端和服务端的数据传输。RPC是远程过程调用协议,主要是实现不同计算机应用之间的数据通信,让开发者像调用本地服务一样调用远程服务。
b、http是已经成熟的应用层协议,定义了通信报文的格式,比如请求头请求体等。RPC只是通信协议的规范,并没有具体实现,我们可以自定义报文的协议规范和序列化方式。
c、http协议和实现了rpc协议的框架都能实现跨网络节点的服务通信,底层都使用的TCP。但是由于RPC只是一种标准协议我们只需符合RPC协议这个框架都属于RPC协议,因此RPC也可以使用http来实现(比如grpc、OpenFeign)

5、SpringCloud如何实现服务注册
服务法布时,指定对应服务名,将服务注册到注册中心(Eureka、ZooKeeper)
注册中心加@EnableEurakeServer,服务用@EnableDiscoveryClient,然后人Ribbon或Feign进行服务直接发现和调用。

6、

软件设计基础

软件设计模式

1、工厂模式
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行

2、抽象工厂模式
是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
例如:有颜色和形状两个抽象类及其实现。设计一个抽象工厂类包含这两个抽象类。这时再创建抽象工厂的实现类就包含了颜色和形状。

3、单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
可以使用private修饰构造方法+静态本类型的实例,对外提供一个get方法。
Java中单例模式实现方式:懒汉式、饿汉式、双重校验锁、登记式/静态内部类、枚举

4、适配器模式
是作为两个不兼容的接口之间的桥梁。

5、观察者模式/发布订阅模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。

6、策略模式
一个类的行为或其算法可以在运行时更改。在创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
例如:一个抽象类A保存操作数,几个类实现抽象类A并实现操作数和操作方法。一个Context类有抽象类A成员变量,通过改变Context类实例变量(不同构造函数 或者 不同参数)来改变执行结果。

7、责任链模式
参考链接

8、模板模式
一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

9、组合模式
把一些属性拆分成类,在引用实例。

WEB

1、forward与redirect区别
a、从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过 来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的 地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地 址栏显示的是新的URL.
b、从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.

2、Servlet生命周期
Servlet被服务器实例化后,容器运行其init方法。当请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。
与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。

3、servlet是否为单例以及原因是什么?
Servlet单实例,减少了产生servlet的开销;一个Servlet可以处理多个请求。

4、get和post请求
①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;
②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?“连接,而各个变量之间使用”&“连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;
③get传输的数据要受到URL长度限制(1024字节);而post可以传输大量的数据,上传文件通常要使用post方式;
④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post;
⑤get使用MIME类型application/x-www-form-urlencoded的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是”%20"。

5、什么是Web Service
是向外界暴露出一个能够通过Web进行调用的API

6、cookie 和 session 的区别?
a、cookie数据存放在客户的浏览器上,session数据放在服务器(文件或数据库)上。
b、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session。
c、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
d、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

7、Cookie用到的属性

8、Session存储在哪里
tomcat会将session存储在内存中,也可以持久化到文件、数据库、memchache、redis等中。

RESTful

RESTful是Web应用程序的设计风格和开发方式
1、特点
a、每一个URI代表1种资源
b、使用GET、POST、PUT、DELETE请求来表示对资源的操作
c、客户端与服务端之间的交互在请求之间是无状态的

网络基础

1、7层模型、5层TCP/IP
7:物链网输会示用
4:数据链路层、网络层(IP)、传输层(TCP/UDP)、应用层(HTTP、FTP)

2、TCP三次握手、四次挥手
握手:

第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

挥手:

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A,用来关闭服务器B到客户A的数据传送。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

2.1、为什么要3次握手,2次不行吗
目的是为了防止已失效的链接请求报文突然又传送到了服务端。比如客户端A发送的请求在某一点滞留了失效了。后到达服务器时,服务器会以为是新的建立连接。

3、为什么tcp可靠
a、应用数据被分割成TCP合适的数据块
b、校验和:TCP 将保持它首部和数据的检验和,端到端验证。
c、流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据
d、拥塞控制:当网络拥塞时,减少数据的发送。
e、ARQ协议–确认应答:也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。
f、超时重传

3.1、TCP接受到乱序数据包怎么处理
6789,收到了689,缺失7。接收端返回三个6的ack。发送方有快重传机制,重新发送。
6789,收到了689,缺失7。接收端返6的ack、8的sack、9的sack,发送方重新发送7。

4、HTTP常见状态码
304:304(未修改)自从上次请求后,请求的网页未修改过。
502:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
504:作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。
1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。

5、浏览器从接收到一个URL,到最后展示出页面,经历了哪些过程
1.DNS解析 2.TCP连接 3.发送HTTP请求 4.服务器处理请求并返回HTTP报文 5.浏览器解析渲染页面

6、负载均衡、反向代理模式
反向代理(Reverse Proxy):是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。
负载均衡技术是把将来自internet上的连接请求以反向代理的方式动态地转发给内部网络上的多台服务器进行处理,从而达到负载均衡的目的。

7、拥塞控制
拥塞控制是防止过多的数据注入网络,使得网络中的路由器或者链路过载。流量控制是点对点的通信量控制,而拥塞控制是全局的网络流量整体性的控制。发送双方都有一个拥塞窗口——cwnd。
具体用塞控制方法是:慢启动+用塞避免、快重传+快恢复

8、HTTP如何传输数据
HTTP协议是客户端和服务器端之间数据之间实现可靠性的传输文字、图片、音频、视频等超文本数据的规范,简称超文本传输协议。属于应用层,使用的TCP传输。
通过get和post的请求方式。get主要用于查询操作,post主要用于增删改以及登录操作。

8.1、完成HTTP请求过程
建立TCP连接
Web浏览器向服务器发起请求
服务器返回数据
服务器关闭tcp连接

9、HTTP和HTTPS区别
HTTP协议是以明文的方式在网络中传输数据,而HTTPS协议传输的数据则是经过TLS加密后的,HTTPS具有更高的安全性。

9.1、HTTP 1.0 和 HTTP 2.0区别

10、TCP和UDP的区别
TCP是面向连接的协议,发送数据前先建立连接,TCP提供可靠的服务。只支持点对点通信。
UDP是无连接的协议,发送数据前不需要建立连接,提供不可靠服务。支持一对一,多对一,一对多,多对多。
TCP是面向字节流的,以字节为单位,一个数据包可以拆分成若干组发送。UDP是面向报文的,一个报文只能一次发送完。
TCP的首部开销比UDP更大
UDP的主机不需要维护复杂的连接状态

10.1、运行中TCP或UDP的应用层协议
TCP:HTTP、Telnet、FTP、SMTP
UDP:DNS、RIP、SNMP

11、HTTPS的加密方式是什么
HTTPS就是使用SSL/TLS协议进行加密传输,让客户端拿到服务器的公钥,然后客户端随机生成一个对称加密的秘钥,使用公钥加密,传输给服务端,后续的所有信息都通过该对称秘钥进行加密解密,完成整个HTTPS的流程。

12、Http和https的三次握手有什么区别

https协议需要到ca申请证书或自制证书。
http的信息是明文传输,https则是具有安全性的ssl加密。
http是直接与TCP进行数据传输,而https是经过一层SSL(OSI表示层),用的端口也不一样,前者是80(需要国内备案),后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

13、请求转发和重定向
请求转发:request.getRequestDispatcher().forward();
重定向:response.sendRedirect();
1)请求转发和重定向都可以实现访问一个资源时转向当前应用资源;
2)请求转发是一次请求一次响应,而重定向为两次请求两次响应;
3)一般情况下应该使用请求转发,减少浏览器对服务器的访问,减轻服务器压力;
4)请求转发地址栏没有变化,如果需要改变浏览器的地址栏或更改浏览器的刷新按钮的功能时需要使用重定向;
5)请求转发是在服务器端完成的,重定向是在客户端发生的;
6)请求转发的速度快,重定向速度慢;
7)请求转发必须是在同一台服务器下完成,重定向可以在不同的服务器下完成

14、Socket
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个socket。
socket连接就是所谓的长连接,客户端和服务器需要互相连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉的。
在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

操作系统

1、 线程和进程
一个线程只能属于一个进程,而一个进程可以有多个线程。
进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
进程是资源分配的最小单位,线程是CPU调度的最小单位。
操作系统对进程的创建和撤销的开销显著地大于在创建或撤消线程时的开销。
进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉。

2、进程间通信的方式
管道、系统IPC(消息队列、信号量、信号、共享内存)、套接字socket

3、用户态 内核态
CPU指令集需要不同的权限来操作的。比如进程中需要IO读写就需要ring 0级别的权限,也就需要从用户态切换为内核态。

4、线程的blocked和waiting
都是属于线程的阻塞等待状态
BLOCKED 状态是指线程在等待监视器锁的时候的阻塞状态。(多个线程去竞争 Synchronized 同步锁的时候,没有竞争到锁资源的线程,会被阻塞等待,这个时候线程状态就是 BLOCKED)
Waiting状态是表示线程的等待状态。在这种状态下,线程需要等待某个线程的特定操作才会被唤醒。我们可以使用wait() 、 join() 、LockSupport.park()这些方法使得线程进入到 WAITING 状态,在这个状态下,必须要等待特定的方法来唤醒。

数据库部分

数据库三范式
第一,每列保持原子性,所有字段都是不可分解的原子性
第二,每列都依赖主键,而不是依赖部分主键
第三,每列都直接依赖主键

MySQL

执行计划,在sql语句之前加explain关键字。在type字段说明查询效率,越往下越慢。key判断当前sql语句有没有用到索引。extra 表示额外信息

0、MySQL存储引擎
client–server–存储引擎(不同数据文件在磁盘的不同组织形式,包括默认的innoDB和myisam等存储引擎)。区别
主要
InnoDB支持事务,MyISAM不支持
InnoDB支持外键,而MyISAM不支持
InnoDB支持行级锁,MyISAM支持表级锁
InnoDB有聚集索引,数据文件是和主键索引绑在一起的;MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。

1、 索引
索引用来提高查询效率。索引和实际数据都存在磁盘,在进行读取的时候会优先把索引加载到内存中。
索引的形式:k-v格式的数据,选用的B+树。
使用B+树原因:可能内存无法一次读取所有索引数据,并且减少io方面的限制(io量 io次数)所以采用分治思想。 hash和B+树选择B+树,hash因为可能导致分布不均,以及范围查找效率低的问题不选用。相比于二叉树,b+树每一层能容纳更多。相比于b树,b+树可以每层不存数据data只存表示它的大小的,数据存在最后一层。这样每个磁盘块的范围就更大了。减少了io次数。
MySQL的B+树通常3-4层支持千万级别的存储。
索引设计时让key少占用空间,varchar和int 选择较小者。

索引类型:FULLTEXT全文索引(MyISAM)、HASH、BTREE(MySQL默认)、RTREE

索引种类:普通索引、唯一索引、主键索引、组合索引

语法:create index 索引名称 on 表名(字段名);

数据插入时必须和某个索引列绑定,索引列选择过程:先选择主键,没有主键选唯一键,在没有就选6字节的rowID存储。

示例
id,name,age,gender
id是主键,name普通索引
select * from table where name=‘zhangsan’;
先根据name B+树匹配对应叶子节点,查询到对应行记录的id值,再根据id值去id的B+树检索整条记录。

1、数据结构:B+树
2、设计原则
3、优化
一般优化并不是出现问题才优化的,在进行数据库建模和数据库设计的时候会预先考虑到一些优化问题,比如:表字段的类型、长度等,包括创建合适的索引等方式。但是这种方式只是一种提前的预防,并不一定能解决所有问题。当实际生产环境中出现问题之后。要根据数据库的性能监控,索引的创建和维护,sql语句的调整、参数设置,架构调整等多个方面去综合考虑。性能监控时会选择show porfiles,performance_scheme来进行监控,索引会进行xxx调整参数xxxx调整。(接下来编个例子)
4、失效
5、回表:
先根据name B+树匹配对应叶子节点,查询到对应行记录的id值,再根据id值去id的B+树检索整条记录。回表效率低,减少回表。
6、索引覆盖(只需要在一颗索引树就能获取所需数据吗,无需回表)
select id,name from table where name='zhangsan';此时不需要回表,因为查到name B+树匹配对应叶子节点,查询到对应行记录的id值,就可以结束了
7、最左匹配
创建索引的时候可以选择多个列来共同组成索引,叫做组合索引。要遵循最左匹配原则。例如name和age建立组合索引,查询条件 name='xxx' and age=xxx;和 name='xxx';会使用组合索引。而age='xxx' and name='xxx'也会使用组合索引,mysql会自动交换顺序,编程第一种。
8、索引下推
select * from table where name='zhangsan' and age=12;
没有索引下推时,现根据name从存储引擎拉去数据到server层,在server中对age数据过滤。
有索引下推之后,根据name和age两个条件来做数据筛选,返回筛选之后的内容。下推指的是server层的条件过滤下推到存储引擎层
9、聚簇索引:
数据和索引存储在一起。其他索引的叶子节点存储的数据不再是数据而是聚簇索引的id值。innodb是既有聚簇索引又有非聚簇索引。例如主键聚簇,其他字段的索引就是非聚簇。

1.1、二级索引(非聚簇索引)
主键索引是InnoDB存储引擎默认给我们创建的一套索引结构(数据会放在逐渐索引里,作为叶子结点的数据页),根据业务需求需要在不同字段上建立索引,这就是二级索引(仅存放主键字段和建立索引的字段),在查询的时候,根据索引字段查询到主键id,再回表查询。

2、四个隔离级别
读取未提交:脏读(读未提交),不可重复读,幻读
RC读取提交:不可重复读,幻读。解决了脏读
RR可重复读(默认),存在幻读(同一事务中,多次读取的数据放生变化),
串行化:解决幻读。
参考文章

RC 隔离级别,通过 where 条件走非索引列过滤之后,不符合条件的记录上的行锁,会释放掉(虽然这里破坏了“两阶段加锁原则”);但是RR隔离级别,通过 where 条件走非索引列过滤之后,即使不符合where条件的记录,也是会加行锁。

全是快照读就没有幻读问题。当前读、快照读联合使用就会有幻读问题。加锁可以解决幻读问题。

3、mvcc
MVCC是一种多版本并发控制机制,是MySQL的InnoDB存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。MVCC是通过保存数据在某个时间点的快照来实现该机制,其在每行记录后面保存两个隐藏的列,分别保存这个行的创建版本号和删除版本号,然后Innodb的MVCC使用到的快照存储在Undo日志中,该日志通过回滚指针把一个数据行所有快照连接起来。
当前读:总是读取到数据的最新版本。select…lock in share mode;select…for update;update;delete;insert等操作。
快照读:读取的历史版本。select…

mvcc实现原理
第一部分–隐藏字段:每一行记录都会包含几个用户不可见的字段:
DB_TRX_ID表示创建或者最后修改该记录的事务id;
DB_ORW_ID表示隐藏主键,
DB_ROLL_PTR表示回滚指针,和undolog配合使用
第二部分–undolog:回滚日志,保存数据的历史版本状态。当不同事务对同一条记录做修改时,会导致该记录的undolog形成一个链表,链表的表头是最新记录,链尾是最早记录。
这时新来一个事务,读取到的是
第三部分–readview:事务在进行快照读的时候产生的读视图。包含及部分:
trx_list表示当前系统活跃的事务id
up_limit_id表示列表中事务最小id
low_limit_id表示系统尚未分配的下一个事务id
示例:

事务1 事务2 事务3 事务4
开启 开启 开启 开启
修改某个值并commit
快照读注1
注1:是否能读到刚刚修改的记录值?能
trx_list->1,2,3
up_limit_id->1
low_limit_id->5
DB_TRX_ID->4
RR可见性规则:
1、首先比较DB_TRX_ID<up_limit_id,如果成立,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断。
2、判断DB_TRX_ID>=low_limit_id,如果成立则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么当前事务不可见。如果小于则进入下一步判断。
3、判断DB_TRX_ID时候在活跃事务trx_list中,如果在则代表Read View生成时刻,这个事务还是活跃状态,没有commit,修改的数据当前事务也看不到。如果不在,说明这个事务在Read View生成之前就已经commit了,那么修改结果能看见。

RC和RR隔离级别区别是Read View生成时机不同。
RC:每次在进行快照读的时候都会生成新的readview
RR:只有在第一次进行快照读的时候才会生成readview

4、acid
a:原子性–undolog实现
c:一致性–其他三个共同实现
i:隔离性–mvcc实现
d:持久性–redolog实现–问到二阶段提交-WAL write ahead log:先写日志再写数据(因为随机读写的效率要低于顺序读写,为了保证数据的一致性,可以先将数据顺序读写的方式写到日志文件中,然后将数据写入对应磁盘文件中,如果实际数据没有写入到磁盘,只要日志文件保存成功了,那么数据就不会丢失)

5、binlog&&先写入redolog(作用是确保事务持久性)
mysql自己的日志:binlog,innodb又有redolog。两个日志属于不同的组件,为了保证数据一致性,要保证binlog和redolog一致,就有了二阶段提交的概念。
二阶提交:先写入redolog,让redolog处于prepare阶段,然后再写入binlog,提交完成后redolog变成commit状态。

6、数据库层面的锁

乐观锁
悲观锁
间隙锁
行锁
表锁
记录锁
自增锁
意向锁

6.1、意向锁
事务A先申请表的意向共享锁,再申请一行的行锁。这时事务B想要申请表锁的时候,就不用遍历每一行看有没有被上锁。

7、undolog(作用是回滚)
它里面记录的是对数据的回滚操作。当我们对数据库中的数据有变动操作的时候,为了可以回滚到数据被改动之前的版本,就把数据的变动过程的逆向操作给记录在undo log中。只有数据库中的数据有变化的操作才会记录undo log。我们执行一个insert语句,在undo log中就记录一个delete语句。

8、慢查询优化
首先分析语句,是不是load了不需要的数据
分析语句的执行计划,获取其使用索引情况,再改写语句或者修改索引,使语句尽可能命中索引
横向/纵向分表

9、MySQL主从复制
主:binlog线程,记录所有改变DB的语句放进binlog中
从:io线程,在使用slave之后负责从master拉取binlog数据放进relay log。sql执行relay log中语句

10、Explain性能分析
使用explain关键字可以模拟执行SQL语句,从而知道MySQL如何处理的。从而优化SQL语句

11、优化SQL
In包含的值不应该过多
select指定查询字段
使用limit
排序字段使用索引
使用or时也需要索引

12、Mysql性能优化——mark
a、硬件和操作系统方面,由DBA或者运维负责
b、架构设计,主从集群、读写分离、分库分表、热点数据引入缓存redis
c、MySQL程序配置优化,比如连接数、binlog日志、缓存池等
d、SQL语句优化

Redis

聊一下Redis:有的场景下例如秒杀库存、流量高峰,不适用MySql之类的关系型数据库,引入缓存中间件。Redis数据结构String、hash、list、set、zset。单个value最大1G,字符串最大512MB
String:最简单的k-v
list:双向链表
set:集合,多个value添加到集合key
hash:一个key下可以有多个field-value,适合存放对象
zset:有序集合,每个value都有一个score用来排序

1、redis的List能在什么场景下使用
Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。

2、缓存的优点
减少了对数据库的读操作,数据库的压力降低 。加快了响应速度

3、redis为什么是单线程
因为Redis是基于内存的,它的瓶颈最有可能是机器内存或者网络带宽。
其次,多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU。
虽然单线程无法发挥多核CPU的性能,但是可以单机多开Redis实例

4、redis的主从复制怎么做的
与master建立连接
向master发起同步请求(SYNC)
接受master发来的RDB数据
载入RDB文件

5、redis集群
主从模式:一主多从,主写从读减少压力。不具备容错,主服务有单点风险
哨兵模式:在主从模式下设置为哨兵模式。涉及到心跳机制+投票裁决。主机宕机从机上位。
Cluster模式:所有的redis节点互联,分配slot。根据key计算命中槽,选择对应redis节点。解决了redis扩容问题

5.1、redis集群失效
集群没有复杂模型的情况下,其中一台挂掉,会导致整个集群缺失某一范围的槽而导致redis集群不可用。
PS:redis集群有16384个槽,每个key通过CRC16校验后取模决定放入哪个槽,集群每个节点负责一部分槽。

6、Redis持久化
RDB策略,指定时间间隔内执行了指定次数写操作之后自动触发持久化操作,把内存中的数据以二进制文件写入磁盘。
AOF策略,采用操作日志记录每一次写操作,重启redis服务都会重新执行一遍。效率低
一般使用RDB,重要的时候都开启。
因为RDB是每隔一段时间触发一次,因此数据安全性低,AOF可以做到实时持久化;RDB文件默认采用压缩方式持久化,AOF存储的是指令,所以RDB在数据恢复效率更高。

6.1、AOF重写机制
AOF会把每个数据更改的操作指令,追加存储到 aof 文件里面。所以容易导致AOF文件过大造成IO性能问题。
Redis为了解决这个问题,设计了AOF重写机制,也就是把AOF文件里面相同的命令进行压缩,只保留最新的数据指令。例如AOF文件中存放了某个key多个变更命令,但实际上最终在恢复的时候只需要执行最新的指令操作。

7、Redis事务
只能保证部分原子性,事务开始后,把操作命令压入队列,如果这时有错误命令,那本事务都不会执行;如果一组命令压入时正常,但执行时出错,就不能保证原子性。

7.1、Redis事务使用
MULTI 组装事务
EXEC 执行事务
DISCARD取消事务
WATCH 监视key,如果在事务执行前改变则取消事务

8、过期策略
a、当内存限制达到时返回错误
b、回收最近最少使用
c、回收过期
d、随机回收
e、过期随机回收

9、缓存穿透、缓存击穿、缓存雪崩
缓存穿透:缓存和数据库中都没有的数据,而用户不断发起请求
缓存击穿:指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
缓存雪崩:缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

9.1、解决方式
缓存穿透:不存在的值设置为空放入redis 过期时间设置很短;布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap,一个一定不存在的就会被bitmap拦截掉
缓存击穿:使用redis的setnx互斥锁,当操作成功时在进行load DB并回设缓存,否则重试get缓存的方法;永不过期。
缓存雪崩:批量设置过期时间加上随机值

10、redis的优化操作
尽量使用短的key
避免使用keys *
在存到Redis之前先把你的数据压缩下
设置 key 有效期
选择回收策略(maxmemory-policy)
使用bit位级别操作和byte字节级别操作来减少不必要的内存使用。
尽可能地使用hashes哈希存储。
当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能。
想要一次添加多条数据的时候可以使用管道。
限制redis的内存大小(64位系统不限制内存,32位系统默认最多使用3GB内存)

11、redis为什么快
1)首先是因为它数据都在内存中
2)采用了 IO 阻塞 和 多路复用

12、redis、数据库双写一致性参考
先更新数据库,再删除缓存。
这种策略下也会有并发问题:缓存刚好失效时,请求A查询数据库得到旧值,请求B将新值写入数据库,请求B删除缓存,请求A将旧值写入缓存。
但这种情况发生概率会很小,因为此情况发生需要的条件是B写数据库的时间长于A查询数据库。但一般情况下读比写快。

13、redis热点key
热点key:
对单一key对访问量激增导致超过所在server极限。
处理:
服务端缓存:即将热点数据缓存至服务端的内存中.
备份热点Key:即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了.
热点key发现:
a、凭借经验,进行预估:例如提前知道了某个活动的开启,那么就将此Key作为热点Key
b、在操作Redis之前对数据进行统计
c、抓包进行评估:Redis使用TCP协议与客户端进行通信,通信协议采用的是RESP,所以能进行拦截包进行解析。
d、Redis自带命令查询:Redis4.0.4版本提供了redis-cli –hotkeys就能找出热点Key

14、如何保证Redis高并发、高可用
Redis实现高并发主要依靠主从机制;单主用来写入数据,单机几万QPS,多从用来读取数据。
如果需要在实现高并发的同时容纳大量数据,则需要Redis集群来实现。
Redis如果使用的是主从架构部署,那么加上哨兵就可以实现主备切换了。

15、redis找出某种固定前缀的key
可以使用keys指令可以扫出指定模式的key列表
但是如果key数量众多的话,由于redis是单线程的keys指令会导致线程阻塞一段时间。线上服务会停顿。
可以使用scan指令,能无阻塞的取出指定模式的key,但是会有一定的重复,需要在代码中去重。整体更优

16、使用redis做异步队列
可以使用list数据结构作为队列,rpush生产消息,lpop消费消息。无消息时适当sleep
缺点:消息会丢失
1:N可以通过pub/sub主题订阅模式实现

17、redis分布式锁
a、加锁、设置过期时间 执行各自的命令,无法保证原子性
b、加锁并过期时间setnx(key_resource_id, expiresStr)
c、lua脚本保证加锁和设置过期时间原子性
上面操作无法保证锁到期前业务完成
d、使用watch dog自动续时,通过lua脚本保证加锁和watch dog的原子性

18、RedLock
需要至少5个实例都是master,客户端向5个实例都发起加锁时间,并截取当前时间t1设置一个超时时间(不是锁的超时时间),当一个实例加不上锁的时候就下一个实例。
当加锁成功的实例>=3时截取当前时间t2,如果t2-t1<超时时间,则加锁成功,进行业务操作。
解决:加锁后master宕机,从机上位后,从机没有锁的问题。
缺点:太笨重,性价比低

19、多线程
随着网络硬件的性能提升,Redis的性能瓶颈有时会出现在网络IO的处理上,也就是说,单个主线程处理网络请求的速度跟不上底层网络硬件的速度。为了应对这个问题,一般有两种方法。
一是用用户态网络协议栈(例如DPDK)取代内核网络协议栈,让网络请求的处理不用在内核里执行,直接在用户态完成处理就行。
二是采用多个IO线程来处理网络请求,提高网络请求处理的并行度。Redis的多IO线程只是用来处理网络请求的,对于读写命令,Redis仍然使用单线程来处理。

ElasticSearch

MongoDB

分布式

1、分布式集群下如何做到唯一序列号

数据库自增长序列或字段
UUID
Redis生成ID
Twitter的snowflake算法
利用zookeeper生成唯一ID

2、CAP
一致性、可用性、分区容错性,分布式系统往往无法兼容者三者,只能设计满足其中两个

3、如何理解RPC

4、如何实现分布式锁——mark
分布式锁是跨机器节点的互斥锁,它可以用来保证多机器多共享资源访问的排他性。分布式锁和和线程锁本质上是一样的。
一般可以通过redis或者zookeeper实现

5、分布式事务实现方式

6、处理高并发

7、分布式缓存和本地缓存
本地缓存:
访问速度快,但无法进行大数据存储。集群的数据更新问题(影响一致性)。数据随应用进程的重启而丢失。缓存一般是一种key-value的键值对数据结构,所以需要使用字典数据结构来实现,在 Java 编程中,常用的字典实现包括 HashMap 和 ConcurretHashMap。
分布式缓存:
支持大数据量存储,不受应用进程重启影响。数据集中存储,保证数据一致性。数据读写分离,高性能,高可用。 数据跨网络传输,性能低于本地缓存(缺点)。通常使用Redis实现

ZooKeeper

是一个分布式协调服务,他是集群的管理者,监视着集群的各个节点的状态,根据节点提交的反馈进行下一步合理操作。分布式应用可以基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调、集群管理、master选举、分布式锁、分布式队列等功能。

1、Eureka和Zookeeper区别
zookeeper是CP原则,强一致性和分区容错性。当主节点故障时,zookeeper会在剩余节点重新选择主节点,耗时长,选取主节点期间会导致服务不可用。
eureka是AP原则,可用性和分区容错性。一个节点挂掉其他节点正常保证服务。

2、有哪些功能
集群管理、主节点选举、分布式锁、命名服务

3、通知机制
client端会对某个znode建立watcher事件,当znode发生变化时,这些client就会收到zk的通知。

4、怎么保证主从节点的状态同步
zookeeper核心是原子广播机制,保证了各个server之间的同步。实现这个机制的协议是Zab协议,Zab协议主要有两种模式:
恢复模式:原leader崩溃后,Zab进入恢复模式,当新leader被选举出来且大多数server完成了新leader同步之后,结束恢复模式。
广播模式:新follower加入zookeeper服务中,它会在恢复模式下启动,发现leader并和leader进入状态同步,他也参与到消息广播

5、如何实现leader选举
zk的集群节点由三种角色组成:Leader(复制所有事务请求的处理,以及过半提交的投票发起和决策)、Follower(负责接受客户端的非事务请求,参与Leader选举)、Observer(负责接受客户端的非事务请求,不参与任何投票,只是为了扩展zk集群分担读操作压力)
选举:每个节点会向集群其他节点发送票据Vote包含:epoch(逻辑时钟用来表示当前票据是否过期)、zxid(事务id表示当前节点最新存储的数据的事务编号)、myid(服务器id)。第一次投票的时候携带的是当前节点的信息,接下来每个节点用收到的票据和自己节点的票据做比较,根据 epoch、zxid、myid的顺序逐一比较,以值最大的一方获胜。

Dubbo

是阿里巴巴开源的基于Java的高性能RPC分布式服务框架。使用Dubbo可以将核心业务抽取出来作为单独服务,逐渐形成稳定的服务中心,提高业务灵活扩展。

0、Dubbo10层结构——mark
service:业务逻辑层,提供接口和实现
config:配置层,用于初始化配置信息
proxy:代理层,用于提供provider和consumer代理
register:服务注册层,封装服务地址的注册和发现
cluster:路由层,封装provider路由和负载均衡
monitor:监控层,提供RPC调用时间和次数监控
protocol:远程调用层,封装RPC调用
exchange:信息交换层,用于封装请求响应模式,同步转异步
transport:网络传输层
serialize:序列化层

0.1、总体可以分为三层
第一层:业务逻辑层由我们自己来提供接口和实现还有一些配置
第二层:RPC调用核心层负责封装和实现这个RPC调用过程、负载均衡、集群、代理等
第三层:对网络协议的封装和数据转化

0.2、Dubbo负载均衡策略
加权随机:服务器加权后按权重线性排列,访问的时候随机生成一个数,落在哪个区间就访问对应服务器
最小活跃度:服务器收到请求后活跃度加1,处理完请求后活跃度-1,新请求选择活跃度低的服务器。
一致性Hash: 将节点分布到Hash环上,调用的时候通过hash算法确定对应的节点
加权轮询:根据权重比例对应接收到请求的概率
最短响应时间权重随机:计算目标服务请求响应的时间,选择最带你的服务配置更高的权重访问。

1、Dubbo核心能力
面向接口代理的高性能RPC、服务容错和负载均衡、服务自动注册和发现
高度可扩展、运行时流量调度、可视化服务治理和运维

2、Dubbo和Spring Cloud区别
Dubbo使用RPC通信,SpringCloud使用HTTP RESTful方式
组成部分不同,Dubbo

3、Dubbo支持什么协议
dubbo(推荐)、rmi、http、hessian等

4、Dubbo基本工作流程
服务提供者把服务注册到注册中心
消费者去注册中心订阅自己需要的服务
订阅服务信息变更时,注册中心推送服务列表
服务消费者从服务列表中基于负载均衡选择选择一个服务提供者,宕机就换一个
提供者和消费者的调用次数和响应时间定期同步给监视器

5、Dubbo请求服务失败怎么处理
Dubbo对服务请求失败的场景默认提供了重试机制,默认的额外重试次数是2次。除此之外,Dubbo还提供了其他的容错机制:
a、快速失败策略。服务消费只发起一次请求。如果失败就直接抛出异常。
b、失败安全策略。如果服务通信出现异常,直接把这个异常吞掉不向上抛出
c、失败自动恢复策略。后台记录失败请求,通过定时任务对失败请求进行重发。
d、并行调用多个服务策略。把这个消息广播给服务提供者集群,只要有任何一个节点返回就表示成功。
e、广播调用策略,逐个调用服务提供者集群,有任意节点出现异常就表示失败。

6、Dubbo怎么动态感知服务下线
Dubbo默认采用了Zookeeper实现服务的注册和发现,多个Dubbo服务之间的通信地址是使用Zookeeper来维护的。
Dubbo消费端会从Zookeeper Server上查找目标服务的地址列表,从而完成服务的注册和消费。Zookeeper会通过心跳检测机制来判断Dubbo服务提供端的运行状态,决定是否应该把这个服务从地址列表剔除。
当Dubbo服务提供放出现故障导致Zookeeper剔除了这个服务的地址,那么Dubbo服务消费端需要感知到地址列表的变换。这个能力是Zookeeper提供的watch机制来实现的,Dubbo服务消费端会使用Zookeeper中的watch来针对服务提供者节点注册监听,一旦这个提供者节点下线,Zookeeper Server就会发送一个事件通知给Dubbo客户端。

Eureka

用于服务注册与发现,Eureka包含两个端:Server(用于维护和管理注册服务列表)和Client(向注册中心注册服务的应用)

1、Eureka server 数据同步原理
Eureka 是一个服务注册中心,为了保证 Eureka 的高可用性,提供了集群的部署方式。集群部署采用的是两两相互注册的方式来实现,也就是说每个 Eureka Server 节点都需要发现集群中的其他节点并建立连接,然后通过心跳的方式来维持这个连接的状态。
Eureka Server 集群节点之间的数据同步方式非常简单粗暴,使用的是对等复制的方式来实现数据同步。也就是Eureka Server集群中没有主从之分,所有节点都可以接受或者写入数据,一旦集群中任意一个节点接收到了数据的变更,就直接同步到其他节点。

Feign

基于动态代理,根据注解选择机器进行服务调用

Ribbon

实现负载均衡

Gateway

Spring Cloud Gateway是Spring Cloud的网关框架,功能包括路由转发、权限校验、限流控制等。使用了RouteLocatorBuilder的bean取创建路由,使开发着可以添加predicates断言和filter过滤器

Seata

在微服务架构下,由于数据库和应用服务的拆分,导致原本一个事务单元中的多个DML操作变成了跨进程或跨数据库的多个事务单元的多个DML操作。
传统的数据库事务就对此无能为力,因此引出了分布式事务的概念。分布式事务本质上要解决的是跨网络节点的多个事务数据一致性问题。
分布式事务大致可以分为两类。
强一致性:CP模型,
强可用性:AP模型,

Seata封装了四种分布式事务模式
1、AT模式(默认):基于本地事务+二阶段提交。
2、TCC模式:Try、Confirm、Cancel。将业务拆成三段。根据每个分支事务执行的情况分别调用confirm或者cancel
3、Saga模式:长事务解决方案,每个业务流程参与者都提交本地事务,当出现一个参与者失败则补偿前面已经成功的参与者。
4、XA模式:强一致性,利用事务资源对XA协议的支持,以XA协议机制来管理分支事务。

分布式事务

1、如何实现分布式事务

分布式零散

1、熔断和服务降级
熔断:当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂停对该服务的调用。
服务降级:从整个系统的负荷情况出发和考虑的,对某些负荷比较高的情况,为防止某些功能负荷过载或者响应慢的情况,在其内部暂停对一些非核心接口和数据的请求,直接返回提前准备的错误信息。

2、分布式锁采用Redis还是zookeeper
Redis是AP模型强调可用性和分区容错性,zk是CP模型强调一致性和分区容错性。
共享锁情况下,一般采用redis的set nx再加过期时间的命令,可以使用lua脚本保证操作原子性。
排他锁情况下,可以采用zk实现,每个线程在zk的/lock目录下创建一个临时有序的节点去抢占锁。线程创建好节点之后获取/lock目录下所有子节点,判断自己是不是最小的。如果当前节点是最小节点,则获得锁;如果不是则对接待你序号前面的节点添加事件监听,当前一个节点释放锁之后出发通知,再次抢占锁。
总的来说,redis抢占锁更简单粗暴,事件开发中一般不会遇到极端复杂情况,并且一般还会在程序中做兜底。所有redis会采用的比较多。但更推荐zk的方式。

Linux

MQ

Producer -> Broker -> Consumer

1、MQ作用
异步处理、应用解耦、流量削峰、日志处理

1.1、MQ选择
小吞吐量选ActiveMQ、RabbitMQ
大吞吐量选Kafka、RocketMQ

2、消息的重发
在MQ中,当消费者消费消息产生异常时,消费者会把消息放入一个特殊队列中,进行下次处理,这就是消息的重发。

3、MQ系统的数据如何保证不丢失

消息持久化
ACK确认机制
设置集群镜像模式

4、rocketMQ
吞吐量高、可用性高、消息可靠性高、支持10亿级别消息堆积、稳定性高、源码是Java

4.1、rocketMQ工作流程
启动NameServer监听端口,等待Broker、Producer已经Consumer
启动Broker,和NameServer建立长连接,定时发送心跳包。心跳包包含当前Broker信息、Topic信息已经Broker和Topic映射关系
创建Topic,创建时需要指定该Topic存在啊些Broker
Producer发送消息。启动时先和NameServer建立长连接,获取Topic所在Broker再建立长连接,发送消息。
Consumer消费消息。启动时先和NameServer建立长连接,获取Topic所在Broker再建立长连接,消费消息。

4.2、rocketMQ高可用性
集群化NameServer、集群化broker、broker之间可以设置同步复制

5、如何保证消息不被重复消费
根本原因是消费完之后,消费者给MQ的确认已消费的反馈,MQ没有成功接收
a、生产全局ID,消费成功的消息直接丢弃
b、消息中保存业务数据的主键字段,结合业务系统需求场景进行处理。

5.1、如何保证消息消费的幂等性
a、每个消息都有id,先查再保存
b、业务表添加unique约束
c、redis中存消息id,重复则不消费
d、redis和zookeeper分布式锁

6、消息不丢失
生产者丢失消息:生产者发送消息到MQ时丢失。MQ有消息确认和事务机制。
MQ丢失消息:开启MQ持久化配置
消费者丢失:采用消息自动确认模式,消费者取到消息未处理挂掉了。改为手动确认模式,消费者成功消费消息再确认

7、消息有序性
生产者保证消息入队的顺序
MQ本身就是先进先出
避免多消费者并发消费一个queue中的消息

Kafka

1、零拷贝(考点)
零拷贝通过DMA技术把文件内容复制到内核空间中的Read Buffer,接着把包含数据位置和长度信息的文案金描述符加载到Socket Buffer中,DMA引擎直接可以把数据从内核空间中传递给网卡设备。减少了数据拷贝,提升了数据的效率。

2、kafka怎么避免重复消费
Kafka Broker 上存储的消息都有一个 Offset标记,kafka消费者通过offset标记来维护当前已经消费的数据。
默认情况下,消息消费完后会自动提交offset的值避免重复消费。kafka消费端的自动提交逻辑有一个默认5秒间隔,所以再consumer消费过程中应用程序被强制kill或宕机,可能导致offset没提交导致重复提交问题。
此外,kafka里面有一个partition balance机制(把多个partition均衡分配给多个消费者)也会导致重复消费。因为consumer再默认5秒内没有处理完这批消息,会导致rebalance机制,让offset自动提交失效。而rebalance之后,consumer还是会从上一次的offset开始消费,从而导致重复消费。
处理方式:
提高消费端的处理能力避免rebalance,比如异步处理。

3、ISR

4、kafka怎么保证消息消费的顺序性
为什么会出现无序消费:在kafka中用到了partition分区机制来实现消息的物理存储,在同一个topic下,可以维护多个partition来实现消息分片。生产者在发送消息的时候,会根据消息的 key 进行取模,来决定把当前消息存储到哪个 partition 里面。例如:假设一个topic存在三个partition 消息正好被路由到三个独立的partition里。然后消费端有三个消费者通过 balance 机制分别指派了对应的消费分区。因为消费者完全独立,所以可能出现消费顺序不是发送顺序。
针对这个问题,一般的解决办法就是自定义消息分区路由的算法,然后把指定的key 都发送到同一个 Partition 里面。

Nginx

Nginx是web服务期的反向代理服务器,Nginx收到请求之后按照一定规则分发给后端业务处理服务器进行处理。它可以隐藏源服务器的存在和特征,增加安全性和负载均衡。

1、为什么nginx性能高
a、使用多线程
b、事件处理机制:异步非阻塞事件处理机制:运用epoll模型 提供了一个队列排队解决。

2、master节点和worker节点
Nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程。
master:接收外界信号发送给worker节点,让worker节点处理请求。监控worker节点状态,挂了就重启
worker :处理请求

3、负载均衡算法怎么实现的,有哪些策略
轮询(默认):按时间顺序逐一分配
权重:权重越大访问比例越高
ip绑定:ip经过hash后分配到固定后端,可以解决session共享问题
fair:第三方插件提供,根据页面大小和加载时间智能分配
url_hash:根据url的hash结果分配请求,使每个url有固定的后端服务器

4、高可用性
当下游服务出现故障或者没有及时响应的话,可以轮训到其他服务器,保证服务器的高可用

5、限流
正常限制访问频率、突发限制访问频率、限制并发连接数

6、动静分离
动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们则根据静态资源的特点将其做缓存操作。让静态的资源只走静态资源服务器,动态的走动态的服务器。
Nginx处理静态资源的能力很强,可以将静态资源放在nginx中进行缓存。动态资源给tomcat处理

其他

设计

1、秒杀系统设计:主要问题是瞬时高并发
a、读多写少:使用多节点redis,mysql无法支持这么多连接
b、缓存问题:缓存击穿(缓存中不存在去搜DB的时候加锁)、缓存穿透(布隆过滤器把不存在的商品id也缓存起来)
c、库存问题:超卖问题,DB用乐观锁、Redis缓存用lua脚本扣减库存解决
d、分布式锁:redis的set加锁
e、mq异步:将秒杀、下单、支付拆分开。mq发送失败的问题,可以先把消息写入消息发送表再发送mq消息。消费者消费消息时,处理完业务逻辑后,再回调生产者的接口修改消息状态为已处理。写消息发送表成功发mq失败可以使用job增加重试机制。job扫表重新发送到mq。重复消费问题,增加消息处理表,处理之后往这个表里加记录,后序先判断重复再处理。15分钟取消订单问题,使用mq的延迟队列,生成待支付订单后向延迟队列发送一条消息,到了延迟时间,消费者读取消息后查询订单状态如果还是待支付就取消。
f、如何限流:nginx限流和redis限流。验证码

Java面试 数据库 网络 设计相关推荐

  1. Java面试,如何设计一个秒杀系统

    Java面试,如何设计一个秒杀系统说起秒杀,我想你肯定不陌生,从双十一购物到春节抢红包,再到逢年过节抢⻋票,"秒杀"的场景在我们的生活中处处可⻅.简单来说,秒杀就是在同一个时刻有大 ...

  2. JAVA+MYSQL数据库课程设计“迪士尼票务与信息管理系统“(一)附带详细资源

    目录 背景介绍 需求分析 外部接口需求 系统功能需求 用户需求 性能需求 安全性需求 可行性分析 技术可行性 应用可行性 经济可行性 概念设计 局部E-R图设计 门票信息局部E-R图 游乐项目信息局部 ...

  3. java面试---数据库之数据库优化及mycat分库分表

    目录 oracle和mysql分页的区别:oracle分页的原理: 数据库隔离级别有哪些,各自的含义是什么,MYSQL默认的隔离级别是是什么 数据库查询执行流程 数据库怎么优化:索引的原因:为什么要使 ...

  4. java面试④数据库部分

    2.3.1 数据库的分类及常用的数据库 数据库分为:关系型数据库和非关系型数据库 关系数据库:mysql,oracle,sqlServer 非关系型:redis,mongoDB 2.3.2 简单介绍一 ...

  5. Java面试——数据库

    系统性学习,移步 IT-BLOG-CN 一.数据库隔离级别 [1]Read Uncommitted(读取未提交内容):出现脏读,也就是可能读取到其他会话中未提交事务修改的数据. [2]Read Com ...

  6. springboot入门ppt,java面试数据库隔离级别实战

    一.背景 我们日常在电商网站购物时经常会遇到一些高并发的场景,例如电商 App 上经常出现的秒杀活动.限量优惠券抢购,还有我们去哪儿网的火车票抢票系统等,这些场景有一个共同特点就是访问量激增,虽然在系 ...

  7. java毕业设计读书网络社区设计Mybatis+系统+数据库+调试部署

    java毕业设计读书网络社区设计Mybatis+系统+数据库+调试部署 java毕业设计读书网络社区设计Mybatis+系统+数据库+调试部署 本源码技术栈: 项目架构:B/S架构 开发语言:Java ...

  8. Java 面试知识点解析(六)——数据库篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  9. Java面试:数据库,Java,框架,前端技术。应有尽有

    ** Java面试宝典 ** 一. HTML&CSS部分 1.HTML中定义表格的宽度用80px和80%的区别是什么? PX标识像素,%标识整个页面的宽度百分比 2.CSS样式定义优先级顺序是 ...

最新文章

  1. C#第一个程序Helloworld
  2. Redhat 中裸设备(raw) 的配置和oracle中使用
  3. 网关是个啥?为什么电脑不设置网关就没法上网?笔记本为啥不用设置网关?
  4. Spring Session官方介绍及spring框架学习方法
  5. Dubbo 新编程模型之外部化配置
  6. 转 list三种遍历效率
  7. 用python做一个简单的投票程序_以一个投票程序的实例来讲解Python的Django框架使...
  8. 【CodeForces - 689D】Friends and Subsequences(RMQ,二分 或单调队列)
  9. 【转】linux图形界面编程基本知识
  10. 黑客利用智能灯泡窃取用户数据!
  11. 再学 GDI+[77]: 区域(6) - GetRegionScans - 获取区域中的所有矩形
  12. 20162306 2017-2018-1《程序设计与数据结构》 第11周学习总结
  13. python实现给定一个列表计数指定数字出现的所有次数
  14. php 字符串长度的解释
  15. 博世BMI160驱动程序 C语言编程,如何使用PSoC 6制作完整的测试系统来与BMI160进行通话...
  16. 悟空CRM系统项目测试
  17. VOT数据集下载——(vot2013到vot2019)
  18. python贴吧数据可视化软件_Python数据可视化工具之Pyecharts初体验
  19. 【安全科普】揭秘IPS之网络攻击的“字典”
  20. JAVA的三大特性(举例说明)

热门文章

  1. 局域网交换机的配置及性能比较(转)
  2. VS中lib库文件的生成并使用
  3. 张萌韩墨羽——Android蓝牙
  4. [T][2]NOIP 2014 无线网络发射器选址
  5. vue + element 导航点击不失色
  6. postgres启动服务器时missing or erroneous pg_hba.conf file
  7. 基于Springboot技术开发的在线商城
  8. 城市的未来要押注无人驾驶汽车吗?这是个艰难的抉择
  9. 从零开始缓慢深入Linux - 基础指令篇(3)
  10. .eep_eep ..已经是三月了