Java 高频面试题场景(一):社区智能充电桩管理系统
系列文章
| 序号 | 文章名称 |
|---|---|
| 1 | Java 高频面试题场景(一):社区智能充电桩管理系统 |
文章目录
系列文章目录
一、项目信息
一、项目介绍
技术栈
主要工作
二、面试题及回答
1. 面试官询问:在项目中采用了MQTT协议,请具体说明其应用方式。
2. 面试官询问:项目中Redis主要用于存储什么数据?请举例说明。
3. 面试官询问:用户扫码登录时如何防止多人同时启动同一充电桩?
4. 面试官询问:在项目中使用RabbitMQ处理了哪些业务逻辑?能否详细说明?
5. 面试官询问:设备离线报警功能是如何实现的?
6. 面试官询问:在查询在线设备时如何优化接口响应速度?
7. 面试官询问:Spring Boot环境中如何配置MQTT协议?能否简述关键步骤?
8. 面试官询问:为预约充电桩时如何确保分布式锁的独特性?
9. 面试官询问:通过Redis的GeoHash实现附近充电桩搜索效率如何提升?
10. 面试官询问:支付回调接口为何能避免重复处理?
11. 面试官询问:在管理后台导入Excel文件至充电桩库时如何避免内存溢出?
12. 面试官询问:当用户扫码后设备未响应时应采取何种排查方式?
13. 面试官询问:在RabbitMQ消费端开了多少线程?如何确定该数值?
14. 面试官询问:充电流程中的数据库事务机制是如何保障一致性?
15. 面试官询问:调用支付宝支付接口时若出现超时情况应采取什么措施?
一、项目信息
项目介绍
参与开发社区充电桩管理系统的主要职责是设计与实现后端核心功能。该系统覆盖超过20个社区,并接入超过1000台充电桩,在确保设备状态实时监控的同时实现了扫码充电全流程的自动化管理,并支持运营数据的全面统计。通过物联网技术和消息队列系统的优化,在故障发生时能够迅速响应并在15分钟内解决问题;同时提升了用户的充电成功率至99%以上,并使物业操作效率提高了30%。
技术栈
-
后端框架:
-
Spring Boot框架用于业务逻辑开发
-
MyBatis Plus技术用于数据库操作
-
中间件配置包括:
-
消息队列服务器用于处理支付通知与设备报警
-
Redis缓存系统管理设备状态信息
-
物联网应用采用MQTT 协议作为设备通信基础
-
数据库系统中使用MySQL存储充电记录信息及用户数据
-
前端 :
-
技术:所使用的技术包括 Vue.js 框架以及百度地图API。
-
协作功能通过 Swagger 接口文档实现,并支持前端设备状态同步与订单信息查询。此外, 该系统还集成有其他相关功能模块以提升整体协同效率.
主要工作
基于MQTT协议开发服务端的订阅功能,并对充电桩上报的状态数据(如在线状态、充电中及故障状态)进行解析与处理。实时更新Redis缓存以及MySQL数据库中的相关资源信息,并根据具体的数据类型分别完成相应的存储操作:例如设备通过指定Topic charge/pile/001/status上传状态信息;后端接收并解析这些数据后,在Redis中创建对应的键值对,并同步更新MySQL数据库中的相关内容
充电业务流程开发 * 实现扫码充电核心逻辑:用户扫码后,后端通过Redis分布式锁防止并发占用设备,调用支付宝接口生成支付二维码。支付结果通过RabbitMQ异步处理,更新订单状态并发送通知。例如,使用set lock:pile:001 user123 nx px 5000获取锁,确保同一设备同一时间仅被一个用户操作。
* 开发预约功能:用户可预约空闲充电桩,预约信息存入Redis(键reserve:pile:001,有效期30分钟),定时任务自动释放过期预约,避免资源浪费。
数据存储与性能优化 * 为提升系统性能,在实验室内规划创建 charging_pile 与 charging_record 两张表,并借助 MyBatis Plus 实现基本 CRUD 操作功能。建议在 status 和 start_time 字段上建立索引以提高查询效率,并建议采用如下策略:例如,在线设备的查询可通过 status 索引快速筛选出相关数据集。* 采用 Redis 缓存高频数据以缓解数据库压力;其中对于充电桩地理位置信息(经纬度),建议采用 GeoHash 表示法进行编码存储,并支持依据地理位置快速查找附近设备;同时将实时计算产生的费用数据存储于 Redis 的哈希结构中以减少数据库负载压力
消息队列与异常处理 * RabbitMQ被用于在支付成功后自动更新订单状态以及在设备故障时发送短信通知。当订单支付成功时,在消息队列中进行异步更新;当订单失败或设备出现故障时,在死信队列中重试失败的消息并将相关信息发送到短信中以确保通知可靠性。
-
全局异常处理机制利用Spring的@ControllerAdvice注解捕获各类业务异常(如设备繁忙、支付失败等),同时也能处理系统层面的异常情况并返回统一错误格式的同时,并通过日志系统记录详细信息来协助后续的故障排查工作。
接口开发与协作 * 包含扫码充电、设备查询等核心功能模块,并通过 Swagger文档 清晰说明接口参数格式及返回值。例如:/charge/start 接收设备ID并返回支付二维码URL;/pile/nearby 根据经纬度信息返回附近设备列表。
- 配合前端展示设备状态并实时更新订单信息, 保证前后端数据的一致性, 通过 WebSocket 协议推送充电进度数据
二、面试题及回答
1.面试官问 : 你在项目里用了MQTT协议,能说说具体怎么用的吗?
应试者回答主要用于接收充电桩的状态数据
面试官追问
面试官再追问
2.面试官问 : 项目里Redis主要缓存什么?举个例子。
应试者回答
面试官追问:
当数据库中的设备费率发生更改时,请问 Redis 缓存未及时更新怎么办?
应试者回答:
在管理后台进行费率调整时,我们会首先同步至数据库,并立刻清除对应的 Redis 缓存项。这样,在下一次查询时就能从数据库中读取最新费率信息。对于批量调整的情况,则会通过RabbitMQ发送通知至后台任务队列中进行批量清空操作以确保数据一致性。
面试官再追问 :
是否曾遇到过缓存穿透的问题?具体是如何解决的?
应试者回答 :
曾有过前端传递不存在于系统中的设备序列号(SN)用于查询状态的情况。每次都会调用数据库进行查询操作。后来在系统后方方程组中引入了布隆过滤器技术,并将所有已存在的设备序列号提前存入集合中,在执行查询前先进行一次过滤检查。这样做的好处是可以将确实存在的设备序列号直接返回给客户端而不必调用数据库获取信息,在无法确定是否存在的情况下则会触发数据库调用操作以验证结果的一致性。值得注意的是该方案虽然在内存占用上有所增加但其效果较为显著并且能够有效缓解因缓存穿透导致的数据库负载压力
3.面试官问 : 用户扫码的时候,怎么避免两个人同时启动同一个充电桩?
Redis分布式锁机制的核心在于使用setIfAbsent命令实现资源锁定功能。例如,在用户扫码授权流程中,后端会执行以下操作:调用库函数redisTemplate.opsForValue().setIfAbsent("lock:pile:001", "user123", 5, TimeUnit.SECONDS)。其中,“nx参数”的作用是表示只有当对应的锁未被占用时才会创建新的实例(即实现抢占式独占),这与传统互斥locks的行为逻辑相一致。一旦获取到该锁之后,则需进一步核查设备运行状态是否为空闲状态才能进行充电操作。
面试官追问 :
当锁过期但充电流程尚未完成时,请问有什么应对措施?
应试者回答 :
在这种情况下可能会出现资源被抢占的情况因此建议在执行关键操作前对系统进行全面检查确保资源可用性。具体实施上可采取以下措施:首先在生成支付二维码的过程中系统将对数据库进行多次验证以确保设备未被占用;其次为了避免不必要的重复检查系统设计会优先进行资源分配管理从而提高整体效率。
面试官再追问
4.面试官问 : 项目里用RabbitMQ处理什么逻辑?能详细说说吗?
应试者回答
面试官追问 :
如果RabbitMQ发生故障时(即出现崩溃),消息是否会丢失?为了确保消息不丢失,请说明具体的实现细节。
应试者回答 :
消息不会丢失是因为我们启用了持久化功能。在队列声明时将durable属性设为true,并在消息发送过程中调用setPersistent(true)方法以确保数据持久存储于磁盘上。即使RabbitMQ重启后也会无一例外地保存所有已发送的消息。此外,在生产者端实现了一种确认机制:发送消息后会等待Broker返回确认信息;如果未收到确认,则会进行重试操作,并且最多尝试3次以确保消息能够成功发送并被正确处理。
面试官再追问 :
在死信队列中的消息应该如何进行处理?这会影响到正常的业务运行吗?
【应试者回答
5.面试官问 : 设备离线报警是怎么实现的?
应试者回答 : 通过Spring实现定时周期,在Redis数据库中为每个设备设置一个last_heartbeat_time字段记录其上一次心跳的时间点。每当系统检测到当前时间与该设备上一次心跳的时间差超过5分钟时,则认为该设备处于离线状态并触发报警机制:此时向RabbitMQ报警队列发送一条消息通知运维人员,并在管理后台将该设备的状态标记为红色状态以进行监控
面试官追问 :
除了离线故障外,还有其他类型的故障吗?如何进行处理?
应试者回答 :
设备返回的消息中包含错误码信息,并通过后端系统解析这些错误码后发现具体问题所在。例如error_code=101代表充电模块发生故障,请您关注该情况;而error_code=102则提示电压异常问题,请及时排查并恢复供电。同时,在数据库中建立故障日志记录机制,以便运维团队能够快速定位并解决问题。
面试官继续提问 :
在报警短信发送过程中出现故障时该怎么办?例如,在某些情况下会出现短信发送超时的问题。
应答者解释说明 :
该系统设计中包含了一个重试机制,在第一次尝试失败后会等待1分钟后进行第二次尝试,并允许最多重试三次。如果在第三次尝试仍未能成功,则需要将此次事件记录至数据库中的alarm_log表中,并将其状态标记为"未发送"。每日系统会自动生成统计报表,并将相关数据提交给运维团队进行手动补发工作。尽管这一操作流程相对繁琐一些(操作流程约为3分钟),但此类故障的发生频率较低,在大多数情况下能够得到及时有效的解决。
6.面试官问 : 查询在线设备时,怎么让接口响应更快?
应试者回答
面试官追问 :
为提高效率,在处理充电记录时采用了分表策略。具体来说是基于时间字段将数据按照年度和月份分别存储为不同的数据表(例如每年包含12个月份的数据表),如charging_record_202301、charging_record_202302等。系统在执行查询操作时会根据查询月份自动匹配相应的数据表以实现更快捷的数据检索。此外,在计算统计信息的过程中还应用了MyBatis Plus的技术手段,并通过@Cacheable注解实现了对相关结果的有效缓存机制。这样一来,在后续的操作中系统能够快速调用已有的缓存结果以显著提升响应效率。
应试者回答 :
面试官再追问 :
分表完成后,在涉及跨月的数据查询方面该如何操作?
应试者回答 :
建议减少对跨月数据的查询需求。前端设计仅支持在过去6个月内的数据查询。如果有必要进行跨月数据查询,则需通过Elasticsearch同步数据源以确保一致性。使用Elasticsearch的聚合功能可提高此类复杂场景下的操作效率。目前项目中尚未实施此功能,并非因为大部分统计报表均基于月份汇总计算(即按月份统计的数据占主要使用场景),而是由于跨月数据的需求较为有限。
7.面试官问 : Spring Boot里怎么配置MQTT?能说说关键步骤吗?
应试者回答 :
首先在配置文件里写MQTT服务器的地址和客户端ID,比如:
spring.mqtt.client.url=tcp://mqtt.example.com:1883
spring.mqtt.client.client-id=charge-backend
properties
编写一个配置类,并将MqttClient类型的Bean注入其中。在@PostConstruct处初始化连接,并订阅所需的主题(如charge/pile/+)。当接收到消息后,请自定义监听器解析接收到的消息内容,并调用业务层处理数据。
面试官追问
应试者回答
面试官再追问 :
是否曾遇到过消息重复消费的情况?如果是的话,请具体说明解决方法。
应试者回答 :
在QoS 1模式下可能会出现重复的消息,在数据库中增加message_id字段后能够有效识别并避免重复记录。我们通过在数据库中增加message_id字段来实现这一目的,并且消息ID由设备SN与时间戳结合生成以保证唯一性。如果收到重复消息,则直接跳过处理流程。
8.面试官问 : 预约充电桩时,分布式锁怎么保证唯一性?
在应试者回答中
面试官追问 :
当预约信息失效时,锁机制如何实现自动解锁?
应试者回答 :
Redis系统自带失效项清除功能,在不需人工操作的情况下即可完成相关数据清理工作。然而,为了确保数据完整性与系统稳定性,我们在常规清理基础上额外配置了一个每日午夜定时脚本.该脚本不仅在线扫描并清除已超时的所有预约记录,还支持批量处理其他可能存在的失效项.
面试官再追问 :
如果用户预约后,管理员手动占用设备时如何处理锁冲突?
应试者回答 :
当管理员进行设备占用操作时(或:管理员操作时),系统会首先检查是否存在预约锁(或:发现是否存在预约锁)。如果存在,则会先删除Redis中的预约键(或:系统会先删除Redis中的预约键),随后将设备状态更新为"占用"(或:"占用"状态)。这样,在用户端刷新设备状态信息后(或:在用户端刷新查看),会看到设备状态已经变更(或:显示已发生变更),从而实现相应的解锁效果以避免冲突。
9.面试官问 : 查附近充电桩时,Redis的GeoHash怎么提升效率?
应试者回答 :
该系统将充电桩的经纬度通过GeoHash编码存储于Redis数据库中,在查询时使用GEORADIUS命令按距离排序。例如,在用户位置为(116.4, 39.9)时,执行以下命令:GEORADIUS pile_locs 116.4 39.9 1 km WITHCOORD COUNT=20。从而检索到最近的20个设备,并且该方法运行速度显著优于传统数据库方法。
面试官追问 :
如何选择GeoHash的精确度?
应试者回答 :
我们采用6位GeoHash编码,在1公里范围内提供足够的精确度来覆盖周边区域。若采用7位编码,则将精确度提升至100米以内区间,并带来更高的内存占用成本。根据实际应用场景的需求,在大多数情况下而言,默认定位在几百米范围内的设备即可满足使用场景要求;因此我们选择了6位编码方案,在保证性能的同时实现了对存储资源的有效平衡。
面试官认为:当Redis采用集群模式时,请问使用GeoHash进行查询是否存在问题?
在单机模式下没有问题,在集群模式下可能需要跨节点查询。由于GeoHash的范围通常会跨越多个分片,在这种情况下可能会出现数据不一致的问题。
目前我们的项目仅使用单机形式的Redis服务。当前设备规模尚未达到需要部署集群的地步。如果未来设备数量增加至一定程度,则计划采用Redis Cluster技术,并根据地理区域进行分片存储策略以提高数据一致性与可用性。具体来说,在这种情况下我们希望确保同一地理区域内所有设备位于同一个数据节点中。
10.面试官问 : 支付回调接口怎么防止重复处理?
为数据库中的charging_record表新增了payment_no字段用于记录支付平台发出的独一无二的订单编号每当接收到回调通知时系统会检查该payment_no是否已存在若已存在则直接返回成功状态以避免冗余处理从而确保即使支付平台多次通知也不会导致额外的订单产生
面试官追问 :
在支付平台中出现payment_no重复的情况时该怎么办?
应试者回答 :
这种情况发生的可能性极低,并为此我们准备了备用方案。为了防止冲突,在数据库中为payment_no字段设置了唯一索引,并通过异常处理机制保障数据一致性。实际应用中尚未遇到过此类情况发生(到目前为止尚未遇到过类似情况),这是因为支付平台通常采用唯一的订单号作为识别依据。
面试官再追问 :
除了数据库,还有其他方法吗?
应试者回答 :
可以用Redis缓存已处理的payment_no,设置过期时间24小时。收到回调消息先查Redis,存在就直接返回成功。这种方法适合高并发场景,减少数据库压力。我们项目里因为支付量不是特别大,所以先用了数据库唯一键的方式,简单直接。
11.面试官问 : 管理后台导入充电桩Excel时,怎么避免内存溢出?
应试者回答 :
采用EasyExcel提供的流式读取功能,并逐行加载数据而不一次性将所有数据加载进内存中。例如,在处理过程中每隔10秒就会触发一次特定操作:当累积100条记录时触发一次批量插入操作,并在处理完毕后清空临时存储列表以释放资源。这种设计能够有效避免无论数据量多大都可确保不会导致内存占用过高问题出现的情况发生。具体实现时需要对核心逻辑进行重新编写并整合原有功能模块以完成完整的系统流程任务
面试官追问 :
当用户取消导入时该怎么办?
应试者回答 :
我们创建了一个用于记录任务状态的import_task表,并且在数据库层面实现了事务管理功能。每当用户点击取消按钮时,系统会将任务状态更新为'已取消'。后端线程会检测到这种状态变更,并相应地终止当前的数据导入操作;同时为了防止出现脏数据问题,在完成了必要的回滚操作后还会进行数据校验。如果有部分数据已经完成导入,在后续的操作中我们会将其标记为无效等待重新倒入;一旦新用户的输入到来系统就会覆盖掉原有的部分数据并重新启动完整的流程。
面试官再追问 :
在Excel表格中如何解决数据格式错误的问题?例如,在某些情况下经纬度字段被误输入为文字类型。
应试者回答 :
为了确保数据质量,在程序运行过程中通过监听器机制执行数据验证操作。具体来说,在接收输入数据时需检查各个字段类型是否正确:例如经纬度字段必须是有效的数字格式,并符合预期的格式要求。当发现有异常值时(如非数字字符),将这些异常记录至一个错误列表中,并将问题数据跳过后继续处理其他有效数据。导入完成后系统会自动生成一个详细的错误报告表单,并列出所有出错的数据行及其具体原因。
12.面试官问 : 用户反馈扫码后设备没反应,你会怎么排查?
应试者回答
面试官追问 :
遇到接口返回500错误时,请问如何定位导致该错误的原因?
应试者回答 :
首先查阅ELK日志,并通过请求ID来检索相关日志记录。查看异常堆栈的具体情况。如果发现是Redis连接超时,则确认Redis服务器的状态是否正常,并核实内存资源是否足够;如果遇到数据库操作失败的情况(如出现唯一键冲突),则需检查插入的数据是否存在重复现象;若为MQTT消息发送失败,则需检查MQTT服务器的连接状态。查看日志中会包含详细的错误信息提示。
面试官认真追问:
如何有效预防类似故障反复发生?
应试者详细解答:
基于故障分析结果进行代码优化, 包括在Redis操作中加入超时重试机制, 以防止长时间阻塞; 为数据库唯一键冲突配置友好的错误提示信息, 以便用户了解哪里出现了问题; 并对设备锁实施自动释放策略, 定时扫描并清理过期的锁. 此外, 建立完善的监控告警机制, 如当Redis内存占用超过80%时触发报警, 从而及时扩容以规避服务崩溃.
13.面试官问 : 项目里RabbitMQ的消费者开了多少线程?怎么确定这个数值的?
应试者回答 :
默认启用了3个线程以处理消息流。由于项目中同时处理的消息数量不算大,默认选择这个数值是基于压测结果确定的。通过JMeter进行了1000条消息的模拟测试,在使用3个线程的情况下达到了最高的吞吐量,在这种情况下再多启用一个以上的线程会导致CPU利用率无法提升,并使效率有所下降。如果未来消息流量增加,则需要动态地调整线程数量,并且可以通过配置文件修改concurrency参数来实现动态调整
面试官追问
面试官再追问
14.面试官问 : 在充电流程中,数据库事务是怎么保证一致性的?
应试者回答 :
在完成充电订单的创建及设备状态更新过程中,采用Spring框架中的@Transactional注解进行操作管理。确保这两个操作要么全部成功执行,要么全部出现故障。例如,在支付完成后先记录订单信息并随后将设备状态标记为"充电中"。若设备状态更新失败,则会同时回滚相关的订单记录以防止出现虽然完成了支付但设备状态未被更新的情况发生。
面试官追问 :
事务的隔离级别用的是默认设置吗?为什么要选择这个选项呢?
应试者回答 :
采用了默认设置READ_COMMITTED的原因是由于项目中存在大量读操作而较少的写操作。选择这种隔离级别能够有效防止出现脏读问题,并且与可重复读相比不会造成阻塞或性能上的损失。具体来说,在查询设备状态时(例如获取最新的设备信息),这种方法确保了我们不会因未提交的数据而影响查询结果的一致性。
面试官再次提问:
若场景中有高耗时的操作,请问如何进行优化?
应试者作答:
建议将耗时操作移出事务范围进行处理。具体而言,在此类场景中可采取以下措施:例如,在处理订单的同时同步更新设备状态信息;而将如发送短信、生成日志记录等高耗时任务独立为非阻塞流程。进一步优化方法是将具有较高并发需求的操作集中配置在一个单独的可并行执行的交易体内(如创建订单及设备状态更新),同时将所有非并行的任务(如短信发送)安排在该交易之外运行。通过RabbitMQ实现异步处理机制后可显著降低此类交易的整体执行时间,并有效减少锁长时间占用的情况;最终能够有效提升系统的并发处理能力。
15.面试官问 : 调用支付宝支付接口时,如果超时怎么办?
应试者回答 :
配置了超时设置,并指定时间为5秒的时间长度。
当超出该时间后触发异常处理并通知用户。
在数据库中将订单状态标记为待付款。
启动一个定时进程以每分钟间隔检查支付进度。
该过程持续至支付完成或出现故障。
这一安排旨在防止订单停留在半途而废的状态。
面试官追问 :
请阐述定时任务是如何实现的?是否采用了Spring Schedule?
应试者回答 :
是的,在项目中我们配置了一个定时任务,并使用@Scheduled(fixedRate = 60000)来设置每分钟扫描所有处于'支付中'状态下的订单,并发起支付宝支付状态查询请求。该功能能够每隔一分钟自动检查订单的状态,并根据结果进行相应的处理:若24小时内未收到支付确认,则会自动取消该订单并释放设备锁以避免资源占用问题。
面试官继续询问
应试者详细解答
最后总结
