API 网关是一个更为智能的应用服务器,它的定义类似面向对象设计模式中的Facade模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由,负载均衡,校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列高级功能
路由规则:
zuul默认会将通过以服务名作为contextPath的方式来创建路由映射,大部分情况下,这样的默认设置已经可以实现我们大部分的路由需求,如果有一些特殊的情况,还可以做一些特别的配置
在spring cloud zuul实现路由功能非常简单,只需要对网关服务增加一些关于路由规则的配置,就能实现传统的路由转发功能,比如
zuul.routes.api-a-url.path=/api-a-url/** zuul.routes.api-a-url.url=http://localhost:8080 |
其中api-a-url 为路由的名字,可以任意定义
上面配置所表达的意思是
所有符合/api-a-url/**规则的访问都被路由转发到http://localhost:8080/地址上,比如当我们访问
http://{zuul.hostname}:{zuul.port}/api-a-url/hello的时候,API网关服务会将请求路由到http://localhost:8080/hello
提供的微服务接口上
面向服务的路由
spring cloud zuul实现了与 spring cloud eureka的无缝整合,我们可以让路由的path不是映射具体的url,而是让它
映射到某个具体的服务,而具体的url则交给eureka的服务发现机制去自动维护
spring.application.name=api-gateway server.port=5555 zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=hello-service
zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=feign-consumer
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/ |
签名校验:
开发者可以通过使用zuul来创建各种校验器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,不然就返回错误提示
每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制。在不使用网关的时候,我们可能会对每个服务消费者定义过滤器或拦截器等拦截每个请求,若每个微服务都实现一套鉴权的逻辑,而且每个微服务鉴权的逻辑差不多,这样会带来代码的冗余。
在引入网关层后,我们可以在网关完成校验和过滤,这使得微服务应用接口的开发和测试复杂度也得到相应降低
服务网关 Zuul基本使用
版本 spring boot 1.5.14
eureka注册中心
配置文件 |
server.port=1111 eureka.instance.hostname=localhost #由于该应用为注册中心,所有设置为false,代表不向注册中心注册自己 eureka.client.register-with-eureka=false #由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/ |
启动类 |
package springcloud.eurekaserver;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer // 注解启动一个服务注册中心 @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } } |
服务提供者
配置文件 |
spring.application.name=hello-service eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ |
controller |
package springcloud.eureka_provider_pt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;
@RestController public class HelloController { @Autowired private DiscoveryClient client; @Qualifier("eurekaRegistration") @Autowired private org.springframework.cloud.client.serviceregistry.Registration eurekaRegistration;
@RequestMapping(value = "/hello",method = RequestMethod.GET) public String index(){ System.out.println("serviceId:"+eurekaRegistration.getServiceId()+" hostName:"+eurekaRegistration.getHost()); return "Hello World eueka_provider gz1"; } } |
启动类 |
package springcloud.eureka_provider_pt;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient @SpringBootApplication public class EurekaProviderPtApplication { public static void main(String[] args) { SpringApplication.run(EurekaProviderPtApplication.class, args); } } |
服务消费者
配置文件 |
spring.application.name=ribbon-consumer server.port=9000 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ |
controller |
package springcloud.eureka_consumer_pt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;
@RestController public class ConsumerController { @Autowired RestTemplate restTemplate;
@RequestMapping(value = "/ribbon-consumer",method = RequestMethod.GET) public String helloConsumer(){
return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody(); }
} |
启动类 |
package springcloud.eureka_consumer_pt;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient @SpringBootApplication public class EurekaConsumerPtApplication {
public static void main(String[] args) { SpringApplication.run(EurekaConsumerPtApplication.class, args); }
@Bean @LoadBalanced RestTemplate restTemplate(){ return new RestTemplate(); }
} |
zuul网关
配置文件 |
spring.application.name=api-gateway server.port=5555 zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=ribbon-consumer
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/ |
filter 类 |
package springcloud.zuul;
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
public class AccessFilter extends ZuulFilter{ private static Logger log = LoggerFactory.getLogger(AccessFilter.class); /* 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。 这里定义为 pre,代表会在请求被路由之前执行 routing: 在路由请求时被调用 post: 在routing 和 error 过滤器之后被调用 error: 处理请求时发生错误时被调用 */ @Override public String filterType() { return "pre"; }
/* 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行 */ @Override public int filterOrder() { return 0; }
/* 判断过滤器是否需要被执行,这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数 来指定过滤器的有效范围 */ @Override public boolean shouldFilter() { return true; }
/* 过滤器的具体逻辑。这里我们通过 ctx.setSendZuulResponse(false);令zuul 过滤该请求,不对其进行路由 ,然后ctx.setResponseStatusCode(401);设置了返回错误码, */ @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info("send {} request to {},",request.getMethod(),request.getRequestURL().toString()); Object accessToken = request.getParameter("accessToken"); if(accessToken==null){ log.warn("access token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } log.info("access token ok"); return null; } } |
启动类 |
package springcloud.zuul;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean;
@EnableZuulProxy @SpringCloudApplication public class ZuulApplication {
public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); }
@Bean public AccessFilter accessFilter(){ return new AccessFilter(); }
} |
结果:
参考 spring cloud 微服务实战<程序员DD>