前戏

您能介绍一下您的基本情况和工作经历吗?

解答:略

您目前是否在职?如果面试通过,多久能到岗?

解答:略

您对中科软的了解如何?

解答:略

您能详细介绍一下目前负责的项目和职责吗?

解答:略

您提到的项目涉及到十几张表,在设计这些数据结构时是如何考虑的?

解答:略

课程和老师的关联是如何设计的?

解答:略

如果老师将课程下架,学生那边的课程信息是如何处理的?

解答:略

你们的系统是否有考试功能?考试如何实现的?

解答:略

您能详细讲一下课表管理和学习记录的设计和实现吗?

解答:略

正题

你们系统中如何处理消息丢失、消息重复消费和消息积压的问题?

消息丢失

生产者丢失

  • 消息持久化:在生产者发送消息时,确保消息持久化到MQ(消息队列)服务端,以保证消息不会因MQ服务重启或故障而丢失

  • 生产者确认机制:开启生产者确认机制,生产者发送消息后,会等待MQ服务端的确认,如果未收到确认,生产者会重试发送,确保消息成功进入队列

  • 失败消息处理:如果生产者多次重试发送消息失败,我们会将失败消息记录到数据库中,并通过定时任务或人工处理,确保所有消息最终都会被处理

消费者丢失

  • 手动确认机制:关闭消费者的自动确认机制,改为手动确认,当消费者成功处理完消息后,再发送确认信息给MQ服务端,只有在收到确认后,MQ服务端才会将消息从队列中删除,如果消费者处理消息失败,则不会发送确认信息,MQ服务端会重新投递该消息

  • 重试机制:配置消费者在处理消息失败时进行重试,并记录失败次数,如果超过预设的重试次数,将消息记录到数据库或发送告警,在进行人工去处理

消息重复消费

防止消息重复消费的方法主要通过幂等性设计来实现:

  1. 唯一业务标识:在处理消息时,设计唯一的业务标识,例如用户ID和课程ID的组合。在处理每条消息前,先检查是否已经处理过这条消息(即是否存在该唯一标识的记录),如果存在则跳过处理。

  2. 幂等性校验:每次处理消息前,进行幂等性校验,确保同一消息不会被重复处理。例如,课程报名时,通过检查用户和课程的关联关系是否已存在,来决定是否需要处理该消息。

你们使用的集群是什么类型?是服务集群还是镜像集群?

服务集群

服务集群通常用于应用服务的部署和管理。

镜像集群

镜像集群主要用于数据存储和分布式文件系统。

参考回答:

  • Kafka

    • 消息镜像:Kafka的MirrorMaker功能用于跨数据中心复制消息,确保数据的高可用性和灾难恢复

    • 分区和副本:Kafka通过分区和副本机制,确保消息在集群中的冗余存储和高可用性

  • Elasticsearch

    • 数据分片和副本:Elasticsearch通过数据分片和副本机制,确保数据的高可用性和快速查询

    • 跨集群复制:Elasticsearch的跨集群复制功能,用于在多个集群之间复制数据,提高系统的容错能力

如果消息积压了,您会如何处理?

参考回答

最简单的方式就是水平扩展机器数量,增加内存和存储

您了解Kafka吗?它与MQ有什么区别?Kafka适用于哪些场景?

纯八股文,参考下面的回答

Kafka 是一个分布式的流式处理平台和消息队列系统,与传统的消息队列(MQ)系统在设计和应用场景上有一些显著的区别。

Kafka 与传统消息队列的区别

  1. 数据持久性

    • Kafka 是设计为高度持久的消息系统,它将消息持久化到磁盘,支持消息的持久存储和回放。

    • 传统的消息队列(如 RabbitMQ、ActiveMQ)通常也支持持久化,但在设计上不同,更多专注于点对点的消息传递和队列管理。

  2. 发布订阅模型

    • Kafka 基于发布订阅模型,消息由生产者发布到主题(topic),消费者订阅主题并实时获取消息。

    • 传统消息队列通常以点对点模式为主,即消息从一个发送者直接传递到一个接收者。

  3. 处理能力

    • Kafka 在处理大规模数据流时表现优异,支持高吞吐量和低延迟,适合处理大量实时数据流。

    • 传统消息队列也能处理大量消息,但通常更关注传输的可靠性和消息队列的管理特性。

  4. 分布式架构

    • Kafka 是为分布式环境设计的,可以水平扩展,支持多个 broker 构成的集群。

    • 传统消息队列系统也可以进行集群化部署,但架构和扩展性可能不如 Kafka 灵活。

Kafka 的适用场景:

  • 实时数据管道:用于构建实时数据管道,将大量实时数据从多个数据源汇聚到数据湖或数据仓库中。

  • 日志聚合和监控:适合用作日志聚合和分析,支持快速的日志数据收集和处理。

  • 事件驱动架构:作为事件驱动架构的核心组件,支持事件发布和订阅,实现高效的事件驱动通信。

  • 流处理应用:结合 Kafka Streams 或类似的流处理框架,可以实现复杂的流处理和实时分析。

Kafka 适合需要高度可扩展、高吞吐量和低延迟的实时数据处理和消息传递场景,特别是在大数据领域和微服务架构中被广泛应用。传统消息队列则更适合于点对点的消息传递和简单的队列管理需求。

详细可以看这篇文章:kafka和rabbitmq之间的区别以及适用场景

网关的作用是什么?

RPC:首先是RPC的端口转发,充当连接不同网络的桥梁,实现跨网络的数据交换和转发

协议转换:网关可以进行不同协议之间的转换,使得使用不同通信协议的网络能够进行互操作,例如,将局域网(LAN)中的数据包转换为互联网(Internet)上使用的数据格式

安全控制:作为网络的入口点,网关可以实施安全措施,如访问控制、防火墙、数据加密等,保护内部网络免受外部威胁的侵害

流量控制:网关可以监控和管理数据流量,优化网络资源的使用,确保网络的高效运行和资源分配

路由选择:根据网络的拓扑结构和目标地址,网关能够选择最佳的路径进行数据传输,以提高传输效率和降低延迟

代理服务:某些网关还提供代理服务,代表客户端请求外部资源,隐藏客户端的真实网络标识,增强网络安全性和隐私保护

拦截器和过滤器的区别是什么?

拦截器(Interceptor)

拦截器是一种设计模式,用于在执行过程中拦截并处理请求或调用,并能够在目标方法执行前后、异常发生时进行拦截处理,常见于面向切面编程(AOP)的实现中,例如在Spring框架中,拦截器可以用来实现日志记录、权限验证、性能监控等功能

过滤器(Filter)

过滤器也是一种设计模式,它用于对数据流进行处理和转换,可以在数据流进入目标之前或离开之后进行处理,通常用于数据的预处理和后处理,例如在Web开发中,过滤器可以用来处理HTTP请求和响应,进行字符编码转换、身份验证、请求参数过滤等操作

主要区别

  • 作用对象不同:拦截器主要针对方法调用或请求处理过程,而过滤器则主要针对数据流的输入输出进行处理。

  • 实现方式不同:拦截器通常通过面向切面编程(AOP)的方式实现,而过滤器则通常通过设计模式中的责任链模式来实现。

  • 使用场景不同:拦截器更多用于在业务方法执行前后进行扩展或处理,而过滤器更多用于对数据流进行过滤和转换,例如对请求和响应进行预处理和后处理。

是先执行拦截器,还是先执行过滤器?

首先,请求到达Servlet容器,触发过滤器(Filter)

然后,请求被DispatcherServlet捕获,触发拦截器(Interceptor)

接着,如果定义了AOP切面,它们会在相应的时机(如方法执行前或后)被触发

最后,请求到达控制器(Controller),执行相应的业务逻辑

在Spring中,通过什么接口实现请求的拦截?

HandlerInterceptor:这个接口允许开发者在请求处理之前(preHandle)、请求处理之后(postHandle)、视图渲染之后(afterCompletion)等时机对请求进行拦截和处理

反射是什么?一般在什么场景使用反射?

Spring Boot常用的一些注解有哪些?

什么是雪花算法?如何解决雪花算法重复生成的问题?

雪花算法是一种分布式唯一 ID 生成算法,最初由Twitter开发并开源,用于生成全局唯一的 ID。它的核心思想是在分布式系统中生成唯一的、有序的、趋势递增的 ID,以便于在分布式环境中进行数据的唯一标识和排序。

雪花算法的结构

雪花算法生成的 ID 通常由以下部分组成:

  • 时间戳:占用 41 位,表示生成 ID 的时间戳。可以精确到毫秒级别,可以使用当前时间减去一个起始时间来获得时间戳的值。

  • 机器标识:占用 10 位,表示机器的标识符。通常是机器的 ID,可以配置。

  • 序列号:占用 12 位,表示同一毫秒内生成的序列号。序列号达到上限时会进行自旋等待,直到下一毫秒再生成新的 ID。

解决重复生成问题

参考回答:

雪花算法在分布式系统中生成的 ID 具有全局唯一性,但是在极端情况下,可能会因为系统时间回拨或者某些错误导致生成重复的 ID。为了解决这个问题,可以采取以下几种方法:

  1. 时钟回拨检测:在生成 ID 之前,可以检测系统时间是否回拨。如果检测到时间回拨,可以选择等待或者使用备用的时钟源,避免生成重复的 ID。

  2. 使用高性能时钟:一些系统可能会使用高性能的时钟源(如机器的 CPU 周期计数器)来代替系统时钟,减少时间回拨的可能性。

  3. 添加机器标识:每个机器分配一个唯一的标识符,将机器标识作为 ID 的一部分,确保同一时刻不同机器生成的 ID 不会重复。

  4. 序列号重置策略:在同一毫秒内序列号达到上限时,可以采取适当的策略,如自旋等待、使用备用序列号生成策略等,确保生成的 ID 不重复。

这些方法结合起来可以有效地减少雪花算法生成重复 ID 的可能性,保证在分布式系统中生成的 ID 唯一性和稳定性。

MySQL中的自增ID是如何实现的?

在 MySQL 中,自增 ID 主要通过 AUTO_INCREMENT 属性来实现。这种属性可以应用于整数类型的列(通常是主键列),用于自动为插入的新记录生成唯一的递增 ID。

id 列被定义为 AUTO_INCREMENT,每次插入一条新记录时,MySQL 会自动为 id 列分配一个递增的唯一值,这样就保证了每条记录有一个独一无二的标识符

如何生成有一定顺序且不重复的订单号?

具体方式有很多,这里列举几个我当时回答的:

  1. UUID

  2. 雪花+时间

  3. Redis+雪花

是否了解线程池?自定义线程池吗?

线程池的工作原理是什么?如何回收?

工作原理:

  1. 线程池初始化

    • 在应用程序启动时,线程池会初始化一定数量的线程,这些线程处于等待就绪状态,可以立即执行任务。

  2. 任务提交

    • 当有任务到达时,线程池会从池中的空闲线程中选择一个线程来执行任务,而不是每次都创建新线程。这种方式减少了线程创建和销毁的开销。

  3. 任务执行

    • 选定的线程会执行提交的任务。如果所有线程都在忙碌,则任务会进入任务队列等待执行。

  4. 任务队列

    • 线程池通常会有一个任务队列(也称作工作队列),用于存储等待执行的任务。当线程池中的线程空闲时,会从队列中取出任务执行。

  5. 线程复用

    • 执行完一个任务后,线程并不会被销毁,而是可以被重用来执行下一个任务,从而节省了线程创建和销毁的开销。

回收机制:

  1. 空闲线程回收

    • 如果线程池中的某些线程在一段时间内没有执行任务,线程池会根据一定的策略进行回收。例如,可以根据线程空闲时间超过一定阈值来回收线程。

  2. 动态调整

    • 线程池一般会根据当前任务的数量和系统负载动态调整线程数量。如果任务数量增加,线程池可以增加线程数以处理更多任务;如果任务减少,可以减少线程数以节省资源。

  3. 线程池关闭

    • 当应用程序关闭或者不再需要线程池时,线程池应当正确地关闭。这包括停止接受新任务,等待现有任务执行完毕,然后释放线程资源。

如何优化SQL?有没有遇到过SQL优化的问题?

本站地址:如何排查慢SQL 常见SQL优化

是否了解执行计划?如何查看执行计划?