跳转至

场景题

二哥星球 https://www.yuque.com/itwanger/gykdzg/ded8g2

某个接口相应慢,如何排查?

  1. 查看服务器日志:服务器日志通常会记录接口请求的相关信息,如请求时间、响应时间、是否有异常报错等。通过分析日志,可以初步确定接口响应慢是偶尔出现还是频繁发生,以及是否存在特定的错误信息提示。
  2. 性能分析工具检测:利用性能分析工具,如 Java 中的 JProfiler、VisualVM 等。这些工具可以帮助分析接口执行过程中各个方法的耗时情况,定位到具体是哪些代码块或方法占用了大量时间,从而找到性能瓶颈。
  3. 数据库查询优化检查:如果接口涉及数据库操作,检查数据库查询语句是关键。可以通过数据库的性能分析工具,查看查询执行计划,判断是否存在全表扫描、缺少索引等问题。优化查询语句,添加必要的索引,能够显著提高数据库查询性能,进而提升接口响应速度。
  4. 网络状况检查:使用网络诊断工具,如 ping、traceroute 等,检查网络连接是否稳定,是否存在高延迟、丢包等情况。如果是分布式系统,还要考虑不同节点之间的网络通信情况,确保网络不会成为数据传输的瓶颈。
  5. 服务器资源监控:监控服务器的 CPU、内存、磁盘 I/O 等资源使用情况。如果服务器资源紧张,如 CPU 使用率过高、内存不足等,可能会导致接口处理速度变慢。此时需要考虑增加服务器资源或优化应用程序对资源的使用。
  6. 缓存机制检查:检查接口是否使用了缓存,以及缓存的策略和有效性。如果缓存未命中或缓存数据更新不及时,可能会导致每次请求都需要从数据库或其他数据源获取数据,增加响应时间。优化缓存策略,合理设置缓存过期时间,提高缓存命中率,可以有效提升接口性能。

表中有一千万条数据,如何处理

分库分表?

手写实现生产者-消费者

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 生成器

怎么设计秒杀系统

如何防止库存超卖

  1. MySQL:直接用 InnoDB 的排他锁,利用事务判断库存是否足够,足够再进行扣减。优点是数据强一致,缺点是并发性能一般,适合对一致性要求高的场景。
  2. Redis:把库存预加载到 Redis,用 setnx 加锁或 Lua 脚本扣减库存,然后异步同步到 MySQL。优点性能很好,适用于高并发,缺点是可能存在短期数据不一致情况。

如何实现 TTL 过期机制

  1. Redis 设置 TTL 过期时间,配合缓存删除策略;
  2. MQ 延迟队列

短链接怎么转换成长链接

电商促销高峰期百万 QPS 如何防止刷单

  1. 建立多维度风控模型
    1. 设备指纹识别:除 IP 外,还需关注设备硬件信息、浏览器指纹、屏幕分辨率等;
    2. 行为模式分析:考察用户在页面的滑动、点击等习惯;
    3. 时间窗口检测:正常用户从点击商品到商品下单有一定时间间隔(如 10 秒),过快很有可能就是刷单行为;
  2. 分层防护体系
    1. CDN 边缘防护:在 CDN 层面识别异常流量,基于 IP 地理位置、请求频率进行初步过滤;
    2. 网关层限流:进行更精细的控制,如设置单 IP 每分钟访问次数
    3. 业务层智能识别:实时计算用户的风险得分,考虑用户注册时长、历史订单情况、设备登录历史等因素
  3. 实时监控,动态调整
    1. 考虑下单转化率、评价会话时长、异常 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 个词。

方法总结

  1. 分而治之,进行哈希取余;
  2. 使用 HashMap 统计频数;
  3. 求解最大的 TopN 个,用小顶堆;求解最小的 TopN 个,用大顶堆