场景题
二哥星球 https://www.yuque.com/itwanger/gykdzg/ded8g2
某个接口相应慢,如何排查?
- 查看服务器日志:服务器日志通常会记录接口请求的相关信息,如请求时间、响应时间、是否有异常报错等。通过分析日志,可以初步确定接口响应慢是偶尔出现还是频繁发生,以及是否存在特定的错误信息提示。
- 性能分析工具检测:利用性能分析工具,如 Java 中的 JProfiler、VisualVM 等。这些工具可以帮助分析接口执行过程中各个方法的耗时情况,定位到具体是哪些代码块或方法占用了大量时间,从而找到性能瓶颈。
- 数据库查询优化检查:如果接口涉及数据库操作,检查数据库查询语句是关键。可以通过数据库的性能分析工具,查看查询执行计划,判断是否存在全表扫描、缺少索引等问题。优化查询语句,添加必要的索引,能够显著提高数据库查询性能,进而提升接口响应速度。
- 网络状况检查:使用网络诊断工具,如 ping、traceroute 等,检查网络连接是否稳定,是否存在高延迟、丢包等情况。如果是分布式系统,还要考虑不同节点之间的网络通信情况,确保网络不会成为数据传输的瓶颈。
- 服务器资源监控:监控服务器的 CPU、内存、磁盘 I/O 等资源使用情况。如果服务器资源紧张,如 CPU 使用率过高、内存不足等,可能会导致接口处理速度变慢。此时需要考虑增加服务器资源或优化应用程序对资源的使用。
- 缓存机制检查:检查接口是否使用了缓存,以及缓存的策略和有效性。如果缓存未命中或缓存数据更新不及时,可能会导致每次请求都需要从数据库或其他数据源获取数据,增加响应时间。优化缓存策略,合理设置缓存过期时间,提高缓存命中率,可以有效提升接口性能。
表中有一千万条数据,如何处理
分库分表?
手写实现生产者-消费者
Java |
---|
| class ProducerConsumer {
private final int capacity;
private final Queue<Integer> queue = new LinkedList<>();
public ProducerConsumer(int capacity) {
this.capacity = capacity;
}
// 生产者方法
public synchronized void produce(int item) throws InterruptedException {
while (queue.size() == capacity) {
wait();
}
queue.add(item);
System.out.println("Produced: " + item);
notifyAll();
}
// 消费者方法
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
int item = queue.poll();
System.out.println("Consumed: " + item);
notifyAll();
return item;
}
}
class Producer implements Runnable {
private final ProducerConsumer pc;
private int count;
public Producer(ProducerConsumer pc) {
this.pc = pc;
}
@Override
public void run() {
try {
while (count < 10) {
pc.produce(count++);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private final ProducerConsumer pc;
public Consumer(ProducerConsumer pc) {
this.pc = pc;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
pc.consume();
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer(5);
Thread producerThread = new Thread(new Producer(pc));
Thread consumerThread = new Thread(new Consumer(pc));
producerThread.start();
consumerThread.start();
}
}
|
设计一个基于 byte[],遵循 FIFO 读取的缓冲区,要求不能有 byte[] 之外的变量
Java |
---|
| public class ByteFIFOBuffer {
private byte[] buffer;
private int readIndex;
private int writeIndex;
public ByteFIFOBuffer(int size) {
buffer = new byte[size];
readIndex = 0;
writeIndex = 0;
}
public synchronized void put(byte b) {
buffer[writeIndex] = b;
writeIndex = (writeIndex + 1) % buffer.length;
}
public synchronized byte get() {
byte b = buffer[readIndex];
readIndex = (readIndex + 1) % buffer.length;
return b;
}
public synchronized boolean isEmpty() {
return readIndex == writeIndex;
}
public synchronized boolean isFull() {
return (writeIndex + 1) % buffer.length == readIndex;
}
}
|
对三个不同数据源进行采集,最后组合返回,怎么做
可以使用多线程或异步编程的方式来同时采集三个数据源的数据,然后再将它们组合起来返回。以 Java 中的 CompletableFuture
为例:
Java |
---|
| import java.util.concurrent.CompletableFuture;
public class DataCollection {
public static String source1() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data from source 1";
}
public static String source2() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data from source 2";
}
public static String source3() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data from source 3";
}
public static String collectData() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(DataCollection::source1);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(DataCollection::source2);
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(DataCollection::source3);
CompletableFuture.allOf(future1, future2, future3).join();
return future1.join() + "\n" + future2.join() + "\n" + future3.join();
}
public static void main(String[] args) {
System.out.println(collectData());
}
}
|
如果有 1000 个任务,每个任务耗时 0.1 秒,需要在 10 秒内完成,该怎么做
可以使用线程池来并发执行这些任务。由于每个任务耗时 0.1 秒,要在 10 秒内完成 1000 个任务,至少需要 10 个线程并发执行。
Java |
---|
| import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TaskExecution {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(new Task());
}
executor.shutdown();
}
}
|
如何实现股票价格的实时变化
怎么设计一个分布式 id 生成器
怎么设计秒杀系统
如何防止库存超卖
- MySQL:直接用 InnoDB 的排他锁,利用事务判断库存是否足够,足够再进行扣减。优点是数据强一致,缺点是并发性能一般,适合对一致性要求高的场景。
- Redis:把库存预加载到 Redis,用 setnx 加锁或 Lua 脚本扣减库存,然后异步同步到 MySQL。优点性能很好,适用于高并发,缺点是可能存在短期数据不一致情况。
如何实现 TTL 过期机制
- Redis 设置 TTL 过期时间,配合缓存删除策略;
- MQ 延迟队列
短链接怎么转换成长链接
电商促销高峰期百万 QPS 如何防止刷单
- 建立多维度风控模型
- 设备指纹识别:除 IP 外,还需关注设备硬件信息、浏览器指纹、屏幕分辨率等;
- 行为模式分析:考察用户在页面的滑动、点击等习惯;
- 时间窗口检测:正常用户从点击商品到商品下单有一定时间间隔(如 10 秒),过快很有可能就是刷单行为;
- 分层防护体系
- CDN 边缘防护:在 CDN 层面识别异常流量,基于 IP 地理位置、请求频率进行初步过滤;
- 网关层限流:进行更精细的控制,如设置单 IP 每分钟访问次数
- 业务层智能识别:实时计算用户的风险得分,考虑用户注册时长、历史订单情况、设备登录历史等因素
- 实时监控,动态调整
- 考虑下单转化率、评价会话时长、异常 IP 占比等,动态调整防刷策略,同时保证正常用户的使用
如何从大量数据中找出高频词
有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。
思路: 对于这种不能一次性把所有数据加载到内存中的情况,可以采用分治的策略,把大文件拆分成小文件,保证每个文件的大小小于 1 MB,将小文件读取到内存中进行统计。
首先遍历大文件,对遍历到的每个词x,执行 hash(x) % 5000
,将结果为 i 的词存放到文件 ai 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 map.put(x, 1)
;若存在,则执行 map.put(x, map.get(x)+1)
,将该词频数加 1。
上面我们统计了每个小文件单词出现的频数。接下来,我们可以通过维护一个小顶堆来找出所有词中出现频数最高的 100 个。具体方法是:依次遍历每个小文件,构建一个小顶堆,堆大小为 100。如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为小顶堆,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。
方法总结
- 分而治之,进行哈希取余;
- 使用 HashMap 统计频数;
- 求解最大的 TopN 个,用小顶堆;求解最小的 TopN 个,用大顶堆。