1 Zuul概述

API网关:

  • 微服务系统统一的调用入口
  • 统一的权限校验
  • 集成ribbon
  • 集成hystrix

1.1 统一的调用入口

第一步:创建springboot项目sp11-zuul
image.png
第二步:添加依赖eureka、zuul依赖和sp01依赖
image.png

<dependency>
   <groupId>cn.tedu</groupId>
   <artifactId>sp01-commons</artifactId>
   <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

第三步:主程序添加注解@EnableZuulProxy、@EnableDiscoveryClient,启动eureka和zuul。

package cn.tedu.sp11;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class Sp11ZuulApplication {
   public static void main(String[] args) {
      SpringApplication.run(Sp11ZuulApplication.class, args);
   }
}

第四步:启动服务器,进行测试
http://eureka1:2001
http://localhost:3001/item-service/35
http://localhost:3001/user-service/7
http://localhost:3001/order-service/

1.2 添加过滤器

四种ZuulFilter过滤器:

  • pre:在请求被路由(转发)之前调用
  • route:在路由(请求)转发时被调用
  • error:服务网关发生异常时被调用
  • post:在路由(转发)请求后调用

第一步:创建AccessFilter过滤器类

  • 继承ZuulFilter接口,并重写里接口的方法。
  • 类上添加@Component注解,将类交给spring管理。
package cn.tedu.sp11.filter;
import cn.tedu.web.util.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
@Slf4j
public class AccessFilter extends ZuulFilter {
    //设置过滤器的类型:pre,routing,post,error
 @Override
 public String filterType() {
        //return "pre";
 return FilterConstants.PRE_TYPE;//两种写法相同,前置过滤器。
 }
    //设置过滤器的顺序号,该过滤器顺序要 > 5,才能得到 serviceid @Override
 public int filterOrder() {
        return 6;
    }
    //返回布尔值,对客户端的当前请求进行判断,判断当前请求是否要执行过滤代码
 @Override
 public boolean shouldFilter() {
        //如果调用item-service,要执行过滤代码判断权限
 //否则不执行过滤代码
 //获取调用的服务id
 RequestContext ctx = RequestContext.getCurrentContext();//请求上下文对象
 String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY);
        //判断是不是 item-service return "item-service".equalsIgnoreCase(serviceId);//忽略大小写判断
 }
    //过滤代码
 @Override
 public Object run() throws ZuulException {
        //有token允许访问,没有token阻止访问
 //http://localhost:3001/
 //获取request对象
 RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        //接收ttoken参数
 String token = request.getParameter("token");
        //如果没有收到token参数,认为用户没登录,阻止他继续调用
 if (StringUtils.isBlank(token)){//commons.lang3.StringUtils
 //阻止他继续调用
 ctx.setSendZuulResponse(false);//不再向后台服务转发
 //设置向客户端发送的响应
 ctx.addZuulResponseHeader("Content-Type", "application/json");
            //JsonResult - {code:401,msg:"not login",data:null}
 ctx.setResponseBody(JsonResult.err().msg("not login").toString());
        }
        //目前该返回值没有被使用
 return null;
    }
}

第二步:启动sp02、sp11、sp05服务器进行测试

1.3 集成ribbon

默认启用了ribbon的负载均衡;默认启用重试,zuul不推荐重试。

1.3.1 启动负载均衡

启动sp02-8002服务器,进行负载均衡测试,会访问8001和8002两个端口对应的服务器。
http://localhost:3001/item-service/35?token=1234

1.3.2 重试

第一步:添加retry依赖

<dependency>
   <groupId>org.springframework.retry</groupId>
   <artifactId>spring-retry</artifactId>
</dependency>

第二步:配置pom文件启用重试,也可配置重试参数

spring:
  application:
    name: zuul
server:
  port: 3001
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
#zuul 路由配置可以省略,缺省以服务 id 作为访问路径,默认配置会从eureka中获取
zuul:
  routes:
    item-service: /item-service/**
    user-service: /user-service/**
    order-service: /order-service/**
    
#配置启用重试
 retryable: true
# 配置重试参数
 ribbon:
    ConnectTimeout: 1000
    ReadTimeout: 1000
    MaxAutoRetriesNextServer: 1
    MaxAutoRetries: 1

第三步:重启sp11服务器,进行测试
sp02-8001和8002的服务器访问延迟,会进行超时访问和转发。
http://localhost:3001/item-service/35?token=1234

1.4 继承hystrix

0配置,默认已经启用hystrix。

@Override
 public String getRoute() {
        return "item-service";
    }
 * 返回 service-id,针对哪个服务进行降级
 * 返回 * / null 表示针对所有服务都进行降级

第一步:降级和熔断的实现

  • 创建降级类实现FallbackProvider接口并重写接口内的方法
  • 实现类上添加@Component注解

降级类ItemFB:

package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
public class ItemFB implements FallbackProvider {
    /**
 * 返回 service-id,针对哪个服务进行降级
 * 返回 * / null 表示针对所有服务都进行降级
 * @return
 */ 
 @Override
 public String getRoute() {
        return "item-service";
    }
    /*
 设置向客户端返回的降级响应
 response包含200,协议体,协议体
 */ @Override
 public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            //下面三个方法都是协议号
 @Override
 public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            @Override
 public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }
            @Override
 public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }
            @Override
 public void close() {
            }
            //协议体
 @Override
 public InputStream getBody() throws IOException {
                //JsonResult - {code:401,msg:"not login",data:null}
 String json = JsonResult.ok().msg("调用后台商品服务失败").toString();
                return new ByteArrayInputStream(json.getBytes("UTF-8"));
            }
            //协议头
 @Override
 public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.add("Content-Type", "application/json;charset=UTF-8");
                return header;
            }
        };
    }
}

降级类OrderFB:

package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
public class OrderFB implements FallbackProvider {
    /**
 * 返回 service-id,针对哪个服务进行降级
 * 返回 * / null 表示针对所有服务都进行降级
 * @return
 */ @Override
 public String getRoute() {
        return "order-service";
    }
    /*
 设置向客户端返回的降级响应
 response包含200,协议体,协议体
 */ @Override
 public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            //下面三个方法都是协议号
 @Override
 public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            @Override
 public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }
            @Override
 public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }
            @Override
 public void close() {
            }
            //协议体
 @Override
 public InputStream getBody() throws IOException {
                //JsonResult - {code:401,msg:"not login",data:null}
 String json = JsonResult.ok().msg("调用后台订单服务失败").toString();
                return new ByteArrayInputStream(json.getBytes("UTF-8"));
            }
            //协议头
 @Override
 public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.add("Content-Type", "application/json;charset=UTF-8");
                return header;
            }
        };
    }
}

第二步:监控查看,设置yml,暴露监控端口

# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

第三步:重启服务sp11,测试查看

image.png

zuul和feign的区别

image.png

zuul:

  • 部署在所有微服务项目之间
  • 网关一般是一个独立的服务,与业务无关
  • 不推荐启用重试
    会造成后台服务器压力翻倍
    重试尽量不部署在最前面,越往后越好

feign:

  • 部署在微服务内部,服务和服务之间调用
  • 不推荐启用hystrix

    一般在最前面进行降级和熔断,类似于电箱断路器,只在入户位置部署
    不在微服务内部部署hystrix,否则会引起混乱


木安
13 声望6 粉丝