简历介绍
自我介绍
面试官您好,我是xxx,来自xxxx的26届学生。在校期间自学Java相关技术栈,javase,javaweb,相关框架SpringCloud Alibaba,SpringBoot,SpringMVC,关系型数据库Mqsql,达梦数据库,非关系型数据库redis,消息队列RabbitMQ,同时参加过国赛。我曾经有过两端实习经历,一段在北京致远互联软件股份有限公司,一段在金华科维城有限公司。同时我自己有自己的学习博客网站分享,浏览量已经破万。在公司期间独立和甲方对接,完成甲方的客制化开发,最近独立完成了统一身份认证的开发,以及在节点审批流程中在自定义节点中处理附件添加水印。在金华科维城公司项目已经在微信小程序上线投入使用。
致远
我在致远互联做的是A8协同办公平台,这其实就是一个企业内部的办公系统,就像钉钉、企业微信那种,但是功能更全面。我们公司主要做的是给大企业、政府机关提供这种办公软件,客户包括很多央企、国企。我近期负责的是统一身份认证这块,就是解决一个很常见的问题:企业里有很多系统,比如OA系统、财务系统、人事系统,员工每次都要登录好几次,很麻烦。我做的就是让员工登录一次,就能访问所有系统,这就是单点登录。遇到的主要问题是并发问题。比如一个审批流程,可能同时有10个人要审批,这时候就容易出现重复审批、状态错乱这些bug。我通过Redis分布式锁解决了这个问题,把响应时间从300毫秒优化到了80毫秒。还有一个是大文件上传的问题,客户经常要上传几百兆的附件,经常断网就白传了。我做了分片上传和断点续传,就是把大文件切成小块,一块一块传,断了也能接着传,效率提升了60%。这个项目让我学到了很多企业级开发的经验,特别是高并发、大数据量这些场景的处理。
统一身份认证
用户 → 业务系统 → 认证中心 → 登录页 → 验证身份 → 颁发code → 业务系统 → 用code换token(JWT) → 携带token访问API → 资源服务器验证token
重置版:因为是新项目,所以这一块mt让我做了,告诉了我一个流程,我自己梳理清楚了才着手开发,首先业务需求是这样,甲方不仅有我们的COP平台,甲方还有其他公司开发的其他平台,比如党建系统,门户系统。我们要实现从我们系统到这些第三方系统都要免验证,甲方以前用的COP系统并不是我们公司的,现在换成我们公司了,所以这一块要重新对接。我们多方系统设置了统一认证中心,他会给我们多个系统分配
client_id和client_secret,明确系统之间的访问权限,这个系统是用的OAuth2.0。当我们A系统需要跳转到B系统的时候,我们内部会先携带code授权码、随机字符串(防止钓鱼)以及注册ID和一个回调地址,去获取token信息,统一认证平台会验证我们的授权码和注册ID的有效性,同时需要将之前的随机字符串以及token返回给我们,将token保存在统一认证的redis,我们再携带这个token和用户信息以及权限信息,访问B系统的时候,B系统会去平台校验我们是否有权限,同时验证token是否在统一认证平台的Redis有保存,如果有就通过我们传递的用户信息进行免登录访问。因为我们系统还有H5端和其他端,统一认证中心都有分配不同的client_id,这里我也做了一个多端适配。如果其他端访问我们对应的接口,我要重定向到统一认证中心验证信息,并且将token和他们的用户信息保存到我们系统的Redis,这样下次再来访问的时候我们就不用再次去统一认证中心验证了。并发审批处理
因为系统分多端的,比如M3、HR、OA都可以审批流程,传统的Synchronized和 ReentrantLock锁就解决不了并发的问题。首先,我们定义了一个审批锁的服务类,用RedissonClient来操作。当有人要审批时,我们通过流程ID生成锁的key,比如”approval:lock:flow_12345”。然后调用redissonClient.getLock(key).tryLock(等待时间, 锁持有时间, 时间单位),比如tryLock(3秒, 30秒, TimeUnit.SECONDS)。如果获取到锁就返回true,获取不到就返回false。获取到锁后,我们执行审批逻辑:更新审批状态、记录审批意见、发送通知等。执行完成后,调用lock.unlock()释放锁。关键是我们还加了重试机制,如果第一次获取锁失败,我们会重试最多3次,每次间隔100毫秒。这样能提高获取锁的成功率。另外,我们还加了异常处理,在finally块里确保锁一定会被释放,避免死锁。而且Redisson内部有看门狗机制,会自动续期,即使业务执行时间比较长,锁也不会过期。
分片上传
前端直接上传整个大文件到后端,后端收到文件后,先检查文件大小,如果超过阈值(比如10MB),就自动进行分片处理。后端分片是这样做的:用Java的RandomAccessFile,按固定大小(比如5MB)读取文件,每读取一片就生成一个分片文件,文件名是”原文件名_分片序号”。然后我们开启线程池,线程池的参数是设置的动态的,由当前操作系统的CPU核心数决定,核心线程数设置的CPU核心的两倍,最大线程数是核心线程数的两倍,任务队列设置的100。因为处理频繁,空闲时间设置的10s。当时做压测的时候,因为产品说会上传几个G的文件,所以我们直接是用的10G的文件做的测试,5M一个分片,假设有2000个分片,我们将每个任务处理10个分片,这样会分为多个任务。线程池设置32个线程,每个线程处理一个任务。关键是我们用了分段处理的方式:每个线程负责一个连续的分片段,比如线程1处理分片1-10,线程2处理分片11-20,依此类推。这样每个线程处理的分片是连续的,不会出现跨段的情况。具体实现是这样的:每个线程执行任务时,会按顺序处理自己负责的分片段,比如线程1先读取分片1,再读取分片2,依此类推,直到处理完分片10。处理过程中,每个线程会生成一个临时文件,比如”temp_1”、”temp_2”、”temp_3”、”temp_4”。所有线程执行完成后,主线程再按临时文件的序号顺序合并,比如先合并”temp_1”,再合并”temp_2”,依此类推。这样能保证最终文件的完整性。这一块线程池也用了自定义拒绝策略做将MQ做兜底,防止任务量太多导致线程执行默认拒绝策略导致的文件分片数少了损坏。
数据迁移
这一块是客户源系统中的数据需要迁移到我们的系统,他们那边使用的源系统内的员工信息、组织架构、审批流程和文档等等系统中的数据,因为使用了我们的新系统,数据格式也需要转换成我们的这里的数据。因为字段名和一些多表连接的问题存在。我需要先设置一个中间表,作为两个系统之间的中间层,二次迁移,能防止迁移失败导致的数据丢失,也能保证数据的安全性,从他们系统迁移到中间表,要经过数据清洗、处理表关联字段和数据校验。这步我也开多线程并发的处理的,数据量确实比较大,中间表迁移好以后对接没有问题,再开线程池,批量的插入我们的系统数据中,同时要记录异常的情况,因为是分批的,如果某一批导入失败了,回滚重试,失败超过三次就做异常处理,将有问题的批次数据放提前设计好的日志表中,该日志表存放一些导入失败的信息,错误类型错误原因和批次ID。等修改好出现问题的原因,通过批次ID,找到对应的一批数据,再次增量导入。当然我们默认是如果一批出现问题,整批回滚,可以通过配置参数改成不回滚,而是将有问题的数据的ID统一返回,检查这批有问题的ID,重新上传即可。
金师附小
Token重复刷新
其实这个业务场景并不是像电商那样的超高并发,但是也是需要预防,比如教师端早上集中批作业的时候,家长端早上送孩子的时候,教师端确认学生是否到的时候。我们token的刷新时间是1小时,如果教师端早上可能检查了学生前一天的昨天的作业有没有提交,然后到了学校以后,需要给每个学生录入考勤,这个时候token已经临近过期了。此时这一连串的 批量请求:获取班级学生列表→批量更新出勤状态→提交作业批改结果→发送家长通知。都会检测到token时间不足,会去请求刷新token。家长端也是一样的,早上起来会检查作业有没有提交,等学生送到学校以后考勤的时候,刚打开小程序是会加载多个模块的,也会有多个请求同时刷新token,这样在早高峰和放学的高峰期,就会出现并发问题,这么多家长和教师,就会有大量的无效请求,因为token只用刷新一次,造成资源浪费,若前 1 次刷新成功生成新 Token 后,旧 Token 被标记失效,后续 2 次重复刷新请求会因 “旧 Token 无效” 失败,导致对应的接口请求(如作业提交)鉴权失败 —— 教师会看到 “登录已过期,请重新登录”,影响教学任务推进。所以这一块我做了ReentrantLock锁,在刷新token接口,使用trylock设置了1m的等待时间,当获取锁失败的时候并不直接抛出异常,而是进行5次自旋的操作,检查Redis中的token是否更新。为了防止token失效和token刷新中间的时间,有其他线程抢时间片,做了Redis的lua脚本当旧token未被修改再去更新token,保证了原子性。数据库层面添加了version,乐观锁的思想,更新的时候version版本+1,并且数据要判断version和自己读到的是否一致。
多层级兜底
这一块的消息通知原本是通过微信小程序进行的同步通知么,但是同步通知的缺点也很明显,如果出现网络问题,阻塞主业务的正常进行,比如在提交作业,录入考勤的时候,以及通知家长考试成绩,如果通知出现问题,也会影响作业的提交。后面因为引入了MQ,所以这部分也进行了修改,用AOP,把原本的同步通知改成了异步。先定义了切面用做消息通知,这里消息发送到队列的时候也做了很多异常处理。首先消息顺利发到队列的以后,会执行微信通知。并添加了消费者ACK,需要手动ACK确认消息已经处理,删除队列的消息。如果消息队列没有收到ACK,默认进行重试机制,为了防止消息不断的重试一直失败,当时设置的三次,如果超过三次都没有收到消费者的ACK,就进入死信交换机里面的死信队列,死信队列中对消息的处理也分死亡原因进行了不同的处理方式,如果过期了,那如果是消费过期了,就发送短信消息直接通知家长。如果队列长度限制导致的死亡,就重入队。通过MQ做兜底+异步,提高了消息处理的效率和消息通知的安全性吧。
跨端数据一致性
在金师附小成长报告系统中,我们最初遇到了一个很典型的数据同步问题:家长端微信小程序和教师端Web管理平台之间存在5-10秒的数据同步延迟。比如老师在教师端录入了学生的出勤信息,家长端要等5-10秒才能看到;学生提交了作业,老师端也要等这么久才能收到通知。这严重影响了家校之间的实时沟通效率。-当教师端或家长端产生数据变更时,我们立即发布一个数据变更事件到RabbitMQ,那ack机制和重试机制,进入死信队列这些消息的保障性也都做了。为了提高数据访问的速度,尤其是在早高峰和下午放学的时候,对于热点数据比如作业提交和考勤,做了缓存预热,同时收到数据变更消息后,立即更新Redis缓存。我们用WebSocket实现实时推送建立了稳定的WebSocket连接池管理,每30秒进行心跳检测,自动清理无效连接,根据数据类型设置不同优先级,评价通知优先级最高,用户离线时,将消息暂存,重新上线后批量推送。
WebSocket
“WebSocket是一种在单个TCP连接上进行全双工通信的协议。本质上,它解决了HTTP轮询的效率问题,让服务器可以主动向客户端推送数据,而不是客户端不停地询问服务器有没有新数据。在金师附小成长报告系统中,我们用WebSocket来解决家长端和教师端的数据实时同步问题。比如老师在教师端更新了学生的出勤信息,我们需要立即推送到家长的小程序端,以前要等5-10秒,现在可以做到1秒以内。
“连接建立的过程是这样的: 客户端发起WebSocket握手请求,从HTTP协议升级到WebSocket协议,建立持久连接,双方都可以随时发送消息我们项目中还实现了连接池管理,用ConcurrentHashMap维护用户连接映射,确保每个用户的所有设备都能收到推送。为了保证连接稳定性,我们实现了心跳检测机制:每30秒发送一个ping帧,客户端必须在规定时间内回复pong帧。如果连续3次心跳失败,就认为连接已断开,会自动清理连接并通知客户端重连。这样就避免了死连接占用资源的问题。消息推送方面,设计了智能策略:
实时推送:对于高优先级消息,如评价通知、作业提醒,立即推送
批量推送:对于非紧急消息,积累一定数量后批量发送,减少网络开销
离线补偿:用户离线时,将消息暂存到Redis,重新上线后批量推送
我们还根据消息类型设置了不同优先级,确保重要通知优先送达。
“异常处理也很关键:
连接断开时,自动触发重连机制,消息发送失败时,记录失败原因,转入补偿流程
内存泄露防护:定期清理无效连接,设置连接最大存活时间
消息的实时推送
短链接
前端每个一段时间发送请求数据
长链接
接收到前端请求,等数据变更再更新数据,返回通知前端 线程阻塞
SSE
前端给后端一个长连接,后端通知前端建立SSE长链接协议,这样服务端和客户端就建立好链接,当服务端消息变更,实时的推送消息到前端。
旅牛网
秒杀
在旅牛网平台,我们推出了限时门票秒杀活动,比如热门景区的早鸟票、特价票等,存在,如果没有防护措施,同一张门票可能被多人同时购买。这个场景瞬时的并发量是比较高的,首先是要解决库存超卖和防止刷单的问题,用了分布式锁+数据库的乐观锁双重保险。首先我们会提交对需要被秒杀的门票进行缓存预热,将门票信息和库存缓存到redis,当用户秒杀,会先判断是否有秒杀资格和库存余量是否足够,没有的话返回提示。接着就使用redis分布式锁,这里我选择用的Redisson保证的库存扣减的原子性,给每个门票生成一把独一无二的锁同一时刻,只允许一个用户扣减这个门票的库存,其他用户需要排队等待锁释放,通常等待时间不超过3秒。扣除库存有三步操作么,检查库存,扣减库存,记录用户购买。同时为了保证通知用户购买成功这个消息,会影响到秒杀场景,由于订单处理阻塞秒杀请求,库存扣减成功以后直接把订单消息丢到消息队列中了,消息队列异步的处理订单,这样用户会直接收到秒杀成功的提示。后续可以通过线程池专门处理用户支付,为了防止分布式锁失效,还做了数据库的乐观锁做兜底。
传输优化
这里之前学习Java的时候,包括很多教程其实序列化这里普遍采用的都是jackson,但是像我们这个项目调用接口的地方其实是比较多的,景区一些数据量也比较大,F12检查过接口反应时间,也是比较长,对用户体验不是很好,后面就了解到protostuff,这个谷歌的二进制序列化工具,实测会快将近十倍。后面也通过前后端数据的统一,封装对应的DTO对象和分页懒加载等优化手段,将接口速度优化,同时会将热点数据放redis缓存一份,让用户对于界面数据渲染的感知进一步降低。
AI推荐
这个在我参加比赛的时候其实是考虑使用的当时一些推荐算法和机器学习算法,那个时候并没有像现在这个有开源的大模型使用,开源的算法比较多,最开始接触也是之前打数据清洗的比赛,比赛方提供的数据有些缺失值,比如有些景区的评论,有课并没有进行打分,那个这里之前是使用的sklearn,是之前比较热门的机器学习库,我们一般要将数据封装成特征矩阵x和目标变量y,并且要划分训练集和测试集,创建模型,训练。最后会评估性能,能让模型达到可以可用的状态。所以这个的准确性其实是需要精确并且大量的数据支撑的。
而现在的大模型的训练方式,其实我觉得底层还是大量的数据训练,但是训练方式跟之前是不太一样啦:现在~~~
动态线程池
1 | 应用启动 → 自动配置 → Redis连接 → 消息监听 → 定时任务 |
这个项目本质上是一个可以远程控制和监控的智能线程池管理系统。它的核心功能包括:
🎯 主要功能
- 远程配置管理:管理员可以通过管理界面实时调整线程池参数,无需重启应用
- 实时监控告警:自动收集线程池性能指标,发现异常及时通知
- 多环境适配:支持开发、测试、生产环境的统一管理
- 可扩展通知:支持钉钉、邮件等多种通知方式
项目是怎么工作的?(文字流程说明)
当你的Spring Boot应用启动时,系统会扫描应用中的所有ThreadPoolExecutor实例
,从Redis中获取线程池配置参数,如果Redis有配置,用新配置替换现有线程池, 启动Redis消息监听器,等待配置更新指令。 启动定时任务,每5秒收集监控数据,每20秒上报配置状态
第一步:管理员在管理界面操作,管理员登录管理后台,查看当前线程池状态,发现某个线程池的核心线程数需要调整,-在界面上修改参数并保存
第二步:配置通过Redis发布,管理后台将新的配置参数保存到Redis数据库,同时通过Redis发布订阅机制发送更新消息,消息包含:应用名、线程池名、新参数等信息
第三步:应用接收并处理消息,应用中的消息监听器收到Redis消息,解析消息内容,验证配置合法性, 调用线程池服务更新参数
第四步:平滑替换线程池实例, 创建新的线程池实例,应用新配置参数,将旧线程池中的待处理任务迁移到新队列,安全关闭旧线程池实例, 用新实例替换Spring容器中的引用
第五步:上报最新状态,更新监控指标数据,发送通知提醒相关人员,保存配置变更历史
| 对比维度 | 传统线程池 | 动态线程池 |
|---|---|---|
| 配置修改 | 改代码→打包→重启 | 界面点击→实时生效 |
| 监控可视化 | 代码埋点或猜 | 实时图表+历史趋势 |
| 多环境管理 | 各自维护,容易不一致 | 统一配置中心管理 |
| 异常处理 | 被动发现,事后补救 | 主动监控,及时告警 |
| 扩展性 | 硬编码,难以扩展 | SPI插件化,可插拔 |





