Spring Cloud服务网关

常用的微服务架构
微服务架构

种种架构的不足就是缺少权限控制, 现在我们需要增加的就是服务网关了,
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、
均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,
为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,
使得服务集群主体能够具备更高的可复用性和可测试性。

下面我们通过实例例子来使用一下Zuul来作为服务的路有功能

准备工作

在使用Zuul之前,我们先构建一个服务注册中心、以及两个简单的服务,比如:我构建了一个simple-service,
一个simple-service1。然后启动eureka-server和这两个服务。
通过访问eureka-server,我们可以看到simple-service和simple-service1已经注册到了服务中心。

开始使用Zuul

引入依赖spring-cloud-starter-zuul、spring-cloud-starter-eureka,如果不是通过指定serviceId的方式,eureka依赖不需要,
但是为了对服务集群细节的透明性,还是用serviceId来避免直接引用url的方式吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

应用主类使用@EnableZuulProxy注解开启Zuul

1
2
3
4
5
6
7
8
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}

@SpringCloudApplication注解,通过源码我们看到,
它整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker

application.properties中配置Zuul应用的基础信息,如:应用名、服务端口等。

1
2
3
spring.application.name=api-gateway
server.port=5555
eureka.client.serviceUrl.defaultZone=http://localhost:11111/eureka/

在Zuul中提供了两种映射方式:

通过url直接映射,我们可以如下配置:

1
2
3
# routes to url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/

该配置,定义了,所有到Zuul的中规则为:/api-a-url/**的访问都映射到http://localhost:2222/上,
也就是说当我们访问http://localhost:5555/api-a-url/add?a=1&b=2的时候,Zuul会将该请求路由到:
http://localhost:2222/add?a=1&b=2上。

通过serviceId的映射

1
2
3
4
5
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B

通过浏览器就可以访问了。如http://localhost:5555/api-b

服务过滤

在完成了服务路由之后,我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源。
所以我们需要利用Zuul的过滤器来实现我们对外服务的安全控制。
在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。

自定义过滤器的实现,需要继承ZuulFilter,需要重写实现下面四个方法:
filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
pre:可以在请求被路由之前调用
routing:在路由请求时候被调用
post:在routing和error过滤器之后被调用
error:处理请求时发生错误时被调用
filterOrder:通过int值来定义过滤器的执行顺序
shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关,默认false,不执行
run:过滤器的具体逻辑

然后在主类中注入该类

1
2
3
4
@Bean
public FilterTest filterTest() {
return new FilterTest();
}

最后在浏览器中输入http://localhost:5555/api-b/add?a=1&b=2就不可以访问,
http://localhost:5555/api-b/add?a=1&b=2&c=d可以访问了

根据之前对filterType生命周期介绍,可以参考下图去理解,并根据自己的需要在不同的生命周期中去实现不同类型的过滤器。
filterType生命周期

总结:
服务网关的优点:
不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。
实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,
将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,
让服务本身更集中关注业务逻辑的处理。
实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。

Spring Cloud分布式配置

Spring Cloud Config为服务端和客户端提供了分布式系统的外部化配置支持。
配置服务器为各应用的所有环境提供了一个中心化的外部配置。它实现了对服务端和客户端对Spring Environment
和PropertySource抽象的映射,所以它除了适用于Spring构建的应用程序,也可以在任何其他语言运行的应用程序中使用。
作为一个应用可以通过部署管道来进行测试或者投入生产,我们可以分别为这些环境创建配置,
并且在需要迁移环境的时候获取对应环境的配置来运行。
配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的
管理和访问配置内容。当然他也提供本地化文件系统的存储方式,下面从这两方面介绍如何使用分布式配置来存储微服务
应用多环境的配置内容。

构建Config Server

场景Spring Boot初始项目,然后在pom.xml中添加如下依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

在Spring Boot主类中添加@EnableConfigServer注解,开启config-server功能

1
2
3
4
5
6
7
8
9
@EnableConfigServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}

在application.properties中配置服务信息以及git信息,如:

1
2
3
4
5
6
7
8
9
10
11
spring.application.name=config-server
server.port=7001
# git管理配置
spring.cloud.config.server.git.uri=https://github.com/yangzhiw/Simple-Spring-Cloud
spring.cloud.config.server.git.searchPaths=config-repo
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=password
# 开启本地配置
# spring.profiles.active=native

spring.cloud.config.server.git.uri:配置git仓库位置
spring.cloud.config.server.git.searchPaths:配置仓库路径下的相对搜索位置,可以配置多个
spring.cloud.config.server.git.username:访问git仓库的用户名
spring.cloud.config.server.git.password:访问git仓库的用户密码

Spring Cloud Config也提供本地存储配置的方式。我们只需要设置属性spring.profiles.active=native,
Config Server会默认从应用的src/main/resource目录下检索配置文件。也可以通过
spring.cloud.config.server.native.searchLocations=file:D:/properties/属性来指定配置文件的位置。
虽然Spring Cloud Config提供了这样的功能,但是为了支持更好的管理内容和版本控制的功能,
还是推荐使用git的方式

服务端验证

在项目中创建一个config-repo文件夹,配置了四个配置文件
配置文件分别为:
juzi.properties
juzi-dev.properties
juzi-test.properties
juzi-prod.properties
可以在四个配置文件中设置属性

URL与配置文件的映射关系如下:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
上面的url会映射{application}-{profile}.properties对应的配置文件,{label}对应git上不同的分支,默认为master。
然后我们就可访问我们的配置文件了。如:http://localhost:7001/juzi/prod/
这便是访问生成环境的配置文件。

服务端配置映射

在完成并验证了配置服务中心之后,下面看看我们如何在微服务应用中获取配置信息。

创建一个Spring Boot应用,在pom.xml中引入spring-cloud-starter-config依赖,完整依赖关系如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

创建bootstrap.properties配置,来指定config server,例如:

1
2
3
4
5
6
spring.application.name=didispace
spring.cloud.config.profile=dev
spring.cloud.config.label=master
spring.cloud.config.uri=http://localhost:7001/
server.port=7002

这里需要格外注意:上面这些属性必须配置在bootstrap.properties中,config部分内容才能被正确加载。
因为config的相关配置会先于application.properties,
而bootstrap.properties的加载也是先于application.properties。

这里需要格外注意:上面这些属性必须配置在bootstrap.properties中,config部分内容才能被正确加载。
因为config的相关配置会先于application.properties,
而bootstrap.properties的加载也是先于application.properties。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RefreshScope
@RestController
class TestController {
@Value("${from}")
private String from;
@RequestMapping("/from")
public String from() {
return this.from;
}
}

通过@Value(“${from}”)绑定配置服务中配置的from属性。

启动该应用,并访问:http://localhost:7002/from ,我们就可以根据配置内容输出对应环境的from内容了。

Spring Cloud路断器

什么是断路器

断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载,
当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,
通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。
这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延.

Netflix Hystrix

在Spring Cloud中使用了Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,
该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

下面我们来看看如何使用Hystrix。

准备工作

启动spring-could,spring-cloud-provide,eureka-ribbon项目

pom.xml中引入依赖hystrix依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

在eureka-ribbon的主类RibbonApplication中使用@EnableCircuitBreaker注解开启断路器功能

1
2
3
4
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class EurekaRibbonApplication {

新增也service类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class SimpleService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "addServiceFallback")
public String addService() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
public String addServiceFallback() {
return "error";
}
}

改变之前Controller中的方法使其掉用Service中的addService即可,没开服务可以发现浏览器中返回了error错误了。
完成

Spring Cloud服务消费者

Ribbon

Ribbon是一个基于HTTP和TCP的客户端负载均衡器。
Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。

1.启动之前的spring-could
2.启动服务提供方spring-cloud-provide

修改spring-cloud-provide中的server-port为2223,再启动一个服务提供方:service

使用Ribbon实现客户端负载均衡的消费者

在pom.xml中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。
创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
}

创建ConsumerController来消费COMPUTE-SERVICE的add服务。
通过直接RestTemplate来调用服务,计算10 + 20的值。

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
}

application.properties中配置eureka服务注册中心

1
2
3
4
spring.application.name=ribbon-consumer
server.port=3333
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

然后启动该项目,在浏览器中输入http://localhost:3333/add
在2222和2223的端口总能看到有其中一个控制台有日志打出,关闭一个,还能继续运行
即通过ribbon实现了负载均衡了。

Spring Cloud服务注册与发现

Spring Cloud简介

Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、
服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、
Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、
pring Cloud Zookeeper、Spring Cloud CLI等项目。

微服务架构

“微服务架构”在这几年非常的火热,以至于关于微服务架构相关的产品社区也变得越来越活跃(比如:netflix、dubbo),
Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。

服务注册与发现

这里用到Spring Cloud Netflix,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,
它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用
模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),
客户端负载均衡(Ribbon)等。
所以,我们这里的核心内容就是服务发现模块:Eureka。下面我们动手来做一些尝试。

创建“服务注册中心”

创建一个基础的Spring Boot工程,并在pom.xml中引入需要的依赖内容:

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。
这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaServer
public class SpringCloudApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudApplication.class, args);
}
}

在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,
所以我们需要禁用它的客户端注册行为,只需要在application.properties中问增加如下配置:

1
2
3
4
5
server.port=11111
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

启动项目,在浏览器输入网址:http://localhost:11111
可以看到注册中心已经启动了

创建服务提供方

现在 提供计算功能的微服务模块,我们实现一个RESTful API,通过传入两个参数a和b,最后返回a + b的结果。

首先,创建一个基本的Spring Boot应用,在pom.xml中,加入如下配置:

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

其次,实现/add请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class ComputeController {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/add" ,method = RequestMethod.GET)
public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
ServiceInstance instance = client.getLocalServiceInstance();
Integer r = a + b;
logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
return r;
}
}

最后在主类中通过加上@EnableDiscoveryClient注解,
该注解能激活Eureka中的DiscoveryClient实现,才能实现Controller中对服务信息的输出。

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudProvideApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudProvideApplication.class, args);
}
}

application.properties的配置

1
2
3
4
5
spring.application.name=compute-service
server.port=2222
eureka.client.serviceUrl.defaultZone=http://localhost:11111/eureka/

通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。

eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。

为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。
启动该工程后,再次访问:http://localhost:11111/