當前位置: 妍妍網 > 碼農

只會用 Spring Boot 建立微服務?這 4 種替代方案絕了!

2024-02-27碼農

點選關註公眾號,Java幹貨 及時送達 👇

前言

在 Java 和 Kotlin 中, 除了使用Spring Boot建立微服務外,還有很多其他的替代方案。

圖片

本文,基於這些微服務框架,建立了五個服務,並使用Consul的服務發現模式實作服務間的 相互通訊。因此,它們形成了異構微服務架構( Heterogeneous Microservice Architecture , 以下簡稱 MSA):

圖片

本文簡要考慮了微服務在各個框架上的實作(更多細節請檢視原始碼: https : //github.com/rkudryashov/heterogeneous-microservices

  • 技術棧:

  • JDK 13

  • Kotlin

  • Gradle (Kotlin DSL)

  • JUnit 5

  • 功能介面(HTTP API):

  • GET /application-info{?request-to=some-service-name}

  • GET /application-info/logo

  • 實作方式:

  • 使用文字檔案的配置方式

  • 使用依賴註入

  • HTTP API

  • MSA:

  • 使用服務發現模式(在Consul中註冊,透過客戶端負載均衡的名稱請求另一個微服務的HTTP API)

  • 構建一個 uber-JAR

  • 先決條件

  • JDK 13

  • Consul

  • 從頭開始建立應用程式

    要基於其中一個框架上生成新計畫,你可以使用web starter 或其他選項(例如,構建工具或 IDE):

    圖片

    Helidon服務

    該框架是在 Oracle 中建立以供內部使用,隨後成為開源。Helidon 非常簡單和快捷,它提供了兩個版本:標準版(SE)和MicroProfile(MP)。在這兩種情況下,服務都是一個常規的 Java SE 程式。(在Helidon上了解更多資訊)

    Helidon MP 是 Eclipse MicroProfile 的實作之一,這使得使用許多 API 成為可能,包括 Java EE 開發人員已知的(例如 JAX-RS、CDI等)和新的 API(健康檢查、指標、容錯等)。在 Helidon SE 模型中,開發人員遵循「沒有魔法」的原則,例如,建立應用程式所需的註解數量較少或完全沒有。

    Helidon SE 被選中用於微服務的開發。因為Helidon SE 缺乏依賴註入的手段,因此為此使用了Koin。

    以下程式碼範例,是包含 main 方法的類。為了實作依賴註入,該類繼承自 KoinComponent

    首先,Koin 啟動,然後初始化所需的依賴並呼叫 startServer() 方法—-其中建立了一個WebServer型別的物件,應用程式配置和路由設定傳遞到該物件;

    啟動應用程式後在Consul註冊:

    object HelidonServiceApplication : KoinComponent {
    @JvmStatic
    fun main(args: Array<String>) {
    val startTime = System.currentTimeMillis()
    startKoin {
    modules(koinModule)
    }
    val applicationInfoService: ApplicationInfoService by inject()
    val consulClient: Consul by inject()
    val applicationInfoProperties: ApplicationInfoProperties by inject()
    val serviceName = applicationInfoProperties.name
    startServer(applicationInfoService, consulClient, serviceName, startTime)
    }
    }
    fun startServer(
    applicationInfoService: ApplicationInfoService,
    consulClient: Consul,
    serviceName: String,
    startTime: Long
    ): WebServer {
    val serverConfig = ServerConfiguration.create(Config.create().get("webserver"))
    val server: WebServer = WebServer
    .builder(createRouting(applicationInfoService))
    .config(serverConfig)
    .build()
    server.start().thenAccept { ws ->
    val durationInMillis = System.currentTimeMillis() - startTime
    log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
    // register in Consul
    consulClient.agentClient().register(createConsulRegistration(serviceName, ws.port()))
    }
    return server
    }




    路由配置如下:

    private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder()
    .register(JacksonSupport.create())
    .get("/application-info", Handler { req, res ->
    val requestTo: String? = req.queryParams()
    .first("request-to")
    .orElse(null)
    res
    .status(Http.ResponseStatus.create(200))
    .send(applicationInfoService.get(requestTo))
    })
    .get("/application-info/logo", Handler { req, res ->
    res.headers().contentType(MediaType.create("image""png"))
    res
    .status(Http.ResponseStatus.create(200))
    .send(applicationInfoService.getLogo())
    })
    .error(Exception:: class.java) { req, res, ex ->
    log.error("Exception:", ex)
    res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send()
    }
    .build()

    該應用程式使用HOCON格式的配置檔:

    webserver {
    port: 8081
    }
    application-info {
    name: "helidon-service"
    framework {
    name: "Helidon SE"
    release-year: 2019
    }
    }

    還可以使用 JSON、YAML 和properties 格式的檔進行配置(在Helidon 配置文件中了解更多資訊)。

    Ktor服務

    該框架是為 Kotlin 編寫和設計的。和 Helidon SE 一樣,Ktor 沒有開箱即用的 DI,所以在啟動伺服器依賴項之前應該使用 Koin 註入:

    val koinModule = module {
    single { ApplicationInfoService(get(), get()) }
    single { ApplicationInfoProperties() }
    single { ServiceClient(get()) }
    single { Consul.builder().withUrl("https://localhost:8500").build() }
    }
    fun main(args: Array<String>) {
    startKoin {
    modules(koinModule)
    }
    val server = embeddedServer(Netty, commandLineEnvironment(args))
    server.start(wait = true)
    }

    應用程式需要的模組在配置檔中指定(HOCON格式;更多配置資訊參考Ktor配置文件 ),其內容如下:

    ktor {
    deployment {
    host = localhost
    port = 8082
    environment = prod
    // for dev purpose
    autoreload = true
    watch = [io.heterogeneousmicroservices.ktorservice]
    }
    application {
    modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module]
    }
    }
    application-info {
    name: "ktor-service"
    framework {
    name: "Ktor"
    release-year: 2018
    }
    }

    在 Ktor 和 Koin 中,術語「模組」具有不同的含義。

    在 Koin 中,模組類似於 Spring 框架中的應用程式上下文。Ktor的模組是一個使用者定義的函式,它接受一個 Application 型別的物件,可以配置流水線、註冊路由、處理請求等:

    fun Application.module() {
    val applicationInfoService: ApplicationInfoService by inject()
    if (!isTest()) {
    val consulClient: Consul by inject()
    registerInConsul(applicationInfoService.get(null).name, consulClient)
    }
    install(DefaultHeaders)
    install(Compression)
    install(CallLogging)
    install(ContentNegotiation) {
    jackson {}
    }
    routing {
    route("application-info") {
    get {
    val requestTo: String? = call.parameters["request-to"]
    call.respond(applicationInfoService.get(requestTo))
    }
    static {
    resource("/logo""logo.png")
    }
    }
    }
    }

    此程式碼是配置請求的路由,特別是靜態資源logo.png。

    下面是基於Round-robin演算法結合客戶端負載均衡實作服務發現模式的程式碼:

    class ConsulFeature(private val consulClient: Consul) {
    class Config {
    lateinit var consulClient: Consul
    }
    companion object Feature : HttpClientFeature<Config, ConsulFeature> {
    var serviceInstanceIndex: Int = 0
    override val key = AttributeKey<ConsulFeature>("ConsulFeature")
    override fun prepare(block: Config.() -> Unit) = ConsulFeature(Config().apply(block).consulClient)
    override fun install(feature: ConsulFeature, scope: HttpClient) {
    scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
    val serviceName = context.url.host
    val serviceInstances =
    feature.consulClient.healthClient().getHealthyServiceInstances(serviceName).response
    val selectedInstance = serviceInstances[serviceInstanceIndex]
    context.url.apply {
    host = selectedInstance.service.address
    port = selectedInstance.service.port
    }
    serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
    }
    }
    }
    }



    主要邏輯在install方法中:在Render請求階段(在Send階段之前執行)首先確定被呼叫服務的名稱,然後 consulClient 請求服務的例項列表,然後透過迴圈演算法定義一個例項正在呼叫。因此,以下呼叫成為可能:

    fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking {
    httpClient.get<ApplicationInfo>("http://$serviceName/application-info")
    }

    Micronaut 服務

    Micronaut 由Grails框架的建立者開發,靈感來自使用 Spring、Spring Boot 和 Grails 構建服務的經驗。該框架目前支持 Java、Kotlin 和 Groovy 語言。依賴是在編譯時註入的,與 Spring Boot 相比,這會導致更少的記憶體消耗和更快的應用程式啟動。

    主類如下所示:

    object MicronautServiceApplication {
    @JvmStatic
    fun main(args: Array<String>) {
    Micronaut.build()
    .packages("io.heterogeneousmicroservices.micronautservice")
    .main class(MicronautServiceApplication.java class)
    .start()
    }
    }

    基於 Micronaut 的應用程式的某些元件與它們在 Spring Boot 應用程式中的對應元件類似,例如,以下是控制器程式碼:

    @Controller(
    value = "/application-info",
    consumes = [MediaType.APPLICATION_JSON],
    produces = [MediaType.APPLICATION_JSON]
    )
    class ApplicationInfoController(
    private val applicationInfoService: ApplicationInfoService
    ) {
    @Get
    fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
    @Get("/logo", produces = [MediaType.IMAGE_PNG])
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
    }

    Micronaut 中對 Kotlin 的支持建立在kapt編譯器外掛程式的基礎上(參考Micronaut Kotlin 指南了解更多詳細資訊)。

    構建指令碼配置如下:

    plugins {
    ...
    kotlin("kapt")
    ...
    }
    dependencies {
    kapt("io.micronaut:micronaut-inject-java:$micronautVersion")
    ...
    kaptTest("io.micronaut:micronaut-inject-java:$micronautVersion")
    ...
    }

    以下是配置檔的內容:

    micronaut:
    application:
    name: micronaut-service
    server:
    port: 8083
    consul:
    client:
    registration:
    enabled: true
    application-info:
    name: ${micronaut.application.name}
    framework:
    name: Micronaut
    release-year: 2018

    JSON、properties和 Groovy 檔格式也可用於配置(參考Micronaut 配置指南檢視更多詳細資訊)。

    Quarkus服務

    Quarkus是作為一種應對新部署環境和應用程式架構等挑戰的工具而引入的,在框架上編寫的應用程式將具有低記憶體消耗和更快的啟動時間。此外,對開發人員也很友好,例如,開箱即用的即時重新載入。

    Quarkus 應用程式目前沒有 main 方法,但也許未來會出現(GitHub 上的問題)。

    對於熟悉 Spring 或 Java EE 的人來說,Controller 看起來非常熟悉:

    @Path("/application-info")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    class ApplicationInfoResource(
    @Inject private val applicationInfoService: ApplicationInfoService
    ) {
    @GET
    fun get(@QueryParam("request-to") requestTo: String?): Response =
    Response.ok(applicationInfoService.get(requestTo)).build()
    @GET
    @Path("/logo")
    @Produces("image/png")
    fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
    }

    如你所見,bean 是透過@Inject註解註入的,對於註入的 bean,你可以指定一個範圍,例如:

    @ApplicationScoped
    class ApplicationInfoService(
    ...
    ) {
    ...
    }

    為其他服務建立 REST 介面,就像使用 JAX-RS 和 MicroProfile 建立介面一樣簡單:

    @ApplicationScoped
    @Path("/")
    interface ExternalServiceClient {
    @GET
    @Path("/application-info")
    @Produces("application/json")
    fun getApplicationInfo(): ApplicationInfo
    }
    @RegisterRestClient(baseUri = "http://helidon-service")
    interface HelidonServiceClient : ExternalServiceClient
    @RegisterRestClient(baseUri = "http://ktor-service")
    interface KtorServiceClient : ExternalServiceClient
    @RegisterRestClient(baseUri = "http://micronaut-service")
    interface MicronautServiceClient : ExternalServiceClient
    @RegisterRestClient(baseUri = "http://quarkus-service")
    interface QuarkusServiceClient : ExternalServiceClient
    @RegisterRestClient(baseUri = "http://spring-boot-service")
    interface SpringBootServiceClient : ExternalServiceClient



    但是它現在缺乏對服務發現 ( Eureka和Consul ) 的內建支持,因為該框架主要針對雲環境。因此,在 Helidon 和 Ktor 服務中, 我使用了Java類別庫方式的Consul 客戶端。

    首先,需要註冊應用程式:

    @ApplicationScoped
    class ConsulRegistrationBean(
    @Inject private val consulClient: ConsulClient
    ) {
    fun onStart(@Observes event: StartupEvent) {
    consulClient.register()
    }
    }

    然後需要將服務的名稱解析到其特定位置;

    解析是透過從 Consul 客戶端獲得的服務的位置替換 requestContext 的URI 來實作的:

    @Provider
    @ApplicationScoped
    class ConsulFilter(
    @Inject private val consulClient: ConsulClient
    ) : ClientRequestFilter {
    override fun filter(requestContext: ClientRequestContext) {
    val serviceName = requestContext.uri.host
    val serviceInstance = consulClient.getServiceInstance(serviceName)
    val newUri: URI = URIBuilder(URI.create(requestContext.uri.toString()))
    .setHost(serviceInstance.address)
    .setPort(serviceInstance.port)
    .build()
    requestContext.uri = newUri
    }
    }

    Quarkus也支持透過 properties 或 YAML 檔進行配置(參考Quarkus 配置指南了解更多詳細資訊)。

    Spring Boot服務

    建立該框架是為了使用 Spring Framework 生態系,同時有利於簡化應用程式的開發。這是透過 auto-configuration 實作的。

    以下是控制器程式碼:

    @RestController
    @RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_VALUE])
    class ApplicationInfoController(
    private val applicationInfoService: ApplicationInfoService
    ) {
    @GetMapping
    fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo)
    @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE])
    fun getLogo(): ByteArray = applicationInfoService.getLogo()
    }

    微服務由 YAML 檔配置:

    spring:
    application:
    name: spring-boot-service
    server:
    port: 8085
    application-info:
    name: ${spring.application.name}
    framework:
    name: Spring Boot
    release-year: 2014

    也可以使用properties檔進行配置(更多資訊參考Spring Boot 配置文件)。

    啟動微服務

    在啟動微服務之前,你需要安裝Consul和 啟動代理-例如,像這樣:consul agent -dev。

    你可以從以下位置啟動微服務:

    IDE中啟動微服務IntelliJ IDEA 的使用者可能會看到如下內容:

    圖片

    要啟動 Quarkus 服務,你需要啟動quarkusDev的Gradle 任務。

    console中啟動微服務在計畫的根資料夾中執行:

    java -jar helidon-service/build/libs/helidon-service-all.jar
    java -jar ktor-service/build/libs/ktor-service-all.jar
    java -jar micronaut-service/build/libs/micronaut-service-all.jar
    java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar
    java -jar spring-boot-service/build/libs/spring-boot-service.jar


    啟動所有微服務後,存取 http://localhost:8500/ui/dc1/services ,你將看到:

    圖片

    API測試

    以Helidon服務的API測試結果為例:

    GET http://localhost:8081/application-info

    {
    "name""helidon-service",
    "framework": {
    "name""Helidon SE",
    "releaseYear": 2019
    },
    "requestedService": null
    }

    GET http://localhost:8081/application-info?request-to=ktor-service

    {
    "name""helidon-service",
    "framework": {
    "name""Helidon SE",
    "releaseYear": 2019
    },
    "requestedService": {
    "name""ktor-service",
    "framework": {
    "name""Ktor",
    "releaseYear": 2018
    },
    "requestedService": null
    }
    }

    GET http://localhost:8081/application-info/logo返回logo資訊

    你可以使用Postman 、IntelliJ IDEA HTTP 客戶端 、瀏覽器或其他工具測試微服務的 API介面 。

    不同微服務框架對比

    不同微服務框架的新版本釋出後,下面的結果可能會有變化;你可以使用此GitHub計畫自行檢查最新的對比結果 。

    另外,微服務系列面試題和答案全部整理好了,微信搜尋互聯網架構師,在後台發送:面試,可以線上閱讀。

    程式大小

    為了保證設定應用程式的簡單性,構建指令碼中沒有排除傳遞依賴項,因此 Spring Boot 服務 uber-JAR 的大小大大超過了其他框架上的類似物的大小(因為使用 starters 不僅匯入了必要的依賴項;如果需要,可以透過排除指定依賴來減小大小):

    備註:什麽是 maven的uber-jar

    在maven的一些文件中我們會發現 「uber-jar」這個術語,許多人看到後感到困惑。其實在很多程式語言中會把super叫做uber (因為super可能是關鍵字), 這是上世紀80年代開始流行的,比如管superman叫uberman。所以uber-jar從字面上理解就是super-jar,這樣的jar不但包含自己程式碼中的 class ,也會包含一些第三方依賴的jar,也就是把自身的程式碼和其依賴的jar全打包在一個jar裏面了,所以就很形象的稱其為super-jar ,uber-jar來歷就是這樣的。

    圖片

    啟動時長

    每個應用程式的啟動時長都是不固定的:

    圖片

    值得註意的是,如果你將 Spring Boot 中不必要的依賴排除,並註意設定套用的啟動參數(例如,只掃描必要的包並使用 bean 的延遲初始化),那麽你可以顯著地減少啟動時間。

    記憶體使用情況

    對於每個微服務,確定了以下內容:

  • 透過-Xmx參數,指定微服務所需的堆記憶體大小

  • 透過負載測試服務健康的請求(能夠響應不同的請求)

  • 透過負載測試 50 個使用者 * 1000 個 的請求

  • 透過負載測試 500 個使用者 * 1000 個 的請求

  • 堆記憶體只是為應用程式分配的總記憶體的一部份。例如,如果要測量總體記憶體使用情況,可以參考本指南。

    對於負載測試,使用了Gatling和Scala指令碼 。

    1、負載生成器和被測試的服務在同一台機器上執行(Windows 10、3.2 GHz 四核處理器、24 GB RAM、SSD)。

    2、服務的埠在 Scala 指令碼中指定。

    3、透過負載測試意味著微服務已經響應了所有時間的所有請求。

    圖片

    需要註意的是,所有微服務都使用 Netty HTTP 伺服器。

    結論

    透過上文,我們所需的功能——一個帶有 HTTP API 的簡單服務和在 MSA 中執行的能力——在所有考慮的框架中都取得了成功。

    是時候開始盤點並考慮他們的利弊了。

    Helidon標準版

    優點

    建立的應用程式,只需要一個註釋(@JvmStatic)

    缺點

    開發所需的一些元件缺少開箱即用(例如,依賴註入和與服務發現伺服器的互動)

    Helidon MicroProfile

    微服務還沒有在這個框架上實作,所以這裏簡單說明一下。

    優點

    1、Eclipse MicroProfile 實作

    2、本質上,MicroProfile 是針對 MSA 最佳化的 Java EE。因此,首先你可以存取各種 Java EE API,包括專門為 MSA 開發的 API,其次,你可以將 MicroProfile 的實作更改為任何其他實作(例如:Open Liberty、WildFly Swarm 等)

    Ktor

    優點

    1、輕量級的允許你僅添加執行任務直接需要的那些功能

    2、套用參數所有參數的良好結果

    缺點

    1、依賴於Kotlin,即用其他語言開發可能是不可能的或不值得的

    2、微框架:參考Helidon SE

    3、目前最流行的兩種 Java 開發模型(Spring Boot/Micronaut)和 Java EE/MicroProfile)

    4、中沒有包含該框架,這會導致:

  • 難以尋找專家

  • 由於需要顯式配置所需的功能,因此與 Spring Boot 相比,執行任務的時間有所增加

  • Micronaut

    優點

    1、AOT如前所述,與 Spring Boot 上的模擬相比,AOT 可以減少應用程式的啟動時間和記憶體消耗

    2、類Spring開發模式有 Spring 框架經驗的程式設計師不會花太多時間來掌握這個框架

    3、Micronaut for Spring可以改變現有的Spring Boot應用程式的執行環境到Micronaut中(有限制)

    Quarkus

    優點

    1、Eclipse MicroProfile 的實作

    2、該框架為多種 Spring 技術提供了相容層:DI、 Web、Security、Data JPA

    Spring Boot

    優點

    1、平台成熟度和生態系對於大多數日常任務,Spring的編程範式已經有了解決方案,也是很多程式設計師習慣的方式。此外,starter和auto-configuration的概念簡化了開發

    2、專家多,文件詳細

    我想很多人都會同意 Spring 在不久的將來仍將是 Java/Kotlin開發領域領先的框架。

    缺點

  • 套用參數多且復雜但是,有些參數,如前所述,你可以自己最佳化。還有一個Spring Fu計畫的存在,該計畫正在積極開發中,使用它可以減少參數。

  • Helidon SE 和 Ktor 是 微框架,Spring Boot 和 Micronaut 是全棧框架,Quarkus 和 Helidon MP 是 MicroProfile 框架。微框架的功能有限,這會減慢開發速度。

    我不敢判斷這個或那個框架會不會在近期「大更新」,所以在我看來,目前最好繼續觀察,使用熟悉的框架解決工作問題。

    同時,如本文所示,新框架在應用程式參數設定方面贏得了 Spring Boot。如果這些參數中的任何一個對你的某個微服務至關重要,那麽也許值得關註。

    但是,我們不要忘記,Spring Boot 一是在不斷改進,二是它擁有龐大的生態系,並且有相當多的 Java 程式設計師熟悉它。此外,還有未涉及的其他框架:Vert.x、Javalin 等,也值得關註。

    原文:https://dzone.com/articles/not-only-spring-boot-a-review-of-alternatives

    譯文:https://www.kubernetes.org.cn/9526.html


    END


    看完本文有收獲?請轉發分享給更多人

    關註「Java編程鴨」,提升Java技能

    關註Java編程鴨微信公眾號,後台回復:碼農大禮包可以獲取最新整理的技術資料一份。涵蓋Java 框架學習、架構師學習等

    文章有幫助的話,在看,轉發吧。

    謝謝支持喲 (*^__^*)