查看原文
其他

什么是 AOP?

编程导航和鱼友们 面试鸭 2024-03-29

大家好呀,今天是编程导航 30 天面试题挑战的第二十六天,一起来看看今天有哪些优质面试题吧。

后端

题目一

什么是 AOP?有哪些实现 AOP 的方式?Spring AOP 和 AspectJ AOP 有什么区别?

官方解析

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它旨在解决软件开发中的横切关注点(cross-cutting concerns)问题。横切关注点是那些分布于多个模块或对象的功能,例如日志记录、安全检查、事务管理等。AOP 通过将横切关注点与业务逻辑分离,从而提高了代码的模块化程度,使得开发更加简洁、易于维护。

Spring AOP 和 AspectJ AOP 是两种不同的 AOP 实现。

  1. Spring AOP:是 Spring 框架中的 AOP 实现,基于动态代理实现。Spring AOP 主要用于解决 Spring 容器中 Bean 的横切关注点问题。由于它使用了动态代理,所以只支持方法级别的切面(即横切关注点只能织入方法的执行)。Spring AOP 的性能略逊于 AspectJ,但对于大部分应用来说,性能影响不大。
  2. AspectJ AOP:是一个独立的、功能更强大的 AOP 实现,不仅支持方法级别的切面,还支持字段、构造器等其他切面。AspectJ 可以通过编译时织入(编译时修改字节码)或加载时织入(在类加载时修改字节码)的方式实现 AOP。Spring 可以与 AspectJ 结合使用,以提供更强大的 AOP 功能。

实现 AOP 的方式主要有以下几种:

  1. 动态代理:通过代理模式,为目标对象生成一个代理对象,然后在代理对象中实现横切关注点的织入。动态代理可以分为JDK动态代理(基于接口)和 CGLIB 动态代理(基于类)。
  2. 编译时织入:在编译阶段,通过修改字节码实现 AOP。AspectJ 的编译时织入就是这种方式。
  3. 类加载时织入:在类加载阶段,通过修改字节码实现 AOP。AspectJ 的加载时织入就是这种方式。

AOP的实现方式取决于具体需求和技术选型。对于 Spring 应用来说,通常可以使用 Spring AOP 满足大部分需求,如果需要更强大的 AOP 功能,可以考虑使用 AspectJ。

鱼友的精彩回答

Antony 的回答

AOP:Aspect Orlented Programming 面向切面编程;

AOP的作用

下面两点是同一件事的两面,好比硬币的两面;

简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高了内聚性;代码增强:把特定的功能封装到切面类中,看哪里有需要就往上进行套用,被套用了切面逻辑的方法就被切面给增强了;

实现方法如下

动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver 就是织入器。Spring 只是借用了 AspectJ 中的注解。

Gianhing 的回答

AOP:面向切面编程,是一种编程思想,将一些与核心业务无关的公共逻辑(日志、事务等)抽离出来,在不改变原有业务逻辑上,以切面的方式对程序进行一个横向的扩展,提高代码的可重用性、可维护性和可扩展性。

AOP 的相关概念:

概念含义
目标对象(Target Object)被切面织入的对象
连接点(Join Point)可以被切面插入的地方,如方法调用、异常抛出等,所有方法都可以是连接点
切入点(Pointcut)被切面增强的连接点,即哪些连接点会触发切面的通知
通知(Advice)切面在连接点处执行的代码,它包括了前置通知、后置通知、环绕通知、异常通知和最终通知
切面(Aspect)横切关注点的模块化,即通知 + 切入点
织入(Weaving)将切面应用到目标对象的过程,可以在编译期、类加载期和运行期进行
引入(Introduction)为目标对象动态的添加属性和方法

Spring AOP 和 AspectJ AOP 是 AOP 的两种实现方式:

  • Spring AOP 是基于动态代理实现的,通过 JDK 动态代理或 CGLIB 代理来生成代理对象,只能用于方法级别的拦截和处理,在运行时织入
  • AspectJ AOP 是一个更加完整的 AOP 框架,基于字节码操作,支持更多的切面表达式和切面类型,可以在编译期、类加载期进行织入,性能更加高效

实现 AOP 的方式:

  • 静态代理:手动编写代理类,实现增强逻辑。需要为每个目标类编写代理类,代码量大
  • JDK 动态代理:利用反射机制生成代理类,只能用于实现了接口的类
  • CGLIB 动态代理:利用字节码生成目标类的子类(即代理类),可以代理未实现接口的类
  • AspectJ AOP:使用 AspectJ 在编译期或类加载期对目标类进行织入

HeiHei 的回答

AOP

IoC 使软件组件解耦合。AOP 可以捕捉系统中经常使用的功能,把它转化成组件。将核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为 AOP。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。AOP 底层使用的就是动态代理来实现的

Spring AOP 和 AspectJ AOP 的区别:

Spring AOP 属于运行时增强,主要具有如下特点:

基于动态代理来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现 Spring AOP 需要依赖 IOC 容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。Spring AOP 致力于解决企业级开发中最普遍的 AOP(方法织入)。

AspectJ 是一个易用的功能强大的 AOP 框架,属于编译时增强, 可以单独使用,也可以整合到其它框架中,是 AOP 编程的完全解决方案。AspectJ 需要用到单独的编译器 ajc。

AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的,一般有如下几个织入的时机:

  1. 编译期织入(Compile-time weaving):如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
  2. 编译后织入(Post-compile weaving):也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

  3. 类加载后织入(Load-time weaving):指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法





实现AOP:

  1. 静态代理:通过编写代理类,将目标对象和切面结合起来,从而实现AOP。这种方式需要手动编写代理类,而且对于多个目标对象需要编写多个代理类,因此不够灵活。
  2. 动态代理:通过使用 JDK 提供的 Proxy 类或者第三方库(如 CGLIB)来创建代理对象,从而在运行时动态地将切面织入到目标对象中。这种方式比静态代理更灵活,但仍然存在一些限制,例如只能代理实现了接口的类。
  3. 基于注解:通过在切面类上使用注解来标记切点和通知类型,然后使用 AOP 框架(如 Spring AOP)来自动创建代理对象并将切面织入到目标对象中。这种方式可以减少手动编写代理类的工作量,同时也比较灵活,支持多种切面类型。
  4. 面向切面编程框架:通过使用专门的 AOP 框架(如 AspectJ)来实现 AOP。这种方式可以提供更为灵活、高效的 AOP 支持,并且支持更多的切面类型和功能,例如使用 AspectJ 语言编写切面、进行更细粒度的控制等。

题目二

什么是分布式的 BASE 理论,它与 CAP 理论有什么联系?

官方解析

BASE 理论是分布式系统中用于描述数据一致性的一个概念。它是一个缩写,分别代表:

  1. 基本可用(Basic Availability):分布式系统在出现故障时,依然能够保证系统的可用性,但可能会出现部分功能或性能降低的情况。
  2. 软状态(Soft State):由于分布式系统中各个节点的状态可能会有一定的延迟,系统允许在一定时间内存在数据不一致的情况。
  3. 最终一致性(Eventual Consistency):在一段时间内,分布式系统中的数据可能不一致,但最终会达到一致状态。这个过程可能需要一定的时间。

CAP理论是另一个关于分布式系统的理论,它指出在分布式系统中,不能同时满足以下三个属性:

  1. 一致性(Consistency):在分布式系统中的所有节点,在同一时刻具有相同的数据。
  2. 可用性(Availability):分布式系统在任何时刻都能对外提供服务,响应用户请求。
  3. 分区容错性(Partition Tolerance):分布式系统在遇到网络分区(部分节点之间通信中断)的情况下仍然能够正常运行。

BASE 理论和 CAP 理论之间的关系是:BASE 理论实际上是对 CAP 理论的一种实践和解释。在 CAP 理论中,由于无法同时满足三个属性,因此在实际的分布式系统设计中,通常需要在一致性和可用性之间做出权衡。BASE 理论就是这种权衡的一种体现,它强调基本的可用性、软状态和最终一致性,而不是追求强一致性。在很多场景中,采用 BASE 理论能够带来更高的系统可用性和更好的性能表现。

鱼友的精彩回答

HeiHei 的回答

BASE 理论:

BASE 理论是对 CAP 理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。

  1. Basically Available(基本可用):分布式同再出现不可预知故障的时候,允许损失部分可用性
  2. Soft state(软状态):软状态也称弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
  3. Eventually consistent(最终一致性):最终一致性强调的是系统中所有的数据副本,在经过一段时间上的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性
CAP 与 BASE 关系

BASE 是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于 CAP 定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),更具体地说,是对 CAP 中 AP 方案的一个补充。其基本思路就是:通过业务,牺牲强一致性而获得可用性,并允许数据在一段时间内是不一致的,但是最终达到一致性状态。

题目三

如何使用 Redis 实现分布式锁?

官方解析

使用 Redis 实现分布式锁的基本思路是使用 Redis 的原子性操作来确保在多个客户端之间只有一个客户端可以成功获取锁。以下是使用 Redis 实现分布式锁的步骤:

  1. 获取锁:客户端尝试获取锁,可以使用 Redis 的 SET 命令结合NX和EX选项来实现。SET 命令用于设置一个键值对,NX 选项表示只有在键不存在时才设置,EX 选项表示设置一个过期时间(单位为秒)。示例:SET lock_key unique_value NX EX 30。这个命令表示如果 lock_key 不存在,就设置它的值为 unique_value,并设置 30 秒的过期时间。如果设置成功,表示获取锁成功;如果设置失败,表示锁已被其他客户端持有,需要等待或重试。
  2. 持有锁:在持有锁的客户端执行临界区代码。在此期间,其他客户端无法获取锁。
  3. 释放锁:在临界区代码执行完毕后,需要释放锁以供其他客户端使用。为了避免误解锁的情况(例如,由于执行时间过长导致锁过期,然后被其他客户端获取),在释放锁时需要检查锁的值是否与获取锁时设置的值相同。这可以通过 Redis 的 EVAL 命令来实现,使用 Lua 脚本进行原子性操作。示例:EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock_key unique_value。这个命令表示检查lock_key的值是否为unique_value,如果是,则删除 lock_key 以释放锁;否则不执行任何操作。通过以上步骤,可以实现一个基本的 Redis 分布式锁。在实际应用中,还需要考虑一些其他因素,例如获取锁的重试策略、锁的续期等。

鱼皮的评论

题解更倾向于以原生命令的方式去实现,其实还有很多现成的第三方库,比如 Redisson。这里要关注分布式锁可能出现的各种异常情况。

鱼友的精彩回答

猿二哈的回答

redis 实现分布式锁

  1. redis 实现分布式锁的方式有两种:
  2. 通过 redis 提供的 setnx 进行实现,往 redis 中使用 setnx 插入 key 时,如果 key 存在,则返回 0,可以通过插入 key 的返回值进行判断来实现分布式锁
  3. 通过使用 Redission(客户端)来实现分布式锁。可以调用 Redission 提供的 api,即 lock(),unlock()方法进行加锁和锁的释放。此外, Redission 还提供了 watchdog,即看门狗,来对加锁的 key 每隔 10 s对该 key 进行续时,(从而避免锁的过期)
  4. Redission 的所有指令是通过 lua 脚本进行实现的,lua 脚本可以保证所有指令执行的原子性

关键词:setnx,Redission,lock,unlock,watchdog,lua,原子性

回家养猪的回答

基于 Redis 的分布式锁实现思路:

  • 利用 setnx 获取锁,并设置过期时间,保存线程标识
    • uuid+ 线程 id 因为不同服务器线程 id 有可能一样, 所以拼接 uuid
  • 释放锁时先判断线程标识是否与自己一致,一致则删除锁
    • 判断锁是否为自己的然后再释放, 这个过程必须是原子性的否则有可能释放别人的锁, 这就需要 lua 脚本.

特性:

  • 利用 set nx 满足互斥性
  • 利用 set ex 保证故障时锁依然能释放,避免死锁,提高安全性
  • 利用 Redi s集群保证高可用和高并发特性

可重入

  • 和 jdk 的可重入锁的原理是一致的
  • 使用 has h类型, key 为 userId 或者商品 id , field 为线程 id, value 为数字. 加锁解锁都需要使用 lua 脚本
    • 方法 1 加锁后, value=1, 方法1调用方法2
    • 方法 2 加锁, redisson 判断锁标识是否是自己(field 字段是否为同一线程), 若是则 value++, value 为 2
    • 同理, 方法 3 加锁, value=3
    • 判断 key 是否存在, 不存在则直接加锁, 加过期时间 (key 为 userId 或者商品 id , field 为线程 id, value 为 1)
    • 存在则说明有方法已经加了锁, 此时判断 field 的线程 id 是否则自己一致(是否为同一线程), 不同则返回, 相同则 value+1, 然后设置过期时间
    • 加锁
    • 加锁后执行流程
  • 释放锁
    • 判断锁是否是自己线程的. 不是则退出. 是则 value-1
    • 判断重复次数 value 是否为 0, 如果为 0 则释放锁. 不为 0 说明锁其他人还在用, 则重置有效期.
  • 释放锁执行流程
    • 方法 3 执行完毕, 释放锁, value--, value=2
    • 方法 2 释放锁, value=1
    • 方法 1 释放锁, value=0, 此时删除 redis 的该数据, 锁完全释放.
    • 可重试:利用信号量和 redis 的 pubsub 发布订阅机制实现等待、唤醒,获取锁失败的重试机制
  • 先直接获取锁, 如果获取失败, 并不是直接重试, 因为现在立即重试大概率其他线程正在执行业务. 获取锁失败会先暂时等待. (CPU 占用率不会很高, 性能不错)
  • 获取锁成功的线程在释放锁时会发布一条消息.
  • 当其他线程得到该消息时, 就会重新获取锁, 如果再次获取锁失败, 就会再次等待.
  • 但是不是无限制的等待, 因为他会有一个等待时间, 超过该时间则不重试直接返回 false

超时续约:利用 watchDog,每隔一段时间(releaseTime/3), 重置超时时间

  • 看门狗机制会创建一个守护线程, 当锁快到期但是业务线程没执行完时为锁增加时间 (续命).
  • 当然看门狗也不会无限地增加超时时间, redisson 一个参数用来设置加锁的时间, 超过这个时间后锁便自动解开了,不会延长锁的有效期。

主从一致性问题: 使用多个独立的 Redis 节点,

  • 获取锁时, 往每一个 redis 节点都写入 key. 即便其中一台 redis 宕机, 其他 redis 依旧有锁信息.
  • 并且必须在所有节点都获取到锁, 才算获取锁成功.
  • Redisson 分布式联锁 RedissonMultiLock 对象可以将多个 RLock 锁对象关联为一个联锁, 可以把一组锁当作一个锁来加锁和释放。

爱吃鱼蛋的回答

为什么

对于分布式系统来说不能够使用 Java 自带的 synchronized 或 Lock 等实现上锁,因为分布式情况下不同节点的 JVM 不同,导致锁对象不同,会造成锁失效问题。而使用 Redis 实现分布式锁可以很好的解决分布式所带来的的问题,并且因为 Redis 的特性,锁的性能并不低。同时由于加锁是使用到了 Redis 中的原子命令 SET key value [EX seconds] [NX],因此在实现过程中并不需要自行编写 Lua 脚本来保证锁的原子性,简化了开发难度。

解释一下 SET key value [EX seconds] [NX]命令:

  • EX 表示该命令会设置一个过期时间,单位为秒,后面的 seconds 便是具体的数值;
  • NX 表示只有 key 不存在的时候才会设置成功,使用 Redis 实现分布式锁的核心便是这个特性。
实现步骤

下面是使用 Redis 实现分布式锁的步骤:

  1. 生成分布式锁的 key,一般为业务相关的业务 key;
  2. 生成分布式锁的 value,一般为1或者线程 id;
  3. 生成分布式锁的过期时间,避免锁过程中宕机锁无法解开;
  4. 通过不同语言的 API 实现 SET key value [EX seconds] [NX]命令;
  5. 通过命令执行结果返回的标识判断是否加锁成功,一般 true 为上锁成功;
  6. 释放锁时,通过删除分布式锁的 key 实现。
Java 实现

这里提供不可重入的分布式锁的实现方式:

@Component
public class RedisLock {

    @Resource
    private StringRedisTemplate redisTemplate;

    /**
     * 尝试获取分布式锁
     * @param key 锁的键值
     * @param value 锁的值
     * @param expireTime 锁的过期时间
     * @param timeUnit 锁的过期时间单位
     * @return 是否获取到锁
     */
    public boolean tryLock(String key, String value, long expireTime, TimeUnit timeUnit) {
        // 利用 setIfAbsent 方法实现分布式锁
        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, timeUnit);
        return Boolean.isTrue(success);
    }

    /**
     * 释放分布式锁
     * @param key 锁的键值
     */
    public void unlock(String key) {
        redisTemplate.delete(key);
    }
}
好处

使用 Redis 实现分布式锁主要有以下好处:

  • 高性能:Redis 是一个高性能的内存数据库,可以快速地完成获取和释放锁的操作;
  • 可靠性:Redis 的持久化机制和主从复制机制可以保证锁的可靠性,即使 Redis 节点出现故障,也可以保证锁的可用性;
  • 可重入性:可以支持可重入锁,即同一个线程可以重复获取同一个锁,而不会被其他线程所影响;
  • 灵活性:可以支持多种锁的实现方式,如阻塞式、非阻塞式、公平锁、非公平锁等,可以根据业务需求选择最合适的锁;
  • 分布式环境下的协作:可以支持分布式环境下的多个进程或者节点之间的协作,实现分布式系统的同步和协作。

补充:这种方式可能会出现业务还未完成但锁已经释放的问题(如果业务执行时间超过了锁的过期时间),这个问题可以使用 Redssion 解决(看门狗机制)

猫十二懿的回答

使用 Redis 实现分布式锁的核心思想是利用 Redis 的原子性操作,对 Redis 中特定的 key 进行 setnx(set if not exists)操作,如果成功返回true 表示获取到了锁,否则返回 false 表示没有获取到锁。在获取到锁的情况下,需要设置过期时间和在释放锁之前判断当前锁是否是自己持有的,以防出现死锁等问题。

具体实现步骤如下:

  1. 生成一个唯一的锁标识 key,并设置锁的过期时间。
  2. 使用 setnx 命令尝试获取锁,如果返回结果为 1,则表示获取锁成功,进入下一步;否则等待一段时间重试,直到获取到锁为止。
  3. 在持有锁的时间内,执行相关业务逻辑操作,并定时更新锁的过期时间,防止锁时间过长而导致锁自动失效。

  4. 释放锁,通过比较锁的持有者是否是自己来判断是否能够释放锁。



通过 redis 命令实现分布式锁:

Java 实现一个分布式锁:

public class RedisDistributedLock {
    
    private static final String LOCK_PREFIX = "redis_lock_";
    private static final int EXPIRE_TIME = 5; // 锁过期时间5秒
    
    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
 
 // 构造函数
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_PREFIX + lockKey;
        this.lockValue = UUID.randomUUID().toString();
    }
    
    // 获取锁
    public boolean lock() {
        try {
            String result = redisTemplate.execute((RedisCallback<String>) connection -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.set(lockKey, lockValue, "NX""EX", EXPIRE_TIME);
            });
            return StringUtils.isNotBlank(result);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // 释放锁
    public boolean unlock() {
        try {
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return (Long) commands.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            });
            return result != null && result > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

前端

题目一

什么是 Node.js 的事件循环机制?它是怎么实现的?

官方解析

Node.js 的事件循环(Event Loop)机制是 Node.js 中实现异步非阻塞I/O的核心。事件循环允许 Node.js 可以在单个线程中处理高并发的请求,提高了程序的性能和响应能力。

事件循环是 Node.js 的运行机制,负责调度和处理各种事件(如I/O操作、定时器、网络请求等)。以下是事件循环的主要实现流程:

  1. Node.js 在启动时会初始化事件循环。
  2. 执行输入的脚本,可能会注册各种事件(如I/O操作、定时器等)。
  3. 事件循环开始运行,进入不同的阶段(Phases),如Timers、I/O callbacks、Idle/Prepare、Poll、Check和Close callbacks。每个阶段负责处理特定类型的事件。
  4. 当事件队列中的事件被处理完毕,事件循环会检查是否还有待处理的事件或回调。如果没有,事件循环结束,程序退出;否则,事件循环继续运行,处理新的事件。

Node.js 的事件循环机制是基于 libuv 库实现的。libuv 是一个跨平台的异步I/O库,提供了事件循环、线程池等功能。Node.js 使用 libuv 来实现事件循环,处理各种异步操作,如文件I/O、网络请求、定时器等。

值得注意的是,Node.js 的事件循环与浏览器的事件循环有一定的区别。虽然它们的核心概念相似,但在实现细节和运行环境上有所不同。在使用 Node.js 时,需要理解其事件循环的特点,以便更好地编写异步代码。

题目二

DNS 预解析是什么?怎么实现?

官方解析

DNS 预解析(DNS Prefetching)是一种网络性能优化技术,用于提前解析用户可能访问的域名,以减少实际请求时的 DNS 解析时间。通过预解析,浏览器可以在用户实际访问一个链接之前就已经获取到对应的IP地址,从而降低网络延迟,加快页面加载速度。

DNS 预解析主要有两种实现方式:

自动预解析:现代浏览器通常会自动进行 DNS 预解析。浏览器会根据用户的浏览历史和页面内容分析可能需要预解析的域名,然后在后台进行 DNS 解析。这种方式无需开发者进行任何操作,浏览器会自动进行预解析。

手动预解析:开发者可以通过在 HTML 文档中添加标签来手动指定需要进行预解析的域名。这种方式可以让开发者更精确地控制哪些域名需要进行预解析。示例如下:

<head>
  ...
  <link rel="dns-prefetch" href="//example.com">
  <link rel="dns-prefetch" href="//cdn.example.com">
  ...
</head>

在这个示例中,使用标签并设置 rel 属性为 dns-prefetch,将 href 属性设置为需要预解析的域名。注意,域名前需要加上//,表示使用当前页面的协议(HTTP 或 HTTPS)。

使用DNS预解析可以在一定程度上提高网页的加载速度和用户体验,但需要注意的是,过多的预解析可能会消耗用户的网络资源和带宽。因此,在实际应用中,应该根据实际需求合理地使用 DNS 预解析。

鱼友的精彩回答

会冒泡的可乐的回答

DNS 预解析就是让浏览器在用户访问链接之前解析域名,其范围包括文档的所有链接,无论是图片的,CSS 的,还是 JavaScript 等其他用户能够点击的 URL。

实现流程:

  1. 首先客户端位置是一台电脑或手机,在打开浏览器以后,比如输入 http://www.zdns.cn 的域名,它首先是由浏览器发起一个 DNS 解析请求,如果本地缓存服务器中找不到结果,则首先会向根服务器查询,根服务器里面记录的都是各个顶级域所在的服务器的位置,当向根请求 http://www.zdns.cn 的时候,根服务器就会返回 .cn 服务器的位置信息。

  2. 递归服务器拿到.cn的权威服务器地址以后,就会寻问cn的权威服务器,知不知道 http://www.zdns.cn 的位置。这个时候cn权威服务器查找并返回http://zdns.cn服务器的地址。

  3. 继续向 http://zdns.cn 的权威服务器去查询这个地址,由 http://zdns.cn 的服务器给出了地址:202.173.11.10

  4. 最终才能进行 http 的链接,顺利访问网站。

当递归服务器拿到解析记录以后,就会在本地进行缓存,如果下次客户端再请求本地的递归域名服务器相同域名的时候,就不会再这样一层一层查了,因为本地服务器里面已经有缓存了,这个时候就直接把http://www.zdns.cn 的 A 记录返回给客户端就可以了。

悟道的回答

DNS 预解析(DNS prefetching)是指在网页加载时,提前获取该网页中需要引用的外部资源的 DNS 解析结果,以便在浏览器发起请求时,可以直接使用已经解析好的IP地址,加快网页加载速度。

DNS 预解析可以通过以下两种方式实现:

1、HTML 标签方式:在网页 head 标签内,使用标签指定需要预解析的DNS域名:

<head>
    <link rel="dns-prefetch" href="//example.com">
</head>

这样浏览器在解析HTML时,就会提前进行DNS解析,减少后续请求时的解析时间。

2、JavaScript方式:在网页中使用JavaScript代码实现DNS预解析:

<script>
    var dns = new Image();
    dns.src = "//example.com";
</script>

该代码会创建一个 Image 对象,使用其 src 属性指定需要预解析的域名。这样在网页加载时,浏览器就会提前解析该域名的 DNS,加快后续请求的速度。

需要注意的是,DNS 预解析虽然可以提高网页加载速度,但过多的 DNS 预解析可能会增加网络流量,甚至会对服务器造成压力。因此,应该根据网页实际情况进行适当的 DNS 预解析。

Kristen 的回答

DNS 解析

大多数人是通过域名访问网站,当浏览器从(第三方)服务器请求资源时,必须先将该域名解析为 IP 地址,然后浏览器才能向该域名发出请求,域名到 IP 这一过程称为 DNS 解析。一般来说,一次 DNS 解析需要耗费 20-120 ms,所以为了优化 DNS,我们可以考虑两个方向:减少 DNS 请求次数 缩短DNS 解析时间 dns-prefetch

为什么要有 DNS 预解析

因为 DNS 解析需要时间,就衍生出了 DNS 预解析作为一个优化。

当你的网站第一次请求某个跨域域名时,需要先解析该域名(例如页面访问 cdn 资源,第一次访问需要先解析 cdn)。可以在请求的 Timing 上看到有一个 DNS Lookup 阶段,而在这个请求之后的其他该域名的请求都没有这项时间支出。

DNS 解析时间可能导致大量用户感知延迟(在移动端可能比较明显),DNS 解析所需的时间差异非常大,延迟范围可以从 0 ms(本地缓存结果)到几秒钟时间(网络极差)。

什么是 DNS 预解析

DNS 预解析是一项让浏览器主动去执行域名解析的功能。浏览器试图在用户访问链接之前解析域名,其范围包括文档的所有链接,无论是图片的,CSS  的,还是 JavaScript 等其他用户能够点击的 URL。因为预读取会在后台执行,所以 DNS 很可能在链接对应的东西出现之前就已经解析完毕,这能够减少用户点击链接时的延迟。

如何开启DNS预解析
  <link rel="dns-prefetch" href="xxx.com"

简单的一行代码就能让支持该特性的浏览器提前解析 DNS。也就是说在浏览器请求资源时,DNS 查询就已经准备好了。

什么是dns-prefetch? dns-prefetch(DNS 预获取)是前端网络性能优化的一种措施。它根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短DNS解析时间,进而提高网站的访问速度。

为什么要用dns-prefetch? 每当浏览器从(第三方)服务器发送一次请求时,都要先通过DNS解析将该跨域域名解析为 IP 地址,然后浏览器才能发出请求。

如果某一时间内,有多个请求都发送给同一个服务器,那么 DNS 解析会多次并且重复触发。这样会导致整体的网页加载有延迟的情况。

虽然 DNS 解析占用不了多大带宽,但是它会产生很高的延迟,尤其是对于移动网络会更为明显。

因此,为了减少 DNS 解析产生的延迟,我们可以通过 dns-prefetch 预解析技术有效地缩短 DNS 解析时间。

<link rel="dns-prefetch" href="https://baidu.com/"

注意 http 页面下所有的 a 标签的 href 都会自动去启用 DNS Prefetch,也就是说,网页的 a 标签 href 带的域名,是不需要在 head 里面加上 link 手动设置的。https 页面需要使用 meta 标签强制开启:

  <meta http-equiv="x-dns-prefetch-control" content="on">

dns-prefetch 适用于网页引用了大量其他域名的资源,例如淘宝。

题目三

什么是低代码?你用过哪些低代码工具?

官方解析

低代码(Low-Code)是一种软件开发方法,它允许开发者通过可视化界面、拖拽组件和一些简单的逻辑配置来构建应用程序,而无需编写大量的代码。低代码平台的目标是提高开发效率,降低开发难度,使非专业开发人员(如业务分析师、产品经理等)也能参与到应用程序的开发过程中。

低代码平台的主要特点包括:

  1. 可视化界面:低代码平台通常提供可视化的界面设计工具,开发者可以通过拖拽组件、配置属性等方式来构建应用程序界面。

  2. 预构建组件:低代码平台提供一系列预构建的组件和模板,开发者可以直接使用这些组件,而无需从头编写代码。

  3. 逻辑配置:低代码平台允许开发者通过图形化界面或简单的脚本语言来配置应用程序的逻辑,降低了编程难度。

  4. 一键部署:低代码平台通常提供一键部署功能,开发者可以轻松地将应用程序发布到目标环境中。一些常见的低代码工具和平台包括:

  5. OutSystems:一款功能强大的低代码开发平台,适用于构建企业级应用程序,支持 Web 和移动应用开发。

  6. Appian:提供低代码开发和业务流程管理功能,适用于构建企业应用程序,特别是需要复杂流程控制的场景。

  7. Microsoft Power Apps:Microsoft 推出的低代码应用程序开发平台,可以轻松地与其他 Microsoft 365服务集成。

  8. Mendix:一款低代码开发平台,提供丰富的预构建组件和可视化开发工具,适用于构建企业应用程序。虽然低代码平台可以提高开发效率,但在某些场景下,它们可能无法满足复杂的定制需求或实现高度优化的性能。因此,在选择低代码平台时,需要根据项目需求和团队技能来权衡。

鱼皮的评论:

大家在回答时可以简单补充 “低代码的实现原理”,比如通过配置生成界面

星球活动

1.欢迎参与 30 天面试题挑战活动 ,搞定高频面试题,斩杀面试官!

2.欢迎已加入星球的同学 免费申请一年编程导航网站会员 !

3.欢迎学习 鱼皮最新原创项目教程,手把手教你做出项目、写出高分简历!

加入我们

欢迎加入鱼皮的编程导航知识星球,鱼皮会 1 对 1 回答您的问题、直播带你做出项目、为你定制学习计划和求职指导,还能获取海量编程学习资源,和上万名学编程的同学共享知识、交流进步。

💎 加入星球后,您可以:

1)添加鱼皮本人微信,向他 1 对 1 提问,帮您解决问题、告别迷茫!点击了解详情

2)获取海量编程知识和资源,包括:3000+ 鱼皮的编程答疑和求职指导、原创编程学习路线、几十万字的编程学习知识库、几十 T 编程学习资源、500+ 精华帖等!点击了解详情

3)找鱼皮咨询求职建议和优化简历,次数不限!点击了解详情

4)鱼皮直播从 0 到 1 带大家做出项目,已有 50+ 直播、完结 3 套项目、10+ 项目分享,帮您掌握独立开发项目的能力、丰富简历!点击了解详情

外面一套项目课就上千元了,而星球内所有项目都有指导答疑,轻松解决问题

星球提供的所有服务,都是为了帮您更好地学编程、找到理想的工作。诚挚地欢迎您的加入,这可能是最好的学习机会,也是最值得的一笔投资!

长按扫码领优惠券加入,也可以添加微信 yupi1085 咨询星球(备注“想加星球”):

往期推荐

编程导航,火了!

什么是注册中心?

Spring 支持哪几种事务管理类型?

什么是 Bean 的生命周期?

RPC是什么?

什么是零拷贝?




继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存