2023【腾讯】面试真题

2023-07-15 15:38:43 浏览数 (1)

【腾讯】面试真题:

1、Kafka 是什么?主要应用场景有哪些?

Kafka 是一个分布式流式处理平台。这到底是什么意思呢?

流平台具有三个关键功能:

消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。

容错的持久方式存储记录消息流: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。

流式处理平台: 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。

Kafka 主要有两大应用场景:

消息队列 :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。

数据处理: 构建实时的流数据处理程序来转换或处理数据流。

2、kafka 为什么有 topic 还要用 patition?

Kafka 可以将主题划分为多个分区(Partition),会根据分区规则选择把消息存储到哪个分区中,只要分区规则设置的合理,那么所有的消息将会被均匀的分布到不同的分区中,这样就实现了负载均衡和水平扩展。另外,多个订阅者可以从一个或者多个分区中同时消费数据,以支撑海量数据处理能力。

producer 只需要关心消息发往哪个 topic,而 consumer 只关心自己订阅哪个 topic,并不关心每条消息存于整个集群的哪个 broker。 为了性能考虑,如果 topic 内的消息只存于一个 broker,那这个 broker 会成为瓶颈,无法做到水平扩展。所以把 topic 内的数据分布到整个集群就是一个自然而然的设计方式。

Partition 的引入就是解决水平扩展问题的一个方案。

3、客户端和服务器之间最多能建立多少个连接 ?

65535 。

服务器的 ip ,端口号 ,客户端的 ip 都是确定的。 能变的只有客户端的端口号。

加网卡 ,保证四元组唯一,理论上能是客户端和服务器之间建立 10 万以上的连接 。

4、HashMap 结构,线程不安全举个例子?

多个线程同时操作一个 hashmap 就可能出现不安全的情况。

如果两个线程同时遇到 HashMap 的大小达到 12 的倍数时,就很有可能会出现在将oldTable 转移到 newTable 的过程中遇到问题,从而导致最终的 HashMap 的值存储异常。

构造 entry<K,V>单链表时,也会出现不安全的情况。

5、MySQL 索引分类?

单列索引

普通索引:MySQL 中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。

唯一索引:索引列中的值必须是唯一的,但是允许为空值,

主键索引:是一种特殊的唯一索引,不允许有空值。

组合索引:

多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。

全文索引:

只有在 MyISAM 引擎上才能使用,只能在 CHAR,VARCHAR,TEXT 类型字段上使用全文索引,介绍了要求,说说什么是全文索引,就是在一堆文字中,通过其中的某个关键字等,就能找到该字段所属的记录行,比如有"你是个靓仔,靓女 ..." 通过靓仔,可能就可以找到该条记录

空间索引:

空间索引是对空间数据类型的字段建立的索引,MySQL 中的空间数据类型有四种,GEOMETRY、POINT、LINESTRING、POLYGON。在创建空间索引时,使用 SPATIAL 关键字。要求,引擎为 MyISAM,创建空间索引的列,必须将其声明为 NOT NULL。

6、了解线程 & 进程的区别吗?

操作系统中可以拥有多个进程,一个进程里可以拥有多个线程,线程在进程内执行进程和线程的区别

容易创建新线程。创建新进程需要重复父进程

线程可以控制同一进程的其他线程。进程无法控制兄弟进程,只能控制其子进程

进程拥有自己的内存空间。线程使用进程的内存空间,且要和该进程的其他线程共享这个空间;而不是在进程中给每个线程单独划分一点空间。(同一进程中的)线程在共享内存空间中运行,而进程在不同的内存空间中运行

线程可以使用 wait(),notify(),notifyAll()等方法直接与其他线程(同一进程)通信;而,进程需要使用“进程间通信”(IPC)来与操作系统中的其他进程通信。

7、Java 进程间的几种通信方式?

管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。l 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

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

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

8、多台服务器同时对一个数据定时任务,怎么处理 ?

对于一个定时任务,如果当前任务已经被某一个服务器处理后,另外一个服务器就不需要执行这个任务了

在定时任务里加锁机制,等某台服务器获取权限,其他服务器将不再执行此次定时任务。

在数据库的创建定时任务控制表 job_controller,创建 updated_by 字段,用来存放执行代码的服务器生成的序列号。创建 updateTime 字段,用于记录标记更新 update_by的时间戳,也可以理解为上一次任务执行的时间戳。

在代码层面,在执行任务的时候,首先生成一个序列号,然后将序列号存储在当前任务的记录上。然后再从数据库里查询当前记录的序列号,在做标记前,首先检查当前任务的上一次执行时间离当前时间超过阈值(自己定义),如果超过则表明还没有其他节点执行该任务,然后为 task 保存标签和当前运行时间。当然如果上一次运行时间为空的情况下,也是允许标记的,如果数据库中的序列号与当前节点生成序列号相匹配,则执行任务的具体逻辑,反之,则什么都不做处理。

9、常见分布式锁的几种实现方式?

基于数据库实现分布式锁

基于缓存实现分布式锁

基于 Zookeeper 实现分布式锁

10、Redis 分布式锁实现原理?

set px nx

守护线程,进行 renew

Redis 分布式锁实现: 先拿 setnx 来争抢锁,抢到之后,再用 expire(过期)给锁加一个过期时间防止锁忘记了释放。

如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样:set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的!

11、Redis 的数据类型及它们的使用场景?

string

key/value; 二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。一个键最大能存储 512MB。

hash

存储对象数据

list : 简单的字符串列表

关注列表

队列

set: string 类型的无序集合

共同关注列表

统计独立 IP

zset : (sorted set:有序集合),每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

排行

带权重的消息队列

12、信号量与信号的区别?

l 信号:(signal)是一种处理异步事件的方式。信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程外,还可以发送信号给进程本身。

信号量:(Semaphore)进程间通信处理同步互斥的机制。是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。

简单地说,信号就是一种异步通信,通知进程某种事件的发生;信号量是进程/线程同步与互斥的一种机制,保证进程/线程间之间的有序执行或对公共资源的有序访问。

13、select 和 epoll 的底层结构是什么原理

select:支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的 BLOCK 或 NONBLOCK 操作。当应用程序通过设备驱动访问该设备时(默认为 BLOCK 操作),若该设备当前没有数据可读或写,则将该用户进程插入到该设备驱动对应的读/写等待队列让其睡眠一段时间,等到有数据可读/写时再将该进程唤醒。

select 就是巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。

epoll:epoll 由三个系统调用组成,分别是 epoll_create,epoll_ctl 和 epoll_wait。

epoll_create 用于创建和初始化一些内部使用的数据结构;epoll_ctl 用于添加,删除或者修改指定的 fd 及其期待的事件,epoll_wait 就是用于等待任何先前指定的 fd 事件。

14、场景题:1 亿个数据取出最大前 100 个有什么方法?

最容易想到的方法是将数据全部排序,然后在排序后的集合中进行查找,最快的排序算法的时间复杂度一般为 O(nlogn),如快速排序。

局部淘汰法,该方法与排序方法类似,用一个容器保存前 10000 个数,然后将剩余的所有数字——与容器内的最小数字相比,如果所有后续的元素都比容器内的 10000 个数还小,那么容器内这个 10000 个数就是最大 10000 个数。如果某一后续元素比容器内最小数字大,则删掉容器内最小元素,并将该元素插入容器,最后遍历完这 1 亿个数,得到的结果容器中保存的数即为最终结果了。此时的时间复杂度为 O(n m^2),其中 m为容器的大小,即 10000。

分治法,将 1 亿个数据分成 100 份,每份 100 万个数据,找到每份数据中最大的10000 个,最后在剩下的 10010000 个数据里面找出最大的 10000 个。如果 100万数据选择足够理想,那么可以过滤掉 1 亿数据里面 99%的数据。100 万个数据里面查找最大的 10000 个数据的方法如下:用快速排序的方法,将数据分为 2 堆,如果大的那堆个数 N 大于 10000 个,继续对大堆快速排序一次分成 2 堆,如果大的那堆个数 N 大于 10000 个,继续对大堆快速排序一次分成 2 堆,如果大堆个数 N 小于10000 个,就在小的那堆里面快速排序一次,找第 10000-n 大的数字;递归以上过程,就可以找到第 1w 大的数。参考上面的找出第 1w 大数字,就可以类似的方法找到前 10000 大数字了。此种方法需要每次的内存空间为 10^64=4MB,一共需要 101次这样的比较。

Hash 法,如果这 1 亿个书里面有很多重复的数,先通过 Hash 法,把这 1 亿个数字去重复,这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间,然后通过分治法或最小堆法查找最大的 10000 个数。

采用最小堆法,首先读入前 10000 个数来创建大小为 10000 的最小堆,建堆的时间复杂度为 O(mlogm)(m 为数组的大小即为 10000),然后遍历后续的数字,并于堆顶(最小)数字进行比较。如果比最小的数小,则继续读取后续数字;如果比堆顶数字大,则替换堆顶元素并重新调整堆为最小堆。整个过程直至 1 亿个数全部遍历完为止。

然后按照中序遍历的方式输出当前堆中的所有 10000 个数字。该算法的时间复杂度为O(nmlogm),空间复杂度是 10000(常数)。

15、kafka 如何保证消息可靠?

生产者丢失消息的情况

生产者(Producer) 调用 send 方法发送消息之后,消息可能因为网络问题并没有发送过去。

为了确定消息是发送成功,我们要判断消息发送的结果,Kafka 生产者(Producer) 使用send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作,可以采用为其添加回调函数的形式,示例代码如下:

ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);

future.addCallback(result -> logger.info("生产者成功发送消息到 topic:{} partition:{}的消息",result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));

Producer 的 retries(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你 3 次一下子就重试完了消费者丢失消息的情况当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。

解决办法也比较粗暴,我们手动关闭自动提交 offset,每次在真正消费完消息之后再自己手动提交 offset 。 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。

Kafka 弄丢了消息

试想一种情况:假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。

当我们配置了 unclean.leader.election.enable = false 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。

16、消息队列的使用场景?

消息队列在实际应用中包括如下四个场景:

应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;

异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;

限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;

消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;

17、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

悲观锁:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。

乐观锁:

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,

其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

18、ArrayList 和 LinkedList 的区别在哪里?

数据结构实现:ArrayList :基于数组,便于按 index 访问,超过数组需要扩容,扩容成本较高。LinkedList:使用链表实现,无需扩容。

随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。

增加和删除效率:在非首尾的增删操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据的下标。

内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

线程安全:ArrayList 和 LinkList 都是不同步的,不保证线程安全。

综合来说,需要频繁读取集合中的元素时,更推荐使用 Arrayist,而在增删操作较多时,更推荐使用 LinkedList。

LinkedList 的双向链表是链表的一种,它的每个数据结点中都有 2 个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便的访问它的前驱结点和后继结点。

19、谈谈你对 SQL 注入式攻击的理解?

所谓 SQL 注入式攻击,就是攻击者把 SQL 命令插入到 Web 表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的 SQL 命令。

如何防范 SQL 注入式攻击?

在利用表单输入的内容构造 SQL 命令之前,把所有输入内容过滤一番就可以了。过滤输入内容可以按多种方式进行。

对于动态构造 SQL 查询的场合

a. 替换单引号,即把所有单独出现的单引号改成两个单引号,防止攻击者修改 SQL 命令的含义。

b. 删除用户输入内容中的所有连字符

c. 对于用来执行查询的数据库帐户,限制其权限。用不同的用户帐户执行查询、插入、更新、删除操作。

用存储过程来执行所有的查询。

限制表单或查询字符串输入的长度。

检查用户输入的合法性。

将用户登录名称、密码等数据加密保存。

检查提取数据的查询所返回的记录数量。

20、数据库事务的特性?

原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行。

一致性或可串性。事务的执行使得数据库从一种正确状态转换成另一种正确状态

隔离性。在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务,

持久性。事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。

21、Redis 如何做内存优化?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面.

22、缓存穿透,缓存击穿,缓存雪崩都是咋回事?解决办法?

缓存穿透

问题:大量并发查询不存在的 KEY,在缓存和数据库中都不存在,同时给缓存和数据库带来压力。

原因:一般而言,缓存穿透有 2 种可能性:业务数据被误删,导致缓存和数据库中都没有数据。恶意进行 ddos 攻击。

分析:为什么会多次透传呢?不存在 一直为空,需要注意让缓存能够区分 KEY 不存在和查询到一个空值。

解决办法:缓存空值的 KEY,这样第一次不存在也会被加载会记录,下次拿到有这个KEY。Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在,如果布隆过滤器中没有查到这个数据,就不去数据库中查。在处理请求前增加恶意请求检查,如果检测到是恶意攻击,则拒绝进行服务。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。

缓存击穿

问题:某个 KEY 失效的时候,正好有大量并发请求访问这个 KEY。

分析:跟穿透其实很像,属于比较偶然的。

解决办法:KEY 的更新操作添加全局互斥锁。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。

缓存雪崩

问题:当某一时刻发生大规模的缓存失效的情况,导致大量的请求无法获取数据,从而将流量压力传导到数据库上,导致数据库压力过大甚至宕机。

原因:一般而言,缓存雪崩有 2 种可能性:大量的数据同一个时间失效:比如业务关系强相关的数据要求同时失效 Redis 宕机 分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存数据同一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合适,数据要均匀分享,缓存服务器要多台高可用。

解决办法:更新策略在时间上做到比较平均。如果数据需要同一时间失效,可以给这批数据加上一些随机值,使得这批数据不要在同一个时间过期,降低数据库的压力。使用的热数据尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现高可用。做好主从的部署,当主节点挂掉后,能快速的使用从结点顶上。实现熔断限流机制,对系统进行负载能力控制。对于非核心功能的业务,拒绝其请求,只允许核心功能业务访问数据库获取数据。服务降价:提供默认返回值,或简单的提示信息。

23、数组和链表的区别?当数组内存过大时会出现什么问题?链表增删过多会出现的什么问题?

数组静态分配内存,链表动态分配内存;

数组事先定义固定的长度,不能适应数据动态的增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;

链表动态地进行存储分配,可以适应数据动态地增减的情况l 数组在内存中连续,链表不连续;

数组元素在栈区,链表元素在堆区;

(静态)数组从栈中分配空间,对于程序员方便快速,但是自由度小;

链表从堆中分配空间,自由度大但是申请管理比较麻烦。

数组利用下标定位,时间复杂度为 O(1),链表定位元素时间复杂度 O(n);

数组插入或删除元素的时间复杂度 O(n),链表的时间复杂度 O(1)。

当数组内存过大时会出现什么问题(堆内存溢出),链表增删过多会出现的什么问题(大量内存碎片)

24、常见排序算法和分别的复杂度?

冒泡排序,O(n2),通过重复走完数组的所有元素,通过两两比较,直到没有数可以交换的时候结束这个数,再到下个数,直到整个数组排好顺序。

插入排序,O(n2),每次从未排好序的数据堆中拿出一个数,插入到已排好序的数据队列的正确位置。

选择排序,O(n2),每次从未排好序的数据堆中找到最小的数,插入到已排好序的数据队列的头部。

快速排序,O(N*logN),以数据堆中的一个数为标准,将数据堆分为小于等于和大于该数的两堆,对于分割后的两堆数再分别利用上述方法进行分割,以此类推,直到堆中只有一个数为止。

堆排序,O(N*logN),将数据堆中的数两两组队排序,对于排序好的这些子堆再两两组队排序,以此类推,直到只剩下一个堆。

归并排序,O(N*logN),基于堆的排序算法,分为最大堆和最小堆。排序分为两个过程堆的构造和堆的排序。

25、jdk 1.8 的 JVM 内存划分模型 ,堆和栈的区别

方法区(method):被所有的线程共享。方法区包含所有的类信息和静态变量。(运行时常量池)

堆(heap):被所有的线程共享,存放对象实例以及数组,Java 堆是 GC 的主要区域。

栈(stack):每个线程包含一个栈区,栈中保存一些局部变量等。(本地局部变量、操作数栈、动态链接、返回地址)

程序计数器:是当前线程执行的字节码的行指示器。

本地方法栈

26、简单描述 MySQL 中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响(从读写两方面)?

索引是一种特殊的文件(InnoDB 数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。

普通索引(由关键字 KEY 或 INDEX 定义的索引)的唯一任务是加快对数据的访问速度。

普通索引允许被索引的数据列包含重复的值。如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字 UNIQUE 把它定义为一个唯一索引。也就是说,唯一索引可以保证数据记录的唯一性。

主键,是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。

索引可以覆盖多个数据列,如像 INDEX(columnA, columnB)索引,这就是联合索引。

索引可以极大的提高数据的查询速度,但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件。

27、I/O 模型有哪几种?

阻塞 I/O, 非阻塞 I/O 模型,I/O 复用模型,信号驱动 I/O 模型 ,异步 I/O 模型。

28、当你用浏览器打开一个链接的时候,计算机做了哪些工作步骤?

域名解析–> 发起 TCP 的 3 次握手 –> 建立 TCP 连接后发起 http 请求 –> 服务器响应

http 请求–>浏览器得到 html 代码 –> 浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等) –> 浏览器对页面进行渲染呈现给用户 。

29、虚拟 DOM 的优劣如何?

优点:

保证性能下限: 虚拟 DOM 可以经过 diff 找出最小差异,然后批量进行 patch,这种操作虽然比不上手动优化,但是比起粗暴的 DOM 操作性能要好很多,因此虚拟 DOM 可以保证性能下限

无需手动操作 DOM: 虚拟 DOM 的 diff 和 patch 都是在一次更新中自动进行的,我们无需手动操作 DOM,极大提高开发效率

跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟DOM 可以进行更方便地跨平台操作,例如服务器渲染、移动端开发等等

缺点:

无法进行极致优化: 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化,比如 VScode 采用直接手动操作 DOM 的方式进行极端的性能优化。

30、幻读是什么,用什么隔离级别可以防止幻读?

幻读是一个事务在前后两次查询同一个范围的时候、后一次查询看到了前一次查询未看到的行。

在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。

SERIALIZABLE(可串行化)可以防止幻读:最高的隔离级别,完全服从 ACID 的隔离级别。

所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。

0 人点赞