在 Spring Cloud 微服务系统中, 一种常见的负载均衡方式是, 客户端的请求首先经过负载均衡(Ngnix), 再到达服务网关(Zuul 集群), 然后再到具体的服务. 服务统一注册到高可用的服务注册中心集群, 服务的所有的配置文件由配置服务管理, 配置服务的配置文件放在 Git 仓库, 方便开发人员随时改配置.
一, Zuul
Zuul 包含了对请求的路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上, 是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预, 是实现请求校验, 服务聚合等功能的基础.
Zuul 和 Eureka 进行整合, 将 Zuul 自身注册为 Eureka 服务治理下的应用, 同时从 Eureka 中获得其他微服务的消息, 也即以后的访问微服务都是通过 Zuul 跳转后获得. Zuul 服务最终还是会注册进 Eureka.
总体来说 Zuul 提供代理, 路由, 过滤三大功能.
1.1 创建 Zuul 项目
创建一个 spring-cloud-learn-zuul 项目, 创建方式与之前相同, pom.xml 文件如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>com.yuanqinnan</groupId>
- <artifactId>spring-cloud-learn-parent</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- </parent>
- <artifactId>spring-cloud-learn-zuul</artifactId>
- <packaging>jar</packaging>
- <name>spring-cloud-learn-zuul</name>
- <dependencies>
- <!-- Spring Boot Begin -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-tomcat</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- Spring Boot End -->
- <!-- Spring Cloud Begin -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
- </dependency>
- <!-- Spring Cloud End -->
- </dependencies>
- </project>
新建启动类 ZuulApplication, 增加 @EnableZuulProxy 注解
- @SpringBootApplication
- @EnableEurekaClient
- @EnableZuulProxy
- public class ZuulApplication {
- public static void main(String[] args) {
- SpringApplication.run(ZuulApplication.class, args);
- }
- }
application.YAML 配置如下:
- spring:
- application:
- name: spring-cloud-learn-zuul
- server:
- port: 8769
- eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
- zuul:
- routes:
- API-a:
- path: /API/a/**
- serviceId: spring-cloud-learn-consumer-dept-ribbon
- API-b:
- path: /API/b/**
- serviceId: spring-cloud-learn-consumer-dept-feign
- 这个配置文件也很好理解, 主要是配置路由:
- 以 /API/a 开头的请求都转发给 spring-cloud-learn-consumer-dept-ribbon 服务
- 以 /API/b 开头的请求都转发给 spring-cloud-learn-consumer-dept-feign 服务
- 然后再启动之前的所有项目
- 打开浏览器访问: http://localhost:8769/API/a/hi?message=HelloZuul 浏览器显示
- Hi,your message is :"HelloZuul" i am from port:8763
- 打开浏览器访问: http://localhost:8769/API/a/hi?message=HelloZuul 浏览器显示
- Hi,your message is : HelloZuul i am from port : 8763
- 1.2 失败时的回调
- 由于网络震荡或者一些未知原因, 可能会导致网关调用失败, 这个时候我们需要配置一个网关路由失败时的回调, 继承 FallbackProvider
- @Component
- public class ConsumerDeptFeignFallbackProvider implements FallbackProvider {
- @Override
- public String getRoute() {
- // ServiceId, 如果需要所有调用都支持回退, 则 return "*" 或 return null
- return "spring-cloud-learn-consumer-dept-feign";
- }
- /**
- * 如果请求服务失败, 则返回指定的信息给调用者
- * @param
- * @return
- * @date
- */
- @Override
- public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
- return new ClientHttpResponse() {
- /**
- * 网关向 API 服务请求失败了, 但是消费者客户端向网关发起的请求是成功的,
- * 不应该把 API 的 404,500 等问题抛给客户端
- * 网关和 API 服务集群对于客户端来说是黑盒
- * @return
- * @throws IOException
- */
- @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 {
- ObjectMapper objectMapper = new ObjectMapper();
- Map<String, Object> map = new HashMap<>();
- map.put("status", 200);
- map.put("message", "无法连接, 请检查您的网络");
- return new ByteArrayInputStream(objectMapper.writeValueAsString(map).getBytes("UTF-8"));
- }
- @Override
- public HttpHeaders getHeaders() {
- HttpHeaders headers = new HttpHeaders();
- // 和 getBody 中的内容编码一致
- headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
- return headers;
- }
- };
- }
- }
1.3 过滤功能
Zuul 不仅仅只是路由, 还有很多强大的功能, 本节演示一下它的服务过滤功能, 比如用在安全验证方面.
来创建下过滤器功能: 只要继承 ZuulFilter 类并在类上增加 @Component 注解就可以使用服务过滤功能了
- @Component
- public class LoginFilter extends ZuulFilter {
- private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class);
- /**
- * 配置过滤类型, 有四种不同生命周期的过滤器类型
- * 1. pre: 路由之前
- * 2. routing: 路由之时
- * 3. post: 路由之后
- * 4. error: 发送错误调用
- */
- @Override
- public String filterType() {
- return "pre";
- }
- /**
- * 配置过滤的顺序
- */
- @Override
- public int filterOrder() {
- return 0;
- }
- /**
- * 配置是否需要过滤: true / 需要, false / 不需要
- */
- @Override
- public boolean shouldFilter() {
- return false;
- }
- /**
- * 过滤器的具体业务代码
- * @param
- */
- @Override
- public Object run() throws ZuulException {
- RequestContext context = RequestContext.getCurrentContext();
- HttpServletRequest request = context.getRequest();
- logger.info("{}>>> {}", request.getMethod(), request.getRequestURL().toString());
- String token = request.getParameter("token");
- if (token == null) {
- logger.warn("Token is empty");
- context.setSendZuulResponse(false);
- context.setResponseStatusCode(401);
- try {
- context.getResponse().getWriter().write("Token is empty");
- } catch (IOException e) {
- }
- } else {
- logger.info("OK");
- }
- return null;
- }
这里的四个方法:
filterType: 返回一个字符串代表过滤器的类型, 在 Zuul 中定义了四种不同生命周期的过滤器类型
pre: 路由之前
routing: 路由之时
post: 路由之后
error: 发送错误调用
filterOrder: 过滤的顺序
shouldFilter: 是否需要过滤, 这里是 true, 需要过滤
run: 过滤器的具体业务代码
测试下: http://localhost:8769/API/a/hi?message=HelloZuul 网页显示
- Token is empty
- http://localhost:8769/API/b/hi?message=HelloZuul&token=123 网页显示
- Hi,your message is :"HelloZuul" i am from port:8763
二, 分布式配置中心
在分布式系统中, 由于服务数量巨多, 为了方便服务配置文件统一管理, 实时更新, 所以需要分布式配置中心组件. 在 Spring Cloud 中, 有分布式配置中心组件 Spring Cloud Config , 它支持配置服务放在配置服务的内存中(即本地), 也支持放在远程 Git 仓库中. 在 Spring Cloud Config 组件中, 分两个角色, 一是 Config Server, 二是 Config Client.
2.1 Config Server 项目
创建项目的方式与之前相同, 新建 spring-cloud-learn-config 项目, pom.xml 文件如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>com.yuanqinnan</groupId>
- <artifactId>spring-cloud-learn-parent</artifactId>
- <version>1.0.0-SNAPSHOT</version>
- </parent>
- <artifactId>spring-cloud-learn-config</artifactId>
- <packaging>jar</packaging>
- <name>spring-cloud-learn-config</name>
- <dependencies>
- <!-- Spring Boot Begin -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-Web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-tomcat</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- Spring Boot End -->
- <!-- Spring Cloud Begin -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-config-server</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
- </dependency>
- <!-- Spring Cloud End -->
- </dependencies>
- </project>
新建启动项目 ConfigApplication, 增加 @EnableConfigServer 注解
- @SpringBootApplication
- @EnableConfigServer
- @EnableEurekaClient
- public class ConfigApplication {
- public static void main(String[] args) {
- SpringApplication.run(ConfigApplication.class, args);
- }
- }
application.YAML 配置文件
- spring:
- application:
- name: spring-cloud-learn-config
- cloud:
- config:
- label: master
- server:
- Git:
- uri: https://github.com/yuanqinnan/spring-cloud-config
- search-paths: respo
- username:
- password:
- server:
- port: 8888
- eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
这里注意的地方是端口号不要修改, 使用 8888, 如要修改创建一个 Bootstrap.properties 文件进行修改, 原因是 Bootstrap 开头的配置文件会被优先加载和配置, 切记.
这里的配置文件也比较好理解, 相关配置说明, 如下:
spring.cloud.config.label: 配置仓库的分支
spring.cloud.config.server.Git.uri: 配置 Git 仓库地址(GitHub,GitLab, 码云 ...)
spring.cloud.config.server.Git.search-paths: 配置仓库路径(存放配置文件的目录)
spring.cloud.config.server.Git.username: 访问 Git 仓库的账号
spring.cloud.config.server.Git.password: 访问 Git 仓库的密码
注意事项:
如果使用 GitLab 作为仓库的话, Git.uri 需要在结尾加上 .Git,GitHub 则不用
我们想要测试的话, 先在自己的 GitHub 上新建一个仓库, 并新增 respo 文件夹 (存放配置文件的目录), 然后新增一个配置文件 consumer-dept-feign.YAML(这个是配置 feign 项目的) 即可, 然后我们就可以看看效果:
访问路径为: http://localhost:8888/consumer-dept-feign/master 这里路径也好理解, 这里的是配合文件名称加上分支名称
在实际开发过程中, 我们一般有三个环境, 开发, 测试, 生产, 而我们一般会在配置文件后加后缀来区分, 如 consumer-dept-feign-dev.YAML, 这个时候的访问路径就是 http://localhost:8888/consumer-dept-feign/dev/master, 路径地址增加环境地址, 为了方便测试, 再上传一个 consumer-dept-feign-prod.YAML, 作为正式环境的配置, 只修改一下端口号 (8766) 即可.
这里看出来, 我们的 feign 项目的配置文件已经换成配置中心的, 那现在可以去改造 feign, 让他去取配置中心的配置.
2.2 改造 Config Client 项目
因为我们刚刚上传了 spring-cloud-learn-consumer-dept-feign 项目的配置文件, 现在拿来做下实践, 第一步还是添加依赖:
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-config</artifactId>
- </dependency>
启动类没有变化, 只需修改配置文件:
- spring:
- cloud:
- config:
- uri: http://localhost:8888
- name: consumer-dept-feign
- label: master
- profile: dev
我们看到这里只保留了 cloud.config 下的相关配置, 因为其他所有的配置, 我们都可以去云配置中找, 这里的配置说明如下:
相关配置说明, 如下:
spring.cloud.config.uri: 配置服务中心的网址
spring.cloud.config.name: 配置文件名称的前缀
spring.cloud.config.label: 配置仓库的分支
spring.cloud.config.profile
: 配置文件的环境标识
dev: 表示开发环境
test: 表示测试环境
prod: 表示生产环境
现在只要能够启动 feign 项目, 就说明配置中心生效了, 我们启动一下, 一切顺利, 可以启动成功. 当我们修改 profile 为 prod 时, 启动的就是 8766 端口.
来源: https://www.cnblogs.com/yuanqinnan/p/11568787.html