为什么Spring仍然会是云原生时代最佳平台之一?
简介: 基于Java语言的Spring生态,还能否适应新的开发方式,比如Cloud Native、Serverless、Faas等,它还会是云原生时代的最佳平台的选择吗?本文将从5个角度来为你分析一下这个问题,分别是:Java和JDK的发展、充满良性竞争的JVM语言、成熟的面向服务架构的Spring Boot和Spring Cloud、让事件驱动架构更易使用的Spring Reactive。
作者 | 雷卷
来源 | 阿里技术公众号
大家好,我是陈立兵,花名雷卷,Java/Kotlin工程师、 Alibaba RSocket Broker开发者Reactive基金会的初创成员。目前主要关注于Reactive/RSocket、Serverless、WebAssembly、 Deno/Rust等相关技术。
今天和大家聊聊为什么我认为Spring仍然会是云原生时代最佳平台之一。
时间回到2015年,当时我在华盛顿参与SpringOne 2015大会,主题演讲是Cloud Native Enterprise。那次大会的口号也是Cloud Native,铺天盖地的海报都与Cloud Native有关。
你可能会感到奇怪,那个时候容器还没有流行啊,怎么就敢称为Cloud Native呢?虽然不少同学对Cloud Native的理解可能不太一样,但是越来越多的人相信“Cloud native is about culture, not containers”。可以肯定的是,云原生不等于容器,它是一种软件开发的文化,即便后续有基于V8隔离的Serverless或者WebAssembly FaaS平台,也还是云原生的范畴。
而在云原生的潮流下,Spring做了很多事情,比如支持分布式架构的Spring Cloud,它基本是Java应用分布式和云化的必备框架,各个云厂商都有Spring Cloud的对接版本,比如Spring Cloud Alibaba、AWS、Azure、GCP等,Spring Cloud极大地简化了Java应用和云服务的对接。
可以说,在微服务逐渐流行的过程中,Spring生态一直处于领先地位。很多人可能会疑惑:Spring之前取得的这些成就我们都知道,可这两年好像并没有什么发展,Spring还能继续引领该技术趋势吗?
其实,之所以会有这样的疑惑,是因为当我们谈到基于云和云原生环境的开发时,普遍更关注的是技术栈的选择,而这背后主要是费用的问题。
每一位开发者或中小公司都希望购买更少的云资源干更多的事情,其中最主要的是内存和CPU。内存消耗要求小,如果能编译为独立可执行程序,就尽量选择一些轻量型的开发框架;而且要考虑语言高效且全异步化的框架,这样可以保证充分利用CPU,避免线程等待等造成的浪费。
综合来看,很多人会更倾向于选择Rust、Node.js、Golang等技术栈。而Java启动慢,消耗内存多,好像还比较费钱,看起来已经不太适合接下来的云原生时代。
那么作为基于Java语言的Spring生态,还能否适应新的开发方式,比如Cloud Native、Serverless、Faas等,它还会是云原生时代的最佳平台的选择吗?
接下来,我将从5个角度来为你分析一下这个问题,分别是:Java和JDK的发展、充满良性竞争的JVM语言、成熟的面向服务架构的Spring Boot和Spring Cloud、让事件驱动架构更易使用的Spring Reactive。
一 Java 和 JDK的发展
我们知道Spring基于的是Java框架,而JDK又是运行Java程序的基础。要探讨“Spring还会不会是云原生的最佳平台之一”这个问题,我们需要先来看看JDK这个底座怎么样,是不是能为Spring与时俱进的发展提供良好的基础。
首先是版本迭代。现在OpenJDK的开发速度非常快,之前是三年出一个重大版本,现在半年就出一个版本。好多同学感叹现在都快Java 17了,而自己还在用Java 1.8。我们都说软件开发要小步快跑,这个是一个好的开发方式:小步快跑、快速反馈、快速迭代开发。类似的还有JavaScript一年一版,TypeScript一年三版,Java一年两版,可以说都是非常好的节奏。
其次是JDK特性的更新。如果你关注JDK的话就不难发现,越来越多的特性被融入到JDK中,比如类似协程的轻量级线程Loom项目、提升Native调用支持的Panama项目(SIMD支持)、更高级的GC算法等。
而针对我们前面提到的Java启动慢、消耗内存多的问题,Oracle推出了基于OpenJDK的GraalVM,它可以直接在JVM中运行JavaScript、Python、Ruby等语言,是名副其实的Ployglot JVM。另外,GraalVM还提供了Native Image特性,可以将Java代码转换为独立可执行程序。Spring也推出Spring Native项目,可以非常方便将Spring Boot应用native-image化,这样Spring应用启动的速度更快,消耗的内存也更少。native-image化后的Spring应用就可以满足每一位开发者或中小公司对“上云且费用更少”的需求。即便是Java的命令行应用,GraalVM也有非常好的解决方案——Picocli + GraalVM Native Image + upx,它可以将Java应用编译为更便捷的可执行程序。
下图就是GraalVM多语言和Native Image支持列表,如下:
此外,各个云厂商也在积极JDK的开发,如AliJDK、Amazon Corretto、Azul等,这些都是对JDK发展有非常好的作用。
所以说,对Spring而言,JDK这个基础还是非常稳固的。
二 充满良性竞争的 JVM 语言
JDK并不是只能支持Java这一门编程语言,相反,它可以支持多种,比如Kotlin、Scala、Groovy等,这里我们统一称之为JVM语言。当下,JVM语言正处于一个充满良性的竞争状态。
我们知道每种语言都有自己擅长的使用场景。在云原生时代,多语言开发支持非常重要,开发人员需要根据实际的业务场景选择不同的开发语言。无论JVM语言或者GraalVM支持的语言,都可以和Spring直接整合,方便开发人员充分利用Spring技术栈做开发。
其中,对Spring支持最好的语言是Kotlin。Kotlin和Spring的整合已经渗透到框架的各个方面,比如Spring Boot和Kotlin整合,Spring Webflux对Kotlin Coroutines & Flow的支持,Spring Data、SpringIntegration、SpringCloud Function等也都包含对Kotlin的支持,这些都实现了对Kotlin的深度整合,目的就是让语言和框架结合得更好,提升开发效率。此外,Kotlin和Scala也是与Spring整合得比较好的语言。
因为Java、Kotlin和Scala这三种语言在Spring中的整合非常好,所以接下来我就对它们具体说明一下,帮助你对JVM语言当前的发展情况有一个比较清晰的认知。
Java
Java语言的发展还是非常快速的,伴随着OpenJDK的开发,Java半年就会推出一个版本,每个版本都会伴随着语法的升级、标准库的升级等,极大地方便了开发人员。从Java 9到16,已经完成了很多语法和特性方面的提升,比如大家知道var关键字(局部变量类型推导)、Text block、Pattern Matching for instanceof、Record、sealed interface等。
Kotlin
虽然说Kotlin不完全是针对JVM的,它也包括Native、JavaScript等,但是Kotlin对JVM和Android的支持,目前是众多Kotlin平台中特性最多且稳定的。
现在Kotlin的发展势头非常迅速,已经孵化了多个子项目,比如最新的Kotlin Multiplatform Mobile(一套代码支持多个手机平台)、 Kotlin Compose for Desktop(一套代码开发多操作系统的桌面端应用)等。当然,这些变化中最引入注目的是Kotlin 1.5推出的Kotlin intermediate representation (IR) ,借助于新的IR,Kotlin的代码可以编译输出到各种目标平台且程序更优,如大家都关注的Kotlin WebAssembly。
Scala
Scala 2是2006年3月发布的,距今已经有非常长的时间了。好的消息是Scala 3.0已经于2021年5月发布啦。Scala 3在添加新特性的同时,去除了Scala 2中一些晦涩的特性,目的是让Scala更好使用。如果你有兴趣,可以关注下Scala3的官网。
Scala 3还有一个核心的变化是编译器的调整,名称为TASTy,是 Typed Abstract Syntax Trees的缩写,结构如下:
借助于TASTy,目前已经有Scala.js ,后续有Scala WebAssembly也不奇怪。
Kotlin和Scala对函数编程都非常友好,在这点上都弥补了Java的不足。此外,Kotlin和Scala同时支持JavaScript编译输出,这个对基于V8的Serverless平台帮助非常大,比如Cloudflare的Serverless平台。此外,Kotlin和Scala也在积极探索对WebAssembly支持,这些都是支持Web开发的好消息。
当然,JVM语言还有很多,如Groovy、Clojure等,也都在积极发展,这些JVM语言彼此竞争,彼此借鉴,这对开发者来说,都是非常好的消息。
Spring依赖的JDK和JVM语言说完了,下面我们来聊聊Spring中非常成功的两个项目SpringBoot 和 Spring Cloud。
Spring Boot和Spring Cloud是非常成功的两个项目,基于Java技术栈的互联网公司基本都在使用。此外,近期如果你有关注Spring顶级布道师Josh Long的演讲的话,就会发现他一直在讲Spring Reactive,他还编写了一本书,就叫《Spring Reactive》。
那为什么Spring Boot、Spring Cloud和Spring Reactive会如此备受关注呢?
我们知道,对于云原生来讲,有两种非常重要的架构方案,一个是面向服务化架构,另一个是事件驱动架构。面向服务化架构通常基于同步的请求/响应模型,Spring Boot、Spring Cloud的目标就是支持这一特性;事件驱动架构则基于异步化的消息模型和消息路由,而Spring Reactive这个项目的核心就是支撑这一架构。
接下来,我就带你看看Spring具体是如何支持云原生里这两种重要的架构方案的,借此,你也会对Spring里重要的三个项目Spring Boot、Spring Cloud和Spring Reactive有一个比较清楚的认知。
三 成熟的面向服务架构的 Spring Boot 和 Spring Cloud
通常,云原生下的应用都倾向于微服务设计,而微服务设计的核心内容就是面向服务化架构设计和应用编程接口(API)管理,Spring Boot和Spring Cloud的目标就是支持这一特性的,而且这两个项目做得非常成功,基于Java技术栈的互联网公司基本都在使用。
我们知道,服务化接口最典型的结构就是请求(Request)/响应(Response)模式,尤其是Web的Request/Response模式。当然了,远程RPC的request/response也在此范畴,比如HTTP REST、gRPC和Dubbo。
对应到Spring中面向服务架构设计,SpringMVC可以非常好地支持Web和HTTP REST API,还包括对OpenAPI的支持。其他RPC类的通讯,也都有对应的Spring Boot starter支持,在Spring Boot中也有对应的starter组件支持,开发也非常便捷。如果要暴露其他形式服务,如Web Service和SOAP等传统服务形式,也可以哦通过Spring Web Services(spring-ws)项目达成。
另一个特性API管理,主要涉及服务治理和服务消费端调用,对应的核心技术栈主要是Spring Cloud项目。Spring Cloud为服务化架构和API管理提供了多种基础设置,如服务注册发现、负载均衡、API Gateway、断路保护等,这些技术栈方便我们更好地管理和治理服务,同时也让消费端能更好地调用服务。
综合下来,服务化架构和API管理,对应到Spring Boot和Spring Cloud技术栈,典型的架构如下:
如果你的架构是面向服务的,如涉及到服务编排、服务治理和API Gateway等,那么Spring Cloud会是非常好的选择,特性完备、框架稳定、文档完善、历经过诸如阿里巴巴等亲自实践。如果你的公司基于Java技术栈,那么Spring Cloud是实现微服务架构最好的支持和实践。
阿里巴巴从2018年7月开始就开源了Spring Cloud Alibaba,截止至今,它已经获得了超过1.9万的star数,已经超过了所有其他实现的总和。从X-lab 开放实验室发布的《2020年微服务领域开源数字化报告》中,我们也看到Spring Cloud Alibaba已经成为最活跃、最受开发者欢迎、工具链最完善的Spring Cloud实现。大家在选型过程中可以考虑。
四 让事件驱动架构更易使用的 Spring Reactive
前面我们讲述了Spring对面向服务架构的支持,还有另外一种架构模式,就是事件驱动架构。事件驱动架构被普遍认为是云原生首选的架构方案,那么Spring对这方面支持如何?这就涉及到Spring Reactive这个项目了,其核心就是支撑事件驱动架构。
Spring Reactive是面向异步化消息的,消息或者事件是企业集成模式(EIP)首推的架构模式,你可能听说过Spring企业集成(SpringIntegration)这个项目,其主要就是负责企业集成。Spring Reactive的目的则是让事件驱动架构设计也能成为应用架构的首选,而且要更加简单。
这里讲一个题外话:我和一些资深的Spring Boot和Spring Cloud程序员交流过,大家对各种服务化接口设计如数家珍,甚至包括代码层级的细节,但是一讨论到企业集成模式(EIP),都表示这是Martin Fowler的知名大做,问及大家的系统中是否使用到诸如Apache Camel或者Spring Integration,大家都表示现在面向服务结构已经满足业务需求啦。
那么这里就有一个问题,如此知名的EIP、ESB或者EDA为何没有在架构设计中所采纳?是灯下黑被遗忘、还是门槛太高、或者压根就是一个纯理论,完全脱离实践?
在面向服务化的架构设计中,基本都是基于服务接口调用,并且调用是同步的,所以服务编排、API管理起着非常重要的作用。当然在面向服务化的架构设计中,我们也会引入一些消息驱动设计,借助像Kafka或者其他MQ系统,处理一些异步化或者数据分析的场景,如用户注册后发一封邮件、用户登录触发事件进行安全审计、商品信息或价格修改后触发搜索引擎增量索引、用户下单后触发各种通知等等。
面向服务有其好处,但是要解决非常多的问题,如非阻塞、服务注册和发现、高性能、易集成等,这也是很多专家提出云原生场景中,事件驱动架构才是主流。
事件驱动架构不涉及服务注册和发现这些负责的机制,基于消息的路由也非常容易,异步化消息通讯的性能更高(当然你的账单也更小),各种SaaS服务集成也更简单(EventBridge产品)、FaaS触发容易等等。如果你想了解更多,可以看看《Building Event-Driven Microservices》这本书。这里贴一下Redhat推荐的基于微服务思想的事件驱动架构设计,如下:
从这张图中我们可以看出,所有系统之间的交互都是通过消息进行通讯的,微服务不再是面向接口的请求/响应设计,处理来自不同的应用的Request/Response请求,而是以一种异步化的方式不断地在处理来自Broker的消息。
说到这里,你可能明白了,如果说Cloud Native以事件驱动设计架构为核心,那么Spring Reactive的目的是让事件驱动架构简单易用,开发便捷,这个就是Spring Boot和Cloud对面向服务架构的支持是一样的。此外这两种架构并不是矛盾的,都是可以相互共存和相互补充的。
要实现更好的事件驱动架构,两个基础少不了:异步化和消息化。异步化可以解决线程等待的问题,而消息及其消息路由,可以很好地实现应用的松耦合。当前越来越多的产品使用异步化消息方案,各种消息产品中间件自不必说,大家熟知的HTTP/2都是基于异步消息通讯的。
那为何要异步化?我们知道消息处理的模型和同步服务调用是不一样的,如消息处理的Event Loop和Actor模型,都是非常高效的消息处理方式,如果我们在消息处理的过程中,还要处理基于线程池的同步服务调用,势必会影响模型和性能。当然也不是完全不能有同步,只是尽量避免过多的服务同步调用的场景。
围绕异步化和消息的场景,Spring要做非常多的工作,我们都知道Java对异步化支持非常滞后。Java 1.8加入了CompletionStage接口,Java 9添加了Reactive Stream支持介入了Flow。大家一直关注的轻量级线程的Loom项目还在开发中,预计要到Java 17才能发布。
所以目前Java多数的项目项目都会选择Reactive框架,当然Spring团队开发了Reactor框架,增加了Reactor对Netty、Kafka等适配,从而保证从底层开始就是异步化,所以这也是起名为Spring Reactive的原因。
接下来就是大家都看到的场景,Spring Webflux、Spring Integration、Spring Data等众多项目都添加了对异步化的支持,所有的这些调整,就是要确保从底层开始就是全异步化的,你可以理解为5G的非独立组网和独立组网两种方式,只是Spring选择了Reactive这种独立组网、独立生态的方式,当然工作量也是非常大的。这其中就涉及了两个非常大的问题:数据库和分布式通讯。
目前大多数NoSQL产品都提供了异步化的接口,异步化访问NoSQL产品没有什么问题。虽然NoSQL发展很迅猛,但是还是没有撼动数据库的地位,我们都知道Java中数据库访问的规范是JDBC,但是JDBC是同步的接口,所以Spring启动了R2DBC项目,目的就是能够以Reactive异步化接口访问数据库,目前来说,在Spring框架下使用JDBC和R2DBC的体验几乎是一致的。
这里我和同学们同步两个消息:Spring 5.3已经内置R2DBC支持,也就是spring-r2dbc,和spring-jdbc并列。大家都喜欢使用的MySQL数据库,也推出了mariadb-r2dbc 1.0.0稳定版,Oracle也推出了R2DBC版本的驱动。当然Java中的DB框架,Spring JPA、MyBatis等都添加了对R2DBC的适配。Hibernate也推出了hibernate-reactive项目(基于Vert.x database),虽然是不是基于R2DBC的,但是也是全异步化的。
另外一个问题就是分布式通讯,这个在微服务架构中扮演中非常重要的作用。一直以来Spring主要支持HTTP,但是HTTP协议主要是针对浏览器设计的,在后端服务之间通讯优势并不明显,而且性能上也没有优势。当然基于HTTP/2异步消息通讯的gRPC是一个很好的选择,但是Spring一直没有内置gRPC支持,当然这对大多数开发者来说,这不是什么大问题,第三方的grpc-spring-boot-starter做的也非常好。
如果你关注Spring发布的话应该知道,Spring 5.2后续版本选择RSocket通讯协议,并将RSocket内置到spring-message项目中。为何?RSocket是全异步的二进制消息通讯协议,提供了完备的通讯模型,而且是对等通讯的,非常匹配事件驱动架构,这也是Spring Reactive将RSocket纳入到Spring核心的原因,当然RSocket背后的开发,Spring和阿里团队起着主导作用。
CNCF下有一个CloudEvents规范,主要解决异构系统的事件通讯,消息和事件在数据结构上是一致的,都是Headers和Payload(data)结构,同时包含消息头的扩展。最新的cloudevents-java SDK 2.1,Spring messaging添加了对CloudEvents的支持,同时支持CloudEvent接口的编解码支持,将消息和事件进行了统一和整合,在Spring中处理CloudEvents和消息更简单。
和企业集成相关产品,如Apache Camel, 基于Akka的Alpakk、MuleSoft的Mule 4、Spring Integration等,这些产品都提供了对Reactive的集成,可以说可以和Spring Reactive无缝对接。
五 总结
回到文章的问题,为什么Spring 仍然会是云原生时代最佳平台之一?总结下来,Java和JDK的开发处以非常好的发展,JVM语言良性发展且充满竞争。成熟的Spring Boot和Spring Cloud已经让面向服务的架构设计简单易用,而Spring Reactive则将事件驱动架构更易被使用,同时可以让更多的企业系统完成和Spring Reacitve对接,无论是IoT设备、ESB产品、SaaS或者云服务等。
针对微服务的场景,Spring Cloud提供了成熟的面向架构服务基础,Spring Reactive则是面向未来的事件驱动架构,当然这里并不是说,面向服务的架构已经过时,事实上面向服务的架构非常成功,但是在云原生或新的Serverless环境下,可能事件驱动架构可能更合理一些,或者是两者的配合使用,不用担心,Spring在这两方面都做的非常好。
原文链接
本文为阿里云原创内容,未经允许不得转载。
为什么Spring仍然会是云原生时代最佳平台之一?的更多相关文章
- 进击的 Java ,云原生时代的蜕变
作者| 易立 阿里云资深技术专家 导读:云原生时代的来临,与Java 开发者到底有什么联系?有人说,云原生压根不是为了 Java 存在的.然而,本文的作者却认为云原生时代,Java 依然可以胜任&qu ...
- [转帖]从 SOA 到微服务,企业分布式应用架构在云原生时代如何重塑?
从 SOA 到微服务,企业分布式应用架构在云原生时代如何重塑? 2019-10-08 10:26:28 阿里云云栖社区 阅读数 54 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权 ...
- 云原生时代,Java的危与机(周志明)
说明 本篇文章是转载自周志明老师的文章,链接地址:https://www.infoq.cn/article/RQfWw2R2ZpYQiOlc1WBE 今天,25 岁的 Java 仍然是最具有统治力的编 ...
- 云原生时代的Java
原文链接(作者:周志明):https://time.geekbang.org/column/article/321185 公开课链接:https://time.geekbang.org/opencou ...
- 云原生时代的DevOps平台设计之道
开发人员与运维人员是 IT 领域很重要的两大人群,他们都会参与到各种业务系统的建设过程中去.DevOps 是近年间火爆起来的一种新理念,这种理念被很多人错误的解读为"由开发人员(Dev)学习 ...
- 阿里云弹性容器实例产品 ECI ——云原生时代的基础设施
阿里云弹性容器实例产品 ECI ——云原生时代的基础设施 1. 什么是 ECI 弹性容器实例 ECI (Elastic Container Instance) 是阿里云在云原生时代为用户提供的基础计算 ...
- 进击的.NET 在云原生时代的蜕变
你一定看过这篇文章 <进击的 Java ,云原生时代的蜕变>, 本篇文章的灵感来自于这篇文章.明天就将正式发布.NET Core 3.0, 所以写下这篇文章让大家全面认识.NET Cor ...
- 开放下载 | 《Knative 云原生应用开发指南》开启云原生时代 Serverless 之门
点击下载<Knative 云原生应用开发指南> 自 2018 年 Knative 项目开源后,就得到了广大开发者的密切关注.Knative 在 Kubernetes 之上提供了一套完整的应 ...
- .NET 在云原生时代的蜕变,让我在云时代脱颖而出
.NET 生态系统是一个不断变化的生态圈,我相信它正在朝着一个伟大的方向发展.有了开源和跨平台这两个关键优先事项,我们就可以放心了.云原生对应用运行时的不同需求,说明一个.NET Core 在云原生时 ...
- 【转】.NET 在云原生时代的蜕变,让我在云时代脱颖而出
原创:张善友 原文:https://www.cnblogs.com/shanyou/p/12198741.html .NET 生态系统是一个不断变化的生态圈,我相信它正在朝着一个伟大的方向发展.有了开 ...
随机推荐
- AOSP编译成功后关闭终端emulator命令找不到
当我们编译好AOSP系统源码后,可以通过emulator命令打开模拟器,但是当我们关闭终端后,在次打开终端输入emulator命令,提示未找到命令: 此时我们需要重新执行下面语句 source bui ...
- 从静态到动态化,Python数据可视化中的Matplotlib和Seaborn
本文分享自华为云社区<Python数据可视化大揭秘:Matplotlib和Seaborn高效应用指南>,作者: 柠檬味拥抱. 安装Matplotlib和Seaborn 首先,确保你已经安装 ...
- C#中字典集合的两种遍历
记录一下 Dictionary<string, string> dictionary = new Dictionary<string,string>(); foreach (s ...
- vue项目中添加水印效果
新建js文件:例如warterMark.js 'use strict' let watermark = {} let setWatermark = (str) => { let id = '1. ...
- KingbaseESV8R6表空间与数据库,模式,表的关系
自定义表空间的作用 使用多个表空间可以更灵活地执行数据库操作.当数据库具有多个表空间时,您可以: 1.将用户数据与系统表数据分开存储在不同性能的存储上,以减少I/O争用. 2.将一个应用程序的数据与另 ...
- KingbaseES V8R3 集群运维系列 -- sync_flag参数配置
案例说明: 在KingbaseES V8R3集群一主二备的架构中,配置了流复制为同步(sync)模式,但是集群启动后,流复制状态中显示备库是async模式(备库和主库数据已经同步),从备库的rec ...
- MySQL面试必备一之索引
本文首发于公众号:Hunter后端 原文链接:MySQL面试必备一之索引 在面试过程中,会有一些关于 MySQL 索引相关的问题,以下总结了一些: MySQL 的数据存储使用的是什么索引结构 B+ 树 ...
- 学习 Tensorflow 的困境与解药
我构建的预测模型 在过去的一段时间里我抓去了小宇宙内上万条播客节目的首日播放量的数据,并利用这些数据构建了一个用于预测播客节目播放量的模型.包含以下六个输入参数: 节目发布于一周中的哪一天 节目发布于 ...
- 白话分解入门操作系统到 Java
一.完成一个任务需要什么? 时间 + 资源 + 处理能力 时间就是时间. 资源就是资源. 处理能力就是能够利用时间和资源完成任务的主体. 二.关于操作系统 处理能力就是cpu. 资源就是存储. 时间就 ...
- #排序,去重#洛谷 5682 [CSPJX2019]次大值
题目 分析 首先,显然要排序去重,考虑最大值应该是\(a_{n-1}\)(排序后) 那次大值只有可能出现在\(a_{n-2}\)或者\(a_n\bmod a_{n-1}\)中 当然去重后如果只有一个数 ...