独家面经总结,超级精彩
本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!
Java面试准备
准确的说这里又分为两部分:
- Java刷题
- 算法刷题
Java刷题:此份文档详细记录了千道面试题与详解;
- 从PC时代到移动时代,但是产品还保留着一些历史兼容的痕迹。比如常用语是按照PC和移动进行一级分类,站点样式类型只能设置一个端。
旧版客户端界面示例
【架构层面】客户端架构多年未演进,功能迭代难以为继
-
客户端仅支持Windows系统且架构一直未演进,技术栈基于C++,和团队主要技术栈偏离,只能艰难维护,无力承接新功能需求。迫切需要演进为能跨平台、主流的、前端技术栈;
-
访客侧前端还未做到前后端分离架构,体验和开发效率大打折扣。
【架构层面】服务端架构的基础沟通层待演进
沟通协议层作为沟通产品非常重要的一环,还存在架构方面的不足:
-
多种网络连接协议下的稳定性需提高;
-
和不同端的消息发送性能需提高。
【架构层面】服务端架构的业务层待演进
业务层包含 20+ 服务模块,主要的业务逻辑采用共享库的方式维护,导致模块边界不清,数据链路混乱,功能重叠耦合严重,迫切需要演进为主流微服务架构。
-
模块内职责不够内聚,模块间调用关系耦合高;
-
同样的数据存在多份存储,数据一致性存在问题;
-
数据流的同步异步传输链路混乱。
【架构层面】整体服务端架构自运维成本高,可维护性很低
历史遗留系统中需要运维多种自运维中间件,导致团队不能聚焦业务功能开发。既降低了研发生产力,也给系统稳定性带来巨大挑战。
-
自运维了反向代理 Nginx 集群、Zookeeper 集群、Storm 集群、Kafka 集群、Solr 集群、Prometheus 集群;
-
离部门的主服务端集群面向云原生的服务治理架构还有不小差距。
【组织层面】产研团队整体对业务的理解不够且未拉齐
-
业务架构和研发架构长期脱钩,导致团队对大到沟通行业小到具体某个模块的领域知识沉淀缺乏,迫切需要在产研层面拉齐现有认知;
-
在团队达成共识的基础上将来才能形成随产品快速演进从而快速迭代领域认知的正向循环。
2.2 认清挑战
归因清楚问题后,重构的方向逐渐清晰起来。但执行落地阶段也会面临着业务演进压力,原架构基础薄弱,资源短缺等挑战。
架构陈旧,代码里有不少隐蔽的『坑』
从以往经历看,有时候一个很小的改动,看起来很有把握的一次上线也可能造成客户问题。一方面代码中缺乏设计的地方多,另一方面整体回归测试覆盖不全。组内自嘲这种状态为『每一行代码都刚刚好』,不能多也不能少。
重构和业务演进既要又要
这个挑战是大部分团队都会遇到的,业务不可能停止演进等待技术重构。如何能在不影响已有业务且保证部分高优业务需求正常迭代的情况下进行重构是必须要回答的问题。
不能仅仅是重构,客户可感知的体验要更好
涉及客户端架构升级,必然会带来一些新的用户体验,需要管理好存量用户的预期。本次重构范围大,产品质量不下降既是要求也是挑战。
产研团队较新,对原有业务功能缺乏足够了解
业务研发团队很依赖领域专家的业务知识指导,子领域间和模块间的职责和边界划分,数据归属等理解需要建立在业务理解的基础上。这些对现有团队是个不小的挑战。
因此,抓主要矛盾,分阶段小步快跑是本次重构的基调。
三、纾困:解决问题
=========
仅仅从技术层面做重构只能解决眼前的技术问题,随着业务快速迭代,纯技术重构的成果很容易消失殆尽。考虑到需要对业务和技术层面双管齐下做出改变,在现有复杂业务基础上仍能保持高效的产研交付效率,加上隔壁兄弟团队之前在线索管家产品已经收获了 DDD 改造的收益,因此本次技术重构决定结合 DDD 来做,从产品到技术来一次认知升级、架构升级。
3.1 定位:确定产品方向及核心痛点
产品定位及差异价值
产品定位:选择『不做什么』更加重要
-
聚焦在售前接待场景,帮助商家获取联系方式,不做售后服务场景;
-
聚焦在广告营销场景,帮助广告主接待推广流量并优化效果;
-
由于是 ToB SaaS 模式,所以暂时聚焦企业客户需求,不做平台型针对企业的上层需求。
产品使用角色:谁是我们的用户?
- 聚焦在B端客服角色。剥离其他角色相关功能,比如跟进线索的名片功能归到线索管家模块(销售角色),反哺功能归到 oCPC 反哺模块(SEM角色)。
差异化价值:客户为什么会选择我们?
-
全链路闭环:从推广开始到访客进站、对话、留资,直至标记会话反馈oCPC目标,全程无缝衔接;
-
与线索管家结合:智能识别会话和留言板中的线索信息,自动沉淀至线索管家,有效节省线索梳理工作;
-
智能营销:访客意图智能分析识别,千人千话引导访客开口留资;
-
多端共用:支持 Web、App、PC 端同时使用,随时随地实现沟通。
3.2 分析:识别核心领域和模块,拆解业务逻辑
3.2.1 事件风暴:剖析流程和对齐认知的好帮手
针对主要业务流程,产研团队通过事件风暴的方式梳理了事件流,定义了每个事件相关的角色、动作、规则条件和事件结果。最重要的是对齐了团队的业务认知,靠集体智慧剖析了整体业务细节。
3.2.2 边界是合作的基础:划分领域和模块,形成统一语言
根据产品定位及产品价值分析,结合梳理好的业务流程,需要划分子领域,相应配比合适的资源投入。
【核心域】
-
访客域和客服域属于核心域比较自然,同时作为底层的基础能力,协议连接域包括tcp、websocket、http、long polling协议,协议报文格式,连接状态维护等也应该是核心域。其次会话域也是核心域,互发消息才算进入真正沟通,会话内容里的意图表达和留资才是沟通的主要目的;
-
核心域的策略是围绕产品价值,重点投入资源。尽可能把非核心功能从核心域剥离,警惕容易引起团队失焦的投入。
【支撑域】
-
数据分析域是必要的功能但目前还不是重点,线索域对沟通来说是后链路必经环节,但应该更多利用爱番番线索管家的能力。广告域包含访客推广信息解析,会话效果反哺,照理是核心能力。但这里划为支持域是因为关键的能力在搜索团队已提供,沟通团队做好数据接入和数据供给工作;
-
支撑域的策略是尽可能以较少资源建设必要能力。当然,随着业务的发展支撑域也可能在未来变成核心域。
【通用域】
-
账号权限功能是大多数系统的通用能力。访客场景属于ToC场景,会遇到黑产流量攻击,包括访客进站和访客发送消息需要引入风控反作弊能力。爱番番沟通主要借助了爱番番策略团队和厂内安全部的能力;
-
通用域的策略是尽可能不亲自建设系统,借助外部能力快速完成能力建设。
3.3 架构:搭建整体技术架构
架构目标及设计要点
-
根据流量南北向把各种服务按照职责类别分为多个层次,用户界面、接入网关、业务前后台、沟通协议连接等5层由沟通团队建设维护,底下基础服务和存储层主要借助基础技术能力。分层建设能够定义服务不同等级、高效使用团队研发资源、承接不同流量类型(实际用户流量、后台用户流量、异步调用流量、定时任务流量等)、简化请求涉及的数据链路、根据层次不同建设非功能性需求(技术栈选择、熔断限流、弹性伸缩等)。
-
技术架构匹配业务架构。服务模块边界符合业务边界。核心服务内需设计领域模型,围绕领域层和应用层构建业务逻辑,搭建DDD四层分层架构,做到领域模型和技术细节分离,不稳定实现依赖稳定实现。
-
符合典型微服务架构。服务职责内聚,服务和数据一体。数据归服务私有,服务间不共享业务逻辑,服务间通过API或领域事件进行协作。
-
数据架构合理。尽可能采用数据最终一致性策略。每种数据非必要不多处存储,多处存储须有最终一致性方案保证。涉及nosql类存储如Redis、HBase、ES(Elastic Search)时,防止大key造成分片不均,业务数据按需进行分库分表存储。
3.4 突破:架构设计的关键技术
3.4.1 落地真正的微服务架构
随着子领域和模块的划分确定后,需要调整对应的模块职责及模块间协作关系进行改造,重点改造点包括:
合并老模块
改造前服务端有45+服务模块,服务职责划分不当,服务粒度不合适。具体表现为:
-
有些功能粒度太细,徒增维护成本,可以合并。
-
某些类似功能散落在多个服务,比如5个模块都有提供访客相关信息查询,可以合并。
-
有些服务随着老客户端的升级,功能改造后更合适合并到其他服务,原服务可以下线。
-
反向代理层职责划分不合理导致服务集群太多,绝大部分可以迁移至公司级的 BFE 进群,少数包含很多 lua 逻辑 Nginx 集群暂时保留,但可以合并。
经过合并下线改造后,服务数量减少了 15+。
拆分新模块
有些功能很重要,需要形成独立的模块重点建设。比如:
-
访客广告信息解析服务。广告信息对于客服刻画访客画像,理解访客非常重要。但之前的解析逻辑散落在多个模块且实现不统一,解析准确率不高,没有足够的补偿策略保证必要的解析成功率。
-
机器人智能回复服务。这也是产品定位的一个差异化价值。为了让客服更高效接待访客,引导访客多留资,这块的产品演进越来越多,复杂度也随着加大。
-
线索服务。这里的线索服务是爱番番沟通和线索管家产品的边界,主要是针对会话内容或者留言内容提取联系方式,然后通过接口或事件的方式流转到线索管家,同时也要形成咨询到线索的闭环数据。
模块间不共享业务逻辑
改造前的后端业务服务不是真正的微服务,虽然都是独立部署,各自暴露接口,但服务实现层耦合严重:
-
通过公共库( 即 java 的 jar 包 )共享业务逻辑。同一段业务代码被多个业务服务依赖,既降低了代码可维护性,也降低了服务的可测试性。
-
通过缓存( Redis )传递数据。一个 redis key 经常既有多个服务在写入,也有多个服务在读取。
-
通过 DB 共享数据,直接读取属于其他服务职责的数据表。
改造原则:不共享包括业务逻辑的公共库,让微服务垂直划分,相关业务数据(包括缓存数据)归服务私有,通过 API 接口提供能力,或者通过领域事件推动下游流程。
最终一致性前提下的高可用性
可用性的关键手段是数据复制。可以借助不同的数据同步方法,结合不同特点的存储类型完成多样化业务场景的高可用性。常用的数据复制/同步手段有:
-
发布/订阅模式:上游服务利用消息队列把相关数据以消息为载体发,下游服务订阅该消息并做相应的持久化。整个沟通服务端在大量使用这种方法,也是服务解耦的一大利器。
-
CDC 模式( Change Data Capture ):简单说就是通过监听 MySQL 的binlog 感知到上游服务的数据变化(包括新增、更新、删除),解析日志并做一些处理(比如关联表查询等)后发送到消息队列,下游按需订阅处理。
CDC 模式和发布订阅模式配合使用能满足很多场景,分离读写服务和选取异构存储介质。比如访客进站记录写入 MySQL 和访客历史记录查询ES,会话写入 Table 和会话分析服务查询 Doris 。即能有效满足各自场景的数据存取需求也能提高场景的可用性。
当然,这种可用性往往会牺牲一定时效性内的数据一致性,需要根据实际业务场景做出权衡。根据经验判断在马上得到答案和得到正确答案之间,大多数人更想要的其实是马上得到答案。
3.4.2 数据链路治理
改造前主要场景包括进站、离站、自动回复、会话内容校验、线索识别、结束会话等的数据流的必经节点是实时计算服务,其核心实现是 storm,但因为多种原因该集群很不稳定,会引发出上述提到的大量客户问题。深层分析现状主要有以下弊端:
-
storm 拓扑设计不合理,拓扑节点职责不清;
-
拓扑节点中存在大量的业务逻辑,普遍利用 redis 传递数据,redis 键设计混乱,可维护性很差;
-
storm 集群是几年前引入的,版本低,一直没升级。
经过分析业务需求,只升级 storm 集群版本不会解决实际问题,另外实时计算框架在现阶段不是必须项,因此得出了以下改造思路:
-
去除这个集中式的计算集群,按业务场景梳理各自数据流,避免互相干扰。让对应业务服务模块承接业务逻辑,如需提高业务响应可通过缓存集群加速;
-
服务模块间尽可能通过异步方式( kafka 消息队列 )传递数据,目前消息队列也能达到近实时效果,同时增强消息队列的灾备功能和订阅情况监控;
-
访客一段时间不说话需要自动回复等延时场景通过延时任务的方案解决;
-
redis key 重新梳理,优化大 key( 一个 key 承载的内容特别大,比如一个key 就包含全系统访客的部分信息,这样的 key 设计显然太大 ),尽量不跨服务模块直接操作 redis。
业务程序的灵魂是数据,技术架构时要多花时间考虑数据存储和读取的方方面面。比如用什么存储系统( 存储系统不可能读也最快,写也最快,需要权衡 )、什么时候用缓存,整个业务流程的数据传输链路应该怎么样,沟通系统涉及到很多写放大还是读放大的权衡等等。本次重构也涉及到了这些方面的梳理和改造,在此不一一介绍。
3.4.3 沟通协议优化
为什么要做协议优化?
针对 1.2 章节中提到的客户端上经常出现丢访客,消息不上屏等问题,简单的打补丁方式已经难以将问题彻底解决,因此必须从协议层进行彻底的改造优化。详细痛点如下:
-
现有协议缺乏鲁棒性,从协议层面埋藏着隐患。一个事件(如进站、建立沟通、离站)需要多个包来完成交互,如果一个访客操作频繁,访客状态也会频繁做变更,很容易出错。
-
富客户端模式,端上维护了过多的状态信息,过度依赖推送包的顺序,而且缺乏容错、自恢复恢复机制,容易出现访客不展示,消息不上屏等问题。
如何优化?
-
通知模块采用分布式锁控制并发,并为报文增加SeqId来确认早晚顺序,为客户端提供判断依据。
-
优化状态协议,简化掉动作通知类报文,采用以访客状态为主的报文,如下图所示,将动作报文简化掉,只保留状态报文,报文数量减少约 60%,降低客户端处理复杂度,减小出错概率。
- 客户端侧,由 socket 长连接改为为 http + socket 推拉结合的方式,当断网重连、或者报文丢失、错乱时,则客户端主动拉取最新状态,彻底接解决访客状态不对,消息不上屏等问题。
猜你想问:
1、上面提到分布式锁控制并发,会因锁竞争而增加请求处理时间吗?
答:锁粒度为单个访客粒度,粒度足够小,而且同一个访客在快速操作( 如频繁快速打开页面、发起沟通 )时,才会出现锁竞争的情况,对单访客来说,常规的操作并发不大。
2、既然协议优化收益这么搞,为什么不早点做协议优化呢?
答:之前受限于业务边界划分不清晰,访客状态变更散落在业务前台、业务后台、原 storm 集群多个地方,无法做统一管控。只有在完成了前期建构优化、数据链路治理完成之后,站在原有的工作成果至上,才能做协议优化。
3、客户端的推拉结合为什么不早点做呢?
答:如前文 2.1 中第 2 条所说,客户端技术栈基于 C++,只能艰难维护,无力承接新功能需求。因此想改动客户端的协议,可谓异常艰难,这也是下文 3.5 章节客户端架构升级的一大原因。
小结
-
访客、客服、会话管理模块的 DDD 改造。
-
由贫血模型改为富血模型,通过状态机控制状态变更。
-
客户端请求以 http 为主,同步得到返回值,降低出错概率。socket 主要用于给端上的通知。
-
协议包简化, 以访客状态维度进行交互,极大减少包的数量。
3.4.4 去除自运维中间件
如前面所述由于历史技术栈原因爱番番沟通团队内部运维了好几种中间件,先不说引入这些中间件的正确与否,现状是没有足够知识储备,既给系统带来了很多不稳定因素,也降低了团队的研发效率。因此本次重构在这个方面的改造原则是优先考虑下线架构中不必要的中间件,必要的中间件也不另行维护,迁移到部门基础技术团队运维。
集群改造下线
-
Zookeeper 集群:改造前主要用来做业务配置中心,迁移到 k8s 更友好的ConfigMap( 由基础技术团队运维 );
-
Nginx 集群:改造前有好几套反向代理集群,其中既有路由转发逻辑,也有业务逻辑。业务逻辑下沉至对应的 gateway 服务,由团队维护。路由转发逻辑迁移至 bfe 集群,由基础技术团队统一运维;
-
Storm 集群:逻辑改造,下线。细节上面已交代;
-
Solr 集群:下线,相应查询逻辑改造迁移至 ES 集群。
集群迁移
此部分集群虽然不能下线,但团队内不另行维护,转而迁移至部门集群。包括Kafka 和 Prometheus 集群。
3.5 扩展:客户端架构实践
3.5.1 客户端跨平台架构
随着原客户端维护代价越来越大,结合客户对 mac 端的诉求,因此选择了跨平台的 Electron 框架。
为什么选择 Electron ?
-
开源的核心扩展比较容易。
-
界面定制性强,原则上只要是 Web 能做的它都能做。
-
是目前最廉价的跨平台技术方案,HTML + JS 的技术储备,而且有海量的现存 UI 库。
-
相对其他跨平台方案( 如 QT GTK+ 等 ),更稳定,bug 少, 只要浏览器跑起来了,问题不会太多 。
-
方便拓展,可以直接嵌入现有 web 页面。
Electron 系统架构
爱番番前端团队的技术栈是 Vue,所以我们选择使用 Electron-Vue 来搭建项目。Electron 有两个进程,分别为主进程( main )和渲染进程( renderer )。主进程中包含了客户端自动更新、插件核心、系统 API 等。渲染进程是 vue + webpack 的架构,两个进程间通过 ipc 进行通信。
爱番番客户端主要是IM业务,所以通信方面使用 websocket 来进行消息通知,由于客服发送消息包含样式设置,所以传输内容包含富文本,这样就很容易引起一些xss 问题。我们使用 xss 白名单的方式来过滤 xss 攻击,并且所有内容都会通过策略过滤,拦截黄反等不良文本。
爱番番沟通考虑到今后能更灵活地接入更多业务垂类并且支持第三方自主开发个性化功能。同时需要兼顾平台代码的稳定性和易用性,我们采用了插件化架构的方式来实现客户端。
开发中遇到的问题
Electron 带来很大便利的同时,其本身也有很多硬伤。如常被人吐槽的内存占用高、和原生客户端性能差异、API 系统兼容性问题等。这些问题在开发过程中需要提前考虑到。下面是开发过程中必然会遇到的几个问题。
1、性能优化
性能优化是在开发完需求功能后经常需要考虑的。在 Electron 中,最好的分析工具就是 Chrome 开发者工具的 Performance ,通过火焰图,JS 执行过程的任何问题都可以直观的看到。
最后
分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。
《Java高级面试》
《Java高级架构知识》
《算法知识》
系统兼容性问题等。这些问题在开发过程中需要提前考虑到。下面是开发过程中必然会遇到的几个问题。
1、性能优化
性能优化是在开发完需求功能后经常需要考虑的。在 Electron 中,最好的分析工具就是 Chrome 开发者工具的 Performance ,通过火焰图,JS 执行过程的任何问题都可以直观的看到。
最后
分享一些资料给大家,我觉得这些都是很有用的东西,大家也可以跟着来学习,查漏补缺。
《Java高级面试》
[外链图片转存中…(img-fPZ8IvIK-1715346921245)]
《Java高级架构知识》
[外链图片转存中…(img-EXKC8qBj-1715346921245)]
《算法知识》
[外链图片转存中…(img-mGXczrVy-1715346921245)]