记录互联网大中厂面试常见以及常见八股文

记录互联网大中厂面试常见以及常见八股文

收录有:Java+ 计算机基础 + 数据库 + 常用框架 + 中间件 + 开发工具 + 项目
逐渐完善,慢慢积累。部分图片来源于网络,侵删。
答案为本人基于自己的理解,如有大佬认为不足可评论区指正

文章目录

java基础常见问题

1、Java源码的编译过程(华为)

源代码-> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字节码生成器

jvm的作用

保证Java一次编译到处运行,屏蔽了机器底层机器码。保证Java不面向任何的处理器而只是面向于虚拟机。

Java如何跳出多重循环(华为)
String a1 = "";
String b1 = "";
here:
for (int i = 1; i <= 4; i++) {
    a1 = "外层循环第"+i+"层";
    for (int j = 1; j <= 4; j++) {
        b1 = "内层循环第"+j+"层";
        if (2 == j & 2 == i) {
            break here;
        }
    }
}

2、Hash为什么要右移16位异或?(美团)(滴滴)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }

首先这个方法的返回值还是一个哈希值。为什么不直接返回key.hashCode()呢?还要与 (h >>> 16)异或。先看一个例子:

h = key.hashcode() 1111 1101 1101 1111 0101 1101 0010 1111
^
h >>> 16           0000 0000 0000 0000 1111 1101 1101 1111
----------------------------------------------------------
h ^ (h >>> 16)     1111 1101 1101 1111 1010 0000 1111 0000
h = key.hashcode() 1111 1101 1101 1111 0101 1101 0010 1111  

对比h = key.hashcode()h ^ (h >>> 16) 发现,将h无符号右移16位相当于将高区16位移动到了低区的16位,再与原hashcode做异或运算,可以将高低位二进制特征混合起来。例子中可以看出高区的16位并没有变化。低区的16位发 生了较大的变化。这样做的目的是什么呢?

我们计算出的hash值在后面会参与到元素index的计算中。计算公式为 hash & (length - 1)。

仔细观察上文不难发现,高区的16位很有可能会被数组槽位数的二进制码锁屏蔽,如果我们不做刚才移位异或运算,那么在计算index时将丢失高区特征。如果没有上面这个异或操作,假设里两个hash值只有高位一点点的差异,然后在计算index过程中还丢失了高位的信息,那么就计算出同一个index。这也是将性能做到极致的一种体现!!!

使用异或运算的原因?(美团)

异或运算能更平均的保留各部分的特征,如果采用 & 运算计算出来的值会向1靠拢,采用 | 运算计算出来的值会向0靠拢

为什么槽位数必须使用2^n?(美团)

  1. 为了让hash后的结果更加均匀

    如果 length = 17 那么 hash & (17 - 1) 。16转化为二进制包含更多的0,这样一来计算会被更多的0屏蔽。

  2. 便于扩容后的重新计算index。

为什么扩容时总是把capacity扩大为原来的2倍?

由于我们要维护hashmap的大小为2^n,这样就使得len-1的二进制中全部都是1。进行位运算时可以降低hash碰撞的出现。

HashMap的负载因子为什么是0.75?(美团)

负载因子主要与扩容有关,如果将负载因子设置为1,空间利用的就更加充分了,但是这样一来会增大hash碰撞的出现,有些位置的链表会过长,不利于查找。如果设置的过小的话虽然降低了hash碰撞的发生,但是会频繁触发扩容机制。所以为了折中,应用泊松定理,将负载因子设置为0.75是对空间与时间的取舍。

HashMap的线程安全问题?(滴滴)

1.多线程的put操作可能导致元素丢失

2.put和get并发时可能导致get为null

  • 线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。

3.jdk 1.7 中并发put导致的循环链表导致get出现死循环

  • 发生在多线程并发resize的情况下可能会导致环形链表的出现。

hashmap的哈希冲突可以通过用多个hash函数解决吗?(滴滴)

不能,只是能降低冲突的概率,完全解决冲突是不可能的。

解决hash冲突的方法?(美团)

1.拉链法(链地址):Java中的HashMap在存储数据的时候就是用的拉链法来实现的,拉链发就是把具有相同散列地址的关键字 (同义词)值放在同一个单链表中,称为同义词链表。

2.线性探测法:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

3.二次探测法:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

3、Java多线程编程时有哪几种线程间通信方式?(跟谁学)

  1. 共享内存法

    volatile,synchronized

  2. wait/notify机制

    来自Object类的方法。当满足某种情况时A线程调用wait()方法放弃CPU时间片,并进入阻塞状态。当满足某种条件时,B线程调用notify()方法通知A线程。唤醒A线程,并让它进入可运行状态。

  3. Lock/Condition机制

    Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。

4、volatile如何实现内存可见性?(美团)(字节跳动)

volatile为什么会出现:(字节跳动)

首先先分析一下没有volatile的情况下线程在自己的私有内存中对共享变量做出了改变之后无法及时告知其他线程,这就是volatile的作用,解决内存可见性问题。这种问题用synchronized关键字可以解决。但是一个问题是synchronized是重量级锁,同一时间内只允许一个线程去操作共享变量。操作完成之后在将改变后的变量值刷新回共享内存空间中。这样一来的话并发性就没有了。而且synchronized关键词的使用基于操作系统实现,会使得线程从用户态陷入内核态。这一步是很耗时间的。于使volatile应运而生。它是一个轻量级的synchronized。只是用来解决内存可见性问题的。

volatile可见性实现原理(字节跳动)

变量被volatile关键字修饰后,底层汇编指令中会出现一个lock前缀指令。会导致以下两种事情的发生:

  1. 修改volatile变量时会强制将修改后的值刷新到主内存中。
  2. 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

volatile有序性实现原理(字节跳动)

指令重排序:编译器在不改变单线程程序语义的前提下,重新安排语句的执行顺序,指令重排序在单线程下不会有问题,但是在多线程下,可能会出现问题。

volatile有序性的保证就是通过禁止指令重排序来实现的。指令重排序包括编译器和处理器重排序,JMM会分别限制这两种指令重排序。禁止指令重排序又是通过加内存屏障实现的。

// 内存屏障(memory barriers):一组处理器指令,用于实现对内存操作的顺序限制。

添加了volatile关键字可以避免半初始化的指令重排。

volatile为什么不保证原子性?

java中只有对变量的赋值和读取是原子性的,其他的操作都不是原子性的。所以即使volatile即使能保证被修饰的变量具有可见性,但是不能保证原子性。

5、了解CountDownLatch吗?

闭锁可以用来确保某些活动直到其他活动全部结束之后才进行;

主要包含两个方法,一个是countDown(),一个是await();以及一个计数器变量cntcountDown() 方法用来给计数器cnt减一;
await() 方法是用来阻塞当前线程,直到计数器为0的时候在唤醒线程继续执行;

6、了解Semaphore吗?

信号量,用于多个共享资源的互斥使用,也可以用来控制线程的并发量,类似于线程池的作用。

可以用于限制线程的并发数。

7、Thread Local 作用、原理、内存泄漏问题?(字节跳动)(滴滴)

又叫线程本地变量、或线程本地存储。

作用:

ThreadLocal为解决多线程下的线程安全问题提供了一个新思路,它通过为每一个线程提供一个独立的变量副本解决了线程并发访问共享变量出现的安全问题。在很多情况下ThreadLocal比直接使用synchronized同步机制解决线程安全问题更加方便、简洁。且拥有更加高的并发性。

原理:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
  1. 在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal的引用,value为变量副本(即T类型的变量)。
  2. 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal对象引用为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals
  3. 然后在当前线程里面,如果要使用副本变量,就可以通过get()方法在threadLocals里面查找。

ThreadLocal内存泄漏

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时(无论是否OOM)弱引用Key会被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收, Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏

解决办法:每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

ThreadLocal应用场景

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

8、JVM模型?(字节跳动)(美团)(滴滴)

分为 线程私有部分线程共有部分

私有部分:虚拟机栈、本地方法栈(JNI)、程序计数器。

共有部分:方法区、堆(年轻代、老年代)。

9、类的实例化顺序?

  1. 父类static修饰的方法变量、子类static修饰的方法变量。
  2. 父类的普通逻辑,父类的构造函数
  3. 子类的普通逻辑,子类的构造函数

10、几种垃圾收集算法?(太多了)

标记清除:会产生大量的内存碎片

复制算法:用于新生代的垃圾回收。不会产生内存碎片,但是会浪费约10%的内存空间。

标记压缩(清除):用于老年代的垃圾回收。耗时较长,但是不会产生内存碎片也不会浪费空间。

分代收集:根据对象的存活周期的不同将内存划分为几块

11、常用的垃圾收集器?(太多了)

serial:年轻代单线程串行垃圾收集器

CMS:老年代多线程并行垃圾收集器

  1. 初始标记(STW)GC root的直接引用
  2. 并发标记,标记所有GC root可达的引用对象(无STW)
  3. 重新标记(STW)这一步为了标记在并发标记期间由于工作线程工作带来的垃圾
  4. 并发清除。

缺点是 由于以最短停顿为目标,所以会导致吞吐量低。标记清除算法带来的内存碎片问题。

G1:填补了CMS的不足,是当前服务端最优的垃圾收集器。通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

**初始标记(STW)、并发标记、最终标记(STW)、筛选清除。**G1收集过程中整体来看基于标记整理算法,局部来看基于复制算法。所以在收集垃圾过程中不会产生垃圾碎片。

12、AQS内部如何控制并发?(字节跳动)

AQS(AbstractQueuedSynchronizer)J.U.C包下lock实现的核心。主要是其提供的一个FIFO的队列来维护获取锁失败而进入阻塞的线程,以及一个volatile关键字修饰的state变量表示当前同步状态。当一个线程获取到同步状态(修改state=1),那么其他线程便无法获取,转而被构造成节点并加入同步队列中。加入队列的过程基于CAS算法。即比较当前线程认为的尾节点与当前节点,比较成功后才能正式加入队列尾部。队列头节点表示的为当前正在运行的线程,该线程执行结束后会激活它下面的一个线程进入执行状态。

FIFO同步队列控制并发。

13、解释对象创建的过程?(美团)

  1. 申请堆内存(半初始化,变量未赋值)
  2. 变量赋值
  3. 建立引用关系

注意区分对象创建问题类加载过程问题!

14、DCL单例为什么需要加vloatile (半初始化的指令重排)?(滴滴)(字节跳动)

/**
单例设计模式---饿汉
缺点:上来就初始化了一个对象,浪费资源
**/ 
public class test {
    // 静态的对象实例
    private static test instance = new test();
    private test() {};
    // 供外界调用的获取实例的方法
    public static getInstance () {
        return instance;
    }
}
/**
单例设计模式---懒汉 解决了浪费资源的问题但是又带来了线程不安全问题。

**/ 
public class test {
    private static test instance; 
    private test() {};
    public static getInstance () {
        if (instance == null) {
            instance = new test();
        }else {
            return instance;
        }
    }
}
// 单例设计模式---懒汉 加锁保证线程安全
public class test {
    private static test instance; 
    private test() {};
    // synchronized 保证线程安全,缺点锁粒度太大,影响性能
    public static synchronized getInstance () {
        if (instance == null) {
            instance = new test();
        }else {
            return instance;
        }
    }
}
// 单例设计模式---懒汉  加锁 并双重锁定检查(Double Check Lock){DCL单例}
// 注意:多线程环境下的指令重排可能会产生问题
 public class test {
    private static volatile test instance; 
    private test() {};
    // synchronized 保证线程安全,缺点锁粒度太大,影响性能
    public static getInstance () {
        if (instance == null) { //第一次检查
            synchronized (test.class) {
                if (instance == null) { // 第二次检查
                    instance = new test(); 
                }
            }
        }else {
            return instance;
        }
    }
}

volatile两个作用:保持线程可见性;禁止指令重排序

DCL单例需要加volatile,来禁止指令重排。

由于由于java编译器允许处理器乱序执行(以获得最优的性能),new对象的操作不是原子性的。这句代码最终会被编译成多条汇编指令。所以需要volatile关键字来禁止指令重排。

创建一个对象的过程中一旦出现了指令重排,可能就会获得半初始化的对象,即还没来得及赋值就先建立了引用关系。要避免这种情况的发生就要使用volatile关键字修饰实列变量。
在这里插入图片描述

15、对象在内存中的存储布局?(美团)

在这里插入图片描述

mark wordclass pointer 组成对象头class pointer指向对象的类。padding作用是将对象在内存中占用的字节数补齐到被8整除。(64位OS下)

追问,对象头具体包括什么?(有赞)(跟谁学)

主要包括锁的信息,有一个锁升级的过程。还记录了对象的年龄,四位二进制,超过16岁进入老年代。以及类指针

在synchronized大(优化)升级之前,是重量级锁,锁操作都要经过OS。向OS内核去申请。(jdk1.5之后)到现在的synchronized是有一个复杂的锁升级过程。

无锁 -> 偏向锁 -> 自旋锁(轻量级锁) -> (重量级锁)悲观锁。

以上的升级状态都记录在对象头中。

**偏向锁:**hotspot虚拟机认为大多数时间是不存在锁竞争的,所以每次都会把锁分配给上一次获得锁的线程,直到出现了锁竞争。

**自旋锁:**线程之间以CAS的方式进行锁资源的争抢。当一个线程自旋超过了10次或者当前自旋等待的线程超过了CPU核数的1/2(升级后优化为自适应自旋),会进行锁升级。

synchronized: 向OS申请资源,从用户态切换到内核态。线程挂起进入等待队列,等待OS的调度。然后再映射回用户空间。

16、对象如何定位?(美团)

hotspot使用的是直接指针方式。

17、对象如何分配?(shopee)(美团)

  1. 栈上分配:需要满足标量替换以及无逃逸(在一个方法中使用)。比在堆中分配时间快一倍。且无需GC回收。方法执行结束自动出栈。

  2. 堆中分配:无法进行栈上分配:判断对象个头,大对象直接入老年代。否则在伊甸区分配。伊甸区分配前先判断是否符合线程本地分配(由于线程争先恐后的在内存中分配,会加锁,效率不高,所以JVM做了一级优化,直接将对象分配到线程的私有空间中,这一操作不需要锁)

    具体过程如下图所示:
    在这里插入图片描述

18、一个Object占多少字节?(shopee)

Object o = new Object();
// o普通对象指针(Oops)4 字节(开启压缩占 4 字节,没开启占 8 字节),object对象占 16 字节 

19、Java的线程模型

在这里插入图片描述

线程与进程区别?

  • 资源分配:进程是资源分配的最小单位
  • 调度:线程是调度的最小单位
  • 通信:进程通信需要IPC
  • 创建:进程创建开销很大,线程创建的开销小。

进程与协程区别?

  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU
  2. 线程进程都是同步机制,而协程则是异步
  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

20、谈一下AQS,为什么底层使用CAS和volatile?(字节跳动)

  1. AQS源码中state状态值使用volatile修饰保证内存的可见性。因为涉及到多线程对state的修改,必须保证其对所有线程的可见性。
  2. CAS操作主要用于对state值的修改。

21、Synchronized与ReentrantLock的区别?(太多了)

  • 实现原理上synchronized是依靠jvm以及配合操作系统来实现,是一个关键字reentrantLockjdk1.5之后提供的API层面的互斥锁。
  • 使用便利性上synchronized只需要添加上相关关键字即可,加锁与释放过程由操作系统完成。reentrantLock则需要手动加锁与释放锁。
  • 性能区别synchronized优化之后性能与reentrantLock已经不相上下了,官方甚至更建议使用synchronized关键字。
  • 锁粒度与灵活度reentrantLock要强于synchronized

ReentrantLock提供了三个高级功能

  1. 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
  2. 多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
  3. 一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

22、Synchronized实现原理?(美团)(字节)(滴滴)

要说清楚锁升级的过程。

每个对象(在对象头中)有一个监视器锁(monitor),当monitor被占用时就处于锁定状态。线程执行monitorenter(汇编指令)尝试获取monitor的所有权。

  1. 如果monitor计数器当前值为0,那么该线程进入monitor并将计数器加1,
  2. 如果当前monitor计数器值不为0,那么该线程阻塞并进入(OS维护的)队列等待,等到OS的调度。

底层字节码被编译成monitorentermonitorexit两个指令。线程执行monitorexit指令,monitor计数器减1,如果减到0了,表示当前线程不在拥有该监视器锁。等待队列中的线程有机会获得锁资源。

追问,你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?(字节)

这个锁就是一个引用对象,也就是要加锁或者解锁的对象。

  1. 如果指明了锁对象如Synchronized(this) 则对this对象进行加锁

  2. 如果直接在方法上添加Synchronized,则锁定的是该方法所在对象

  3. 如果是对静态方法使用Synchronized,则是对静态方法所对应的类对象加锁

对一个对象加锁不影响对该对象其他方法的使用

追问,什么是可重入性,为什么说 Synchronized 是可重入锁?(字节)

重入性就是在一个同步方法中调用另一个同步方法,主要是为了防止自己把自己锁死的情况发生。

synchronized可重入锁的实现?

jvm对于重入锁的操作也很简单,在执行 monitorenter 指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器 +1,其实本质上就通过这种方式实现了可重入性。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁

追问,为什么说 Synchronized 是非公平锁?

非公平是指在获取锁的行为上,并不是按照线程申请顺序进行分配的,当锁被释放后,所有线程都有机会获取到锁,这样提高了性能,但是可能会出现某些线程饥饿的情况。

23、什么是锁消除和锁粗化?

  • 锁消除:虚拟机的运行时编译器在运行时如果检测到一些要求同步的代码上不可能发生共享数据竞争,则会去掉这些锁。
  • 锁粗化:将临近的代码块用同一个锁合并起来。

24、为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性?

当 Synchronized升级为重量级锁时,他是一个悲观锁。获取不到锁资源线程的线程由OS统一管理,涉及到用户态到内核态的切换。

乐观锁就是,当一个线程想要对变量进行操作时,先读取变量值,然后真正更改时会再次对当前值与自己之前读取的值是否相同,相同才会进行更改,不相同的话就会再次读取,然后在进行对比更改。主要是基于CAS实现。

CAS(compare and swap) :它涉及到3个操作数:1.内存值,预期值, 新值,只有当内存值和预期值相等的时候(证明没有其他线程在使用),才会将内存值设置为预期值。

CAS具有原子性,他的原子性由CPU保证,由JNI调用c++硬件代码实现,jdk中提供了unsafe来进行这些操作。

25、乐观锁一定就是好的吗?

不一定

  1. 乐观锁的情况下,如果线程并发度确实很高,那么大多数的线程都会处于自旋等待以获取锁对象的状态。这样会导致CPU占用过高。

  2. CAS另一个缺点就是ABA问题。一个值从A改为B又改为A,则CAS认为没有发生变化,解决的方式是使用版本号来记录操作次数。

26、ReentrantLock实现原理?

ReentrantLock的可重入功能基于AQS的同步状态:state。 其原理大致为:当某一线程获取锁后,将state值+1,并记录下当前持有锁的线程,再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,将state值再+1,如果不是,阻塞线程。

27、AQS原理?

AQS框架是用来构建锁的同步器框架,包括了常用的ReentrantLock,ReadWriteLock,CountDownLatch等都是基于AQS框架来实现的。

AQS使用一个FIFO队列表示排队等待锁的线程,队列头结点称作“哨兵节点”或者“哑结点”,它不与任何线程关联。其他的节点与等待线程关联,每个阶段维护一个等待状态waitStatus。

AQS中有一个表示状态的字段state,例如ReentrantLock用它来表示线程重入锁的次数,Semphore用它表示剩余的许可数量,FutureTask用它表示任务的状态。对state变量值的更新都采用CAS操作保证更新操作的原子性。

28、ReentrantLock 是如何实现可重入性的?

ReentrantLock内部持有了一个sync对象,这个对象实现了AQS,并且加锁的时候使用CAS算法,在所对象申请的时候,在锁等待node链表中查看当前申请的锁的对象是否是同一个对象,如果是的话,进行重入。

29、除了 ReetrantLock,你还接触过 JUC 中的哪些并发工具?

  • 提供了CountDownLatch CyclicBarrier Semaphore等更加高级的同步框架
  • 提供了currentHashMap,有序的ConcurrentSkipListMap等线程安全容器
  • 提供了针对各种场景的并发队列的实现
  • 强大的Executor框架,可以创建不同类型的线程池

30、Java中线程池是如何实现的?(滴滴)

java线程池中的对象被抽象成work对象,基于AQS,存放在线程池中的HashSet中,等待执行的任务则存放在成员变量workQueue中

31、线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?(滴滴)

不是

32、既然提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池又有哪些呢?

  • SingleThreadExecutor 线程池:只有一个线程在工作,也就是串行,如果线程出现问题,会有一个新的线程代替它
  • FixedThreadPool 线程池: 固定大小线程池,线程池大小达到最大就会保持不变
  • CachedThreadPool 线程池:无界线程池,SynchronousQueue 是一个是缓冲区为 1 的阻塞队列
  • ScheduledThreadPool 线程池: 核心线程固定,大小不限的线程池

33、如何在线程池中提交线程?

  1. execute():无返回值

  2. submit():返回Future对象。可以通过get()方法获取返回值。如果线程没有执行完成会阻塞。

34、动态代理是如何实现的?

直接使用InvocationHandler接口进行实现,同时利用Proxy类设置动态请求对象;使用CGLIB来避免对于代理设计模式需要使用接口实现的限制。

35、HashMap 为什么线程不安全?

HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入(头插法)。多线程环境下执行插入操作时,可能会发生多个线程同时获取了链表的头节点。可能会造成线程写入的操作被覆盖。

36、HashMap 对比ConcurrentHashMap?(网易)

  • HashMap:线程不安全,
  • ConcurrentHashMap:线程安全。JDK1.7之前由分段锁(继承了可重入锁)实现。JDK1.8之后由CAS+Synchronized实现。

37、LinkedHashMap了解吗?

一种可以实现LRU的数据结构。是有序的HashMap

38、wait()对比sleep()?(大华)(字节)

  1. wait()是object类的方法,sleep()是thread的方法
  2. 调用wait之后进入阻塞状态,同时会失去CPU时间片。而调用sleep()不会失去。

39、垃圾回收算法中是如何来判断垃圾的?

  1. 引用计数法:为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
  2. 可达性分析:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。

40、GC root包含什么?(字节)(有赞)(跟谁学)

  • 虚拟机栈空间中非静态变量表中引用的对象
  • 本地方法栈中 JNI 中引用的对象
  • 方法区中静态变量引用的对象
  • 方法区中的常量引用的对象

41、类的加载过程?

  1. 加载:加载class字节码文件
  2. 验证:验证字节码文件中是否会拟机安全的
  3. 准备:准备阶段为类变量分配内存并设置初始值,使用的是方法区(jdk 1.8 元空间实现)的内存。
  4. 解析:将常量池的符号引用替换为直接引用的过程。
  5. 初始化:初始化阶段才真正开始执行类中定义的 Java 程序代码。

42、类加载器的分类?

启动类加载器、扩展类加载器、应用程序类加载器

43、双亲委任机制?

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。向上委派,向下查找

优点

  1. 避免类的重复加载
  2. 保护程序安全,防止核心API被破坏。

44、JVM常用的参数?(美团)

  • 初始堆内存: -Xms
  • 最大堆内存: -Xmx
  • 元空间: -XX MetaSpaceSize
  • 新生代初始内存: -XX NewSize
  • 新生代最大内存: -XX MaxNewSize
  • 设置栈内存:-Xss

45、JVM 加载 Class 文件的原理机制?

以双亲委任制的方式去加载class文件。

46、垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?(滴滴)

垃圾回收器不会马上释放内存空间,而是在某个对象被标记为垃圾之后的下一次GC时才会对内存空间进行释放。

我们可以手动执行System.gc(),通知虚拟机进行GC,但是Java语言规范并不保证GC一定会执行。

47、深拷贝和浅拷贝?

浅拷贝:原始对象的引用与副本对象的引用指向堆中的同一个对象。

深拷贝:将原始对象完整复制一份放到堆内存中,原始对象的引用与副本对象的引用指向堆中的不同对象。

Object 类提供的 clone( ) 只能实现浅拷贝。

48、System.gc() 和 Runtime.gc() 会做什么事情?

调用System.gc()方法会通知jvm进行垃圾回收,但是并不保证一定会垃圾回收。

每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。 Runtime.getRuntime().gc()Runtime.gc()System.gc()并没有实质性的区别,唯一的区别就是前者比较好写一点儿。

49、finalize() 方法什么时候被调用?

当一个对象不再被引用时,GC过程中会调用该对象的finalize() 方法(继承自Object类)对该对象进行回收。

50、JVM 的永久代中会发生垃圾回收么?

会,主要是对常量池以及类的卸载。对类的卸载需要满足三个条件。

  • 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。

51、为什么集合类没有实现 Cloneable 和 Serializable 接口?(跟谁学)

Cloneable标识一个类可以被克隆,Serializable标识一个类可以被序列化。

集合类没有实现这两个接口,但是集合类的具体实现类实现了这两个接口。集合类接口不是具体的容器,所以不需要实现这两个接口,没有任何意义。

52、Iterator 和 ListIterator 的区别是什么?

Iterator可以用来遍历Set、List。 ListIterator只能用来遍历List 。

ListIterator实现了Iterator,在Iterator的基础上有了更强大的功能,比如增加、替换元素。还支持向前遍历。

53、原子类的实现原理?

atomic 主要利用 CAS (Compare And Swap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

54、JavaIO 流中使用了哪些设计模式?

装饰模式和适配器模式

55、主线程会等待其他线程执行完吗?

不会,其他线程不受主线程结束的影响。

56、gc时间是否可控?

单次GC的时间其实是不可控的,但是取了平均值,GC就可以动态去调整heap的大小,或者其他的一些GC参数,从而保证每次GC的时间不会超过这个平均值。

57、垃圾收集算法中的复制算法会有s1区空间不够的情况吗?(跟谁学)

IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。

58、虚拟机栈包含什么?(跟谁学)

虚拟机栈空间以栈帧为基本单位

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟街运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表操作数栈动态链接方法返回地址等信息。每一个方法从调用开始到执行完成的过程,都是一个栈帧在虚拟机栈里面从出栈到入栈的过程。

59、四种引用介绍一下?

不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。

强、软、弱、虚

60、java 内存模型?(很长问)

Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。

61、堆、栈内存溢出问题的排查与解决方法?(滴滴)

  • 堆内存溢出原因:heap空间吃紧。排查:检查是否内存溢出,检查代码是否有死循环、递归等操作。最后考虑使用 -Xmx增加堆的大小。
  • 栈溢出原因:栈帧太大或者虚拟机栈空间太小,当内存无法分配时会导致StackOverflowError 异常。

查找关键报错信息,确定是StackOverflowError还是OutOfMemoryError
如果是StackOverflowError,检查代码是否递归调用方法等。
如果是OutOfMemoryError,检查是否有死循环创建线程等,通过-Xss降低的每个线程栈大小的容量。

62、不使用额外的变量完成交换两个变量数值?(有赞)

利用异或性质。

public class 交换两个变量 {
    public static void main(String[] args) {
        int a = 2;
        int b = 3;
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        System.out.println(a);
        System.out.println(b);
    }
}

63、线上生产如何尽量避免Full GC的出现?(有赞)

  1. 禁止System.gc()方法的调用,因为该方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存。
  2. 老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space
    为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

64、java中有CAS的实现吗?

JUC包下的原子类都是基于CAS操作的。JAVA中的CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,由JVM本地实现。Unsafe中对CAS的实现是C++写的,从上图可以看出最后调用的是Atomic:comxchg这个方法,这个方法的实现放在hotspot下的os_cpu包中,说明这个方法的实现和操作系统、CPU都有关系。

65、线程池构造?(滴滴)

为什么有了核心线程数参数还需要最大线程数参数?(滴滴)(美团)

基于性能考虑,核心线程数的设置与日常流量有关。最大线程数与最大峰值流量(如秒杀场景下) 有关,超过最大线程数后反而会导致机器的性能变低。并且合理的设置阻塞队列的长度。

等待队列的有界、无界了解吗?(滴滴)

阻塞队列的几种实现:

LinkedBlockingQueue
SynchronousQueue
ArrayBlockingQueue;

根据线程池的运行原理,如果选用了无界的等待队列,那么会导致最大线程数参数失效。因为队列永远不会装满,我们的服务器面对高并发时也就无法发挥最优性能。

与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。

你知道线程池为什么这样设计吗?(滴滴)

线程池这样设计实际上是构建了一个生产者消费者模型,它将线程和任务两者解耦,从而良好的缓冲任务,复用线程。线程池的运行分为两大部分,任务管理、线程管理。

任务管理充当生产者,当任务提交后,线程池会判断该任务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3)拒绝该任务。

线程管理充当消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

线程数的设置与IO时间以及cpu执行时间的一个关系?(美团)

阿姆达尔定律:

设置的线程数 = CPU 核数 * (1 + IO time / CPU computing time)

举例说明,假设4核 CPU,每个任务中的 IO 任务占总任务的80%,CPU时间占用20%。则线程数应设置为:4 * (1 + 4) = 20个线程,这里的20个线程对应的是4核心的 CPU。

队列大小 = 线程数 * (目标相应时间/任务实际处理时间)等待队列一定要使用有界队列,否则会拖垮整个系统。

66、Java是编译型语言还是解释型语言?(华为)

半解释半编译。首先由Javac将Java文件编译为class文件,然后由jvm解释执行。

67、说说你对Error和Exception的理解?(华为)

首先两者继承自Throwable类,Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。 Error 是指程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。

异常分为 运行时异常其他异常

  • 运行时异常(不受检异常):空指针异常、数组索引越界异常、
  • 其他异常(受检异常):例如 IOException 使用 try catch finally 进行处理

68、谈一下反射?(网易)

java语言本身是静态类型的语言,但是由于有了反射机制使得java具有了一定的动态特性。

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

反射是动态代理的基础,动态代理提供了运行时的代理模式。是Spring AOP的实现方式。

69、TreeMap与LinkedHashMap的有序性区别?(华为)

LinkedHashMap 通常提供的是遍历顺序符合插入顺序,它的实现是通过为条目(键值对)维护一个双向链表。注意,通过特定构造函数,我们可以创建反映访问顺序的实例,所谓的 put、get 都算作访问。

对于 TreeMap,它的整体顺序是由键的顺序关系决定的,通过 Comparator 或Comparable(自然顺序)来决定。

70、谈谈面向对象语言和面向过程语言的区别?(华为)

  • 面向过程:面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
  • 面向对象:面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。

71、抽象类与接口的区别?

  • 此为JDK1.8之前:

    1. 抽象类中可定义构造函数,接口中不可
    2. 抽象类中可有抽象方法和具体方法,接口中只有抽象方法
    3. 抽象类的成员权限可为 public、默认、protected,而接口成员只可为public
    4. 抽象类中可包含静态方法,接口中不可包含
  • JDK1.8之后,接口中加入了 default,即默认方法,允许接口中可有具体方法;可包含静态方法但是不包含静态代码块

72、TreeMap与HashMap与HashTable的区别?

  1. 这三个都对Map接口进行了实现
  2. 2.HashMap是不安全的线程,他允许Key值出现一次null Value值出现无数次的Null
  3. 3.Hashtable是安全的线程,他不仅实现了Map接口也实现了Dictionary接口,他的key值与Value值都不允许出现Null
  4. 4.TreeMap是可以进行排序的,默认按照键的自然顺序进行升序排序,若要进行降序排序则需要在构造集合时候传递一个比较器

操作系统常见问题

1、如何进行进程通信?(百度)(字节)(shopee)(腾讯)

  1. 共享内存:两个进程通过对一块共享空 间的访问实现通信。各进程对共享空间的访问是互斥的。又可以细分为基于数据结构(共享空间放一个长度为10的数组,这种共享方式比较慢,是一种低级的通信方式)、基于存储区(在内存中划一块共享储存区,数据的形式、存放位置都由进程控制,而不是OS,相比之下这种共享方式更快,是一种高级通信方式)。

    **共享内存通信的优缺点:**可以解决消息队列通信带来的数据拷贝带来的开销问题。

  2. 消息队列:进程间的数据交换以格式化信息为单位,进程通过OS提供的发送消息/接收消息两个原语进行数据交换。消息队列是保存在内核中的消息链表

    直接通信方式:消息直接挂到接收进程的消息缓冲队列上

    间接通信方式:消息要先发送到中间实体(信箱)中。

    消息队列通信的优缺点:

    首先解决了管道通信的不适合进程频繁通信的问题。但是它的缺点是:一,通信不及时。二,附件有大小的限制。三,通信过程中会存在着用户态与内核态之间的数据拷贝带来的开销。

  3. 管道/匿名管道通信:管道是指用于连接读写进程的一个共享文件,又名pipe,其实就是内存中开辟的一个大小固定的缓冲区。单管道只能进行半双工通信。Linux中管道符为 | 。

    管道通信的优缺点:

    管道通信的效率低,不适合进程间频繁的交换数据,好处是简单,我们很容易的就可以知道管道中的数据被另一个进程读取。

  4. 有名管道:匿名管道由于没有名字,只能用于亲缘关系的进程间通信,有名管道严格遵循先进先出,以磁盘方式存在,实现本机任意两进程通信

  5. 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生

  6. 信号量:信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间的同步,主要解决与同步相关的问题并避免竞争条件

  7. Socket:前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。

2、页面置换算法 ?(百度)(美团)(滴滴)

现在大多数都采用了虚拟内存技术,所以需要页面置换算法。在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断(要访问的资源不存在,由用户态陷入内核态调用相关资源,是一种内中断)从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间

  1. 最佳置换算法(OPT):是一种仅存在于理论中的算法,因为无法得知哪一个页面是最长时间没有被访问的。

  2. 先进先出置换算法(FIFO):把调入内存的页面根据调入的先后顺序排成一个队列,队列的大小为OS为进程分配的多少个内存块。需要置换的时候将第一个进入队列的页调出。该算法并不符合实际的运行规律。

  3. 最近最久未使用置换算法(LRU):每次淘汰的是最近最久未使用的页面。页表中用访问字段记录该页面自上次访问以来所经历的时间t,当需要淘汰一个页面时,选择现有页中t值最大的。算法性能较好,但是开销较大

  4. 时钟置换算法(CLOCK):时钟置换算法是一种兼顾性能与开销的算法。

    实现过程:

    1. 为页面设置一个访问位,然后将内存中的页面通过指针连结成一个循环队列
    2. 当某个页被访问时,访问位置1。当需要淘汰一个页时,只需要检查访问位,0则换出;如果是1则置0,暂不换出,继续检查下一个页面访问位。最多经过两轮检查会找到一个淘汰页面。

3、进程的内核态和用户态?(百度)(网易)

为了维护OS安全,设置了特权级概念。运行于不同特权级的进程所拥有的权限也不同。大部分进程都是运行在用户态的。只有当需要完成一些自己本身权限无法完成的业务时,会通过系统调用切换到内核态来让操作系统帮忙执行。

4、进程调度算法?(腾讯)

为什么需要进程调度?由于cpu资源的有限性,导致需要一套完整的算法来对进程做一个管理。

  • 非抢占式调度算法:
  1. 先来先服务(FCFS,first come first served):进程按照它们请求CPU的顺序使用CPU.就像你买东西去排队,谁第一个排,谁就先被执行,在它执行的过程中,不会中断它。当其他人也想进入内存被执行,就要排队等着,如果在执行过程中出现一些事,他现在不想排队了,下一个排队的就补上。此时如果他又想排队了,只能站到队尾去
  2. 最短作业优先 (SJF, Shortest Job First):对预计执行时间短的进程优先分派处理机。通常后来的短进程不抢先正在执行的进程。相比FCFS 算法,该算法可改善平均周转时间和平均带权周转时间,缩短进程的等待时间,提高系统的吞吐量。对于长进程不利,可能会导致长进程的饥饿
  • 抢占式调度算法:
  1. 最短剩余时间优先 :是最短作业优先的抢占版本,按照剩余运行时间进行调度。当一个新作业到达时,其整个的运行时间与当前运行进程的剩余时间对比。如果新的进程所需时间更少会将当前运行进程挂起,运行新的进程。

  2. 时间片轮转算法(RR,Round-Robin):每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。让就绪进程以FCFS 的方式按时间片轮流使用CPU 的调度方式,即将系统中所有的就绪进程按照FCFS 原则,排成一个队列,每次调度时将CPU 分派给队首进程,让其执行一个时间片,时间片的长度从几个ms 到几百ms。在一个时间片结束时,发生时钟中断,调度程序据此暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前的队首进程,进程可以未使用完一个时间片,就出让CPU(如阻塞)。

  3. 多级反馈队列(Multilevel Feedback Queue) :如果一个进程需要执行100个时间片,如果采用时间片轮转算法则需要交换100次,多级队列专门为了这种需要连续执行多个时间片的进程考虑。它设置多个队列。每个队列时间片的大小也不相同。假设现在有四个队列。时间片分别为 1、2、4、8那么一个需要100时间片的进程仅需要交换7次(100/(1+2+4+8)= 7)。

    进程在队列1中执行结束后进入队列2…依次直到进程耗尽时间片。最上面的队列优先级最高,只有当上一个队列没有进程排队,才能调度当前队列的进程。

5、如何实现进程同步?(字节)

进程同步:多个进程按照一定的顺序执行。

  • 信号量机制
  • 管程

6、什么是死锁?(美团)(跟谁学)(滴滴)(百度)

多个进程在运行过程中因资源争夺而造成的一种僵局。

7、死锁的必要条件?

  • 互斥:被争夺的资源要么分配给了一个进程要么就是可用的
  • 请求与保持:已经得到资源的进程依旧可以申请新资源
  • 不可抢占:已经分配的资源不可以强制性的被抢占,只能被拥有者显式释放
  • 循环等待:有两个或以上的进程形成一条环路。

8、如何处理死锁? (美团)(跟谁学)(滴滴)(百度)

  1. 鸵鸟策略:假装没有发生死锁
  2. 死锁检测与死锁恢复
  3. 死锁预防:破坏死锁产生的必要条件,破坏互斥条件、不可抢占条件、环路等待条件。
  4. 死锁避免:在程序运行时避免发生死锁。

解决死锁方法多角度分析,有 预防、避免、检测 和 解除

  • 预防:是采用某种策略,限制并发进程对资源的请求,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
  • 避免:则是系统在分配资源时,根据资源的使用情况提前做出预测,从而避免死锁的发生
  • 检测:是指系统设有专门的机构,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
  • 解除:是与检测相配套的一种措施,用于将进程从死锁状态下解脱出来。

死锁的预防:只要破坏四个必要条件中的任何一个就能够预防死锁的发生。

  • 破坏第一个条件 互斥条件:使得资源是可以同时访问的,这是种简单的方法,磁盘就可以用这种方法管理,但是我们要知道,有很多资源 往往是不能同时访问的 ,所以这种做法在大多数的场合是行不通的。
  • 破坏第三个条件 非抢占 :也就是说可以采用 剥夺式调度算法,但剥夺式调度方法目前一般仅适用于 主存资源 和 处理器资源 的分配,并不适用于所以的资源,会导致 资源利用率下降。
  • 所以一般比较实用的 预防死锁的方法,是通过考虑破坏第二个条件和第四个条件。

1、静态分配策略:可以破坏死锁产生的第二个条件(占有并等待)。所谓静态分配策略,就是指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况。静态分配策略逻辑简单,实现也很容易,但这种策略 严重地降低了资源利用率,因为在每个进程所占有的资源中,有些资源是在比较靠后的执行时间里采用的,甚至有些资源是在额外的情况下才是用的,这样就可能造成了一个进程占有了一些 几乎不用的资源而使其他需要该资源的进程产生等待 的情况。

2、层次分配策略:破坏了产生死锁的第四个条件(循环等待)。在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源,按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略,证明略。

死锁的避免:上面提到的 破坏 死锁产生的四个必要条件之一就可以成功 预防系统发生死锁 ,但是会导致 低效的进程运行 和 资源使用率 。而死锁的避免相反,它的角度是允许系统中同时存在四个必要条件 ,只要掌握并发进程中与每个进程有关的资源动态申请情况,做出 明智和合理的选择 ,仍然可以避免死锁,因为四大条件仅仅是产生死锁的必要条件。

我们将系统的状态分为 安全状态 和 不安全状态 ,每当在未申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。如果操作系统能够保证所有的进程在有限的时间内得到需要的全部资源,则称系统处于安全状态,否则说系统是不安全的。很显然,系统处于安全状态则不会发生死锁,系统若处于不安全状态则可能发生死锁。那么如何保证系统保持在安全状态呢?通过算法,其中最具有代表性的 避免死锁算法 就是 Dijkstra 的银行家算法,银行家算法用一句话表达就是:当一个进程申请使用资源的时候,银行家算法 通过先 试探 分配给该进程资源,然后通过 安全性算法 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就 真的分配资源给该进程。

死锁的避免(银行家算法)改善解决了 资源使用率低的问题 ,但是它要不断地检测每个进程对各类资源的占用和申请情况,以及做 安全性检查 ,需要花费较多的时间。

死锁的检测:对资源的分配加以限制可以 预防和避免 死锁的发生,但是都不利于各进程对系统资源的充分共享。解决死锁问题的另一条途径是 死锁检测和解除 。这种方法对资源的分配不加以任何限制,也不采取死锁避免措施,但系统 定时地运行一个 “死锁检测” 的程序,判断系统内是否出现死锁,如果检测到系统发生了死锁,再采取措施去解除它。# 进程-资源分配图操作系统中的每一刻时刻的系统状态都可以用进程-资源分配图来表示,进程-资源分配图是描述进程和资源申请及分配关系的一种有向图,可用于检测系统是否处于死锁状态。用一个方框表示每一个资源类,方框中的黑点表示该资源类中的各个资源,每个键进程用一个圆圈表示,用 有向边 来表示进程申请资源和资源被分配的情况。

死锁检测步骤:知道了死锁检测的原理,我们可以利用下列步骤编写一个 死锁检测 程序,检测系统是否产生了死锁。

  1. 如果进程-资源分配图中无环路,则此时系统没有发生死锁
  2. 如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
  3. 如果进程-资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程-资源分配图中找出一个 既不阻塞又非独立的进程 ,该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能在有限的时间内 消除所有的边 ,则不会发生死锁,否则会发生死锁。(消除边的过程类似于 拓扑排序)

死锁的解除:当死锁检测程序检测到存在死锁发生时,应设法让其解除,让系统从死锁状态中恢复过来,常用的解除死锁的方法有以下四种:

  1. 立即结束所有进程的执行,重新启动操作系统 :这种方法简单,但以前所在的工作全部作废,损失很大。
  2. 撤销涉及死锁的所有进程,解除死锁后继续运行 :这种方法能彻底打破死锁的循环等待条件,但将付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
  3. 逐个撤销涉及死锁的进程,回收其资源直至死锁解除。
  4. 抢占资源 :从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除。

9、什么是虚拟内存?(腾讯)

对于一些很大的程序,将很快需要用到的装入内存,当需要访问的页不在内存时再由OS外存调入(依据页面置换算法)。当内存吃紧时,再由OS将内存中暂时不用的信息换出到外存。这就是虚拟内存技术。

10、什么是上下文切换?(美团)

将CPU资源从一个进程分配到另一个进程的机制。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。

11、为什么进程上下文切换的代价比线程上下文切换高?(美团)

进程切换分两步:

  1. 切换页目录以使用新的地址空间
  2. 切换内核栈和硬件上下文

线程切换不需要切换页目录。

12、时间片概念?(百度)

是时分操作系统分配给每个正在运行的进程微观上的一段CPU时间。

13、段、页式存储?

  • 分页存储:将内存空间分成一个个大小相等的分区,每一页有自己的编号,页框不能设置太大,否则会产生内部碎片。不方便按照逻辑模块实现信息的共享。
  • 分段存储:信息的逻辑单位,很方便的按照逻辑模块实现信息的共享,缺点是可能会由于段长度过大导致

14、内存管理方式?

  • 物理内存管理:包括程序装入等概念、交换技术、连续分配管理方式和非连续分配管理方式(分页、分段、段页式)。
  • 虚拟内存管理:虚拟内存管理包括虚拟内存概念、请求分页管理方式、页面置换算法、页面分配策略、工作集和抖动。

计算机网络常见问题

1、路由表中有环怎么办?(字节)

路由是网络层组件

什么是路由表?

称 路由择域信息库 (RIB, Routing Information Base),是一个存储在 路由器 或者联网计算机中的电子表格(文件)或类数据库。 路由表存储着指向特定 网络地址 的路径(在有些情况下,还记录有路径的路由度量值)。

什么是路由回路?

在维护路由表信息的时候,如果在拓扑发生改变后,网络收敛缓慢产生了不协调或者矛盾的路由选择条目,就会发生路由环路的问题。这种情况下会导致用户的IP数据包不停在网络上循环发送,最终造成网络资源的严重浪费。

RIP协议解决如何解决路由回路问题?

RIP协议是一种基于距离度量的路由选择协议。

  1. 最大跳数:主要是针对的距离矢量的路由协议来说的,是说的这样的路由协议能把一个路由通告传送过最多多少个路由器。直接相邻的路由器跳数为1,跳数超过15表示不可达。
  2. 水平分割是一种避免路由环路的出现和加快 路由汇聚 的技术。 由于 路由器 可能收到它自己发送的路由信息,而这种信息是无用的,水平分割技术不反向通告任何从终端收到的路由更新信息,而只通告那些不会由于计数到无穷而清除的路由。
  3. 路由中毒:是指在路由信息在路由表中失效时,先将度量值变为无穷大,而不是马上从路由表中删掉这条路由信息。 (这句话要理解,如RIP协议中,其度量值变为16,意味着路由不可达)再将其信息发布出去,这样相邻的路由器就得知这条路由己无效了
  4. 毒性逆转:实际上是一种改进的水平分割,这种方法的运作原理是:路由器从某个接口上接收到某个网段的路由信息之后,并不是不往回发送信息了,而是发 送,只不过是将这个网段标志为不可达,再发送出去。 收到此种的路由信息后,接收方路由器会立刻抛弃该路由,而不是等待其老化时间到(Age Out)。
  5. 控制更新时间:顾名思义,RIP的更新信息发布是由更新定时器控制的,默认为 每30秒 发送一次
  6. 触发更新:正常情况路由器会基于 更新计时器每30s 将路由表发送给邻居路由器,而 触发更新是立刻发送路由更新信息。触发更新就是当检测到网络拓扑发生变动时,路由器会立即发送一个更新信息给邻居路由器,并依次产生触发更新通知它们的邻居路由器,此过程就叫触发更新。

OSPF(开放最短路径优先)协议解决路由回路问题。

2、四次挥手最后等待为什么2MSL?(百度)(腾讯)(字节)(美团)(顺丰科技)(滴滴)(跟谁学)(有赞)

两个原因:

  1. 确保最后一个确认报文到达,如果server没有收到client发送来的确认报文,那么就会重新发送连接释放请求报文。客户机等待一段时间就是为了处理这种情况发生。
  2. 等待一段时间是为了让本连接内产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

3、https怎么保证安全?(百度)

http的安全问题:

  1. 使用明文通信,内容可能被窃听。
  2. 不验证通信方身份,通信方身份可能存在伪装。
  3. 无法验证报文的完整性,报文有可能遭到篡改。

https通过使用SSL具有了加密(混合加密)、认证(数字证书,用于存放公钥,保证其不被篡改以及可信性)、完整性保护(摘要算法)等功能。

4、SSL/TLS连接怎么建立?

SSL:安全套接字层,位于传输层与应用层之间的一种协议层。通过相互认证、使用数字签名确保完整性。使用加密保证私密性。以实现客户机服务器之间的安全通讯。该协议由两层组成:SSL记录协议和SSL握手协议。

  1. 客户端向服务器索要并验证服务器的公钥。
  2. 双方协商生产「会话秘钥」。
  3. 双方采用「会话秘钥」进行加密通信。

5、为什么是四次挥手、为什么time-wait等待2MSL时间?

如果是两次挥手,客户机请求关闭服务端直接关闭了,有可能服务端的数据并没有传输完成,造成数据丢失。如果是三次挥手,服务端数据传输完成立即关闭连接。可能会导致本次tcp连接产生的报文残留在网络中。所以需要四次挥手。

6、TCP与UDP?(百度)(腾讯)(用友)(大华)(字节)(美团)(顺丰科技)(滴滴)(跟谁学)(有赞)(华为)

TCP是传输控制协议,提供面向连接可靠字节流服务。通过三次握手建立连接。之后才能进行数据传输。TCP提供超时重传、流量控制、拥塞控制等功能。

UDP是用户数据报协议,是一个简单的面向无连接的协议。UDP不提供可靠服务,由于传输数据之前不需要建立连接,所以传输速度很快。其主要使用场景有流媒体传输

7、TCP的流量控制?(华为)(中兴)(百度)(字节)

利用滑动窗口实现流量控制,如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。

8、TCP三次握手过程中客户机与服务端的状态变化?(腾讯)(跟谁学)(字节)

客户机经历了:close -> SYN-sent -> estab-listen

服务端经历了:close -> listen -> SYN-RCVD ->estab-listen

9、TCP的拥塞控制?(百度)

拥塞窗口概念。初始化cwnd = 1

慢开始、拥塞避免、快重传、快恢复。

10、ARP工作原理?

ARP:地址解析协议。

每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。

源主机向当前网段的所有主机发送ARP数据包,包含了源主机IP、MAC地址,以及目标主机IP地址。

本地网络的主机收到数据包后检查其中的目标IP是否为自己的。如果不是则忽略,如果是的话就将自己的MAC地址写入数据包,并将数据包中源主机的IP、MAC地址写入自己的ARP缓冲区中。

11、ICMP协议?

ICMP是Internet Control Message Protocol,因特网控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由器是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。ICMP报文有两种:差错报告报文和询问报文。

12、数据链路层的作用?(字节)

封装成帧、透明传输、差错检测(循环冗余检测保证传输过程中的数据准确性)。

13、传输层协议与网络层协议的区别?

网络层协议负责的是提供主机间的逻辑通信,运输层协议负责的是提供进程间的逻辑通信。

14、静态路由和动态路由的区别?

静态路由是由管理员手工配置的,适合比较简单的网络或需要做路由特殊控制。而动态路由则是由动态路由协议自动维护的,不需人工干预,适合比较复杂大型的网络。

15、IO同步、异步区别?阻塞、非阻塞区别?

客户端发出一个请求,在服务器做出响应之前客户端发过来的请求线程会被挂起,这就是阻塞。此时线程只能等完成这次的请求之后才可以去处理其他的事件,这就叫做同步

客户端发出了一个请求,然后不等服务器处理就直接返回给客户端了。此时请求线程没有被挂起,这就是非阻塞。线程可以去处理其他事件,这就是异步。服务器通过回调函数来处理这个请求。

16、数据链路层的协议?(字节)

点到点信道的数据链路层协议:

PPP(point-to-point protocal)协议:互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议

使用广播信道的数据链路层协议:

载波监听、多点接入、冲突检测协议:

  • 多点接入:许多计算机以多点接入的方式连在一根总线上。
  • 载波监听:监听线路上其他设备是否在线,不在线的话可以进行通信
  • 冲突检测:在发送中,如果监听到信道已有其它主机正在发送数据,就表示发生了碰撞。

17、DNS寻址过程?(美团)

  1. 本地host文件
  2. 本地DNS解析器缓存
  3. 本地DNS服务器
  4. 根域名DNS服务器
  5. 递归的去找到包含目标IP的DNS服务器

18、输入网址的过程?(字节)(百度)

  1. 浏览器向DNS服务器请求解析该url中携带域名的IP地址,并返回给浏览器
  2. 浏览器与上述ip地址服务器通过三次握手建立TCP连接
  3. 浏览器发出HTTP请求,随后服务器将浏览器请求的资源发送给浏览器
  4. 数据传输完成,浏览器请求关闭TCP连接(四次挥手)
  5. 浏览器渲染资源,进行页面展示

19、HTTP1.0 和 HTTP1.1 和 HTTP2的区别?(百度)

HTTP1.1相较于HTTP1.0 增加了长连接功能,该功能不会主动的去断开一个TCP连接,这样的话就不用每次发送HTTP请求时都重新建立TCP连接。因为重复的建立、断开TCP连接费时又费资源。

HTTP2.0主要有以下几个新特性:

头部压缩、多路复用、二进制帧层

20、Http协议的标准请求头有哪些?(百度)

Accept:可以接收的响应内容格式

Connection:客户端想要优先使用的连接类型,keep-alive、upgrade

Host:客户端告诉服务端它请求的资源所在的主机与端口号

cookie:客户端请求时携带的数据

21、如何预防TCP三次握手期间的SYN攻击?(百度)

首先,TCP协议建立连接前需要双方确认信息,用于防止伪造连接以及精准控制整个数据传输过程中数据完整有效。这样就会造成TCP连接的资源消耗,其中包括:数据包信息、条件状态、序列号等等。SYN攻击就是故意不完成建立连接所需要的三次握手过程,造成连接一方的资源耗尽。

SYN攻击: SYN洪泛攻击的基础是依靠TCP建立连接时三次握手的设计。 第三个数据包验证连接发起人在第一次请求中使用的源IP地址上具有接受数据包的能力,即其返回是可达的。 据统计,在所有 黑客 攻击事件中,SYN攻击是最常见又最容易被利用的一种攻击手法。

**如何检测:**Linux中使用 netstat -n -p TCP | grep SYN_RECV 命令检测是否被SYN攻击。

如何防范?主要有两大类,一类是通过防火墙、路由器等过滤网关防护,另一类是通过加固TCP/IP协议栈防范

22、TCP与UDP的区别?

  1. TCP是面向连接的协议,在收发数据前需要与对方建立可靠的连接。一个TCP连接必须通过三次握手才能建立起可靠的连接。而UDP是一个非连接协议。由于传输数据不建立连接,因此一台服务器可以向多客户机传输相同的信息。
  2. TCP的滑动窗口可以进行流量控制,而UDP的吞吐量除了受网络带宽等限制外不受其他限制。
  3. TCP提供基于超时重传的可靠交付,而UDP提供尽最大努力交付,即不保证可靠交付。
  4. UDP是面向报文的。发送方的UDP对应用程序交下来的报文, 在添加首部后就向下交付给IP层。TCP面向数据流。

23、HTTP协议为什么设计为无状态的?(百度)

HTTP设计为无状态的话服务端就可以根据需求将请求分发到服务集群的任意节点上。有利于做负载均衡。

24、TCP如何保证可靠传输?(华为)(滴滴)

依靠超时重传实现可靠传输。TCP每发送一个数据报后会开启一个计时器,等待目标服务器确认收到了这个报文段。如果计时器时间内没有收到确认,则会重发这个报文段。

25、服务端可以主动断开TCP连接吗?(跟谁学)

可以

26、http常用状态码?

1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。

2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。

3xx 类状态码表示客户端请求的资源发送了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向

301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。

302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。

4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。

404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。

5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。

27、TCP的三次握手为什么不是两次?(字节)(美团)(shopee)

  • 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
  • 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
  • 顺带也解决了已失效的连接请求又传送到服务器端的问题。

大数据常见问题

1、用4G的内存空间存储10亿个64字节url用什么数据结构?(字节)

假设每个网页 url 平均长度 64 字节,则 10 亿个 url 大约需要 60 G 内存。

使用布隆过滤器,针对 10 亿个 url,我们分配 100 亿个 bit,大约 1.2 G, 相比 100 G 内存,提升了近百倍

2、有几十亿的中文分词,分词是从文档中摘出来的。现在需要你设计一种数据结构来储存分词与文档的映射关系?(字节)

Linux常见问题

1、Linux中有多个文件,从里面匹配一个字符串命令?(百度)(美团)

cat a.txt b.txt | grep string

2、查看网络是否连通的指令?(网易)(有赞)

netstat

3、查看所有进程?

ps -ef

4、查看文件内容有哪些指令?

  • vi 文件名:编辑方式查看,可修改
  • cat 文件名 :显示全部文件内容
  • more 文件名:分页显示文件内容
  • less 文件名:与 more 相似,更好的是可以往前翻页
  • tail 文件名:仅查看尾部,还可以指定行数
  • head 文件名:仅查看头部,还可以指定行数

5、复制文件指令?连同文件夹一起复制?

cp -r

6、删除文件用哪个命令?如果需要连目录及目录下文件一块删除呢?

rm -r

7、对一个文件的内容进行统计的指令?(行数、字节数、单词数)

wc 命令 - c 统计字节数 - l 统计行数 - w 统计字数。

8、grep指令的作用?

是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

9、如何查看后台任务?

job -l

10、终止进程?

kill -9 pid

11、搜索文件的命令?

find、whereis、locate等等

12、查看使用过的命令的列表?

history

13、查看磁盘使用空间?

df -hl Size(总空间) Used(已用) Avail(可用) Use%(使用百分比) Mounted on(挂载区)

14、如何一页一页的查看大文件的内容?

cat a.txt | more

15、IO多路复用?(百度)(字节)

IO多路复用的目的就是为了设计一个高性能的网络服务器,可供多个客户端去连接。之所以我们不用多线程的方式去设计是因为多线程环境下的上下文切换带来的消耗也是很大的。

在Linux系统中,一切都是文件,每一个网络连接(socket)在内核中都是以文件描述符(fd)的形式存放。

select实现IO多路复用的实现方式

#select函数实现
使用 fd_set 实现,里面装的是文件描述符,大小限制为 1024bit
select()函数会将 fd_set 数组从用户态一次性的拷入内核态,交由内核处理会大大提升效率
当有数据到达一个描述符时,select函数会返回(在此之前是阻塞的)
然后遍历 fd_set 找到那个有数据到达的文件描述符 O(n)的时间复杂度获取到相关描述符。
缺点:
1、fd_set 不可重用
2、用户态到内核态数据拷贝的开销
3O(n)时间复杂度的轮询

epoll实现IO多路复用的实现方式

// epoll函数实现
epoll_create() 创建一个白板(是一块用户态和内核态共享的一块内存空间)存放fd_events
epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上。
epoll_wait 通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符 O(1)的时间复杂度获取到相关描述符。
优点:
完全解决了select的所有问题。

epoll支持两种触发模式:

LT:水平触发
epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。
ET:边缘触发
和 LT 模式不同的是,通知之后进程必须立即处理事件。
下次再调用 epoll_wait() 时不会再得到事件到达的通知。很大程度上减少了 epoll 事件被重复触发的次数,
因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

16、秒杀中的日志中记录了某个商品的下单情况,统计出秒杀失败的人数统计?(美团)

cat miaoshalog | wc -w failperson

17、查看当前IP的网络链接指令?(美团)

netstat

MySQL常见问题

1、为什么用MySQL?(招银网络科技)(有赞)

  • mysql性能卓越,服务稳定,很少出现异常宕机。
  • mysql开放源代码且无版权制约,自主性及使用成本低。
  • mysql历史悠久,社区及用户非常活跃,遇到问题,可以寻求帮助。
  • mysql软件体积小,安装使用简单,并且易于维护,安装及维护成本低。
  • mysql品牌口碑效应,使得企业无需考虑就直接用之,lamp,lnmp流行架构

2、讲一下数据库的表连接操作?什么是笛卡尔积?

左连接、右连接、内连接

笛卡尔积,又叫cross join,是SQL中两表连接的一种方式。 假如A表中的数据为m行,B表中的数据有n行,那么A和B做笛卡尔积,结果为m*n行。 通常我们都要在实际SQL中避免直接使用笛卡尔积,因为它会使“数据爆炸”

3、查数据库怎么防止sql注入?(有赞)

Mybatis中使用#{ }代替数据部分防止SQL注入。

4、MySQL走索引怎么查到数据?

innodb引擎以的形式将数据储存到磁盘,查询时将页读入内存,在叶子节点中查取数据,叶节点内部通过二分法查找,找不到转到该页指向的下一个页继续查询。

5、介绍ACID?(字节)(美团)(滴滴)(跟谁学)(有赞)(shopee)

Atomicity:事务本身被视为不可分割的最小单元,事务的操作要么全部成功要么全部失败回滚。

Consistency:数据库在事务的执行前后都保持一致,所有事务对同一数据的读取结果都相同

Isolation:一个事务的操作在提交之前,对其他事务是不可见的

Durability:一旦事务提交之后对于数据库的更改就是永久不可回退的

6、事务的 ACID 特性在MySQL中的实现?(有赞)(shopee)(字节)

事务:MySQL事务是在引擎层实现的,MySQL原生myISAM存储引擎不支持事务。

原子性:利用undo log 实现的

持久性:利用redo log 实现的

一致性:是利用 原子性、持久性、隔离性来实现的。事务的四大特性中一致性是目的,其他都是保证一致性的手段。

redo log:记录了数据操作在物理层面的修改,事务进行中会不断的产生redo log 在事务进行提交时一次flush操作保存到磁盘中。

undo log: 记录事务的修改操作,可以实现事务的回滚。

事务的隔离性由MVCC(多版本并发控制)与锁实现:因而隔离性也可以叫做并发控制。
innodb存储引擎中实现了三种隔离级别,分别为读未提交、读已提交、可重复读。其中后两者的实现均基于MVCC,其原理为根据read view(当前未提交事务视图)在事务回滚连中往寻找,直到找到了合适的记录。在聚簇索引中存在两个隐藏列为trx_id:当前行最近的事务改变、roll_pointer:当前旧版本在undo log(事务回滚链路)中位置的指针。
RU: 读未提交
RC: 读已提交 每次读语句开始时新建视图
RR: 可重复读(解决不可重复读问题) 每次事务开始前创建视图
串行化: 锁实现

InnoDB储存引擎标准实现的锁只有两种:行级锁、意向锁

InnoDB实现了如下两种标准的行级锁:

  • 共享锁(读锁 S Lock),允许事务读一行数据
  • 排它锁(写锁 X Lock),允许事务删除一行数据或者更新一行数据

InnoDB支持两种意向锁(即为表级别的锁):

  • 意向共享锁(读锁 IS Lock),事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(写锁 IX Lock),事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

加意向锁表明某个事务正在锁定一行或者将要锁定一行。首先申请意向锁的动作是InnoDB完成的,怎么理解意向锁呢?例如:事务A要对一行记录r进行上X锁,那么InnoDB会先申请表的IX锁,再锁定记录r的X锁。在事务A完成之前,事务B想要来个全表操作,此时直接在表级别的IX就告诉事务B需要等待而不需要在表上判断每一行是否有锁。意向排它锁存在的价值在于节约InnoDB对于锁的定位和处理性能。

InnoDB3种行锁的算法

  • Record Lock:单个行记录上的锁
  • Gap Lock:间隙锁,锁定一个范围,而非记录本身
  • Next-Key Lock:结合Gap Lock和Record Lock,锁定一个范围,并且锁定记录本身。主要解决的问题是RR隔离级别下的幻读问题

7、聚簇索引与非聚簇索引?(美团)(滴滴)(跟谁学)

的概念:一块小的且连续的内存空间

聚簇索引:

InnoDB储存引擎中,聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据也将聚簇索引的叶子节点称为数据页

一般建表会用一个自增主键聚簇索引没有的话MySQL会默认创建,但是这个主键如果更改代价较高(页撕裂),故建表时要考虑自增ID不能频繁update这点。

我们日常工作中,根据实际情况自行添加的索引都是辅助索引,辅助索引就是一个为了寻找主键索引的二级索引,先找到主键索引再通过主键索引找数据。非聚簇索引的叶子节点存储的是数据行的主键信息。

聚簇索引的优点:

  • 数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
  • 聚簇索引对于主键的排序查找和范围查找速度非常快

聚簇索引的缺点:

  • 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  • 更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
  • 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。这种二次查询的方式叫做回表查询

8、B+树的特性?(美团)

  1. 所有的非叶子节点只保存索引,不保存数据。因此树结构更加矮胖,相较于普通N叉树减少磁盘I/O次数。
  2. 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历。
  3. 索引维护过程可能会导致页分裂(当前数据页满了,需申请新数据页)、页合并 影响空间利用率。

9、非聚簇索引的叶子节点储存什么数据?

非聚簇索引又叫二级索引,该索引的叶子节点保存的是数据行的主键值,想要得到结果还需要使用主键值去聚簇索引(主键索引)中进行二次检索。这一过程称之为回表。

10、MySQL多字段查询?如何设置索引?索引的顺序?

最长搜索的字段放最右侧。范围搜索后面的字段的索引会失效。尽量使用覆盖索引。

11、MVCC是什么?原理?(shopee)(跟谁学)

全称多版本并发控制,与之相对的是基于锁的并发控制

MVCC最大的优势:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能

MVCC实现

而 MVCC 利用了多版本快照的思想,写操作更新最新的版本快照,而读操作去读旧版本快照(read view)根据隔离级别不同读取的规则也不同,没有互斥关系。

12、联合索引的数据结构?(美团)

联合索引的数据结构依然是B+树。其非叶子节点储存的是第一个关键字的索引。叶子节点存储的是三个关键字的顺序。且按照字段从左到右排序。

如图,index(年龄, 姓氏,名字),叶节点上data域存储的是三个关键字的数据。且是按照年龄、姓氏、名字的顺序排列的。

在这里插入图片描述

如果跳过年纪按照后面两个字段搜索,会导致全表扫描。

13、explain查询到的字段?(有赞)(跟谁学)

  1. select_type : 查询类型,有简单查询、联合查询、子查询等
  2. key : 实际使用到的索引,如果为null,表示没有使用到索引。
  3. possiable_key:
  4. type:显示查询使用了何种索引类型,all < index < range < ref
  5. table:显示这一行的数据是关于哪张表的
  6. rows : 根据表统计信息及索引选用情况,大致估算出找到所需数据所需要读取的行数。
  7. id: select查询的序列号,包含一组数字,表示查询中执行select子句的顺序。
  8. extra:其他信息,显示如 using index 、using filesort 等等。

14、MySQL的自增ID用完了怎么办?(招银网络科技)(顺丰科技)

数据库表的自增 ID 达到上限之后,再申请时它的值就不会在改变了,继续插入数据时会导致报主键冲突错误。因此在设计数据表时,尽量根据业务需求来选择合适的字段类型。可以考虑使用bigint 类型。

15、数据库中保存商品价格使用什么数据类型?(百度)

在java的开发中,货币在数据库中MySQL常用DecimalNumric类型表示,这两种类型被MySQL实现为同样的类型。

DECIMAL和NUMERIC值作为字符串存储,而不是作为二进制浮点数,以便保存那些值的小数精度。

不使用float或者double的原因:因为float和double是以二进制存储的,所以有一定的误差。

16、 如果数据库出现了死锁,怎么去发现死锁?(shopee)

通过获取死锁日志来获取死锁信息。

mysql使用几个特殊的表名来作为监控的开关。比如在数据库中创建一个表名为innodb_monitor的表用于开启标准监控。创建一个表名为 innodb_lock_monitor 的表开启锁监控。MySQL 通过检测是否存在这个表名来决定是否开启监控,至于表的结构和表里的内容无所谓。相反的,如果要关闭监控,则将这两个表删除即可。

17、你能用sql语句模拟一下幻读的情况吗?(美团)

事务1
select age from table where id > 2
事务2
Insert into table(id , age) values (5, 10)
commit
事务1 
select age from table where id > 2
commit
事务1两个相同的select语句执行了两次,两次的查询结果不相同,这就是产生了幻读。

18、redo undo log 的作用?(shopee)(美团)

redo log 常用作MySQL服务器异常宕机后的数据恢复工作,复杂保证事务的持久性

undo log 常用于记录被改动的数据,负责事务的一致性。

19、MySQL中除了undo log 以外还有什么操作是为了保证事务的一致性?(美团)

各种隔离级别保证事务的一致性。

20、数据库是怎么去做持久性的,做持久性的时候可能会遇到什么问题?(美团)

利用 redo log 做持久性,redo log主要记录了data在物理层面的修改。redo log 在事务进行提交时一次flush操作保存到磁盘中

21、如何保证MySQL的主从强一致性?(shopee)

  1. 主库事务提交的时候,同时发起两个操作,操作一是将日志写到本地磁盘,操作二是将日志同步到从库并确保落盘。
  2. 主库此时等待两个操作全部成功返回之后,才返回给应用程序,事务提交成功。

22、mysql主从一致要求强一致会导致什么问题?(shopee)

事务的每次提交都需要等到从机的落盘完成后才可以提交。

23、MySQL的日志除了redo undo log别的有了解吗?(跟谁学)

bin log 是MySQL数据库的二进制日志,用于记录用户对数据库操作的SQL语句((除了数据查询语句)信息。

24、bin log 与 redo log 的区别?(shopee)

  1. bin log是MySQL级别的日志文件,无论使用哪种存储引擎都会生成。而redo log 是innodb引擎独有的日志,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。
  2. 两种日志记录的内容形式不同。MySQL的bin log是逻辑日志,其记录是对应的SQL语句。而innodb存储引擎层面的重做日志是物理日志。
  3. 两种日志与记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入。而innodb存储引擎的重做日志在事务进行中不断地被写入,并日志不是随事务提交的顺序进行写入的。
  4. bin log可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。

25、四种隔离级别解决的问题?(跟谁学)

  1. 读未提交:会导致 脏读、不可重复读、幻读。解决了更新丢失问题(两个事务对一条数据修改导致的更新覆盖问题)可以直接使用排它写锁实现。
  2. 读已提交:会导致不可重复读、幻读。解决了脏读问题(innoDB基于MVCC实现)
  3. 可重复度:解决了不可重复读问题,会导致幻读(innoDB基于MVCC实现)
  4. 序列化:全部解决(提供严格的事务隔离,事务没有并发性可言)

26、读已提交隔离级别为什么会有不可重复读的问题出现?(跟谁学)

//开启事务并设置隔离级别为读已提交,表count两个字段 name, money
A事务 select * from count  结果name = Tom money = 1000 
B事务 update money = 2000 from count where name = Tom B事务提交
A事务 select * from count  结果name = Tom money = 2000 显然A事务对一个数据行两次读操作结果不一致,这就导致了不可重复读问题

27、如何保证MySQL主从的高可用性?(shopee)

HA(High Availability)检测工具应运而生。HA工具一般部署在第三台服务器上,同时连接主从,检测主从是否存活,如果主库宕机则及时将仓库升级为主库,将原来的主库降级为从库。

28、介绍几种索引吧?(美团)

  1. 单一索引:单一索引是指索引列为一列的情况,即新建索引的语句只实施在一列上
  2. 复合索引:根据创建联合索引的顺序,以最左前缀匹配原则进行where检索。
  3. 覆盖索引:查询的字段与建立索引的字段一一对应就叫做覆盖索引。

29、B+树实现索引的原理说一下?(美团)

B+跟B*树不同B+树的非叶子节点不保存关键字记录的指针,只进行数据索引,这样使得B+树每个非叶子节点所能保存的关键字大大增加

30、非叶子节点它的一个数据结构描述一下?(美团)

非叶子结点中仅含有其子节点的索引,不包含实际数据。

31、innodb与myisam的适用场景?(平安科技)(顺丰科技)

  1. 大量读不需要事务控制的情况下使用myisam,写操作多的情况下使用innodb存储引擎。
  2. 需要用到行锁的场景下要使用innodb

32、什么是索引下推?(有赞)

索引下推在非主键索引上的优化,可以有效减少回表的次数,大大提升了查询的效率。
举例:
表k中建立有联合索引(name, age),执行如下语句

select * from k where name like '张%' and age = 10 and sex = 1;
  1. 在MySQL5.6之前,在利用完name索引后,只能从根据name找到的第一个主键id逐一回表查询数据行,对比字段值。
  2. 在MySQL5.6之后,在回表查询之前,会先对索引中包含的字段进行判断,过滤不满足条件的记录,从而减少回表次数。

33、不可重复读与幻读的区别?(shopee)(美团)

不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的「数据不一样」。(因为中间有其他事务提交了修改)

幻读的重点在于新增或者删除:在同一事务中,同样的条件,第一次和第二次读出来的「记录数不一样」。(因为中间有其他事务提交了插入/删除)

34、当前读与快照读?(美团)

在一个支持MVCC的系统中,读操作被分为当前读与快照读

快照读:简单的select操作,不加锁。

select * from table where ?;

当前读:插入/更新/删除操作,需要加锁

select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values ();
update table set ? where ?;
delete from table where ?;

35、DDL与DML?(平安科技)

  • DML(data manipulation language)数据操纵语言:

就是我们最经常用到的 SELECT、UPDATE、INSERT、DELETE。 主要用来对数据库的数据进行一些操作。

  • DDL(data definition language)数据库定义语言:

其实就是我们在创建表的时候用到的一些sql,比如说:CREATE、ALTER、DROP等。DDL主要是用在定义或改变表的结构,数据类型,表之间的链接和约束等初始化工作上

36、JDBC说一下?(华为)

  1. 通过驱动建立一个连接,这个连接代表着一个真实的数据库连接。
  2. 由conn建立一个StatementPreparedStatement对象。
  3. stmt.executeUpdate(sql)执行语句,返回即查询解决。

37、一条sql语句(非update)是如何执行的?

  1. 连接器,管理连接、权限验证
  2. 分析器,进行词法分析以及语法分析
  3. 优化器,进行语句优化,索引选择
  4. 执行器,操作底层的数据存储引擎,返回结果
  5. 存储引擎,存储数据,对外提供读写接口。

38、一条update语句是如何执行的?

进行update操作还涉及到redo log、binlog。
redo log: (innodb引擎引入的日志,本质上类似于记账账本,不直接在mysql中进行存储,而视在空闲时利用redo log进行数据到磁盘的写入,innodb引擎利用redo log保证数据的不丢失)。其是物理日志,记录某个数据页上做的修改,大小固定,循环写入,一旦空间用完会清除。
binlog: MySQL server提供的功能,逻辑日志,击记录的是sql语句的原始逻辑,如“给 id = 2 的一行数据的c字段增加1”。追加写入,binlog文件到一定大小后会切换到下一个
过程:

  1. 执行器根据id找到数据,然后判断内存中是否存在该数据,不存在的话从磁盘中读取并读入内存。
  2. 执行器拿到引擎给的数据进行更新,引擎将这条修改后的数据更新入内存,同时更新操作记录到redo log中,此时redo log处于prepare状态,告知执行器处理完成随时可以提交事务
  3. 执行器生成该操作的binlog并写入磁盘
  4. 执行器调用引擎的提交事务接口,引擎将redo log状态由prepare修改为commit,更新完成。
    redo log的两阶段提交是为了保证redo log 与binlog之间的逻辑一致。这样来即便发生MySQL的crash也会保证两个日志的数据一致性。

39、覆盖索引

假设table k表中定义了两个索引分别为主键索引id以及非主键索引name。那么如下sql语句

select * from k where name between 3 and 5

则需要进行回表查询,而

select id from k where name between 3 and 5

由于name索引的叶子节点存储的即为其主键id值,这一过程是不需要回表查询的。索引name覆盖了我们的查询需求,称之为覆盖索引。

40、如何避免多事务的锁冲突导致的死锁问题?

  1. 进入等待,直到超时,超时时间通过innodb_lock_wait_timeout设置。
  2. 发起死锁检测,主动回滚死锁链条中的某个事务,让其他事务得以执行,参数innodb_deadlock_detect设置为on。
    在2的基础上,如果并发度很高的情况下进行死锁检测也是一个很费时的操作,可能的话在MySQL的服务端进行限流,在请求进入innodb前进行排队,限制同一时刻修改db中某一行的线程数。不仅降低了发生死锁的概率,即使发生了死锁进行死锁检测的效率也会提升很大。

41、普通索引与唯一索引该怎么选

在查询性能上两者差距微乎其微,在更新性能上由于普通索引可以利用change buffer的优化机制性能更优。

42、count(*)为什么这么慢?

count 方法可以返回表内精确的行数,每执行一次都会进行一次全表扫描, 以避免由于其他连接进行 delete 和 insert 引起结果不精确。 在某些索引下是好事,但是如果表中有主键, count (*) 的速度就会很慢,特别在千万记录以上的大表

43、order by如何工作?

两种情况,内存大小允许的情况下仅使用快排在内存中排序,否则的话需要用到外部硬盘空间,在这块空间中MySQL将数据分为12块进行归并排序。

Redis常见问题

1、什么是redis?

Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的高性能非关系型(NoSQL)的键值对数据库。Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。redis每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外redis也常用来做分布式锁。

2、redis的优缺点?(招银网络科技)

优点:

  1. 支持多种数据类型 包括set,zset,list,hash,string这五种数据类型,操作非常方便。 比如,如果你在做好友系统,查看自己的好友关系,如果采用其他的key-value系统,则必须把对应的好友拼接成字符串,然后在提取好友时,再把value进行解析,而redis则相对简单,直接支持list的存储 (采用双向链表或者压缩链表的存储方式)。
  2. 持久化存储 作为一个内存数据库,最担心的,就是万一机器死机,数据会消失掉。 redi使用rdb和aof做数据的持久化存储。
  3. 丰富的特性 pub/sub,key过期策略,事务,支持多个DB等。
  4. 性能很好 由于是全内存操作,所以读写性能很好,可以达到10w/s的频率。 公司有项目使用redis,目前的访问频率是80w/s,通过适当的部署,线上运行一切ok。

缺点:

  1. 由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然Redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
  2. 如果进行完整重同步,由于需要生成rdb文件,并进行传输,会占用主机的CPU,并会消耗现网的带宽。不过redis2.8版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
  3. 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,redis不能提供服务。

3、为什么要用缓存?(美团)

因为redis的高性能与高并发。

4、为什么redis单线程还支持高并发?(美团)

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现而且省去了很多上下文切换线程的时间,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

redis使用 epoll多路I/O复用技术 ,单个线程可以处理大量的并发连接。epoll是一种高效的多路复用技术。

5、为什么要用redis而不是map或者guava做缓存?(shopee)

缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性

使用 redismemcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redismemcached服务的高可用,整个程序架构上较为复杂。

6、Redis数据结构?string与hash的区别?(美团)(字节)

string、list、hash、set、zset。

String多应用于简单的键值对缓存;hash储存结构化数据,比如一个对象。

7、redis应用场景?

计数器、分布式会话缓存、分布式锁实现等等。

8、跳跃表(skipList)?(字节)(shopee)

SkipList是在有序链表的基础上进行了扩展,解决了有序链表结构查找特定值困难的问题,查找特定值的时间复杂度为O(logn),他是一种可以代替平衡树的数据结构。

它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,就能轻松实现一个 SkipList

9、redis持久化机制?(有赞)(跟谁学)(滴滴)

  1. RDB:开启一个新的线程来完成往rdb文件中的写操作。主线程继续处理命令。使用单独的子线程来进行持久化。主线程不进行任何的IO操作。保证redis的高性能。缺点是可能会丢失一些数据。
  2. AOF :AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。AOF有一个重写模式,当日志文件过大时可以对其进行压缩。AOF往往效率低于RDB一些。

AOF的追写策略:建议使用每秒同步一次(everysec)策略。

rewrite机制:rewrite会记录上次重写时AOF文件的大小,当AOF文件是上一次大小的二倍且大于64M时触发。

10、如何选择合适的持久化方式?

一般来说两者配合使用效果最佳,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。

如果可以容忍数分钟内的数据丢失,可以只选用RDB方式,还比较快。

不推荐只使用AOF方式。

11、过期键的删除策略?(美团)

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

12、redis设置键的过期时间以及永不过期命令是什么?

expire设置过期时间

persist设置键永不过期,多用于热点数据。

13、redis的内存淘汰策略有哪些?

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)

  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

14、redis线程模型?(shopee)

redis单线程模式运行,但是通过使用 I/O 多路复用来监听多个套接字(socket), 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。

15、redis主从复制?(shopee)

主从连接过程:

  1. 从服务器连接主服务器,发送SYNC命令。主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令。
  2. 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
  3. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
  4. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
  5. 一旦主机挂了,从机会原地待命,但是使用 salveof no one 命令会使从机反仆为主

**作用:**数据冗余、故障恢复、负载均衡、高可用的基石。使用slave of 命令将某一台redis变为从机。

16、redis哨兵机制?(shopee)(滴滴)(新浪)

  1. Sentinel(哨兵) 进程是用于监控 Redis 集群中 Master 主服务器工作的状态
  2. 在 Master 主服务器发生故障的时候,可以实现 Master 和 Slave 服务器的切换,保证系统的高可用(High Availability)
  3. 为了防止脑裂发生,节点个数一般配置为 2n+1。

17、redis集群?redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?(滴滴)

为什么有了哨兵模式还需要集群?

redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis分布式存储,也就是说每台redis节点上存储不同的内容。

数据分配策略?

采用一种叫做哈希槽 (hash slot)的方式来分配数据,redis cluster 默认分配了 16384 个slot。将key的 hashCode % 16384得出数据的槽位。

分布式寻址算法

  1. hash 算法(大量缓存重建)
  2. 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
  3. redis cluster(集群) 的 hash slot (槽)算法

18、一致性hash说一下?(跟谁学)

首先面对海量数据,一台redis肯定是不够用的,一致性hash算法主要是用来将数据按照一定的算法规律存储到指定的redis服务器中。

常规的hash算法会导致一个问题:当redis的实例个数变了那么所有的hash值都需要重新计算,这是非常耗时的。一致性hash的出现解决了这种问题。

  1. hash(IP) % 2^32 -1 求出redis主机在圆环中的位置,
  2. 使用hash(key) % 2^32-1求出数据在环上的位置,从该位置顺时针查找到的第一个主机即该数据存储的位置。
  3. 这样一来及有一个标准化的计算过程了。

19、什么是RedLock?

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

  1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁
  2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
  3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务

20、缓存雪崩、击穿、穿透?(美团)(滴滴)

  • 缓存雪崩:大量热点key同时失效的情况,高并发情况下,大量请求直接到数据库,
  • 缓存击穿:像子弹一样,同一时间大量请求同一数据,导致缓存用完(热点数据),导致请求直接打在数据库上
  • 缓存穿透:请求数据库中不存在的数据,导致缓存失效,请求直接到数据库查看

21、缓存预热?(百度)

秒杀开始前,商品数据以及库存都预热到redis。

22、Redis支持的客户端?

Redissonjedislettuce等等,官方推荐使用Redisson

23、Jedis与Redisson对比有什么优缺点?(美团)

JedisRedis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redisson解决了锁的自动续期问题,只要业务还在执行,Redisson就会为锁自动续期。

24、redis事务?(有赞)(跟谁学)

Redis 事务的本质是通过 MULTI、EXEC、WATCH、discard 等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控

如果一个事务中的命令出现错误,那么所有命令都不会执行。
Redis事务不保证多条指令的原子性
基于Lua脚本可以保证脚本中的指令一次性按顺序执行。

25、redis与memcached的区别?(平安科技)

  1. 支持存储的数据类型redis支持五种类型。memcached支持文本类型与二进制类型。
  2. 网络IO模型redis是单线程的多路IO复用模型,memcached是多线程的非阻塞IO模式。
  3. redis支持数据持久化memcached不支持
  4. 使用场景:redis适用于复杂的数据结构环境,有持久化需求。memcached适用于纯<k,v>且数据量矩大的环境下。

26、redis常见的性能问题与对应的解决方案?(新浪)(shopee)

  1. Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
  2. 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
  4. 尽量避免在压力较大的主库上增加从库
  5. Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
  6. 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

27、假如Redis有一亿个key,其中10万个key以某个固定的已知前缀开头,如何把他们都找出来?(字节)

keys指令可以扫描得出指定模式的key列表

但是问题是由于redis是单线程的,keys指令会导致线程阻塞一段时间,此时的线上服务会有短暂停顿直到keys指令执行完毕。

使用scan指令可以做到无阻塞的提取出指定模式的key列表。但有一定的重复几率。再做一遍去重就可以。

28、布隆过滤器了解吗?(滴滴)(字节)

解决缓存穿透的问题。

redis中的一种数据结构,它将MySQL数据库中所有可能存在的数据都缓存到布隆过滤器中。当攻击者访问不存在的数据时迅速返回避免请求打到数据库上导致数据库宕机问题。

原理:

Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展。当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。
检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它。
值得注意的是:如果这些点有任何一个 0,则被检索元素一定不在。
如果都是 1,则被检索元素很可能在。

29、布隆过滤器的优、缺点?

  • 优点

    空间效率查询效率都远远超过一般的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(k)。
    另外, 散列函数相互之间没有关系,方便由硬件并行实现。
    布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

  • 缺点

    布隆过滤器的缺点和优点一样明显。
    误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表就可以。
    另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

30、redis分布式锁实现原理?(美团)(字节)

  1. 一个线程尝试去获取锁lock,通过setnx(lock,uuid,过期时间)。如果lock不存在就会设置成功,返回true,否则返回false。

  2. 获取分布式锁成功之后,需要使用expire命令设置锁有效期,防止死锁。

  3. 执行相关业务逻辑

  4. 释放锁,首先获取到lock对应的value,将此value与uuid对比,如果相同的话执行delete指令删除锁。注意!上述两个步骤需要保证原子性。需要使用lua脚本。

31、Redis针对数据结构做了哪些优化?(美团)

redis数据模型:

在Redis中,会给每一个key-value键值对分配一个字典实体,就是dicEntrydicEntry包含三部分: key的指针、val的指针、next指针,next指针指向下一个dicteEntry形成链表,这个next指针可以将多个哈希值相同的键值对链接在一起,通过链地址法来解决哈希冲突的问题

在这里插入图片描述

  • sdsSimple Dynamic String,简单动态字符串,存储字符串数据。

  • redisObject:Redis的5种常用类型都是以RedisObject来存储的,redisObject中的type字段指明了值的数据类型(也就是5种基本类型)。ptr字段指向对象所在的地址。

  1. String: sds实现,自定义类型加入了长度,每次获取字符串长度的时间复杂度就是O(1),而利用len和free属性对追加字符串进行优化,也可以降低重新分配内存的次数。
  2. Hash: zip-list 或者 hash-table(数组+链表)扩容机制采用渐进式扩容。
  3. Set: int-Set
  4. Zset: skip-list
  5. List: zip-list -> linked-list -> quick-list (前两者合并之后创建了快速链表)

32、redis是如何保证原子操作的?(美团)(新浪)

Redis是单线程的。在单线程程序中,任务一个一个地做,必须做完一个任务后,才会去做另一个任务。因而redis的操作保证了原子性。

33、什么命令会触发写RDB文件?(跟谁学)

BGSAVE:后台处理,不会阻塞工作线程。

SAVE:会导致工作线程的阻塞。

34、知道大Key问题吗?(滴滴)

由于Redis主线程为单线程模型,大key也会带来一些问题,如:

  1. 集群模式在slot分片均匀情况下,会出现数据和查询倾斜情况,部分有大key的Redis节点占用内存多,QPS高。
  2. 大key相关的删除或者自动过期时,会出现qps突降或者突升的情况,极端情况下,会造成主从复制异常,Redis服务阻塞无法响应请求。

redis4.0之前的大key的发现与删除方法

  • redis-rdb-tools工具。redis实例上执行bgsave,然后对生成的rdb文件进行分析,找到其中的大KEY。
  • redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。

由于在redis4.0前,没有lazy free机制;针对扫描出来的大key,DBA只能通过hscan、sscan、zscan方式渐进删除若干个元素,但面对过期键删除的场景,这种取巧的删除就无能为力。我们只能祈祷自动清理过期key刚好在系统低峰时,降低对业务的影响。

Redis 4.0之后的大key的发现与删除方法

Redis 4.0引入了memory usage命令和lazy free机制,不管是对大key的发现,还是解决大key删除或者过期造成的阻塞问题都有明显的提升。

Nginx常见问题

1、常用配置参数?

worker_processes number | auto;  // 设置工作线程数,是Nginx服务器实现并发处理服务的关键所在。
worker_connections number; // 设置最大连接数
keepalive_timeout timeout [header_timeout]; // 配置连接超时时间

2、nginx进程模型?(shopee)

在这里插入图片描述

Nginx是多进程的,启动时会先启动一个 Master 进程,然后由 Master 进程启动 子Worker 工作进程,Master主要作配置读取维护 Worker 进程启动-销毁等,Worker进程对请求进行处理,Worker进程之间通过共享内存进行通信,启动Nginx时,默认设置Worker进程数为CPU的核心数。、

3、nginx负载均衡?(美团)(滴滴)(平安科技)

  • round robin (轮询)
  • random (随机)
  • weight (权重)
  • fair (按响应时长,三方插件)
  • url_hash (url的hash值)
  • ip_hash (ip的hash值)
  • least_conn (最少连接数)

Spring框架常见问题

1、http请求过来springMVC是怎么处理的?(百度)

  1. 请求被Spring 前端控制器 DispatcherServlet 捕获
  2. DispatcherServlet对请求URL进行解析(核心方法doDispatch()),得到请求资源标识符(URI)然后根据该URI,调用HandlerMapping获得该Handler(Controller)配置的所有相关的对象
  3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter(执行目标方法的反射工具)
  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler
  5. Controller -> Service -> Dao 查询到数据
  6. Controller执行完成后,向DispatcherServlet 返回一个ModelAndView对象
  7. 根据返回的ModelAndView,选择一个适合的ViewResolver(视图解析器)
  8. ViewResolver 结合Model和View,来渲染视图
  9. DispatcherServlet 响应给客户端

2、视图解析器怎么解析的?(字节)

  1. 任何方法的返回值最终都会封装为ModelAndView对象。
  2. viewResolver唯一作用是根据ModelAndView得到view对象,视图对象才能真正的转发或者重定向到页面(并将模型中的数据暴露到请求域中)。
  3. 视图对象是真正进行视图渲染的。调用view的方法:render(ModelAndView, request, response) 进行页面渲染。
  4. 根据view的种类不同它渲染出来的视图也不相同。一般我们使用的都是InternalResourceView视图。

3、注解实现的原理?如果让你实现一个注解你会怎么做?

Annotation其实是一种接口。通过java的反射机制相关的API来访问Annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。

4、spring循环依赖?(阿里)(美团)

多个bean之间的互相引用,导致一个闭环的出现。

采用三级缓存模式来解决循环依赖问题。

singletonFactories : //单例对象工厂的cache
earlySingletonObjects ://提前暴光的单例对象的Cache
singletonObjects://单例对象的cache

注意:构造器注入导致的循环依赖无法解决。

假设现在有两个bean X Y互相依赖,且都是单例的,X开始生命周期后直到X通过构造器以及创建对象后,会有一个暴露阶段,此时会将X的一个ObiectFcatory对象暴露出去并存入二级缓存中。然后会进行X的属性注入,这是会将Y注入,但是还没有Y,然后进入到Y bean的生命周期。一直到Y暴露出自己的ObjectFcatory对象暴露出去并存入二级缓存中后,Y进行依赖注入,需要注入X,然后二级缓存中有X的一个对应的工厂对象。至此完成了循环依赖。需要注意的是此过程仅适用于由于属性注入引起的循环依赖,对于由于构造器注入引起的循环依赖不能解决,原因是ObiectFcatory对象是在根据构造器通过反射创建对象后才产生的。对于构造器注入引起的循环依赖无法起作用。

5、spring bean 生命周期?(阿里)(大华)

1、实例化过程

1.1 首先spring通过BeanDefinitionReader会将xml、Java类型的配置文件解析为BeanDefinition类型注册到容器中。BeanDefinition实际上是一个用来存储class信息的对象。它里面包含了一个类的基本信息、类的父类的信息、是否懒加载、是否为单例等等。beandifinition定义了bean的基本信息,根据它来创造bean然后<BeanDefinitionbeanName>分别作为<value,key>存入一个map中。这个map是存在于BeanFactory中的。

1.2 BeanDefinition会转化为mergebeandefinition,其中包括了BeanDefinition以及 parent BeanDefinition的信息。

1.3 配置 BeanDefinition 的depends-on BeanDefinition

1.4 根据BeanDefinition中指定的class信息,以及构造器信息最终通过反射获取到BeanDefinition的实列对象(注意此时还不是一个bean,经过后续的一些操作才会变成一个完整的bean)。

1.5 判断对象是否允许循环依赖?是否需要AOP,属性注入。

1.6 判断是否需要暴露。需要的话会将一个objectFactory对象存入一个二级缓存中。

1.7 spring bean 的实例化完成,加入到spring 的单例缓冲池中(一个map)

2、初始化过程

调用init-method进行bean的初始化,主要用于项目的一些依赖(配置文件或者数据库连接等等)

3、 销毁bean

destory-metnod方法进行bean的销毁。

6、spring容器启动流程?(阿里)

  1. 将配置文件加载为BeanDefinition并注册到容器中;这一步需要XmlBeanDefinitionReader的配合。
  2. 注册BeanFactoryPostProcesser,包含一个可以传入beanFactory引用的方法,获取到容器之后可以做很多事情。由于BeanFactoryPostProcesser工作在bean实例化之前,所以可以通过beanFactory获取到map从而手动修改或者移除beanDefinition
  3. 注册BeanPostProcesser,工作于bean实例化或者初始化前后。其包含两个方法。是spring作为扩展接口留给开发人员使用的。
public interface BeanPostProcessor {
	//在初始化之前调用
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
	//在初始化之后调用
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
  1. 创建事件传播器对象

  2. beanDefinition实例化

7、BeanFactory和ApplicationContext有什么区别?(滴滴)

BeanFactory:是Spring里面最底层的接口,提供了最简单的容器的功能,包含了各种Bean的定义,读取bean配置文件,管理bean的加载、实例化、控制bean的生命周期、维护bean之间的依赖关系等等。

ApplicationContext:继承了BeanFactory接口。是spring中更高一级的容器。提供了比BeanFactory更多的功能。

区别:

BeanFactory采用懒加载形式注入bean,ApplicationContext在容器启动时一次性创建所有bean,这样在容器启动时就可以发现Spring中存在的配置错误。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

8、bean的作用域?

默认为单例的,可以通过xml文件中的scope标签来做更改。如原型模式、request、session。

9、spring基于XML文件注入bean的方式?

构造器注入:无参构造器注入,有参构造器注入。

set方法注入:要求被注入的属性必须有set方法。

10、spring的自动装配?(字节)

Spring自动将某个bean的引用装配给了指定属性,这一过程叫做自动装配 。Spring提供了三种自动装配的策略。

    //无需自动装配
    int AUTOWIRE_NO = 0;
    //按名称自动装配bean属性
    int AUTOWIRE_BY_NAME = 1;
    //按类型自动装配bean属性
    int AUTOWIRE_BY_TYPE = 2;
    //按构造器自动装配
    int AUTOWIRE_CONSTRUCTOR = 3;
    //过时方法,Spring3.0之后不再支持

上面介绍的是基于xml配置文件的自动装配过程。下面介绍基于注解的自动装配过程。

基于注解的自动装配:

@Autowired注解可以实现bean的自动装配。默认是按照类型进行装配的。但是如果匹配到同一类型的多个实例,再通过byName来确定要装配的bean

11、SpringBoot的关键注解?

@SpringBootApplication启动类注解,等同于@SpringBootConfiguration、 @EnableAutoConfiguration、 @ComponentScan 这三个注解

12、Spring AOP是什么?(美团)

AOP意为面向切面编程,与OOP一样,是一种编程理念,如果把OOP看作是自上而下的层层抽象,那么AOP就是从左至右的相同功能模块的抽取和封装。开发中使用AOP可以大大减少冗余代码,降低模块之间的耦合度,并且有利于未来的扩展性。比如商城业务中好多的微服务模块都要先进行用户验证。我们就可以把验证用户这一功能抽取出来作为一个切面。

AOP 当中的概念:
  • 切入点(Pointcut)
    在哪些类,哪些方法上切入(where
  • 通知(Advice)
    在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能)
  • 切面(Aspect)
    切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
  • 织入(Weaving)
    把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

举一个例子:

在这里插入图片描述

@Component("landlord")
public class Landlord {
    // 下面方法是连接点
    public void service() {
        // 仅仅只是实现了核心的业务功能
        System.out.println("签合同");
        System.out.println("收房租");
    }
}
@Component // 标识为一个Bean
@Aspect // 标识为一个切面
class Broker {
    // 前置通知,表示在连接点方法之前执行
    // 定义了 execution 的正则表达式,Spring 通过这个正则表达式判断具体要拦截的是哪一个类的哪一个方法
    @Before("execution(* pojo.Landlord.service())")
    public void before(){
        System.out.println("带租客看房");
        System.out.println("谈价格");
    }
    // 后置通知,表示在切入点方法之后执行
    @After("execution(* pojo.Landlord.service())")
    public void after(){
        System.out.println("交钥匙");
    }
}

13、AspectJ是什么?与Spring AOP的区别?

AspectJ是AOP的一种实现,是目前Java开发社区中最流行的AOP框架,拥有更好的性能。

14、SpringMVC如何将纯文本的Http协议的请求转化为Java对象的?(字节)

利用HttpMessageConverter的实现类将http的请求转化为Java对象的。同时,响应的时候还可以利用HttpMessageConverter的实现类将Java对象转化为http响应的格式。

15、讲下SpringMVC的核心入口类是什么?(美团)

DispatcherServletSpringMVC的核心入口。

16、Spring 中的单例 Beans 是线程安全的么?(美团)

不是线程安全的,对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。

但如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如SpringMVC 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

17、spring定时任务?

定时任务 指的是应用程序在指定的时间执行预先定义好的程序片段 . 在 Spring 中使用定时任务非常简便,分为三步:

  1. 编写定时任务类并注入到 IOC 容器,一般使用 @Component 注入;
  2. 编写定时任务方法并使用 @Scheduled 标记,这里需要了解一个叫 Cron 表达式的知识点
  3. SpringBoot 启动类上使用 @EnableScheduling 开启定时任务功能

MyBatis常见问题

1、什么是MyBatis?

是一个ORM(对象关系映射)框架。

Mybatis内部封装了jdbc,使得开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。

MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO映射成数据库中的记录。

2、什么是SQL注入?如何防止?(滴滴)

是一种注入攻击,它通过将任意代码插入数据库查询,使得攻击者完全控制数据库服务器。 攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。

使用#{}可以有效的防止SQL注入MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。

原理:

在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。

--Mybatis在处理#{}时
select id,name,age from student where id =#{id}
当前端把id值1传入到后台的时候,就相当于:
select id,name,age from student where id ='1'

--Mybatis在处理${}时
select id,name,age from student where id =${id}
当前端把id值1传入到后台的时候,就相当于:
select id,name,age from student where id = 1

3、什么情况下用${ }、什么情况下用#{ }? (滴滴)

Mybatis 中优先使用 #{}。当需要动态传入表名或列名时,使用 ${} 。

#{}是预编译处理,${}是字符串替换,#{}可预防SQL注入,提高系统安全性,将SQL中的#{}替换成?占位符

4、Mybatis 中一级缓存与二级缓存的区别?

合理利用缓存可以避免频繁操作数据库,减轻数据库压力,同时提高系统的性能。

一级缓存是sqlSession级别的,Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存。一级缓存在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。当在同一个sqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存)。

二级缓存是mapper级别的缓存,多个sqlSession 去操作同一个mapper的sql语句,它们可以公用二级缓存,二级缓存是跨sqlSession的。

5、使用MyBatis的Mapper接口调用时有什么要求?

  1. 接口中的方法名应与mapper中的每一个sql的id相同。
  2. 接口方法的输出参数类型和 mapper.xml 中定义的每个 sqlresultType 的类型相同
  3. 接口方法的输入参数应与mapper.xml中定义的每一个sqlparameterType类型相同。

6、MyBatis的运行步骤?

  1. 创建 SqlSessionFactory
  2. 通过 SqlSessionFactory 创建 SqlSession
  3. 通过 sqlsession 执行数据库操作
  4. 调用 session.commit()提交事务
  5. 调用 session.close()关闭会话

7、MyBatis的工作原理说一下?(新浪)

MyBatis先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。

MyBatis也有四大核心对象:

  1. SqlSession对象,该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。
  2. Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement
  3. MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
  4. ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。

8、MyBatis 详细工作流程?

  1. 读取MyBatis的配置文件。mybatis-config.xmlMyBatis的全局配置文件,用于配置数据库连接信息。
  2. 加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
  3. 构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory
  4. 创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
  5. Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
  6. MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
  7. 输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
  8. 输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

9、MyBatis中接口绑定有几种实现方式?

  1. 通过注解绑定,在接口的方法上通过@Select@Update等注解里面包含SQL语句来绑定。
  2. 通过Mapper.XML文件中编写SQL语句来绑定,SQL语句的id必须与对应接口的方法名一致,输入输出的类型也必须一致。

Spring Cloud 组件 Open Feign常见问题

1、如何使用?

  1. 首先,调用以及被调用的微服务双方都应该被注册到注册中心

  2. Spring Boot启动APP上标注 @EnableFeignClients注解。

  3. 编写远程调用接口并标注@FeignClient注解。(括号内添加所要调用的微服务名称)

  4. 接口中的方法为实际想要调用的服务的方法签名,并使用@PostMapping注解映射为一个post类型的HTTP请求。

2、实现远程调用的原理?(新浪)(百度)

核心原理就是通过一系列的封装和处理,将以Java注解的方式定义的远程调用API接口,最终转化为HTTP的请求与响应结果。

在这里插入图片描述

从上图可以看到,Feign通过处理注解将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。

  1. 微服务启动时,feign对添加了@FeignClient的接口扫描,创建远程接口的本地JDK Proxy代理实例。然后注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

  2. Feign的方法处理器 MethodHandler 。它用来解析方法上的url,以及@postMapping注解中包含的数据,并生成一个http请求模板。

  3. MethodHandler 中具体由requestTemplate发起request请求。Request 经过编码交给httpclient发送到远程调用服务。

3、如何解决远程调用的负载均衡问题?(百度)

feign内部有Ribbon实现了客户端的负载均衡。从注册中心读取所有可用的服务提供者,在客户端每次调用接口时采用如轮询负载均衡算法选出一个服务提供者调用,因此,Ribbon是一个客户端负载均衡器。

常见的负载均衡策略有:

  1. 轮询:可能会导致性能较弱的服务器过载
  2. 加权轮询:可以为每台服务器分配权重,能力较弱的服务器分配较少的连接请求
  3. 最少连接数:对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。
  4. 加权最少连接:是最小连接调度的超集,各个服务器相应的权值表示其处理性能。 服务器的缺省权值为 1,系统管理员可以动态地设置服务器的权值。

Spring Cloud 组件 Gateway常见问题

1、网关定义?

挡在众多微服务前面的一堵墙,用来管理、授权、流量限制等等。可以保护后台的微服务。Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

2、作用?

  1. 作为所有微服务的请求入口。
  2. 对所有的API进行统一的管理、分发。
  3. 作为所有后端服务的聚合点。

3、几个重要的概念说一说?(美团)

  1. Route(路由):是网关的基本构建模块,由一个ID、一个目标URI、一组断言、一组过滤器实现。如果断言为真,则会路由至指定的服务中。
  2. Predicate (断言):即一种匹配规则。可以根据多种情况进行匹配。我最常用的是根据path或Host进行匹配。
  3. Filter(过滤器):负责对过来的请求按照某种规则进行修改
    gateway:
    # 路由的示例
      routes:
        - id: product_route
          uri: lb://mall-product
          predicates:
            # 根据path进行匹配
            - Path=/api/product/**
            # 根据host进行匹配,可以一次匹配多个host。
            - Host=catmall.com, item.catmall.com
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

Spring Cloud 组件Nacos常见问题

1、主流的服务注册中心产品?(美团)

NacosEurekaConsulCoreDNSZookeeper
一致性协议CP+APAPCPCP
健康检查TCP/HTTP/MYSQL/Client BeatClient BeatTCP/HTTP/gRPC/CmdKeep Alive
负载均衡策略权重/ metadata/SelectorRibbonFabioRoundRobin
雪崩保护
自动注销实例支持支持不支持不支持支持
访问协议HTTP/DNSHTTPHTTP/DNSDNSTCP
监听支持支持支持支持不支持支持
多数据中心支持支持支持不支持不支持
跨注册中心同步支持不支持支持不支持不支持
SpringCloud集成支持支持支持不支持支持
Dubbo集成支持不支持不支持不支持支持
K8S集成支持不支持支持支持不支持

2、nacos工作原理?(华为)(滴滴)

  1. 服务注册原理:在nacos的服务端,有一个用来管理微服务实例的容器,注册中心将微服务的实例交由ServiceHolder处理,ServiceHolder为微服务提供空间并将它的所有实例挂在该空间下。服务注册完成后提供者将于注册中心维护心跳机制,心跳机制可以保证注册中心可以及时的剔除失效的实例。

  2. 服务发现原理:服务完成注册之后,消费者可以向注册中心订阅某个服务,并提交一个监视器,当注册中心的服务发生变更时监听器会收到通知,然后消费者可以更新本地的服务实例列表,以保证所有的服务均可用。

  3. nacos的负载均衡Nacos 的客户端在获取到服务的完整实例列表后,会在客户端进行负载均衡算法来获取一个可用的实例,模式使用的是随机获取的方式。

3、dubbo的注册中心原理?(百度)

  1. 服务容器负责启动,加载,运行服务提供者。

  2. 服务提供者在启动时,向注册中心注册自己提供的服务。

  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Spring Cloud Alibaba 组件 seata常见问题

1、什么是分布式事务?(shopee)(平安科技)(有赞)

是指事务的参与方位于不同的分布式系统的节点上。

2、分布式事务的基础理论?(shopee)

  • CAP理论:是设计分布式系统的基础理论依据。强一致性、可用性、分区容错性。

  • BASE理论:是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展。

    • 基本可用:
    • 软状态:相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
    • 最终一致性:

3、分布式事务协议?(shopee)(美团)

两阶段提交协议2PC:

两阶段提交协议中,存在一个节点作为协调者,其他参与事务的节点作为参与者

第一阶段(准备阶段):

  • 协调者询问所有参与者是否可以执行提交操作,并等待参与者节点的响应。
  • 参与者执行各自的本地事务操作,并将操作写入undo 日志。
  • 若参与者事务执行成功,返回给协调者同意信号,否则返回终止信息。

第二阶段(提交阶段):

当协调者节点从所有参与者节点获得的相应消息都为"同意"时

  • 协调者向所有参与者发出commit请求。
  • 参与者节点完成事务提交操作并释放整个事务期间占用的资源。
  • 参与者向协调者返回完成信息。协调者接收到所有参与者返回的完成信息后完成事务。

若任一参与者节点在第一阶段返回的响应消息为"中止"。

  • 协调者节点向所有参与者节点发出"回滚操作(rollback)"的请求。
  • 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
  • 参与者节点向协调者节点发送"回滚完成"消息。
  • 协调者节点受到所有参与者节点反馈的"回滚完成"消息后,取消事务。

三阶段提交协议3PC:

与两阶段提交不同的是,三阶段提交有两个改动点。

  • 引入超时机制。同时在协调者和参与者中都引入超时机制。
  • 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommitPreCommitDoCommit三个阶段。

4、分布式事务的解决方案?(shopee)(美团)

1. XA:该协议采用两阶段提交(2PC),即整个事务控制过程经历了两个阶段。

  1. 第一阶段:所有分支的操作都准备好了,由TM告知每个分支准备提交,此时,作为全局事物中的每个RM记录着在稳定存储中的动作(也就是说记录着准备好的这个动作)。
  2. 第二阶段:TM会告诉RM是提交或回滚,如果所有分支表明准备好能够提交时,RM会被告知提交,如果任何一个RM表明没有准备好或者不能提交,则进行全部回滚。

2. TCC(try-confirm-cancel):

try阶段:尝试执行,完成所有的业务检查,预留完成业务所必须的资源。

confirm阶段:当TCC事务管理器决定commit全局事务时,就会逐个执行Try操作指定的Confirm操作,将Try未完成的事项最终完成。不作任何业务检查,只使用Try阶段预留的业务资源。

cancel阶段:Cancel 是对Try操作的一个回撤。当TCC事务管理器决定rollback全局事务时,就会逐个执行Try操作指定的Cancel操作,将Try操作已完成的事项全部撤回。

3. MQ事务:

在这里插入图片描述

  1. 在系统A处理任务A前,首先向消息中间件发送一条消息
  2. 消息中间件收到后将该条消息持久化,但并不投递。此时下游系统B仍然不知道该条消息的存在。
  3. 消息中间件持久化成功后,便向系统A返回一个确认应答;
  4. 系统A收到确认应答后,则可以开始处理任务A;
  5. 任务A处理完成后,向消息中间件发送Commit请求。该请求发送完成后,对系统A而言,该事务的处理过程就结束了,此时它可以处理别的任务了。 但commit消息可能会在传输途中丢失,从而消息中间件并不会向系统B投递这条消息,从而系统就会出现不一致性。这个问题由消息中间件的事务回查机制完成。
  6. 消息中间件收到Commit指令后,便向系统B投递该消息,从而触发任务B的执行;
  7. 当任务B执行完成后,系统B向消息中间件返回一个确认应答,告诉消息中间件该消息已经成功消费,此时,这个分布式事务完成。

5、Seata是什么?(滴滴)(华为)(shopee)

Seata提供一站式的分布式事务解决方案。可以提供AT(默认使用,基于2PC两阶段模式)、 TCC、 SAGA 、XA事务模式。

Seata的3 + 1个概念:

  • TC:事务协调器,负责维护分布式事务的运行状态。负责协调并驱动全局事务的提交或者回滚。
  • TM: 事务管理器,是一个分布式的发起者和终结者。最终发起全局提交或回滚决议。
  • RM:资源管理器,负责本地事务的运行。负责分支注册,状态汇报,接收事务协调器的指令,驱动本地事务的提交和回滚。
  • Transaction ID:全局事务的唯一ID , XID。

TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运行。

6、Seata工作流程?

  1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
  2. XID在微服务的调用链路中传播
  3. RM向TC注册分支事务,将其纳入XID对应的全局事务管辖
  4. TM向TC发起针对XID的全局提交或者回滚决议
  5. TC调度XID下管辖的全部分支事务完成提交或者回滚请求

7、怎么用?

  1. 对于每个微服务对应的数据库都需要添加一张回滚日志表。用于自动的回滚一些数据。

  2. 下载seata-server软件包,github.com/seata/seata/releases

  3. 导入依赖 spring-cloud-starter-alibaba-seata

  4. 启动 seata-server

  5. 所有想要使用到分布式事务的微服务使用seata DatasourceProxy代理自己的数据源

  6. @GlobaTransactional 开启全局事务。(只需要给分布式事务入口标上该注解即可),对于远程调用的方法不需要标。

  7. 启动各个微服务即可。

Spring Cloud Alibaba 组件 sentinel 常见问题

由于Netflix的hystrix停止更新,所以改用spring cloud Alibaba的sentinel组件实现系统的限流、熔断、降级。

1、 sentinel是什么?

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的轻量级高可用流量控制产品,由阿里中间件团队开源。主要以流量为切入点,从**流量限制、服务熔断降级、**系统负载保护等多个维度来帮助您保护服务的稳定性。

2、sentinel原理?

sentinel项目分为7个主要部分,其中最主要的 就是sentinel-core模块。限流、熔断、降级、系统保护等都在这里实现。

3、什么是流量控制?

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。

4、限流、熔断、降级分别是什么?

限流:对于打入集群的请求流量在入口处进行控制,使服务能够承担不超过自己能力的流量压 力。

熔断:A服务调用B服务,由于各种各样的原因导致请求时间过长。这样的情况次数过多就应该考虑将B服务直接断路。凡是调用B服务直接返回错误数据。

降级:由于服务器的压力较大而对一些服务进行有针对的降级,从而保证核心业务的正常运行。

5、服务端接口有哪些保护方案?

  1. Token授权认证,防止未授权用户获取数据;
  2. 时间戳超时机制;
  3. URL签名,防止请求参数被篡改;
  4. 防重放,防止接口被第二次请求,防采集;
  5. 采用HTTPS通信协议,防止数据明文传输;

6、服务降级有哪几种策略?

  1. 服务接口拒绝服务:无用户特定信息,页面能访问,但是添加删除提示服务器繁忙。页面内容也可在Varnish或CDN内获取。
  2. 页面拒绝服务:页面提示由于服务繁忙此服务暂停。跳转到varnish或nginx的一个静态页面。
  3. 延迟持久化:页面访问照常,但是涉及记录变更,会提示稍晚能看到结果,将数据记录到异步队列或log,服务恢复后执行。
  4. 随机拒绝服务:服务接口随机拒绝服务,让用户重试,目前较少有人采用。因为用户体验不佳。

7、sentinel对比hystrix?(美团)

对比内容SentinelHystrix
隔离策略信号量隔离线程池隔离/信号量隔离
熔断降级策略基于响应时间或失败比率基于失败比率
实时指标实现滑动窗口滑动窗口(基于 RxJava)
规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持支持支持
限流基于 QPS,支持基于调用关系的限流不支持
流量整形支持慢启动、匀速器模式不支持
系统负载保护支持不支持
控制台开箱即用,可配置规则、查看秒级监控、机器发现等不完善
常见框架的适配Servlet、Spring Cloud、Dubbo、gRPC 等Servlet、Spring Cloud Netflix

ActiveMQ常见问题

1、什么是ActiveMQ?

ActiveMQ是一种开源的,实现了JMS1.1规范的,面向消息(MOM)的中间件,为应用程序提供高效的、可扩展的、稳定的和安全的企业级消息通信。

2、activeMQ的作用以及原理?

实现系统间的通信,实现系统间解耦、异步、削峰等作用。

原理就是生产者生产消息, 把消息发送给activemqActivemq 接收到消息, 然后查看有多少个消费者, 然后把消息转发给消费者, 此过程中生产者无需参与。 消费者接收到消息后做相应的处理和生产者没有任何关系。

3、activeMQ的有几种通信方式?(网易)

  1. 点对点通信:一个生产者对应一个消费者。
  2. 发布订阅模式:类似于微信的公众号,一个生产者对应多个消费者。

两种模式的异同?

订阅发布模式,没有订阅者的话消息会被丢弃。点对点模式消息会保存到activeMQ服务器中。

订阅发布模式随着订阅的增长性能会逐渐降低,点对点模式不会。

4、如何解决消息丢失问题?(网易)

点对点模式的话, 如果消息发送不成功此消息默认会保存到 activemq 服务端直到有消费者将其消费, 所以此时消息是不会丢失的。

5、如何解决消息重复消费的问题?(网易)

  1. MySQL数据库中添加一张消息消费记录表,记录已经消费过的消息的ID,每当一个消息进来先判断它是否被执行过,如果执行过就放弃。如果没执行过就开始执行消息,消息执行完之后将该消息的ID存入表中。

  2. 由connection创建session时有两个参数供选择,一个是事务,一个是签收机制。什么是签收机制?消费者接受到消息后,需要告诉消息服务器,我收到消息了。当消息服务器收到回执后,本条消息将失效。因此签收将对PTP模式产生很大影响。如果消费者收到消息后,并不签收,那么本条消息继续有效,很可能会被其他消费者消费掉!从而导致重复消费。

    消息的签收有三种可供选择:

    AUTO_ACKNOWLEDGE//表示在消费者receive消息的时候自动的签收
    CLIENT_ACKNOWLEDGE//表示消费者receive消息后必须手动的调用acknowledge()方法进行签收
    DUPS_OK_ACKNOWLEDGE//签不签收无所谓了,只要消费者能够容忍重复的消息接受,当然这样会降低Session的开销
    

    一般生产上会选取CLIENT_ACKNOWLEDGE作为签收策略。因为接收到了消息,并不意味着成功的处理了消息,假设我们采用手动签收的方式,只有在消息成功处理的前提下才进行签收,那么只要消息处理失败,那么消息还有效,仍然会继续消费,直至成功处理!

6、Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么不同?(美团)

特性ActiveMQRabbitMQRocketMQKafka
单机吞吐量万级万级10万级10万级
时效性ms微秒级msms
可用性非常高非常高
消息可靠性可能会丢失可以到达0丢失可以到达0丢失
Java编写,阿里开源。适用于大数据领域

7、ActiveMQ如何保障高可用?(美团)

搭建集群、外部持久化(服务器意外宕机,消息依然存在)、签收、事务。

8、利用MQ异步下订单?(美团)(滴滴)

秒杀环境下,一旦redis库存扣减成功了,就相当于完成了购物操作,由于秒杀的特殊性,如果对每一个对于数据库的写订单、减库存操作的立即执行的话,对数据库的压力过大。

于是,我们将每一条秒杀成功的消息封装后存入消息队列中,然后给用户返回“抢购排队中”的结果。然后将消息队列中的下单操作一个个的写入数据库中、下订单、写订单详情。最终返回用户“秒杀成功”。比起多线程同步的修改数据库的操作,这样一来大大的缓解了数据库的连接压力。

9、MQ中的基本组件有哪些?

连接工厂、连接、会话(目的地(queue、topic)、生产者、消费者)包含两个参数事务、确认机制。其中事务偏向于生产者,确认机制偏向于消费者。

10、消息消费的方式?(美团)

jms是异步通信,发送方发送消息后就可以继续其它业务,而不用阻塞等等接收方响应。但接收方在接收消息上有两种模式:一种是同步接收消息,一种是异步接收消息。下面的示例中也会分别演示

  1. 同步方式:接收者通过receive方法来接收消息,receive方法在接收到消息之前(或超时之前)将一直阻塞。
  2. 异步方式:当消息队列有消息时会调用接收者的onMessage方法,接收者不用阻塞等待,可执行其它业务。 实现接口MessageListener,注册监听器 consumer.setMessageListener(this); (异步接收) ,实现 onMessage方法。

11、activeMQ在项目中的应用有哪些?(美团)

Activemq在项目中主要是完成系统之间通信,并且将系统之间的调用进行解耦。例如在添加、修改商品信息后,需要将商品信息同步到索引库、同步缓存中的数据以及生成静态页面一系列操作。在此场景下就可以使用activemq。一旦后台对商品信息进行修改后,就向activemq发送一条消息,然后通过activemq将消息发送给消息的消费端,消费端接收到消息可以进行相应的业务处理。

12、单点登录系统中如果cookie禁用,该如何解决?

可以使用url中带参数,把token传递给服务端。http的get方式。

13、订单表的数据量太大,我把订单分到许多表中,那么我我想用一条sql查处所有的订单,怎么解决?(滴滴)

分库情况下:可以使用mycat数据库中间件实现多个表的统一管理。虽然物理上是把一个表中的数据保存到多个数据库中,但是逻辑上还是一个表,使用一条sql语句就可以把数据全部查询出来。

14、activeMQ的事务?(美团)

session提供了commit以及rollback方法进行事务的提交与回滚。在事务状态下进行发送操作,消息并未真正投递到中间件。而只有进行session.commit操作之后,消息才会发送到中间件,再转发到适当的消费者进行处理。如果是调用rollback操作,则表明,当前事务期间内所发送的消息都取消掉。

开启事务后,producer发送message时在message中带有transaction_ID。broker收到message后判断是否有transaction_ID,如果有就把message保存在transaction store中,等待commit或者rollback消息。所以ActiveMQ的事务是针对broker而不是producer的,不管session是否commit,broker都会收到message。如果producer发送模式选择了persistent,那么message过期后会进入死亡队列。在message进入死亡队列之前,ActiveMQ会删除message中的transaction_ID,这样过期的message就不在事务中了,不会保存在transaction store中,会直接进入死亡队列。

15、activeMQ的外部持久化机制?

levelDB、kahaDB、JDBC

16、activeMQ的的签收机制?

默认使用自动签收机制。生产上推荐使用client_ackonwlege模式。可以确保消息被消费后才签收。这样的话就不会有重复消费的问题出现了。

Zookeeper常见问题

1、什么是zookeeper?(跟谁学)

是一个开源的分布式协同服务系统,Zookeeper的设计目标是将那些复杂容易出错的分布式一致性服务封装起来。

2、Zookeeper可以实现什么功能?

服务的注册与发现、分布式锁、集群管理、负载均衡等等。

3、Zookeeper的数据模型?(有赞)(滴滴)

共享的、树形结构,由一系列的 ZNode数据节点组成,类似文件系统(目录不能存数据)。ZNode存有数据信息,如版本号等等。ZNode之间的层级关系,像文件系统中的目录结构一样。并且它是将数据存在内存中,这样可以提高吞吐、减少延迟。

4、如何识别请求的先后顺序?

ZooKeeper会给每个更新请求,分配一个全局唯一的递增编号(zxid),编号的大小体现事务操作的先后顺序。

5、Znode的类型?

  • 持久节点:一旦创建,除非主动移除,否则会一直保存在ZooKeeper。
  • 临时节点:生命周期和客户端会话绑定,会话失效,相关的临时节点被移除

6、ZooKeeper定义了几种权限?

  1. CREATE
  2. READ
  3. WRITE
  4. DELETE
  5. ADMIN

7、ZAB了解吗?(跟谁学)(有赞)

全称 Zookeeper Atomic Broadcast (Zookeeper原子广播)。

Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。是一种支持崩溃恢复的原子广播协议。

zab有两种基本模式:

  1. 崩溃恢复:在正常情况下运行非常良好,一旦Leader出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。为了程序的正确运行,整个恢复过程后需要选举出一个新的Leader,因此需要一个高效可靠的选举方法快速选举出一个Leader。
  2. 消息广播:类似一个两阶段提交过程,针对客户端的事务请求, Leader服务器会为其生成对应的事务Proposal,并将其发送给集群中的其余所有机器,再分别收集各自的选票,最后进行事务提交。

8、什么情况会导致ZAB进入崩溃恢复模式并选取出新的leader?

启动过程或Leader出现网络中断、崩溃退出与重启等异常情况时。

当选举出新的Leader后,同时集群中已有过半的机器与该Leader服务器完成了状态同步之后,ZAB就会退出恢复模式。

9、如何创建一个Znode?

create -e或者-s /aaa "bbb"

10、如何获取指定节点信息?

get /aaa

11、如何查看子节点信息?

ls /aaa

12、如何更新指定节点信息?

set /aaa "ccc"

13、如何删除指定节点?

delete /aaa

14、watch监听器?

ZooKeeper允许用户在指定节点上注册Watcher,当触发特定事件时,ZooKeeper服务端会把相应的事件通知到相应的客户端上,属于ZooKeeper一个重要的特性。

get /aaa watch 监控节点变化

15、客户端如何获取配置信息?

启动时主动到服务端拉取信息,同时,在制定节点注册Watcher监听。一旦有配置变化,服务端就会实时通知订阅它的所有客户端。

Docker常见问题

1、是什么?(美团)(滴滴)

Docker是一个容器化平台,它以容器的形式将您的应用程序及其所有依赖项打包在一起,以确保您的应用程序在任何环境中无缝运行。

2、docker与虚拟机的区别?(网易)(滴滴)

  1. docker不需要像VM一样去模拟计算机硬件环境,
  2. 与VM相比,docker中的镜像只保留核心功能,如Linux镜像在docker中仅仅有170M。
  3. 主机上的所有容器共享主机的调度程序,从而节省了额外资源的需求。

3、docker原理?(美团)(百度)

Docker分客户端和服务端概念,Docker服务端有一个守护线程以及多个工作线程概念(类似于nginx)。Docker客户端与Docker守护进程通信,**Docker守护进程负责构建,运行和分发Docker容器。**工作线程负责从仓库拉取镜像。

4、docker镜像是什么?(美团)(百度)

Docker镜像是Docker容器的源代码,Docker镜像用于创建容器。使用build命令创建镜像。

5、docker容器是什么?(美团)(百度)

Docker容器包括应用程序及其所有依赖项,作为操作系统的独立进程运行。

6、docker常用命令?

  1. docker pull 从仓库中拉取镜像
  2. docker push 将镜像推送到远程仓库
  3. docker rm 删除容器
  4. docker rmi 删除镜像
  5. docker images 列出所有的镜像
  6. docker ps 列出所有的容器
  7. docker run 运行一个容器

秒杀项目常见问题

1、介绍一下你的项目?

为什么做这个项目?

希望将过去所学的一些知识做一个系统的深入理解。秒杀项目运用场景多,涉及的问题与中间件较为复杂,更有利于对web服务的深入学习。

详细过程?

本项目主要是为了模拟一种高并发的场景,请求到达nginx后首先经由负载轮询策略到达某一台服务器中(后端部署了两台服务器)。为了解决秒杀场景下的入口大流量、瞬时高并发问题。引入了redis作为缓存中间件,主要作用是缓存预热、预减库存等等。引入秒杀令牌与秒杀大闸机制来解决了入口大流量问题。引入线程池技术来解决了浪涌(高并发)问题。

2、秒杀中如何处理超卖问题?(网易)(百度)(美团)(滴滴)(字节)

直接由数据库操作库存的sql语句如下所示。依靠MySQL中的排他锁实现

 update table_prmo set num = num - 1 WHERE id = 1001 and num > 0

利用redis的单线程特性预减库存处理秒杀超卖问题!!!

  1. 在系统初始化时,将商品以及对应的库存数量预先加载到Redis缓存中;(缓存预热)
  2. 接收到秒杀请求时,在Redis中进行预减库存(decrement),当Redis中的库存不足时,直接返回秒杀失败,否则继续进行第3步;
  3. 将请求放入异步队列中,返回正在排队中;
  4. 服务端异步队列(MQ)将请求出队,出队成功的请求可以生成秒杀订单,减少数据库库存,返回秒杀订单详情。

3、秒杀中如何解决重复下单问题?(网易)

mysql唯一索引(商品索引)+ 分布式锁

4、热点数据失效(缓存击穿)问题如何解决?(网易)(美团)

设置热点数据永远不过期。设置不同的失效时间

5、缓存和数据库数据一致性如何保证?(shopee)(美团)(网易)

  • 使用canal组件实现(canal的原理,模拟MySQL的主从复制机制)

  • 更新数据库后立即删缓存,然后下一次查缓存找不到数据后会再次从数据库同步到缓存。

6、减库存成功了,但是生成订单失败了,怎么办?(shopee)(美团)(华为)

非分布式事务:系统中使用Spring提供的事务功能即可。

分布式事务:将减库存与生成订单操作组合为一个事务。要么一起成功,要么一起失败。

CAP理论(只能保证 CP、AP)、BASE理论(最终一致性,基本可用性、柔性事务)。

分布式事务的两个协议以及几种解决方案:

  1. 全局消息
  2. 基于可靠消息(MQ)的分布式事务
  3. TCC
  4. 最大努力通知

seata分布式事务控制组件。

7、做了什么限流削峰的措施?(字节)(美团)(华为)

秒杀令牌(token)加秒杀大闸限制入口流量。线程池技术限制瞬时并发数。验证码做防刷功能。

8、如何解决客户的恶意下单问题?(shopee)

封IP,nginx中有一个设置,单个IP访问频率和次数多了之后有一个拉黑操作。

9、多机器扣减库存,如何保证它的线程安全的?(shopee)(美团)(华为)

分布式锁。redission客户端实现分布式锁

10、如何去减Redis中的库存?(华为)

decrement API减库存,increment API回增库存。以上的指令都是原子性的

11、缓存中的数据突然失效,导致请求全部打到了数据库,如何解决?(字节)

典型的缓存雪崩问题,给缓存中的数据的过期时间加随机数。

12、如果项目中的Redis挂掉,如何减轻数据库的压力?(滴滴)(华为)

redis集群,主从模式、哨兵模式、集群模式。

主从模式中:如果主机宕机,使用slave of no one 断开主从关系并且把从机升级为主机。

哨兵模式中:自动监控master / slave的运行状态,基本原理是:心跳机制+投票裁决。

每个sentinel会向其它sentinel、master、slave定时发送消息(哨兵定期给主或者从和slave发送ping包(IP:port),正常则响应pong,ping和pong就叫心跳机制),以确认对方是否“活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的“主观认为宕机” Subjective Down,简称SDOWN)。

若master被判断死亡之后,通过选举算法,从剩下的slave节点中选一台升级为master。并自动修改相关配置。

13、页面静态化

那就把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。

14、秒杀系统面临的问题有哪些?(滴滴)(华为)(字节)(美团)

  1. 高并发
  2. 超卖、重复卖问题
  3. 脚本恶意请求
  4. 数据库扛不住
  5. 加了缓存之后的缓存三大问题(击穿、穿透、雪崩)

15、秒杀系统设计?

在这里插入图片描述

  1. nginx做一个动静分离以及负载均衡
  2. redis缓存预热、预减库存
  3. MQ异步下单

16、分布式会话问题?(顺丰科技)(网易)(美团)

token+redis解决分布式会话问题。

Token是服务端生成的一串字符串,作为客户端进行请求的一个令牌,当第一次登录后,服务器生成一个userToken便将此Token返回给客户端,存入cookie中保存,以后客户端只需带上这个userToken前来请求数据即可,无需再次带上用户名和密码。二次登录时,只需要去redis中获取对应token的value,验证用户信息即可。

// 用户第一次登录时,经过相关信息的验证后将对应的登录信息以及凭证(token)存入reids中
String uuid = UUID.rondom().toString();
redisTemplate.opsForValue().set(uuid, userModel);
// token下发到客户端存入cookie中进行保存

// 再次登录时cookie携带着token到redis中找到对应的value不为空,表示该用户已经登陆过了,如果查询结果为空,则让该用户重新登陆,然后将用户信息保存到redis中。
// 一般设置一个过期时间,表示的就是多久后用户的登录态就失效了。

17、线程池的执行过程?(美团)(滴滴)

先说一下核心参数:

  • corePoolSize: 线程池核心线程数最大值
  • maximumPoolSize: 线程池最大线程数大小
  • keepAliveTime: 线程池中非核心线程空闲的存活时间大小
  • unit: 线程空闲存活时间的单位
  • workQueue: 存放任务的阻塞队列
  • threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
  • handler: 线城池的饱和策略事件,主要有四种类型。

一个任务进来,先判断当前线程池中的核心线程数是否小于corePoolSize。小于的话会直接创建一个核心线程去提交业务。如果核心线程数达到限制,那么接下来的任务会被放入阻塞队列中排队等待执行。当核心线程数达到限制且阻塞队列已满,开始创建非核心线程来执行阻塞队列中的 业务。当线程数达到了maximumPoolSize且阻塞队列已满,那么会采用拒绝策略处理后来的业务。

18、你项目中难的难点是什么?(字节)(百度)(平安科技)(新浪)

一、限流、削峰部分的设计。

入口大流量限制

例如有10W用户来抢购10件商品,我们只放100个用户进来。

采取发放令牌机制(控制流量),根据商品id和一串uuid产生一个令牌存入redis中同时引入了秒杀大闸,目的是流量控制,比如当前活动商品只有100件,我们就发放500个令牌,秒杀前会先发放令牌,令牌发放完则把后来的用户挡在这一层之外,控制了流量。

获取令牌后会对比redis中用户产生的令牌,对比成功才可以购买商品

// 设置秒杀大闸
redistemplate.opsForValue().set("door_count"+promoId, itemModel.getStock()*5)
// 发放令牌时,先去redis获取当前大闸剩余令牌数
int dazha = redistemplate.opsForValue().get("door_count"+promoId)
    if (dazha <= 0) {
        // 抛出一个异常
        throw new exception;
    }else {
        String tocken = UUIDUtils.getUUID()+promoId;
        // 用户只有拥有这个token才有资格下单
        redistemplate.opsForValue().set(userToken, token);
    }

高并发流量的限制(泄洪):利用线程池技术,维护一个具有固定线程数的线程池。每次只放固定多用户访问服务,其他用户排队。另外一种实现方式就是J.U.C包中的信号量(Semaphore)机制。可以有效的限制线程的进入。

二、用户登录的问题(分布式会话)

做完了分布式扩展之后,发现有时候已经登录过了但是系统仍然会提示去登录,后来经过查资料发现是cookie和session的问题。然后通过设置cookie跨域分享以及利用redis存储token信息得以解决。

19、项目中Redis都做了些什么?

  1. 作为缓存中间件提升系统性能
  2. 预减库存,防止超卖功能实现
  3. redis设置热点数据永不过期

20、项目中ActiveMQ都做了什么?

作为异步下单的中间件,利用队列排队下单缓解数据库的并发压力。

21、线程池技术中核心线程数的取值有经验值吗?(美团)(滴滴)

CPU密集型业务:N+1;IO密集型业务:2N+1

22、TPS提升了多少?(美团)

基础架构下的tps是2000

经过做动静分离、nginx反向代理并做了分布式扩展、引入redis中间件后达到了2500 tps。

23、nginx的负载均衡策略?(字节)(顺丰科技)(大华)(跟谁学)(有赞)

轮询、权重、IP_hash、最少连接。

24、一个人同时用电脑和手机去抢购商品,会颁发几个token?(美团)

首先多台设备登录属于SSO问题,用户登录一端之后另外一端可以通过扫码等形式登录。虽然用户登录了多台设备,但是用户名是一样的。为用户办法的token是相同的。我们为一个用户只会颁发一个token。

25、如何利用线程池实现了流量削峰?

设置最大线程数来限制浪涌流量

26、线程池的拒绝策略能详细说一下吗?(美团)

ThreadPoolExecutor.AbortPolicy://丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy//丢弃任务,但是不抛出异常。
DiscardOldestPolicy//丢弃队列最前面的任务,然后重新提交被拒绝的任务
CallerRunsPolicy//由调用线程(提交任务的线程)处理该任务

27、被线程池拒绝掉的那部分用户的秒杀令牌还有效吗?(美团)

无效,会从redis中删除,

28、线程池中阻塞队列的大小设置为多少合适?(美团)

设置为秒杀商品的个数减去核心线程数最合适。

29、项目上线之后想看JVM的GC情况在Linux中用什么命令?(美团)

jstat -gc vmid count
jstat -gc 12538 5000 // 表示将12538进程对应的Java进程的GC情况,每5秒打印一次

30、秒杀令牌(token)每秒钟生成多少个?(美团)

跟随用户的请求会动态变化,令牌桶机制可以控制每秒生成令牌的个数。

31、能不能详细描述一下使用MQ异步减redis与MySQL库存的过程?(美团)

redis中库存减成功后,生成一条消息包含了商品信息、用户信息消息由MQ的生产者生产,经由queue模式发送给消费方,即订单生成的业务模块,在该模块会消费这条消息,根据其中的信息进行订单的生成,以及数据库的修改操作。

32、做到了什么程度、库存量与并发度是多少?(美团)

TPS:单机2000

33、MySQL中的表是怎么设计的?(美团)(字节)

item表、item_stock表、order表、用户信息表、

34、如何只使用MySQL保证商品没有超卖?(大华)

将查库存、减库存两个sql语句作为一个事务进行控制,保证每一个库存只能被一个用户消费。两条语句都执行成功进行事务提交,否则回滚。但这样会导致并发很低。但也没办法。

35、数据库改库存的SQL?(美团)

update table set stock = stock-1 where prom_id = ? and stock > 1;

36、如何防止用户一直点击下单按钮?(华为)

前端限制:一次点击之后按钮置灰几秒钟。

后端限制:由于秒杀令牌的设置,用户的一个下单请求会先判断用户当前是否已经持有令牌了,因为用户全局只能获取一次令牌,然后存入到Redis缓存中。用户有令牌的话直接返回 “正在抢购中”。

商城常见问题

1、详细讲一下你的项目?

项目基于多个微服务模块实现,使用Nacos做为微服务的注册与发现中心。引入gateway组件作为服务入口的统一管理。使用Open Feign作为服务间通信的组件,基于请求与响应的方式。使用seata组件提供分布式事务的服务。基于ES实现商品的检索等等。

2、你项目中的难点是什么?(字节)(华为)(美团)

一、查询商品详情

查询商品详情,也就是从商城主页面点击一个商品到展示一个商品的详细信息这一步骤。由于逻辑比较复杂,而且有些数据还需要进行远程调用。

**(获取SKU基本信息 -> 获取SKU图片信息 -> 获取SKU促销信息 -> 获取SPU销售属性 -> 获取规格参数组以及组下参数-> 获取SPU详情)**为了优化这一部分的时间,引入了线程池技术从而多线程异步的方式执行以上任务。

但是异步任务又涉及到一个异步编排的问题,也就是说有些任务的先后顺序是有要求的。可以使用completableFuture来完成异步编排。

二、登录问题

抽取SSO单点登录模块,一切的登录请求由该模块处理。是基于相同顶级域名的SSO实现。

3、你用了线程池,为什么用线程池?工作顺序说一下?(顺丰科技)(滴滴)(美团)(shopee)

  1. 使用线程池可以减少创建和销毁线程的次数,从而降低资源的消耗。每个工作线程都可以重复使用
  2. 可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃

创建线程池之后,核心线程开始执行任务,核心线程占用完了,其他任务进入阻塞队列,队列满了但运行线程小于最大线程数,创建非核心线程立刻运行任务,空闲线程开始执行任务。没有空闲线程了并且队列也满了,使用拒绝策略拒绝其他任务。

拒绝策略:调用者执行、抛弃老任务策略、丢弃新任务策略、直接抛异常策略等等

4、单点登录是什么?(顺丰科技)(shopee)

单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

普通的登录认证机制:
在这里插入图片描述

我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成登录认证。这时,我们在这个用户的session中标记登录状态为yes(已登录),同时在浏览器(Browser)中写入Cookie,这个Cookie是这个用户的唯一标识。下次我们再访问这个应用的时候,请求中会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。如果不做特殊配置,这个Cookie的名字叫做jsessionid,值在服务端(server)是唯一的。

同顶级域名下的单点登录:
在这里插入图片描述
一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统(微服务),叫做:sso.a.com。

我们只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.a.com中登录了,其实是在sso.a.com的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.a.com下写入了Cookie。那么我们怎么才能让app1.a.com和app2.a.com登录呢?这里有两个问题:

  • Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。
  • sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。

解决办法:针对cookie跨域问题,将cookie域设置为顶域,即属于a.com。这样所有的子域系统都能访问到顶域的cookie。针对session问题,可以使用Spring-session 解决session共享问题。

5、spring session 原理?

spring-session 配合redis 将session 进行统一管理。

6、为什么需要单点登录?

方便客户、不需要记住多个 ID 和密码。

7、cookie可以跨域名传输吗?(平安科技)

默认不可以,但可以设置。

服务端使用使用@CrossOrigin注解时。只需要在网页端设置跨域 XMLHttpRequest 请求的 withCredentials 属性就可以正常设置和获取跨域 Cookie。

8、你Jmeter压测的什么接口?能达到一个什么样的性能?(美团)

分别压测了动静分离、redis中间件引入前的商城主页面接口。

9、你为什么用canal?(美团)

解决数据库缓存的一致性问题。另外一种解决方式是采用改完数据库删缓存的方式。

canal通过模拟MySQL的从机,从而完成类似于MySQL的主从复制的过程。然后canal将从MySQL读到的数据同步到redis、ES中。

10、什么是分布式事务?有什么特点?和单机事务有什么区别?(shopee)(跟谁学)

后面分布式事务中有详细讲解。

11、两阶段提交?

2pc,后面分布式事务中有详细讲解。

12、线程池的核心线程数有没有经验值?(滴滴)

  • 如果是CPU密集型应用(CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。),则线程池核心线程数大小设置为N+1
  • 如果是IO密集型应用(IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。),则线程池核心线程数大小大小设置为2N+1

其中N为CPU的核心数。

13、你分布式服务中假设同一功能的微服务部署了多台,你的请求是怎么处理的?(百度)

可以依靠 Open Feign 组件内部封装的Ribbon实现提供相同服务的微服务的负载均衡。

@FeignClient("mall-coupon") // 标识了要去调用的那个微服务
public interface CouponFeignService {
    @PostMapping("/coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);

    @PostMapping("/coupon/skufullreduction/saveinfo")
    R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);

}

在使用@FeignClient注解的时候 是默认使用了Ribbon进行客户端的负载均衡的,默认的是随机的策略,那么如果我们想要更改策略的话,需要修改消费者yml中的配置,如下:

ribbon:
	NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略

14、你的项目还有什么可以扩展的地方?(字节)

加第三方登录(微博、QQ等)功能。完善SSO功能。

15、数据库结构设计方面问题?

采用分库分表的方式设置数据库。为每一个微服务设置独立的数据库。

16、分布式服务中生成全局唯一ID的方式?(招银网络科技)

分布式ID的特点:全局唯一性、递增性、高可用性。

常见解决方案:UUID、雪花算法、UidGenerator、Leaf

雪花算法概要:SnowFlake是Twitter公司采用的一种算法,目的是在分布式系统中产生全局唯一且趋势递增的ID。SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢: 同一毫秒的ID数量 = 1024 X 4096 = 4194304。雪花算法的实现主要依赖于数据中心ID和数据节点ID这两个参数。

17、分布式、微服务、SOA?(shopee)(美团)

微服务的特点:技术异构性、隔离性、可扩展性、易于优化性。

18、购物车模块用户登录与没有登录分别如何使用购物车功能?(shopee)

  1. 用户没有登录时,我们将用户的购物车信息存储在cookie中。但是此时用户登录了在其他电脑上就会看不到当前购物车的信息。
  2. 用户登录后,将用户的cookie中的信息存储到redis中保存,这样的话同一账号下都可以使用同一个购物车的信息。

19、如果用户一直添加购物车添加商品怎么办?并且他添加一次你查询一次数据库?互联网上用户那么多,这样会对数据库造成很大压力你怎么办?(shopee)

用户往购物车中添加商品并不会直接操作数据库,而是通过操作redis或者cookie然后最终通过redis的持久化机制存储到MySQL中。所以频繁的添加购物车不会有问题。

20、如何解决MySQL数据库和ES数据不一致问题?(美团)(字节)

  1. 双写模式: 我们采取MySQL作为主要的数据存储,利用MySQL的事务特性维护数据一致性,使用ElasticSearch进行数据汇集和查询,此时es与数据库的同步方案就尤为重要。

    保证es与数据库的同步方案:

    1、首先添加商品入数据库,添加商品成功后,商品入ES,若入ES失败,将失败的商品ID放入redis的缓存队列(或MQ),且失败的商品ID入log文件(若出现redis挂掉,可从日志中取异常商品ID然后再入ES),
    task任务每秒刷新一下redis缓存队列,若是从缓存队列中取到商品ID,则根据商品ID从数据库中获取商品数据然后入ES。

  2. canal组件控制一致性

21、如果数据库写失败了怎么办?ES写失败了怎么办?

若入ES失败,将失败的商品ID放入redis的缓存队列(或MQ),且失败的商品ID入log文件(若出现redis挂掉,可从日志中取异常商品ID然后再入ES),
task任务每秒刷新一下redis缓存队列,若是从缓存队列中取到商品ID,则根据商品ID从数据库中获取商品数据然后入ES。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天你学Java了吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值