【字节面经】压力拉满|0925
- 自我介绍
- 你在深圳吗?为什么投上海的岗位?
- 浏览器输入URL后会发生什么?
- 描述一下三次握手?丢失时重发为什么倍增重发等待时间?
- TCP传输哪里会受到攻击?如何进行改进?比如说DDoS攻击;
- Redis用在那些场景下?你是怎么设计这个数据结构的?
- 如何保证MySQL和你的Redis的缓存一致性?
- 分布式锁加锁和解锁的过程?你如何设计?
- 你的MQ在项目中怎么用的?如何避免消息的重复消费?全局唯一id如何生成?了解其他的吗?
- 你平时是如何学习的?
- 手撕:力扣3.无重复字符的最长子串
1. 浏览器输入URL后会发生什么?
URL解析
浏览器首先会对输入的URL进行解析,提取出协议(如HTTP、HTTPS)、域名(或IP地址)、端口号(如果未指定,则默认为协议的默认端口,如HTTP默认为80,HTTPS默认为443)、路径、查询参数以及片段标识符(通常用于指定页面中的锚点位置,但不会被发送到服务器)。
缓存检查
在进行DNS解析之前,浏览器会先检查本地缓存(包括浏览器缓存和系统缓存)中是否已有该URL的解析结果或页面内容。如果缓存命中,则直接加载缓存中的页面,跳过后续步骤,以提高加载速度。
DNS解析
如果URL中包含的是域名而非IP地址,且缓存未命中,浏览器将进行DNS解析。这一过程可能涉及本地hosts文件、本地DNS缓存、操作系统DNS缓存、ISP DNS缓存以及递归查询根域名服务器、顶级域名服务器和权威域名服务器,直至找到对应的IP地址。
建立TCP连接
浏览器使用解析得到的IP地址和端口号,通过TCP协议与服务器建立连接。这一过程包括三次握手,确保双方可以稳定地进行数据传输。
发送HTTP请求
连接建立后,浏览器向服务器发送HTTP请求。请求中包含请求方法(如GET、POST)、请求头部(包含浏览器信息、Cookie等)、空行以及请求体(对于POST请求)。
服务器处理请求
服务器接收到请求后,根据请求的内容(如URL路径、查询参数等)进行相应的处理。这可能包括读取文件、执行数据库查询、处理用户输入等,并最终生成响应。
服务器响应
服务器处理完请求后,向浏览器发送HTTP响应。响应中包含状态码(如200表示成功)、响应头部(包含内容类型、缓存控制等)以及响应体(所请求的资源内容)。
浏览器接收并渲染响应
浏览器接收到响应后,首先解析响应内容。如果响应体是HTML文档,浏览器会解析HTML并构建DOM树。同时,解析CSS并构建CSSOM树。随后,将DOM树和CSSOM树合并成渲染树,根据渲染树进行页面布局和绘制,最终将页面展示给用户。
断开连接
在HTTP/1.1及以后的版本中,TCP连接可能会保持一段时间以便进行后续的请求,这称为持久连接。然而,在某些情况下,如请求完成且不再需要进一步的通信时,浏览器会发起TCP四次挥手以断开连接。
2. 描述一下三次握手?丢失时重发为什么倍增重发等待时间?
三次握手
三次握手是TCP/IP协议中用于建立可靠的传输连接的通信过程。这一过程的目的是确保通信双方(客户端和服务器)的接收能力和发送能力都正常,以及为数据传输准备好必要的序列号和窗口大小等参数。具体过程如下:
第一次握手(SYN):
客户端发送一个带有SYN(同步)标志的数据包给服务器端,表明客户端请求建立连接,并告知服务器端自己的初始序列号(ISN)。
此时,客户端进入SYN_SENT状态,等待服务器的响应。
第二次握手(SYN+ACK):
服务器端收到客户端的SYN数据包后,会回复一个带有SYN/ACK(同步/确认)标志的数据包给客户端。
该数据包中,SYN位用于指明服务器端收到了客户端的请求,并且服务器端也想和客户端建立连接;ACK位则确认了服务器端收到了客户端的请求。
同时,服务器端会为建立的连接分配资源,并指定自己的初始序列号(ISN)。
此时,服务器端进入SYN_RECV状态,等待客户端的最终确认。
第三次握手(ACK):
客户端收到服务器端的SYN/ACK数据包后,会向服务器端发送一个带有ACK(确认)标志的数据包。
该数据包中,ACK位用于确认服务器端收到了客户端的同步请求,同时客户端会向服务器端传递自己分配到的初始序列号。
此时,客户端进入ESTABLISHED(已建立连接)状态。服务器端收到客户端的ACK数据包后,也进入ESTABLISHED状态。
至此,TCP连接建立成功,可以开始进行数据的传输。
丢失时重发倍增重发等待时间
TCP在传输过程中,如果数据包或确认包丢失,会触发重传机制。TCP重传超时时间(RTO,Retransmission Timeout)的设置是为了在数据包丢失时,通过重传来保证数据的可靠传输。当数据包第一次丢失时,TCP会使用一个默认的RTO时间进行重传。如果重传的数据包再次丢失,TCP会采用倍增(指数退避)的方式增加重传的等待时间。
原因如下:
避免网络拥塞:
网络拥塞是数据包丢失的一个常见原因。在拥塞状态下,网络可能变得特别不稳定,继续以原来的频率发送数据可能会进一步加剧拥塞问题。
通过增加重传的等待时间,TCP给网络留出更多时间来缓解拥塞,减少不必要的重传和进一步的拥塞。
提高传输效率:
TCP协议设计的目标是既要保证数据的可靠传输,又要尽量提高传输效率。
如果在第一次丢包后就立即倍增RTO,可能会造成不必要的延迟,影响传输效率。因此,TCP在第一次丢包时使用默认的RTO时间,只有在后续重传再次失败时才采用倍增策略。
适应网络环境:
网络环境是动态变化的,TCP通过倍增重传等待时间的方式,可以更好地适应不同的网络环境。
在网络状况较好的情况下,重传时间相对较短;在网络状况较差的情况下,重传时间会逐渐增加,以减少重传对网络资源的占用。
3. TCP传输哪里会受到攻击?如何进行改进?比如说DDoS攻击;
TCP可能受到的攻击类型
TCP SYN Flood攻击
攻击原理:利用TCP三次握手过程中存在的漏洞,攻击者向目标主机发送大量伪造的TCP SYN连接请求,但不完成三次握手的最后一步(不发送ACK报文)。这导致目标主机在SYN半连接队列中积累大量无效连接,最终耗尽系统资源,无法响应正常的服务请求。
防御措施:
使用防火墙过滤恶意SYN数据包。
启用TCP SYN Cookie机制,允许在不存储连接状态的情况下验证SYN数据包的合法性。
限制每个IP地址的TCP连接数。
TCP RST攻击
攻击原理:攻击者通过伪造带有RST(重置)标志的TCP数据包,强制关闭受害主机上的合法TCP连接,从而中断通信。
防御措施:
加强网络访问控制,限制非授权设备的网络访问。
使用加密和认证机制保护TCP会话,防止会话被伪造和劫持。
TCP会话劫持攻击
攻击原理:攻击者通过监听和猜测TCP序列号,假冒合法用户接管已建立的TCP会话,进行非法通信。
防御措施:
使用加密技术(如SSL/TLS)保护会话数据,防止会话被窃听和劫持。
启用IPSec等网络层安全协议,提供数据完整性和认证保护。
DDoS攻击
攻击原理:DDoS攻击通过控制大量分布式的攻击源,向目标主机发送海量数据或请求,导致目标主机资源耗尽,无法响应正常服务请求。
防御措施:
使用高性能的网络设备和硬件防火墙,确保网络带宽和处理能力不被轻易耗尽。
部署DDoS防护系统,如流量清洗设备、高防IP等,过滤和清洗恶意流量。
配置合理的路由策略,将DDoS流量分散到多个节点,减轻单一节点的压力。
启用资源配额和限流机制,限制单个IP地址或用户的资源使用量,防止资源被恶意占用。
改进TCP传输安全性
增强TCP协议本身的安全性
修改TCP协议中ISN(初始序列号)的生成算法,增加随机性和复杂性,降低被猜测的风险。
引入更严格的会话认证和加密机制,确保通信双方的身份和数据完整性。
加强网络基础设施的安全防护
定期更新和升级网络设备、操作系统和应用程序,修复已知漏洞。
部署多层次的安全防御体系,包括防火墙、入侵检测/防御系统(IDS/IPS)、安全审计系统等。
4. Redis用在那些场景下?
1. 缓存
- 作用:Redis最常见的用途是作为缓存层,以减轻数据库负载,提高数据访问速度。
- 适用场景:存储频繁访问的数据,如网页内容、会话状态、API调用结果等。
- 优势:通过缓存热门数据,减少对后端数据存储的请求,显著提升应用性能和响应速度。
2. 会话管理
- 作用:在Web应用中,Redis可用于存储用户的会话信息,如登录状态、购物车内容等。
- 优势:由于其快速的读写速度,Redis非常适合需要快速访问和更新会话数据的场景。
3. 消息队列
- 作用:Redis支持发布/订阅模式,可以用作轻量级的消息队列系统。
- 适用场景:异步任务处理、事件通知等。
- 优势:实现低延迟的消息传递,支持多种消息模式,如发布/订阅、队列等。
4. 计数器和排行榜
- 作用:Redis的原子增减操作非常适合用于计数器和排行榜应用。
- 适用场景:社交媒体的点赞数、阅读数、排名等。
- 优势:确保数据的一致性和准确性,支持高并发访问。
5. 分布式锁
- 作用:在分布式系统中,Redis可用于实现分布式锁,确保多个节点之间共享资源的一致性。
- 优势:通过锁机制,防止多个客户端同时修改同一资源,保证数据的一致性。
6. 地理位置应用
- 作用:Redis支持地理空间数据,可用于构建地理位置应用。
- 适用场景:附近的位置查找、位置跟踪等。
- 优势:提供高效的地理位置查询和推荐功能。
5. 如何保证MySQL和你的Redis的缓存一致性?
保证 MySQL 和 Redis 缓存一致性的方法有几种,常见的策略包括:
写入时缓存:
在对 MySQL 进行写入操作时,同时更新 Redis 缓存。这可以确保缓存中的数据与数据库中的数据一致。
过期时间:
为缓存设置合理的过期时间(TTL)。这样,缓存数据在一定时间后会被自动清除,确保下次读取时从数据库中获取最新数据。
延迟双删:
在更新或删除 MySQL 数据时,先删除 Redis 缓存,再稍等一段时间后再次删除 Redis 中的相同缓存,以防在高并发情况下缓存未及时失效。
事件驱动架构:
使用事件源或订阅发布机制,实时更新缓存。当数据库发生变化时,触发事件来更新缓存。
读写分离:
对于读取操作,优先从缓存中获取,如果缓存未命中,则从数据库读取并更新缓存。确保在写入时同时更新数据库和缓存。
强一致性策略:
如果对一致性要求非常高,可以考虑使用分布式事务或其他强一致性方案,但这可能会影响系统性能。
6. 分布式锁加锁和解锁的过程?你如何设计?
分布式锁的加锁过程通常包括以下几个步骤:
获取锁:
客户端向分布式锁服务(如Redis、ZooKeeper等)发送加锁请求。
锁服务检查锁的状态,如果锁未被其他客户端持有,则将该锁分配给请求客户端。
分配锁时,通常会设置一个唯一标识符(如UUID)来标识锁的持有者,并设置一个过期时间以避免死锁。
设置唯一标识符和过期时间:
使用如Redis的
SET key value NX PX milliseconds
命令,其中key
是锁的名称,value
是客户端生成的唯一标识符,NX
表示仅当key
不存在时才设置,PX milliseconds
设置锁的过期时间(毫秒)。这个步骤确保了加锁的原子性,即在同一时间只有一个客户端能成功加锁。
处理加锁失败:
如果锁已被其他客户端持有,则当前客户端加锁失败。
客户端可以选择等待锁释放(通过轮询或订阅锁释放的通知),或者立即返回失败并采取相应的处理措施。
分布式锁解锁过程
验证锁的持有者:
客户端在尝试解锁时,需要首先验证自己是否是当前锁的持有者。
删除锁:
如果验证成功,客户端将向锁服务发送删除锁的请求。
分布式锁的设计考虑
在设计分布式锁时,需要考虑以下几个方面:
- 互斥性:确保在任何时刻只有一个客户端能持有锁。
- 安全性:防止死锁和错误解锁的情况发生。通过设置过期时间和验证锁的持有者来确保安全性。
- 可用性:确保在锁服务部分节点故障时,系统仍能正常工作。这通常通过锁服务的冗余部署和高可用性设计来实现。
- 性能:优化锁服务的性能,以减少加锁和解锁操作的延迟。这可以通过使用高效的存储系统和优化锁的实现算法来实现。
- 可扩展性:随着系统规模的扩大,锁服务需要能够平滑地扩展以满足更高的并发需求。
7. MQ如何避免消息的重复消费?全局唯一id如何生成?
为了避免消息的重复消费,可以采取以下几种策略:
消息确认机制:
大多数消息队列都支持消息确认机制。消费者在处理完消息后,需要显式地告知MQ服务端消息已被成功处理。例如,在RabbitMQ中,使用acknowledgment模式,在消费者收到消息后调用basicAck方法确认消息。如果消费者未能在一定时间内确认消息,则消息会被重新发送。
Kafka则通过提交消费偏移量(offset)来确保消息只被消费一次。每个消费者组都有自己的偏移量,消费完消息后提交偏移量,防止重复消费。
幂等性消费:
对于Kafka等没有内置消息确认机制的消息队列,可以通过实现幂等性消费来避免重复消费。幂等性指的是对同一操作发起多次请求具有相同的结果,即无论执行多少次都不会改变结果。在设计业务逻辑时,可以确保即使消息被重复消费也不会导致错误的结果。
全局唯一ID:
为每条消息赋予一个全局唯一的ID,消费时先检查该ID是否已处理过。这种方法可以在数据库或缓存中记录已处理的消息ID,以此来避免重复消费。
状态校验:
在处理消息之前,先检查业务状态,只有在符合条件的情况下才处理消息。这可以确保即使消息被重复发送,也不会因为业务状态的不一致而导致重复处理。
分布式锁:
对于涉及到关键资源操作的消息处理,可以使用分布式锁来锁定相关资源,确保同一时间只有一个消费者能够处理这条消息。
数据库事务:
对于涉及到数据库操作的消息处理,可以使用数据库事务来保证数据的一致性。即使消息被重复消费,由于事务的原子性,最终只会有一条记录被持久化。
全局唯一ID的生成
在分布式系统中,生成全局唯一ID是一个重要且常见的需求。以下是几种常见的全局唯一ID生成方法:
UUID:
UUID是一种广泛使用的全局唯一标识符,由JDK提供生成方法。UUID是基于机器地址、时间戳等信息生成的,具有全局唯一性。但UUID的缺点是长度较长(128位),作为主键时索引效率可能较低。
数据库自增序列:
使用数据库提供的自增序列生成全局唯一ID。但这种方法存在单点故障和性能瓶颈的问题,尤其是在高并发场景下。
时间戳+机器标识+序列号:
结合时间戳、机器标识和序列号来生成全局唯一ID。这种方法可以根据实际需求调整各部分的位数,以满足不同的业务场景。例如,Twitter的Snowflake算法就是采用这种思路。
Redis生成:
利用Redis的原子操作来生成唯一ID。Redis提供了INCR命令等原子操作,可以在分布式环境下生成全局唯一的序列号。但这种方法依赖于Redis的稳定性和性能。
8. 手撕:力扣3.无重复字符的最长子串
可以采用滑动窗口的方法。
初始化:设置两个指针,一个指向窗口的起始位置,另一个指向窗口的结束位置。同时,需要一个哈希表来存储窗口中每个字符最近出现的位置。
右指针扩展:遍历字符串,右指针不断向右移动,将遇到的字符加入哈希表,并更新其位置。如果遇到重复字符,则需要移动左指针以缩小窗口,直到窗口中不再包含重复字符。
更新结果:在每次移动右指针后,计算当前窗口的大小,并与之前的最大长度进行比较,更新最大长度。
重复步骤2和3:直到右指针遍历完整个字符串。
算法实现
import java.util.HashMap; import java.util.Map; public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(); int maxLength = 0; Map<Character, Integer> charIndexMap = new HashMap<>(); int left = 0; // 左指针 for (int right = 0; right < n; right++) { char c = s.charAt(right); if (charIndexMap.containsKey(c) && charIndexMap.get(c) >= left) { // 如果字符c在哈希表中且其位置大于等于左指针,说明窗口中存在重复字符 // 需要移动左指针直到窗口中不再包含重复字符 left = charIndexMap.get(c) + 1; } // 更新字符c的最新位置 charIndexMap.put(c, right); // 计算并更新最长无重复字符子串的长度 maxLength = Math.max(maxLength, right - left + 1); } return maxLength; } }
面经原帖由北雷村卖炒饭发布,答案由程序员Hasity整理
收录各个网友分享的各个公司的面经,并给出答案。