当业务操作为耗时操作时,将会占用Worker线程资源从而影响到其他的请求的处理,也会影响到IO数据读写的效率
当网络IO读写相关操作耗时也将影响业务的执行效率
/zoos/{id}
/zoos/{id}/animals
InterceptorRegistration#addPathPatterns("/foo/**", "/fo?/b*r/")
InterceptorRegistration#excludePathPatterns("/bar/**", "/foo/bar")
@Path
@GET, @POST, @PUT, @DELETE
@Produces, @Consumes
@PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam
@DefaultValue
...
@RequestMapping
@RequestParam
@RequestHeader
@PathVariable
@CookieValue
@MatrixVariable
...
更加简洁:JAX-RS注解风格更加简洁,形式也更加统一,而Spring MVC的注解所有稍显冗长。
更加灵活:JAX-RS的注解并非只能用在Controller上,@Produces, @Consumes更是可以用在序列化反序列化扩展实现等各种地方。@DefaultValue注解也可以和其他注解搭配使用。而@RequestMapping将各种功能都揉在一个注解中,代码显得冗长且复杂。
更加通用:JAX-RS注解是标准的Java注解,可以在各种环境中使用,而类似@GetMapping, @PostMapping等注解都依赖Spring的@AliasFor注解,只能在Spring环境中使用。
<dependency>
<groupId>io.esastack</groupId>
<artifactId>restlight-starter</artifactId>
<version>0.1.1</version>
</dependency>
public class RestlightDemoApplication {
"/hello") (
public String hello() {
return "Hello Restlight!";
}
public static void main(String[] args) {
SpringApplication.run(RestlightDemoApplication.class, args);
}
}
wrk4.1.0
-server -Xms3072m -Xmx3072m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+PrintTenuringDistribution -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:logs/gc-${appName}-%t.log -XX:NumberOfGCLogFiles=20 -XX:GCLogFileSize=480M -XX:+UseGCLogFileRotation -XX:HeapDumpPath=.
HTTP1.1/HTTP2/H2C/HTTPS支持
SpringMVC 及 JAX-RS注解支持
线程调度:随意调度Controller在任意线程池中执行
增强的SPI能力:按照分组,标签,顺序等多种条件加载及过滤
自我保护:CPU过载保护,新建连接数限制
Spring Boot Actuator支持
全异步过滤器,拦截器,异常处理器支持
Jackson/Fastjson/Gson/Protobuf序列化支持:支持序列化协商及注解随意指定序列化方式
兼容不同运行环境:原生Java,Spring,Spring Boot环境均能支持
AccessLog
IP白名单
快速失败
Mock测试
...
云原生:快速启动、省资源、轻量级
高性能:持续不懈追求的目标 & 核心竞争力,基于高性能网络框架Netty实现
高扩展性:开放扩展点,满足业务多样化的需求
低接入成本:兼容SpringMVC 和 JAX-RS常用注解,降低用户使用成本
全链路异步:基于CompletableFuture提供完善的异步处理能力
监控与统计:完善的线程池等指标监控和请求链路追踪与统计
在ESA HttpServer基础之上封装了
引入业务线程池
Filter
请求路由(根据url, method, header等条件将请求路由到对应的Handler)
基于CompletableFuture的响应式编程支持
线程调度
…
<dependency>
<groupId>io.esastack</groupId>
<artifactId>restlight-server</artifactId>
<version>0.1.1</version>
</dependency>
Restlite.forServer()
.daemon(false)
.deployments()
.addRoute(route(get("/hello"))
.handle((request, response) ->
response.sendResult("Hello Restlight!".getBytes(StandardCharsets.UTF_8))))
.server()
.start();
HandlerInterceptor: 拦截器
ExceptionHandler: 全局异常处理器
BeanValidation: 参数校验
ArgumentResolver: 参数解析扩展
ReturnValueResolver: 返回值解析扩展
RequestSerializer: 请求序列化器(通常负责反序列化body内容)
ResposneSerializer: 响应序列化器(通常负责序列化响应对象到body)
内置Jackson, Fastjson, Gson, ProtoBuf序列化支持
…
<dependency>
<groupId>io.esastack</groupId>
<artifactId>restlight-core</artifactId>
<version>0.1.1</version>
</dependency>
<dependency>
<groupId>io.esastack</groupId>
<artifactId>restlight-jaxrs-provider</artifactId>
<version>0.1.1</version>
</dependency>
public class HelloController {
public String restlight() {
return "Hello Restlight!";
}
}
Restlight.forServer()
.daemon(false)
.deployments()
.addController(HelloController.class)
.server()
.start();
<dependency>
<groupId>io.esastack</groupId>
<artifactId>restlight-core</artifactId>
<version>0.1.1</version>
</dependency>
<dependency>
<groupId>io.esastack</groupId>
<artifactId>restlight-jaxrs-provider</artifactId>
<version>0.1.1</version>
</dependency>
public class HelloController {
public String restlight() {
return "Hello Restlight!";
}
}
Restlight.forServer()
.daemon(false)
.deployments()
.addController(HelloController.class)
.server()
.start();
Acceptor:由1个线程组成的线程池, 负责监听本地端口并分发IO 事件。
IO EventLoopGroup:由多个线程组成,负责读写IO数据(对应图中的read()和write())以及HTTP协议的编解码和分发到业务线程池的工作。
Biz Scheduler:负责执行真正的业务逻辑(大多为Controller中的业务处理,拦截器等)。
Custom Scheduler: 自定义线程池
public String list() {
return "Hello";
}
public String list() {
// ...
return "Hello";
}
public String list() {
// ...
return "Hello";
}
public Scheduler scheduler() {
// 注入自定义线程池
return Schedulers.fromExecutor("foo", Executors.newCachedThreadPool());
}
Epoll & NIO
ByteBuf
PooledByteBufAllocator
EventLoopGroup
Future & Promise
FastThreadLocal
InternalThreadLocalMap
Recycler
...
将收到的body数据转储到本地磁盘,释放内存资源,等需要使用的时候通过流的方式读取磁盘数据。
每收到一部分body数据都立马消费掉并释放这段内存。
HttpServer.create()
.handle(req -> {
req.onData(buf -> {
// 每收到一部分的body数据都将调用此逻辑
System.out.println(buf.toString(StandardCharsets.UTF_8));
});
req.onEnd(p -> {
// 写响应
req.response()
.setStatus(200)
.end("Hello ESA Http Server!".getBytes(StandardCharsets.UTF_8));
return p.setSuccess(null);
});
})
.listen(8080)
.awaitUninterruptibly();
HttpServer.create()
.handle(req -> {
// 设置期望聚合所有的body体
req.aggregate(true);
req.onEnd(p -> {
// 获取聚合后的body
System.out.println(req.aggregated().body().toString(StandardCharsets.UTF_8));
// 写响应
req.response()
.setStatus(200)
.end("Hello ESA Http Server!".getBytes());
return p.setSuccess(null);
});
})
.listen(8080)
.awaitUninterruptibly();
HttpServer.create()
.handle(req -> {
req.onData(buf -> {
// 每收到一部分的body数据都将调用此逻辑
System.out.println(buf.toString(StandardCharsets.UTF_8));
});
req.onEnd(p -> {
req.response().setStatus(200);
// 写第一段响应body
req.response().write("Hello".getBytes(StandardCharsets.UTF_8));
// 写第二段响应body
req.response().write(" ESA Http Server!".getBytes(StandardCharsets.UTF_8));
// 结束请求
req.response().end();
return p.setSuccess(null);
});
})
.listen(8080)
.awaitUninterruptibly();
wrk4.1.0
-server -Xms3072m -Xmx3072m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:+PrintTenuringDistribution -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:logs/gc-${appName}-%t.log -XX:NumberOfGCLogFiles=20 -XX:GCLogFileSize=480M -XX:+UseGCLogFileRotation -XX:HeapDumpPath=.
二八原则(80%的业务由20%的接口处理)
算法:类LFU(Least Frequently Used)算法
({Mode.Throughput})
(TimeUnit.MILLISECONDS)
5) (iterations =
10) (iterations =
(Threads.MAX)
1) (
(Scope.Benchmark)
public class CachedRouteRegistryBenchmark {
private ReadOnlyRouteRegistry cache;
private ReadOnlyRouteRegistry noCache;
"10", "20", "50", "100"}) ({
private int routes = 100;
private AsyncRequest[] requests;
private double lambda;
public void setUp() {
RouteRegistry cache = new CachedRouteRegistry(1);
RouteRegistry noCache = new SimpleRouteRegistry();
Mapping[] mappings = new Mapping[routes];
for (int i = 0; i < routes; i++) {
HttpMethod method = HttpMethod.values()[ThreadLocalRandom.current().nextInt(HttpMethod.values().length)];
final MappingImpl mapping = Mapping.mapping("/f?o/b*r/**/??x" + i)
.method(method)
.hasParam("a" + i)
.hasParam("b" + i, "1")
.hasHeader("c" + i)
.hasHeader("d" + i, "1")
.consumes(MediaType.APPLICATION_JSON)
.produces(MediaType.TEXT_PLAIN);
mappings[i] = mapping;
}
for (Mapping m : mappings) {
Route route = Route.route(m);
cache.registerRoute(route);
noCache.registerRoute(route);
}
requests = new AsyncRequest[routes];
for (int i = 0; i < requests.length; i++) {
requests[i] = MockAsyncRequest.aMockRequest()
.withMethod(mappings[i].method()[0].name())
.withUri("/foo/bar/baz/qux" + i)
.withParameter("a" + i, "a")
.withParameter("b" + i, "1")
.withHeader("c" + i, "c")
.withHeader("d" + i, "1")
.withHeader(HttpHeaderNames.CONTENT_TYPE.toString(), MediaType.APPLICATION_JSON.value())
.withHeader(HttpHeaderNames.ACCEPT.toString(), MediaType.TEXT_PLAIN.value())
.build();
}
this.cache = cache.toReadOnly();
this.noCache = noCache.toReadOnly();
this.lambda = (double) routes / 2;
}
public Route matchByCachedRouteRegistry() {
return cache.route(getRequest());
}
public Route matchByDefaultRouteRegistry() {
return noCache.route(getRequest());
}
private AsyncRequest getRequest() {
return requests[getPossionVariable(lambda, routes - 1)];
}
private static int getPossionVariable(double lambda, int max) {
int x = 0;
double y = Math.random(), cdf = getPossionProbability(x, lambda);
while (cdf < y) {
x++;
cdf += getPossionProbability(x, lambda);
}
return Math.min(x, max);
}
private static double getPossionProbability(int k, double lamda) {
double c = Math.exp(-lamda), sum = 1;
for (int i = 1; i <= k; i++) {
sum *= lamda / i;
}
return sum * c;
}
}
Benchmark (routes) Mode Cnt Score Error Units
CachedRouteRegistryBenchmark.matchByCachedRouteRegistry 10 thrpt 10 1353.846 ± 26.633 ops/ms
CachedRouteRegistryBenchmark.matchByCachedRouteRegistry 20 thrpt 10 982.295 ± 26.771 ops/ms
CachedRouteRegistryBenchmark.matchByCachedRouteRegistry 50 thrpt 10 639.418 ± 22.458 ops/ms
CachedRouteRegistryBenchmark.matchByCachedRouteRegistry 100 thrpt 10 411.046 ± 5.647 ops/ms
CachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 10 thrpt 10 941.917 ± 33.079 ops/ms
CachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 20 thrpt 10 524.540 ± 18.628 ops/ms
CachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 50 thrpt 10 224.370 ± 9.683 ops/ms
CachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 100 thrpt 10 113.883 ± 5.847 ops/ms
public RouteInterceptor interceptor() {
return new RouteInterceptor() {
public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
AsyncResponse response,
Object handler) {
// biz logic
return CompletableFuture.completedFuture(null);
}
public boolean match(DeployContext<? extends RestlightOptions> ctx, Route route) {
HttpMethod[] method = route.mapping().method();
return method.length == 1 && method[0] == HttpMethod.GET;
}
};
}
public MappingInterceptor interceptor() {
return new MappingInterceptor() {
public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
AsyncResponse response,
Object handler) {
// biz logic
return CompletableFuture.completedFuture(null);
}
public boolean test(AsyncRequest request) {
return request.containsHeader("X-Foo");
}
};
}
includes(): 指定拦截器作用范围的Path, 默认作用于所有请求。
excludes(): 指定拦截器排除的Path(优先级高于includes)默认为空。
public HandlerInterceptor interceptor() {
return new HandlerInterceptor() {
public CompletableFuture<Boolean> preHandle0(AsyncRequest request,
AsyncResponse response,
Object handler) {
// biz logic
return CompletableFuture.completedFuture(null);
}
public String[] includes() {
return new String[] {"/foo/**"};
}
public String[] excludes() {
return new String[] {"/foo/bar"};
}
};
}
includes("/foo/**")
includes("/foo/b?r")
includes("/foo/b*")
includes("/f?o/b*r")
excludes("/foo/**")
拦截器的includes()和excludes()规则一定会匹配到controller时,则在初始化阶段便直接和controller绑定,运行时不进行任何匹配操作。
拦截器的includes()和excludes()规则一定不会匹配到controller时,则在初始化阶段便直接忽略,运行时不进行任何匹配操作。
拦截器的includes()和excludes()可能会匹配到controller时,运行时进行匹配
按使用量付费
按需获取
快速弹性伸缩
事件驱动
状态非本地持久化
资源维护托管
启动速度本身足够的快
应用体积足够小(节省镜像拉取的时间)
资源占用少(更少的CPU,内存占用)
启动快
小体积:不依赖任何三方依赖
丰富的指标:IO线程,Biz线程池指标
无环境依赖:纯原生Java便可启动
支持JAX-RS
高性能:单Pod可以承载更多的并发请求,节省成本
动态Route:运行时动态修改Web容器中的Route,满足运行时特化需求。
协程支持:以更加轻量的方式运行Function,减少资源间的争抢。
Route隔离: 满足不同Function之间的隔离要求,避免一个Function影响其他Function。
资源计费:不同Function分别使用了多少资源。
更加精细化的Metrics:更精确,及时的指标,满足快速扩缩容需求。
作者简介
Norman OPPO高级后端工程师
专注云原生微服务领域,云原生框架,ServiceMesh,Serverless等技术。