开源中国 Java开发 二面 面经

1. 深入聊聊你的项目,从架构设计到技术选型的考虑 (30min)

项目背景和规模:

我负责的抽奖系统是为电商平台设计的营销工具,主要用于节日促销、新品推广等场景。系统上线后日均抽奖10万次,高峰期每秒1500次请求。注册用户50万,活跃用户5万左右。

架构演进过程:

最初是单体架构,所有功能在一个项目中。随着业务增长,出现了性能瓶颈和维护困难。我们进行了微服务改造,拆分为用户服务、活动服务、抽奖服务、奖品服务四个核心服务。

用户服务负责用户认证、权限管理、用户信息维护。活动服务负责活动的创建、配置、状态管理。抽奖服务是核心,负责抽奖逻辑、概率计算、库存扣减。奖品服务负责奖品管理、发放、物流对接。

技术选型考虑:

选择Spring Cloud作为微服务框架,因为它生态完善,社区活跃,团队熟悉。使用Nacos做注册中心和配置中心,相比Eureka更轻量,支持配置动态刷新。

数据库选择MySQL,因为抽奖数据需要强一致性,而且团队对MySQL运维经验丰富。使用主从复制实现读写分离,提高查询性能。

缓存选择Redis,用于存储活动配置、奖品库存、用户抽奖次数。Redis的高性能和丰富的数据结构非常适合这个场景。

消息队列选择RabbitMQ,用于异步处理中奖通知、奖品发放。选择RabbitMQ而不是Kafka,是因为我们的消息量不是特别大,更看重消息的可靠性和灵活的路由规则。

核心技术难点:

第一个难点是高并发下的库存扣减。我们使用Redis的Lua脚本保证原子性,避免超卖。使用分布式锁防止重复抽奖。使用限流算法控制请求速率,保护系统。

第二个难点是概率算法的设计。要支持灵活的概率配置,保证概率的准确性,还要考虑性能。我们使用区间映射法,预先计算好概率区间,抽奖时只需要生成随机数判断落在哪个区间。

第三个难点是数据一致性。Redis和数据库的数据要保持一致,异步任务失败要有补偿机制。我们使用最终一致性方案,配合定时对账和人工介入。

性能优化措施:

使用多级缓存,本地缓存加Redis缓存,减少网络IO。活动配置使用本地缓存,设置1分钟过期,大部分请求不需要访问Redis。

使用异步处理,抽奖成功后立即返回,中奖记录异步保存。使用消息队列削峰填谷,平滑处理高并发请求。

数据库优化,使用索引加速查询,使用分表减少单表数据量。中奖记录按月分表,历史数据归档到冷存储。

使用连接池复用连接,减少连接建立开销。使用线程池处理任务,避免频繁创建销毁线程。

监控和运维:

使用Prometheus采集指标,Grafana展示监控面板。监控QPS、响应时间、错误率、库存数量等关键指标。

使用Skywalking做链路追踪,快速定位性能瓶颈。使用ELK收集日志,分析用户行为和系统问题。

设置告警规则,异常时通过钉钉、短信通知。制定应急预案,系统故障时快速响应。

项目成果:

优化后系统可以稳定支持每秒1500次抽奖请求,响应时间在100毫秒以内。系统可用性达到99.9%以上,没有出现过超卖问题。用户反馈体验流畅,活动转化率提升了20%。

2. Redis的数据结构底层实现,跳表的原理

Redis数据结构:

Redis有五种基本数据结构,每种都有不同的底层实现。String底层是SDS简单动态字符串。List底层是quicklist,结合了ziplist和linkedlist。Hash底层是ziplist或hashtable。Set底层是intset或hashtable。Sorted Set底层是ziplist或skiplist加hashtable。

SDS简单动态字符串:

SDS不是C语言的字符串,而是自己实现的数据结构。它包含长度、剩余空间、字符数组三个字段。

SDS的优点是获取长度是O(1),不需要遍历。预分配空间,减少内存重分配次数。二进制安全,可以存储任意数据。

跳表原理:

跳表是Sorted Set的底层实现之一,用于有序集合。它是一种多层链表结构,通过索引层加速查找。

最底层是完整的有序链表,包含所有元素。上层是索引层,每层的元素数量是下层的一半左右。查找时从最高层开始,找到区间后下降到下一层,直到找到目标元素。

跳表的查找、插入、删除时间复杂度都是O(log n),和平衡树相当。但跳表实现更简单,而且支持范围查询。

跳表的插入:

插入元素时,先在最底层找到插入位置,插入元素。然后随机决定是否在上层也插入索引,概率是50%。这样保证了跳表的平衡性。

随机层数的期望是log n,所以跳表的高度是log n。空间复杂度是O(n),因为索引层的元素总数是n + n/2 + n/4 + ... ≈ 2n。

为什么用跳表而不是红黑树:

第一是实现简单,跳表的代码比红黑树简单很多,容易理解和维护。

第二是支持范围查询,跳表可以方便地遍历一个范围内的元素。红黑树需要中序遍历,相对复杂。

第三是内存局部性好,跳表的节点在内存中相对连续,缓存友好。红黑树的节点分散,缓存命中率低。

第四是并发性能好,跳表可以用CAS实现无锁并发,红黑树需要复杂的锁机制。

ziplist压缩列表:

ziplist是一种紧凑的数据结构,用于节省内存。它是一段连续的内存,每个元素包含前一个元素的长度、编码类型、数据。

ziplist适合元素数量少、元素大小小的场景。当元素数量或大小超过阈值,会转换为其他数据结构。

ziplist的缺点是插入删除需要移动元素,时间复杂度O(n)。而且可能触发连锁更新,影响性能。

3. JVM的垃圾回收器,G1和ZGC的区别

垃圾回收器演进:

Java的垃圾回收器经历了多代演进。Serial是单线程收集器,适合单核CPU。Parallel是多线程收集器,注重吞吐量。CMS是并发收集器,追求低停顿。G1是分区收集器,可预测停顿。ZGC是低延迟收集器,停顿时间在10毫秒以内。

G1收集器:

G1把堆划分为多个大小相等的Region,每个Region可以是Eden、Survivor、Old、Humongous区。大对象直接分配到Humongous区。

G1的回收过程分为四个阶段。初始标记标记GC Roots直接关联的对象,需要短暂停顿。并发标记从GC Roots遍历对象图,和应用程序并发执行。最终标记处理并发标记期间的变化,需要短暂停顿。筛选回收根据停顿时间目标,选择回收价值最大的Region进行回收。

G1的优点是可以设置停顿时间目标,比如200毫秒。G1会尽量在这个时间内完成回收。而且没有内存碎片问题,使用复制算法整理内存。

G1适合大堆内存场景,比如几十GB的堆。对于小堆内存,G1的开销反而更大。

ZGC收集器:

ZGC是JDK 11引入的低延迟收集器,目标是停顿时间不超过10毫秒,支持TB级别的堆。

ZGC使用染色指针技术,在指针中存储对象的状态信息。通过读屏障,在访问对象时检查状态,必要时进行处理。

ZGC的回收过程几乎全部并发执行,只有初始标记和最终标记需要短暂停顿。而且停顿时间不随堆大小增加,无论堆多大,停顿时间都在10毫秒以内。

ZGC使用内存多重映射技术,同一块物理内存映射到多个虚拟地址。通过切换映射,实现并发的内存整理。

G1 vs ZGC:

停顿时间方面,G1的停顿时间在几十到几百毫秒,ZGC在10毫秒以内。ZGC的停顿时间更短,更稳定。

吞吐量方面,G1的吞吐量更高,ZGC为了低延迟牺牲了一些吞吐量。

内存开销方面,ZGC的内存开销更大,需要额外的内存存储元数据。

适用场景方面,G1适合对停顿时间要求不是特别高的场景。ZGC适合对延迟敏感的场景,比如交易系统、实时系统。

选择建议:

JDK 8推荐使用G1,设置合理的停顿时间目标。JDK 11及以上,如果对延迟要求高,可以使用ZGC。

要根据实际情况测试,不同的应用特点不同,适合的收集器也不同。要监控GC日志,分析GC频率和停顿时间,调整参数。

4. 分布式事务的实现方案,Seata的AT模式原理

分布式事务问题:

在微服务架构中,一个业务操作可能涉及多个服务,每个服务有自己的数据库。如何保证这些操作要么全部成功,要么全部失败,这就是分布式事务问题。

常见解决方案:

两阶段提交2PC是传统方案,但性能差,而且存在单点故障。TCC方案性能好,但开发成本高,需要实现Try、Confirm、Cancel三个方

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

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

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

全部评论

相关推荐

昨天 17:40
门头沟学院 Java
不愧是字节,面麻了给我...1、项目介绍2、Agent项目是实习项目还是个人项目?有没有上线?3、拷打实习4、大模型微调,你的训练数据集是如何构建的?数据量有多大?5、在构建数据集的过程中,遇到了哪些挑战?花了多长时间?6、你之前的实习经历偏后端工程,你未来的职业规划更倾向于纯后端开.发,还是希望从事与AI/大模型结合的工作?7、详细讲一下Golang中Channel的概念和作用,它是否是并发安.全的?8、Channel和传统的锁(Mutex)在实现并发控制时有什么区别?各自的适用场景是什么?9、讲一下GMP模型10、当P的本地队列为空或者不为空时,它会怎么去调度G(协程)?11、Redis支持哪些数据结构12、为什么Redis的速度这么快13、如何实现一个类似某宝搜索框的实时商品名称模糊搜索功能?14、实时输入联想与输入完成后点击搜索在技术实现上有什么本质区别?15、实时搜索通常使用什么网络协议(如WebSocket)?你了解或有使用过吗?讲一下16、请详细说明扫码登录的完整流程和背后发生的原理17、在微服务架构中,服务发现和负载均衡是如何实现的?18、服务注册中心(如Nacos, Consul)是如何工作的?服务实例如何注册和保活(如通过心跳机制)?19、讲一下Agent中的“长短期记忆”20、什么样的信息应该放在长期记忆,什么样的信息放在短期记忆?21、当对话轮数很多,上下文窗口不足时,有哪些处理策略?(如截断、压缩)22、如果要进行记忆压缩,通常有哪些方法?23、了解过Agent的设计范式吗?有哪些?24、你设计的Agent是怎么实现ReAct模式的?详细讲讲25、手撕:实现一个并发任务处理器:给定一个包含100个任务ID的列表,要求控制最.大并发数为3,模拟并发调用某个外部接口
查看24道真题和解析
点赞 评论 收藏
分享
子夏2024:小米果然是花小钱办大事,十块 100块的红包搞这么大排场,给人一种很亲人的感觉,还让员工感动上了,我度发的基本都是365,588这种,也没见老板们搞什么煽情让给员工感恩,重在实在~,企业文化显而易见
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

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