question

文章目录

java并发

一、JAVA如何开启线程,怎么保证线程安全

线程和进程的区别: 进程是操作系统进行资源分配的最小单元,线程是操作系统有进行任务分配的最小单元,线程隶属于进程。

如何开启线程:

  • 继承Thread类,重写run方法(由于Java只支持单继承,所以用继承的方式创建线程,不够灵活,所以不推荐使用继承Thread)
  • 实现Runnable接口,实现run方法,run()不可以抛出异常
  • 实现Callable接口,实现call方法,通过FutureTask创建一个线程,获取到线程的返回值。call方法可以抛出异常
  • 线程池

怎么保证安全:

  • jvm提供的锁, Synchronized
  • JDK提供的各种锁Lock

线程安全在三个方面体现:
原子性—atomic、synchronized
可见性—volatile、synchronized
有序性—volatile、synchronized、lock

线程和进程的区别

Java中如何保证线程安全性

二、Volatile和Synchronized有什么区别?Volatile能不能保证线程安全?DCL(Double Check Lock)单例为什么要加Volatile?

1、Synchronized,用来加锁。Volatile只是保持变量的线程可见性。通常使用于一个线程写,多个线程读的场景。

2、不能,Volatile只能保证线程可见性,不能保证原子性

3、Volatile防止指令重排,在DCl中,防止高并发情况下,指令重排造成的线程安全问题

在这里插入图片描述

//懒汉式单例
public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    private volatile static LazyMan lazyMan;

    //双重检测锁模式的 懒汉式单例 DCL懒汉式
    public static void getInstance() {
        //加锁
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();//不是一个原子性操作
                    /**
                     * 1.分派内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把 这个对象指向这个空间  volatile
                     */
                }
            }
        }
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new God() {
                @Override
                public void run() {
                    LazyMan.getInstance();
                }
            }).start();
        }
    }
}

class God implements Runnable {
    @Override
    public void run() {
        LazyMan.getInstance();
    }
}

三、JAVA线程锁机制是怎样的?偏向锁、轻量级锁、重量级锁有什么区别,锁机制是如何升级的?

1、java的锁就是在对象的markWord中记录一个锁状态。无锁,偏向锁,轻量级锁,重量级锁对应不同的锁状态。

2、java的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程
在这里插入图片描述

四、AQS如何实现可重入锁?

可重入锁:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

  • synchronized
  • ReentrantLock
package com.test.reen;

// 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁,synchronized和ReentrantLock都是可重入的
// 可重入降低了编程复杂性
public class WhatReentrant {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (this) {
					System.out.println("第1次获取锁,这个锁是:" + this);
					int index = 1;
					while (true) {
						synchronized (this) {
							System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
						}
						if (index == 10) {
							break;
						}
					}
				}
			}
		}).start();
	}
}

1、AQS是JAVA线程同步的框架,是JDK中很多锁工具的核心实现框架

2、在AQS中,维护可一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯,用来控制线程排队或者放行的。

3、在可重入锁的这个场景下,state用来表示加锁的次数,0表示无锁,每次加锁state就加1。释放锁state就减1。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

AQS详解

五、三个线程同时执行?并发情况下保证三个线程依次执行?三个线程有序交错进行?

CountDownLatch:countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。

CylicBarrier:让所有线程都等待完成后才会继续下一步行动。

Semaphore:用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

/**
 * 三个线程同时执行
 */
public class T {
    public int count = 0;
    public void add() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        int size = 3;
        T t = new T();
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < size; i++) {
            new Thread(() -> {
                try {
                	//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
                    countDownLatch.await();//三个线程都在这里等待
                    System.out.println(System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        Thread.sleep(5000);
        countDownLatch.countDown();
    }
}
/**
 * 三个线程依次执行
 */
public class T {
    static volatile int ticket = 1;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (ticket == 1) {
                    try {
                        Thread.sleep(100);
                        for (int i = 0; i < 10; i++) {
                            System.out.println("a" + i);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket = 2;
                    return;
                }
            }
        });

        Thread t2 = new Thread(() -> {
            while (true) {
                if (ticket == 2) {
                    try {
                        Thread.sleep(100);
                        for (int i = 0; i < 10; i++) {
                            System.out.println("b" + i);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket = 3;
                    return;
                }
            }
        });
        Thread t3 = new Thread(() -> {
            while (true) {
                if (ticket == 3) {
                    try {
                        Thread.sleep(100);
                        for (int i = 0; i < 10; i++) {
                            System.out.println("c" + i);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket = 1;
                    return;
                }
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}
/**
 * 三个线程交替执行
 */
public class T {
    private static Semaphore s1 = new Semaphore(1);
    private static Semaphore s2 = new Semaphore(1);
    private static Semaphore s3 = new Semaphore(1);

    public static void main(String[] args)  {
        try {
            s2.acquire();
            s3.acquire();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            while (true){
                try {
                    s1.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A");
                s2.release();
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    s2.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("B");
                s3.release();
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    s3.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("C");
                s1.release();
            }
        }).start();

    }
}

六、如何对一个字符串快速进行排序?

Fork/Join框架

/**
 * ForkJoin JDK1.7, 并行执行任务!提高效率,大数据量
 * 如何使用ForkJoin
 * 1、forkjoinPool通过它来执行
 * 2、计算任务forkjoinPool.execute(ForkJoinTask task)
 * 3、计算类要继承ForkJoinTask
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    //临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    //计算方法
    @Override
    protected Long compute() {
        if (end - start < temp) {
            Long sum = 0L;
            for (Long i = start; i < end; i++) {
                sum += i;
            }
            return sum;
        } else {//forkjion
            long middle = (start + end) / 2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork();//拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
            task2.fork();//拆分任务,把任务压入线程队列
            return task1.join() + task2.join();
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        System.out.println(sum);
    }

}

网络

一、TCP和UDP有什么区别?TCP为什么是三次握手?

TCP Transfer Control Protocol是一种面向连接的、可靠的、传输层通信协议

  • 打电话:面向连接,点对点的通信,高可靠的,效率低,占用的系统资源比较多

UDP User Datagram Protocol 是一种无连接的,不可靠的、传输层通信协议

  • 特点:广播:不需要连接,发送方不管接收方有没有准备好,直接发送消息;可以进行广播发送的;传输不可靠,有可能丢失消息;效率比较高;协议就会比较简单,占用的系统资源就比较少

TCP 建立连接三次握手,断开连接四次握手

如果是两次握手,可能造成连接资源浪费的情况

二、JAVA有哪几种IO模型?有什么区别?

BIO 同步阻塞IO:blocking 可靠性差,适用于连接比较少且比较固定的场景,
在这里插入图片描述

NIO 同步非阻塞IO:non-blocking 可靠性高,吞吐量高,适用于连接比较多并且连接比较短。例如聊天室

多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。 
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
在这里插入图片描述

AIO 异步非阻塞IO:Asynchronous IO 可靠性最好,吞吐量高,适用于连接比较多,并且连接比较长。JDK7,需要操作系统的支持

在这里插入图片描述

同步、异步、阻塞、非阻塞
在一个网络请求中,客户端会发一个请求到服务端。

1、客户端发了请求后,就一直等待服务端的响应。客户端:阻塞。请求:同步

2、客户端发了请求后,就去干别的事情去了,时不时的过来检查服务端是否给出了响应。客户端:非阻塞。请求:同步

3、换成异步请求,客户端发了请求后,就等待服务端返回响应。客户端:阻塞,服务端:异步

4、客户端发了请求后,就去干别的事情了。等到服务端给出响应后,再过来处理业务。客户端:非阻塞。请求:异步

同步模型(synchronous IO)

  • 阻塞IO(bloking IO)
  • 非阻塞IO(non-blocking IO)
  • 多路复用IO(multiplexing IO)
  • 多路复用IO(multiplexing IO)

异步IO(asynchronous IO)
IO模型详解

三、JAVA NIO的几个核心组件是什么?分别有什么作用?

Buffer Channel Selector
在这里插入图片描述
channel 类似于流,每个channel对应一个buffer缓冲区。channel会注册到selector。
select会根据channel上发生的读写事件,将请求交由某个空闲的线程处理。selector对应一个或多个线程。
Buffer、Channel都是可读,可写的

四、select,poll和epoll有什么区别?

他们是NIO中多路复用的三种实现机制,是由Linux操作系统提供的。

用户空间和内核空间:操作系统为了保护系统安全,将内核划分为两个部分,一个是用户空间,一个是内核空间。用户空间不能直接访问底层的硬件设备,必须通过内核空间。

文件描述符 File Descriptor(FD):是一个抽象的概念,形式上是一个整数,实际上是一个索引值。指向内核中为每个进程维护进程所打开的文件记录表。当程序打开一个文件或者创建一个文件时,内核就会向进程返回一个FD。

  • Select机制:会维护一个FD的结合fd_set(数组)。将fd_set从用户空间复制到内核空间,激活socket。 x64 2048
  • poll机制:和select机制差不多,把fd_set结构进行了优化,FD集合的大小就突破了操作系统的限制。poll结构来代替fd_set,通过链表实现的。
  • EPoll:Event Poll。EPoll不再扫描所有的FD,只将用户关心的FD的事件存放到内核的一个事件表当中。这样,可以减少用户空间与内核空间之前需要拷贝的数据。
.操作方式底层实现最大链接数IO效率
Select遍历数组受限于内核一般
poll遍历链表无上限一般
EPoll事件回调红黑树无上限

java的NIO当中用的那种机制?

五、HTTP和HTTPS的区别?

HTTP:是互联网上应用最为广泛的一种网络通信协议,基于TCP,可以使浏览器工作更为高效,减少网络传输。

HTTPS:是HTTP的加强版,可以认为是HTTP+SSl(secure socket layer)。在HTTP的基础上增加了一些列的安全机制,乙方面保证数据传输安全,另一方面对访问者增加了验证机制,是目前最为安全的解决方案。

主要区别

  • HTTP的连接是简单无状态的,HTTPS的数据传输时经过证书加密的,安全性更高。
  • 传输协议不同,所以端口不一样,80、443

HTTPS的缺点:

  • HTTPS的握手协议比较费时,所以会影响服务的响应速度以及吞吐量。
  • HTTPS也并不是完全安全的。他的证书体系其实并不是完全安全的。

HTTP和HTTPS的区别

HTTP特点:

  • 无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作
  • 无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。
  • 基于请求和响应:基本的特性,由客户端发起请求,服务端响应
  • 简单快速、灵活
  • 通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性

结果分析:HTTP协议传输数据以明文形式显示

针对无状态的一些解决策略:
场景:逛电商商场用户需要使用的时间比较长,需要对用户一段时间的HTTP通信状态进行保存,比如执行一次登陆操作,在30分钟内所有的请求都不需要再次登陆。

  • 通过Cookie/Session技术
  • HTTP/1.1持久连接(HTTP keep-alive)方法,只要任意一端没有明确提出断开连接,则保持TCP连接状态,在请求首部字段中的Connection: keep-alive即为表明使用了持久连接

HTTPS特点:

基于HTTP协议,通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护

  • 内容加密:采用混合加密技术,中间者无法直接查看明文内容
  • 验证身份:通过证书认证客户端访问的是自己的服务器
  • 保护数据完整性:防止传输的内容被中间人冒充或者篡改

在这里插入图片描述
在这里插入图片描述
为什么需要三次握手呢?为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

**在这里插入图片描述**
为什么需要四次挥手呢?TCP是全双工模式,当client发出FIN报文段时,只是表示client已经没有数据要发送了,client告诉server,它的数据已经全部发送完毕了;但是,这个时候client还是可以接受来server的数据;当server返回ACK报文段时,表示它已经知道client没有数据发送了,但是server还是可以发送数据到client的;当server也发送了FIN报文段时,这个时候就表示server也没有数据要发送了,就会告诉client,我也没有数据要发送了,如果收到client确认报文段,之后彼此就会愉快的中断这次TCP连接。

JVM

一、JVM的内存模型

内存模型
在这里插入图片描述

1.8
在这里插入图片描述
在这里插入图片描述

二、JAVA 类加载的过程是怎样的?什么是双亲委派机制?

AppClassLoader->ExtentionClassLoader->BootStrapClassLoader

每种类加载器都有他自己的加载目录。

每个类加载器对他加载过的类都有缓存。

双亲委派:向上委托查找,向下委托加载。作用保护JAVA的层的类不会被应用程序覆盖。

类加载过程

  1. 加载:把java的字节码数据加载到JVM中,并映射成JVM认可的数据结
  2. 连接:分为三个小的
    - 验证:检查加载到的字节信息是否符合JVM规范
    - 准备:创建类或接口的静态变量,并赋初值 半初始化状态
    - 解析:把符号引用转为直接应勇
  3. 初始化

一个对象从加载到JVM,再到被GC清除,都经历了什么过程?

  1. 用户创建一个对象,JVM首先需要到方法区去找对象的类型信息。然后再创建对象

  2. JVM要实例化一个对象,首先要在堆当中先创建一个对象。(半初始化状态)

  3. 对象首先会分配在堆内存中新生代的Eden区,然后经过一个Minnor GC,对象如果存活,就会进入S区,在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄+1。(多大年龄才会移入老年代?)在这里插入图片描述
    由于对象头存储年龄的比特域占4bit,所以年龄最大为15(1111)超过后,对象转入老年代。

  4. 当方法执行结束后,栈中 的指针会先移除掉。

  5. 堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。

三、怎么确定一个对象到底是不是垃圾,什么是GC Root?

有两种定位垃圾的方式:

  1. 引用计数法:这种方式是给堆内存当中的每个对象记录一个引用个数,引用个数为0的就认为是垃圾。引用计数无法解决循环引用的问题。
  2. 根可达算法:这种方式是在内存中,从引用根对象向下一直找引用,找不到的对象就是垃圾。

哪些是GC Root?Stack->JVM Stack,Native Stack,class类,run-time constant pool 常量池,static reference 静态变量。

四、垃圾回收算法

MarkSweep标记清除算法

标记阶段,把垃圾内存标记出来,清除阶段,直接将垃圾内存回收。会产生内存碎片

Copying拷贝算法

为了解决标记清除算法的内存碎片问题,就产生了拷贝算法。拷贝算法将内存分为大小相等的两半,每次只使用其中一半,垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。

这种算法没有内存碎片,但是会浪费空间,只使用一半,他的效率跟存活对象有关。

MarkCompack标记压缩算法

为了解决拷贝算法的缺陷。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将这一端边界以外的所有内存直接清除。

五、JVM有哪些垃圾回收器,他们都是怎么工作的?什么是STW?他都发生在哪些阶段?什么是三色标记?如何解决错标记和漏标记的问题?为什么要这几这么多的垃圾回收器?

Jvm垃圾回收器(基础篇)
Jvm垃圾回收器(基础篇)
Jvm垃圾回收器(终结篇)


STW:是垃圾回收算法执行过程中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的,native方法可以执行,但是不能与JVM交互,GC算法优化的重点就是减少STW
在这里插入图片描述

serial:GC时需STW,这个垃圾回收器是早期垃圾回收器,只有一个线程执行GC,在多CPU架构下,性能就会下降严重,只适用于几十兆的内存空间。

Parallerl:在串行基础上,增加多线程GC,PS+PO这种组合是JDK1.8默认的垃圾回收器。在多CPU的架构下,性能会比Serial高很多。
在这里插入图片描述

CMS:Concurrent Mark Sweep,一部分GC线程与用户线程并发执行,整个过程分为四个阶段
在这里插入图片描述

  1. 初始标记阶段:STW只标记出根对象直接引用的对象
  2. 并发标记:继续标记其它对象,与应用程序是并发执行的
  3. 重新标记:STW对并发执行阶段的对象进行重新标记
  4. 并发清除:并行,将产生的垃圾清除。清除过程中,应用程序又会不断的产生新的垃圾,叫做浮动垃圾。这些垃圾就要留到下一次GC过程中清除。

G1:Garbage First 垃圾优先

他的内存模型是实际不分代,但是逻辑上是分代的。在内存模型中,对于堆内存就不再分老年代和新生代,而是划分成一个一个的小内存块,叫做Regon。每个Region可以隶属于不同的年代。

GC分为四个阶段:

  1. 初始标记:标记出GCRoot直接引用的对象。STW
  2. 标记Region,通过Rset标记出上一个阶段标记的Region引用到的Old区Region
  3. 并发标记阶段:跟CMS差不多,只是遍历的范围不再是整个Old区,而是需要遍历第二部标记出来的Region
  4. 重新标记
  5. 垃圾清理:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。

CMS的核心算法就是三色标记

三色标记:将每个内存对象分成三种颜色,黑色:表示自己和成员变量都已经标记完毕,灰色:自己标记完了,但是成员变量还没有标记完成,白色:自己未标记完

CMS通过增量标记increment update 的方式来解决漏标的问题

六、如何进行JVM调优,JVM参数有哪些?怎么查看JAVA进程的JVM参数?如果一个JAVA程序每次运行一段时间后,就变得卡顿,你准备如何优化?

JVM调优主要就是通过定制JVM运行参数来提高JAVA应用程序的运行数据

JVM参数大致分为三类:

  1. 标注指令:-开头,这些是所有的HotSpot都支持的参数。可以用java -help打印出来
  2. 非标准指令:-X开头,这些指令通常是跟特定的HotSpot版本对应的, 可以用java -X打印出来
  3. 不稳定参数:-XX开头,
    java -XX:+PrintCommandLineFlags,查看当前命令的不稳定指令
    java -XX:+PrintFlagsinitial,查看所有不稳定指令的默认值
    java -XX:+PrintFlagsFinal,查看所有不稳定指令最终生效的实际值

MQ

一、MQ有什么用,有哪些具体的使用场景?

MQ:MessageQueue,消息队列。队列是一种FIFO先进先出的数据结构。消息由生产者发送到MQ进行排队,然后由消费者对消息进行处理。QQ、微信就是典型的MQ场景。

MQ的作用主要有三个方面:

  1. 异步
    例子:快递,快递员->菜鸟驿站<-客户
    作用:异步能提高系统的响应速度和吞吐量
  2. 解耦:
    作用:服务之间进行解耦,可以减少服务之间的影响,提高系统的稳定性和可扩展性。解耦之后可以实现数据分发,生产者发送一个消息后,可以由多个消费者来处理。
  3. 削峰:
    作用:以稳定的系统资源应对突发的流量冲击。

MQ的缺点:

  1. 系统的可用性降低:一旦MQ宕机,整个业务就会产生影响。高可用
  2. 系统的复杂度提高:引入MQ之后,数据链路就会变得复杂。如何保证消息不丢失?消息不会重复调用?怎么保证消息的顺序性?
  3. 数据一致性:A系统发消息,需要由B、C两个系统一同处理,如果B系统处理成功、C系统处理失败,这就会造成数据一致性的问题。

二、如何进行产品选型?

Kafka

优点:吞吐里大,性能非常好,集群高可用
缺点:会丢数据,功能比较单一。
使用场景:日志分析,大数据采集

RabbitMQ

优点:可靠性高,功能全面。
缺点:吞吐量比较低,消息积累会严重影响性能。erlang语言不好定制
使用场景:小规模场景。

RocketMQ

优点:高吞吐,高性能,高可用,功能非常全面
缺点:客户端只支持java

三、如何保证消息不丢失?

1、哪些环节会丢消息
在这里插入图片描述
2、怎么防止消息丢失

2.1生产者发送消息不丢失

kafka:消息发送+回调

RocketMQ:

  1. 消息发送+回调。
  2. 事务消息
    在这里插入图片描述

RabbitMQ:

  1. 消息发送+回调
  2. 手动事务:channel.txSelect()开启事务,channel.txComment()提交事务,channel.txRollback()回滚事务。这种方式对channel会产生阻塞,造成吞吐量下降。
  3. Pulisher Confirm。整个处理流程跟RocketMQ事务消息基本一样。

2.2MQ主从消息同步不丢失

RocketMQ:1、普通集群中,同步同步,异步同步。异步效率高,但是会丢消息,同步不会。2、Dledger集群-两阶段提交

RabbitMQ:

  • 普通集群:消息是分散存储的,节点之间不会主动进行消息同步,是有可能丢失消息的。
  • 镜像集群:镜像集群会在节点之间主动进行数据同步,这样数据安全性得到提高

Kafka:通常都是用在消息少量丢失的场景

2.3MQ消息存盘不丢失

RocketMQ:同步刷盘,异步刷盘

RabbitMQ:将队列配置成持久化队列

2.4消费者消费消息不丢失

RocketMQ:使用默认的方式消费就行,不要采用异步

RabbitMQ:autoCommit->手动提交offset

Kafka:手动提交offset

四、如何保证消息消费的幂等性?

其实就是要防止消费者重复消费消息的问题

RocketMQ:给每个消息分配了MessageID,这个MessageID就可以作为消费者判断幂等的依据。

五、如何保证消息的顺序?

全局有序和局部有序:MQ只需要保证局部有序,不需要保证全局有序。

在这里插入图片描述
生产者把一组有序的消息放到同一个队列中,而消费者一次消费整个队列中的消息。

RocketMQ中有完整的设计

RabbitMQ:要保证目标exchange只对应一个队列,并且一个队列只对应一个消费者

Kaf卡:生产者通过定制partion分配规则,将消息分配到同一个partition。Topic下只对应一个消费者

五、如何保证消息的高速读写?

零拷贝:kafka和RocketMQ都是通过零拷贝技术来优化文件读写

传统文件复制方式
在这里插入图片描述
零拷贝:有两种方式,mmap和tansfile

七、使用MQ如何让保证分布式事务的最终一致性?

分布式事务:业务相关的多个操作,保证他们同时成功或者同时失败。

最终一致性:与之对应的就是强一致性

MQ中要保护事务的最终一致性,就需要做到两点

  1. 生产者要保证100%的消息投递。事务消息机制
  2. 消费者这一端需要保证幂等消费。唯一ID+业务自己实现幂等

八、如何设计一个MQ?

  1. 实现一个单机的队列数据结构,高效,可以扩展
  2. 将单机队列扩展成为分布式队列。分布式集群管理
  3. 基于Topic定制消息路由策略。发送者路由策略,消费者与队列对应关系,消费路由策略
  4. 实现高效的网络通信。Netty、Http
  5. 规划日志文件,实现文件高效读写。零拷贝,顺序写。服务重启后,快速还要运行现场
  6. 定制高级功能,死信队列、延迟队列,事务消息。

缓存

一、为什么使用缓存?

为什么要用缓存?

在这里插入图片描述

1、高可用

mysql 数据库对于高并发来说天然支持不好,mysql 单机支撑到 2000QPS 也开始容易报警了。

所以若是系统高峰期一秒钟有1万个请求,那么一个 mysql 单机绝对会死掉。这个时候就只能上缓存,把很多数据放入缓存,别放入 mysql。缓存功能简单,说白了就是 key-value 式操作,单机支撑的并发量一秒可达几万十几万,单机承载并发量是 mysql 单机的几十倍。

缓存是走内存的,内存天然就支撑高并发。

2、高性能

假设这么个场景,有个操作,一个请求过来,耗时 600ms 操作 mysql查出来一个结果,但是这个结果可能接下来几个小时都不会变了,或者变了也可以不会立即反馈给用户。那么此时咋办?

将折腾 600ms 查出来的结果放入缓存里,一个 key 对应一个 value,下次查找时不经过 mysql,直接从缓存里通过一个 key 查出来一个 value,2ms 搞定,性能提升 300 倍。

所以对于一些需要复杂操作耗时查出来的结果,确定后面不怎么变化,但是有很多读请求,直接将查询出来的结果放在缓存中,后面直接读缓存就好。

二、什么是缓存穿透?缓存击穿? 缓存雪崩?

缓存穿透:缓存中查不到,数据库中也查不到

解决方案:1、对数据进行合法效验,2、将数据库中没有=查到结果的数据也写入缓存。这时要注意为了防止Redis被无用的Key占满,这一类缓存的有效期要设置的短一点。3、引入布隆过滤器。在访问Redis之前判断数据是否存在。布隆过滤器存在一定的误判率,并且,布隆过滤器只能加数据不能减数据。

在这里插入图片描述
缓存击穿:缓存中没有,数据库中有,一般是出现在key过期了的情况。他的问题在于,重新写入缓存需要一定的时间,如果是在高并发场景下,过多的请求就会瞬间写道DB上,给DB造成很大的压力

解决方案:1、设置这个热点缓存永不过期。这时要注意在value当中包含一个逻辑上的过期时间,然后另起一个线程,定期重建这些缓存。2、加载DB的时候,要防止并发。

缓存雪崩:缓存大面积 过期,导致请求都被转发到DB

解决方案:1、把缓存的失效时间分散开。例如,在原有的统一失效时间基础上增加一个随机值。2、对热点数据设置永不过期。

三、如何保证Redis与数据库的数据一致?

在这里插入图片描述

mysql

一 、MySQL有哪几种数据存储引擎?有什么区别?

在这里插入图片描述

二、什么是脏读、幻读、不可重复读?怎么处理?

脏读:在事务进行过程中,读到了其它事务未提交的数据

不可重复读:在一个事务过程中,多次查询的结果不一致

幻读:在一个事务过程中,用同样的操作查询数据,得到的记录数不相同

处理方式:加锁、事务隔离、MVCC

加锁:

  • 脏读:在修改时加排他锁,直到事务提交才释放 。读取时加共享锁,读完释放锁
  • 不可重复读:读数据时加共享锁,写数据时加排他锁
  • 幻读:加范围锁

三、事务的基本特性和隔离级别有哪些?

事务:表示多个数据操作组成一个完整的事务单元,这个事务内的所有数据操作要么同时成功,要么同时失败。

事务的特性:ACID

  • 原子性 Atomicity:事务是不可分割的,要么完全成功,要么完全失败
  • 一致性 Consistency:事务无论是完成还是失败,都必须保持事务内操作的一致性。当失败时,都要对前面的操作进行回滚,不管中途是否成功。也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  • 隔离性 Isolation:当多个事务操作一个数据的时候,为了防止数据损坏,需要将每个事务进行隔离,互补干扰。
  • 持久性(Durability): 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

MySQL当中有五种隔离级别

  • NONE: 不使用事务
  • READ UNCOMMIT:允许脏读
  • READ COMMIT:防止脏读,最常用的隔离级别
  • REPEATABLE READ: 防止脏读和不可重复读。mysql默认
  • SERIALZEBLE:事务串行,可以防止脏读、幻读,不可重复读

脏读(READ UNCOMMIT)是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

不可重复读(REPEATABLE READ)是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

幻读(READ COMMIT)是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

隔离级别越高,事务的安全性更高,但是事务的并行性能就会越低。

MySQL锁有哪些?什么是间隙锁?

从锁的粒度来区分
1、 行锁:加锁粒度小,但是加锁资源开销比较大。InnoDB支持

  • 共享锁:读锁,多个事务可以同时对同一个数据共享一把锁。持有锁的事务都可以访问数据,但是只能读不能修改 。select * * LOCK IN SHARE MODE
  • 排他锁: 写锁。只有一个事务能够获得排他锁,其他事务都不能获取该行的锁。InnoDB会对update/delete/insert语句自动添加排他锁。SELECT xxx FOR UPDATE
  • 自增锁: 通常是针对MySQL当中的自增字段,如果有事务回滚这种情况,数据会回滚,但是自增序列不会回滚

2、表锁:加锁粒度大,加锁资源开销比较小。MySAM和InnoDB都支持。

  • 表共享锁
  • 表排他锁
  • 意向锁:是innoDB自动添加的一种锁,不需要用户干预

3、全局锁:Flush table with read lock 加锁之后整个数据库实例都处于制度状态。所有的数据变更操作都会被挂起,一般用于全库备份

常见的锁算法:

1、记录锁:锁一条具体的数据
2、间隙锁:RR隔离级别下,会加间隙锁。锁一定的范围,而不锁具体的记录,是为了防止产生幻读
3、Next-key

五、mysql的索引是什么样的,聚簇索引和非聚簇索引又是什么?

二叉树:每个节点最多有两个节点,左边的子节点都比当前节点小,右边的子节点都比当前节点大。

AVL树:树种任意系欸但的两个子树的高度差最大为1

红黑树:每个节点都是红色或者黑色,根节点是黑色,每个叶子节点都是黑色的空节点,红色节点的父子节点都必须是黑色。从任一节点到其每个叶子节点的所有路径都包含相同的黑色节点

B-树:1、B-树的每一个非叶子节点的子节点个数都不会超过D(D是阶)2、所有的叶子节点都在同一层。3、所有节点关键字都是按照递增序列排序

B+树:1、非叶子节点不存储数据,只存储数据索引。 2、所有数据都存储在叶子节点当中。3、每个叶子节点都存有相邻叶子节点的指针。4、叶子节点按照本身关键字从小到大排序。

聚簇索引就是数据和索引是在一起的

myisam使用的是非聚簇索引,树的子节点上的data不是数据本身,而是数据存放的地址。InnoDB采用的是聚簇索引,树的叶子节点上的data就是数据本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

德玛西亚!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值