美云智数 Java软件开发 二面 面经

1. 深入聊聊你的项目,从需求到上线的完整流程 (30min)

项目背景:

我做的是一个在线教育平台项目,主要面向K12学生提供在线课程和作业辅导服务。项目上线后日活用户达到5万左右,高峰期并发量在3000左右。我在项目中主要负责课程管理模块和作业系统模块的后端开发。

技术架构:

整体采用微服务架构,前后端分离。后端使用Spring Cloud技术栈,包括Gateway网关、Nacos注册中心、Feign服务调用、Sentinel限流熔断。数据库使用MySQL主从架构实现读写分离,Redis做缓存和分布式锁。消息队列使用RabbitMQ处理异步任务。文件存储使用阿里云OSS。

服务拆分为用户服务、课程服务、订单服务、作业服务等。每个服务独立部署,使用Docker容器化,通过Jenkins实现CI/CD自动化部署。

开发流程:

需求阶段,产品经理提出需求,我们进行需求评审,明确功能点和验收标准。然后进行技术方案设计,评估技术难度和工作量。

开发阶段,我们使用敏捷开发模式,两周一个迭代。每天站会同步进度,遇到问题及时沟通。开发完成后进行代码review,保证代码质量。

测试阶段,先自测功能,然后提交给测试人员。测试发现bug后在禅道系统提单,我们修复后回归测试。

上线阶段,先在测试环境验证,然后灰度发布到生产环境。先发布10%流量观察,没问题再全量发布。上线后监控系统指标,及时处理异常。

技术难点:

最大的挑战是作业批改的性能问题。学生提交作业后,系统要自动批改选择题和填空题。高峰期有上千份作业同时提交,同步处理会导致接口超时。

我的解决方案是改为异步处理。学生提交作业后立即返回成功,作业信息发送到RabbitMQ。后台消费者从队列获取作业进行批改,批改完成后通过WebSocket推送结果给学生。这样接口响应时间从5秒降到200毫秒,用户体验大幅提升。

另一个难点是课程视频的防盗链。我们使用阿里云OSS的签名URL机制,每次播放视频时生成临时URL,设置1小时过期。同时在播放器中加入防录屏水印,显示学生ID和时间戳。

项目成果:

项目上线后运行稳定,接口平均响应时间在200毫秒以内,99分位响应时间在500毫秒以内。系统可用性达到99.9%以上。通过Redis缓存,数据库查询压力降低了70%。用户反馈体验良好,续费率提升了15%。

2. JVM内存模型和垃圾回收机制,如何进行JVM调优?

JVM内存模型:

JVM运行时数据区分为线程共享和线程私有两部分。

线程私有的有程序计数器、虚拟机栈、本地方法栈。程序计数器记录当前线程执行的字节码行号。虚拟机栈存储局部变量、操作数栈、方法出口等信息,每个方法对应一个栈帧。本地方法栈为Native方法服务。

线程共享的有堆和方法区。堆是最大的内存区域,存储对象实例和数组,是垃圾回收的主要区域。方法区存储类信息、常量、静态变量、JIT编译后的代码,JDK8后改为元空间使用本地内存。

垃圾回收机制:

垃圾回收主要针对堆内存。首先要判断对象是否存活,主要使用可达性分析算法。从GC Roots开始遍历对象引用链,不可达的对象就是垃圾。GC Roots包括虚拟机栈中的引用、方法区的静态变量、常量引用、本地方法栈的引用等。

垃圾回收算法有标记-清除、标记-复制、标记-整理三种。新生代使用标记-复制算法,分为Eden和两个Survivor区,比例8:1:1。每次使用Eden和一个Survivor,回收时把存活对象复制到另一个Survivor。老年代使用标记-整理算法,标记存活对象后让它们向一端移动,清理边界外的内存。

垃圾收集器:

常用的垃圾收集器有Serial、Parallel、CMS、G1、ZGC。

Serial是单线程收集器,适合单核CPU和小内存场景。Parallel是多线程收集器,注重吞吐量,适合后台计算任务。

CMS是并发收集器,追求最短停顿时间。分为初始标记、并发标记、重新标记、并发清除四个阶段。只有初始标记和重新标记需要停顿,其他阶段和应用程序并发执行。缺点是会产生内存碎片,而且并发阶段占用CPU资源。

G1是目前主流的收集器,把堆划分为多个Region,可以设置停顿时间目标。G1会优先回收价值最大的Region,实现可预测的停顿。适合大堆内存场景。

JVM调优:

首先要设置合适的堆内存大小。一般设置为物理内存的一半到三分之二。Xms和Xmx设置为相同值,避免动态扩容。新生代大小设置为堆的三分之一左右。

选择合适的垃圾收集器。JDK8推荐使用G1,设置最大停顿时间目标,比如200毫秒。

开启GC日志,分析GC频率和停顿时间。如果Full GC频繁,可能是老年代空间不足或内存泄漏。如果Young GC频繁,可能是新生代空间太小。

使用jstat、jmap、jstack等工具监控JVM状态。发现内存泄漏时用jmap dump堆内存,用MAT工具分析。发现CPU高时用jstack查看线程堆栈,定位问题代码。

调优要根据实际情况,不能盲目调整参数。要先监控分析,找到瓶颈,再针对性优化。

3. MySQL的主从复制原理,如何保证数据一致性?

主从复制原理:

MySQL主从复制是异步复制机制,用于实现数据备份和读写分离。

复制过程分为三个步骤。第一步,主库执行SQL语句后,把数据变更记录到binlog二进制日志。binlog有三种格式:Statement记录SQL语句,Row记录数据变更,Mixed混合模式。

第二步,从库的IO线程连接主库,读取binlog内容,写入从库的relay log中继日志。主库有一个binlog dump线程负责发送binlog给从库。

第三步,从库的SQL线程读取relay log,解析并执行SQL语句,重放主库的数据变更。这样从库的数据就和主库保持一致。

复制延迟问题:

主从复制是异步的,存在延迟。主库写入后,从库可能还没同步完成,这时读从库会读到旧数据。

延迟的原因有几个。一是网络延迟,主从库之间的网络传输需要时间。二是从库性能差,SQL线程执行慢。三是主库写入量大,从库来不及同步。四是大事务,一个事务修改大量数据,从库重放需要很长时间。

保证数据一致性:

第一种方案是使用半同步复制。主库写入binlog后,至少等待一个从库接收到binlog并写入relay log,才返回成功。这样保证至少有一个从库有最新数据,但会影响性能。

第二种方案是读写分离时强制读主库。对于必须读最新数据的场景,比如用户刚修改完信息立即查询,强制读主库。其他场景读从库。

第三种方案是延迟读取。写入后等待一段时间再读取,比如等待1秒,让从库有时间同步。但这种方式用户体验不好。

第四种方案是使用缓存。写入时同时更新缓存,读取时先读缓存。这样即使从库有延迟,也能读到最新数据。

第五种方案是监控主从延迟。通过show slave status查看Seconds_Behind_Master,如果延迟超过阈值,暂时不读该从库。

实际使用中,要根据业务特点选择方案。对一致性要求高的用半同步复制或强制读主库。对一致性要求不高的可以接受短暂延迟。

4. 分布式锁的实现方式,Redisson的原理

分布式锁的必要性:

在分布式系统中,多个服务实例可能同时操作共享资源,需要分布式锁保证互斥。比如秒杀场景,多个服务实例同时扣减库存,需要加锁保证原子性。

实现方式:

基于数据库实现,可以用唯一索引或for update悲观锁。优点是简单,缺点是性能差,数据库压力

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论

相关推荐

03-31 09:40
门头沟学院 Java
刷到这个话题,突然就停下了敲代码的手。作为刚实习三个月的后端开发,vibe coding 早就不是什么 “高大上的项目创作”,而是我每天下班之后,在出租屋里给自己留的最后一点编程的快乐。白天在公司写代码,全是条条框框。要严格遵守团队的开发规范,要过同事的 code review,要写全单元测试,要考虑线上性能,要应对没完没了的需求变更。写的永远是重复的 CRUD,改的永远是测不完的 bug,开的永远是没营养的会,敲键盘的时候,心里想的全是 “别出 bug、别被 mentor 骂、别耽误提测”。只有晚上回到出租屋,打开电脑,进入 vibe coding 的状态,才觉得自己是真的在写代码,而不是完成任务。不用管什么开发规范,不用管什么架构设计,不用管什么性能优化,想怎么写就怎么写,想加什么功能就加什么功能,哪怕代码写得再糙,哪怕只有自己能用,哪怕写完玩十分钟就腻了,也没关系。最开始写的第一个 vibe coding 小玩意,是个打卡提醒的 Python 脚本。公司是弹性打卡,早来早走、晚来晚走,我总是忙起来就忘了下班时间,经常免费加班半小时才反应过来。就花了半个多小时,写了个挂在后台的小脚本,到了下班时间就弹全屏提醒,还能自动统计每天的打卡时长,算加班了多久,不用再对着打卡表掰着手指头算。这个脚本没有 UI,没有打包,甚至连异常处理都只写了最基础的,可我用到现在,每天下班都靠它提醒,比手机闹钟好用一百倍。后来又写了个摸鱼刷题的小工具。秋招要刷算法题,上班总打开牛客网页,怕被路过的 leader 看见,就用 Java 写了个最小化的桌面小程序,挂在屏幕角落,每隔一小时弹一道 LeetCode 简单题,写完就能收起来,不影响写业务代码,也不会被人发现。周末闲着没事的时候,就更放飞了。有次周六下雨,没法出门,就在出租屋里跟着感觉,用 Swing 写了个贪吃蛇小游戏。没有什么复杂的玩法,就是最基础的上下左右吃豆子,甚至连碰撞检测都写得很糙,可我对着这个小游戏,改颜色、改速度、加无敌模式,折腾了整整一下午,写完之后自己玩了十分钟就腻了,可写代码的那一下午,是我实习以来最放松的时刻。不用管需求,不用管评审,不用管上线,不用为任何人负责,代码只需要取悦我自己。我也试过跟着网上的教程,想写个完整的个人博客,搭好了 SpringBoot+Vue 的框架,写了登录接口,可写着写着就觉得没意思了 —— 这和白天在公司写业务代码有什么区别?反而没了 vibe coding 的快乐,最后这个项目就扔在 GitHub 里,再也没动过。现在我终于明白,vibe coding 对我这种实习程序员来说,从来不是为了做出什么牛逼的项目,也不是为了写进简历里加分,就是在被工作磨掉对编程的热情的时候,给自己找回来一点最开始学代码的快乐。大一的时候,第一次用 C 语言写出个 Hello World,都能开心半天;第一次写出个简单的计算器,能跟室友炫耀好久。那时候写代码,没有 KPI,没有 bug 追责,没有需求变更,就是单纯的觉得好玩、有意思。而 vibe coding,就是让我在实习的兵荒马马里,重新找回这种快乐的方式。它不用很复杂,不用很完美,甚至不用写完。只要写的那一刻,我是开心的,就够了。
你都用vibe codi...
点赞 评论 收藏
分享
评论
1
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务