-
当业务操作为耗时操作时,将会占用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=.
2-4倍的提升。
-
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)算法
泊松分布, 5轮预热,每次测试10次迭代。
({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时,运行时进行匹配
-
按使用量付费
-
按需获取
-
快速弹性伸缩
-
事件驱动
-
状态非本地持久化
-
资源维护托管
快速弹性伸缩便是一个棘手的问题。
冷启动问题,Pod缩容到0之后,新的请求进来时需要尽快的去调度一个新的Pod提供服务。
-
启动速度本身足够的快
-
应用体积足够小(节省镜像拉取的时间)
-
资源占用少(更少的CPU,内存占用)
-
启动快
-
小体积:不依赖任何三方依赖
-
丰富的指标:IO线程,Biz线程池指标
-
无环境依赖:纯原生Java便可启动
-
支持JAX-RS
-
高性能:单Pod可以承载更多的并发请求,节省成本
-
动态Route:运行时动态修改Web容器中的Route,满足运行时特化需求。
-
协程支持:以更加轻量的方式运行Function,减少资源间的争抢。
-
Route隔离: 满足不同Function之间的隔离要求,避免一个Function影响其他Function。
-
资源计费:不同Function分别使用了多少资源。
-
更加精细化的Metrics:更精确,及时的指标,满足快速扩缩容需求。
作者简介
Norman OPPO高级后端工程师
专注云原生微服务领域,云原生框架,ServiceMesh,Serverless等技术。
如何识别并解决复杂的dcache问题
统一预估引擎的设计与实现
10分钟掌握Java性能分析诀窍
本文分享自微信公众号 – OPPO互联网技术(OPPO_tech)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
{{m.name}}
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/70167.html