大家好,我是二哥呀。
上次有同学留言说想要看上海和杭州值得去的公司名单,意向的同学也非常多。那大家有需求就必须满足,我们就先来盘点一下上海的。
这个榜单的前缀是“氛围好薪资高”,但还需要各位验验货。
前十名依次是微软、英伟达、高通、eBay、网易、携程、东方财富、财妙信息科技、缤客、叠纸游戏。
我必须诚恳地说一句,榜单上的公司可能和有些同学预期的有出入,但我的出发点是帮大家打开思路。
不少同学学历很不错、项目也不差,但就吃亏在信息差上,不知道自己该去投哪些公司,似乎脑袋里只有拼多多、得物、唯品会、哈啰出行这些。
但其实还有很多公司可以投,包括外企(魔都的选择还是多啊)。
只要人家收你的简历,你就大胆去投,机会很多时候都来得让你觉得意想不到。
这次我们就以榜单上的第五名网易为切入点,来复盘一下《Java面试指南》里收录的《同学 4(网易云音乐)》面经,下次碰到原题的同学一定要过哦。
国庆后的第一波面试,一定要把握住,这时候面试官和 HR 的精气神都会 很足。
这十来天就别学习新的东西了,放松+复盘,等节后冲一波。
同学 4(网易云音乐)面经
问项目,RAG怎么解决LLM上下文窗口有限的问题?
首先是知识入库阶段。我们不会在用户提问时才把所有文档都丢给模型。相反,我们会预先将所有的知识(比如公司的规章制度、产品手册、技术文档等)进行处理。这个处理过程包括:
- 1)将长文档切分成更小的、逻辑完整的段落或“块”(Chunks);
- 2)调用 Embedding 模型,将每个文本块都转换成一个数学向量,这个向量可以被认为是该文本块在多维空间中的“语义坐标”;
- 3)将这些文本块和它们对应的向量存入向量数据库中,比如说 ElasticSearch。
其次是检索与生成阶段。当用户提出一个问题时,RAG 并不会直接把问题扔给 LLM。它会执行以下步骤:
- 1)使用与入库时相同的 Embedding 模型,将用户的问题也转换成一个向量;
- 2)用这个“问题向量”去向量数据库中进行“语义搜索”或“相似度查询”,找到与问题语义最相近的几个文本块;
- 3)最后,也是最关键的一步,系统会将用户的原始问题和搜索到的这几个最相关的文本块一起打包,形成一个 Prompt,然后发送给 LLM。
通过这种方式,LLM 在回答问题时,它的上下文窗口里不再是海量的、不相关的原始文档,而是系统为它精心挑选的、与当前问题高度相关的几段“参考资料”。
流式对话支持多轮吗?怎么实现的?
支持的。
派聪明当前采用了基于 Redis 的对话历史管理机制,每个用户都有一个唯一的会话 ID,所有的对话内容都按照时间顺序存在 Redis 中,并设置了 7 天的过期时间。
考虑到上下文会越来越长,我们打算在下一版实现一个滑动窗口,比如只保留最近 10 轮对话,或者根据 token 数量动态调整。
用过哪些缓存数据库,除redis以外
技术派实战项目中还用到了 Guava Cache 和 Caffeine 作为本地缓存,Guava Cache 适合小规模缓存,Caffeine 性能更好,支持更多高级特性。
Caffeine 通常用来作为二级缓存来使用,主要用于存储一些不经常变动的数据,以减轻 Redis 的压力。
redis缓存和mysql一致性怎么解决
在技术派实战项目中,对于文章标签这种允许短暂不一致的数据,我会采用 Cache Aside + TTL 过期机制来保证缓存和数据库的一致性。
具体做法是读取时先查 Redis,未命中再查 MySQL,同时为缓存设置一个合理的过期时间;更新时先更新 MySQL,再删除 Redis。
这种方式简单有效,适用于读多写少的场景。TTL 过期时间也能够保证即使更新操作失败,未能及时删除缓存,过期时间也能确保数据最终一致。
kakfa怎么保证消息重试
生产者在发送消息到 Kafka Broker 的过程中,可能会因为网络抖动、Broker 临时故障等原因失败。为了应对这种情况,Kafka 生产者内置了简单的重试机制。在派聪明项目中,我们为 Kafka 的 retries 参数设置了 3。
这意味着,当生产者发送消息失败时,它会自动尝试重新发送,最多 3 次。同时, enable.idempotence: true 的配置也能确保即使在重试过程中,消息也只会被写入一次,避免了因重试导致的数据重复问题。
消费者的重试比生产者稍微复杂一些,因为它处理的是业务逻辑的失败。比如,在派聪明中,消费者拿到文件处理任务后,可能因为数据库连接超时、embedding 模型暂时不可用等原因处理失败。如果此时直接确认消息,这条任务就丢失了;如果不确认,消息会一直被重复消费。
派聪明通过 DefaultErrorHandler 实现了一套优雅的消费者重试与死信队列机制。FixedBackOff 会阻塞当前消费线程 ,等待 1 秒后进行第一次重试。如果再次失败,它会再等 1 秒,进行第二次重试,总共最多重试 4 次。如果 4 次后仍然失败, DeadLetterPublishingRecoverer 会接管这个消息,将它发送到死信队列中 (在项目中是 file-processing-dlt )。
介绍一下CAP理论
CAP 是分布式系统设计中的一个非常重要的定理,它指出分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三个特性,最多只能同时满足其中两个。
一致性指的是所有节点在同一时间看到的数据都是一致的。比如我在北京的服务器上更新了用户的余额,那么上海的服务器上也应该立即看到这个更新后的余额。强一致性要求数据更新完成后,任何后续的访问都能返回更新后的值。
可用性是指系统在正常响应时间内返回合理的响应,即使部分节点出现故障,系统仍然可以继续提供服务。比如电商网站不能因为某个数据库节点挂了就完全无法访问,用户至少应该能看到商品信息,可能只是下单功能暂时不可用。
分区容错性是指当网络分区故障发生时,系统仍然能够继续运行。网络分区是分布式系统中不可避免的问题,比如机房之间的网络中断,这时候不同机房的节点就无法正常通信了。
谈一下最终一致性和强一致性个人的理解
强一致性要求任何时候所有节点看到的数据都是完全相同的。也就是数据一旦写入成功,系统中所有的读操作都必须能立即读到最新的值。
就像银行转账一样,钱从 A 账户扣除的同时必须立即加到 B 账户上,不能存在中间状态。
也就是说,涉及到金额计算的,必须保证强一致性。比如用户充值,扣款成功就必须立即反映到用户余额上,而且所有的服务节点都要能看到最新的余额。
强一致性的代价是比较高的。为了保证所有节点的数据同步,系统需要在写操作时等待所有相关节点都确认更新完成,这会带来较高的延迟。而且在网络分区或节点故障时,系统可能会选择暂停服务来保证一致性,可用性会受到影响。
最终一致性相对比较宽松,它允许系统在短时间内存在数据不一致的情况,但保证经过一段时间后,所有节点的数据最终会达到一致的状态。
redis集群是属于强一致性和最终一致性
最终一致性的典型应用就是分布式缓存系统。比如 Redis 集群中,主节点写入成功后立即返回,然后异步复制到从节点。这种模式下,读操作可能会读到稍微过期的数据,但系统的整体性能和可用性都很好。
介绍一下mysql的B+树的优势和B树的区别
B+ 树相比 B 树有 3 个显著优势:
第一,B 树的每个节点既存储键值,又存储数据和指针,导致单节点存储的键值数量较少。
一个 16KB 的 InnoDB 页,如果数据较大,B 树的非叶子节点只能容纳几十个键值,而 B+ 树的非叶子节点可以容纳上千个键值。
第二,B 树的范围查询需要通过中序遍历逐层回溯;而 B+ 树的叶子节点通过双向链表顺序连接,范围查询只需定位起始点后顺序遍历链表即可,没有回溯开销。
第三,B 树的数据可能存储在任意节点,假如目标数据恰好位于根节点或上层节点,查询仅需 1-2 次 I/O;但如果数据位于底层节点,则需多次 I/O,导致查询时间波动较大。
而 B+ 树的所有数据都存储在叶子节点,查询路径的长度是固定的,时间稳定为 O(logN),对 MySQL 在高并发场景下的稳定性至关重要。
G1相比于CMS垃圾回收器有什么区别
特性 | CMS | G1 |
---|---|---|
设计目标 | 低停顿时间 | 可预测的停顿时间 |
并发性 | 是 | 是 |
内存碎片 | 是,容易产生碎片 | 否,通过区域划分和压缩减少碎片 |
收集代数 | 年轻代和老年代 | 整个堆,但区分年轻代和老年代 |
并发阶段 | 并发标记、并发清理 | 并发标记、并发清理、并发回收 |
停顿时间预测 | 较难预测 | 可配置停顿时间目标 |
容易出现的问题 | 内存碎片、Concurrent Mode Failure | 较少出现长时间停顿 |
CMS 适用于对延迟敏感的应用场景,主要目标是减少停顿时间,但容易产生内存碎片。
G1 则提供了更好的停顿时间预测和内存压缩能力,适用于大内存和多核处理器环境。
G1分region垃圾回收,有哪些好处
G1 把 Java 堆划分为多个大小相等的独立区域 Region,每个区域都可以扮演新生代或老年代的角色。
同时,G1 还有一个专门为大对象设计的 Region,叫 Humongous 区。
大对象的判定规则是,如果一个大对象超过了一个 Region 大小的 50%,比如每个 Region 是 2M,只要一个对象超过了 1M,就会被放入 Humongous 中。
这种区域化管理使得 G1 可以更灵活地进行垃圾收集,只回收部分区域而不是整个新生代或老年代。
jvm调优有那些参数
我自己用过的 JVM 调优参数主要有 -Xms
设置初始堆大小,-Xmx
设置最大堆大小,-XX:+UseG1GC
使用 G1 垃圾收集器,-XX:MaxGCPauseMillis=n
设置最大垃圾回收停顿时间,-XX:+PrintGCDetails
输出 GC 详细日志等。
如果频繁发生full gc,可以怎么调整,调整什么参数最好
频繁的 Full GC 通常意味着老年代中的对象频繁地被垃圾回收,可能是因为老年代空间设置的过小,或者是因为程序中存在大量的长生命周期对象。
我一般会使用 JDK 的自带工具,包括 jmap、jstat 等。
# 查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000
# 查看堆内存中的存活对象,并按空间排序
jmap -histo pid | head -n20
# dump堆内存文件
jmap -dump:format=b,file=heap pid
或者使用一些可视化的工具,比如 VisualVM、JConsole 等,查看堆内存的使用情况。
假如是因为大对象直接分配到老年代导致的 Full GC 频繁,可以通过 -XX:PretenureSizeThreshold
参数设置大对象直接进入老年代的阈值。
或者将大对象拆分成小对象,减少大对象的创建。比如说分页。
假如是因为内存泄漏导致的频繁 Full GC,可以通过分析堆内存 dump 文件找到内存泄漏的对象,再找到内存泄漏的代码位置。
假如是因为长生命周期的对象进入到了老年代,要及时释放资源,比如说 ThreadLocal、数据库连接、IO 资源等。
更多问题
一些重复性的问题大家可以直接通过面渣逆袭在线版查看。
ending
一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 9800 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个 简历精修 + 编程项目实战+ Java 面试指南的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。
两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的学习资源,相信能帮助你走的更快、更稳、更远。
欢迎点击左下角阅读原文了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。
最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。
回复