hwyzw 发表于 2024-12-27 12:47:55

深入解析微服务架构:从单体应用到网上超市的演变过程

    点击上方学好Java,选明星公众号

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre><pre style="margin-right: 0em;margin-left: 0em;letter-spacing: 0.544px;"><section style="margin: 0em 8px;padding-right: 0.5em;padding-left: 0.5em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;text-align: center;"><span style="font-size: 14px;color: rgb(178, 178, 178);letter-spacing: 0.5px;">重磅资讯、干货,第一时间送达</span></section><section style="margin: 0em 8px;padding-right: 0.5em;padding-left: 0.5em;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;text-align: center;"><br/></section></pre></p>
    今日推荐:

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre><pre style="letter-spacing: 0.544px;"><pre><section style="line-height: 1.8;word-spacing: 2px;letter-spacing: 2px;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;"><blockquote style="padding: 15px 15px 15px 1rem;border-left-width: 5px;border-left-color: rgb(61, 170, 221);color: rgb(0, 0, 0);font-size: 0.9em;overflow-wrap: normal;line-height: inherit;background: none 0% 0% repeat scroll rgb(239, 235, 233);overflow: auto;word-break: normal;"><section style="margin-right: 8px;margin-left: 8px;font-size: inherit;color: inherit;line-height: inherit;">个人原创100W+访问量博客:<span style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;font-size: 18px;"><strong style="letter-spacing: 1px;text-align: left;text-indent: 32px;"><span style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;font-family: 微软雅黑;font-size: 16px;">点击前往,查看更多</span></strong></span></section></blockquote></section></pre></p>
    原来的:

    本文将介绍微服务架构和相关组件,介绍它们是什么以及为什么使用微服务架构和这些组件。本文重点是简洁地表达微服务架构的整体图景,因此不会深入讨论如何使用组件等细节。

    要了解微服务,首先要了解非微服务。通常微服务的对立面是单体应用程序,即将所有功能打包成一个独立单元的应用程序。从单体应用程序到微服务的转变并不是一朝一夕就能完成的。这是一个渐进的进化过程。本文将以一个在线超市应用程序为例来说明这个过程。

    初始需求

    几年前,小明和小皮一起开了一家网上超市。小明负责程序开发,小皮负责其他事宜。当时互联网尚未发达,网上超市还是一片蓝海。只要实现了功能,就可以随意赚钱。所以他们的需求很简单。他们只需要一个公共网络上的网站,用户可以在其中浏览和购买产品。他们还需要一个可以管理产品、用户和订单数据的管理后端。

    我们来整理一下功能列表:

    管理背景

    由于要求简单,小明慢动作地动了动左右手,网站就准备好了。出于安全原因,管理后端不与网站位于同一地点。慢镜头回放小明的右手和左手,管理网站已准备就绪。整体架构图如下:

    小明一挥手,找到了一个云服务并部署了,网站就上线了。上线后好评如潮,深受各类肥宅的喜爱。小明和小皮高兴地躺下收钱。

    随着业务的增长...

    美好的时光并没有持续多久。几天之内,各种网上超市如雨后春笋般涌现,对小明小皮产生了强烈的冲击。

    迫于竞争压力,小明小皮决定进行一些营销手段:

    这些活动需要计划开发支持。小明拉着他的同学小红加入了队伍。小红负责数据分析和移动端相关开发。小明负责促销活动相关功能的开发。

    由于开发任务比较紧急,小明和小红并没有规划整个系统的架构。相反,他们随意拍拍脑袋,决定把促销管理和数据分析放在管理后台,微信和手机APP分开建设。经过几天通宵工作,新功能、新应用基本完成。此时的架构图如下:

    这个阶段有很多不合理的地方:

    虽然问题很多,但这一阶段的成果是不可否认的:系统根据业务变化很快搭建起来。然而,紧迫而繁重的任务很容易让人陷入片面、短视的思维,做出妥协的决定。在这种结构中,大家只关注自己的一亩三分地,缺乏整体和长远的设计。长此以往,制度建设将越来越困难,甚至可能陷入不断推翻和重建的循环之中。

    是时候做出改变了

    庆幸的是,小明和肖红都是有追求、有理想的好年轻人。意识到问题后,小明和肖红从琐碎的业务需求中解放了一些精力,开始梳理整体架构,准备改造问题。

    想要转型,首先要有足够的精力和资源。如果你的需求方(业务人员、项目经理、老板等)对需求进度的追求太强烈,以至于你无法分配额外的精力和资源,那么你可能什么都做不了……

    在编程的世界里,最重要的是抽象能力。微服务改造的过程实际上是一个抽象的过程。小明和小红整理了网上超市的业务逻辑,抽象出通用的业务能力,创建了几个公共服务:

    每个应用后台只需要从这些服务中获取所需的数据,从而删除了大量冗余代码,只留下薄薄的控制层和前端。该阶段的结构如下:

    现阶段只是服务分离,数据库仍然是共享的,所以烟囱系统的一些缺点仍然存在:

    数据库成为性能瓶颈并面临单点故障的风险。

    数据管理往往比较混乱。即使一开始有很好的模块化设计,随着时间的推移,总会出现一个服务直接从数据库取另一个服务的数据的现象。

    数据库表结构可能会依赖多个服务,影响整个系统,难以调整。

    如果一直维持共享数据库模型,整个架构就会变得越来越僵化,也就失去了微服务架构的意义。于是,小明和小红联手对数据库进行了拆分。所有持久层都是相互隔离的,并且由每个服务负责。另外,为了提高系统的实时性,增加了消息队列机制。架构如下:

    完全分离后,各个服务可以使用异构技术。例如,数据分析服务可以使用数据仓库作为持久层,高效地进行一些统计计算;商品服务和促销服务访问比较频繁,所以增加了缓存机制。

    另一种抽象公共逻辑的方法是将这些公共逻辑做成公共框架库。这种方法可以减少服务调用的性能损失。但这种方式的管理成本非常高,而且很难保证所有应用版本的一致性。

    数据库拆分也存在一些问题和挑战:比如需要跨数据库级联、通过服务查询数据的粒度等。但这些问题都可以通过合理的设计来解决。总体而言,数据库拆分利大于弊。

    微服务架构还具有非技术优势。使得整个系统的分工更加清晰,职责更加明确。每个人都有责任为他人提供更好的服务。在单体应用时代,公共业务功能往往没有明确的归属。最终,要么大家各司其职,大家又执行;或者随机的一个人(通常是更有能力或更热情的人)实现他负责的应用程序。在后一种情况下,这个人除了对自己的应用负责之外,还负责向其他人提供这些公共功能——而这个功能本来就不对任何人负责,只是因为他更有能力/热心。莫名其妙地承担责任(这种情况也被委婉的说是那些能做更多工作的人)。最终没有人愿意提供公共职能。随着时间的推移,团队里的人逐渐独立起来,不再关心整体架构设计。

    从这个角度来看,使用微服务架构也需要对组织架构进行相应的调整。因此,微服务转型需要管理者的支持。

    转型完成后,小明和肖红明确了各自的角色。两个人都很满意,一切都像麦克斯韦方程组一样美丽、完美。

    然而……

    没有银弹

    春天来了,万物复苏,一年一度的购物狂欢节又来了。看着每天的订单量稳步上升,小皮、小明、小红开心地笑了。不幸的是,美好的时光并没有持续多久。极度的喜悦带来了悲伤。突然,系统崩溃了。

    https://img0.baidu.com/it/u=534302371,3625179719&fm=253&fmt=JPEG&app=138&f=JPEG?w=745&h=383

    过去,对于单个应用程序,故障排除通常涉及查看日志、研究错误消息和调用堆栈。在微服务架构中,整个应用分散成多个服务,故障点定位非常困难。小明一一查看日志,手动调用各个服务。经过十多分钟的查找,小明终于找到了故障点:促销服务因收到大量请求而停止响应。其他服务直接或间接调用了提升服务,因此也往下走。在微服务架构中,服务故障可能会产生雪崩效应,导致整个系统故障。事实上,小明和小红在节前就进行了请求量评估。预计服务器资源足以支撑假期请求量,所以肯定有问题。然而,情况紧急。每一分每一秒过去都是浪费金钱,所以小明根本没有时间去排查问题。他当即做出决定,在云端新建几台虚拟机,然后一一部署新的推广服务。节点。经过几分钟的运行,系统终于恢复正常。整个失败期间估计损失了几十万的销量,三个人的心都在滴血……

    随后,小明干脆写了一个日志分析工具(体积大到用文本编辑器几乎打不开,肉眼也看不出来),统计了推广服务的访问日志,发现故障期间,产品服务因代码被阻塞。问题在于,在某些场景下,会向促销服务发出大量请求。这个问题并不复杂。小明弹指一挥间就修复了这个价值几十万的bug。

    问题已经解决,但谁也不能保证其他类似问题不会再次发生。虽然微服务架构在逻辑设计上看似完美,但它就像积木搭建的华丽宫殿,经不起风浪。微服务架构虽然解决了老问题,但也引入了新问题:

    小明和肖红吸取了经验,决心解决这些问题。故障排除一般涉及两个方面:一方面尽量降低故障发生的概率,另一方面减少故障的影响。

    监控 - 发现故障迹象

    在高并发的分布式场景中,故障往往是突发性的、雪崩式的。因此,必须建立一套完整的监控系统,尽可能地发现故障迹象。

    微服务架构中有很多组件,每个组件需要监控不同的指标。比如Redis缓存一般监控内存占用值和网络流量,数据库监控连接数和磁盘空间,业务服务监控并发数、响应延迟、错误率等,因此是不现实的建立一个庞大而全面的监控系统来监控每个组件,可扩展性会很差。一般的做法是让每个组件提供一个接口()来报告其当前状态。该接口输出的数据格式应保持一致。然后部署一个指标收集器组件,定期从这些接口获取和维护组件状态,同时提供查询服务。最后需要一个UI来从指标收集器中查询各种指标,绘制监控界面,或者根据阈值发出警报。

    大多数组件不需要自己开发,网上有开源组件。小明下载了,这两个组件分别提供了Redis缓存和MySQL数据库的指标接口。微服务根据各个服务的业务逻辑实现定制化的指标接口。然后小明用它作为指标采集器来配置监控界面和邮件报警。这样一个微服务监控系统搭建起来:

    定位问题——链路追踪

    在微服务架构下,一个用户的请求往往会涉及到多个内部服务调用。为了方便问题定位,需要能够记录每个用户请求时微服务内部产生了多少次服务调用,以及它们的调用关系。这称为链接跟踪。

    我们使用 Istio 文档中的链接跟踪示例来看看效果:

    图片来自 Istio 文档

    从图中可以看出,这是一个用户请求访问该页面。在请求过程中,服务依次调用与服务的接口。服务在响应过程中调用该接口。整个链路踪迹的记录是一棵树:

    为了实现链路跟踪,每次服务调用都会在HTTP中记录至少四条数据:

    另外,还需要调用用于日志收集和存储的组件,以及用于显示链接调用的UI组件。

    以上只是一个最简单的解释。链接跟踪的理论基础可以详细找到。

    了解了理论基础后,小明选择了开源实现。然后弹指一挥间,我写了一个HTTP请求拦截器,生成这些数据并注入到每个HTTP请求中,同时将调用日志异步发送到日志收集器。这里额外提一下的是,HTTP请求拦截器可以在微服务的代码中实现,也可以使用网络代理组件来实现(但这样的话,每个微服务都需要添加一层代理)。

    链路跟踪只能定位哪个服务出现问题,无法提供具体的错误信息。查找具体错误信息的能力需要日志分析组件提供。

    分析问题——日志分析

    日志分析组件在微服务兴起之前应该已经得到了广泛的应用。即使是单一的应用架构,当访问次数增加或服务器规模增大时,日志文件的大小也会膨胀到难以用文本编辑器访问的程度。更糟糕的是它们分散在多个服务器上。排查问题时,需要登录各个服务器获取日志文件,并逐一搜索想要的日志信息(而且打开和搜索都很慢)。

    因此,当应用规模变大时,我们需要一个日志的“搜索引擎”。以便您能够准确的找到您想要的日志。另外,数据源端还需要收集日志的组件和显示结果的UI组件:

    小明调查并使用了著名的ELK日志分析组件。 ELK 是三个组件的缩写: 、 、 和 。

    最后还有一个小问题,日志如何发送到。一种解决方案是在日志输出时直接调用发送日志的接口。这样一来,代码又需要修改了(哎,为什么要用“和”)……所以小明选择了另一种解决方案:日志仍然输出到文件中,在每个服务中部署一个Agent来扫描日志文件,然后将其输出到.

    网关-权限控制、服务治理

    分裂成微服务后,出现了大量的服务和接口,使得整个调用关系变得混乱。经常在开发过程中,边写边写,突然想不起来某条数据应该调用哪个服务。或者写错了,调用了不该调用的服务,只读函数最终修改了数据……

    为了应对这些情况,微服务的调用需要一个看门人,也就是网关。在调用者和被调用者之间添加一层网关,每次调用时进行权限验证。另外,网关还可以作为提供服务接口文档的平台。

    使用网关的一个问题是决定应该使用它的粒度:最粗粒度的解决方案是整个微服务的网关。微服务外部通过网关访问微服务,微服务内部直接调用;最细粒度是所有调用。无论是微服务内部调用还是外部调用,都必须经过网关。一个折衷的方案是,将微服务按照业务区域划分为若干个区域,区域内直接调用,通过网关调用。

    由于整个网上超市的服务数量并不是特别多,所以小明采用了最粗粒度的方案:

    发现中的服务注册-动态扩展

    前面的组件都是为了减少出现故障的可能性而设计的。然而,故障总是会发生,因此另一个需要研究的问题是如何减少故障的影响。

    最原始(也是最常用)的故障处理策略是冗余。一般来说,一个服务会部署多个实例,这样可以分担压力,提高性能,其次,即使一个实例出现故障,其他实例仍然可以响应。

    冗余的一个问题是使用了多少冗余?这个问题在时间线上并没有确切的答案。根据业务功能和时间段的不同,需要不同数量的实例。例如,在工作日,4 个实例可能就足够了;在促销期间,当流量显着增加时,可能需要40个实例。因此,冗余量并不是固定值,而是可以根据需要实时调整。

    一般来说,添加实例的操作是:

    部署新实例

    向负载均衡器或 DNS 注册新实例

    操作只有两步,但如果注册到负载均衡或DNS的操作是手动的,那么事情就不会简单了。想想添加 40 个新实例后必须手动输入 40 个 IP 的感觉……

    https://img2.baidu.com/it/u=1267209131,1949879563&fm=253&fmt=JPEG&app=120&f=JPEG?w=922&h=500

    这个问题的解决方案是自动服务注册和发现。首先,您需要部署一个服务发现服务,为所有注册的服务提供地址信息。 DNS也可以看作是一种服务发现服务。然后,每个应用程序服务在启动时都会自动向服务发现服务注册。并且应用服务启动后,各个应用服务的地址列表会从服务发现服务实时(定时)同步到本地。服务发现服务还会定期检查应用服务的健康状况,并删除不健康的实例地址。这样,在添加实例时,只需部署新实例即可。当实例离线时,您可以直接关闭服务。服务发现会自动检查服务实例的增加或减少。

    服务发现还可以与客户端负载平衡一起使用。由于应用服务已在本地同步了服务地址列表,因此您在访问微服务时可以自行决定加载策略。甚至可以在注册服务时添加一些元数据(服务版本等信息),客户端负载会根据这些元数据进行流量控制,实现A/B测试、蓝绿发布等功能。

    服务发现有很多组件可供选择,例如 、 、 、 etcd。不过小明觉得自己很擅长,想一展身手,于是就基于Redis写了一个……

    断路器、服务降级、限流断路器

    当服务由于各种原因停止响应时,调用者通常会等待一段时间,然后超时或收到错误返回。如果调用链路比较长,可能会导致请求堆积,整个链路占用大量资源并等待下游响应。因此,当多次访问某个服务失败时,应该断开断路器,将服务标记为已停止工作,并直接返回错误。等待服务恢复正常后再重新建立连接。

    图片来自《微服务设计》

    服务降级

    当下游服务停止工作时,如果该服务不是核心业务,则应将上游服务降级,以保证核心业务不中断。例如,网上超市订购界面有一个收集推荐产品订单的功能。当推荐模块宕机时,点餐功能不能同时宕机。您只需暂时关闭推荐功能即可。

    限流

    服务挂掉后,上游服务或者用户通常会习惯性地重试访问。这意味着一旦服务恢复正常,很可能会因为网络流量过大而立即挂断,并在棺材里重复仰卧起坐。因此,服务需要能够保护自身——限制流量。限流策略有很多种。最简单的就是当单位时间内请求过多时,丢弃多余的请求。另外,还可以考虑分区限流。仅拒绝来自生成大量请求的服务的请求。例如,商品服务和订单服务都需要接入促销服务。产品服务因代码问题发起大量请求。促销服务仅限制商品服务的请求,订单服务的请求正常响应。

    测试

    微服务架构下,测试分为三个层次:

    端到端测试:覆盖整个系统,通常针对用户界面模型进行测试。

    服务测试:测试服务接口。

    单元测试:测试代码单元。

    三类测试的实施难易程度从上到下逐渐增加,但测试效果逐渐降低。端到端的测试是最耗时耗力的,但是通过测试之后我们对系统最有信心。单元测试最容易实现,效率最高,但不能保证测试后整个系统没有问题。

    由于端到端测试很难实现,所以一般只对核心功能进行端到端测试。一旦端到端测试失败,就需要将其分解为单元测试:然后分析失败的原因,然后编写单元测试来重现问题,以便我们可以更快地捕获相同的错误。未来。

    服务测试的困难在于服务通常依赖于其他服务。这个问题可以通过Mock来解决:

    每个人都熟悉单元测试。我们通常会编写大量的单元测试(包括回归测试)以试图覆盖所有代码。

    微服务框架

    指标接口、链路跟踪注入、日志导流、服务注册发现、路由规则等组件以及熔断、限流等功能都需要在应用服务上添加一些对接代码。如果单独实现每一个应用服务,会非常耗时耗力。基于DRY的原则,小明开发了一个微服务框架,将与各个组件接口的代码以及其他公共代码抽取到框架中,所有应用服务都使用这个框架进行开发。

    利用微服务框架可以实现很多定制化的功能。甚至可以将程序调用堆栈信息注入到链接跟踪中,实现代码级的链接跟踪。或者输出线程池和连接池的状态信息,实时监控服务底层状态。

    使用统一的微服务框架有一个严重的问题:更新框架的成本非常高。每次框架升级时,所有应用服务都需要配合升级。当然,一般会采用兼容的方案,允许一段并行时间等待所有应用服务升级。但如果应用服务较多,升级时间可能会很长。而且有一些非常稳定的应用服务很少更新,负责人可能会拒绝升级……因此,使用统一的微服务框架需要完整的版本管理方法和开发管理规范。

    另一种方式——网格

    另一种抽象公共代码的方法是将其直接抽象为反向代理组件。每个服务额外部署这个代理组件,所有出站和入站流量都通过该组件进行处理和转发。该组件称为。

    没有额外的网络费用。它将与微服务节点部署在同一主机上,共享同一个虚拟网卡。因此,与微服务节点的通信实际上只是通过内存复制来实现。

    图片来自::网格

    只负责网络通讯。还需要一个组件来统一管理所有配置。在Mesh中,负责网络通信的部分称为数据平面,负责配置管理的部分称为控制平面。数据面和控制面构成了Mesh的基本架构。

    图片来自::网格

    Mesh相对于微服务框架的优势在于不侵入代码,更方便升级和维护。它经常因性能问题而受到批评。即使环回网络不产生实际的网络请求,仍然存在内存复制的额外成本。另外,一些集中的流量处理也会影响性能。

    结束也是开始

    微服务并不是架构演进的终结。再进一步,还有FaaS等方向。另一方面,也有人唱着,和谐必须在时间长了之后分裂,单体建筑必须被重新发现……

    无论如何,微服务架构的转型暂时告一段落。小明满意地拍了拍自己越来越光滑的脑袋,打算这个周末休息一下,和肖红一起喝杯咖啡。

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre style="letter-spacing: 0.544px;font-size: 16px;widows: 1;word-spacing: 2px;caret-color: rgb(255, 0, 0);color: rgb(0, 0, 0);background-color: rgb(255, 255, 255);text-align: center;"><span style="word-spacing: 2px;caret-color: rgb(255, 0, 0);color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 0.75px;">最后,再附上我历时三个月总结的 </span>Java 面试 + Java 后端技术学习指南<span style="word-spacing: 2px;caret-color: rgb(255, 0, 0);color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 0.75px;">,笔者这几年及春招的总结,github 1.4k star,拿去不谢!</span>
<pre data-darkmode-color="rgb(168, 168, 168)" data-style="padding-right: 0.5em; padding-left: 0.5em; max-width: 100%; color: rgb(62, 62, 62); background-color: rgb(255, 255, 255); letter-spacing: 0.544px; font-size: 15px; word-spacing: 2px; box-sizing: border-box !important; overflow-wrap: break-word !important; word-break: normal !important;" data-darkmode-bgcolor="rgb(36, 36, 36)" data-darkmode-bgcolor-15910235082632="rgb(36, 36, 36)" data-darkmode-original-bgcolor-15910235082632="rgb(255, 255, 255)" data-darkmode-color-15910235082632="rgb(168, 168, 168)" data-darkmode-original-color-15910235082632="rgb(62, 62, 62)" data-darkmode-original-bgcolor="rgb(255, 255, 255)" data-darkmode-original-color="rgb(62, 62, 62)" style="padding-right: 0.5em;padding-left: 0.5em;letter-spacing: 0.544px;color: rgb(62, 62, 62);font-size: 15px;word-break: normal !important;">    <h4><section style="margin: 1.3em 8px;letter-spacing: 0.544px;white-space: normal;font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;line-height: inherit;"><span style="padding-right: 0.5em;padding-left: 0.5em;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 0.75px;text-align: left;"></span></section></h4><h3 data-tool="mdnice编辑器" style="margin: 1.2em 8px 1em;font-weight: bold;font-size: 20px;white-space: normal;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;text-align: left;letter-spacing: 0.75px;color: rgb(53, 179, 120);">下载方式</h3><section style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;white-space: normal;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;letter-spacing: 0.75px;line-height: 26px;">1. 首先扫描下方二维码</section><section style="margin: 1em 8px;padding-top: 8px;padding-bottom: 8px;white-space: normal;color: black;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;text-align: left;letter-spacing: 0.75px;line-height: 26px;">2. 后台回复「Java面试」即可获取</section></pre></p>
页: [1]
查看完整版本: 深入解析微服务架构:从单体应用到网上超市的演变过程