SpringCloud提供了快速构建分布式中常见模式的工具,包括配置管理、服务发现、断路器、智能路由、微代理、控制总线等。SpringCloud中间件是基于SpringBoot的实现,提供了对微服务完整的一套解决方案。
应用架构的发展历程:
单体应用架构 –> 分布式架构 –>面向服务的SOA架构 –> 微服务架构
SOA架构个人理解是多个应用之间通过企业数据总线ESB通信的架构,其应用程序通过网络协议提供服务,消费服务,不同业务提供不同的服务。(阿里的服务治理框架Dubbo)
微服务架构:一个大型的应用拆分为多个相互独立的微服务,每个服务之间松耦合,通过REST API或者HTTP进行通信。
SpringCloud包包含以下组件:
服务治理组件 Eureka / Consul + 客户端负载均衡组件 Ribbon + 声明式服务调用组件 Feign + API网关治理组件 Zuul / GateWay(高并发) + 熔断机制 HyStrix + 分布式配置中心组件 Spring Cloud Config / 携程 Apollo + 消息总线组件 Bus + 消息驱动组件 Stream + 分布式服务跟踪组件 Sleuth + 全链路监控 SkyWalking.
Tips: 代码基于Spring Cloud Finchley 版本
服务治理:Spring Cloud Eureka
负责微服务架构中的服务治理功能,即各个微服务实例的自动化注册与发现。
SpringCloud Eureka 是由 Netflix Eureka实现的,即包含了服务端组件也包含了客户端组件。
Eureka服务端也被称为服务注册中心,各个微服务启动时会向Eureka Server 注册自己的信息。代码如下:
在https://start.spring.io/ 中新建一个Eureka Server的Demo,或者直接在Maven项目中的pom.xml文件中添加如下Dependence:
1 |
|
EurekaServerApplication.java
1 | package com.leezy.eureka_server; |
Eureka Server 中的 application.yml 和 application-standalone.yml
1 | # application.yml |
1 | # application-standalone.yml |
打开 http://localhost:8761/ 看到Eureka的控制面板。
Eureka服务注册端, Eureka Client将微服务注册到Eureka Server上。
EurekaClientApplication.java
1 | package com.leezy.eureka_client; |
Maven依赖:
1 | <dependency> |
Eureka Client的配置文件: application.yml 和 application-demo.yml
1 | [[application]].yml |
1 | [[application-demo]].yml |
刷新Eureka控制台就可以看到注册到Server上的服务了。
Eureka的设计理念:
- 服务实例如何注册到服务中心:
(1)调用Eureka Server的REST API 的 register方法
(2)Java语言使用者可以调用NetFlix的Eureka Client封装的API
(3)Spring Cloud使用者在pom.xml文件中引用 spring-cloud-starter-netflix-eureka-client,基于Spring Boot的自动配置即可。 - 服务实例从服务中心剔除
(1)服务实例正常关闭时,通过钩子方法或者生命周期回调方法调用Eureka Server 的REST API的de-register方法。
(2)Eureka要求Client定时续约(30s),如果90s没有续约操作则Eureka Server主动剔除该操作。 - 服务实例信息的一致性问题
服务注册与发现中心应该也是一个集群,如何保证一致性
(1)AP优于CP (Zookeeper-CP, Eureka-AP)
(2)Peer to Peer架构(1. 主从复制 2. 对等复制)
(3)Zone及Region设计
(4)SELF PRESERVATION设计
WebService客户端 Feign
Feign是一个声明式的Web Service客户端,用于服务与服务之间的调用,支持SpringMVC注解,整合了Ribbon以及Hystrix。
对应的POM依赖:
1 | <dependency> |
应用入口程序SpringCloudFeignApplication.java
1 | package com.leezy.hello_feign; |
接口类:HelloFeignService.java,作用是应用指定的URL最终转化为Github API允许的URL。
eg:https://api.github.com/search/repositories?q=spring-cloud
(Github RestAPI的文档:https://developer.github.com/v3/search/)
1 | package com.leezy.hello_feign.service; |
控制类:HelloFeignController.java,作用:调用服务提供者的接口
1 | package com.leezy.hello_feign.controller; |
配置类:HelloFeignServiceConfig.java,@Bean注解配置日志的bean
1 | package com.leezy.hello_feign.config; |
配置文件: application.yml
1 | server: |
启动应用后,访问网址:http://localhost:8010/search/github?str=spring-cloud
Feign支持的属性文件配置方式有两种:
application.yml(application.properties) 以及 Java方式的配置类,但是配置文件的优先级会高于Java类的优先级。
Feign默认的是JDK原生的URL Connection,并没有使用连接池,可以用Http Client和 okHttp进行替换对项目进行调优。
Feign 的 POST 和 GET 的多参数传递
Feign拦截器,将Json转化为Map。
实现Feign的RequestInterceptor中的apply方法来进行统一拦截转换处理Feign中的GET方法多参数传递问题。集成Swapper,编写服务消费者用于调用Feign进行Get或Post多参数传递。
负载均衡组件 Ribbon
Feign中集成了Ribbon,但是Ribbon可以单独使用,它是一种进程内负载均衡器(客户端负载均衡),它赋予了应用支配Http和Tcp的能力。
负载均衡策略:最常用的是RoundRobinRule 轮询策略
代码样例:
pom.xml
1 | <dependencies> |
启动类:RibbonLoadbalancerApplication.java
注入一个RestTemplate的Bean,并且使用@LoadBalances注解才能使其具备负载均衡的能力。
1 | package cn.springcloud.book; |
TestController.java
Ribbon客户端需要创建一个API来调用Eureka源服务自定义的API
1 | import org.springframework.beans.factory.annotation.Autowired; |
通过查找继承关系,发现接口ILoadBalancer的实现抽象类AbstractLoadBalancer的实现类BaseLoadBalancer中的chooseServer方法是真正实现负载均衡的地方。
1 | /* |
熔断机制 Spring Cloud Hystrix
Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.
Hystrix的设计目标是:
- 通过客户端对延迟和故障进行保护和控制
- 在一个复杂的分布式系统中停止级联故障
- 快速失败和迅速恢复
- 在合理的情况下回退和优雅地降级
- 开启实时监控、告警和操作控制
pom.xmlClientApplication.java1
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.leezy</groupId>
<artifactId>hystrix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hystrix</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>TestController.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.leezy.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}IUserService.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.leezy.hystrix.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.leezy.hystrix.service.IUserService;
public class TestController {
private IUserService userService;
public String getUser( String username)throws Exception{
return userService.getUser(username);
}
}UserService.java1
2
3
4
5
6package com.leezy.hystrix.service;
public interface IUserService {
public String getUser(String username) throws Exception;
}bootstrap.yml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.leezy.hystrix.service.impl;
import org.springframework.stereotype.Component;
import com.leezy.hystrix.service.IUserService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
public class UserService implements IUserService{
// 降级处理
public String getUser(String username) throws Exception {
if (username.equals("spring")) {
return "This is real user.";
} else {
throw new Exception();
}
}
public String defaultUser(String username) {
return "The User does not exist in the system...Test!";
}
}打开浏览器访问:http://localhost:8888/getUser?username=spring 和 http://localhost:8888/getUser?username=testERROR1
2
3
4
5
6
7
8
9
10
11server:
port: 8888
spring:
application:
name: hystrix-client-service
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
instance:
prefer-ip-address: true
Hystrix Dashboard
Hystrix Dashboard仪表盘是根据系统一段时间内发生的请求情况来展示的可视化面板。
Hystrix的指标需要端口进行支撑,所以需要增加actuator依赖。
pom.xml
1 | <dependency> |
HystrixDashboardApplication.java
1 |
|
上面是单个实例的Hystrix Dashboard,整个系统和集群的情况下并不是特别有用。Turbine就是聚合所有相关Hystrix.stream 流的方案。
网关治理组件 Zuul
Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. Zuul是对内部的微服务提供可配置的,对外URL到服务的映射关系,基于JVM的后端路由器。
pom.xml
1 | <dependency> |
启动类ZuulServerApplication.java
1 |
|
bootstrap.yml
1 | spring: |
最后五行的代码表示,Zuul组件的端口为portA,则将/client开头的URL映射搭配client-a这个服务中去,即实际访问portB。
/** 匹配任意数量的路径和字符
/* 匹配任意数量的字符
/? 匹配单个字符
Spring Cloud Zuul Filter链
(1) Filter的类型
(2) Filter的执行顺序
(3) Filter的执行条件
(4) Filter的执行效果
Zuul有四种不同生命周期的Filter,分别是:
pre Filter 按照规则路由到下级服务之前执行。比如鉴权、限流等
route Filter 路由动作的执行者(Apache HttpClient或Netflix Ribbon构建和发送原始Http请求的地方)
post Fliter 在源服务返回结果或者异常信息发生后执行的,对返回信息做一些处理
error Filter 在整个生命周期内如果发生异常,则会进入error Filter
Spring Cloud Zuul 权限集成
OAuth2.0 + JWT(JSON Web Token)
动态路由 Dynamic Routing
有两种解决方案:
(1) Spring Cloud Config + Bus、动态刷新配置文件。
(2) 重写Zuul的配置读取方式