资讯专栏INFORMATION COLUMN

使用servlet3.0异步特性改造spring-cloud-zuul

HmyBmny / 539人阅读

摘要:不过在出来之后支持异步了,可以把业务操作放到独立的线程池里面去,这样可以尽快释放线程,本身也支持异步了,本篇文章将带你如何使用的异步特性来改造优化其性能。

​ 我们知道spring-cloud-zuul是依赖springMVC来注册路由的,而springMVC又是在建立在servlet之上的(这里微服务专家杨波老师写过一篇文章讲述其网络模型,可以参考看看),在servlet3.0之前使用的是thread per connection方式处理请求,就是每一个请求需要servlet容器为其分配一个线程来处理,直到响应完用户请求,才被释放回容器线程池,如果后端业务处理比较耗时,那么这个线程将会被一直阻塞,不能干其他事情,如果耗时请求比较多时,servlet容器线程将被耗尽,也就无法处理新的请求了,所以Netflix还专门开发了一个熔断的组件Hystrix 来保护这样的服务,防止其因后端的一些慢服务耗尽资源,造成服务不可用。不过在servlet3.0出来之后支持异步servlet了,可以把业务操作放到独立的线程池里面去,这样可以尽快释放servlet线程,springMVC本身也支持异步servlet了,本篇文章将带你如何使用servlet3.0的异步特性来改造spring-cloud-zuul优化其性能。

​ 我们先来创建一个zuul的maven项目,就叫async-zuul吧,具体代码我放在github上了。项目依赖于consul做注册中心,启动时先要在本地启动consul,为了能看到效果我们先来新建一个zuul的filter类:

@Component
public class TestFilter extends ZuulFilter {
    //忽略无关代码,具体看github上的源码
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        System.out.println("==============线程名称:" +                                         Thread.currentThread().getName() 
                + ",访问url:" + request.getRequestURI() + "================");
        return null;
    }
}

主要就是打印下线程的名称,这个filter是zuul的前置过滤器,我们主要就是看下在zuul在执行路由时是由什么线程执行的。好了我们来启动下main方法,不过我们还需要一个后端服务,很简单,创建一个springcloud项目名叫book即可,并提供一个url:/book/borrow,启动后把服务注册到consul上,成功后我们通过zuul的代理来访问下book服务:

http://localhost:8080/book/book/borrow

输出:

==========线程名称:http-nio-8080-exec-10,访问url:/book/book/borrow=======

很清楚的看到执行filter的线程是servlet容器线程,等下我们改造成异步后再做一下对比。

​ 还记得在文章spring-cloud-zuul原理解析(一)中我们分析到,spring-cloud-zuul的路由映射使用到springMVC的两大组件ZuulHandlerMappingZuulController ,目前肯定是无法支持异步servlet的。那么这两个类在哪里被加载的呢?答案就是ZuulServerAutoConfiguration,此类是spring-cloud-zuul自动配置类,源码如下:

@Configuration
@ConditionalOnBean(annotation=EnableZuulProxy.class)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
    //无关代码省略..........
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }
    //无关代码省略..........
}

可以看到这两个类在spring-cloud-zuul中并没有为我们提供扩展,没法替换它们来实现servlet的异步逻辑,那该怎么办呢?spring-cloud-zuul还有一个自动配置配ZuulProxyAutoConfiguration继承自ZuulServerAutoConfiguration,我们把这两个配置类全部替换掉,换成我们自己的不就可以了么?是的,不过首先我们得先排除加载这两个自动配置类,springboot为我们提供这样的设置:

@EnableZuulProxy
//排除ZuulProxyAutoConfiguration配置类
@SpringBootApplication(exclude=ZuulProxyAutoConfiguration.class)
public class Startup {
    public static void main(String[] args) {
        SpringApplication.run(Startup.class, args);
    }
}

之后,我们创建两个自己的配置配,完全拷贝ZuulServerAutoConfigurationZuulProxyAutoConfiguration这两个类,不过光这两个类还是不行,这两个类使用到了类RibbonCommandFactoryConfiguration,里面的内部类是protected的,我们没法使用,也得自己创建,也是拷贝自RibbonCommandFactoryConfiguration,然后我们还需修改ZuulController的逻辑改成异步方式,所以再新建一个类继承ZuulController,这样我们就新建了自己的三个配置类和一个自己的ZuulController`类,如下:

public class MyZuulController extends ZuulController{
    private final AsyncTaskExecutor asyncTaskExecutor;
    public MyZuulController(AsyncTaskExecutor asyncTaskExecutor) {
        super();
        this.asyncTaskExecutor = asyncTaskExecutor;
    }
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //真正的异步化逻辑
        final AsyncContext asyncCtx = request.startAsync();
        this.asyncTaskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
        MyZuulController.this.handleRequestInternal((HttpServletRequest)asyncCtx.getRequest(),
                            (HttpServletResponse)asyncCtx.getResponse());
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    asyncCtx.complete();
                    RequestContext.getCurrentContext().unset();
                }
            }
        });
        return null;
    }
}

@Configuration
@ConditionalOnBean(annotation=EnableZuulProxy.class)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class MyZuulServerAutoConfiguration {
    //省略代码,完全拷贝自ZuulServerAutoConfiguration
    /**
     * 自定义线程池
     * @return
     */
    @Bean
    public AsyncTaskExecutor zuulAsyncPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("zuul-async-");
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(50);
        return executor;
    }
    //这里换成我们自己的MyZuulController类,并且传入一个我们自定义的线程池
    @Bean
    public ZuulController zuulController(AsyncTaskExecutor asyncTaskExecutor) {
        return new MyZuulController(asyncTaskExecutor);
    }
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,AsyncTaskExecutor asyncTaskExecutor) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController(asyncTaskExecutor));
        mapping.setErrorController(this.errorController);
        return mapping;
    }
}

@Configuration
@Import({ MyRibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
    MyRibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
    MyRibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
@ConditionalOnBean(annotation=EnableZuulProxy.class)
public class MyZuulProxyAutoConfiguration extends MyZuulServerAutoConfiguration {
    //省略代码,完全拷贝自ZuulProxyAutoConfiguration
}

public class MyRibbonCommandFactoryConfiguration {
    //省略代码,完全拷贝自RibbonCommandFactoryConfiguration
}

这里我们稍作了一点修改

@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
//替换成:
@ConditionalOnBean(annotation=EnableZuulProxy.class)

这样做的目的主要是配合注解@EnableZuulProxy使用,只有开启了此注解才加载配置类。我们还替换ZuulController成我们自定义的MyZuulController了,这里是异步化的主要逻辑,其实也非常简单,就是使用了serv3.0为我们提供的api来开启异步化。万事已经具备啦,我们再次启动zuul,访问上面的url,输出:

==========线程名称:zuul-async-1,访问url:/book/book/borrow==========

哈哈,执行filter的线程变成我们自定义的线程名称了,达到了我们的需求,servlet已经变成异步的了。

这是我对spring-cloud-zuul实现异步servlet的想法,记录下来,可能不是最好的实现方式,如果您有更好的方法欢迎留言给我一起探讨下!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77109.html

相关文章

  • dubbo源码解析(四十三)2.7新特性

    摘要:大揭秘目标了解的新特性,以及版本升级的引导。四元数据改造我们知道以前的版本只有注册中心,注册中心的有数十个的键值对,包含了一个服务所有的元数据。 DUBBO——2.7大揭秘 目标:了解2.7的新特性,以及版本升级的引导。 前言 我们知道Dubbo在2011年开源,停止更新了一段时间。在2017 年 9 月 7 日,Dubbo 悄悄的在 GitHub 发布了 2.5.4 版本。随后,版本...

    qqlcbb 评论0 收藏0
  • Spring之旅 - 3.0、3.1、4.0导引

    摘要:之旅简化开发的使命简化开发为了降低开发的复杂性,采取如下关键策略基于的轻量级和最小侵入性编程通过依赖注入和面向接口实现松耦合基于切面和惯例进行声明式编程通过切面和模版减少样式代码依赖注入耦合性具有两面性一方面,紧密耦合的代码难以测试难以复 Spring之旅 简化Java开发 Spring的使命:简化Java开发 为了降低Java开发的复杂性,采取如下关键策略:基于POJO的轻量级和最...

    leon 评论0 收藏0
  • spring-cloud-zuul原理解析(一)

    摘要:是开源的微服务网关,它可以和,等组件配合使用,网上也有很多如何使用的文章,我们也在生产环境使用了,所以读了下的源码,下面把它分享出来,与大家探讨下核心原理。 Zuul是Netflix开源的微服务网关,它可以和Eureka,consul,Ribbon,Hystrix等组件配合使用,网上也有很多如何使用zuul的文章,我们也在生产环境使用了,所以读了下zuul的源码,下面把它分享出来,与大...

    qingshanli1988 评论0 收藏0
  • 一起学习使用Spring Cloud Netflix之Zuul

    摘要:前言在体系中扮演着统一网关的角色,负责与外部交互。与结合使用,可以根据服务名来访问后端的服务,对于而言,也是一个。这段代码表示,如果请求中没有信息,就会报错。 前言 Zuul在Spring Cloud 体系中扮演着统一网关的角色,负责与外部交互。用户可以通过不同的URL特征来访问不同的后端服务,类似于Nginx代理的效果。Zuul与Eureka结合使用,可以根据服务名来访问后端的服务,...

    FullStackDeveloper 评论0 收藏0
  • comet实现(原理)

    摘要:最近对服务器推送技术比较感兴趣,在网上也看了好些文章,由于每个人理解的不同,实现细节或者语言表达方式不同,本人被各种名词或者技术实现搞的头大,于是自己准备整理下。定时器就可以实现,每次请求如果服务器端有更新数据则响应到客户端。 最近对服务器推送技术比较感兴趣,在网上也看了好些文章,由于每个人理解的不同,实现细节或者语言表达方式不同,本人被各种名词或者技术实现搞的头大,于是自己准备整理下...

    rozbo 评论0 收藏0

发表评论

0条评论

HmyBmny

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<