秒杀springboot——未来轻量级高性能的Java云原生微服务框架来啦
引子
自2003年Rod、Juergen 和 Yann开发并发布Spring项目后,J2EE 迎来了新的开始。在 2013 年初Spring Boot 项目横空出世,Spring 的 Web 应用体系结构得以大大简化。在好未来的Java研发伙伴的实践中,越来越多的团队开始使用Springboot及SpringCloud。
那么除了Spring家族,还有没有更好的web开发框架呢?特别是在云时代和Docker化时代的当下,有没有更贴合技术发展方向的web框架呢?当然有!本文笔者就带大家走出低头走路的状态,抬头看天看未来。这就是秒杀Springboot,具有表征未来趋势的轻量级高性能Java云原生微服务框架light-4j!
light-4j简介
light-4j(官网地址:https://doc.networknt.com/)于2016年在GitHub(GitHub地址:https://github.com/networknt/light-4j)首发的框架。light-4j是一个快速、轻量级的云原生微服务框架,其主要设计目标是高吞吐量、低延迟和小内存占用。快速而小的内存占用,可以为企业降低成本,这也是其未来发展空间广阔的原因之一。
大家有木有想过这个框架为什么会叫light-4j呢?light有光的意思,也有闪电的意思,在这里意译为闪电,闪电就意味着很轻量级、有闪电般的速度,4j其实就是for java的简写,换言之框架基于Java SE (Java 8) 实现。
那么light-4j有多快呢?light-4j比最流行的微服务平台Spring Boot(嵌入式Tomcat的形式)快44倍,并且只使用其1 / 5的内存!惊不惊喜,意不意外?!我们可以从benchmark 的性能测试报告,来一探light-4j与 Spring Boot 及其他微服务平台的比较结果:
Framework | Language | Max Throughput | Avg Latency | Transfer |
Go FastHttp | Go | 1,396,685.83 | 99.98ms | 167.83MB |
Light-4j | Java | 1,344,512.65 | 2.36ms | 169.25MB |
ActFramework | Java | 945,429.13 | 2.22ms | 136.15MB |
Go Iris | Go | 828,035.66 | 5.77ms | 112.92MB |
Vertx | Java | 803,311.31 | 2.37ms | 98.06MB |
Node-uws | Node/C++ | 589,924.44 | 7.22ms | 28.69MB |
Dotnet | .Net | 486,216.93 | 2.93ms | 57.03MB |
Jooby/Undertow | Java | 362,018.07 | 3.95ms | 47.99MB |
SeedStack-Filter | Java | 343,416.33 | 4.41ms | 51.42MB |
Spring Boot Reactor | Java | 243,240.17 | 7.44ms | 17.86MB |
Pippo-Undertow | Java | 216,254.56 | 9.80ms | 31.35MB |
Spark | Java | 194,553.83 | 13.85ms | 32.47MB |
Pippo-Jetty | Java | 178,055.45 | 15.66ms | 26.83MB |
Play-Java | Java | 177,202.75 | 12.15ms | 21.80MB |
Go HttpRouter | Go | 171,852.31 | 14.12ms | 21.14MB |
Go Http | Go | 170,313.02 | 15.01ms | 20.95MB |
JFinal | Java | 139,467.87 | 11.89ms | 29.79MB |
Akka-Http | Java | 132,157.96 | 12.21ms | 19.54MB |
RatPack | Java | 124,700.70 | 13.45ms | 10.82MB |
Pippo-Tomcat | Java | 103,948.18 | 23.50ms | 15.29MB |
Bootique + Jetty/Jersey | Java | 65,072.20 | 39.08ms | 11.17MB |
SeedStack-Jersey2 | Java | 52,310.11 | 26.88ms | 11.87MB |
Baseio | Java | 50,361.98 | 22.20ms | 6.39MB |
NinjaFramework | Java | 47,956.43 | 55.76ms | 13.67MB |
Play 1 | Java | 44,491.87 | 10.73ms | 18.75MB |
Spring Boot Undertow | Java | 44,260.61 | 38.94ms | 6.42MB |
Nodejs Express | Node | 42,443.34 | 22.30ms | 9.31MB |
Dropwizard | Java | 33,819.90 | 98.78ms | 3.23MB |
Spring Boot Tomcat | Java | 33,086.22 | 82.93ms | 3.98MB |
Node-Loopback | Node | 32,091.95 | 34.42ms | 11.51MB |
Payra Micro | Java | 24,768.69 | 118.86ms | 3.50MB |
WildFly Swarm | Java | 21,541.07 | 59.77ms | 2.83MB |
除了Springboot,我们还可以将light-4j和其他 web 框架的对比,从结果看light-4j足以吊打 Spring 等各种框架!
light-4j的特性
light-4j提供了丰富的特性,包含启动/关闭钩子和各种中间件的插件架构。具体细节汇总如下:
1)集成了分布式OAuth2 JWT安全验证
2)基于OpenAPI规范进行请求和响应验证
3)收集测量指标并支持服务和客户端在控制台显示
4)全局运行时异常处理,如API异常及其他受检查异常
5)在日志输出前加密敏感数据,如:信用卡、SIN号等
6)为请求参数、请求头、BODY清除跨站攻击脚本
7)重要信息或整个请求/响应的审计
8)请求体支持各种类型的content-type
9)配置标准化响应码及响应消息
10)支持外部配置化Docker环境所有模块
11)来自其他域名的跨域处理支持
12)对外提供的服务限速处理服务发现与注册支持直连
13)Consul和Zookeeper客户端侧的发现和负载平衡
14)消除代理层与Light-OAuth2紧密集成并支持可跟踪性
light-4j采用设计和测试驱动开发的方式来提高生产率。light-4j设计了OpenAPI规范并从中生成服务,该规范也是在运行时驱动安全验证和请求验证的框架的一部分。为了实现高质量产品的测试驱动,light-4j采用单元/端到端测试存根的形式。
此外light-4j内置的DevOps流程支持持续集成到生产中,light-4j可以生成Dockerfile和DevOps支持文件,以支持文档化和持续集成到生产中。
light-4j的组成
和springcloud一样,light-4j也是多个组件构成。常见的有5种不同类型微服务的多个框架,分别是:
1)light-rest-4j是一个RESTful微服务框架,具有OpenAPI规范,用于代码生成、运行时安全性和验证。
2)light-graphql-4j是一个graphql微服务框架,支持从IDL和插件生成模式。
3)light-hybrid-4j是一个混合微服务框架,它利用了单体服务和微服务体系结构的优势。
4)light-eventuate是一个基于Kafka、事件源和CQRS的基于消息传递的微服务框架。
除了上述常见的框架,还有其他几款框架可供选型,分别汇总如下:
1)Light-tram-4j: 保证服务之间的事务性消息/命令/事件的传递
2)Light-eventuate-4j : 基于事件源和CQRS的最终一致性框架
3)Light-saga-4j: 跨多个微服务管理分布式事务的saga实现
4)Light-spring-boot : 在spring-boot 中利用light-4j中间件处理程序
5)Light-session-4j : 支持服务器集群的分布式会话管理器( Redis、Hazelcast、JDBC )
6)Light-spa-4j :一组用于单页应用程序的中间件处理程序。
light-4j也支持Web Server 和WebSocket,其中 Web Server用于提供静态内容和应用程序接口网络服务器 ,WebSocket 支持客户端对客户端或客户端对服务器通信的WebSocket服务。
目前light-4j建立比较完备的基础设施服务,现已集成Consul 、Kafka、Elasticsearch、InfluxDB 、Prometheus、Kubernetes、Openshift 等,并已经构建一些通用service,如:
1)Light-oauth2 :为微服务提供OAuth 2.0服务;
2)Light-portal :集成所有服务以管理服务生命周期的门户用户界面;
3)Light-router :配合外部客户端或未在Java 8及更高版本中实现的客户端;
4)Light-proxy :部署在遗留应用程序之前,以解决交叉问题;
5)Light-config-server :集中式配置和秘钥管理服务;
6)Light-tokenization :在将信息发送到公司网络之外之前,标记敏感信息。
light-4j提供的构建工具有:
<!--[if !supportLists]-->1)<!--[endif]-->Light-bot :基于微服务的DevOps代理,处理多个代码库和依赖关系;
<!--[if !supportLists]-->2)<!--[endif]-->Light-codegen :代码生成器;
3)Swagger-bundler: 一个实用程序,将多个Swagger规范文件合并到一个解析了外部引用的文件中;
4)Openapi-bundler: 一个实用程序,将多个OpenAPI规范文件合并到一个解析了外部引用的文件中。
为了丰富其生态系统,light-4j正在逐步支持多语言。目前对Java语系的支持最好,当前的所有开源框架都是用Java构建的, Nodejs框架也正在开发中。
light-4j的使用
使用light-4j前,需进行环境准备,主要依赖的环境有JDK 8 、Git、maven、Docker。
我们可以通过light-codegen生成一个工作项目。目前,它支持light-rest-4j, light-graphql-4j, light-hybrid-server-4j and light-hybrid-service-4j。在GitHub的项目中,light-codegen项目的README.md通过示例描述了使用生成器的四种方法,笔者建议使用light-codegen的命令行的形式生成代码。
我们可以克隆或下载light-example-4j项目,删除或重命名petstore 文件夹,并进入networknt,通过运行命令:
java -jar light-codegen/codegen-cli/target/codegen-cli.jar -f openapi -o light-example-4j/rest/openapi/petstore -m model-config/rest/openapi/petstore/1.0.0/openapi.json -c model-config/rest/openapi/petstore/1.0.0/config.json 来生产工程。
config.json描述了工程的生成配置文件
{
"name": "petstore",
"version": "3.0.1",
"groupId": "com.networknt",
"artifactId": "petstore",
"rootPackage": "com.networknt.petstore",
"handlerPackage":"com.networknt.petstore.handler",
"modelPackage":"com.networknt.petstore.model",
"overwriteHandler": true,
"overwriteHandlerTest": true,
"overwriteModel": true,
"httpPort": 8080,
"enableHttp": false,
"httpsPort": 8443,
"enableHttps": true,
"enableHttp2": true,
"enableRegistry": false,
"supportDb": false,
"supportH2ForTest": false,
"supportClient": false,
"dockerOrganization": "networknt"
}
工程生成完毕后,我们可以基于maven构建打包项目(暂不支持Gradle,后续会支持Gradle )。我们将目录切换到light-example-4j/rest/openapi/petstore下,在DOS界面运行命令:
mvn install exec:exec
即可启动服务,此时服务的访问端口是8443(详见配置中的 "httpsPort": 8443),同时支持HTTP和HTTPS协议。而服务器的配置信息可以在server.yml进行配置,server.yml常用的字段如下:
# This is the default binding address if the service is dockerized.
ip: 0.0.0.0
# Http port if enableHttp is true. It will be ignored if dynamicPort is true.
httpPort: 8080
# Enable HTTP should be false by default. It should be only used for testing with clients or tools that don't support https or very hard to import the certificate.
enableHttp: false
# Https port if enableHttps is true. It will be ignored if dynamicPort is true.
httpsPort: 8443
# Enable HTTPS should be true on official environment and most dev environments.
enableHttps: true
# Http/2 is enabled by default for better performance and it works with the client module
enableHttp2: true
# Keystore file name in config folder. KeystorePass is in secret.yml to access it.
keystoreName: tls/server.keystore
# Flag that indicate if two way TLS is enabled. Not recommended in docker container.
enableTwoWayTls: false
# Truststore file name in config folder. TruststorePass is in secret.yml to access it.
truststoreName: tls/server.truststore
# Unique service identifier. Used in service registration and discovery etc.
serviceId: com.networknt.petstore-1.0.1
# Flag to enable self service registration. This should be turned on on official test and production. And dyanmicPort should be enabled if any orchestration tool is used like Kubernetes.
enableRegistry: false
# Dynamic port is used in situation that multiple services will be deployed on the same host and normally
# you will have enableRegistry set to true so that other services can find the dynamic port service. When
# deployed to Kubernetes cluster, the Pod must be annotated as hostNetwork: true
dynamicPort: false
# Minimum port range. This define a range for the dynamic allocated ports so that it is easier to setup
# firewall rule to enable this range. Default 2400 to 2500 block has 100 port numbers and should be
# enough for most cases unless you are using a big bare metal box as Kubernetes node that can run 1000s pods
minPort: 2400
# Maximum port rang. The range can be customized to adopt your network security policy and can be increased or
# reduced to ease firewall rules.
maxPort: 2500
# environment tag that will be registered on consul to support multiple instances per env for testing.
# https://github.com/networknt/light-doc/blob/master/docs/content/design/env-segregation.md
# This tag should only be set for testing env, not production. The production certification process will enforce it.
# environment: test1
我们可以通过访问测试URL来验证服务是否启动成功,如通过命令:
curl -k https://localhost:8443/v1/pets/111
如果服务器启动成功,则服务器返回类似结果:
{"id":1,"name":"Jessica Right","tag":"pet"}
基于light-4j的业务代码编写
我们以常见的controller-service模式来看一下light-4j框架下代码的编写。
controller入口demo代码如下:
package com.networknt.database;
import com.networknt.server.HandlerProvider;
import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.util.Methods;
import com.networknt.info.ServerInfoGetHandler;
import com.networknt.database.handler.*;
public class PathHandlerProvider implements HandlerProvider {
@Override
public HttpHandler getHandler() {
return Handlers.routing()
.add(Methods.GET, "/v1/queries", new QueriesGetHandler())
.add(Methods.GET, "/v1/query", new QueryGetHandler())
.add(Methods.GET, "/v1/server/info", new ServerInfoGetHandler())
.add(Methods.GET, "/v1/updates", new UpdatesGetHandler())
;
}
}
我们可以看到,light-4j将URL的映射放到了HandlerProvider的接口实现类中集中配置。
那么多个路径对应的Handler是如何编写的呢?以“ .add(Methods.GET, "/v1/updates", new UpdatesGetHandler())”中的UpdatesGetHandler类为例,代码如下:
package com.networknt.database.handler;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HttpString;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringEscapeUtils;
public class UpdatesGetHandler implements HttpHandler {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
Map<String, Object> examples = new HashMap<>();
examples.put("application/json", StringEscapeUtils.unescapeHtml4("[ { randomNumber : 123, id : 123} ]"));
if(examples.size() > 0) {
exchange.getResponseHeaders().add(new HttpString("Content-Type"), "application/json");
exchange.getResponseSender().send((String)examples.get("application/json"));
} else {
exchange.endExchange();
}
}
}
那么如何操作数据库呢?我们以mongodb为例展示其操作方式。首先构建mongodb对应的配置类MongoConfig,代码如下:
package com.networknt.database.db;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class MongoConfig {
@JsonIgnore
String description;
String host;
String name;
public MongoConfig() {
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
mongodb 的DAO类需基于StartupHookProvider实现,代码如下:
package com.networknt.database.db;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;
import com.networknt.config.Config;
import com.networknt.server.StartupHookProvider;
public class MongoStartupHookProvider implements StartupHookProvider {
static String CONFIG_NAME = "mongo";
public static MongoDatabase db;
public void onStartup() {
System.out.println("MongoStartupHookProvider is called");
initDataSource();
System.out.println("MongoStartupHookProvider db = " + db);
}
static void initDataSource() {
MongoConfig config = (MongoConfig) Config.getInstance().getJsonObjectConfig(CONFIG_NAME, MongoConfig.class);
MongoClientOptions.Builder clientOptions = new MongoClientOptions.Builder();
clientOptions.minConnectionsPerHost(10);//minPoolSize
clientOptions.connectionsPerHost(50);//maxPoolSize
MongoClient mongoClient = new MongoClient(new ServerAddress(config.getHost()), clientOptions.build());
db = mongoClient.getDatabase(config.getName());
}
}