Spring Cloud GateWay-Netflix Zuul

本文详细介绍了Zuul网关的引入及其在微服务架构中的作用,涵盖了Zuul的服务搭建、路由配置、过滤器机制及配置管理。重点探讨了如何利用Zuul实现动态路由、过滤请求和响应,以及如何通过自定义过滤器实现AB测试和日志串联,同时提供了超时配置等实用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 网关引入与Zuul

1.1 网关引入

1.2 Zuul介绍

2  Zuul服务搭建

3 Zuul 路由

3.1 默认配置

3.1.1 DiscoverClient方式

3.1.2 SpringCloud增强版的Rest Template

3.1.3 Netflix Feign

3.2 手动配置

3.2.1   手动配置实例

3.2.3 其他配置

3.3 静态配置

3.4 动态配置

4 Zuul过滤器

5  前置过滤器

5.1 Zuul前置过滤器-生成TraceId

5.2 客户端使用TraceId

5.3 客户端RestTemplate拦截器

6 后置过滤器

7 路由过滤器

7.1 AB test的实现

7.1.1 定义后置过滤器

7.1.2 获取AB test的配置信息

7.1.3 路由策略

7.1.4 获取新服务地址

7.1.5 转发路由

7.1.6 测试

7.2 关于其他策略

8 Zull相关配置

8.1 超时配置


1 网关引入与Zuul

1.1 网关引入

1、两大功能

主要包括 路由和过滤器 两大功能。主要功能体现在过滤器,实现“横切关注点”水平切分各个服务的公共部分,如日志记录等功能,作为服务来提供。

2、Zull和nginx区别

(1)动态注册和注销 客户端服务时,网关服务可以立刻生效。Nginx需要通过修改配置文件才能生效。

(2)横切服务关注点

1.2 Zuul介绍

在没有网关之前,客户端服务包括两个步骤:

  • 从EurekaServer获取一个客户端服务的所有地址
  • 通过Ribbon负载均衡选择一个机器进行访问

 

引入网关之后,客户端通过 “http://gateway/服务ID/url” URL格式调用网关,网关执行包括两个步骤:

  • 使用服务ID查找客户端服务
  • 使用Netflix Ribbon对调用服务进行负载均衡。

 

2  Zuul服务搭建

1、pom.xml

1

2

3

4

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-zuul</artifactId>

</dependency>

2、使用注解@EnableZuulProxy

1

2

3

4

5

6

7

8

@SpringBootApplication(scanBasePackages = {"com.example"})

@EnableZuulProxy

public class DemoApplication {

 

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);

    }

}

3、配置文件

1

2

3

4

5

6

eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

server.port=8763

spring.application.name=gateway

eureka.instance.status-page-url=http://localhost:${server.port}/swagger-ui.html

 

management.security.enabled=false

4、启动之后

 

5、可以通过 http://localhost:8763/routes 查看网关当前路由信息

49450231

如果出现“There was an unexpected error (type=Unauthorized, status=401).”,解决该问题就是添加一个配置如下:

1

management.security.enabled=false

3 Zuul 路由

3.1 默认配置

URL的格式如下,默认情况下都是Eureka上注册的服务ID。

1

http://gateway/服务ID/url

  • gateway,网关服务在Eureka注册的服务ID
  • 服务ID,客户端服务在Eureka注册的服务ID
  • url,访问客户端服务的url。

3.1.1 DiscoverClient方式

1、使用DiscoveryClient

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Service

public class DiscoveryClientService {

    private static final Logger LOGGER = LoggerFactory.getLogger(DiscoveryClientService.class);

    @Autowired

    private DiscoveryClient discoveryClient;

 

    public String queryWithGateWay(){

        // 1.获取网关服务的IP

        List<ServiceInstance> instances = discoveryClient.getInstances("gateway");

        StringBuilder url = new StringBuilder("http://");

        url.append(instances.get(0).getHost() + ":" + instances.get(0).getPort());

        // 2.添加服务名称

        url.append("/service-prodvider1");

        // 3.添加查询url

        url.append("/query");

 

        RestTemplate restTemplate = new RestTemplate();

        String response = restTemplate.getForObject(url.toString(), String.class);

        LOGGER.info("query with discovery client :{}", response);

        return response;

    }

}

2、测试

1

2

3

4

5

6

7

8

9

@RequestMapping(value = "/testDiscoveryWithGateWay", method = RequestMethod.GET)

    @ResponseBody

    public ResponseDemo testDiscoveryWithGateWay() {

        String response = discoveryClientService.queryWithGateWay();

        ResponseDemo demo = new ResponseDemo();

        demo.setMessage(response);

        demo.setResultCode("000");

        return demo;

    }

3、执行结果

 

3.1.2 SpringCloud增强版的Rest Template

1、使用网关来访问微服务

1

2

3

4

5

6

7

8

9

10

11

12

13

14

    public String queryWithGateWay() {

 

        StringBuilder url = new StringBuilder("http://");

        // 1.添加网关

        url.append("gateway");

        // 2.添加服务ID,不需要具体的IP和端口号

        url.append("/service-prodvider1");

        // 3.下游服务的接口

        url.append("/query");

 

        String response = restTemplate.getForObject(url.toString(), String.class);

        return response;

 

    }

2、测试

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#################

ClientController.java

#################

    @Autowired

    private SpringCloudRestTemplateService springCloudRestTemplateService;

 

  @RequestMapping(value = "/testSpringCloudRestWithGateWay", method = RequestMethod.GET)

    @ResponseBody

    public ResponseDemo testSpringCloudRestWithGateWay() {

        String response = springCloudRestTemplateService.queryWithGateWay();

        ResponseDemo demo = new ResponseDemo();

        demo.setMessage(response);

        demo.setResultCode("000");

        return demo;

    }

3、执行结果

51317884

3.1.3 Netflix Feign

1、使用Feign调用网关

1

2

3

4

5

6

7

8

9

// 通过FeignClient指定网关

@FeignClient("gateWay")

// 指定服务ID

@RequestMapping("/service-prodvider1")

public interface FeignClientWithGateWayServcie {

    // 服务查询url

    @RequestMapping(value = "/query", method = RequestMethod.GET, consumes = "application/json")

    String query();

}

2、测试

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#################

ClientController.java

#################

   @Autowired

   private FeignClientWithGateWayServcie feignClientWithGateWayServcie;

 

   @RequestMapping(value = "/testFeignGateWayClient", method = RequestMethod.GET)

    @ResponseBody

    public ResponseDemo testFeignGateWayClient() {

        String response = feignClientWithGateWayServcie.query();

        ResponseDemo demo = new ResponseDemo();

        demo.setMessage(response);

        demo.setResultCode("000");

        return demo;

    }

3、结果

51263227

3.2 手动配置

3.2.1   手动配置实例

1、在网关服务中的配置文件中添加

1

2

## 使用provieder1标识服务ID为service-prodvider1

zuul.routes.service-prodvider1:/provider1/**

此时重新启动网关服务,访问网关服务的 /routes 接口,如下。发现除了默认的根据eureka注册服务ID的映射之外,还有一个是自己手动配置映射记录。

49450231

2、客户端使用

在使用网关时,此时只需要把网关名字由服务ID(service-prodvider1)改为自己定义的名字(provider1)就可以了。如下

1

2

3

4

5

6

7

8

9

10

11

12

13

  public String queryWithGateWay() {

        StringBuilder url = new StringBuilder("http://");

        url.append("gateway");

 

        // 添加服务ID,此时自己新定义的名字

        url.append("/provider1");

 

        url.append("/query");

 

        String response = restTemplate.getForObject(url.toString(), String.class);

        return response;

 

    }

3.2.3 其他配置

1、取消默认配置

(1)取消一个服务的默认配置

1

zuul.ignored-services='service-prodvider1'

(2)取消所有服务的自动配置

1

zuul.ignored-services='*'

3.3 静态配置

对于Spring mvc和非JVM(python等)服务并没有在Eureka上注册,此时在网关进行配置路由时只能指定一台服务,无法使用Ribbon进行负载均衡。

1

2

zuul.routes.noJvmService.path:/noJvmService/**

zuul.routes.noJvmService.url:http://IP:PORT

解决方案就是将这两类服务注册到Eureka。后续会介绍这两类服务的注册。

3.4 动态配置

使用spring cloud或者apollo来进行配置路由。

4 Zuul过滤器

过滤器是网关GateWay 引入最重要的一点,实现了横切关注点,即将服务的一些功能移动到网关服务中,如添加traceID、验证/授权等。目前主要有前置过滤器、路由过滤器和后置过滤器 三种过滤器。如下图:

  • 前置过滤器。在Zull网关调用服务之前,对请求request进行处理。如添加traceId,请求的验证和授权。
  • 路由过滤器。在Zull网关调用服务之前,重新设置新的路由规则,可以覆盖zuul的路由配置。如A/B 测试。
  • 后置过滤器。在Zull网关调用服务之后,对response进行处理。如添加traceId。

 

三种过滤器区别就在于执行时间点不同,对于前置过滤器和路由过滤器是在Zuul网关调用下游服务前;对于后置过滤器是在Zuul网关调用下游服务之后。

三种过滤器共同点都是继承自ZuulFilter,需要实现四个方法:

  • filterType。值有:pre/post/route,对应的常量值为FilterConstants.PRE_TYPE、FilterConstants.POST_TYPE、FilterConstants.POST_TYPE。
  • filterOrder。filter执行顺序
  • shouldFilter。是否需要执行此过滤器。
  • run。过滤器执行逻辑

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

public class XXXFilter extends ZuulFilter {

    // 过滤器类型

    @Override

    public String filterType() {

        return "pre";

    }

 

    // 过滤器顺序

    @Override

    public int filterOrder() {

        return 1;

    }

 

    // 是否需要执行过滤器

    @Override

    public boolean shouldFilter() {

        return true;

    }

 

    //过滤器的执行逻辑

    @Override

    public Object run() {....}

}

5  前置过滤器

使用前置过滤器可以为各服务生成一个TraceId为例,TracId可以将各个服务的日志串联起来,方便查询问题。实现此串联TrancId的功能主要包括网关和客户端两部分:

(1)网关。负责在请求中生成traceID

  • Zuul前置过滤器。从http request中获取traceID,保存到上下文RequestContext中;如果traceId不存在就生成一个traceId中。

(2)客户端。负责从请求获取traceID、使用traceID、传递TranceID到下游服务

  • 过滤器Filter。从HTTP Request的头部获取TraceId.
  • 执行服务的业务逻辑。使用RequestContext中traceId,如打印日志。
  • 添加RestTemplate拦截器。在请求下游服务时,添加traceId,把TraceID传递到下游服务。

 

5.1 Zuul前置过滤器-生成TraceId

1、生成TraceId

在请求达到网关服务之后,首先判断request的header中是否存在traceId,如果存在就直接跳过,否则生成一个traceId,放到request的header中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

/**

* 需要覆盖四个方法。

*/

@Component

public class TraceIdFilter extends ZuulFilter {

    private static final String TRANCE_ID_KEY = "TRANCE_ID_KEY";

 

     // 过滤器类型

    @Override

    public String filterType() {

        return FilterConstants.PRE_TYPE;

    }

 

    // 过滤器顺序

    @Override

    public int filterOrder() {

        return 1;

    }

 

    // 是否需要执行过滤器

    @Override

    public boolean shouldFilter() {

        return true;

    }

 

    // 过滤器的执行逻辑

    @Override

    public Object run() {

        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();

        // 1.判断请求中是否存在。如果存在,立刻返回

        if (isExistTraceId(request)) {

            return null;

        }

 

        // 2.不存在就新生成一个

        String traceId = UUID.randomUUID().toString();

        RequestContext context = RequestContext.getCurrentContext();

        context.addZuulRequestHeader(TRANCE_ID_KEY, traceId);

        return null;

    }

 

    private boolean isExistTraceId(HttpServletRequest request) {

        String value = request.getHeader(TRANCE_ID_KEY);

        if (StringUtils.isBlank(value)) {

            return false;

        }

        return true;

    }

}

注意:

  • RequestContext使用的是zuul提供的,不是Spring提供的。

1

com.netflix.zuul.context.RequestContext;

2、测试

在网关生成traceId之后,就可以在客户端通获取上面的traceId了

1

2

3

4

5

6

7

   @RequestMapping("/query")

   @ResponseBody

    public ViewVo query(HttpServletRequest request) {

        // 获取traceId

        String traceId = request.getHeader("TRANCE_ID_KEY");

        .......

    }

5.2 客户端使用TraceId

客户端可以通过过滤器获取请求head中的traceId,并把TraceId保存到上下文中;执行业务逻辑时就可以从这个上下文中获取traceId。

1、获取Http Request头部中的traceId

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

/**

* 从请求中获取关联ID,然后保存到上下文中

*/

@Component

public class ContextFilter implements Filter {

    @Override

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,

                         FilterChain filterChain)

            throws IOException, ServletException {

 

        HttpServletRequest request = (HttpServletRequest) servletRequest;

        String traceId = request.getHeader("TRANCE_ID_KEY");

        if (!StringUtils.isEmpty(traceId)) {

            UserContextHolder.getContext().setLogId(traceId);

        }

        filterChain.doFilter(servletRequest, servletResponse);

    }

 

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

    }

 

    @Override

    public void destroy() {

    }

}

2、使用上下文中的traceId

1

String traceId = UserContextHolder.getContext().getLogId();

3、上下文和上下文Holder

(1)上下文

1

2

3

4

5

6

7

8

9

public class UserContext {

    private String logId;

    public String getLogId() {

        return logId;

    }

    public void setLogId(String logId) {

        this.logId = logId;

    }

}

(2)上下文Holder

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public class UserContextHolder {

    private static ThreadLocal<UserContext> contextLocal = new ThreadLocal<>();

 

    public static UserContext getContext() {

        UserContext context = contextLocal.get();

        if (null == context) {

            UserContext ctx = new UserContext();

            contextLocal.set(ctx);

            return ctx;

        }

        return context;

    }

 

    public static void setContext(UserContext context) {

        contextLocal.set(context);

    }

}

5.3 客户端RestTemplate拦截器

1、定义SpringTemplate的拦截器,在发送的Request的头部添加tranceId

1

2

3

4

5

6

7

8

9

10

11

12

public class ContextInterceptor implements ClientHttpRequestInterceptor {

    @Override

    public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,

                                        ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {

        String traceId = UserContextHolder.getContext().getLogId();

        HttpHeaders headers = httpRequest.getHeaders();

        if (!StringUtils.isEmpty(traceId)) {

            headers.add("TRANCE_ID_KEY", traceId);

        }

        return clientHttpRequestExecution.execute(httpRequest, bytes);

    }

}

2、将新定义的拦截器加入到RestTemplate。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Configuration

public class RestTemplateConfig {

 

    @LoadBalanced

    @Bean

    public RestTemplate restTemplate() {

        RestTemplate restTemplate = new RestTemplate();

        List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();

        if (CollectionUtils.isEmpty(interceptors)) {

            restTemplate.setInterceptors(Lists.newArrayList(new ContextInterceptor()));

        } else {

            interceptors.add(new ContextInterceptor());

            restTemplate.setInterceptors(interceptors);

        }

        return restTemplate;

    }

}

3、测试

使用上面的RestTemplate,进行访问其他服务,如下代码。这个新定义的RestTemplate属于服务查找方式中“SpringCloud 增强版的RestTemplate”。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Service

public class SpringCloudRestTemplateService {

    @Autowired

    private RestTemplate restTemplate;

 

    public String query() {

 

        StringBuilder url = new StringBuilder("http://");

        // 添加服务ID,不需要具体的IP和端口号

        url.append("service-prodvider2");

        // 下游服务的接口

        url.append("/query");

 

        String response = restTemplate.getForObject(url.toString(), String.class);

        return response;

    }

}

6 后置过滤器

通过后置过滤器可以处理Response。如在response中添加traceID,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

@Component

public class PostFilter extends ZuulFilter {

    //  过滤器类型

    @Override

    public String filterType() {

        return FilterConstants.POST_TYPE;

    }

 

    // 过滤器顺序

    @Override

    public int filterOrder() {

        return 1;

    }

 

    // 是否需要执行过滤器

    @Override

    public boolean shouldFilter() {

        return true;

    }

 

    // 过滤器执行逻辑

    @Override

    public Object run() {

        RequestContext ctx = RequestContext.getCurrentContext();

        // 1.从head中获取traceId

        HttpServletRequest httpRequest = ctx.getRequest();

        String traceId = httpRequest.getHeader("TRANCE_ID_KEY");

 

        // 2.添加traceId到response中

        if (!StringUtils.isEmpty(traceId)) {

            HttpServletResponse response = ctx.getResponse();

            response.addHeader("TRANCE_ID_KEY", traceId);

        }

        return null;

    }

}

7 路由过滤器

路由过滤器功能就是可以覆盖Zuul原来的路由配置,重新路由请求。在实际中,灰度发布、Abtest都可以通过这个过滤器实现。

7.1 AB test的实现

这里以实现A/B test为例。首先介绍下AB test:

A/B test,或者说灰度发布。对于一次升级,并不想要一次让所有用户看到,而是循序渐进的让更多用户看到,这样也方便优化。灰色,介于白色和黑色之间,寓意一个功能即不是全部给用户看到也不是所有用户看不到,而是部分用户可见。

如下是一个简单Abtest的实现流程,主要包括如下步骤:

  • 获取Ab Test的配置信息。如服务的灰度机器列表信息等。
  • 校验是否需要走新的路由。AB test 的策略
  • 获取新的服务地址。需要考虑负载均衡
  • 转发路由。

83054971

7.1.1 定义后置过滤器

如下是路由过滤器定义,需要注意:

  • filterOrder()。返回的是SIMPLE_HOST_ROUTING_FILTER_ORDER – 1。SimpleHostRoutingFilter中改返回值是SIMPLE_HOST_ROUTING_FILTER_ORDER, 所以这里要减一,保证此过滤器在SimpleHostRoutingFilter之前执行。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

@Component

public class RouteFilter extends ZuulFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RouteFilter.class);

    @Autowired

    private ProxyRequestHelper helper;

 

    /**

     * 过滤器类型

     *

     * @return

     */

    @Override

    public String filterType() {

        return FilterConstants.ROUTE_TYPE;

    }

 

    /**

     * 过滤器顺序

     *

     * @return

     */

    @Override

    public int filterOrder() {

        //  SimpleHostRoutingFilter的值是SIMPLE_HOST_ROUTING_FILTER_ORDER,

        // 所以这里要减一,保证在SimpleHostRoutingFilter之前执行。

        return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;

    }

 

    /**

     * 是否需要执行过滤器。参考SimpleHostRoutingFilter实现

     *

     * @return

     */

    @Override

    public boolean shouldFilter() {

        return true;

    }

 

    @Override

    public Object run() {

        // 1.查询配置信息

        AbTestRoute route = queryAbTestRoute();

 

        // 2.校验是否命中智能路由

        boolean needRoute = checkNeedRoute(route);

        if (!needRoute) {

            return null;

        }

 

        // 3.获取一个目的服务地址

        String address = getAddress(route);

 

        // 4.转发请求

        forward(address);

 

        return null;

    }

}

7.1.2 获取AB test的配置信息

为了测试方便,这里把配置信息当成常量来写了,其实应该是调用配置中心来获取的。这里需要注意的有:

  • 需要有一个灰度开关。在不需要灰度,可以关闭次开关。

1

2

3

4

5

6

7

8

9

10

  // 可以查询配置服务(SpringCloudConfig或Apollo)来获取查询信息,这里为了测试,写成一个常量。

    private AbTestRoute queryAbTestRoute() {

        AbTestRoute route = new AbTestRoute();

        route.setAddress(Lists.newArrayList("localhost:8765"));

        // 标识90%进入到新服务

        route.setWeight(9);

        // 灰度测试开关打开

        route.setRouteSwitch(true);

        return route;

    }

对应配置信息类为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

public class AbTestRoute {

    // 灰度流量百分比

    private int weight;

    // 格式为 "IP:端口号"

    private List<String> address = Lists.newArrayList();

    // 灰度开关

    private boolean routeSwitch;

 

    public int getWeight() {

        return weight;

    }

 

    public void setWeight(int weight) {

        this.weight = weight;

    }

 

    public List<String> getAddress() {

        return address;

    }

 

    public void setAddress(List<String> address) {

        this.address = address;

    }

 

    public boolean getRouteSwitch() {

        return routeSwitch;

    }

 

    public void setRouteSwitch(boolean routeSwitch) {

        this.routeSwitch = routeSwitch;

    }

}

7.1.3 路由策略

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// 生成随机数1到10随机数,设置的是9,如果随机数小于9就通过,所以此时通过率为90%

    private boolean checkNeedRoute(AbTestRoute route) {

        // 如果灰度开关关闭,则直接返回

        if(!route.getRouteSwitch()){

            return false;

        }

 

        Random random = new Random();

        int value = random.nextInt(10) + 1;

 

        if (route.getWeight() > value) {

            return true;

        } else {

            return false;

        }

    }

7.1.4 获取新服务地址

1

2

3

4

  // 从灰度服务的机器列表中,选择一个。这里需要负载均衡,为了方便直接返回1个

    private String getAddress(AbTestRoute route) {

        return route.getAddress().get(0);

    }

7.1.5 转发路由

用到了OkHttpClient,需要引入下面的maven包

1

2

3

4

5

<dependency>

    <groupId>com.squareup.okhttp</groupId>

    <artifactId>okhttp</artifactId>

    <version>2.7.5</version>

</dependency>

转发路由的逻辑如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

/**

     * 转发请求到目的地址

     *

     * @param address 格式为:"IP:端口号"

     */

    private void forward(String address) {

        try {

            OkHttpClient httpClient = new OkHttpClient();

 

            RequestContext context = RequestContext.getCurrentContext();

            HttpServletRequest request = context.getRequest();

            String method = request.getMethod();

            String uri = this.helper.buildZuulRequestURI(request);

 

            // 对URI进行处理todo,修改为http://IP:端口/uri

            uri = "http://" + address + uri;

 

            Headers.Builder headers = new Headers.Builder();

            Enumeration<String> headerNames = request.getHeaderNames();

            while (headerNames.hasMoreElements()) {

                String name = headerNames.nextElement();

                Enumeration<String> values = request.getHeaders(name);

 

                while (values.hasMoreElements()) {

                    String value = values.nextElement();

                    headers.add(name, value);

                }

            }

            InputStream inputStream = request.getInputStream();

            RequestBody requestBody = null;

            if (inputStream != null && HttpMethod.permitsRequestBody(method)) {

                MediaType mediaType = null;

                if (headers.get("Content-Type") != null) {

                    mediaType = MediaType.parse(headers.get("Content-Type"));

                }

                requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));

            }

            Request.Builder builder = new Request.Builder()

                    .headers(headers.build())

                    .url(uri)

                    .method(method, requestBody);

            Response response = httpClient.newCall(builder.build()).execute();

 

            LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();

 

            for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {

                responseHeaders.put(entry.getKey(), entry.getValue());

            }

 

            this.helper.setResponse(response.code(), response.body().byteStream(),

                    responseHeaders);

 

            // prevent SimpleHostRoutingFilter from running

            // 阻塞SimpleHostRoutingFilter执行

            context.setRouteHost(null);

 

        } catch (Exception e) {

            LOGGER.error("执行错误:{}", e);

        }

    }

7.1.6 测试

1、客户端

1

2

3

4

5

6

7

8

9

@RequestMapping(value = "/testSpringCloudRestWithGateWay", method = RequestMethod.GET)

    @ResponseBody

    public ResponseDemo testSpringCloudRestWithGateWay() {

        String response = springCloudRestTemplateService.queryWithGateWay();

        ResponseDemo demo = new ResponseDemo();

        demo.setMessage(response);

        demo.setResultCode("000");

        return demo;

    }

默认是请求客户端服务Provider1,如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public String queryWithGateWay() {

 

        StringBuilder url = new StringBuilder("http://");

        // 1.添加网关

        url.append("gateway");

        // 2.添加服务ID,不需要具体的IP和端口号

        url.append("/prodvider1");

        // 3.下游服务的接口

        url.append("/query");

 

        String response = restTemplate.getForObject(url.toString(), String.class);

        return response;

 

    }

2、Provider1提供的query接口逻辑如下

1

2

3

4

5

6

7

8

  @RequestMapping("/query")

    @ResponseBody

    public ViewVo query(HttpServletRequest request) {

        ViewVo vo = new ViewVo();

        vo.setName("provider1");

        vo.setDecription("provider1");

        return vo;

    }

3、Provider2提供的query接口逻辑如下

1

2

3

4

5

6

7

8

   @RequestMapping("/query")

    @ResponseBody

    public ViewVo query(HttpServletRequest request) {

        ViewVo vo = new ViewVo();

        vo.setName("provider2");

        vo.setDecription("provider2");

        return vo;

    }

4、返回结果为

81328642

7.2 关于其他策略

1、根据用户ID后两位实现AB test的路由策略

对于部分用户可以看到新功能或者新服务,除了上面的随机概率方式,我们还可以根据userId的后两位,如为00,10,20,30…90结尾用户可见,此时就占10%的用户可以看到了新服务了。

1

2

3

4

5

6

7

8

9

10

11

12

public class AbTestRoute {

    // 保存用户后两位信息,00,10,..90。

    private List<String> rule = Lists.newArrayList;

    // 格式为 "IP:端口号"

    private List<String> address = Lists.newArrayList();

    // 灰度开关

    private boolean routeSwitch;

    

    ..................

    getter和setter方法

    ..................

}

2、项目中也常会遇到,对于上线新产品,需要开小流量,即只有部分用户可以看到这个产品,这种场景就不需要通过Zull来完成了,只需要在代码添加开关就可以了。因为这些代码需要全量上线的。对于代码只上线部分机器,并进行观察的场景,可以考虑Zuul实现灰度

8 Zull相关配置

8.1 超时配置

1、全部服务

1

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000  // 设置2s超时

2、指定服务

如果服务ID为provider1,则替换default,如下

1

hystrix.command.provider1.execution.isolation.thread.timeoutInMilliseconds=2000  // 设置2s超时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值