跳到主要内容

宗蔚 2024-03-20

后端

1、不同的数据库如何实现分页查询

2、有多少种索引以及应用场景

主键索引 唯一索引 普通索引 组合索引 全文索引

3、索引在数据库的底层实现

B+ 树

非叶子节点存放的是索引,叶子节点存放的是数据。

4、like 在不同数据库的实现原理

对每个字段进行匹配。

5、连接函数的实现原理

6、怎么实现数据库兼容

7、JVM 的内存模型

8、导致内存溢出的原因有哪些? 分别是导致什么内存溢出(栈内存溢出,堆内存溢出)

9、如何控制线程池(线程池的参数设置)如果任务塞满阻塞队列线程池会怎么处理。

线程池一般通过创建 ThreadPoolExecutor 来创建一个线程池,在创建的时候可以传入 7 个参数,分别是

  • 核心线程的数量
  • 最大线程的数量
  • 超时时间
  • 超时时间的单位 秒,毫秒,分等
  • 线程工厂,用来创建线程的工厂
  • 阻塞队列,当工作的线程达到最大线程数时,会把新加的任务添加到阻塞队列里面
  • 拒绝策略,阻塞队列满了,会根据拒绝策略来判断新家的任务是如何操作的。
    • 直接丢弃
    • 报错
    • 丢弃阻塞队列的第一个任务之后再插入
    • 在当前线程执行。

如果允许的任务数达到了最大线程数,此时,新添加一个任务到线程池,会将任务添加到阻塞队列(工作队列),如果阻塞队列已满,就在判断一下当前 运行的任务是否达到最大线程数,没有达到就会直接新建一个线程来运行这个任务,否则根据拒绝策略来对任务进行处理。

不建议使用 Executors 来创建线程池,因为在某种情况下会导致内存溢出。(Executors 部分方法使用的是无界队列)

线程池的运行过程

  1. 刚开始运行时,线程池是空的。
  2. 一个任务进来,检查池中的线程数量,是否达到 corePoolSize, 如果没有达到,则创建线程,执行任务。
  3. 任务执行完成后,线程不会销毁,而是阻塞,等待下一个任务。
  4. 又进来一个任务,不是直接使用阻塞的线程,而是检查线程池中的线程数大小,是否达到 corePoolSize,如果没有达到,则继续创建新的线程,来执行新的任务,如此往复, 直到线程池中的线程数达到 corePoolSize ,此时停止创建新的线程。
  5. 此时,又来新的任务,会选择线程池中阻塞等待的线程来执行任务,有一个任务进来,唤醒一个线程来执行这个任务,处理完之后,再次阻塞,尝试在 workQueue 上获取 下一个任务,如果线程池中没有可唤醒的线程, 则任务进入 workQueue,排队等待。
  6. 如果队列是无界队列,比如 LinkedBlockingQueue,默认最大容量为 Integer.Max,接近于无界, 可用无限制的接收任务,如果任务是有界队列,比如 ArrayBlockingQueue,可限定队列大小,当线程池中的线程来不及处理, 然后,所有的任务都进入队列,队列的任务数也达到限定大小,此时,再来新的任务,就会入队失败,然后,就会再次尝试在线程池创建线程, 直到线程数达到 maximumPoolSize,停止创建线程。
  7. 此时,队列满了,新的任务无法入队。创建的线程数也达到了 maximumPoolSize,无法再创建新的线程,此时,就会 reject,使用拒绝策略 RejectedExecutionHandler,不让继续提交任务,默认的是 AbortPolicy 策略,拒绝,并抛出异常。
  8. 超出 corePoolSize 数创建的那部分线程,是跟空闲时间 keepAliveTime 相关的,如果超过 keepAliveTime 时间还获取不到 任务,线程会被销毁,自动释放掉。

为什么不建议使用 Executors 创建线程,而使用 ThreadPoolExecute 实现类来创建线程?

Executors 创建的 FixedThreadPoolSingleThreadExecutor 使 用的是 LinkedBlockingQueue 阻塞队列,默认大小是 Integer.MAX_VALUE, 可以无限制的将任务添加到队列中。CachedThreadPool 允许创建线程的数量为 Integer.MAX_VALUE,可能会创建大量的线程。在极端的情况下会导致 JVM OOM,系统就挂了。

线程池调优

  • 高并发、任务执行时间短,此类任务可以充分利用 CPU,尽可能减少上下文切换,线程池的线程数可以设置为 CPU core + 1
  • 并发不高,任务执行时间长
    • IO 密集 2 * CPU core
    • CPU 密集 CPU core + 1
  • 高并发、业务执行时间长 考虑拆分、解耦、部分数据缓存,增加服务器。

tasks 每秒的任务数 taskCost 每个任务花费的时间 responseTime 系统容忍的最大响应时间

corePoolSize = tasks * taskCost / responseTime

实际场景

  1. 任务数多但资源占用不大: 电商平台的消息推送或短信通知
BlockingQueue queue = newArrayBlockingQueue<>(4096);
ThreadPoolExecutor executor = newThreadPoolExecutor(16, 16, 0, TimeUnit.SECONDS, queue);
  1. 任务数不多但资源占用大: 日志收集、图片流压缩或批量订单处理等场景
BlockingQueue queue = newArrayBlockingQueue<>(512);
ThreadPoolExecutor executor =newThreadPoolExecutor(16, 64, 30, TimeUnit.SECONDS, queue);
  1. 极端情况,任务多,资源占用大
BlockingQueue queue = newSynchronousQueue<>();
ThreadPoolExecutor executor =newThreadPoolExecutor(64, 64, 0, TimeUnit.SECONDS, queue);

10、分布式锁的具体实现原理

11、gateway 如何实现路由转发(如何定位一个服务)