當業務操做爲耗時操做時,將會佔用Worker線程資源從而影響到其餘的請求的處理,也會影響到IO數據讀寫的效率javascript
當網絡IO讀寫相關操做耗時也將影響業務的執行效率css
/zoos/{id}java
/zoos/{id}/animalsgit
InterceptorRegistration#addPathPatterns("/foo/**", "/fo?/b*r/")github
InterceptorRegistration#excludePathPatterns("/bar/**", "/foo/bar")web
@Path
正則表達式@GET, @POST, @PUT, @DELETE算法
@Produces, @Consumesspring
@PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParamtypescript
@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 UnitsCachedRouteRegistryBenchmark.matchByCachedRouteRegistry 10 thrpt 10 1353.846 ± 26.633 ops/msCachedRouteRegistryBenchmark.matchByCachedRouteRegistry 20 thrpt 10 982.295 ± 26.771 ops/msCachedRouteRegistryBenchmark.matchByCachedRouteRegistry 50 thrpt 10 639.418 ± 22.458 ops/msCachedRouteRegistryBenchmark.matchByCachedRouteRegistry 100 thrpt 10 411.046 ± 5.647 ops/msCachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 10 thrpt 10 941.917 ± 33.079 ops/msCachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 20 thrpt 10 524.540 ± 18.628 ops/msCachedRouteRegistryBenchmark.matchByDefaultRouteRegistry 50 thrpt 10 224.370 ± 9.683 ops/msCachedRouteRegistryBenchmark.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等技術。
本文分享自微信公衆號 - OPPO互聯網技術(OPPO_tech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。