本来打算用Netty来实现一个Severlet服务器,发现spring已经做了相应的支持,那么我们来看看究竟他们的性能有什么差异,如果我们要用netty实现一个severlet容器应该这么做
我们先对上面说的三个接口进行压测,为避免网络环境影响,我们直接在服务器上使用ab进行压力测试。
压测分三组,每组压测这三个接口,每个接口发起10w请求,每组用户数分别为200、500、1000,从而查看不同用户数请求下的响应结果。
实现:
再定义一个传统的service,为模拟真实环境请求,service下的方法请求耗时100ms:
模拟耗时100ms
最后我们写三个接口,每个接口采用不同的方式:
代码如下图所示:
第一组
压力测试结果:
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有大幅度的提升。
构建一个maven工程,并在pom中加入以下依赖.这里使用了netty和spring的容器作为基础.
io.netty
netty-all
4.1.36.Final
org.springframework
spring-core
5.1.9.RELEASE
org.springframework
spring-beans
5.1.9.RELEASE
org.springframework
spring-context
5.1.9.RELEASE
第二步: 构建一个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() {
@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 handlerMappings;
//处理器的适配器
private List 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 mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
ArrayList mappings = new ArrayList<>(mappingBeans.values());
AnnotationAwareOrderComparator.sort(mappings);
this.handlerMappings = Collections.unmodifiableList(mappings);
Map 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 handlerMap = new LinkedHashMap<>();
/**
* method—>controller的对应
*/
private Map controllerMap = new HashMap<>();
private void registerHandler(ApplicationContext context) {
Map annotationControllerClasses = context.getBeansWithAnnotation(Controller.class);
Set> 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 | 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 |
---|---|
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