大家好,我是二哥呀。
应该是上周,有球友在 VIP 群里报喜,说字节已经 OC 了。字节有很多优点,比如说 TikTok 真正做到了全球化,抖音更是成为微信后又一个国民级的应用。
但字节同时也存在一些问题,比如说加班严重,字节一年人间三年并非空穴来风;还有就是抖音的短视频几乎成为当下最没有养分的“精神安眠药”。
但对于绝大多数的小伙伴来说,能去字节这个互联网大厂,已经是职业生涯中最优的选择之一。
那接下来我们就以《Java 面试指南》中收录的字节跳动面经同学 21 抖音商城一面为例,来看看宇宙厂的面试官都喜欢问哪些问题,好做到心中有数。
能看得出来,基本上就是围绕着二哥一直强调的 Java 后端四大件展开,希望大家在准备八股的时候要有针对性,这样效率会高很多。
字节跳动抖音商城一面面经
redis为什么能处理高并发
Redis 的速度⾮常快,单机的 Redis 就可以⽀撑每秒十几万的并发,性能是 MySQL 的⼏⼗倍。原因主要有⼏点:
①、基于内存的数据存储,Redis 将数据存储在内存当中,使得数据的读写操作避开了磁盘 I/O。而内存的访问速度远超硬盘,这是 Redis 读写速度快的根本原因。
②、单线程模型,Redis 使用单线程模型来处理客户端的请求,这意味着在任何时刻只有一个命令在执行。这样就避免了线程切换和锁竞争带来的消耗。
③、IO 多路复⽤,基于 Linux 的 select/epoll 机制。该机制允许内核中同时存在多个监听套接字和已连接套接字,内核会一直监听这些套接字上的连接请求或者数据请求,一旦有请求到达,就会交给 Redis 处理,就实现了所谓的 Redis 单个线程处理多个 IO 读写的请求。
④、高效的数据结构,Redis 提供了多种高效的数据结构,如字符串(String)、列表(List)、集合(Set)、有序集合(Sorted Set)等,这些数据结构经过了高度优化,能够支持快速的数据操作。
redis如何保证扩容过程中数据正常访问插入
Redis 集群使用数据分片和哈希槽的机制将数据分布到不同的节点上。集群扩容和缩容的关键,在于槽和节点之间的对应关系。
当需要扩容时,新的节点被添加到集群中,集群会自动执行数据迁移,以重新分布哈希槽到新的节点。数据迁移的过程可以确保在扩容期间数据的正常访问和插入。
当数据正在迁移时,客户端请求可能被路由到原有节点或新节点。Redis Cluster 会根据哈希槽的映射关系判断请求应该被路由到哪个节点,并在必要时进行重定向。
如果请求被路由到正在迁移数据的哈希槽,Redis Cluster 会返回一个 MOVED 响应,指示客户端重新路由请求到正确的目标节点。这种机制也就保证了数据迁移过程中的最终一致性。
当需要缩容时,Redis 集群会将槽从要缩容的节点上迁移到其他节点上,然后将要缩容的节点从集群中移除。
加分布式锁时redis如何保证不会发生冲突
①、使用 SET NX PX 或 SETNX 命令确保锁的获取是一个原子操作,同时设置锁的过期时间防止死锁。
比如说 SET lock_key unique_value NX PX 5000
命令,其中 NX
确保了原子操作,,如果 lock_key 已存在,SET 操作会返回 nil;PX 5000
设置过期时间为 5000 毫秒,避免死锁。
②、使用 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
③、使用 Redlock 算法确保锁的正确获取和释放。
RLock lock = redisson.getLock("lock_key");
try {
// 500ms 等待时间,10000ms 锁过期时间
boolean isLocked = lock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
if (isLocked) {
// 执行需要同步的操作
}
} finally {
lock.unlock();
}
分布式锁过期怎么办?
在分布式系统中,使用 Redis 分布式锁时,锁通常会设置一个过期时间以防止死锁。然而,如果客户端在执行任务的过程中超过了锁的过期时间,那么锁将被 Redis 自动释放,可能会导致其他客户端获取到了同一个锁,引发严重的并发问题。
Redisson 提供的分布式锁是支持锁自动续期的,也就是说,如果线程在锁到期之前还没有执行完,那么 Redisson 会自动给锁续期,避免锁的过期。
这被称为“看门狗”机制。
class RedissonWatchdogExample {
public static void main(String[] args) {
// 配置 Redisson 客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁对象
RLock lock = redisson.getLock("myLock");
try {
// 获取锁,默认看门狗机制会启动
lock.lock();
// 模拟任务执行
System.out.println("Task is running...");
Thread.sleep(40000); // 模拟长时间任务(40秒)
System.out.println("Task completed.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
// 关闭 Redisson 客户端
redisson.shutdown();
}
}
看门狗启动后,每隔 10 秒会刷新锁的过期时间,将其延长到 30 秒,确保在锁持有期间不会因为过期而释放。
当任务执行完成时,客户端调用 unlock()
方法释放锁,看门狗也随之停止。
如果客户端宕机服务器如何感知?
每个客户端在 Redis 中维护一个特定的键(称为心跳键),用于表示客户端的健康状态。该键具有一个设置的超时时间,例如 10 秒。
客户端定期(如每 5 秒)更新这个心跳键的超时时间,保持它的存活状态,通常通过 SET 命令重设键的过期时间。
import redis.clients.jedis.Jedis;
public class ClientHeartbeat {
private static final String HEARTBEAT_KEY = "client:heartbeat";
private static final int EXPIRE_TIME = 10; // 10秒
public static void main(String[] args) {
// 创建 Redis 连接
Jedis jedis = new Jedis("localhost");
// 定时更新心跳键
while (true) {
try {
// 设置心跳键并设置过期时间
jedis.setex(HEARTBEAT_KEY, EXPIRE_TIME, "alive");
// 打印心跳日志
System.out.println("Heartbeat sent.");
// 等待一段时间后再次发送心跳
Thread.sleep(5000); // 每5秒发送一次心跳
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
Redis 服务端定期检查这个心跳键。如果发现该键已超时并被 Redis 自动删除,说明客户端可能已宕机。
import redis.clients.jedis.Jedis;
public class ServerMonitor {
private static final String HEARTBEAT_KEY = "client:heartbeat";
public static void main(String[] args) {
// 创建 Redis 连接
Jedis jedis = new Jedis("localhost");
// 定期检查心跳键
while (true) {
try {
// 检查心跳键是否存在
if (jedis.exists(HEARTBEAT_KEY)) {
System.out.println("Client is alive.");
} else {
System.out.println("Client is down or disconnected.");
}
// 每隔一段时间检查一次
Thread.sleep(10000); // 每10秒检查一次
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
io多路复用
I/O 多路复用是一种同时监视多个文件描述符(或套接字)是否可读、可写的机制,当其中的某个或多个文件描述符准备好时,通知应用程序执行相应的 I/O 操作。常见的 I/O 多路复用机制包括 select、poll 和 epoll(在 Linux 中)等。
特性 | select | poll | epoll |
---|---|---|---|
文件描述符限制 | 受 FD_SETSIZE 限制 | 无限制 | 无限制 |
时间复杂度 | O(n) | O(n) | O(1) |
数据复制 | 需要 | 需要 | 不需要 |
工作方式 | 线性扫描 | 线性扫描 | 事件通知 |
内核支持 | 所有 UNIX 系统 | 所有 UNIX 系统 | Linux 2.6 及以上版本 |
适用场景 | 少量连接 | 中等连接 | 大量并发连接 |
比如说你是一名数学老师,上课时提出了一个问题:“今天谁来证明一下勾股定律?”
同学小王举手,你就让小王回答;小李举手,你就让小李回答;小张举手,你就让小张回答。
这种模式就是 IO 多路复用,你只需要在讲台上等,谁举手谁回答,不需要一个一个去问。
Redis 就是使用 epoll 这样的 I/O 多路复用机制,在单线程模型下实现高效的网络 I/O,从而支持高并发的请求处理。
mysql分为几层?binlog写入在哪一层
MySQL 的架构大致可以分为三层,从上到下依次是:连接层、服务层、和存储引擎层。
①、连接层主要负责客户端连接的管理,包括验证用户身份、权限校验、连接管理等。可以通过数据库连接池来提升连接的处理效率。
②、服务层是 MySQL 的核心,主要负责查询解析、优化、执行等操作。在这一层,SQL 语句会经过解析、优化器优化,然后转发到存储引擎执行,并返回结果。这一层包含查询解析器、优化器、执行计划生成器、缓存(如查询缓存)、日志模块等。
③、存储引擎层负责数据的实际存储和提取,是 MySQL 架构中与数据交互最直接的层。MySQL 支持多种存储引擎,如 InnoDB、MyISAM、Memory 等。
binlog写入在哪一层?
binlog 在服务层,负责记录 SQL 语句的变化。它记录了所有对数据库进行更改的操作,用于数据恢复、主从复制等。
sql的语法树解析
语法树(或抽象语法树,AST)是 SQL 解析过程中的中间表示,它使用树形结构表示 SQL 语句的层次和逻辑。语法树由节点(Node)组成,每个节点表示 SQL 语句中的一个语法元素。
- 根节点:通常是 SQL 语句的主要操作,例如 SELECT、INSERT、UPDATE、DELETE 等。
- 内部节点:表示语句中的操作符、子查询、连接操作等。例如,WHERE 子句、JOIN 操作等。
- 叶子节点:表示具体的标识符、常量、列名、表名等。例如,users 表、id 列、常量 1 等。
以一个简单的 SQL 查询语句为例:
SELECT name, age FROM users WHERE age > 18;
这个查询语句的语法树可以表示为:
SELECT
/ \
Columns FROM
/ \ |
name age users
|
WHERE
|
age > 18
内容来源
- 星球嘉宾三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
- 二哥的 Java 进阶之路(GitHub 已有 12000+star):https://javabetter.cn