面试笔记(精简版)

2023年 10月 9日 131.1k 0

个人笔记

自我介绍

面试官你好,我是计算机工程学院的24届在校生,在大二确定了自己的方向,开始java方面的自学,在此期间,自学了Java开发语言,能够简单使用SSM框架进行开发,同时对中间件的使用有一定的了解,这是我的一个情况

实习收获

Docker和其他软件冲突

问题:MQ的Docker无法正常启动

排查:不安装奇安信可以启动,所以先确定问题在这个软件冲突上

查看MQ日志,发现在MQ在日志网络配置里面报错,定位到网络问题

 ERROR: network example_network not found

然后检查奇安信对网络方面做了哪些限制和保护措施,发现奇安信在安装后会限制添加网桥(网桥的工作原理类似于物理交换机,但它在软件层面上实现),导致docker无法正常启动

开发做过什么模块

参与过一个跨节点传输的系统,主要是节点之间的传输,一个节点可以发送给另外一个节点

有一个收集分发系统,一个节点对应一个收集分发系统,然后通过数据总线来传输消息,收集分发系统通过mq来将数据发送到数据总线,数据总线来将消息发送给对应的收集分发系统,然后对应的收集分发系统是通过mq来接收消息的

我负责过一个从mq接收消息的模块,做一些数据校验、然后和前端页面进行交互显示

然后还有节点的测试部署,后面也是参与了自研框架的开发

MQ 在多节点并发通信时的短路问题

两个方面:

  • 金蝶MQ在使用时,一个配置文件是debug模式,在使用MQ时会写入大量日志文件(占用大量磁盘空间,影响性能,导致MQ变得不稳定),导致MQ承载不住这么大的压力,造成消息丢失
  • 优化配置方面:使用的麒麟系统(基于Linux内核的一个国内发行版本)部署,然后设置的句柄数太少,导致MQ在连接网络、文件操作方面出问题,通过修改文件/etc/sysctl.conf 来增加句柄数的限制
  • 句柄数:是用于访问文件或其他资源的引用, 在Linux系统中,每个进程都有一定数量的可用句柄。如果句柄数不足,可能会导致系统无法正常处理文件、网络连接等资源,从而影响应用程序的运行。

    MQ消息丢失怎么处理

    • 首先考虑MQ的持久化机制,保证在MQ服务器宕机或者故障时造成的消息丢失
    • 消费者端ACK确认机制,消费者向消息队发送ACK来表示处理了消息。告诉消息队列系统可以将该消息从队列中删除
    • 使用死信队列,存储无法成功处理的消息的特殊队列,后面手动处理这些消息

    OpenRestry-Caffine-Redis分布式三级缓存框架

    三级缓存架构:

    本地内存缓存(Caffeine):一级缓存、位于应用程序的内存中,访问速度比较快,适用于频繁访问的数据,热点数据,但是保存在JVM容量有限,数据存储在本地,不支持分布式

    分布式内存缓存(Redis):位于多个应用程序实例之间共享的内存中,提供了更大的容量,适用于共享数据和跨实例的缓存,可以在一定程度上支持分布式环境,但仍然需要访问网络

    以及分布式缓存层(OpenResty+Nginx):处理全局缓存需求,通过CDN内容分发、分布式代理等方式,将数据从远程数据中心传递到本地服务器中

    实现思路:

    • 创建一个Spring Boot自动配置类,用于配置和初始化各个组件
    • 创建自定义注解(用于指定缓存的key、过期时间等信息),如@Cacheable,用于标识需要进行缓存操作的方法
    • 创建一个AOP切面,拦截自定义注解的方法调用。在切面中,执行缓存操作
    • 调用缓存框架处理,然后当写入缓存时,使用消息队列异步的来进行三级缓存之间的同步

    流程:首先,框架会在本地的Caffeine缓存中尝试查找请求所需的数据,如果没有命中就会从redis中去取,如果还没有命中就在分布式缓存层也就是分布式缓存服务器中查找(negix),如果找到了直接返回并存到上一级缓存中,没有找到就去数据库查询

    OpenRestry:基于Nginx的Web服务器,内置的Lua模块来扩展

    缓存不一致问题

    • 设置合理的过期策略(更新频繁的,设置较短的过期时间)
    • 延时双删策略:在写入数据前后都进行删除缓存,并且设置一个合理的超时时间(为了删缓存的时候去删除脏数据)
    • 异步更新缓存(基于订阅bin log日志的同步机制):中间件cancel对MySQL的binlog进行订阅,binlog相关的信息推送到Redis

    使用Alibaba Canal监听MySQL binlog日志并与MQ(消息队列)异步集成的操作

    • 安装并配置MySQL数据库,确保binlog已启用。
    • 安装并配置Canal服务器,配置Canal与MySQL的连接信息。
    • 引入Canal客户端依赖。
    • 编写Java代码,配置Canal客户端,设置与Canal服务器的连接信息。
    • 订阅MySQL数据库中的表,以便监听特定表的binlog事件。
     CanalConnector connector = CanalConnectors.newSingleConnector(
         new InetSocketAddress("canal-server-host", 11111),
         "example", "", ""
     );
     connector.connect();
     connector.subscribe("your-database.your-table");
     ​
    
    • 使用Canal客户端监听MySQL binlog事件,当表中的数据发生变化时,Canal会捕获binlog事件。
    • 将捕获的binlog事件转换为业务对象,并进行相应的处理
     while (true) {
         Message message = connector.getWithoutAck(batchSize); // 获取binlog事件
         long batchId = message.getId();
         try {
             for (CanalEntry.Entry entry : message.getEntries()) {
                 if (entry.getEntryType() == EntryType.ROWDATA) {
                     // 处理binlog事件,将其转换为业务对象
                     CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                     // 处理业务逻辑
                 }
             }
             connector.ack(batchId); // 确认处理完成
         } catch (Exception e) {
             connector.rollback(batchId); // 处理失败时回滚
         }
     }
    

    项目

    做的是一个子项目,爱心雨伞,我主要是负责后端对雨伞下单接口的开发和一些优化

    超卖问题

    超卖问题主要发生在减库存的时候,在更新操作的时候(update语句中)添加一个判断,看商品此时是否还有库存,如果没有库存,就不更新订单

    还有一种情况是一个用户同时发了两个请求,假如库存充足,且没有订单生成,那么就会减两次库存

    解决办法是把用户和商品的id建立成唯一索引

    img

    判断重复抢购时,每次都是从数据库查询,效率低下

    把用户的订单信息,保存到redis中,可以更高效的判断是否重复抢购

    接口限流

    自定义注解,里面保存秒和最大访问次数

    写一个拦截器,拿到该方法的注解保存的最大访问次数和时间限制,然后在Redis中查看当前用户id下的count是否为0

    Redis存的是用户请求的url,和访问次数

    如果count为0,就+1,如果count不为0,说明这个用户访问过了,看看访问次数有没有超过最大的访问次数,如果超过了就跳转到访问太频繁的一个页面,返回false

    img

    登录功能优化

    验证码的优化

    之前验证码是保存在session里面的

    需要频繁的访问和刷新,对性能要求比较高

    验证码不需要永久保存,可以在Redis中设置一个过期时间

    使用Redis保存用户的登录凭证

    相当于数据库

    因为每次登录的时候,都要去数据库查询用户的登录凭证

    使用Redis来缓存登录的用户信息

    每次根据userid去数据库查询的时候,都要访问数据库

    这个要设置过期时间

    预减库存

    • 系统初始化,把雨伞库存数量加载到Redis,在执行秒杀的时候不去数据库查询有多少库存,直接走Redis(实现InitializingBean接口,重写afterPropertiesSet接口,在系统启动的时候把库存加载到redis)
    • 收到请求,Redis预减库存,库存不足,直接返回
    • 成功之后,把这个秒杀任务发送给MQ,请求入队,立即返回排队中(异步下单),返回0
    • 请求出队,生成订单,减少库存,把订单写入Redis中
    • 客户端轮询,判断是否秒杀成功

    java基础

    编译型语言和解释型语言

    image-20230908023130082

    java集合

    主要分两大接口,Collection单列接口,和Map双列接口

    image-20230908023317631

    CopyOnWriteArrayList

    概念:java中的一种并发集合类,采用写入时复制的原理

    写入时复制机制:当对其进行写入操作时,会创建一个数组的副本,然后在副本上执行写操作,而原始数组不会改变;写入操作完成后,CopyOnWriteArrayList 会将新的数组副本替换(原子性)原始数组

    这个机制使得读操作可以在不受写操作影响的情况下进行,从而实现了读写分离,确保了读操作的线程安全性,但是可能导致内存开销和一定的性能损失

    HashMap

    底层数据结构:jdk1.8之前,哈希表底层是数组(16长度)+链表,之后数组+链表+红黑树

    红黑树:自平衡二叉搜索树

    为了保证在插入和删除等操作后自动保持平衡,从而防止二叉搜索树在极端情况下退化为链表

    ConcurrentHashMap

    概念:Java 中的一个线程安全的哈希表实现

    线程安全保证:分段锁机制

    分段锁机制:它将数据分成多个段,每个段拥有自己的锁。这使得多个线程可以并发地操作不同段的数据

    枚举怎么防止反射破坏单例的

    反射首先通过class的getDeclaredConstructor()获取到反射对象的构造器,然后通过newInstance()调用其构造方法获取对象

    而newInstance方法的一个判断条件是如果是枚举类型,就会抛出异常

    深克隆和浅克隆的区别

    浅克隆:拷贝对象和原始对象的引用类型引用同一个对象。

    深克隆:拷贝对象和原始对象的引用类型引用不同对象。深拷贝是将对象及值复制过来

    三种IO模型

    • BIO(Blocking I/O):线程执行I/O操作时,线程会等待I/O操作完成后才能继续执行下一步,为每一个IO分配一个线程,如果有大量连接会导致占用大量资源,场景:简单的、低并发
    • NIO(Non-blocking I/O):NIO模型引入了选择器和通道的概念,选择器来监视各个通道,不用为每一个连接建立线程;场景:高并发网络应用,比如Web服务器、聊天服务器、网络代理(多个客户端和服务器之间的连接)
    • AIO(异步IO模型):引入了异步通道和异步操作,回调机制来处理I/O操作的结果;场景: 大量并发I/O操作,例如高性能网络服务器、文件服务器和需要大规模文件读写的应用程序

    JDK8新特性

    • Lambda 表达式: 用于函数式编程和匿名类的创建,使用:(参数列表) -> {方法体};
    •  public class LambdaThreadExample {
           public static void main(String[] args) {
               // 使用Lambda表达式创建线程
               Thread thread1 = new Thread(() -> {
                   for (int i = 0; i < 5; i++) {
                       System.out.println("Thread 1: " + i);
                   }
               });
       ​
               // 启动线程
               thread1.start();
       ​
               // 主线程继续执行其他任务
               for (int i = 0; i < 5; i++) {
                   System.out.println("Main Thread: " + i);
               }
           }
       }
       ​
      
    • Stream流,用于处理集合数据的强大工具,它允许你以声明性的方式执行各种操作
    • 默认方法和静态方法: 接口可以定义默认实现的方法,这样在接口的扩展时可以避免破坏已有的实现类

    java并发

    线程的生命周期?

    • new新建:线程对象刚被创建,但还没有start方法
    • Runnable运行态:当线程调用了start方法正在等待运行或者正在执行时
    • Blocked阻塞:就是一个线程想获取一个锁,但是这个锁被其他对象持有
    • Waiting无限等待:一个线程等待另一个线程执行一个(唤醒)动作
    • Teminated被终止:run方法正常退出或者因为某些原因退出时

    线程池

    使用:

    • 使用Executors工厂类
    • 使用 ThreadPoolExecutor 类
     ExecutorService executor = Executors.newFixedThreadPool(nThreads);
    
     ThreadPoolExecutor executor = new ThreadPoolExecutor(
         corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);
    
     executor.shutdown(); // 此方法会等待已提交的任务执行完毕后关闭线程池
     // 或
     executor.shutdownNow(); // 立即关闭线程池,可能会中断正在执行的任务
    

    核心参数:

    • 核心线程数:线程池会一直维护一定数量线程在线程池中
    • 最大线程数:表示线程池中被允许的最大线程数
    • 空闲线程的存活时间:当线程池中的线程大于核心线程数且线程空闲之后,在指定时间,这些空闲线程将被销毁
    • 阻塞队列:用于存放等待执行的任务的队列。线程池中的任务在执行前会被放入队列中。不同的阻塞队列实现会影响线程池的行为(基于链表的,基于数组的,基于优先级的)
    • 拒绝策略:表示任务过多时无法及时处理时采取的策略

    怎么判断线程空闲?

    使用ThreadPoolExecutorgetActiveCount方法

    使用ThreadPoolExecutorgetQueue方法

    并发三大问题

    • 原子性
    • 可见性:一个线程对共享变量的修改能够被其他线程及时看到,由于多个线程可能有自己的缓存,一个线程的修改可能并不会立即被其他线程看到
    • 有序性:程序的执行顺序与代码的编写顺序一致,在多线程环境下,指令重排可能导致线程间的操作顺序不一致

    synchronized关键字

    synchronized锁升级过程:

    主要有偏向锁、轻量级锁、重量级锁

    偏向锁是指在没有竞争的情况下,锁会偏向于第一个获取锁的线程(会在对象头中存储当前线程的ID是被偏向的线程),避免了每次获取锁都需要进行CAS操作的开销

    轻量级锁是指在竞争不激烈的情况下,锁会使用CAS操作(相当于自旋)来进行快速的加锁和解锁,避免了重量级锁的开销;

    重量级锁是指在竞争激烈的情况下,锁会使用操作系统提供的互斥量来进行加锁和解锁,保证了线程安全性,但开销较大。

    锁的底层原理:每个 Java 对象都有一个与之关联的监视器锁,一个线程已经持有了某个对象的监视器锁,那么其他线程就不能同时持有同一个对象的监视器锁

    volatile关键字

    概念:volatile 是 Java 中的关键字,用于修饰变量,用来确保变量在多线程环境下的可见性和禁止指令重排

    volitale是Java虚拟机提供的一种轻量级的同步机制 想要线程安全必须保证原子性,可见性,有序性。而volatile只能保证可见性和有序性

    应用场景:

    • 计数器、标记变量等: 当一个变量在多线程环境下被读取和写入,并且它的修改频率较低,适合使用 volatile 来保证变量的可见性。
    • 双重检查锁定(Double-Checked Locking)优化: 在单例模式中,使用双重检查锁定以避免不必要的同步开销。在双重检查锁定中,需要使用 volatile 关键字来确保对单例实例的初始化在多线程环境下的正确性。
    • 状态标志: 当一个变量在多个线程之间用作状态标志,用于控制线程的启动、停止或切换时,可以使用 volatile 来确保状态的变更对其他线程是可见的。

    ReentrantLock

    概念:是Java中的一个锁实现,它是一个可重入锁、超时锁、可中断;比synchronized更灵活和强大

    ReentrantLock的Condition接口:使用这个接口可以创建多个条件对象,通过await和signal实现线程协作

     ReentrantLock lock = new ReentrantLock();
     Condition condition = lock.newCondition();
     ​
     ​
     // 线程等待条件满足
     lock.lock();
     try {
         while (!conditionSatisfied()) {
             condition.await();
         }
         // 执行线程的逻辑
     } catch (InterruptedException e) {
         Thread.currentThread().interrupt();
     } finally {
         lock.unlock();
     }
     ​
     // 线程通知条件满足
     lock.lock();
     try {
         updateState();
         condition.signal();
     } finally {
         lock.unlock();
     }
    

    CAS

    Compare and Swap比较并交换,是一种并发控制机制,用于实现多线程同步,可以被视为一种自旋锁的实现方式

    CAS内部有三个操作数:内存位置、期望值、新值;只有内存位置的期望值等于旧值的时候,才会将内存位置更新为新值,如果旧值等于期望值,说明没有其他线程干扰

    ABA问题:使用带有版本号的CAS或者加时间戳

    ThreadLocal

    概念:可以理解为线程本地变量

    底层原理: ThreadLocal 实例在一个线程内部都有一个 ThreadLocalMap,这个 Map 以 ThreadLocal 变量为键,存储了对应线程的变量副本

    内存泄漏问题:比如创建了一个ThreadLocal 对象,里面存储一个Obj对象;当线程结束后被销毁,与这个线程关联的obj变量并不会被立即销毁

    解决:及时remove掉; try-with-resources 语法自动处理

    死锁

    多个线程同时被阻塞,它们都占有在和等待对方资源被释放,因为资源竞争而产生互相等待的过程,比如A线程持有对象B,B线程持有对象A,他们都在等着对方释放资源,而相互等待

    AQS

    AbstractQueuedSynchronizer的简称:Java并发包中的核心组件之一,lock锁的底层实现,用于实现同步器(如锁和信号量等)的基础框架

    思想:使用了int类型的互斥变量state来记录锁竞争的一个状态,使用了CAS机制保证了state更新的原子性,未获得锁线程按先后顺序放入一个等待队列中,采用FIFO(先进先出)的方式管理等待线程。这有助于实现公平性

    内部提供了一个抽象的队列同步器,允许开发者构建自定义的同步器,用于解决多线程并发访问共享资源的问题

    JMM

    概念:java内存模型,规定了线程与主内存之间的交互方式

    一个线程在修改共享变量后,在退出代码块之前会执行写屏障,将修改刷回主存

    以前:

    image-20230908145507924

    现在:

    image-20230908145638219

    MySQL

    DateTime和TimeStamp的区别

    Datetime占用8个字节,字面上的时间,1970-2038年

    timestamp占用4个字节,存储的是UTC时间,能自动转换,1000-9999年

    MySQL的架构

    主要分为server层、存储引擎层

    server层:主要负责建立连接,分析和执行SQL语句

    存储引擎层:主要负责数据的存储和提取

    存储引擎相关

    MySQL的存储引擎有哪些?

    MyISAM:不支持事务和行级锁,适合读多写少

    Innodb:默认存储引擎,它支持事务、行级锁和外键约束,支持ACID

    Memory:存储在内存中

    MVCC的实现

    InnoDB中的每一行数据都包含一个隐藏的列,用于存储版本号或时间戳

    当事务对数据进行修改时,会为该数据行创建一个新的版本,并将新版本的版本号或时间戳更新为当前事务的时间戳。

    每个事务在开始时会被分配一个唯一的读取视图。这个读取视图定义了事务能够看到的数据版本范围。

    快照读: 在MVCC中,读取操作分为两种类型:快照读和当前读。快照读会读取数据行的历史版本,以保证数据的一致性。当事务执行快照读时,它会根据自己的读取视图来选择合适的数据版本。快照读会读取事务开始时的数据状态,不影响其他事务

    当前读: 当前读操作用于锁定数据行,以用于更新操作或锁定行。当前读会获取最新的数据版本,以确保其他事务无法并发修改相同的数据行。当前读会读取最新的数据,可能影响其他事务

    索引相关

    回表

    使用非聚簇索引(如普通索引)进行查询时,由于非聚簇索引不包含实际的数据行,数据库引擎需要进一步在主键索引上进行一次查找以获取完整的数据行

    什么是索引?

    是一种在表的列上创建的数据结构,用于加速数据库中的数据检索操作

    常见的MySQL索引有Hash索引和B+树索引

    索引中除了存储列的值,还存储着一个指向在行数据的索引

    MySQL的索引有哪些?

  • B+树索引: 这是MySQL中最常用的索引类型,用于加速查询和排序操作。包括以下几种具体类型:

  • 主键索引(Primary Key Index):唯一标识每行数据的索引,也是表的默认聚簇索引。
  • 唯一索引(Unique Index):保证索引列的值唯一。
  • 普通索引(Non-Unique Index):用于加速查询,允许索引列中的值重复。
  • 外键索引(Foreign Key Index):用于维护外键关系,通常是对外键列创建的索引。
  • 哈希索引(Hash Index): 使用哈希函数将索引列的值映射到索引表中的特定位置,通常在内存表中使用。

  • 前缀索引(Prefix Index): 对索引列的前几个字符进行索引,适用于长文本或大数据类型的列。

  • 什么情况下需要索引?

    • 表的主键、外键必须有索引
    • 数据量超过300有索引
    • 频繁作为查询条件的列
    • 经常与其他表进行连接的表,在连接字段上建立索引

    什么情况下不能建立索引?

    • 频繁的写入操作不适合创建索引
    • 数据表中包含大量重复的值
    • 高度重复的列:性别
    • 数据量小的表

    使用索引代价

    • 索引会占用空间。你的表越大,索引占用的空间越大。
    • 在更新操作有性能损失。当你在表中添加、删除或者更新行数据的时候, 在索引中也会有相同的操作。

    索引失效

    • 数据量比较少的时候,查询优化器可能选择不走索引
    • 模糊查询以%开头,它是以B+树来构建的,当使用 % 开头的模糊查询时,数据库无法使用索引的前缀匹配能力
    • 索引列上使用了函数
    • or关键字涉及到不同的列,或者or左右有函数表达式时
    • 索引列上使用了not、is null
    • 联合索引不满足最左前缀匹配法则

    为什么MySQL不用二叉树作为索引?

    二叉树存储大量数据的话,高度太高,B树的话,有多个分支,树的高度较低

    B+树只在叶子节点存放数据,非叶子节点只存放关键字,然后相同的空间下,可以顺序存储更多的关键字,

    而且B+的叶子节点之间用双向链表连接,所以扫描全部数据只需要扫描一遍叶子节点

    而且B树支持范围查询(可以存储多个关键字,而且这些关键字是有序的),有自平衡的特征,保证查询的时间稳定性

    为什么InnoDB表必须建主键?

    InnoDB使用B+树作为存储结构,是聚簇索引,是基于表的主键来进行数据存储的

    InnnoDB如果没有主键怎么办?

    会选择一个唯一的非空索引代替,如果没有这样的索引,InnoDB会隐式的定义一个主键来作为聚簇索引

    (也就是我们常说的RowId)

    为什么推荐使用整型的自增主键?

    如果主键不是自增id,排序不方便,而且保存的时候物理存储会不断进行页分裂

    可以保证唯一性,而且不受业务逻辑的影响,保证数据库的完整性和一致性

    有联合索引 (a, b, c) 查询条件为 (a, c) 会不会走索引

    不满足最左匹配原则,但是会走部分索引

    索引下堆

    是为了减少二级索引的回表操作,将之前server层负责的事情,交给存储引擎来处理

    比如查询年龄大于30的用户

    传统的做法:使用该索引找到所有年龄大于 30 岁的用户的主键,然后再通过主键索引获取完整的用户信息

    使用索引下推:数据库引擎可以在使用年龄索引时,直接将符合条件的索引项返回,减少了回表的开销

    事务和锁相关

    Mysql的事务是什么?

    事务是一组逻辑相同的操作组成的最小执行单元,事务和DML语句(插入、增加、删除)有关

    在mysql中,事务是自动提交的

    事务的四大特性?

    • 原子性:这些逻辑相同的操作,要么全做,要么全不做,靠undo log来保证(异常或执行失败后进行回滚)
    • 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏
    • 隔离性:同一时间,只允许一个事务请求数据,不同的事务之间没有干扰,MVCC机制来保证隔离性
    • 持久性:事务完成后对数据库的更新是持久的,靠redo log来保证(保证当MySQL宕机或停电后,可以通过redo log最终将数据保存至磁盘中)

    并发可能产生的问题

    脏读:一个事务读取了另一个事务没有提交的数据

    不可重复读:一个事务两次读到的数据不一致,一个事务中由于其他事务的修改和删除导致两次返回的结果不同

    幻读:同一个事务中有其他的事务进行了插入和删除,导致数据不一致

    事务的隔离级别?

    读未提交:事务可以读取到另一个事务没有提交的数据,最低级的隔离级别,会导致脏读、不可重复读、幻读

    读已提交:事务只能读取到另一个事务提交的数据,可以避免脏读的问题

    可重复读:事务重复读到的数据一致,可以避免脏读和不可重复读,没有完全解决幻读,在快照的情况下解决了幻读,如果是for update的情况下是没有解决幻读的

    串行化:最高的隔离级别,完全按照事务的顺序执行,避免所有的并发问题,但是性能下降

    InnoDB的行锁应该注意什么?

    InnoDB只有在通过索引条件检索数据时才使用行级锁,否则使用表锁

    InnoDB什么时候使用表锁?

    事务需要更新大部分数据并且表比较大

    事务涉及到多个表,比较复杂,可能引起死锁

    Mysql中的意向锁有什么意义?

    事务A对表的某一行申请了写锁,事务B申请对表的写锁

    事务B在申请表锁之前得先知道表的所有行都没有锁,有了意向锁之后,事务B只需进行一次判断即可,不需要扫描所有的行

    日志相关

    两阶段提交

    mysql事务进行提交的时候,需要同时完成redo log和bin log的日志写入

    redo log是事务日志、bin log是数据库变更的逻辑,这是两个独立写入磁盘的操作

    是为了保证两个日志内容的一致性,需要用到两阶段提交

    第一阶段是prepare阶段,mysql会把事务操作记录到redo log中并标记为prepare状态

    第二阶段是commit阶段,事务提交时,mysql会把事务操作记录到bin log中,然后把redo log中的日志设置为commit状态

    所以Innndb在写入redo日志中并不是一次性写完的

    如果在写入redo log之前崩溃,那么redo log和bin log都没有数据

    如果在写入redo log prepare阶段之后立马崩溃,之后会在崩溃恢复时,由于redo log中没有标记为commit,于是拿着redo log中的事务id去bin log中查找肯定是找不到的,这时候就执行回滚操作

    如果在写入bin log后立马崩溃,那么可以在redo log中拿到事务id去bin log中找到记录,然后重新提交数据

    慢查询日志

    位置:在my.ini里面记录了日志的位置

    可以看到哪些SQL语句的执行时间比较慢

    undo redo bin log

  • Undo(回滚日志):当一个事务进行修改操作时,将原始数据的备份(快照)记录到undo日志中,主要来回滚的,保证了原子性
  • Redo(事务日志): 记录事务的修改记录,来保证事务的持久性、一致性,防止崩溃后数据丢失的情况
  • bin log:数据库变更的逻辑日志
  • MySQL的多表查询的时候大表和小表谁该在前面?

    应该将小表放在前面,大表放在后面。

    这是因为MySQL在执行多表查询时,会先读取小表的数据,然后再根据小表的数据去匹配大表的数据,这样可以减少查询的数据量,提高查询效率。同时,还可以利用小表的索引来加速查询。

    SQL优化

    首先看一下索引是不是失效了,查看SQL的EXPLAIN执行计划,看看走不走索引

    看看查询中是不是大量用到了子查询,可以建立冗余字段将子查询改成关联查询

    使用limit只返回必要的行和列

    Redis

    Redis缓存问题

    穿透:缓存和数据库中都没有的数据

    击穿:缓存中没有,数据库里有

    雪崩:大量key同时失效

    Redis实现分布式锁

    Redis 中如何确定一个 key 是热点 key

    • 使用 INFO keyspace 命令,查看key的详细信息(访问次数、过期时间)
    • 使用一个计数器统计访问频率

    Redis和MySQL数据一致性

    延时双删策略:

    在写入数据前后都进行删除缓存,并且设置一个合理的超时时间(为了删缓存的时候去删除脏数据)

    异步更新缓存(基于订阅bin log日志的同步机制):

    使用中间件cancel对MySQL的binlog进行订阅,binlog相关的信息推送到Redis,Redis根据binlog中的记录对Redis进行更新

    热点key优化

    热点key:访问频率较高的key

    解决:服务器缓存+备份热点key(将key+随机数随机分配到Redis其他节点中)

    Redis的Zset为什么使用跳表而不是使用B+树

    • B+树是多叉平衡搜索树,只要3层左右就能存放2kw左右的数据,同样情况下跳表则需要24层左右,高度对应磁盘IO的话,B+的性能会比跳表好,所以MySQL选择了B+树
    • Redis的读写全在内存中,不涉及磁盘的IO,同时跳表实现简单,减少了旋转树的开销,因此Redis使用跳表来实现Zset,而不是树结构

    Redis集群模式

    Redis集群之间复制

    image-20230908190603577

    Spring

    IOC加载过程

    IOC容器的加载开始于Spring应用程序启动时

    Bean对象的定义和实例化:

    • 首先配置文件被BeanDefinitionReader加载,产生一个beanDefinition放到BeanFactory(相当于一个spring容器)里面
    • 然后beanDefinition现在就只存了Bean的定义信息(id、对应的类等),一些后置处理器可以对beanDefinition进行扩展
    • 拓展完之后,生成一个完整的beanDefinition对象,根据beanDefinition对象创建对应的Bean(实例化)

    依赖注入:IOC容器会检查Bean之间的依赖关系,并将依赖的Bean注入到需要它们的Bean中。这个过程可以通过构造函数注入、Setter方法注入或字段注入等方式来完成

    Bean初始化--->使用中---->销毁,开始进行Bean的生命周期

    Bean的生命周期

    实例化---->初始化--->使用中---->销毁

    实例化:实例化前置、Spring容器根据beanDefinition对象创建对应的Bean实例、实例化后置

    初始化:属性赋值、初始化前置、执行初始化方法、初始化后置

    使用中

    销毁:单例的话--走单例模式的销毁流程;原型模式返回Bean给用户,剩下的声明周期由用户控制

    Bean的原型模式

    与默认的单例模式(Singleton)作用域相对应。当将Bean配置为原型模式时,容器每次请求该Bean时都会创建一个新的实例,而不是返回单例对象的引用

    原型Bean的实例不受容器管理

    Spring中IOC的循环依赖问题

    是指两个或多个Bean之间形成了相互依赖的关系,导致它们无法正常初始化

    Spring是使用三级缓存来解决循环依赖问题的:

    singletonObjects、earlySingletonObjects 和 singletonFactories

    singletonObjects:用于存储已经完全初始化的单例Bean。

    earlySingletonObjects:被创建但尚未完全初始化的Bean。

    singletonFactories:这个缓存用于存储用于创建Bean的工厂对象

    过程:比如A依赖于B,B依赖于A,A首先创建Bean,依次查询三级缓存,没有就createBean并放到三级缓存中,然后进行属性装配发现依赖于B,就去创建B实例,创建之前先依次查询三级缓存有没有,没有直接在创建B实例并放到三级缓存中,然后B进行属性装配,发现需要A实例,依次查询三级缓存发现三级缓存里面存在A,就把A放到二级缓存中,并返回A的早期引用,主这样就完成了B的装配,然后接着就完成了A的装配

    IOC源码分析

    Spring提供了很多容器,其中BeanFactory是顶层容器(根容器),不能被实例化,它定义类IOC容器必须遵从的一套原则,具体的容器可以增加额外的功能,比如我们常用的ApplicationContext的实现

    public class MyApp {
        public static void main(String[] args) {
            // 创建IOC容器
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            
            // 从容器中获取Bean
            MyService myService = context.getBean("myService", MyService.class);
            
            // 使用Bean
            myService.doSomething();
        }
    }
    

    AOP分析

    面向切面编程、通过将切面并将其与核心代码分离,来达到动态增强功能的效果,从而提高了代码的可维护性和可重用性

    Spring中的事务

    包含两种:声明式事务、编程式事务

    在方法上+@Transation声明式事务,如果事务里面存在比较耗时的行为,容易引发长事务问题,长事务会带来锁的竞争影响性能,也会导致数据库的连接池消耗完

    Spring中常用的注解

    • @RequestMapping:用于定义处理Web请求的方法,通常用在控制器类的方法上。
    • @Controller:与@Component功能相同,通常用于标记控制器层(Controller)的类,用于处理Web请求
    • @Autowired:用于自动装配Bean的依赖。可以用在构造函数、Setter方法、字段上等地方
    • @Value:用于注入属性值,可以用于属性、构造函数参数、Setter方法参数等地方

    Spring Boot自动装配原理

    Rabbit MQ

    Rabbit MQ优点

    可以用来解耦、异步、削峰处理

    系统的可用性降低:系统引用的外部依赖越多、消息丢失的问题

    RabbitMq的组成部分及流程

    组成部分:

    1、Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue

    2、Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过滤

    3、Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方

    4、producer:消息生产者,即生产方客户端,生产方客户端将消息发送到Mq

    5、Comuser:消息消费者,即消息方客户端,接受Mq转发的请求

    流程:生产者首先与Broker建立tcp连接和通道,然后生产者发送消息给Broker然后由交换机转发消息到指定的队列

    Rabbit MQ的工作模式

    • work Queues(工作队列):多个消费者重复消费一个队列中的消息
    • Publish/Subscribe 发布订阅:工作队列的进阶版,增加了交换机,可以有多个队列
    • Routing 路由:每个队列可以设置一个或多个Routingkey,交换机根据路由请求转发
    • topics 通配符:与routing类似,但不同的是topics使用的是通配符匹配,其中*匹配一个单词,#匹配多个或者0个单词

    如何保证MQ不重复消费(重点)

    • 消费者处理消息保证幂等性,基于数据库的唯一主键进行约束,消费完数据,insert一条数据,如果重复消费主键冲突
    • 生产者将通道channel设置成确认模式,每条消息生成唯一的id,若发送成功,会发送个确认给生产者,若失败,也会发送 个Nack给生产者。Confirm模式最大的好处在于它是异步的
    • MQ会将消息持久化磁盘,宕机重启可以恢复消息

    消息怎么传输的

    由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接上的虚拟连接,且每条 TCP 连接上的信道数量没有限制。

    如何保证RabbitMQ消息的顺序性

    拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;

    或者一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

    MQ接收到的消息如果不消费会一直保存吗?

    会根据保留策略来决定

    消费者消费速度不均衡的问题

    • 优化消费者的业务逻辑,存在性能瓶颈,可能业务比较复杂
    • 使用异步处理+批量消费
    • 硬件层面优化

    Docker

    概念:容器化技术,将应用程序和依赖封装在独立的容器中,解决了程序的可移植性和一致性

    其他概念:

    • Docker容器:轻量的执行环境
    • Docker镜像:只读模板,包含了运行容器所需的所有信息

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论