大厂面试题分享第一期

Redis持久化方式

Redis提供了两种主要的持久化机制,分别是AOF(Append-Only File)和RDB(Redis Database)

  • AOF日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里
  • RDB快照:将某一个时刻的内存数据,以二进制的方式写入磁盘
  1. AOF持久化:是将Redis的操作命令追加到一个只追加文件中。通过记录所有的写操作命令,AOF文件可以重建整个数据集。AOF持久化适用于数据的持久性和故障恢复。

这里以 [set name xiaolin」命令作为例子,Redis 执行了这条命令后,记录在 AOF 日志里的内容如下
在这里插入图片描述
Redis 提供了3种写回硬盘的策略,在 Redis.conf配置文件中的 appendfsync 配置项可以有以下3种参数可填:

  • Always,这个单词的意思是「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘:
  • Everysec,这个单词的意思是「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
  • No,意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也,就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。在这里插入图片描述
  1. RDB持久化:是将Redis的内存数据以快照的方式写入磁盘文件。该机制会在指定的时间间隔或者达到一定的数据变化量时,将当前数据库的数据集合保存到磁盘上的一个二进制文件中。RDB持久化适用于数据备份和恢复,以及冷启动时快速加载数据。

AOF优缺点

优点:

  • 提供了更好的数据安全性,因为它默认每接受到一个写命令就会追加到文件末尾,即使Redis服务器宕机,也只会丢失最后一次写入前的数据;
  • AOF支持多种同步策略(如everysec、always等),可以根据需要调整数据安全性和性能之间的平衡。同时,AOF文件在Redis启动时可以通过重写机制优化,减少文件体积,加快恢复速度。

缺点:
因为记录了每一个写操作,所以AOF文件通常比RDB文件更大,消耗更多的磁盘空间。并且,频繁的磁盘IO操作(尤其是同步策略设置为always时)可能会对Redis的写入性能造成一定影响。

RDB优缺点

优点:

  • RDB通过快照的形式保存某一时刻的数据状态,文件体积小,备份和恢复的速度非常快。
  • RDB是在主线程之外通过fork子进程来进行的,不会阻塞服务器处理命令请求,对Redis服务的性能影响较小。最后,由于是定期快照,RDB文件通常比AOF文件小得多。

缺点:

  • RDB方式在两次快照之间,如果Redis服务器发生故障,这段时间的数据将会丢失。并且,如果在RDB创建快照到恢复期间有写操作,恢复后的数据可能与故障前的数据不完全一致

如何保证Redis和Myql的一致性

如何保证Redis和Myql的一致性

索引下推

在MySQL5.6之前,当查询使用到复合索引时,MySQL会先根据索引的最左前缀原则,在索引上查找到满足条件的记录的主键或行指针,然后再根据这些主键或行指针到数据表中查询完整的行记录。之后,MySQL再根据WHERE子句中的其他条件对这些行进行过滤。这种方式可能导致大量的数据行被检索出来,但实际上只有很少的行满足WHERE子句中的所有条件。

从MySQL5.6开始引入的一个特性,索引下推通过减少回表的次数来提高数据库的查询效率;

注意:索引下推是为了减少回表而发明的。

索引下推的产生一定围绕着回表,没有回表那就没必要产生索引下推,因为上面也说了索引下推的目的就是减少回表,而不是避免回表。(题外话:避免回表使用索引覆盖——建立覆盖索引)

为了解决这个问题,MySQL 5.6引入了索引下推优化。

索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询。
需求: 建立了(name,age)组合索引,查询users表中 “名字第一个字是张,年龄为10岁的所有记录”。

SELECT * FROM users WHERE user_name LIKE '张%' AND user_age = 10;

根据最左前缀法则,该语句在搜索索引树的时候,只能匹配到名字第一个字是‘张’的记录,接下来是怎么处理的呢?当然就是从该记录开始,逐个回表,到主键索引上找出相应的记录,再比对 age 这个字段的值是否符合。

图1: 在 (name,age) 索引里面特意去掉了 age 的值,这个过程 InnoDB 并不会去看 age 的值,只是按顺序把“name 第一个字是’张’”的记录一条条取出来回表。因此,需要回表 4 次

image.png

MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表次数。

图2: InnoDB 在 (name,age) 索引内部就判断了 age 是否等于 10,对于不等于 10 的记录,直接判断并跳过,减少回表次数.
image.png
总结
如果没有索引下推优化(或称ICP优化),当进行索引查询时,首先根据索引来查找记录,然后再根据where条件来过滤记录;

在支持ICP优化后,MySQL会在取出索引的同时,判断是否可以进行where条件过滤再进行索引查询,也就是说提前执行where的部分过滤操作,在某些场景下,可以大大减少回表次数,从而提升整体性能。

输入url到浏览器发生了什么

  1. 用户输入URL(Uniform Resource Locator):用户在浏览器的地址栏输入网址,例如:https://www.example.com。
  2. DNS解析:浏览器将解析输入的URL,首先检查其是否符合有效URL的规范。接下来,浏览器会通过DNS(Domain Name System)将域名解析为对应的IP地址。DNS解析过程可能涉及本地缓存、本地域名服务器、根域名服务器、顶级域名服务器和权威域名服务器。
  3. 获取MAC地址:当浏览器得到IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。

通过将 IP 地址与本机的子网掩码相结合,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 APR 协议获取到目的主机的MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代头转发,此时同样可以通过 ARP 协议来获取网关的 MAC地址,此时目的主机的 MAC 地址应该为网关的地址

  1. 建立TCP连接:在获取到目标服务器的IP地址后,浏览器会向该服务器发送一个TCP连接请求。这个过程通常包括“三次握手”。
  2. 发送HTTP请求:TCP连接建立后,浏览器会通过这个连接向服务器发送一个HTTP(Hypertext Transfer Protocol)请求。请求中包含了请求方法(例如:GET或POST)、请求的资源路径、HTTP版本、请求头(包含浏览器信息、语言、编码等信息)等。
  3. 服务器处理请求:服务器接收到浏览器的请求后,会根据请求的资源路径查找对应的资源,并进行相关处理(例如执行服务器脚本、查询数据库等)。
  4. 服务器响应:服务器处理完请求后,会生成一个HTTP响应,包含HTTP响应状态码(例如200表示成功),响应头(包含响应内容类型、编码等信息)和响应体(请求的资源内容,如HTML文档)。
  5. 浏览器接收响应:浏览器收到服务器的响应后,会根据响应头信息解析响应体中的内容。如果响应内容是HTML文档,浏览器会进行下一步的解析和渲染

ReentranLock底层原理

简单介绍一下AQS
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁 实现的,
在这里插入图片描述
ReentrantLock 主要利用 CAS + AQS队列来实现,通过重写了 AQS 的 tryAcquire 和 tryRelease方法实现的 lock 和 unlock。

  • 它的原理是基于AQS(AbstractQueuedSynchronizer ),多个线程抢锁时,如果争抢失败则会进入阻塞队列。等待唤醒,重新尝试加锁。
  • AQS的子类有公平锁FairSync和非公平锁NofairSync,ReentrantLock的无参构造默认是非公平锁,有参构造参数是true可以设置成公平锁。
    在这里插入图片描述
  • 公平锁:ReentrantLock调用lock方法,最终会调用Sync类的tryAcquire函数,获取资源。当前线程只有在队列为空或者时队首节点的时候,才能获取资源,否则会被加入到阻塞队列中。
  • 非公平锁:调用lock方法时 lock:直接利用CAS尝试将state从 0 改为 1,如果成功,拿锁直接走,如果失败了,执行sync的tryAcquire,不同的是tryAcquire还会调用nofairTryAcquire。在nofairTryAcquire中会再次判断当前锁是否被占用
    1. 如果当前锁没有占用(state==0),直接再次尝试将state从0 改为 1 如果成功,拿锁直接走。
    2. 如果当前锁被占用(state!=0):如果被自己占用,则计数器会state++(可重入锁),如果被其他线程占用加入AQS队列
  • 公平锁和非公平锁的区别: 非公平锁在调用NonfairSync的lock的时候就会马上进行CAS抢锁,抢不到就和公平锁一样进入tryAcquire方法尝试抢锁,如果发现锁被释放了(state==0),非公平锁马上CAS抢锁,而不会管阻塞队列里是否有线程等待。而公平锁会排队等待。

SpringBoot 的启动流程

构造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   
   // 获取应用类型,根据是否加载Servlet类判断是否是web环境
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
   
   // 读取META-INFO/spring.factories文件,获取对应的ApplicationContextInitializer装配到集合
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   // 设置所有监听器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // 推断main函数
   this.mainApplicationClass = deduceMainApplicationClass();
}

当启动springboot应用程序时,会先创建SpringApplication对象,在其构造方法中进行参数的初始化工作:

  1. 推断并设置当前web应用类型
  2. 获取所有初始化器、监听器。扫描所有META-INF/spring.factories文件中分别读取key为ApplicationContextInitializer、ApplicationListener 三种接口类型的实现类,并分别设置对应的属性,将文件中的内容放到缓存对象中,方便后续获取
  3. 定位main应用程序类
/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     *
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        // 启动一个秒表计时器,用于统计项目启动时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 创建启动上下文对象即spring根容器
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        // 定义可配置的应用程序上下文变量
        ConfigurableApplicationContext context = null;
        /**
         * 设置jdk系统属性
         * headless直译就是无头模式,
         * headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
         */
        configureHeadlessProperty();
        /**
         * 获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法
         * 从spring.factories中获取配置
         */
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 启动监听器
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 包装默认应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //
            /**
             * 准备环境 prepareEnvironment 是个硬茬,里面主要涉及到
             * getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
             * environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
             */
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            // 配置忽略的 bean
            configureIgnoreBeanInfo(environment);
            // 打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
            Banner printedBanner = printBanner(environment);
            // 创建 IOC 容器
            context = createApplicationContext();
            // 设置一个启动器,设置应用程序启动
            context.setApplicationStartup(this.applicationStartup);
            // 配置 IOC 容器的基本信息 (spring容器前置处理)
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            /**
             * 刷新IOC容器
             * 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
             */
            refreshContext(context);
            /**
             * 留给用户自定义容器刷新完成后的处理逻辑
             * 刷新容器后的扩展接口(spring容器后置处理)
             */
            afterRefresh(context, applicationArguments);
            // 结束计时器并打印,这就是我们启动后console的显示的时间
            stopWatch.stop();
            if (this.logStartupInfo) {
                // 打印启动完毕的那行日志
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
            listeners.started(context);
            // 执行runner,遍历所有的 runner,调用 run 方法
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 异常处理,如果run过程发生异常
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
 
        try {
            // 所有的运行监听器调用 running() 方法,监听应用上下文
            listeners.running(context);
        } catch (Throwable ex) {
            // 异常处理
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        // 返回最终构建的容器对象
        return context;
    }
复制代码
  1. 在SpringApplication对象创建完成后,开始执行run方法,来完成整个启动,启动过程中最主要有两个方法,一个是prepareContext,另外一个是refreshContext,之前的处理逻辑包含创建定时器、上下文对象的创建、banner的打印等各个准备工作,方便后续来进行调用
  2. 在prepareContext方法主要完成对上下文对象的初始化操作,包括属性的设置,比如把Environment环境变量设置给Spring容器
  3. 在refreshContext方法中会进行整个容器刷新过程,会调用Spring中AbstractApplicationContext#refresh方法,其中有13个关键的方法,来完成整个spring IOC容器的创建过程,这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
  4. listeners.started(context); 发布容器已启动的事件
  5. callRunners(context, applicationArguments); 遍历运行器,并调用run方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值