博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringCloud系列第07节之服务网关Zuul
阅读量:7185 次
发布时间:2019-06-29

本文共 16299 字,大约阅读时间需要 54 分钟。

hot3.png

为什么需要网关

之前的系列文章中演示了,服务提供方和消费方都注册到注册中心,使得消费方能够直接通过 ServiceId 访问服务方

实际情况是:通常我们的服务方可能都需要做 接口权限校验、限流、软负载均衡 等等

而这类工作,完全可以交给服务方的更上一层:服务网关,来集中处理

这样的目的:保证微服务的无状态性,使其更专注于业务处理

所以说,服务网关是微服务架构中一个很重要的节点,Spring Cloud Netflix 中的 Zuul 就担任了这样的角色

当然了,除了 Zuul 之外,还有很多软件也可以作为 API Gateway 的实现,比如 Nginx Plus、Kong 等等

网关映射

通过服务路由的功能,可以在对外提供服务时,只暴露 Zuul 中配置的调用地址,而调用方就不需要了解后端具体的微服务主机

Zuul 提供了两种映射方式:URL 映射和 ServiceId 映射(后者需要将 Zuul 注册到注册中心,使之能够发现后端的微服务)

ServiceId 映射的好处是:它支持软负载均衡,基于 URL 的方式是不支持的(实际测试也的确如此)

示例代码

示例代码如下(也可以直接从 Github 下载:)

它是由六个模块组成的 Maven 工程,其中包含兩个服务提供方、两个服务网关、一个注册中心、一个服务消费方

它们的关系是:消费方走软负载均衡调用两个服务网关,服务网关根据路由配置,再一次走软负载均衡调用两个服务提供方

这是公共的 pom.xml

4.0.0
com.jadyer.demo
demo-cloud-07-zuul
1.1
pom
service-client
service-discovery
service-gateway-01
service-gateway-02
service-server-01
service-server-02
UTF-8
org.springframework.boot
spring-boot-starter-parent
1.4.5.RELEASE
org.springframework.cloud
spring-cloud-dependencies
Camden.SR6
pom
import
org.apache.maven.plugins
maven-compiler-plugin
3.5.1
1.7
1.7

注册中心

这是注册中心的 pom.xml

4.0.0
com.jadyer.demo
demo-cloud-07-zuul
1.1
service-discovery
UTF-8
org.springframework.cloud
spring-cloud-starter-eureka-server

这是注册中心的配置文件 /src/main/resources/application.yml

server:  port: 1100eureka:  server:    enable-self-preservation: false       # 关闭自我保护模式(缺省为打开)    eviction-interval-timer-in-ms: 1000   # 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)  client:    # 设置是否从注册中心获取注册信息(缺省true)    # 因为这是一个单点的EurekaServer,不需要同步其它EurekaServer节点的数据,故设为false    fetch-registry: false    # 设置是否将自己作为客户端注册到注册中心(缺省true)    # 这里为不需要(查看@EnableEurekaServer注解的源码,会发现它间接用到了@EnableDiscoveryClient)    register-with-eureka: false    # 在未设置defaultZone的情况下,注册中心在本例中的默认地址就是http://127.0.0.1:1100/eureka/    # 但奇怪的是,启动注册中心时,控制台还是会打印这个地址的节点:http://localhost:8761/eureka/    # 而实际服务端注册时,要使用1100端口的才能注册成功,8761端口的会注册失败并报告异常    serviceUrl:      # 实际测试:若修改尾部的eureka为其它的,比如/myeureka,注册中心启动没问题,但服务端在注册时会失败      # 报告异常:com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server      defaultZone: http://127.0.0.1:${server.port}/eureka/

这是注册中心的 SpringBoot 启动类 ServiceDiscoveryBootStrap.java

package com.jadyer.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;//创建服务注册中心@EnableEurekaServer@SpringBootApplicationpublic class ServiceDiscoveryBootStrap {    public static void main(String[] args) {        SpringApplication.run(ServiceDiscoveryBootStrap.class, args);    }}

服务提供方01

这是第一个服务提供方的 pom.xml

4.0.0
com.jadyer.demo
demo-cloud-07-zuul
1.1
service-server-01
UTF-8
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-eureka

这是第一个服务提供方的配置文件 /src/main/resources/application.yml

server:  port: 2100spring:  application:    name: CalculatorServer                        # 指定发布的微服务名(以后调用时,只需该名称即可访问该服务)eureka:  instance:    instance-id: ${spring.application.name}:${server.port}    prefer-ip-address: true                       # 设置微服务调用地址为IP优先(缺省为false)    lease-renewal-interval-in-seconds: 5          # 心跳时间,即服务续约间隔时间(缺省为30s)    lease-expiration-duration-in-seconds: 15      # 发呆时间,即服务续约到期时间(缺省为90s)  client:    healthcheck:      enabled: true                               # 开启健康检查(依赖spring-boot-starter-actuator)    serviceUrl:      defaultZone: http://127.0.0.1:1100/eureka/  # 指定服务注册中心的地址

这是第一个服务提供方的 SpringBoot 启动类 ServiceServer01BootStarp.java

package com.jadyer.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;/** * 通过 @EnableEurekaClient 注解,为服务提供方赋予注册和发现服务的能力 * -------------------------------------------------------------------------------------------- * 也可以使用org.springframework.cloud.client.discovery.@EnableDiscoveryClient注解 * 详见以下两篇文章的介绍 * http://cloud.spring.io/spring-cloud-static/Camden.SR3/#_registering_with_eureka * https://spring.io/blog/2015/01/20/microservice-registration-and-discovery-with-spring-cloud-and-netflix-s-eureka * -------------------------------------------------------------------------------------------- * Created by 玄玉
on 2017/1/9 16:00. */@EnableEurekaClient@SpringBootApplicationpublic class ServiceServer01BootStarp { public static void main(String[] args) { SpringApplication.run(ServiceServer01BootStarp.class, args); }}

这是第一个服务提供方暴露的数学运算服务 CalculatorController.java

package com.jadyer.demo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/** * 服务提供方暴露的数学运算服务 * Created by 玄玉
on 2017/1/9 16:00. */@RestControllerpublic class CalculatorController { private final Logger logger = LoggerFactory.getLogger(getClass()); @Resource private DiscoveryClient client; @RequestMapping("/add") public int add(int a, int b){ //加运算 int result = a + b; //输出服务信息 ServiceInstance instance = client.getLocalServiceInstance(); logger.info("uri={},serviceId={},result={}", instance.getUri(), instance.getServiceId(), result); //返回结果 return result; }}

服务提供方02

除了启动端口为2200外,其代码与服务提供方01的完全相同

服务网关01

这是第一个服务网关的 pom.xml

4.0.0
com.jadyer.demo
demo-cloud-07-zuul
1.1
service-gateway-01
UTF-8
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul

这是第一个服务网关的配置文件 /src/main/resources/application.yml

server:  port: 4100spring:  application:    name: jadyer-api-gateway       # 指定发布的微服务名(以后调用时,只需该名称即可访问该服务)zuul:  ignored-services: "*"            # 设置忽略的服务,即配置后将不会被路由(但对于明确配置在路由中的,将不会被忽略)  routes:    api-cal-url:                   # 基于 URL 的映射(这里自定义路由的名字为 api-cal-url,它可任意指定,唯一即可)      path: /cal/**                # http://127.0.0.1:4100/cal/add?a=7&b=17会路由至http://127.0.0.1:2100/add?a=7&b=17      url: http://127.0.0.1:2100/    api-add:                       # 基于 ServiceId 的映射(自定义路由的名字)      path: /caladd/**             # http://127.0.0.1:4100/caladd/add?a=6&b=16会路由至CalculatorServer服务的/add?a=6&b=16      serviceId: CalculatorServer    CalculatorServer:              # 基于 ServiceId 的映射(路由的名字等于 ServiceId 的情况下,serviceId 属性可以省略)      path: /mycall/**             # http://127.0.0.1:4100/mycall/add?a=5&b=15会路由至CalculatorServer服务的 /add?a=5&b=15      #serviceId: CalculatorServereureka:  instance:    instance-id: ${spring.application.name}:${server.port}    prefer-ip-address: true                   # 设置微服务调用地址为IP优先(缺省为false)    lease-renewal-interval-in-seconds: 5      # 心跳时间,即服务续约间隔时间(缺省为30s)    lease-expiration-duration-in-seconds: 15  # 发呆时间,即服务续约到期时间(缺省为90s)  client:    healthcheck:      enabled: true                               # 开启健康检查(依赖spring-boot-starter-actuator)    serviceUrl:      defaultZone: http://127.0.0.1:1100/eureka/  # 指定服务注册中心的地址

这是第一个服务网关的 SpringBoot 启动类 ServiceGateway01BootStarp.java

package com.jadyer.demo;import com.netflix.zuul.ZuulFilter;import org.springframework.boot.SpringApplication;import org.springframework.cloud.client.SpringCloudApplication;import org.springframework.cloud.netflix.zuul.EnableZuulProxy;import org.springframework.context.annotation.Bean;//注意不是@EnableZuulServer@EnableZuulProxy//注意这里使用了更加简化的@SpringCloudApplication@SpringCloudApplicationpublic class ServiceGateway01BootStarp {    /**     * 这里的方法返回值,不能写成com.netflix.zuul.IZuulFilter     * 可以写成com.netflix.zuul.ZuulFilter,或者com.jadyer.demo.GatewayFilter     * 虽然语法上允许返回IZuulFilter,但实际测试发现返回IZuulFilter时,网关功能却没有生效     */    @Bean    public ZuulFilter gatewayFilter() {        return new GatewayFilter();    }    public static void main(String[] args) {        SpringApplication.run(ServiceGateway01BootStarp.class, args);    }}

这是第一个服务网关中,用于控制接口访问权限的过滤器 GatewayFilter.java

package com.jadyer.demo;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletRequest;/** * 利用Zuul的过滤器,可以实现对外服务的安全控制 * ------------------------------------------------------------------------- * 这里实现了在请求被路由之前检查请求中是否有accesstoken参数 * 若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误 * http://127.0.0.1:4100/mycall/add?a=11&b=22:返回"权限不足" * http://127.0.0.1:4100/mycall/add?a=11&b=22&accesstoken=token:返回正常 * ------------------------------------------------------------------------- * Created by 玄玉
on 2017/1/14 15:05. */public class GatewayFilter extends ZuulFilter { private Logger log = LoggerFactory.getLogger(getClass()); /** * 如下所示,Zuul定义了四种不同生命周期的过滤器类型 * pre :可以在请求被路由之前调用 * routing:在路由请求时候被调用 * post :在routing和error过滤器之后被调用 * error :处理请求时发生错误时被调用 */ @Override public String filterType() { return "pre"; } @Override public int filterOrder() { //通过int值来定义过滤器的执行顺序 return 0; } @Override public boolean shouldFilter() { //设置该过滤器总是生效,即总是执行拦截请求 return true; } /** * 过滤器的具体逻辑 */ @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("收到 %s 请求 %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("accesstoken"); if(accessToken == null) { ctx.getResponse().setContentType("text/html;charset=UTF-8"); log.warn("accesstoken为空"); //令zuul过滤该请求,不对其进行路由 ctx.setSendZuulResponse(false); //设置其返回的错误码和报文体 //这里没有设置应答码为401,是因为401会导致客户端走到它的断路器里面(HystrixCalculatorService) //所有设置为200,让应答报文体跳过客户端的断路器,返回给前台 ctx.setResponseStatusCode(200); ctx.setResponseBody("权限不足"); return null; } log.info("accesstoken验证通过"); return null; }}

服务网关02

除了启动端口为4200外,其代码与服务网关01的完全相同

服务消费方

这是服务消费方的 pom.xml

4.0.0
com.jadyer.demo
demo-cloud-07-zuul
1.1
service-client
UTF-8
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-feign

这是服务消费方的配置文件 /src/main/resources/application.yml

server:  port: 3100spring:  application:    name: client-consumer-feigneureka:  instance:    instance-id: ${spring.application.name}:${server.port}    prefer-ip-address: true    lease-renewal-interval-in-seconds: 5    lease-expiration-duration-in-seconds: 15  client:    healthcheck:      enabled: true    serviceUrl:      defaultZone: http://127.0.0.1:1100/eureka/

这是服务消费方 SpringBoot 启动类 ServiceClientBootStarp.java

package com.jadyer.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.cloud.netflix.feign.EnableFeignClients;//开启Feign功能(无需显式@EnableCircuitBreaker,其已含此功能)@EnableFeignClients@EnableEurekaClient@SpringBootApplicationpublic class ServiceClientBootStarp {    public static void main(String[] args) {        SpringApplication.run(ServiceClientBootStarp.class, args);    }}

这是服务消费方的,包含了断路器配置的,调用服务网关的实现 CalculatorService.java

package com.jadyer.demo.feign;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;//绑定該接口到服务网关的jadyer-api-gateway服务,并通知Feign组件对该接口进行代理(不需要编写接口实现)@FeignClient(value="jadyer-api-gateway", fallback=CalculatorService.HystrixCalculatorService.class)public interface CalculatorService {    @PathVariable這種也是支持的    //@RequestMapping(value="/mycall/add/{a}", method=RequestMethod.GET)    //int myadd(@PathVariable("a") int a, @RequestParam("b") int b, @RequestParam("accesstoken") String accesstoken);    //通过SpringMVC的注解来配置所綁定的服务下的具体实现    @RequestMapping(value="/mycall/add", method=RequestMethod.GET)    String myadd(@RequestParam("a") int a, @RequestParam("b") int b, @RequestParam("accesstoken") String accesstoken);    /**     * 这里采用和SpringCloud官方文档相同的做法,把fallback类作为内部类放入Feign接口中     * http://cloud.spring.io/spring-cloud-static/Camden.SR6/#spring-cloud-feign-hystrix     * (也可以外面独立定义该类,个人觉得没必要,这种东西写成内部类最合适)     */    @Component    class HystrixCalculatorService implements CalculatorService {        @Override        public String myadd(@RequestParam("a") int a, @RequestParam("b") int b, @RequestParam("accesstoken") String accesstoken) {            return "负999";        }    }}

这是服务消费方的调用示例 ConsumerController.java

package com.jadyer.demo.feign;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/** * 服务调用方 * Created by 玄玉
on 2017/1/10 18:23. */@RestController@RequestMapping("/demo/feign")public class ConsumerController { @Resource private CalculatorService calculatorService; @RequestMapping("/toadd") String toadd(int a, int b, String accesstoken){ return calculatorService.myadd(a, b, accesstoken); }}

验证

分别用浏览器多次访问以下地址,然后观察两个服务提供方、两个服务网关的控制台输出即可

转载于:https://my.oschina.net/wuweixiang/blog/3025174

你可能感兴趣的文章
采集HeapDump、ThreadDump
查看>>
DWZ 解决隐藏左侧菜单项 刷新无法点开的bug
查看>>
vc++中如何让控制台程序不弹出DOS窗口
查看>>
ThinkpadR61-7755BH1安装Mac Leopard10.5.2
查看>>
python 验证数据类型函数
查看>>
每天一个linux命令(26):用SecureCRT来上传和下载文件
查看>>
在读iOS官方文档时,里面有很多你不懂的单词,不要担心
查看>>
Fedora 17: Grub Rescue
查看>>
不是每个在你身上拉屎的都是你的敌人
查看>>
Linq补漏笔记二:Linq语言技术基础
查看>>
#pragma once
查看>>
node.js打开浏览器
查看>>
四、Oracle的复杂查询
查看>>
logback高级特性一
查看>>
超棒的自定义超酷滚动条jQuery插件 - Perfect Scrollbar
查看>>
进程组 会话组 控制终端
查看>>
Maclean答网友问:深入进阶学习Oracle数据库的规划
查看>>
LINUX下CDT和EDT时区的切换
查看>>
[Android] 备份手机上的超级终端、VIM
查看>>
读写分离,就该这么改进
查看>>