Nesty 高性能轻量级Http Restful Server

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/GugeMichael/article/details/51075224

Nesty开源地址见 http://gugemichael.github.io/nesty/
Nesty开源地址见 http://gugemichael.github.io/nesty/
Nesty开源地址见 http://gugemichael.github.io/nesty/

1. 引言

在大型系统中,复杂的数据流和业务流程,系统模块数量又很多,导致系统间交互的复杂度也高,各种http、rpc混合调用。慢慢的开始流行前后端分离渲染交互逻辑 放到NodeJS去,使得大部分Java后端实现成 Restful的HTTP微服务接口即可

标准的web应用大部分是基于Tomcat、Jboss容器,通过SpringMVC框架即可嵌入业务逻辑。再在前面配置Nginx做负载均衡或转发实现Http服务。这样的流程标准化,利于开发和运维。不过也发现了一些弊端:
- 模板化的Tomcat、Jboss部署方式,对JVM的控制能力弱(例如想通过JVM修改GC算法,增加某些特性)
- Tomcat虽然有基于NIO的方式,但性能仍旧提升不大,QPS有限
- 不利于调试,本地测试需要启动容器,效率低

基于某些应用场景,大部分提供Http Restful的应用逻辑都不是特别复杂,访问缓存或数据库取结果,在做进一步代码逻辑和运算直接返回Json结果的。预计QPS可以1w以上。这样的场景逻辑很轻量级,但对JVM的可控性和性能的考虑较多。所以,想去尝试了一下其他非容器级别的Http框架。

2. 方案选择及设计思路

Tomcat的默认的IO是非NIO的模式,这种模式在业务逻辑复杂,页面渲染复杂的场景下,IO的模式(阻塞和非阻塞)区别不是很大,因为大部分性能消耗在业务逻辑中。但我们的场景是逻辑较少和直接转发,所以对IO的模式 尽量选择NIO。并且尽量脱离容器的方式 ,让开发人员能更多的控制上层JVM,做到非入侵

2.1 Jetty + Jersay

最开始我们想到了Jetty框架,他可以用非容器的方式,只是一个library级别。再配合Jersay完成类似SpringMVC的HTTP注解,使用方式也很简单:

  • 服务启动
ServletHolder sh = new ServletHolder(ServletContainer.class);
sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass", ResourceConfigure.class.getCanonicalName());
// Jersay RESTful 配置
sh.setInitParameter("com.sun.jersey.config.property.packages", "jetty");
sh.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
httpServer = new Server(8080);
httpServer.setSendServerVersion(false);
httpServer.setSendDateHeader(false);
httpServer.setStopAtShutdown(true);

Context context = new Context(httpServer, "/", Context.SESSIONS);
// 配置URL路径
context.addServlet(sh, "/*");

// 启动服务
httpServer.start();
  • Restful注解
public class TaskProvider extends ServiceBaseProvider {

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response create(Task task) {

        ............
        }

使用方式比很简单,也满足非容器的需求,调试方便,启动时间1~2s,足够轻量。但简单做了性能测试后,发现QPS依旧不高。机器配置24核,48G内存,千兆多队列网卡。约7k~8k QPS(Http短连接,4个ab并发128)。将涉及的各个Jetty参数设置了多遍,提升依旧不大。

2.2 NginxLua(OpenResty) or Golang or Node

出于性能考虑,我们参考了一下NginxLua和原生的Nginx,这种基于多进程epoll在非阻塞IO上性能完全可以满足,加之Lua脚本开发成本低。还参考了一下Golang和Node,Golang基于netpoll组件,底层也是基于epoll非阻塞加之协程的并发模式,满足需求。

但NginxLua和Golang是非Java系的,只能满足一些非核心的新应用可以从头开始编码,老应用里又有很多的HSF和Java库的调用,并且没有SpringMVC那种方便的HTTP注解,所以只能部分应用使用。老应用肯定不会迁移上去。

3. Nesty

出于上述的调研想法,我们试图寻找一个能像Nginx或Golang一样性能比较高,并且基于Java的实现。所以我们决定自己实现一个轻量级的Http服务server。JavaNIO方面最成熟的就算Netty了,基于他来做底层的网络IO肯定OK,所以我们的方案中NIO方面就选择它了。网络协议方面正好Netty也提供了相应的decoder和encoder,真是方便。但目前有公司在生产环境使用,但无大型系统的case。所以我们还是仔细的扣了一遍代码,并计划之后fork出来自己优化这个decoder。

Nesty就这么诞生了,从名字可以看出它是Netty + HttpRest。

我们也希望跟我们有类似场景的应用,可以进行一些尝试,所以我们将Nesty开源出来。放在github.com上:
https://github.com/gugemichael/nesty/

3.1 IO/线程 模型

screenshot

非阻塞IO和HTTP协议解析搞定了,剩下的就是HTTP注解了。因为业界最多的就是使用SpringMVC,所以我们仿照SpringMVC的注解的名字和用法,重新实现了一套完整的HTTP注解,熟悉SpringMVC的同学,可以零成本上手

3.2 Nesty的使用

  • Maven依赖
<dependency>
  <groupId>org.nesty</groupId>
  <artifactId>nesty-all</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <type>pom</type>
</dependency>
  • HttpServer的创建
        // 创建异步HttpServer
        HttpServerRouteProvider server = AsyncHttpServerProvider.create("127.0.0.1", 8080);

        // 可选,设置Http相关参数
        server.useOptions(new HttpServerOptions().setMaxConnections(4096)
                                                .setHandlerTimeout(10000)
                                                .setIoThreads(8)
                                                .setHandlerThreads(256));

        // 扫描controller(SpringMVC的逻辑类)所在package
        server.scanHttpController("com.nesty.test.a")
                .scanHttpController("com.nesty.test.b")
                .scanHttpController("org.nesty.example.httpserver.handler");

        // 3. 启动server并阻塞等待结束,返回false则启动失败
        if (!server.start())
            System.err.println("HttpServer run failed");

      // 等待
      server.join();

3.3 Restful注解

@Controller
@RequestMapping("/projects")
public class ServiceController {

    // 适配GET方法,对应URI为projects/name/my_project1?owner=nesty
    @RequestMapping("/name/{projectName}")
    public ServiceResponse getProjectByName(@PathVariable("projectName") String projectName,
                                             @RequestParam(value = "owner", required = false) String owner) {
        System.out.println("getProjectByNam() projectName " + projectName + ", owner " + owner);
        return new ServiceResponse();
    }
}

3.4 支持的功能

  • Http HTTP/1.1 方法 GET | POST | UPDATE | DELETE

  • 支持的注解类型

Annotation 参数来源
@Header http header
@RequestParam http url query string or http body key value pairs
@PathVariabl http uri path vairable with {path}
@Body http body

* Http 支持的注解类型

Class Type Default value (require = false is set) Description
int,short,long 0 primitive
float,double 0.0d primitive
String null string value
Enum null enum class type
Class null from http body serializer parsed
  • 更多case可以参照 nesty-example

4. 解决的场景

Nesty在使用方式上最大的保留了SpringMVC的核心功能,在性能上选了基于NIO selector的Netty及HttpDecoder。性能从理论上也满足了我们的需求,迁移成本和新开发成本都很小。但Nesty也不是什么场景都适合,总结了一下适用场景:
- 轻量型Http处理,如内存计算直接返回,转发,读缓存等
- 对QPS有要求,通过非阻塞IO增加性能
- 对高并发有要求,Netty的IO模型可以处理并发连接,甚至某些时候可以代替去掉前面的Nginx,较少延时
- 应用对JVM有更多的控制权,非入侵
- 易于调试,启动非常快1~2s

有些场景,例如一些对请求处理非常耗时,QPS要求比较低的功能。Nesty并没有什么优势,因为解耦和并发业务逻辑处理也是放到一个逻辑线程池去用多线程并发的。Nesty对这部分只是把对应的参数(HttpServerOptions)暴露出来,可以根据对逻辑线程池的需求做设置。甚至在如转发快速返回不需要逻辑线程参与,直接在IO线程池完成的(减少队列、减少cs和并发)在框架上都提供支持

5. 性能测试

测试命令行及JVM参数:

机器配置
24 cores / 48G memory / 1Gb Net (启用网卡多队列)

java -server -Xmx4G -Xms4G -Xmn1536M -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC

apache ab 测试QPS:
* Conccurent : 512 http connections
* Qps : 40,000+
* Latency : < 10ms

部分CPU 90% ~100%,主要消耗在网卡中断和socket的accept调用上(由于短连接)

6. TODO

后续在御膳房的高并发场景中,我们会结合业务,持续的优化Nesty:
- 支持Https
- 支持长连接,充分挖掘QPS性能
- 支持Spring、Mybaties的继承

猜你喜欢

转载自blog.csdn.net/GugeMichael/article/details/51075224