-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,330 @@ | ||
--- | ||
title: 2024.09.01字节校招一面 | ||
date: 2024-09-01 | ||
category: | ||
- 字节 | ||
- 校招 | ||
- 一面 | ||
--- | ||
|
||
# 2024.09.01字节-商品分析与技术-校招一面 | ||
|
||
## 自我介绍 | ||
|
||
## RPC协议里你都写了什么信息,为什么要自己定义RPC协议 | ||
|
||
RPC协议里你都写了什么信息: | ||
|
||
- 魔数:标识当前消息是 RPC 协议的消息,避免与其他协议的消息混淆,提高消息的可靠性。 | ||
- 版本号:用于标识当前 RPC 协议的版本,以便于后续的协议升级和兼容性管理。 | ||
- 序列化方式:标识消息体采用的序列化方式,如 Protobuf、Hessian 等,便于接收方进行正确的反序列化。 | ||
- 类型:标识当前消息的类型,如请求、响应、通知 | ||
- 状态:标识当前消息的状态,如成功、失败等 | ||
- 请求ID:标识当前消息的唯一标识符,便于接收方与对应的请求进行关联。 | ||
- 消息体长度:标识消息体的长度,便于接收方准确获取完整的消息内容(TCP有半包和粘包问题,传输信息不完整) | ||
- 消息体内容:携带实际的业务数据,如方法名、参数列表、返回值等。 | ||
|
||
为什么要自己定义RPC协议:为了性能优化: | ||
|
||
- 标准的 HTTP/REST 协议虽然使用广泛,但由于其报文头部开销较大,不适合高性能的 RPC 场景。 | ||
- 自定义的二进制协议,如 Protobuf、Thrift 等,可以大幅降低数据传输的开销,提升 RPC 的吞吐量和延迟。 | ||
|
||
## **怎么实现负载均衡的** | ||
|
||
负载均衡可以通过多种算法实现:随机、轮询、加权轮询、平滑加权轮询、一致性哈希等。 | ||
|
||
## 让你实现一个随机数查询怎么实现 | ||
|
||
回答random+权重,使用`random`函数结合权重算法,比如加权随机算法。根据每个选项的权重,累加权重后随机落点。 | ||
|
||
## random效率高吗,有没有比random效率更高的办法 | ||
|
||
`random`在某些场景下效率不够高,可以考虑使用其它更优化的算法或减少随机数生成的次数。 | ||
|
||
## 项目用到了SPI,什么是SPI,和我自己在代码里实现的方法有什么区别 | ||
|
||
SPI(Service Provider Interface)是一种服务发现机制。和手动代码实现不同,SPI提供了模块化的实现,便于扩展,专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。当接口存在于调用方这边时,这就是 **SPI ** 。由接口调用方确定接口规则,然后由不同的厂商根据这个规则对这个接口进行实现,从而提供服务。 | ||
|
||
API(Application Programming Interface) :当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 **API**。这种情况下,接口和实现都是放在实现方的包中。调用方通过接口调用实现方的功能,而不需要关心具体的实现细节。 | ||
|
||
简单来说:API是为了使用功能,SPI是为了扩展功能。 | ||
|
||
## 除子说给我写个插件扩展代码,SPI还能用来干什么 | ||
|
||
SPI不仅用于插件扩展,还可以用于模块化开发,动态装载类,降低耦合。 | ||
|
||
## **Redis锁是怎么实现的** | ||
|
||
Redis锁通常通过`SETNX`和`EXPIRE`命令组合实现,或者使用`Redisson`框架实现分布式锁。 | ||
|
||
Redis的`SETNX`命令(Set if Not Exists)可以确保只有在键不存在时,才会设置键的值。这个特性使得它可以用于抢占锁。 | ||
|
||
```sh | ||
SET lock_key unique_value NX PX 30000 | ||
``` | ||
|
||
+ `NX`:表示只有当`lock_key`不存在时,才设置成功。 | ||
+ `PX 30000`:表示过期时间为30秒(单位为毫秒)。 | ||
|
||
释放锁时,需要注意确保只有持有锁的客户端可以释放它。因此在释放时必须验证`lock_key`的值是否是当前客户端的唯一标识`unique_value`。可以使用lua脚本来实现,释放锁的流程: | ||
|
||
1. 先判断锁的值是否等于当前线程或客户端的唯一标识。 | ||
2. 如果相等,则删除锁。 | ||
|
||
```lua | ||
if redis.call("GET", KEYS[1]) == ARGV[1] then | ||
return redis.call("DEL", KEYS[1]) | ||
else | ||
return 0 | ||
end | ||
``` | ||
|
||
Redis锁存在的问题: | ||
|
||
**锁自动过期**:如果客户端在处理业务逻辑时,时间超过了锁的过期时间,锁会被Redis自动释放,其他客户端就有可能抢到这个锁,导致多个客户端并发处理同一个任务,产生数据不一致的风险。解决办法:1续期机制:通常通过一个定时任务不断地对锁的过期时间进行刷新。2.合理设定过期时间,确保业务处理时间在锁的过期时间内完成,尽量避免锁的过期释放。 | ||
|
||
**锁失效的竞态条件**:如果两个或多个客户端同时执行了`SET lock_key unique_value NX PX 30000`命令,虽然`SETNX`保证了只有一个客户端能够抢到锁,但如果客户端A持有锁的时间超过了它设定的过期时间,锁自动失效后,客户端B可能会获得锁,这时客户端A仍然可能在执行未完成的业务操作,导致并发问题。解决办法:使用**Redlock算法**,这是一种基于Redis的分布式锁算法,能够在分布式系统中确保锁的安全性和一致性。 | ||
|
||
|
||
|
||
## redis锁的value存的是什么值,为什么要存线程id,除了我说的重要人和ID其他线程解锁还有什么用 | ||
|
||
在Redis锁的实现中,锁的**value**通常存储的是当前线程或客户端的唯一标识(如线程ID、UUID、或节点ID)。存储这个唯一标识的主要目的是为了确保锁的**持有者**在解锁时能够正确地释放锁,而不会误释放其他线程或客户端的锁。 | ||
|
||
> 当一个线程A获取锁时,它在Redis中为`lock_key`存储一个唯一的值(如`UUID`或线程ID)。当线程A完成任务后,它会尝试释放锁。但此时,需要确保**只有持有锁的线程**才能释放锁。如果不存储唯一标识,任何其他线程(比如线程B)也可能会误释放锁,造成潜在的并发问题。 | ||
还有什么用: | ||
|
||
防止逻辑错误:假如某个客户端因故障(如网络问题、进程崩溃)导致它未能正常释放锁,而其他客户端不检查唯一标识就解锁,可能会出现逻辑错误(例如未完成的任务被认为已经完成)。 | ||
|
||
提高并发安全:当一个持有锁的客户端完成了任务,必须通过验证锁的`value`才能释放锁。如果不存储线程ID或唯一标识,可能会导致多个线程在不知情的情况下抢夺和释放锁,这会带来并发安全性问题。 | ||
|
||
安全解锁:通过唯一标识,每个线程或客户端在释放锁时会先检查自己是否持有该锁,只有持有锁的线程才能删除这个锁。其他线程即使试图解锁,也无法误操作,这样可以保证锁的正确释放。 | ||
|
||
|
||
|
||
## redis当锁的时候会出现什么问题 | ||
|
||
+ 锁自动过期导致的并发问题 | ||
+ 锁误删除问题 | ||
+ Redis单点故障:如果Redis是单节点部署,且该节点出现故障,那么所有的锁都会在Redis节点恢复之前无法使用,或者在节点恢复后被错误地释放,导致业务出现混乱 | ||
+ 时钟漂移问题:在分布式系统中,时钟漂移是一个常见问题。不同服务器之间的时钟可能不同步,这会影响Redis锁的过期时间。 | ||
+ 死锁:死锁是指某个客户端获取锁后,由于未正常执行解锁操作而导致其他客户端无法获取锁的问题。 | ||
+ 线程安全问题:使用Redis来实现分布式锁,某个客户端成功在主节点获取到锁,但是此时锁的信息还没有同步到从节点。如果主节点突然崩溃,系统可能会选择一个从节点提升为新的主节点,但由于同步未完成,从节点并不知晓该锁的存在。这时,其他客户端可能会重新获取这个锁,导致两个客户端持有相同的锁,从而引发**线程安全问题**。 | ||
|
||
|
||
|
||
|
||
|
||
## 为什么会出现那种群体的问题,还会出现什么具体问题 | ||
|
||
在高并发环境下,多个客户端尝试同时获取同一把锁,如果锁机制设计不当,比如锁失效时间过短、锁未能正确释放或锁机制存在漏洞,多个客户端可能会同时持有同一个锁。 | ||
|
||
|
||
|
||
|
||
|
||
## 还能用什么中间件解决刚刚说的问题,为什么有那些中间件可以 | ||
|
||
ZooKeeper是一个**分布式协调服务**,提供了可靠的**分布式锁**、**分布式队列**等功能。它通过分布式节点树(ZNode)来实现高一致性和可靠的分布式锁,适合处理分布式系统中一致性、领导选举和锁竞争等问题。 | ||
|
||
`etcd` 是一个开源的、高可用的分布式键值存储系统,提供了分布式锁、配置共享、服务发现等功能。它是Kubernetes默认的存储后端,具备**强一致性**和**高可用性**。 | ||
|
||
|
||
|
||
## RBAC模型是什么,有什么优点和缺点,没有了解TAC权限吗 | ||
|
||
**RBAC(Role-Based Access Control)** 是一种通过**角色**来控制用户访问系统资源的权限管理模型。它主要通过**用户、角色、权限**三个核心概念来管理权限。用户被赋予一个或多个角色,而角色则决定了用户能够访问的资源和执行的操作。 | ||
|
||
优点: | ||
|
||
+ **灵活性高**:角色可以根据业务需求自由创建,适应不同的用户群体,满足不同权限需求。 | ||
+ **权限分离**:可以根据角色进行权限的精细化分配,确保不同职能的用户只能访问自己权限范围内的资源,提高系统的安全性。 | ||
|
||
缺点: | ||
|
||
+ **难以精细控制**:如果同一角色下的某些用户需要不同的权限,RBAC就显得有些局限,可能需要通过创建额外的角色或其他机制进行补充。 | ||
|
||
|
||
|
||
**TAC(Trust-Based Access Control)** 或基于属性的访问控制(**ABAC, Attribute-Based Access Control**),是与RBAC相对的一种更灵活的权限管理模型。与RBAC通过角色来分配权限不同,TAC/ABAC 是基于用户、资源、环境的**属性**来动态决定是否授予访问权限。 | ||
|
||
1. **属性(Attributes)**:TAC中的关键,属性可以是用户的属性(如职位、部门)、资源的属性(如敏感性、分类)、操作的属性(如时间、操作类型),也可以是环境属性(如访问时的IP地址、时间段、地理位置等)。 | ||
2. **策略(Policies)**:TAC通过预定义的策略来控制访问。策略包含若干属性规则,只有当用户、资源和环境的属性满足策略中的条件时,用户才会被授予权限。 | ||
|
||
优点: | ||
|
||
**灵活性强**:TAC通过基于属性的访问控制,可以根据用户、资源和环境的动态变化来决定权限,灵活性远超RBAC,能够应对复杂和细粒度的权限需求。 | ||
|
||
缺点: | ||
|
||
**复杂度高**:TAC的灵活性带来的是管理和策略制定的复杂性。定义合理的属性和策略需要精心设计,并且随着系统规模的扩大,管理难度会上升。 | ||
|
||
**性能开销**:由于每次访问权限都需要动态计算是否满足属性条件,系统可能会面临较高的性能开销,尤其是在高并发环境中。 | ||
|
||
### RBAC 与 TAC 的对比 | ||
|
||
| **特性** | **RBAC** | **TAC/ABAC** | | ||
| ------------ | -------------------------------- | -------------------------------------------- | | ||
| **模型基础** | 通过角色分配权限 | 通过属性和策略分配权限 | | ||
| **灵活性** | 灵活性相对较低,难以应对复杂场景 | 灵活性极强,能够动态应对各种复杂权限需求 | | ||
| **易用性** | 简单易用,适合权限较为固定的系统 | 复杂度较高,适合需要动态权限管理的复杂系统 | | ||
| **维护难度** | 角色过多时会产生“角色爆炸”问题 | 属性和策略维护复杂,容易产生冲突 | | ||
| **适用场景** | 用户角色较为明确的企业管理系统 | 动态、复杂的权限控制场景,如金融、军事等系统 | | ||
|
||
|
||
|
||
## 什么是读写分离 | ||
|
||
**MySQL 读写分离** 是一种常见的数据库架构设计,主要用于提升数据库的性能、提高系统的可扩展性和保证数据的高可用性。在读写分离架构中,数据库中的**写操作**(如 `INSERT`、`UPDATE`、`DELETE` 等)和**读操作**(如 `SELECT` 查询)被分离到不同的数据库实例上运行,通常是通过主从复制(**Master-Slave Replication**)机制来实现。 | ||
|
||
应用程序层面或数据库代理层会将**写请求**发送到主数据库,将**读请求**发送到从数据库。 | ||
|
||
主从复制(Replication): | ||
|
||
- **主库**会将写操作的变更记录到二进制日志(**binlog**)中。 | ||
- **从库**会从主库读取二进制日志,并应用这些变更来保持数据一致性。 | ||
|
||
优点: | ||
|
||
+ **提高读性能**:在高并发系统中,读操作通常占大多数。通过将读操作分发到多个从库,能够提高系统的整体读性能,减轻主库的压力。 | ||
+ **故障隔离**:主从分离架构可以在主库出现故障时继续提供读服务,提升系统的高可用性。在主库修复期间,从库可以依然提供部分服务。 | ||
+ **负载均衡**:多个从库可以分担读请求,减少单个数据库实例的负载,实现负载均衡。 | ||
|
||
缺点: | ||
|
||
+ **数据延迟问题**:主从复制有一定的延迟,尤其是在大量写入操作时。从库的数据可能比主库**滞后**,这会导致读取从库时数据不一致的问题。 | ||
+ **数据一致性问题**:由于主库和从库之间有延迟,如果在写入之后立即读取,可能会在从库中读到旧的数据,造成读写不一致的问题。这种情况下可以使用强制走主库的策略。 | ||
+ **主库压力**:虽然读操作被分流到了从库,但主库依然需要处理所有的写操作。如果写操作的压力过大,主库性能仍然可能成为瓶颈 | ||
+ **复制故障和管理复杂度**:如果主从复制出现问题(如网络故障、配置错误),会导致从库数据与主库不一致。管理多个从库的同步状态和复制过程也增加了系统的复杂性。 | ||
|
||
|
||
|
||
## mysql是如何保证数据不丢失的 | ||
|
||
1. **事务机制(ACID)**:确保事务完整性和数据一致性。 | ||
2. **Redo Log**:记录已提交事务,崩溃后重做,防止数据丢失。 | ||
3. **Undo Log**:支持事务回滚,恢复未完成事务。 | ||
4. **Binlog**:记录所有写操作,用于恢复和主从复制。 | ||
5. **双写缓冲区**:防止数据写入时丢失,确保数据完整性。 | ||
6. **崩溃恢复**:利用日志恢复提交和回滚未完成事务。 | ||
7. **定期备份**:通过备份和日志结合进行数据恢复。 | ||
|
||
|
||
|
||
## redis为什么Zset使用跳表不使用红黑树和B+树 | ||
|
||
1. 实现简单:红黑树的插入、删除操作需要复杂的旋转和重平衡操作,以保持其平衡性。而跳表通过随机化的层次结构,只需要修改相邻节点,无需复杂的平衡操作,大大简化了实现。 | ||
|
||
2. 范围查询:跳表本质是多级索引链表,跳表的结构非常适合范围查询,红黑树需要依赖中序遍历 | ||
|
||
3. 内存更优:平衡树每个节点两个指针,跳表为$\frac{1}{1-p}$,Redis中取$p=\frac{1}{4}$,也就是1.33个节点 | ||
|
||
|
||
|
||
|
||
|
||
## 红黑树和跳表的查询问题,怎么实现 | ||
|
||
**红黑树**使用二叉查找思想,左右子树进行递归或迭代查找。 | ||
|
||
**跳表**则通过多级链表跳跃查找,查找过程横向与纵向结合。 | ||
|
||
|
||
|
||
|
||
|
||
## 什么是跳表 | ||
|
||
有序链表+多级索引 | ||
|
||
|
||
|
||
|
||
|
||
## SpringMVC的执行流程是什么,刚刚的那些组件有没有自己去实现过,需要注意什么 | ||
|
||
SpringMVC中的核心组件 | ||
(1)前端控制器:DispactherServlet | ||
(2)处理器映射器:HandlerMapping | ||
(3)处理器适配器:HandlerAdapter | ||
(4)处理器:Handler, | ||
(5)视图解析器:ViewResolver | ||
(6)视图:View | ||
|
||
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/bb11e96aae8578ca0a2077120656e9be.png#pic_center) | ||
|
||
(1)当用户通过浏览器发起一个HTTP请求,请求直接到前端控制器DispatcherServlet; | ||
(2)前端控制器接收到请求以后调用处理器映射器HandlerMapping,处理器映射器根据请求的URL找到具体的Handler,并将它返回给前端控制器; | ||
(3)前端控制器调用处理器适配器HandlerAdapter去适配调用Handler; | ||
(4)处理器适配器会根据Handler去调用真正的处理器去处理请求,并且处理对应的业务逻辑; | ||
(5)当处理器处理完业务之后,会返回一个ModelAndView对象给处理器适配器,HandlerAdapter再将该对象返回给前端控制器;这里的Model是返回的数据对象,View是逻辑上的View。 | ||
(6)前端控制器DispatcherServlet将返回的ModelAndView对象传给视图解析器ViewResolver进行解析,解析完成之后就会返回一个具体的视图View给前端控制器。(ViewResolver根据逻辑的View查找具体的View) | ||
(7)前端控制器DispatcherServlet将具体的视图进行渲染,渲染完成之后响应给用户(浏览器显示)。 | ||
|
||
|
||
|
||
## Spring优雅停机是什么,怎么实现的,如果实现要怎么做 | ||
|
||
在 `application.properties` 文件中将 `server.shutdown` 属性设置为 `graceful`,即可启用优雅停机: | ||
|
||
```properties | ||
server.shutdown=graceful | ||
``` | ||
|
||
在优雅关机时,可能有一些之前的请求仍在处理中。在这种情况下,服务器会等待这些活动请求在指定时间内完成。 | ||
|
||
可以使用 `spring.lifecycle.timeout-per-shutdown-phase` 配置属性来配置超时时间 | ||
|
||
```sh | ||
spring.lifecycle.timeout-per-shutdown-phase=1m | ||
``` | ||
|
||
|
||
|
||
## Spring什么是声明式事务,这个事务使用的时候会出现什么问题,除了失效就没别的问题吗 | ||
|
||
**事务失效**: | ||
|
||
- 非 `public` 方法无效。 | ||
- 类内部自调用时,事务不生效。 | ||
- JDK代理只适用于接口,类需使用 `CGLIB` 代理(`proxyTargetClass = true`)。 | ||
|
||
**传播行为**:使用不当的事务传播(如 `REQUIRED`, `REQUIRES_NEW`)可能导致事务边界不一致。 | ||
|
||
**异常处理**: | ||
|
||
- 默认运行时异常回滚,受检异常不会回滚,需指定 `rollbackFor`。 | ||
|
||
**延迟加载问题**:事务外访问懒加载属性会抛出 `LazyInitializationException`,需提前加载或延长事务。 | ||
|
||
**锁和超时**:并发时可能出现数据库死锁,需合理设置 `timeout` 和锁机制。 | ||
|
||
|
||
|
||
## Springbean是单例吗,单例一定不会有线程安全问题吗 | ||
|
||
Spring中的Bean默认是单例模式的,单例模式本身并不保证线程安全,多个线程同时访问共享资源时,可能会发生并发问题。 | ||
|
||
虽然单例模式保证了一个类在整个应用中只有一个实例,但它本身并不保证线程安全。如果单例 Bean 包含了共享的可变状态(如成员变量),多个线程同时修改这些共享资源时,会引发线程安全问题。因此,需要考虑使用同步机制(如 `synchronized` 关键字)或使用无状态的 Bean,或者通过注入线程安全的依赖,避免并发问题。 | ||
|
||
|
||
|
||
## 并行流在使用的时候需要注意什么,根据线程间隔离来回答 | ||
|
||
注意:**无论执行顺序如何,结果不受影响**,**一个元素的状态不影响另一个元素,并且数据源也不受影响** | ||
|
||
https://blog.csdn.net/zjy_love_java/article/details/108562433 | ||
|
||
|
||
|
||
## 算法:全部有效的括号(回溯),二叉树最大路径和 | ||
|
||
https://leetcode.cn/problems/jC7MId/description/ | ||
|
||
|
||
|
||
|
||
|
||
|
||
|