JavaGuide 官方知识星球

这部分内容结合 JavaGuide 的分布式系统常见面试题总结、高可用系统设计常见面试题总结和系统设计与场景题总结,重点补齐微服务架构的高频追问。

分布式系统常见面试题总结

高可用系统设计常见面试题总结

系统设计与场景题总结

微服务不是把应用拆成多个进程就结束了。一次拆分会同时带来网络通信、数据一致性、部署、监控和组织协作问题。回答这类题时,既要讲收益,也要主动说明复杂度和适用边界。

微服务基础与选型

微服务基础与选型

⭐️什么是微服务架构?

⭐️什么是微服务架构?

微服务架构把一个应用拆成一组围绕业务能力构建、可以独立开发和部署的服务。每个服务拥有明确边界,通过 API 或消息协作,并对自己负责的数据和业务规则保持控制。

“服务数量多”不是微服务的定义。真正重要的是:

服务是否围绕业务边界拆分。

能否独立开发、测试、部署和扩缩容。

数据和接口边界是否清晰。

团队是否能独立负责服务的完整生命周期。

如果多个所谓的微服务必须一起改、一起发版,还共享同一批数据库表,它们很可能只是一个分布式单体。

单体、分布式系统、集群和微服务有什么区别?

单体、分布式系统、集群和微服务有什么区别?

概念关注点单体架构应用的功能通常作为一个整体构建和部署分布式系统组件分布在多个网络节点上,共同完成任务集群多个实例共同提供同一种能力,常用于扩容和容灾微服务架构按业务能力拆分为可独立演进的服务,是分布式架构的一种形式

概念关注点

概念

关注点

单体架构应用的功能通常作为一个整体构建和部署

单体架构

应用的功能通常作为一个整体构建和部署

分布式系统组件分布在多个网络节点上,共同完成任务

分布式系统

组件分布在多个网络节点上,共同完成任务

集群多个实例共同提供同一种能力,常用于扩容和容灾

集群

多个实例共同提供同一种能力,常用于扩容和容灾

微服务架构按业务能力拆分为可独立演进的服务,是分布式架构的一种形式

微服务架构

按业务能力拆分为可独立演进的服务,是分布式架构的一种形式

一个单体应用也可以部署多个实例组成集群;一个分布式系统也不一定采用微服务;同一个微服务通常还会部署多个实例形成集群。

⭐️微服务架构有哪些优点和缺点?

⭐️微服务架构有哪些优点和缺点?

主要优点包括:

独立交付:服务可以按自己的节奏开发、测试和发布。

独立扩容:只扩容压力大的服务,不必复制整个应用。

故障隔离:设计得当时,单个非核心服务故障不会拖垮整个系统。

技术与组织自治:团队可以围绕业务能力负责端到端交付。

降低单个代码库的认知负担:服务边界稳定时,一个团队不必理解整个系统才能修改局部功能。

对应的成本也很明显:

本地调用变成网络调用,会遇到超时、重试、部分失败和版本兼容问题。

跨服务事务和查询更复杂。

部署、配置、服务发现、流量治理和可观测性要求更高。

集成测试与故障排查链路变长。

边界拆错后,跨服务调用和协作成本可能比单体更高。

微服务是在用系统和运维复杂度换取独立演进能力,不是默认更先进的答案。

哪些场景不适合直接上微服务?

哪些场景不适合直接上微服务?

下面几种情况通常应该谨慎:

产品仍在快速试错,业务边界频繁变化。

团队规模小,一个服务长期只有一两个人维护。

流量和交付频率不高,单体尚未遇到明确瓶颈。

自动化测试、持续交付、监控告警和容器平台等基础能力不足。

业务有大量强事务操作,拆分后收益不够抵消一致性成本。

这时可以先采用模块化单体:在一个部署单元内建立清晰模块边界,限制跨模块依赖。等团队、业务和基础设施达到一定阶段,再逐步抽取服务。

服务拆分

服务拆分

⭐️微服务应该如何拆分?

⭐️微服务应该如何拆分?

优先按照业务能力和领域边界拆分,而不是按 Controller、Service、DAO 技术分层,也不要机械地“一张表一个服务”。

一个实用的拆分过程是:

梳理核心业务流程、领域概念和团队职责。

找出内聚的业务能力和明确的上下游关系。

为每个候选服务定义职责、数据所有权和对外契约。

检查跨服务调用频率、强一致性要求和共同变更频率。

从边界清晰、耦合较低的模块开始验证,再迭代调整。

服务粒度没有固定行数或接口数。两个模块如果总是一起修改、一起发布,并且需要大量同步调用,可能不该过早拆开。

什么是限界上下文?它和服务拆分有什么关系?

什么是限界上下文?它和服务拆分有什么关系?

限界上下文来自领域驱动设计,表示一套领域模型和术语有效的明确边界。同一个词在不同上下文里可以有不同含义。例如,“用户”在会员系统里关注等级和积分,在物流系统里关注收货人与地址。

限界上下文可以作为服务边界的重要候选,但不是机械的一一对应关系。一个上下文可能根据规模由多个服务实现,小型系统也可能让多个上下文先存在于一个模块化单体中。关键是不要让一个服务直接修改另一个上下文的内部模型和数据。

⭐️什么是分布式单体?有哪些典型表现?

⭐️什么是分布式单体?有哪些典型表现?

分布式单体表面上拆成多个服务,实际上仍像一个紧耦合的单体运行。常见表现有:

多个服务共享数据库表,任一服务都能直接修改别人的数据。

一次需求要同时修改和发布多个服务。

核心请求串联大量同步调用,任何节点失败都会让整条链路失败。

服务之间共享内部类库和数据库模型,版本升级必须步调一致。

没有契约兼容策略,只能安排“停机联调”或统一发版。

每个服务很小,但跨服务沟通和排障成本很高。

改进方向不是继续拆得更细,而是重新梳理业务边界、数据所有权和接口契约。必要时合并总是共同变化的服务,也是一种合理优化。

服务通信与基础设施

服务通信与基础设施

⭐️微服务之间如何选择 REST、RPC 和消息队列?

⭐️微服务之间如何选择 REST、RPC 和消息队列?

方式特点适用场景REST/HTTP通用、可读、生态成熟对外 API、跨技术栈、普通查询和命令RPC接口约束强、序列化高效、调用体验接近本地内部低延迟调用、接口稳定的服务间通信消息队列异步、削峰、解耦,可广播事件通知、耗时任务、最终一致性和削峰

方式特点适用场景

方式

特点

适用场景

REST/HTTP通用、可读、生态成熟对外 API、跨技术栈、普通查询和命令

REST/HTTP

通用、可读、生态成熟

对外 API、跨技术栈、普通查询和命令

RPC接口约束强、序列化高效、调用体验接近本地内部低延迟调用、接口稳定的服务间通信

RPC

接口约束强、序列化高效、调用体验接近本地

内部低延迟调用、接口稳定的服务间通信

消息队列异步、削峰、解耦,可广播事件通知、耗时任务、最终一致性和削峰

消息队列

异步、削峰、解耦,可广播

事件通知、耗时任务、最终一致性和削峰

RPC 调用流程与核心能力

需要立即返回结果的查询通常适合同步调用;下游可以稍后处理、一个事件有多个订阅者或需要削峰时,更适合消息。实际系统经常混合使用。

无论选哪种方式,都要补充超时、幂等、重试、版本兼容和可观测性。RPC 框架让远程调用写起来像本地调用,但不会消除网络的不可靠性。

同步调用链过长会有什么问题?

同步调用链过长会有什么问题?

假设请求依次调用 A → B → C → D,整条链路的延迟会叠加,任意依赖超时都可能让请求失败。上游重试还可能放大下游流量,引发级联故障。

同步调用链中的雪崩效应传播

常见优化包括:

合并高度耦合、每次都要连续调用的服务。

能并行的查询并行执行,但设置总超时预算。

非实时步骤改成异步事件。

缓存稳定数据,或者为查询建立读模型。

为每一跳设置超时、限流、熔断和并发隔离。

避免在循环中发起大量逐条 RPC,改为批量接口。

服务拆分后,接口数量增加很正常;每个页面请求触发几十次串行 RPC,则通常是在提醒边界或聚合方式需要调整。

⭐️什么是服务注册与发现?

⭐️什么是服务注册与发现?

服务实例会扩缩容、重启,IP 和端口并不固定。服务注册与发现用于维护服务名到可用实例的映射,让调用方不用写死地址。

常见流程是:

服务实例启动后向注册中心注册地址,并持续上报健康状态或租约。

调用方或负载均衡组件根据服务名获取可用实例列表。

负载均衡算法选择一个实例发起请求。

故障实例被摘除,新实例加入列表。

Kubernetes 中通常由 Service 为一组 Pod 提供稳定访问入口和服务发现,不一定需要应用再接入独立注册中心。具体选择取决于运行平台、治理能力和历史架构。

Service

客户端发现和服务端发现有什么区别?

客户端发现和服务端发现有什么区别?

客户端发现:调用方获取实例列表并自己做负载均衡。优点是少一次转发、策略灵活;缺点是每种语言都要接入发现和负载均衡能力。

服务端发现:客户端请求负载均衡器、网关或代理,由中间层选择实例。优点是客户端简单、治理集中;缺点是多一跳,并且中间层本身需要高可用。

Service Mesh 常通过 Sidecar 或节点代理下沉服务发现、负载均衡、mTLS 和可观测性,让业务代码少感知治理逻辑,但也会增加平台复杂度和资源开销。

⭐️API 网关应该负责什么?

⭐️API 网关应该负责什么?

API 网关位于客户端和后端服务之间,常见职责有:

路由与协议适配。

统一认证入口与基础授权校验。

限流、黑白名单和请求大小限制。

灰度发布和流量分配。

日志、指标、追踪上下文和统一错误处理。

针对特定客户端做轻量聚合。

API 网关的职责与部署位置

网关不应该承载大量领域业务逻辑,也不能成为所有服务共享的“超级 Service”。业务规则放进网关后,任何需求都要改网关,最终会形成新的单体和性能瓶颈。

配置中心解决什么问题?配置变更要注意什么?

配置中心解决什么问题?配置变更要注意什么?

配置中心统一管理不同服务、环境和集群的配置,并提供版本、权限、审计和动态更新能力。它解决的是大量实例配置分散、变更难追踪和环境容易不一致的问题。

动态配置并非越快生效越好。配置项需要说明:

是否支持运行时刷新,还是必须重启实例。

是否有类型、范围和依赖校验。

如何灰度、回滚和审计。

配置中心不可用时,实例能否使用本地快照启动或继续运行。

密钥是否交给专门的密钥管理系统,而不是作为普通明文配置保存。

核心配置变更应像代码发布一样可验证、可回滚,不能把配置中心当成线上临时改参数的记事本。

数据拆分与一致性

数据拆分与一致性

⭐️为什么强调每个微服务拥有自己的数据?

⭐️为什么强调每个微服务拥有自己的数据?

数据库按服务拆分的核心是数据所有权。其他服务只能通过该服务公开的 API 或事件使用数据,不能绕过它直接修改内部表。

这样做的好处是服务可以独立调整模型、索引和存储技术,也避免多个服务通过数据库结构形成隐式耦合。但代价是跨服务 Join、事务和统计查询会变复杂。

“每服务一个数据库”是逻辑隔离原则,不一定要求每个服务都独占物理数据库实例。早期可以共享实例但使用独立 Schema 和账号权限,随着规模增长再做物理隔离。

为什么不建议多个服务直接共享数据库表?

为什么不建议多个服务直接共享数据库表?

共享表看起来省掉了接口调用,却会带来长期耦合:

一个服务改表结构可能破坏其他服务。

数据校验和业务规则被多套代码重复实现。

无法确认谁对数据负责,排查修改来源困难。

服务无法独立扩容、迁移存储或发布。

任意服务都可能绕过拥有者的权限与审计逻辑。

迁移阶段可以短期共享,但应明确表的唯一拥有者,并通过变更数据捕获、事件或 API 逐步切断其他服务的直接访问。

⭐️跨服务查询如何实现?

⭐️跨服务查询如何实现?

常见方案有:

API 组合:聚合服务并行调用多个服务,再组合结果。实现直接,适合数据量小、实时性要求高的查询。

CQRS/物化视图:订阅各服务事件,建立面向查询的冗余读模型。查询快、可减少运行时调用,但要接受延迟并处理事件重放与修复。

离线数仓或湖仓:面向报表和分析,把业务数据同步到分析系统,不在生产库做大规模跨服务统计。

搜索索引:把需要检索的字段同步到 Elasticsearch 等搜索系统,适合复杂搜索而非权威数据判断。

选择时要说明实时性、数据量、查询频率和一致性要求。不要为了还原单体里的一个 Join,就让多个服务共享数据库。

⭐️跨服务业务如何保证数据一致性?

⭐️跨服务业务如何保证数据一致性?

微服务通常无法用一个本地事务包住所有服务。常见做法是把业务拆成多个本地事务,通过事件推进,并使用 Saga、TCC、事务消息或 Outbox 等方案实现最终一致性。

订单服务与库存服务形成的分布式事务

以创建订单并扣减库存为例,可以这样设计:

订单服务在本地事务中创建“处理中”订单,并记录待发布事件。

事件可靠投递后,库存服务执行幂等扣减。

库存服务发布成功或失败事件。

订单服务把状态更新为“已确认”或执行取消补偿。

定时任务扫描长时间未完成的状态,重试或进入人工处理。

关键不是喊出“最终一致性”,而是讲清楚中间状态、幂等、重试、补偿、超时和对账。

Saga 模式是什么?

Saga 模式是什么?

Saga 把一个跨服务长事务拆成一系列本地事务。每一步成功后推进下一步;某一步失败时,按业务定义执行前面步骤的补偿操作。

Saga 长事务与补偿事务

Saga 有两种常见协调方式:

编排式:各服务监听事件并发布下一事件。耦合较低,但流程长后不容易看清全局状态。

协调式:由协调器显式发送命令并记录流程状态。流程可见性更强,但协调器要保持高可用,且不能塞入所有领域逻辑。

补偿不是数据库回滚。例如,已经发送的优惠券不能“撤回时间”,只能作废或发起反向操作。中间状态也可能被其他请求看到,因此状态机和业务约束必须一起设计。

⭐️Transactional Outbox 如何避免“数据库写成功但消息发送失败”?

⭐️Transactional Outbox 如何避免“数据库写成功但消息发送失败”?

Transactional Outbox 把业务数据和待发送事件写入同一个本地数据库事务。事务提交后,由后台任务或 CDC 读取 Outbox 表并发送消息,发送成功后再标记或清理记录。

它保证的是:只要业务事务提交,待发送事件就不会凭空丢失。但它不保证消息只发送一次。发布端可能在“消息发送成功、状态还没更新”时崩溃,导致重复发送,所以消费者必须幂等。

Outbox 还要处理:

失败重试与退避。

积压、死信和长时间未发送告警。

事件顺序和分区键。

历史记录清理。

事件 Schema 的兼容升级。

消费者如何实现幂等?

消费者如何实现幂等?

常见方式是为每个业务操作提供稳定的幂等键或事件 ID,在本地事务中同时完成“检查/记录消费结果”和业务更新。

可以使用:

数据库唯一约束防止重复插入。

去重表记录已经处理的事件 ID。

按业务状态机拒绝重复或非法状态迁移。

更新语句带版本号或当前状态条件。

对天然可覆盖的操作使用幂等写入。

仅在 Redis 中 SETNX 一个短期 Key 不一定可靠:Key 可能先过期,业务事务也可能在记录成功后回滚。幂等记录最好与核心业务变更处于同一个本地事务,或者能够通过业务状态恢复判断。

SETNX

稳定性与发布

稳定性与发布

⭐️超时、重试、熔断、限流和隔离分别解决什么问题?

⭐️超时、重试、熔断、限流和隔离分别解决什么问题?

手段主要作用超时限制一次调用最多等待多久,避免资源无限占用重试应对短暂故障,但要求操作幂等,并设置次数、退避和抖动熔断下游持续失败时快速拒绝,减少无效调用并等待恢复限流控制进入系统或依赖的请求速率,保护容量边界隔离限制单个依赖占用的线程、连接或并发,避免故障扩散

手段主要作用

手段

主要作用

超时限制一次调用最多等待多久,避免资源无限占用

超时

限制一次调用最多等待多久,避免资源无限占用

重试应对短暂故障,但要求操作幂等,并设置次数、退避和抖动

重试

应对短暂故障,但要求操作幂等,并设置次数、退避和抖动

熔断下游持续失败时快速拒绝,减少无效调用并等待恢复

熔断

下游持续失败时快速拒绝,减少无效调用并等待恢复

限流控制进入系统或依赖的请求速率,保护容量边界

限流

控制进入系统或依赖的请求速率,保护容量边界

隔离限制单个依赖占用的线程、连接或并发,避免故障扩散

隔离

限制单个依赖占用的线程、连接或并发,避免故障扩散

熔断器状态机

这些手段必须组合设计。没有超时的重试会堆积更多请求;多层同时重试会产生重试风暴;只有熔断没有降级,用户仍然只会得到失败。

更完整的容错和流量治理题见高可用系统设计常见面试题总结。

高可用系统设计常见面试题总结

如何设置微服务调用的超时与重试?

如何设置微服务调用的超时与重试?

先为整条用户请求设置总时间预算,再把预算分配给各个下游,内层超时应小于外层剩余时间。超时需要覆盖连接、读取、连接池等待等阶段。

重试只适合短暂故障和可幂等操作,通常使用有限次数的指数退避并加入随机抖动。下列场景不要盲目重试:

请求已经接近总超时。

明确的参数错误或权限错误。

非幂等写操作没有幂等键。

下游已经过载或熔断。

上下游多层都在重试。

对写请求,“客户端超时”不代表服务端没有执行成功。调用方应通过幂等键或查询接口确认最终结果,而不是立刻重复提交。

⭐️Liveness、Readiness 和 Startup 探针有什么区别?

⭐️Liveness、Readiness 和 Startup 探针有什么区别?

Liveness Probe(存活探针):判断容器是否需要重启。只有进程进入无法自愈的状态时才应失败。

Readiness Probe(就绪探针):判断实例是否应该接收流量。未就绪时实例会从服务端点中移除,但不一定重启。

Startup Probe(启动探针):给慢启动应用更长的初始化时间;成功前不会执行存活和就绪探测。

不要把所有外部依赖都放进存活探针。数据库短暂故障时,如果所有实例同时判定不存活并重启,反而会扩大故障。外部依赖影响接流量时,更适合体现在就绪状态中。

微服务如何实现优雅停机?

微服务如何实现优雅停机?

发布或缩容时,实例不能收到终止信号后立刻退出。常见流程是:

先把实例标记为不就绪,停止接收新流量。

给注册中心、负载均衡器和调用方留出摘除传播时间。

等待正在处理的请求结束,并停止领取新的消息任务。

提交或回滚进行中的本地事务,释放连接和线程。

超过最大宽限时间后再强制退出。

同时要处理长连接、消息消费者和定时任务。只关闭 HTTP 端口,却让消息处理到一半被终止,仍可能产生重复消费和中间状态。

微服务接口如何做到向后兼容?

微服务接口如何做到向后兼容?

接口升级优先采用“扩展而非破坏”:新增可选字段、让旧字段保留一段迁移期、消费者忽略未知字段。删除字段、改变语义、修改枚举或把可选字段改为必填都可能破坏旧客户端。

常见保障手段包括:

OpenAPI、Protobuf 或事件 Schema 版本管理。

Consumer-driven Contract Test(消费者驱动契约测试)。

先发布兼容新旧格式的消费者,再发布生产者,最后清理旧字段。

灰度发布并监控错误率、延迟和业务指标。

数据库变更采用 expand-and-contract,避免代码和表结构必须同时切换。

版本号能帮助区分不兼容接口,但不能代替兼容设计和迁移流程。

可观测性与安全

可观测性与安全

⭐️微服务为什么需要日志、指标和链路追踪?

⭐️微服务为什么需要日志、指标和链路追踪?

一次请求可能跨越多个服务和消息队列,只看单机日志很难回答“慢在哪里、失败发生在哪一跳”。三类信号关注点不同:

指标(Metrics):适合看趋势和告警,例如请求率、错误率、延迟分位数、CPU、连接池和消息积压。

链路追踪(Traces):展示一次请求跨服务的调用路径、每个 Span 的耗时和错误。

日志(Logs):记录具体事件和上下文,适合查看错误细节与业务线索。

服务之间需要传播 Trace Context,把同一次请求的 Span 关联起来;异步消息也要在消息头中携带并恢复上下文。日志中保留 trace ID,才能从告警和链路快速跳到具体日志。

微服务应该监控哪些关键指标?

微服务应该监控哪些关键指标?

可以按四层回答:

入口服务:请求率、成功率、P50/P95/P99 延迟、限流与熔断次数。

应用资源:CPU、内存、GC、线程池、连接池和队列等待时间。

依赖资源:数据库慢查询与连接数、缓存命中率、MQ 消费延迟与积压、下游调用耗时。

业务指标:下单成功率、支付成功率、库存异常、订单停留在中间状态的数量。

技术指标正常不代表业务正常。比如 HTTP 都返回 200,但支付成功率突然下降,只有业务指标能第一时间暴露问题。

⭐️认证和授权应该放在网关还是服务内部?

⭐️认证和授权应该放在网关还是服务内部?

网关适合统一完成 Token 基础校验、流量限制和外部入口保护,但服务内部仍要做与业务资源相关的授权。否则,内部调用绕过网关、错误路由或网络边界变化时,就可能出现越权。

一个常见分工是:

网关验证外部凭据,生成或转发经过签名保护的身份上下文。

服务验证上下文来源,并根据用户、租户、角色和目标资源执行授权。

服务间调用使用工作负载身份和 mTLS,不能把“来自内网”等同于可信。

敏感操作记录审计日志,凭据和个人信息不进入普通日志。

详细的 Token、SSO 和权限模型见认证与授权常见面试题总结,常见攻击与防护见 Web 安全常见面试题总结。

认证与授权常见面试题总结

Web 安全常见面试题总结

微服务迁移

微服务迁移

⭐️如何把单体系统逐步改造成微服务?

⭐️如何把单体系统逐步改造成微服务?

不建议一次性重写。比较稳妥的是绞杀者模式:在保留单体运行的同时,把边界清晰的能力逐步抽到新服务,并通过路由把相应流量切过去。

可以按下面的顺序推进:

先在单体内部建立模块边界,治理跨模块直接依赖。

补齐自动化测试、监控、发布和回滚能力。

选择变化独立、耦合低、收益明确的模块作为第一个服务。

明确新服务的数据所有权,通过 API、事件或 CDC 迁移数据访问。

灰度切流,比较新旧链路的结果和业务指标。

稳定后下线单体中的旧逻辑,再选择下一个模块。

第一个被拆出的模块不一定是最核心、最复杂的模块。用边界清晰的非核心能力验证平台和团队协作方式,失败成本通常更低。

设计一个微服务方案时,回答应包含哪些内容?

设计一个微服务方案时,回答应包含哪些内容?

可以用下面的顺序组织:

业务和约束:用户量、核心流程、一致性、延迟和可用性目标。

服务边界:为什么这样拆,谁拥有数据,哪些模块暂时不拆。

接口与事件:同步/异步选择、契约、幂等键和版本策略。

数据一致性:本地事务、中间状态、重试、补偿和对账。

稳定性:超时、限流、熔断、隔离、降级和容量预估。

可观测性:日志、指标、追踪、业务告警和审计。

交付与迁移:灰度、回滚、数据迁移和故障预案。

面试官更关注取舍是否与约束一致,而不是服务数量。能够主动说出“这里先不拆”和“这个方案会带来什么成本”,通常比堆砌组件名更有说服力。

参考资料

参考资料

Microsoft Azure Architecture Center:Microservices Architecture Style

Microsoft Azure Architecture Center:Microservices Architecture Style

Kubernetes Documentation:Service

Kubernetes Documentation:Service

Kubernetes Documentation:Liveness, Readiness, and Startup Probes

Kubernetes Documentation:Liveness, Readiness, and Startup Probes

AWS Prescriptive Guidance:Database-per-service Pattern

AWS Prescriptive Guidance:Database-per-service Pattern

Microservices.io:Saga Pattern

Microservices.io:Saga Pattern

Microservices.io:Transactional Outbox Pattern

Microservices.io:Transactional Outbox Pattern

OpenTelemetry:Observability Primer

OpenTelemetry:Observability Primer

写在最后

写在最后

感谢你能看到这里,也希望这篇文章对你有点用。

JavaGuide 坚持更新 6 年多,近 6000 次提交、600+ 位贡献者一起打磨。如果这些内容对你有帮助,非常欢迎点个免费的 Star 支持下(完全自愿,觉得有收获再点就好):GitHub | Gitee。

GitHub

Gitee

如果你想要付费支持/面试辅导(比如简历优化、一对一提问、高频考点突击资料等)的话,欢迎了解我的知识星球。已经坚持维护六年,内容持续更新,虽白菜价(0.4元/天)但质量很高,主打一个良心!

知识星球

JavaGuide 公众号