查看原文
其他

腾讯也开始了,要冲就早点冲!

小林coding 小林coding 2024-04-19

图解学习网站:https://xiaolincoding.com

大家好,我是小林。

昨天下午看到腾讯也开展24 届春招/25 届春招实习了,而且目标暑期实习是招 5000 个,这数量有点多啊,难道回暖了一点?互联网又可以了?

这一两周基本互联网大厂公司都已经全面进展到春招环节了,24 届/25 届的同学,要好好准备一下,早点投起来,早点投卷的人少,机会大一些。

不要准备到 90-100 分才敢面试,实际上 70 分左右就可以去投递面试了,边面试边查漏补缺的效率是最快的。

这次跟大家分享一位同学腾讯校招的 Java 后端面经,考察的内容不算特别多,但是都是比较基础的问题,有一些都是经典的问题,值得大家再次复习一波。

考察的知识内,我帮大家罗列了一下:

  • 网络:HTTPS、对称加密与非对称加密

  • Java:hashmap、volatile、synchronized、乐观锁、springboot

  • MySQL&Redis:缓冲雪崩、缓冲一致性、索引失效

  • 算法:lru、接雨水

网络

对称加密和非对称加密的区别?

对称加密只使用一个密钥进行加解密,优点是运算速度快,缺点是密钥必须保密,无法做到安全的密钥交换。

非对称加密使用两个密钥:公钥和私钥。公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。公钥和私钥都可以用来加密和解密,流程的不同,意味着目的也不相同:

  • 公钥加密,私钥解密。这个目的是为了保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容;
  • 私钥加密,公钥解密。这个目的是为了保证消息不会被冒充,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。

一般我们不会用非对称加密来加密实际的传输内容,因为非对称加密的计算比较耗费性能的。

你分别了解哪些对称加密和非对称加密的算法?

对称加密算法包括:

  • AES:对称加密算法中最流行和广泛使用的算法之一,支持不同的密钥长度(如AES-128、AES-256)。

非对称加密算法包括:

  • RSA:最常见的非对称加密算法,用于数据加密和数字签名。
  • ECC:基于椭圆曲线的非对称加密算法,具有较高的安全性和效率,适用于移动设备等资源受限的环境。

在信息传输过程中,Https用的是对称加密还是非对称加密?

混合加密

HTTPS 采用的是对称加密非对称加密结合的「混合加密」方式:

  • 在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密。
  • 在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。

HTTPS 四次握手过程说一下

SSL/TLS 协议基本流程:

  • 客户端向服务器索要并验证服务器的公钥。
  • 双方协商生产「会话秘钥」。
  • 双方采用「会话秘钥」进行加密通信。

前两步也就是 SSL/TLS 的建立过程,也就是 TLS 握手阶段。

基于 RSA 算法的 TLS 握手过程比较容易理解,所以这里先用这个给大家展示 TLS 握手过程,如下图:

HTTPS 连接建立过程

TLS 协议建立的详细流程:

1. ClientHello

首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。

在这一步,客户端主要向服务器发送以下信息:

(1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。

(2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。

(3)客户端支持的密码套件列表,如 RSA 加密算法。

2. SeverHello

服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容:

(1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。

(2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。

(3)确认的密码套件列表,如 RSA 加密算法。

(4)服务器的数字证书。

3.客户端回应

客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。

如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:

(1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。

(2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。

(3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。

上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。

服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」

4. 服务器的最后回应

服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。

然后,向客户端发送最后的信息:

(1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。

(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。

至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

Https客户端校验证书的细节?

如下图图所示,为数字证书签发和验证流程:

img

CA 签发证书的过程,如上图左边部分:

  • 首先 CA 会把持有者的公钥、用途、颁发者、有效时间等信息打成一个包,然后对这些信息进行 Hash 计算,得到一个 Hash 值;
  • 然后 CA 会使用自己的私钥将该 Hash 值加密,生成 Certificate Signature,也就是 CA 对证书做了签名;
  • 最后将 Certificate Signature 添加在文件证书上,形成数字证书;

客户端校验服务端的数字证书的过程,如上图右边部分:

  • 首先客户端会使用同样的 Hash 算法获取该证书的 Hash 值 H1;
  • 通常浏览器和操作系统中集成了 CA 的公钥信息,浏览器收到证书后可以使用 CA 的公钥解密 Certificate Signature 内容,得到一个 Hash 值 H2 ;
  • 最后比较 H1 和 H2,如果值相同,则为可信赖的证书,否则则认为证书不可信。

但事实上,证书的验证过程中还存在一个证书信任链的问题,因为我们向 CA 申请的证书一般不是根证书签发的,而是由中间证书签发的,比如百度的证书,从下图你可以看到,证书的层级有三级:

对于这种三级层级关系的证书的验证过程如下:

  • 客户端收到 baidu.com 的证书后,发现这个证书的签发者不是根证书,就无法根据本地已有的根证书中的公钥去验证 baidu.com 证书是否可信。于是,客户端根据 baidu.com 证书中的签发者,找到该证书的颁发机构是 “GlobalSign Organization Validation CA - SHA256 - G2”,然后向 CA 请求该中间证书。
  • 请求到证书后发现 “GlobalSign Organization Validation CA - SHA256 - G2” 证书是由 “GlobalSign Root CA” 签发的,由于 “GlobalSign Root CA” 没有再上级签发机构,说明它是根证书,也就是自签证书。应用软件会检查此证书有否已预载于根证书清单上,如果有,则可以利用根证书中的公钥去验证 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,如果发现验证通过,就认为该中间证书是可信的。
  • “GlobalSign Organization Validation CA - SHA256 - G2” 证书被信任后,可以使用 “GlobalSign Organization Validation CA - SHA256 - G2” 证书中的公钥去验证 baidu.com 证书的可信性,如果验证通过,就可以信任 baidu.com 证书。

在这四个步骤中,最开始客户端只信任根证书 GlobalSign Root CA 证书的,然后 “GlobalSign Root CA” 证书信任 “GlobalSign Organization Validation CA - SHA256 - G2” 证书,而 “GlobalSign Organization Validation CA - SHA256 - G2” 证书又信任 baidu.com 证书,于是客户端也信任 baidu.com 证书。

总括来说,由于用户信任 GlobalSign,所以由 GlobalSign 所担保的 baidu.com 可以被信任,另外由于用户信任操作系统或浏览器的软件商,所以由软件商预载了根证书的 GlobalSign 都可被信任。

操作系统里一般都会内置一些根证书,比如我的 MAC 电脑里内置的根证书有这么多:

这样的一层层地验证就构成了一条信任链路,整个证书信任链验证流程如下图所示:

怎么防止下载的文件被劫持和篡改?

可以采取以下措施:

  • 使用HTTPS下载:确保下载链接使用HTTPS协议,通过SSL加密传输数据,防止中间人攻击和数据篡改。

  • 验证文件完整性:在下载文件后,使用哈希算法(如MD5、SHA-256)计算文件的哈希值,与官方提供的哈希值进行比对,验证文件是否被篡改。

Java

Hashmap的put流程?

HashMap

HashMap的put()方法用于向HashMap中添加键值对。当调用HashMap的put()方法时,会按照以下详细流程执行:

第一步:根据要添加的键的哈希码计算在数组中的位置(索引)。

第二步:检查该位置是否为空(即没有键值对存在)

  • 如果为空,则直接在该位置创建一个新的Entry对象来存储键值对。将要添加的键值对作为该Entry的键和值,并保存在数组的对应位置。将HashMap的修改次数(modCount)加1,以便在进行迭代时发现并发修改。

第三步:如果该位置已经存在其他键值对,检查该位置的第一个键值对的哈希码和键是否与要添加的键值对相同?

  • 如果相同,则表示找到了相同的键,直接将新的值替换旧的值,完成更新操作。

第四步:如果第一个键值对的哈希码和键不相同,则需要遍历链表或红黑树来查找是否有相同的键:

如果键值对集合是链表结构:

  • 从链表的头部开始逐个比较键的哈希码和equals()方法,直到找到相同的键或达到链表末尾。
  • 如果找到了相同的键,则使用新的值取代旧的值,即更新键对应的值。
  • 如果没有找到相同的键,则将新的键值对添加到链表的头部。

如果键值对集合是红黑树结构:

  • 在红黑树中使用哈希码和equals()方法进行查找。根据键的哈希码,定位到红黑树中的某个节点,然后逐个比较键,直到找到相同的键或达到红黑树末尾。
  • 如果找到了相同的键,则使用新的值取代旧的值,即更新键对应的值。
  • 如果没有找到相同的键,则将新的键值对添加到红黑树中。

第五步:检查链表长度是否达到阈值(默认为8):

  • 如果链表长度超过阈值,且HashMap的数组长度大于等于64,则会将链表转换为红黑树,以提高查询效率。

第六步:检查负载因子是否超过阈值(默认为0.75):

  • 如果键值对的数量(size)与数组的长度的比值大于阈值,则需要进行扩容操作。

第七步:扩容操作:

  • 创建一个新的两倍大小的数组。
  • 将旧数组中的键值对重新计算哈希码并分配到新数组中的位置。
  • 更新HashMap的数组引用和阈值参数。

第八步:完成添加操作。

需要注意的是,HashMap中的键和值都可以为null

此外,HashMap是非线程安全的,如果在多线程环境下使用,需要采取额外的同步措施或使用线程安全的ConcurrentHashMap。

Volatile 和synchronized的区别是什么?

作用:

  • synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
  • volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。

区别:

  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;
  • 从内存可见性角度,volatile读相当于加锁,volatile写相当于解锁;
  • synchronized既能够保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。

乐观锁如何实现,有哪些缺点?

观锁通过在更新数据时先检查数据版本号(或时间戳等),若版本号匹配则更新数据,否则拒绝更新,实现并发控制。实现方式包括:

  1. 添加版本字段:在数据表中增加一个版本字段,每次更新数据时增加版本号。
  2. 检查版本号:在更新数据时,先查询当前版本号,然后在更新时检查版本号是否匹配。

乐观锁的缺点是在高并发场景下,冲突较多,可能导致大量的重试操作。所以,在数据竞争非常激烈的环境中,乐观锁可能不太适用。

介绍一下Springboot Starter的工作机制?

Spring Boot  在启动的时候会干这几件事情:

  1. Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。
  2. 根据 spring.factories 配置加载 AutoConfigure 类
  3. 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context

总结一下,其实就是 Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕后,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可。

MySQL&Redis

缓存雪崩是什么?怎么解决?

通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

图片

那么,当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

图片

可以看到,发生缓存雪崩有两个原因:

  • 原因一:大量数据同时过期。解决方式:
    • 均匀设置过期时间:如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
    • 互斥锁:当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
    • 后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新
  • 原因二:Redis 故障宕机。解决方式:
    • 服务熔断机制:因为 Redis 故障宕机而导致缓存雪崩问题时,我们可以启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行,然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。服务熔断机制是保护数据库的正常允许,但是暂停了业务应用访问缓存服系统,全部业务都无法正常工作
    • 构建 Redis 缓存高可靠集群:还可以通过主从节点的方式构建 Redis 缓存高可靠集群。如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。

多级缓存如何保证数据一致性?

数据库缓存一致的5个方案:

方案一

通过redis的过期时间来更新缓存,mysql 数据库更新不会触发redis 更新,只有当redis的key过期后才会重新加载

这种方案的缺点

  • 数据不一致的时间较长,会造成一定的脏数据

  • 完全依赖过去时间,过期时间太短缓存更新太频繁,过长容易有太长时间更新延迟。

方案二

在方案一的基础上扩展,让key 的过期时间做兜底,在更新mysql 的同时也会更新redis.

这种方案的缺点:如果更新mysql 成功,更新redis 失败,又会成为方案一。

方案三

在方案二的基础上,对redis 的更新操作进行优化,增加消息队列,转为异步更新redis 数据, 将redis 的更新操作交给MQ ,队列来保证可靠性,异步更新redis。

这种方案的缺点

  • 解决不了时序的问题,如果有多个业务实例对同一条数据进行更新,数据更新的先后顺序可能会乱。

  • 引入mq ,增加的系统复杂性,增加mq的维护成本。

方案四

将mysql和redis更新放在一个事务中操作,这样能保证达到强一致性。

这种方案的缺点:

  • mysql或者redis任何一个环节出问题,都会造成数据回滚或者撤销。

  • 如果网络出现超时,不仅可能会造成数据回滚或者撤销,还会有并发问题。

方案五:

通过订阅mysql的Binlog 日志来更新redis, 把我们搭建的mq消费服务,作为mysql的一个salve ,订阅Binlog ,解析出更新的内容,再更新redis

这种方案的缺点:要单独搭建一个同步服务,并且来引入BinLog同步机制,成本较大。

mysql索引失效的几种情况?

  • 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
  • 当我们在查询条件中对索引列使用函数,就会导致索引失效。
  • 当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
  • MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
  • 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。

算法

  • 手写LRU(腾讯真喜欢问这个算法啊,大家多练啊,看到好几个同学面腾讯都是这个)

  • 手撕:接雨水

推荐阅读:
后端突击训练营,又开始卷了!
春招,启动!
米哈游,顺利进入二面!
真等不及了,冲阿里去了!
继续滑动看下一个
向上滑动看下一个

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

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