总结面试中可能会涉及到简历的问题
首先,关于简历中的问题主要集中在项目相关和技能相关,会涉及到简历中提到过的相关技术,相关技术实现了什么样的功能,怎样实现的
自我介绍
面试管你好!我叫张凯,毕业于青岛农业大学计算机科学与技术专业,目前在哈尔滨理工大学攻读软件工程硕士。过去几年我主要在Java开发领域积累了丰富的项目经验,例如在“健身交流网”项目中负责设计Redis集群、实现缓存优化与秒杀模块,在“即时聊天室”项目中搭建了基于WebSocket的聊天系统,同时熟练掌握了Spring Boot、SSM、Vue等主流技术。
健身交流网相关问题
首先,请你介绍一下“健身交流网”项目的整体架构和你在其中承担的具体职责。
项目介绍:健身交流网是一款面向健身爱好者的平台,主要功能包括:登录与认证、健身场馆、器材和教练的浏览,健身卡优惠秒杀、健身打卡和健身心得分享、点赞与关注并通过地理位置检索附近健身馆等功能。
主要工作:
- 设计并实现基于Redis集群的会话存储,解决多节点Session共享问题。
- 利用拦截器和JWT令牌统一鉴权,保证用户登录状态一致性。
- 采用热点数据缓存和随机过期时间策略,有效降低缓存击穿风险。
- 利用Lua脚本、Redis原子操作和互斥锁实现健身卡的秒杀功能,防止超卖现象,保证了数据一致性
- 部署Stream消息队列处理异步订单和活动通知,提升秒杀及健身打卡数据处理吞吐量。
- 使用HyperLogLog、GeoHash、Redis Set技术,实现UV统计、点赞排序和附近健身馆检索功能。
- 基于Kafka异步处理用户打卡、评论和关注等核心交互功能的推送和通知。
主要介绍一下健身交流网项目中个功能都用的哪些技术
1. 登录功能
-
账户密码登录:
-
用户输入账户、密码和验证码。
-
验证码通过通用验证码库生成后,存入 Redis 并设置合理的过期时间;用户提交时对比 Redis 中的验证码验证其正确性。
-
-
手机号登录:
-
用户输入手机号和短信验证码。
-
系统先生成短信验证码,并存入 Redis 设置过期时间;同时调用短信服务平台将验证码发送到用户手机上。
-
用户提交后,后端从 Redis 中取出验证码进行验证,确认无误后完成登录。
-
2. 点赞功能
-
点赞记录更新:
-
初步更新: 用户点击点赞后,利用 Redis 提供的原子命令立即更新点赞数,确保并发环境下操作原子性。
-
异步同步数据库: 为避免每次点赞直接更新数据库导致压力过大,采用消息队列将点赞操作记录异步收集,进行批量写入更新。
-
补偿机制: 设置补偿策略以应对消息任务执行失败的情况,确保数据最终一致性。
-
-
点赞排行榜重建:
-
当排行榜缓存失效时,当前请求检测到缓存缺失后,通过分布式锁(采用双重校验机制)保证仅有一个请求触发缓存重建。
-
将重建缓存的任务异步发送至消息队列,由后台任务完成数据库查询和缓存更新;若短时间内缓存未更新,可返回默认页面,避免对数据库造成过大压力。
-
3. 点赞排序功能
-
实现方式:
-
使用 Redis Sorted Set 存储点赞记录,将每个点赞用户的唯一标识作为 member,使用时间戳(或自定义的分数值)作为 score,保证点赞顺序(时间越早分数越低,越靠前显示)。
-
当需要展示点赞排行榜时,从 Sorted Set 中获取排序后的用户 ID 列表,再根据 ID 查询数据库获得用户详细信息。
-
4. 健身卡秒杀功能
-
全局订单 ID 生成:
-
利用 Redis 的自增数值(32 位)与当前时间戳(31 位)及符号位(1 位)组合,生成唯一订单 ID,保证全局唯一性。
-
-
一人一单限制:
-
使用 Redis 的 setnx 命令为每个用户在特定秒杀活动中设置互斥锁,确保同一用户只能下单一次;同时设置合适的过期时间以防锁死。
-
-
库存预检与扣减:
-
通过 Lua 脚本在 Redis 中实现原子性库存检查和扣减操作,确保在高并发情况下不会出现库存超卖。
-
-
异步下单:
-
将下单请求封装为消息,通过 Redis Stream 消息队列异步写入;后台消费者读取消息后完成订单持久化、库存最终确认和其他业务逻辑处理,支持批量操作和幂等性设计。
-
5. 关注功能
-
实现方式:
-
利用 Redis 的 Set 数据结构管理用户关注关系,实现关注与取消关注的快速响应操作。
-
注意:由于 Redis 持久化机制可能不如数据库稳定,建议定期同步 Redis 数据到数据库,或设置相应的容错措施,防止数据丢失。
-
6. 健身打卡功能
-
签到记录存储:
-
使用 Redis 的 Bitmap(基于 String 数据结构实现)记录用户的每日打卡情况。
-
每个用户打卡记录可用一组位表示,支持海量数据存储(最大 512MB,可表示 2^32 位)并快速查询。
-
7. 健身场馆、器材及教练浏览功能
-
后端数据存储:
-
利用 MySQL 存储健身场馆、器材、教练及用户评分等信息,通过 MyBatis 实现数据的查询和更新操作。
-
-
前端展示与交互:
-
前端使用 Vue 和 Element-UI 构建用户友好的页面,通过 RESTful API 获取数据,并支持 Ajax 异步提交评分和评论,确保交互流畅且响应迅速。
-
为什么需要基于Redis来做Session共享?你考虑过其他方案吗?
session共享是指多台tomcat服务器之间不共享session存储空间,导致不同tomcat服务时会有数据丢失的风险,所以要找到一种解决方案来解决数据共享、内存存储、以及存储数据的结构问题。
而redis就满足以上三个特点,集中存储,同时也满足高并发、高性能的业务要求,同时要注意几点————选择redis数据结构,存储token用string,存储用户用hash
Mysql或者nosql数据库都无法同时满足上述要求,
你实现的会话共享机制中,Redis是如何存储Session数据的?存储格式和数据结构是怎样的?
通常会采用 Key-Value 形式来存储 Session 数据:
-
Key: 一般为 Session ID,可以包含前缀(例如 "sess:")以便区分。也可以是业务的订单key,workout:user:1来区分
-
Value: 存储经过序列化后的用户信息或会话数据,常用 JSON 格式,也可以采用 Java 的序列化方式。
此外,对于需要存储多个字段的情况,也可以利用 Redis 的 Hash(无序) 结构,但多数场景下直接用 String 类型存储序列化后的对象更加简单高效。同时,针对会话有效期,会设置 TTL(过期时间),保证会话自动失效。
介绍一下分布式锁,锁的粒度怎么优化
提到使用热点数据缓存加随机过期时间来降低缓存击穿率,请详细讲讲这个策略的实现原理和优缺点。
-
当热点数据设置固定的过期时间时,一旦到期,大量请求会同时击穿缓存直接访问数据库,造成数据库瞬间压力增大,甚至雪崩。
-
通过在设置缓存时增加一个随机偏移值(例如,在原有 TTL 上加减一个随机时间),使得缓存的实际过期时间各不相同,分散缓存失效的瞬间压力,降低数据库的访问峰值。
你在秒杀模块中提到了使用Lua脚本结合Redis的原子操作以及乐观锁来实现秒杀预检。请详细说明这个方案的流程,以及如何保证在高并发下不会出现超卖问题?
-
秒杀请求进入: 当用户发起秒杀请求时,系统首先通过 Redis 接收到请求。
-
库存预检与扣减(Lua 脚本):
-
利用 Lua 脚本在 Redis 中执行原子操作:检查库存是否充足,如果充足,则立即扣减库存。
-
此步骤保证了在高并发下,库存检查和扣减操作不会被多个请求交叉执行,从而避免超卖。
-
-
一人一单控制:
-
同时使用 Redis 的 setnx 命令为每个用户设置分布式锁,确保同一用户在同一活动中只能下单一次。
-
-
异步下单处理:
-
扣减库存成功后,将下单请求封装为消息,写入消息队列(如 Redis Stream 或 Kafka),由后台消费者异步处理订单持久化、库存最终确认及其他业务逻辑。
-
-
数据库层面的乐观锁:
-
在订单写入数据库时,采用乐观锁机制(例如版本号机制)确保即使在并发写入时,数据也能保持一致性,防止数据冲突。
-
乐观、悲观锁
1、悲观锁
悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候先加锁,确保数据的安全性。
在Java中,常见的悲观锁实现是使用synchronized
关键字或ReentrantLock
类。这些锁能够确保同一时刻只有一个线程可以访问被锁定的代码块或资源,其他线程必须等待锁释放后才能继续执行。
锁实现:关键字synchronized、Lock接口的实现
使用场景:写操作比较多,先加锁可以保证写操作时数据正确
2、乐观锁
乐观锁认为自己在使用数据的时候不会被别的线程修改,所以不会添加锁,只是在更新的时候去判断之前有没有别的线程更改过这个数据
乐观锁的原理主要基于版本号或时间戳来实现。在每次更新数据时,先获取当前数据的版本号或时间戳,然后在更新时比对版本号或时间戳是否一致,若一致则更新成功,否则表示数据已被其他线程修改,更新失败。
锁实现:CAS算法,例如AtomicInteger类的原子自增底层是通过CAS实现的
使用场景:读多,不加锁的特点能够使读的性能大幅度提升
CAS算法
CAS(Compare-And-Swap,即“比较与交换”)是一种常见的无锁原子操作,用于在多线程环境下实现并发控制,确保数据安全更新。其核心思想是利用硬件级别的原子指令,在更新数据前先比较内存中的值是否符合预期,再决定是否进行替换操作。
CAS操作主要有三个参数:要更新的内存位置、期望的值和新值。CAS操作的执行过程如下:
首先,获取要更新的内存位置的值,记为var。
然后,将期望值expected与var进行比较,如果两者相等,则将内存位置的值var更新为新值new。
如果两者不相等,则说明有其他线程修改了内存位置的值var,此时CAS操作失败,需要重新尝试。
如何使用拦截器实现统一鉴权?
我在 Spring Boot 中使用 HandlerInterceptor
拦截所有请求,在 preHandle()
方法中:
- 获取请求头中的 Authorization 字段。
- 校验 JWT 的合法性(签名、过期时间等)。
- 解析出用户信息并存入
ThreadLocal
,供后续业务逻辑使用。 - 若校验失败,返回 401 未授权响应。
即时聊天室相关问题
即时聊天室的相关实现
项目中如何使用 WebSocket 实现群聊和私聊功能?请详细描述消息的分发流程。
在项目中,我们通过 Spring Boot 内置的 WebSocket 支持实现实时聊天。具体流程如下:
- 客户端通过 WebSocket 连接到服务器,并进行身份认证(例如使用 JWT 令牌)。
- 服务端通过配置相应的 WebSocket 端点和消息处理器,根据消息中的标识(如群聊标识或私聊标识)判断消息类型。
- 对于群聊消息,服务器将消息广播给聊天室内所有在线的用户;对于私聊消息,则查找目标用户对应的 WebSocket 连接并推送消息。
- 消息经过分发后,服务端同时将消息持久化到数据库,供历史记录查询及未读消息提醒使用。
- 通过这种方式,能确保实时性和数据持久化之间的平衡。
WebSocket与HTTP协议有什么不同吗?
websoket是全双工通信协议,允许客户端和服务器端互相独立的发送消息,实施的传输数据,建立连接后,可长久开启。适应于在线游戏,即时通讯场景
Http是半双工通信协议,请求-响应模式,每次请求都要建立连接,处理后关闭连接,是英语传统网页,文件下载等场景
请解释一下 JWT 的基本结构及作用?
JWT(JSON Web Token)由三部分组成:Header
.Payload
.Signature
- Header:声明类型(typ: JWT)和签名算法(如 HS256)。
- Payload:携带用户信息(如 userId、role),还有如
exp
(过期时间)等标准字段。 - Signature:将前两部分用秘钥和算法进行签名,防止被篡改。
作用是用于在客户端和服务端之间传递经过加密签名的身份信息,实现无状态认证。
JWT的使用场景
- 身份验证(Authentication):JWT 可以被用作用户登录的身份验证凭证。当用户成功登录后,服务端可以生成一个包含用户信息的 JWT,并将其返回给客户端。以后,客户端在每次请求时都会携带这个 JWT,服务端通过验证 JWT 的签名来确认用户的身份。
- 授权(Authorization):在用户登录后,服务端可以生成包含用户角色、权限等信息的 JWT,并在用户每次请求时进行验证。通过解析 JWT 中的声明信息,服务端可以判断用户是否有权限执行特定的操作或访问特定的资源。
- 信息交换(Information Exchange):由于 JWT 的声明信息可以被加密,因此可以安全地在用户和服务器之间传递信息。这在分布式系统中非常有用,因为可以确保信息在各个环节中的安全传递。
- 单点登录(Single Sign-On):JWT 可以被用于支持单点登录,使得用户在多个应用之间只需要登录一次即可使用多个应用,从而提高用户体验。
在安全性方面,你是如何利用 JWT 和 Filter 实现用户鉴权的?
-
用户登录与 JWT 生成:
当用户提交用户名和密码后,后端服务验证凭证。如果验证成功,服务会生成一个 JWT。这个 JWT 内部包含了用户信息、权限、过期时间等声明(Claims),并使用服务器的密钥进行签名。生成的 JWT 是一个自包含的令牌,客户端无需保存服务器端状态即可进行后续请求的认证。 -
客户端存储与传输 JWT:
生成的 JWT 通常会返回给客户端,客户端一般将其存储在 LocalStorage、SessionStorage 或 Cookie 中。每次后续请求时,客户端会在 HTTP 请求头中附上这个令牌(例如在Authorization
头中使用Bearer <token>
格式)。 -
使用 Filter 拦截请求:
在服务端,可以配置一个过滤器(Filter),例如在 Spring Security 中常用的OncePerRequestFilter
。这个过滤器会拦截每一个进入的 HTTP 请求,并从请求头中提取 JWT。 -
JWT 验证与用户信息注入:
过滤器提取出 JWT 后,会进行以下验证:-
完整性验证: 检查 JWT 的签名是否正确,防止被篡改。
-
过期时间校验: 确认令牌未过期。
-
内容解析: 如果令牌有效,则解析出其中的用户信息和权限。
验证通过后,过滤器会将解析出来的用户信息放入安全上下文(例如 Spring Security 的SecurityContextHolder
中),这样后续的业务逻辑就能通过该上下文获取当前用户的认证信息。
-
-
继续处理请求或返回错误:
如果 JWT 验证成功,过滤器会让请求继续传递到后续的处理链(如 Controller),并允许访问受保护的资源。若验证失败,则过滤器会终止请求,并返回相应的未授权(如 HTTP 401)的错误信息。
项目中是如何整合阿里云 OSS 的?它在文件和表情包上传下载中起到什么作用?
OSS 为 Object Storage Service,即对象存储服务。
OSS 具有与平台无关的 RESTful API 接口,可以在任意应用、任意时间、任意地点 存储与访问 任何类型的数据。简单地理解:OSS 基于网络提供数据存储服务,通过网络可以随时存储、获取 文本、图片、音频、视频等 非结构化数据。比如网站的 图片、视频等文件就可以存放在 OSS 中(海量数据,自己维护起来麻烦,交给其他人去维护),每次从 OSS 中获取即可。
实现过程:1.获取自己的OSSbucket,记录bucket所在区域的Endpoint、AccessKeyId 以及 AccessKeySecret。
2.编写配置类,属性类实现文件上传和下载的接口即可
-
上传:
利用 OSS SDK 的putObject
(或分片上传接口)将本地文件(包括表情包)上传到指定的 Bucket 中。 -
下载:
可以通过 OSS SDK 的getObject
获取文件流进行下载,也可以生成预签名 URL 供客户端直接访问。
如何保证即时聊天室中历史消息的高效存储与检索?请谈谈 MyBatis 在这方面的应用。
1.数据库设计与分区策略
-
分库分表/分区:
聊天记录往往数据量庞大,通过按聊天室 ID、时间戳等维度进行分表或分区可以显著减少单表数据量,提升查询效率。 -
索引设计:
针对常用的查询条件(如聊天室 ID、时间戳)建立合适的索引,以便快速定位数据。
2.在.xml文件中加入分页查询、条件查询,避免一次性加载大量的数据
如何用HYperloglog实现的uv统计?又怎么进行测试实现的误差<0.8%和内存占用减少85%?
HyperLogLog(HLL)是一种用于估计大规模数据集中不同元素数量(如UV统计)的概率算法,它能够在极低的内存占用下提供相对准确的估计。HLL是基于String结构实现的,单个HLL的内存永远小于16kb,内存占用及其小,但由于它的测量结果是由概率性的,小于0.81%的误差,但完全可以忽略不计,同时HLL里面的元素是不可重复的,即使相同的用户连续访问,也只记录一次该用户