本来打算用Netty来实现一个Severlet服务器,发现spring已经做了相应的支持,那么我们来看看究竟他们的性能有什么差异,如果我们要用netty实现一个severlet容器应该这么做
?
- 测试机器:Linux CentOS6.5 4核16G
- SpringBoot版本:2.2.2.RELEASE
- JDK版本:jdk1.8.0_151
ab压测
我们先对上面说的三个接口进行压测,为避免网络环境影响,我们直接在服务器上使用ab进行压力测试。
压测分三组,每组压测这三个接口,每个接口发起10w请求,每组用户数分别为200、500、1000,从而查看不同用户数请求下的响应结果。
实现:
再定义一个传统的service,为模拟真实环境请求,service下的方法请求耗时100ms:
?
模拟耗时100ms
最后我们写三个接口,每个接口采用不同的方式:
- 使用自定义调度器的方式
- 使用缓存的弹性调度器
- 传统的SpringMVC方式
代码如下图所示:
?
?第一组
压力测试结果:
?10w请求数 200用户
可以看见传统的SpringMVC方式已经有阻塞了,最长的一次请求1107ms,但是整体性能基本一致,因为200个线程刚好是tomcat的线程池最大默认数。
第二组
压测结果:
10w请求 500用户
500用户请求时候可以看到hello3接口的响应时间已经是hello1和hello2两个接口响应时间的2倍以上了,但是基于project reactor响应编程开发方式的响应时间依旧和200用户一致。
我们继续将用户数加到1000。
第三组
压测结果:
10w请求 1000用户
我们发现基于project reactor开发的接口响应时间依旧坚挺,传统SpringMVC方式开发的接口90%响应时间已经高达500ms了。
?
tomcat下压测10w请求1000用户
是不是发现netty的性能比tomcat更加优越?99%的请求在149ms即可完成。如果大家自己实操的话也会发现吞吐量也会较tomcat有大幅度的提升。
基于NettyServer的SpringMVC的实现
构建一个maven工程,并在pom中加入以下依赖.这里使用了netty和spring的容器作为基础.
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
第二步: 构建一个Server服务端类,Netty提供了基于服务端的构建,在启动类里面
public static void main(String[] args)throws Exception {
//启动Spring的容器
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
//配置
annotationConfigApplicationContext.scan("com.netty.mvc");
annotationConfigApplicationContext.refresh();
final DispatcherHandler dispatcherHandler = new DispatcherHandler(annotationConfigApplicationContext);
//创建Even Loop Group
//配置服务器的NIO线程组
//两个Reactor 一个用于服务器接收客户端的连接 一个用于经行SocketChannel的网络读写
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
//创建ServerBootStrap
ServerBootstrap b = new ServerBootstrap();
//指定所使用的NIO传输Channle
b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.localAddress(8080)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
//如果ServerHandler被注为@Shareable的时候,则可以总是使用同样的实例
socketChannel.pipeline()
.addLast(new HttpRequestDecoder())
.addLast(new HttpResponseEncoder())
.addLast(new WebServerHandler(dispatcherHandler));
}
});
//异步的绑定服务器,调用sync===方法阻塞,直到绑定完成
ChannelFuture f = b.bind().sync();
System.out.println("netty服务端启动成功");
//获取Channel的CloseFuture,并阻塞当前线程直到它完成
f.channel().closeFuture().sync();
}finally {
//关闭EvenLoopGroup,释放所有资源
bossGroup.shutdownGracefully().sync();
workerGroup.shutdownGracefully().sync();
}
}
第二步: 构建一个WebServerHandler类来处理i来处理请求.
@ChannelHandler.Sharable
public class WebServerHandler extends ChannelInboundHandlerAdapter{
private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;
//请求分发器
DispatcherHandler dispatcherHandler;
public WebServerHandler(DispatcherHandler dispatcherHandler){
this.dispatcherHandler = dispatcherHandler;
}
/**
* 每个信息入站都会调用
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Object result = "";
if(msg instanceof HttpRequest){
result = dispatcherHandler.handle((HttpRequest)msg);
}
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(result.toString().getBytes())); // 2
HttpHeaders heads = response.headers();
heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8");
heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3
heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
//将接受到的消息写给发送者
ctx.write(response);
}
/**
* 通知处理器最后的channelread是当前批处理中的最后一条信息调用
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将未决消息冲刷到远程节点,并关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace(); //打印异常栈追踪
ctx.close(); //关闭该channel
}
}
第四步: 就是构建一个DispatcherHandler作为请求处理份发器
@Configuration
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
//url-->hanlder的映射
private List<HandlerMapping> handlerMappings;
//处理器的适配器
private List<HandlerAdapter> handlerAdapters;
/**
* Create a new {@code DispatcherHandler} for the given {@link ApplicationContext}.
* @param applicationContext the application context to find the handler beans in
*/
public DispatcherHandler(ApplicationContext applicationContext) {
initStrategies(applicationContext);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
initStrategies(applicationContext);
}
protected void initStrategies(ApplicationContext context) {
Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
AnnotationAwareOrderComparator.sort(mappings);
this.handlerMappings = Collections.unmodifiableList(mappings);
Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerAdapter.class, true, false);
this.handlerAdapters = new ArrayList<>(adapterBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
@Override
public Object handle(HttpRequest httpRequest) {
//请求处理器
for(HandlerMapping handlerMapping : handlerMappings){
Object handler = handlerMapping.getWebHandler(httpRequest);
if(handler != null){
for(HandlerAdapter adapter: handlerAdapters){
if(adapter.support(handler)){
return adapter.handle(httpRequest,handler);
}
}
}
}
return null;
}
}
第五步: 构建SprintMVC中最核心的HandlerMapping中映射处理器的注册,这里是借助了Spring的ComponentScan组件实现对于自定义@Controller的注解的扫描.
@Configuration
public class DefaultWebHandler implements WebHandler {
public DefaultWebHandler(ApplicationContext applicationContext) {
registerHandler(applicationContext);
}
/**
* url -> Method对应
*/
private Map<String, Method> handlerMap = new LinkedHashMap<>();
/**
* method—>controller的对应
*/
private Map<Method, Object> controllerMap = new HashMap<>();
private void registerHandler(ApplicationContext context) {
Map<String, Object> annotationControllerClasses = context.getBeansWithAnnotation(Controller.class);
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
for (Object targetType : annotationControllerClasses.values()) {
if (!Proxy.isProxyClass(targetType.getClass())) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : targetType.getClass());
ReflectionUtils.doWithMethods(specificHandlerType, method -> {
String url = "";
if (targetClass.isAnnotationPresent(RequestMapping.class)) {
url = targetClass.getAnnotation(RequestMapping.class).value();
}
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
if (specificMethod.isAnnotationPresent(RequestMapping.class)) {
url += specificMethod.getAnnotation(RequestMapping.class).value();
}
handlerMap.put(url, specificMethod);
controllerMap.put(specificMethod, targetType);
}, ReflectionUtils.USER_DECLARED_METHODS);
}
}
@Override
public Object handle(HttpRequest httpRequest) {
Method method = handlerMap.get(httpRequest.uri());
if(method != null){
try {
return method.invoke(controllerMap.get(method));
}catch (IllegalAccessException e){
e.printStackTrace();
}catch (InvocationTargetException e){
e.printStackTrace();
}
}
return null;
}
}
第六步构建测试用例
@Controller
@RequestMapping(value = "/test")
@Configuration
public class TestController {
@RequestMapping(value = "/get",method = RequestMethod.GET)
public String test(){
return "Hi Netty SpringMVC";
}
}
vert.x响应式编程?
- ert.x采用单一组件结构设计,即Verticle,所有业务功能都使用Verticle这种单一的组件编程完成,克服以往应用框架和平台包含众多类型的组件模式,使得开发人员能极快适应Vert.x编程,加快项目的开发速度。
- Vert.x中所有的Verticle组件都是完全解耦合的,任何组件之间不能直接调用,只能通过在Vert.x的事件总线上发送事件来完成,彻底解决了传统应用系统中管理组件间相互依赖的复杂性,最终使得Vert. x应用编程极其简单高效。
- Vert.x使用单线程事件驱动的异步工作模式,编写Vert.x组件时,不需要考虑复杂的多线程编程难题,并不需要关注线程之间的调用、同步、加锁等繁琐处理编程,简化了编程代码,提高了编程效率。
- Vert. x通过提供一整套的异步编程API实现异步编程模型,在Vert. x中所有的请求处理都是通过注册事件监听处理器机制完成的。编程TCP处理服务器Verticle,通过注册TCPSocket的数据到达事件监听器,实现数据到达后的回调处理,而不是采用一直等待数据读取的阻塞模式,实现的是非阻塞的异步工作模式。
- Vert.x的核心运行机制是事件循环,当Vert.x实例启动后,Vert.x框架在每个CPU的内核创建一个事件循环线程。此事件循环线程永不结束,它不断监听出现的各种事件,如事件总线的事件到达WebSocket上的数据接收,HTTP上的请求到达HTTP响应结束,定时器触发等等,并把事件分发到注册了监听此事件的Verticle,再继续监听其他的事件,如此反复直到Vert.x实例停止。
、vert.x 与 spring的对比
- spring是单体架构设计,
- vert.x面向分布式设计,性能高,在vert.x的概念中,没有MVC,没有AOP,没有ORM。二者的生态框架对比图如下
vert.x | spring |
---|
Vert.x Core | Spring Framework | Vert.x Web | SpringBoot | Vert.x Data Access | Spring Data Jpa | Vert.x Reactive | Project Reactor | Vert.x Microservices | Spring Cloud | Vert.x Authentication and Authorisation | Spring Security | Vert.x MQTT | | Vert.x Messaging | Spring AMQP | Vert.x Devops | Spring Devops |
- vert.x 与 Spring 支持的编程语言对比
vert.x | spring |
---|
java,Kotlin,JavaScript, Groovy,Ruby,Scala | java,Kotlin,Groovy |
参考:
1、基于NettyServer的SpringMVC的实现 - 掘金
?2、netty web 容器_Tomcat和Netty的战场:SpringMVC&WebFlux性能大比拼_文小宁的博客-CSDN博客
3、jianshu.com/p/295e3122b466
|