
当前不乏使用Java和Kotlin创建微服务的框架。 本文讨论了以下内容:
基于它们,已经创建了四个服务,可以使用通过
Consul实现的服务发现模式通过HTTP API相互交互。 因此,它们形成了异构的(在框架级别)微服务架构(以下称为ISA):

为每种服务定义一组要求:
- 技术栈:
- JDK 12;
- 科特林
- Gradle(Kotlin DSL);
- JUnit 5。
- 功能(HTTP API):
GET /application-info{?request-to=some-service-name}
返回有关微服务的一些基本信息(名称,框架,框架的发布年份); 当在其HTTP API的request-to
参数中指定四个微服务之一的名称时,将执行类似的请求,该请求返回基本信息;GET /application-info/logo
返回图像。
- 实施:
- 使用配置文件进行设置;
- 使用依赖注入
- 验证HTTP API功能的测试。
- ISA:
- 使用服务发现模式(向Consul注册,使用客户端负载平衡按名称访问另一个微服务的HTTP API);
- uber-jar神器形成。
接下来,我们考虑在每个框架上实现微服务,并比较接收到的应用程序的参数。
直升机停机坪服务
开发框架是在Oracle上创建的,供内部使用,随后成为开源。 基于此框架的开发模型有两种:标准版(SE)和MicroProfile(MP)。 在这两种情况下,该服务都是常规的Java SE程序。 在
此页面上了解有关差异的更多信息。
简而言之,Helidon MP是Eclipse
MicroProfile的实现之一,这使得可以使用Java EE开发人员先前已知的许多API(例如JAX-RS,CDI)和较新的API(运行状况检查,指标,容错)等)。 在Helidon SE变体中,开发人员遵循“没有魔术”的原则,尤其是用创建应用程序所需的注释更少或没有注释的原则来表达。
Helidon SE被选择用于微服务开发。 除其他外,它缺少用于实现依赖注入的工具,因此使用
Koin来实现依赖。 以下是包含main方法的类。 为了实现依赖注入,该类继承自
KoinComponent 。 Koin首先启动,然后初始化所需的依赖关系,并
startServer()
方法,在该方法中创建
WebServer类型的对象,之前将应用程序配置和路由设置传输到该对象; 启动后,该应用程序将在领事馆注册:
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())
路由配置如下:
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和属性格式的文件进行配置(
此处有更多详细信息)。
Ktor服务
该框架是用Kotlin编写的。 可以通过几种方式创建一个新项目:使用构建系统,
start.ktor.io或IntelliJ IDEA插件(更多
信息请参见)。
像Helidon SE一样,Ktor也没有开箱即用的DI,因此在启动服务器之前使用Koin实现依赖项:
val koinModule = module { single { ApplicationInfoService(get(), get()) } single { ApplicationInfoProperties() } single { ServiceClient(get()) } single { Consul.builder().withUrl("http://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 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
。
Ktor服务可能包含功能。 功能是嵌入在请求-响应
管道中的功能 (
DefaultHeaders,Compression和上面的代码示例中的其他功能)。 可以实现自己的功能,例如,下面的代码结合基于轮循算法的客户端负载平衡来实现服务发现模式:
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
请求此服务的实例列表,然后使用Round-robin算法确定实例。 因此,以下调用变为可能:
fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking { httpClient.get<ApplicationInfo>("http://$serviceName/application-info") }
微航服务
Micronaut由
Grails框架的创建者开发,其灵感来自使用Spring,Spring Boot和Grails构建服务的经验。 该框架是一个多语言版本,支持Java,Kotlin和Groovy。
也许会有对Scala的支持。 依赖注入是在编译阶段执行的,与Spring Boot相比,这导致更少的内存消耗和更快的应用程序启动。
主类具有以下形式:
object MicronautServiceApplication { @JvmStatic fun main(args: Array<String>) { Micronaut.build() .packages("io.heterogeneousmicroservices.micronautservice") .mainClass(MicronautServiceApplication.javaClass) .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编译器
插件 (更多
信息 在此处 )。 汇编脚本的配置如下:
plugins { ... kotlin("kapt") ... } dependencies { kapt("io.micronaut:micronaut-inject-java") ... kaptTest("io.micronaut:micronaut-inject-java") ... }
以下是配置文件的内容:
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,属性和Groovy文件格式进行微服务配置(
此处有更多详细信息)。
春季启动服务
创建该框架是为了简化使用Spring Framework生态系统的应用程序开发。 这是通过连接库时的自动配置机制来实现的。 以下是控制器代码:
@RestController @RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_UTF8_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: 8084 application-info: name: ${spring.application.name} framework: name: Spring Boot release-year: 2014
也可以使用属性格式文件进行配置(
此处有更多详细信息)。
发射
该项目可在JDK 12上运行,尽管也可能在版本11上运行,您只需要相应地在汇编脚本中更改
jvmTarget
参数:
withType<KotlinCompile> { kotlinOptions { jvmTarget = "12" ... } }
在启动微服务之前,您需要
安装 Consul并
启动代理-例如,像这样:
consul agent -dev
。
可以从以下位置启动微服务:
在
http://localhost:8500/ui/dc1/services
上启动所有微
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
返回图像。
您可以使用
Postman (请求的
集合 ),IntelliJ IDEA
HTTP客户端 (请求的
集合 ),浏览器或其他工具来测试任意微服务API。 如果使用前两个客户端,则必须在相应的变量中指定被调用微服务的端口(在Postman中,它在
集合菜单-> Edit-> Variables中 ,在HTTP Client中,它在
此文件中指定的环境变量中),并且在测试方法2时) API还需要指定请求的“幕后”微服务的名称。 答案将与上面给出的答案相似。
应用程序设置比较
工件尺寸
为了保持在汇编脚本中设置和运行应用程序的简便性,未排除任何传递依赖项,因此Spring Boot上的uber-JAR服务的大小大大超过了其他框架上类似物的大小(因为使用启动器时,不仅导入了必需的依赖项;如果需要,可以大大减小尺寸):
发射时间
每个应用程序的启动时间不一致,并落入一些“窗口”中。 下表显示了工件的启动时间,但未指定任何其他参数:
值得注意的是,如果您从不需要的依赖项中“清除”了Spring Boot应用程序,并注意将其配置为启动(例如,仅扫描必要的程序包并使用惰性容器初始化),则可以大大减少启动时间。
负载测试
为了进行测试,使用了
加特林和Scala
脚本 。 负载生成器和被测服务在同一台计算机上运行(Windows 10,四核3.2 GHz处理器,24 GB RAM,SSD)。 该服务的端口在Scala脚本中指示。
对于每个微服务,确定:
- 运行正常(响应请求)微服务所需的最小堆内存(
-Xmx
) - 通过负载测试所需的最小堆内存50个用户* 1000个请求
- 通过负载测试所需的最小堆内存500个用户* 1000个请求
通过负载测试意味着微服务可以随时响应所有请求。
值得注意的是,所有微服务都使用Netty HTTP服务器。
结论
这项任务-使用HTTP API创建简单服务以及在ISA中运行的功能-可以在所有相关框架上完成。 现在是盘点并考虑其优点和缺点的时候了。
海利顿标准版- 加号
- 应用程序设置
在各方面均显示出良好的结果; - “没有魔术”
该框架证明了开发人员所陈述的原理:仅需一个注释即可创建应用程序( @JvmStatic
用于Java-Kotlin Interope)。
- 缺点
- 微框架
工业发展所需的某些组件是开箱即用的,例如,依赖注入和Service Discovery的实现。
微轮廓微服务不是在此框架上实现的,因此,我只想说明几点:
- 加号
- Eclipse MicroProfile实施
本质上,MicroProfile是针对ISA优化的Java EE。 因此,首先,您可以访问各种Java EE API,包括专门为ISA设计的Java EE API;其次,您可以将MicroProfile实现更改为任何其他实现(Open Liberty,WildFly Swarm等)。 。
- 另外
克托- 加号
- 轻盈
仅允许您连接完成任务直接需要的那些功能; - 应用程序设置
在各方面均取得良好结果。
- 缺点
- Kotlin领导下的“锐减”,也就是说,有可能(但不是必须)用Java开发;
- 微型框架(请参阅Helidon SE的类似项目)。
- 另外
一方面,该框架的开发概念未包含在两个最流行的Java开发模型(类似于Spring(Spring Boot / Micronaut)和Java EE / MicroProfile)中,这可能导致:
- 寻找专家的问题;
- 与Spring Boot相比,由于需要显式配置所需的功能,因此完成任务所需的时间更长。
另一方面,与“经典” Spring和Java EE的不同之处使您可以更自觉地从另一个角度看待开发过程。
微航海- 加号
- 奥特
如前所述,与Spring Boot上的应用程序相比,AOT可以减少应用程序的启动时间和内存消耗。 - 春季开发模式
具有在Spring上开发经验的程序员不会花很多时间来掌握这个框架。 - 应用程序设置
在各方面均取得良好结果; - 多种语言
对Java,Kotlin,Groovy的一流公民支持; 也许会有对Scala的支持。 我认为,这可以对社区发展产生积极影响。 顺便说一句,在2019年6月,Groovy在编程语言的流行性排名中, TIOBE从60年前的第60位升至第14位,在JVM语言中名列第二; - Micronaut for Spring项目还允许您将现有Spring Boot应用程序的运行时更改为Micronaut(有限制)。
春季靴- 加号
- 平台成熟度和生态系统
“每天”的框架。 对于大多数日常任务,Spring编程范例中已经有一种解决方案,即许多程序员都熟悉的一种解决方案。 启动器和自动配置的概念简化了开发过程; - 劳动力市场中有大量专家,以及重要的知识库(包括文档和Stack Overflow的答案);
- 观点
我认为许多人都会同意,在不久的将来,Spring将仍然是领先的开发框架。
- 缺点
- 应用程序设置
该框架上的应用程序不在领导者之列,但是,如前所述,某些参数可以独立优化。 还值得回顾的是Spring Fu项目的存在,该项目正在积极开发中,使用它可以减少这些参数。
您还可以突出显示与Spring Boot中缺少的与新框架相关的一般问题:
- 欠发达的生态系统;
- 少数具有这些技术经验的专家;
- 完成任务的时间更长;
- 前景不明朗。
所考虑的框架属于不同的权重类别:Helidon SE和Ktor是
微框架,Spring Boot是全栈框架,Micronaut更有可能也是全栈; 另一个类别是MicroProfile(例如Helidon MP)。 在微帧中,功能是有限的,这会减慢任务的执行; 为了阐明在任何开发框架的基础上实现此功能的可能性,我建议您熟悉其文档。
我不敢判断这个框架或那个框架是否会在不久的将来“解决”,因此,我认为,最好使用现有的开发框架来解决工作任务,继续监视事件的发展。
同时,如本文中所示,新框架在接收到的应用程序的参数方面胜过Spring Boot。 如果这些参数中的任何一个对于您的任何微服务都至关重要,那么您可能需要注意对它们显示出最佳结果的框架。 但是,请不要忘记,Spring Boot首先会继续改进,其次,它具有庞大的生态系统,并且许多Java程序员都熟悉它。 本文未涉及其他框架:Javalin,Quarkus等。
您可以在
GitHub上查看项目代码。 感谢您的关注!
PS:感谢
artglorin对本文的帮助。