# springcloud
**Repository Path**: yolo-charlie/springcloud
## Basic Information
- **Project Name**: springcloud
- **Description**: springcloud自学笔记整理
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: develop
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2020-12-07
- **Last Updated**: 2022-04-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Spring Cloud入门笔记
## 一、SpringCloud介绍
### 1.1 微服务架构
> 微服务的提出者:Martin Flower
>
> https://martinfowler.com/microservices/
> 简而言之,微服务架构样式是一种将单个应用程序开发为一**组小型服务的方法**,每个**小型服务**都**在自己的进程中运行**并与轻量级机制(通常是HTTP资源API)进行通信。这些服务**围绕业务功能构建,**并且可以 由全自动部署机制**独立部署**。有一个**集中管理的最低限度的**这些服务,可以用不同的编程语言和使用不同的数据存储技术。
微服务的解读:
- 微服务架构只是一个样式,一个风格
- 将一个完成的项目,拆分成多个模块去分别开发
- 每一个模块都是单独的运行在自己的容器中
- 每一个模块都是需要相互通讯的。HTTP、RPC、MQ。
- 每一个模块之间是没有依赖关系的,单独部署。
- 可以使用多种语言开发不同的模块。
- 使用MySQL数据库,Redis,ES去存储数据,也可以使用多个MySQL数据库。
总结:将复杂臃肿的单体应用进行细粒度的划分,每个拆分出来的服务各自打包部署。
### 1.2 SpringCloud介绍
> SpringCloud是微服务架构落地的一套技术栈
>
> SpringCloud中的大多数技术都是基于Netflix公司的技术进行二次研发。
>
> 1、SpringCloud的中文社区网站:[Spring Cloud中国社区](http://springcloud.cn/)
>
> 2、SpringCloud的中文网:[Spring Cloud中文网-官方文档中文版](https://www.springcloud.cc/)
>
> 八个技术点:
>
> 1. Eureka - 服务的注册中心
> 2. Robbin - 服务之间的负载均衡
> 3. Feign - 服务之间的通讯
> 4. Hystrix - 服务的线程隔离以及断路由
> 5. Zuul - 服务网关
> 6. Stream - 实现MQ的使用
> 7. Config - 动态配置
> 8. Sleuth - 服务追踪
## 二、服务的注册与发现:Eureka
### 2.1 引言
> Eureka就是帮助我们维护所有服务的信息,以便服务之间的相互调用。

### 2.2 Euraka的快速入门
#### 2.2.1 创建EurekaServer
> 1. 创建一个父工程,并且在父工程中指定SpringCloud的版本,并且将packing修改为pom
```xml
pom
1.8
Hoxton.SR4
org.springframework.cloud
spring-cloud-dependencies
${spring.cloud-version}
pom
import
```
> 2. 创建euraka的server,并且导入依赖,在启动类中添加注解,编写yml文件
**2.1 导入依赖**
```xml
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
```
**2.2 启动类添加注解**
```java
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
```
**2.3 编写yml文件**
```yml
server:
port: 8761 # 端口号
eureka:
instance:
hostname: localhost #localhost
client:
# 当前的euraka服务是单机版
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
```
#### 2.2.2 创建EurekaClient
> 1.创建Maven工程,修改为SpringBoot
> 2. 导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
> 3. 在启动类上添加注解
```java
@EnableEurekaClient
@SpringBootApplication
public class CustomerApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerApplication.class,args);
}
}
```
> 4. 编写配置文件
```yml
# 指定Eureka服务地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#指定服务的名称
spring:
application:
name: CUSTOMER
```
#### 2.2.3 测试Eureka
> 1. 创建了一个Search搜索模块,并且注册到Eureka
> 2. 使用EurekaClient的对象去获取服务信息
```java
@Autowired
private EurekaClient eurekaClient;
```
> 3. 正常RestTemplate调用即可
```java
@GetMapping("/customer")
public String customer(){
//1.通过eurekaClient获取到SEARCH服务信息
InstanceInfo info = eurekaClient.getNextServerFromEureka("SEARCH", false);
//2.获取到访问地址
String url = info.getHomePageUrl();
System.out.println(url);
//3.通过restTemplate访问
String result = restTemplate.getForObject(url + "/search", String.class);
//4.返回
return result;
}
```
### 2.3 Eureka的安全性
**实现Eureka认证**
- 导入依赖
```xml
org.springframework.boot
spring-boot-starter-security
```
- 编写配置类
```java
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 忽略/eureka/**
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
```
- 编写配置文件
```yaml
spring:
security:
user:
name: root
password: root
```
- 其他服务想要注册到注册中心需要用户名密码
```yml
# 指定Eureka服务地址
eureka:
client:
serviceUrl:
defaultZone: http://username:password@localhost:8761/eureka/
```
### 2.4 Eureka的高可用
> 如果程序正在运行,突然Eureka宕机了。
>
> 1. 如果调用方访问过一次被调用方,Eureka的宕机不会影响到功能。
> 2. 如果调用方没有访问过被调用方,Eureka的宕机就会造成当前功能不可用。
**搭建Eureka高可用**
1、准备多台Eureka
> 采用复制的方式,删除iml和target文件,并且修改pom.xml中的项目名称,再给父工程添加一个module
2、让服务注册到多台Eureka
```yml
eureka:
client:
serviceUrl:
defaultZone: http://root:root@localhost:8761/eureka/,http://root:root@localhost:8762/eureka/
```
3、让多台Eureka之间相互通讯
```yml
eureka:
instance:
hostname: localhost #localhost
client:
registerWithEureka: true #注册到Eureka上
fetchRegistry: true #从Eureka上拉取信息
serviceUrl:
defaultZone: http://root:root@${eureka.instance.hostname}:8762/eureka/
```
### 2.5 Eureka的细节
1、EurekaClient启动时,将自己的信息注册到EurekaServer上,EurekaServer就会存储EurekaClient的注册信息
2、当EurekaClient调用服务时,本地没有注册信息的缓存时,取EurekaServer中获取注册信息。
3、EurekaClient会通过心跳的方式和EurekaServer进行连接。(默认30s、EurekaClient会发送一次心跳请求,如果超过了90s还没有收到心跳信息的话,EurekaServer就认为EurekaClient节点宕机了,将当前的EurekaClient从注册表中移除)。
```yml
eureka:
instance:
lease-renewal-interval-in-seconds: 30 #心跳间隔
lease-expiration-duration-in-seconds: 90 #多久没有发送,就认为宕机
```
4、EurekaClient会每隔30s去EurekaServer中去更新本地注册表
```yml
eureka:
client:
registry-fetch-interval-seconds: 30 #每隔多久去更新一下本地的注册表缓存信息
```
5、Eureka的自我保护机制:统计15分钟内,如果一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制
- 不会从EurekaServer中去移除长时间没有收到心跳的服务。
- EurekaServer还是可以正常提供服务的。
- 网络比较稳定时,EurekaServer才会开始将自己的信息被其他节点同步过去。
```yml
eureka:
server:
enable-self-preservation: true #开启自我保护机制
```
6、CAP定理:C:一致性,A:可用性,P:分区容错性,这三个特性在分布式环境下,只能同时满足两个,而且分区容错性在分布式环境下是必须要满足的,只能在AC之间进行权衡。
如果选择了CP,保证了一致性可能会造成系统在一定时间内是不可用的。如果同步数据的时间比较长,将会造成很大的损失。
Eureka就是一个AP的效果,高可用的集群,Eureka集群是无中心,Eureka即便宕机几个也不会影响系统的使用,不需要重新的去推举一个master,但是他会导致一定时间内数据是不一致的。
## 三、服务间的负载均衡:Ribbon
### 3.1 引言
> Robbin是帮助我们实现服务和服务负载均衡,Ribbon属于客户端负载均衡
>
> - 客户端负载均衡:customer客户模块,将两个search模块信息全部拉取到本地的缓存,在customer中自己做一个负载均衡的策略,选中某一个服务。
> - 服务端负载均衡:在注册中心中,直接根据你指定的负载均衡策略,帮你选中一个指定的服务信息,并返回。

### 3.2 Ribbon的快速入门
1、启动两个search模块
2、在customer导入Ribbon依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
```
3、配置整合RestTemplate和Robbin
```java
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
```
4、在customer中访问search
```java
@GetMapping("/customer")
public String customer(){
String result = restTemplate.getForObject("http://SEARCH/search", String.class);
return result;
}
```
### 3.3 Ribbon配置负载均衡策略
1、负载均衡策略
- RandomRule:随机策略
- RoundRobinRule:轮询策略
- WeightedResponseTimeRule:默认会采用轮询的策略,后续会根据服务的响应时间,自动分配权重
- BestAvailableRule:根据被调用方并发数最小的去分配
2、采用注解的形式
```java
@Bean
public IRule robbinRule(){
return new RandomRule();
}
```
3、配置文件指定负载均衡策略(推荐)
```yml
#指定具体服务的负载均衡策略
SEARCH: #编写服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
```
## 四、服务间的调用:Feign
### 4.1 引言
> Feign可以帮助我们实现面向接口编程,直接调用其他服务,简化开发。
### 4.2 Feign的快速入门
·1、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-openfeign
```
2、启动类添加注解
```java
@EnableFeignClients
```
3、创建一个接口,并且和search模块
```java
/**
* FeignClient("SEARCH"):指定服务名称
*/
@FeignClient("SEARCH")
public interface SearchClient {
/**
* value ->目标服务的请求路径,method -> 映射请求方式
* @return
*/
@RequestMapping(value = "/search",method = RequestMethod.GET)
String search();
}
```
4、测试使用
```java
@Autowired
private SearchClient searchClient;
@GetMapping("/customer")
public String customer(){
String result = searchClient.search();
return result;
}
```
### 4.3 Feign的参数传递 方式
1、注意事项
- 如果传递的参数比较复杂时,默认采用POST的请求方式。
- 传递单个参数时,推荐使用@PathVariable,如果传递的单个参数比较多,这里可以采用@RequestParam,不要省略value属性。
- 传递对象信息时,统一采用json方式,添加@RequestBody。
- Client接口必须采用@RequestMapping
2、在search模块下准备三个接口
```java
@GetMapping("/search/{id}")
public Customer findById(@PathVariable Integer id){
return new Customer(id,"张三",23);
}
@GetMapping("/getCustomer")
public Customer getCustomer(@RequestParam Integer id,@RequestParam String name){
return new Customer(id,name,23);
}
@PostMapping("/save")
public Customer save(@RequestBody Customer customer){
return customer;
}
```
3、封装customer模块下的controller
```java
@GetMapping("/customer/{id}")
public Customer findById(@PathVariable Integer id){
return searchClient.findById(id);
}
@GetMapping("/getCustomer")
public Customer getCustomer(@RequestParam Integer id, @RequestParam String name){
return searchClient.getCustomer(id,name);
}
@PostMapping("/save")
public Customer save(@RequestBody Customer customer){
return customer;
}
```
4、再封装client接口
```java
@RequestMapping(value = "/search/{id}",method = RequestMethod.GET)
Customer findById(@PathVariable(value = "id") Integer id);
@RequestMapping(value = "/getCustomer",method = RequestMethod.GET)
Customer getCustomer(@RequestParam(value = "id") Integer id, @RequestParam(value = "name") String name);
@RequestMapping(value = "/save",method = RequestMethod.POST)
Customer save(@RequestBody Customer customer);
```
### 4.4 Feign的Fallback
> Fallback可以帮助我们在使用Feign去调用另外一个服务时,如果出现了问题,走服务降级,返回一个错误数据,避免功能因为一个服务出现问题,全部失效。
1、创建一个POJO类,实现Client接口。
```java
@Component
public class SearchClientFallBack implements SearchClient {
@Override
public String search() {
return "出现错误了";
}
@Override
public Customer findById(Integer id) {
return null;
}
@Override
public Customer getCustomer(Integer id, String name) {
return null;
}
@Override
public Customer save(Customer customer) {
return null;
}
}
```
2、修改Client接口中的注解,添加一个属性
```java
@FeignClient(value = "SEARCH",fallback = SearchClientFallBack.class)
```
3、添加配置文件
```yml
#feign和hystrix组件整合
feign:
hystrix:
enabled: true
```
> 以上方法存在的问题:调用方无法知道具体的错误信息是什么,通过FallBackFactory的方式去实现这个功能。
1、FallBackFactory基于Fallback
2、创建一个POJO类,实现FallBackFactory``
```java
@Component
public class SearchClientFallBackFactory implements FallbackFactory {
@Autowired
private SearchClientFallBack searchClientFallBack;
@Override
public SearchClient create(Throwable throwable) {
throwable.printStackTrace();
return searchClientFallBack;
}
}
```
3、修改Client接口属性
```java
@FeignClient(value = "SEARCH",fallbackFactory = SearchClientFallBackFactory.class)
```
## 五、服务的隔离及熔断:Hystrix
### 5.1 引言

### 5.2 降级机制的实现
1、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
2、添加一个注解
```java
@EnableCircuitBreaker
```
3、针对某一个接口去编写降级方法
```java
@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack")
public Customer findById(@PathVariable Integer id){
int i = 1/0;
return searchClient.findById(id);
}
/**
* findById的降级方法,方法的描述要和接口一致
* @param id
* @return
*/
public Customer findByIdFallBack(Integer id){
return new Customer(-1,"",0);
}
```
4、在接口上添加注解
```java
@HystrixCommand(fallbackMethod = "findByIdFallBack")
```
### 5.3 线程隔离
> 如果使用tomcat的线程池去接收用户的请求,使用当前线程去执行其他服务的功能,如果某一个服务出现了故障,导致tomcat的线程大量的堆积,导致tomcat服务无法处理其他业务功能。
>
> - Hystrix的线程池==(默认)==,接收用户请求采用tomcat的线程池,执行业务代码,调用其他服务时,采用Hystrix的线程池。
> - 信号量,使用的还是tomcat的线程池,帮助我们去管理tomcat的线程池。
1、Hystrix的线程池的配置(具体的配置name属性需要查看HystrixCommandProperties类)
- 线程隔离策略:name=`hystrix.command.default.execution.isolation.strategy`,value=`THREAD`
- 指定超时时间(针对线程池):name=`hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds`,value=`1000`
- 是否开启超时时间配置:name=`hystrix.command.default.execution.timeout.enabled`,value=`true`
- 超时之后是否中断线程:name=`hystrix.command.default.execution.isolation.thread.interruptOnTimeout`,value=`true`
- 取消任务后是否中断线程:name=`hystrix.command.default.execution.isolation.thread.interruptOnCancel`,value=`false`
```java
@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack",commandProperties = {
@HystrixProperty(name="execution.isolation.strategy",value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public Customer findById(@PathVariable Integer id)throws InterruptedException{
System.out.println(Thread.currentThread().getName());
Thread.sleep(3000);
return searchClient.findById(id);
}
```
2、信号量的配置信息
- 线程隔离策略:name=`hystrix.command.default.execution.isolation.strategy`,value=`SEMAPHORE`
- 指定信号量的最大并发请求数:name=`hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests`,value=`10`
```java
@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack",commandProperties = {
@HystrixProperty(name="execution.isolation.strategy",value = "SEMAPHORE"),
})
public Customer findById(@PathVariable Integer id)throws InterruptedException{
System.out.println(Thread.currentThread().getName());
Thread.sleep(3000);
return searchClient.findById(id);
}
```
### 5.4 断路器
#### 5.4.1 断路器介绍
> 在调用指定服务时,如果说这个服务的失败率达到输入的一个阈值,将断路器从closed状态,转变为open状态,指定服务是无法被访问的,如果访问就直接走fallback方法,在一定时间内,open状态会再次转变为half open状态,允许一个请求发送到我指定的服务,如果成功,转变为closed,如果失败,服务再次转变为open状态,会再次循环到half open,直到断路器回到一个closed状态。

#### 5.4.2 配置断路器的监控界面

1、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
```
2、启动类中添加注解
```java
@EnableHystrixDashboard
```
3、配置一个servlet路径,指定上Hystrix的Servlet
```java
@WebServlet("/hystrix.stream")
public class HystrixServlet extends HystrixMetricsStreamServlet {
}
--------------------------------------------------------
//在启动类上添加扫描servlet的注解
```
#### 5.4.3 配置断路器的属性

> [详见官方文档](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitBreaker.enabled)
断路器的属性(10秒钟之内)
1. 断路器开关:name=`hystrix.command.default.circuitBreaker.enabled`,value=`true`
2. 失败阈值的总请求数:name=`hystrix.command.default.circuitBreaker.requestVolumeThreshold`,value=`20`
3. 请求总数失败率达到`%`之多少时开启断路器:name=`hystrix.command.default.circuitBreaker.errorThresholdPercentage`,value=`50`
4. 断路器open状态后,多少秒是拒绝请求的:name=`hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds`,value=`5000`
5. 强制让服务拒绝请求:name=`hystrix.command.default.circuitBreaker.forceOpen`,value=`false`
6. 强制让服务接收请求:name=`hystrix.command.default.circuitBreaker.forceClosed`,value=`false`
```java
@HystrixCommand(fallbackMethod = "findByIdFallBack",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "70"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000")
})
```
### 5.5 请求缓存
#### 5.5.1 请求缓存的实现
1、请求缓存的生命周期是一次请求
2、请求缓存时缓存当前线程中的一个方法,将方法的参数作为key,方法的返回结果作为value
3、在一次请求中,目标方法被调用过一次,以后就都会被缓存。

#### 5.5.2 请求缓存的实现
1、创建一个service,在servie中调用search服务
```java
@Service
public class CustomerService {
@Autowired
private SearchClient searchClient;
@CacheResult
@HystrixCommand(commandKey = "findById")
public Customer findById(@CacheKey Integer id){
return searchClient.findById(id);
}
@CacheRemove(commandKey = "findById")
@HystrixCommand
public void clearFindById(@CacheKey Integer id){
System.out.println("findById被清空");
}
}
```
2、使用请求缓存的注解
```java
//帮助我们缓存当前方法的返回结果(必须配合HystrixCommand使用)
@CacheResult
//帮助我们清除某一个缓存信息(基于commandKey)
@CacheRemove
//指定方法的哪个参数作为缓存的标识,默认情况下是所有的参数
@CacheKey
```
3、修改search模块的返回结果
```java
return new Customer(id,"张三",(int)(Math.random()*1000000));
```
4、编写Filter,去构建HystrixRequestContext
```java
@WebFilter("/*")
public class HystrixRequestContextInitFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HystrixRequestContext.initializeContext();
filterChain.doFilter(servletRequest,servletResponse);
}
}
```
5、修改control
```java
public Customer findById(@PathVariable Integer id){
System.out.println(customerService.findById(id));
System.out.println(customerService.findById(id));
customerService.clearFindById(id);
System.out.println(customerService.findById(id));
System.out.println(customerService.findById(id));
return searchClient.findById(id);
}
```
6、测试结果

## 六、服务的网关:Zuul
### 6.1 引言
> - 客户端维护大量的ip和port信息,直接访问指定服务。
> - 认证和授权操作,需要在每一个模块中都添加认证和授权的操作。
> - 项目的迭代,服务要拆分,服务要合并,需要客户端进行大量的变化。
> - 统一的把安全性校验都放在Zuul中。

### 6.2 Zuul的快速入门
1、创建maven项目,修改为springboot
2、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
3、添加一个注解
```java
@EnableEurekaClient
@EnableZuulProxy
```
4、编写配置文件
```yml
# 指定Eureka服务地址
eureka:
client:
serviceUrl:
defaultZone: http://root:root@localhost:8761/eureka/,http://root:root@localhost:8762/eureka/
#指定服务的名称
spring:
application:
name: ZUUL
server:
port: 80
```
### 6.3 Zuul的常用配置信息
#### 6.3.1 Zuul的监控界面
1、导入依赖
```xml
org.springframework.boot
spring-boot-starter-actuator
```
2、编写配置文件
```yml
#查看zuul的监控界面(开发时,配置为*,上线,不要配置)
management:
endpoints:
web:
exposure:
include: "*"
```
3、直接访问

#### 6.3.2 忽略服务配置
```yml
#zuul配置
zuul:
#基于服务名忽略服务,无法查看,如果要忽略全部的服务,"*",默认配置的全部路径都会被忽略掉(自定义
ignored-services: eureka
#监控界面依然可以查看,在访问的时候,404
ignored-patterns: /**/search/**
```
#### 6.3.3 自定义服务配置
```yml
#指定自定义服务(方式一,key(服务名):value(路径))
routes:
search: /ss/**
customer: /cc/**
#指定自定义服务(方式二)
routes:
kehu: #自定义名称
path: /ccc/** #映射的路径
serviceId: customer #服务名称
```
#### 6.3.4 灰度发布
1、添加一个配置类
```java
@Bean
/**
* 规定:
* 服务的命名规则:服务名-v+版本号
* 访问路径:/v+版本号/路径
*/
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?^.+)-(?v.+$)",
"${version}/${name}");
}
```
2、准备一个服务,提供两个版本
```yml
version : v1
#指定服务的名称
spring:
application:
name: CUSTOMER-${version}
```
3、修改Zuul的配置
```yml
#zuul配置
zuul:
#基于服务名忽略服务,无法查看,如果需要用的`-v`的方式,一定要忽略掉。
#ignored-services: "*"
```
4、测试

### 6.4 Zuul的过滤器执行流程
> 客户端请求发送到zuul服务上,首先通过PreFilter链,如果正常放行,会把请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果后,再次走一个PostFilter的过滤器链,最终再将响应信息交给客户端。

### 6.5 Zuul过滤器入门
1、创建一个POJO类,继承ZuulFilter
```java
@Component
public class TestZuulFilter2 extends ZuulFilter {}
```
2、指定当前过滤器类型
```java
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
```
3、指定过滤器的执行顺序
```java
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
}
```
4、配置是否启用过滤器
```java
@Override
public boolean shouldFilter() {
//开启当前过滤器
return true;
}
```
5、指定过滤器中的具体业务代码
```java
@Override
public Object run() throws ZuulException {
System.out.println("prifix过滤器执行~");
return null;
}
```
6、测试

### 6.6 PreFilter实现token检验
1、准备访问路径,请求参数传递token
http://localhost/v2/customer/version?token=123
2、创建AuthenticationFilter
```java
@Component
public class AuthenticationFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 2;
}
@Override
public boolean shouldFilter() {
return true;
}
}
```
3、在run方法中编写具体的业务逻辑代码
```java
@Override
public Object run() throws ZuulException {
//1.获取Request对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//2.获取token参数
String token = request.getParameter("token");
//3.对比token
if (token ==null || !"123".equalsIgnoreCase(token)){
//4. token检验失败,直接响应数据
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
return null;
}
```
4、测试

### 6.7 Zuul降级
1、创建POJO类,实现接口FallbakProvider
```java
@Component
public class ZuulFallBack implements FallbackProvider {}
```
2、重写两个方法
```java
@Override
public String getRoute() {
//代表出现问题的服务,都走这个降级方法
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("降级的服务:" + route);
cause.printStackTrace();
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
//指定具体的HttpStatus
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
//返回状态码
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
//指定错误信息
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
//给用户响应的信息
String msg = "当前服务:" + route + "出现问题!!!";
return new ByteArrayInputStream(msg.getBytes());
}
@Override
public HttpHeaders getHeaders() {
//指定响应头信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
```
### 6.8 Zuul动态路由
1、创建一个过滤器
> 执行顺序最好放在Pre过滤器的最后面
2、在run方法中编写业务逻辑
```java
@Override
public Object run() throws ZuulException {
//1.获取Request对象
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
//2.获取参数,redisKey
String redisKey = request.getParameter("redisKey");
//3.直接判断
if (redisKey !=null && redisKey.equalsIgnoreCase("customer")){
//http://localhost:8080/customer
context.put(FilterConstants.SERVICE_ID_KEY,"customer-v1");
context.put(FilterConstants.REQUEST_URI_KEY,"/customer");
}else if (redisKey !=null && redisKey.equalsIgnoreCase("search")){
//http://localhost:9090/search/1
context.put(FilterConstants.SERVICE_ID_KEY,"search");
context.put(FilterConstants.REQUEST_URI_KEY,"/search/1");
}
return null;
}
```
3、测试

## 七、多语言支持 Sidecar
### 7.1 引言
> 在SpringCloud的项目中,需要接入一些非Java的程序,第三方接口,无法接入Eureka,Hystrix,Feign等等组件,启动一个代理的微服务,代理微服务去和非Java的程序或第三方接口交互,通过代理微服务去接入SpringCloud的相关组件。

### 7.2 Sidecar实现
1、创建一个第三方的服务
> 创建一个SpringBoot工程,并且添加一个Controller
2、创建maven工程,修改spring Boot
3、导入依赖
```xml
org.springframework.cloud
spring-cloud-netflix-sidecar
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
4、添加注解
```java
@EnableSidecar
```
5、编写配置文件
```yml
server:
port: 81
# 指定Eureka服务
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
#指定服务名称
spring:
application:
name: other-service
#指定代理的第三方服务
sidecar:
port: 7001
```
6、通过customer调用第三方服务

## 八、服务间消息传递:Stream
### 8.1 引言
> 用于构建消息驱动微服务的框架(在下面方便起见也叫它Stream框架),该框架在Spring Boot的基础上整合了Spring Integration来连接消息代理中间件(RabbitMQ,Kafka等)。它支持多个消息中间件的自定义配置,同时吸收了这些消息中间件的部分概念,例如持久化订阅、消费者分组,和分区等概念。使用Stream框架,我们不必关心如何连接各个消息代理中间件,也不必关心消息的发送与接收,只需要进行简单的配置就可以实现这些功能了,可以让我们更敏捷的进行开发主体业务逻辑了。
>
> **Spring Cloud Stream框架的组成部分:**
>
> 1. Stream框架自己的应用模型;
> 2. 绑定器抽象层,可以与消息代理中间件进行绑定,通过绑定器的API,可实现插件式的绑定器。
> 3. 持久化订阅的支持。
> 4. 消费者组的支持。
> 5. Topic分区的支持。

### 8.2 Stream快速入门
1、启动RabbitMQ
2、消费者-导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-stream-rabbit
```
3、消费者-配置文件
```yml
spring:
# 连接RabbitMQ
rabbitmq:
host: 192.168.25.150
port: 5672
username: test
password: test
virtual-host: /
```
4、消费者-监听的队列
```java
public interface StreamClient {
@Input("myMessage")
SubscribableChannel input();
}
```
```java
@Component
@EnableBinding(StreamClient.class)
public class StreamReceiver {
@StreamListener("myMessage")
public void msg(Object msg){
System.out.println("接收到消息:" + msg);
}
}
```
5、生产者-导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-stream-rabbit
```
6、生产者-配置文件
```yml
spring:
# 连接RabbitMQ
rabbitmq:
host: 192.168.25.150
port: 5672
username: test
password: test
virtual-host: /
```
7、生产者-发布消息
> 需要在启动类中添加注解`@EnableBinding(StreamClient.class)`
```java
public interface StreamClient {
@Output("myMessage")
MessageChannel output();
}
```
```java
@RestController
public class MessageController {
@Autowired
private StreamClient streamClient;
@GetMapping("/send")
public String send(){
streamClient.output().send(MessageBuilder.withPayload("Hello Stream!!!").build());
return "消息发送成功!!!";
}
}
```
### 8.3 Stream重复消费
```yml
spring:
cloud:
stream:
bindings:
myMessage: #队列名称
group: customer #消费者组
```
### 8.4 Stream的消费者手动ack
1、编写配置文件
```yml
spring:
cloud:
stream:
#手动实现ACK
rabbit:
bindings:
myMessage:
consumer:
acknowledgeMode: MANUAL
```
2、修改消费端的方法
```java
@StreamListener("myMessage")
public void msg(Object msg,
@Header(name = AmqpHeaders.CHANNEL) Channel channel,
@Header(name = AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
System.out.println("接收到消息:" + msg);
channel.basicAck(deliveryTag,false);
}
```
## 九、服务的动态配置:Config
### 9.1 引言
> - 配置文件分散在不通的项目中,不方便维护
> - 配置文件的安全问题
> - 修改完配置文件,无法立即生效

### 9.2 搭建Config-Server
1、创建maven工程,修改为spring boot
2、导入依赖
```xml
org.springframework.cloud
spring-cloud-config-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
3、添加注解
```java
@EnableConfigServer
```
4、编写配置文件(Git操作)
```yml
#指定服务的名称
spring:
cloud:
config:
server:
git:
basedir: E:\Gitee\config #本地仓库
username: xuxueli982@163.com #远程仓库的用户名
password: Xxl@6553345726 #远程仓库的密码
uri: https://gitee.com/sabiler/config-repo.git #远程仓库地址
```
5、测试:
> `http://localhost:port/{label}/{application}-{profile}.yml`

==问题:==配置好config-server服务后无法通过连接地址访问到远程git仓库中的配置文件
### 9.3 修改customer连接Config
1、导入依赖
```xml
org.springframework.cloud
spring-cloud-config-client
```
2、编写配置文件
```yml
# 指定Eureka服务地址
eureka:
client:
serviceUrl:
defaultZone: http://root:root@localhost:8761/eureka/,http://root:root@localhost:8762/eureka/
#指定服务的名称
spring:
application:
name: CUSTOMER-${version}
cloud:
config:
discovery:
enabled: true
service-id: CONFIG
profile: dev
label: master
version : v1
```
3、修改配置名称
> bootstrap.yml
4、测试发布消息到RabbitMQ
**PS:注意配置文件的文件名要和应用的名称相同并且区分大小写。**
### **9.4 实现动态配置**
#### 9.4.1 实现原理

#### 9.4.2 服务连接RabbitMQ
1、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-bus-amqp
```
2、编写配置文件连接RabbitMQ
```yml
#指定服务的名称
spring:
rabbitmq:
host: 192.168.25.150
port: 5672
username: test
password: test
virtual-host: /
```
#### 9.4.3 实现手动刷新
1、导入依赖
```xml
org.springframework.boot
spring-boot-starter-actuator
```
2、编写配置文件
```yml
management:
endpoints:
web:
exposure:
include: "*"
```
3、为customer添加一个controller
```java
@RefreshScope
public class CustomerController {
@Value("${env}")
private String env;
@GetMapping("/env")
public String env(){
return env;
}
}
```
4、测试
> 1. CONFIG在Gitee修改之后,自动拉取最新的配置信息
> 2. 其他模块需要更新的话,需要手动给CONFIG应用发送一个请求:`http://localhost:8090/actuator/bus-refresh`,不重启项目,即可获取最新的配置信息
#### 9.4.5 实现自动刷新
1、配置Gitee中的WebHooks

添加完webhooks之后gitee会尝试发送一个请求给config应用,这时由于没有配置过滤器会报`400`错误码。
2、给Config添加一个过滤器
```java
@WebFilter("/*")
public class UrlFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest;
String url=httpServletRequest.getRequestURI();
System.out.println(url);
if(!url.endsWith("/actuator/bus-refresh")){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
String body=(httpServletRequest).toString();
System.out.println("original body: "+ body);
RequestWrapper requestWrapper=new RequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper,servletResponse);
}
private class RequestWrapper extends HttpServletRequestWrapper {
public RequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true : false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
}
}
```
## 十、服务的追踪:Sleuth
### 10.1 引言
> 在整个微服务架构中,微服务很多,一个请求可能需要调用很多很多的服务,最终才能完成一个功能,如果说,这个整个功能出现了问题,在这么多的服务中,如何去定位问题的所在点,出现问题的原因是什么。
>
> 1、Sleuth可以获得到整个服务链路的信息。
>
> 2、Zipkin通过图形化界面去看到信息。
>
> 3、Sleuth将日志信息存储到数据库中。

### 10.2 Sleuth使用
1、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-sleuth
```
2、编写配置文件
```yml
logging:
level:
org.springframework.web.servlet.DispatcherServlet: DEBUG
```
3、测试

链路日志各个字段代表的含义:
- SEARCH:总链路id
- 516:当前服务链路id
- false:不会将当前的日志信息输出到其他系统中\
### 10.3 Zipkin的使用
1、搭建Zipkin的web工程
```yml
version: "3.1"
services:
zipkin:
image: daocloud.io/daocloud/zipkin:latest
restart: always
container_name: zipkin
ports:
- 9411:9411
```
2、导入依赖
```xml
org.springframework.cloud
spring-cloud-starter-zipkin
```
3、编写配置文件
```yml
spring:
sleuth:
sampler:
probability: 1 #代表百分之多少的sleuth信息需要输出到zipkin中
zipkin:
base-url: http://192.168.25.150:9411/ #指定zipkin的地址
```
4、测试
### 10.4 整合RabbitMQ
1、导入RabbitMQ依赖
2、修改配置文件
```yml
#指定服务的名称
spring:
zipkin:
base-url: http://192.168.25.150:9411/ #指定zipkin的地址
sender:
type: rabbit
```
3、修改zipkin的信息
```yml
version: "3.1"
services:
zipkin:
image: daocloud.io/daocloud/zipkin:latest
restart: always
container_name: zipkin
ports:
- 9411:9411
environment:
- RABBIT_ADDRESSES=192.168.25.150:5672
- RABBIT_PASSWORD=test
- RABBIT_USER=test
- RABBIT_VIRTUAL_HOST=/
```
4、测试

### 10.5 Zipkin存储数据到ES
1、重新修改zipkin的yml文件
```yml
version: "3.1"
services:
zipkin:
image: daocloud.io/daocloud/zipkin:latest
restart: always
container_name: zipkin
ports:
- 9411:9411
environment:
- RABBIT_ADDRESSES=192.168.25.150:5672
- RABBIT_PASSWORD=test
- RABBIT_USER=test
- RABBIT_VIRTUAL_HOST=/
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=http://192.168.25.150:9200
```
## 十一、完整SpringCloud架构图
