springcloud
1. springcloud简介
一个大型的web系统,功能模块非常多,如果所有的功能写在一个项目中,一个项目只能连接一个数据库一个redis,数据库IO以及项目的请求的并发压力都比较大。
一个大的系统可以拆分成多个项目进行独立开发,每个项目可以连接自己的数据库和redis…,分开部署,减轻项目的io压力以及访问的并发压力
拆分项目的技术:使用Maven就可以拆分
每个模块开发成一个独立应用时 都需要整合多个框架来实现功能,都是web应用
使用springboot可以简化项目的开发的过程
项目拆分后用户访问时可能存在不方便管理的地方,springcloud可以将多个拆分的项目模块有序的管理起来
用户访问时就像访问一个整体的项目一样
springcloud属于分布式架构的一种框架
使用springcloud时,springboot开发的项目属于一个微服务
springboot开发的多个微服务之间如果需要远程访问,springcloud默认采用RestApi的方式实现[HTTP+JSON],请求和响应都必须遵循HTTP协议,数据存在响应体中格式必须为json格式
springboot可以快速的将一个完整的项目拆分成多个模块开发,每个模块都开发了这个系统的一个完整的独立的功能,可以单独运行[scw scw-ui scw-user scw-manager scw-project scw-order scw-pay ]
springcloud就是分布式架构的一个框架,可以将多个独立运行的项目管理起来,用户访问时就像访问一个整体的单个系统一样,包含了一下功能: 路由功能、过滤功能、限流功能、服务管理(系统的各个微服务的配置管理)、配置管理、服务远程调用、熔断保护…
springcloud不是一个新的框架,只是将市面上现有的比较成熟的解决分布式各个功能的框架整合起来,使用类似springboot场景启动器的方式将框架封装为springcloud的场景启动器,然后在springboot项目中也可以通过启动器引入需要使用的某个功能场景
2.springcloud-helloworld案例
案例:
电影系统: 用户查询自己信息、查询电影信息、查询自己的信息和电影信息等功能
用户服务:用户查询自己信息、查询自己的信息和电影信息 服务消费者
电影服务:查询电影信息 服务提供者
注册中心: 管理电影服务的各个服务的项目
1. 电影系统注册中心
以后电影系统的所有的服务都需要注册到注册中心中
电影系统的所有的服务如果需要调用其他的服务都需要到注册中心中去获取要调用服务的配置
注册中心需要提供位置的标志,让其他的服务注册时可以找到自己,url地址
region区域: 一般一个公司使用一个region,一般使用默认的
zone小区: 公司的一个产品使用一个zone,一般也使用默认的,defaultZone
1、创建springboot项目
引入eureka server场景启动器
2、在主程序类名上启用注册中心的功能
@EnableEurekaServer
3、编写注册中心的配置
application.properties
# 如果项目是一个微服务,默认会使用spring.application.name的值作为键将自己的配置注册到注册中心中
spring.application.name=cloud-eureka-registry-center
server.port=8761
# 当前实例的ip地址
eureka.instance.ip-address=127.0.0.1
# 是否要去其他的注册中心中拉取服务列表
eureka.client.fetch-registry=false
# 当前实例是否要注册给注册中心[当前项目就是注册中心]
eureka.client.register-with-eureka=false
# 配置当前注册中心的地址,以后其他服务可以通过此地址将自己注册给此注册中心
eureka.client.service-url.defaultZone=http://${eureka.instance.ip-address}:${server.port}/eureka/
4、启动项目访问
如果看到下面的页面代表注册中心创建成功
eureka server:springcloud的原生的,但是2.0以后进入维护模式(停止更新)
nacos :阿里的
zookeeper:
服务和服务之间将来远程调用使用的方式是什么?
RPC: 远程过程调用,采用socket方式实现的,需要定义传递数据的格式(dubbo)
HTTP+JSON: restful Api的方式
springcloud , 服务调用另一个服务时必须采用HTTP协议封装请求数据发送请求,参数传递时必须使用json字符串的格式传递 , 另一个服务接受请求时必须采用HTTP协议的方式接受,响应时也必须响应HTTP协议格式的报文+数据采用json字符串的格式响应
springcloud所有的服务开发时都需要使用RestController标注在Controller上,用来支持其他服务的远程访问
2. 服务提供者-movie
1、创建movie服务并引入eureka client和web依赖
2、创建application.yml并编写配置
# 服务注册到注册中心的名称
spring:
application:
name: cloud-provider-movie
# eureka相关配置
eureka:
instance:
# 准备当前服务启动时的ip-address 地址交给注册中心管理
prefer-ip-address: true
# 当前服务启动时的ip地址
ip-address: 127.0.0.1
client:
# 配置当前服务要注册的注册中心的地址
# 当前微服务启动时会自动调用将自己的eureka的配置参数传给注册中心
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 7000
3、在主程序类名上添加注解启用eureka客户端功能
@EnableDiscoveryClient //启用eureka客户端功能
@SpringBootApplication
public class CloudProviderMovieApplication{}
4、启动注册中心和电影微服务,如果能看到注册中心中电影服务信息表示注册成功
5、项目中提供查询电影信息的功能
MovieController
MovieService
MovieDao
3. 服务消费者-user
1、创建user服务
引入web、eureka client依赖
2、创建application.yml编写配置
spring:
application:
name: cloud-consumer-user
server:
port: 8000
eureka:
instance:
ip-address: localhost
prefer-ip-address: true
client:
service-url:
defaulZone: http://localhost:8761/eureka/
3、主程序类名上启用eureka 客户端功能
@EnableDiscoveryClient
@SpringBootApplication
public class CloudConsumerUserApplication {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerUserApplication.class, args);
}
}
4、编写user服务的功能
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/user")
public User getUser(Integer id){
return userService.getUser(id);
}
@GetMapping("/userAndMovie")
public Map getUserAndMovie(Integer id){
return userService.getUserAndMovie(id);
}
}
@Service
public class UserService {
@Autowired
UserDao userDao;
//1、查询用户的业务
public User getUser(Integer id){
return userDao.getUserById(id);
}
//2、查询用户信息和电影信息的服务
//如果以json格式响应多个相同类型的对象使用list
// 如果以json格式相应不同类型的多个对象使用map:map.put("user",user) map.put("movie",movie)
// map转为json字符串: { "user":{"id":1001 , "username":"xxx"} , "movie":{"id":11} }
public Map<String,Object> getUserAndMovie(Integer userid){
User user = getUser(userid);
//电影信息需要访问电影服务才可以实现
HashMap<String, Object> map = new HashMap<>();
map.put("user",user);
map.put("movie",null);
return map;
}
}
@Repository
public class UserDao {
//dao:data access object 数据访问对象(表和实体类的对应,一个dao对应一张表的操作)
public User getUserById(Integer id){
return new User(id , "张弛");
}
}
public class User {
private Integer id;
private String username;
public User(Integer id, String username) {
this.id = id;
this.username = username;
}
public User() {
}
//getter-- setter方法省略....
}
java代码实现远程访问:httpClient,这里介绍别的方法进行:如下
4. 服务器消费者整合Ribbon
1、拷贝user服务
2、将拷贝后的user服务引入到当前工作空间
3、在拷贝的项目的pom文件中引入ribbon的场景启动器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
4、在项目的主程序中向容器中注入RestTemplate模板类对象
//RestTemplate :ribbon提供的远程调用其他服务+负载均衡的模板类
@LoadBalanced //启用RestTemplate负载均衡功能,必须启用
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
5、在需要使用远程访问的代码中自动装配RestTemplate对象实现远程访问
userService中查询用户及电影信息时需要使用
@Autowired
RestTemplate restTemplate;
public Map<String,Object> getUserAndMovie(Integer userid){
User user = getUser(userid);
//电影信息需要访问电影服务才可以实现
HashMap<String, Object> map = new HashMap<>();
map.put("user",user);
//在需要远程访问的代码中使用restTemplate实现
// getForObject(): 以get方式访问远程服务将返回值封装为对象
//postForObject() : 使用post方式调用,返回的数据封装为对象
// 远程访问时,由于ip地址+端口号经常变化,不能写死,每次远程访问都可以到注册中心中动态获取
//每个服务的名称都是固定的,根据服务名可以到注册中心拉取服务的配置
//http://CLOUD-PROVIDER-MOVIE/movie?id=9527 等于 http://127.0.0.1:7000/movie?id=9527
Movie movie = restTemplate.getForObject("http://CLOUD-PROVIDER-MOVIE/movie?id=9527", Movie.class);
//post传参使用占位符方式传参: {}
map.put("movie",movie);
return map;
}
restTemplate远程访问时其实和httpclient远程访问某个地址一样
restTemplate的地址中可以用注册中心的服务名称替代服务的ip地址+端口号,运行时restTemplate会自动来去服务名对应的配置来使用
restTemplate调用方式时get和post等方式由要调用的服务的方法设置的Mapping映射的方式相同
调用时的路径一定要指向方法的映射
电影服务需要先启动再启动用户服务[用户服务远程访问电影服务时需要保证电影服务已经运行稳定了]
测试ribbon的负载均衡功能:
以后如果某个服务的方法压力较大,可以采用多实例的方式启动
配置movie服务多实例启动,movie服务多实例的服务名spring.application.name的参数值一定要一样
修改movie服务多实例启动的端口号[7001,7002,7003]
在movie服务的controller方法内打印当前服务的端口号
@Value("${server.port}")
Integer port;
@GetMapping("/movie")
public Movie getMovie(Integer id){
System.out.println("被访问movie服务的端口号为: " +port);
return movieService.getMovie(id);
}
重启所有的movie服务+注册中心+user-ribbon服务
访问user-ribbon服务的获取用户信息+电影信息的接口时可以测试负载均衡(http://localhost:8000/userAndMovie?id=1002)
默认ribbon采用的是轮询的方式实现负载均衡。
5. 服务器消费者整合Feign
1、复制user服务改名为user-feign
2、将拷贝的user-feign引入到当前工作空间
3、在pom文件中引入feign的场景启动器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
4、在主程序上启用feignclient的功能
```java
@EnableFeignClients //启用feign的功能:指定主程序扫描当前包或子包下标注了@FeignClient的注解并创建对象注入到容器中
@EnableDiscoveryClient
@SpringBootApplication
public class CloudConsumerUserApplication {}
5、创建远程调用的feigncleint(以后一个远程的服务对应一个feignclient)
电影服务的feign客户端
//以后代码中通过MovieFeignClient调用getMovie方法时
//会被feign转为 http://CLOUD-PROVIDER-MOVIE/movie?id=xx
//所以 feign和ribbon远程调用方式本质是一样的
//对应电影服务的远程调用的客户端
@FeignClient("CLOUD-PROVIDER-MOVIE")//FeignClient可以通过注册中心中要调用的服务的名称和远程服务进行绑定
public interface MovieFeignClient {
//方法远程访问时,如果有参数,必须给参数指定key,因为远程访问拼接时数据需要以k=v的方式提交
//将要远程调用的服务的所有的controller方法原封不动的拷贝到当前feignclient中
//如果是简单类型: int string 直接通过@RequestParam("key")
//如果是复杂类型:必须转为json传递,@RequestBody
@GetMapping("/movie")
public Movie getMovie(@RequestParam("id") Integer id);
}
6、在需要远程调用的代码中装配feign客户端调用方法实现远程访问功能
//自动装配实现远程调用的feign客户端
@Autowired
MovieFeignClient movieFeignClient;
public Map<String,Object> getUserAndMovie(Integer userid){
User user = getUser(userid);
//电影信息需要访问电影服务才可以实现
HashMap<String, Object> map = new HashMap<>();
map.put("user",user);
Movie movie = movieFeignClient.getMovie(2222);
map.put("movie",movie);
return map;
}
7、由于feign底层使用的是ribbon,默认开启了负载均衡的功能
6. ribbon整合hystrix
远程访问+负载均衡: ribbon、feign
短路器:hystrix
服务降级: 原有的服务远程访问调用失败,使用短路器的默认处理方法返回默认数据
服务熔断: 短路器接受到访问远程服务的请求时直接返回默认数据
1、拷贝user-ribbon项目为user-ribbon-hystrix
2、在拷贝后的项目中的pom文件内引入hystrix短路器的场景启动器
<!-- 短路器场景启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
3、在项目的主程序类名上添加注解@EnableHystrix启用短路保护功能
@EnableHystrix
@EnableDiscoveryClient
@SpringBootApplication
public class CloudConsumerUserApplication {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerUserApplication.class, args);
}
//RestTemplate :ribbon提供的远程调用其他服务+负载均衡的模板类
@LoadBalanced//启用RestTemplate负载均衡功能,必须启用
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
4、在远程访问的代码中,给远程访问的方法提供异常处理方案
@Autowired
RestTemplate restTemplate;
//当远程访问失败异常的处理方法[兜底方法]
public Map<String,Object> getUserAndMovieHystrix(Integer userid){
User user = getUser(userid);
//电影信息需要访问电影服务才可以实现
HashMap<String, Object> map = new HashMap<>();
map.put("user",user);
map.put("movie",new Movie("服务器繁忙,请重试",-1000));
return map;
}
@HystrixCommand(fallbackMethod = "getUserAndMovieHystrix")//指定远程调用失败时的异常处理方案
//正常远程访问的代码
public Map<String,Object> getUserAndMovie(Integer userid){
User user = getUser(userid);
//电影信息需要访问电影服务才可以实现
HashMap<String, Object> map = new HashMap<>();
map.put("user",user);
//在需要远程访问的代码中使用restTemplate实现
// getForObject(): 以get方式访问远程服务将返回值封装为对象
//postForObject() : 使用post方式调用,返回的数据封装为对象
// 远程访问时,由于ip地址+端口号经常变化,不能写死,每次远程访问都可以到注册中心中动态获取
//每个服务的名称都是固定的,根据服务名可以到注册中心拉取服务的配置
//http://CLOUD-PROVIDER-MOVIE/movie?id=9527 等于 http://127.0.0.1:7000/movie?id=9527
Movie movie = restTemplate.getForObject("http://CLOUD-PROVIDER-MOVIE/movie?id=9527", Movie.class);
map.put("movie",movie);
return map;
}
7. feign整合hystrix
1、拷贝user-feign项目为user-feign-hystrix
2、在拷贝后的项目的pom中引入hystrix的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
3、在主程序类名上使用@EnableHystrix注解启用hystrix
4、在application.yml中启用feign对hstrix的支持:
# 启用feign对hyestrix的支持
feign:
hystrix:
enabled: true
5、给feignclient编写异常处理的实现类
实现类中实现feign客户端的所有的方法,当方法访问失败时fiegnclient会自动调用异常处理类的对象的相同方法处理请求返回兜底数据
@Service
public class MovieFeignClientExceptionHandler implements MovieFeignClient{
@Override
public Movie getMovie(Integer id) {
return new Movie("查询失败,请重试" , id);
}
}
6、指定FeignClient的异常处理类
//当Fiegn客户端的方法调用失败时,后自动选择调用fallback指定的异常处理类相同的方法处理请求
@FeignClient(value = "CLOUD-PROVIDER-MOVIE" ,fallback = MovieFeignClientExceptionHandler.class)//FeignClient可以通过注册中心中要调用的服务的名称和远程服务进行绑定
public interface MovieFeignClient {
//方法远程访问时,如果有参数,必须给参数指定key,因为远程访问拼接时数据需要以k=v的方式提交
//将要远程调用的服务的所有的controller方法原封不动的拷贝到当前feignclient中
//如果是简单类型: int string 直接通过@RequestParam("key")
//如果是复杂类型:必须转为json传递,@RequestBody
@GetMapping("/movie")
public Movie getMovie(@RequestParam("id") Integer id);
}
8. actuator数据流监控
feign+hystrix整合的项目中进行数据流监控
数据流中包含了 服务之间的调用的次数,成功失败的比例,调用的时长
1、拷贝feign+hystrix为feign+hystrix+actuator
2、在pom文件中引入actuator的场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3、在application.yml中开发hystrix监控的数据流
management:
endpoints:
web:
exposure:
include: hystrix.stream
# 只暴露 hystrix.stream ,短路器的数据流
# '*' 代表开放所有的数据流监控
# 暴露项目的数据流,actuator默认只暴露了Info、health
4、重新启动服务
在浏览器中访问:http://localhost:8000/actuator/hystrix.stream
如果没有数据流,代表服务还没有远程访问,需要手动访问一次
短路器hystrix:
A->B
当用户访问A服务,A需要调用B服务处理请求返回结果时
如果A调用B一直处于成功状态,短路器一直处于关闭状态
如果A调用B失败了,此时短路器就会处于半开状态(全开),每过500ms放行一个请求访问B服务,其他的请求直接使用短路器返回兜底数据
如果500ms放行的请求响应成功,短路器就会进入关闭状态,以后所有的请求都会直接交给B服务处理
hstrix-dashboard: (了解)
可以解析监控到的数据流显示到视图面板中
如果自己测试的时候数据流监控的 面板页面 如果一直显示 等待连接,证明版本有问题
需要修改springboot项目的版本为2.1.x的版本,然后再测试
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
主程序中添加注释@EnableHystrixDashboard
之后进行页面加载的时候出现了小错误,于是在yml文件中加了视频里面没有的配置:
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
9. spring-gateway网关
9.1 springcloud-gateway简介
网关:
包含 代理(类似于nginx的反向代理,可以代理整个系统的所有的微服务,用户访问时请求交给网关项目就可以访问目标的微服务)、
路由(网关将请求交给指定微服务处理)、
断言(预言,例如断言请求报文中包含某个头或参数,请求时如果没有携带那么网关就不会处理该请求)、
过滤(对请求和响应进行预处理: 修改请求报文、修改响应报文、日志输出…权限检查、流控、黑名单) 四大功能
9.2 为helloworld的分布式系统创建一个网关项目
1、创建微服务引入依赖gateway、eureka client
2、在主程序上启用eureka客户端
@EnableDiscoveryClient
3、创建application.yml配置
spring:
application:
name: cloud-gateway
server:
port: 10000
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
fetch-registry: true
9.3 配置网关项目实现代理电影和用户服务并实现路由
让客户端或者用户访问时 只需要通过一个统一的地址就可以访问到系统的电影和用户服务
http://127.0.0.1:7003/movie?id=123123
http://localhost:8000/user?id=111
http://localhost:10000/…
配置路由和代理功能
先修改movie和user服务,给每个模块的controller添加访问的前缀[方便路径匹配的语法编写]
spring:
application:
name: cloud-gateway
# 配置网关的代理和路由功能
cloud:
gateway:
# routes :就是一个 List<RouteDefinition> routes,
routes:
# -表示给集合创建一个元素 RouteDefinition 代表一个代理+路由对象
# id表示 一个代理路由对象的 唯一标志,可以随意指定但是推荐使用要代理的服务名称
- id: cloud-consumer-user
# 表示网关项目接受到访问user服务的请求时将请求路由给哪个地址来处理
uri: http://127.0.0.1:8000
# 断言集合配置:List<PredicateDefinition>
predicates:
# 地址的断言,访问网关的地址项目名后如果是/user/* 地址,则由本路由代理配置来处理
# 如果浏览器访问:http://localhost:10000/user/getUser?id=1 网关项目接受到后,会将此请求交给这个配置的路由处理
# 网关项目路由此请求给 http://127.0.0.1:8000/user/getUser?id=1
- Path=/user/*,/consumer/*
- id: cloud-provider-movie123
#uri: http://127.0.0.1:7001
# 负载均衡的配置
uri: lb://CLOUD-PROVIDER-MOVIE
predicates:
# http://localhost:10000/movie/xxx
- Path=/m/*
uri地址的配置还可以使用负载均衡的方式配置: 可以在注册中心中使用服务名来拉取配置
或者(使用的少):通过配置类实现代理路由
9.4 网关的 断言 功能
gateway项目启动的时候默认加载了多个断言工厂实例,在配置文件中可以直接通过断言工厂进行断言,满足断言是条件,请求才会被接受匹配完成路由。
- Path=/m/*
- Before=2022-02-17T14:23+08:00
- Between=2022-02-17T14:23:00.0+08:00,2020-02-17T14:26+08:00 # 断言,时间在23分和26分之间才可以被访问
- Method=GET # 断言,请求方式必须为get方式
- Cookie=token,123456 # 断言 请求必须携带token的cookie,值必须为123456
- Query=pwd,[a-z0-9_-]{6} # 断言 请求参数必须携带pwd 值必须为6位可以包含a-z,0-9,_ - 等任意的组合
spring:
application:
name: cloud-gateway
# 配置网关的代理和路由功能
cloud:
gateway:
# routes :就是一个 List<RouteDefinition> routes,
routes:
# -表示给集合创建一个元素 RouteDefinition 代表一个代理+路由对象
# id表示 一个代理路由对象的 唯一标志,可以随意指定但是推荐使用要代理的服务名称
- id: cloud-consumer-user
# 表示网关项目接受到访问user服务的请求时将请求路由给哪个地址来处理
uri: http://127.0.0.1:8000
# 断言集合配置:List<PredicateDefinition>
predicates:
# 地址的断言,访问网关的地址项目名后如果是/user/* 地址,则由本路由代理配置来处理
# 如果浏览器访问:http://localhost:10000/user/getUser?id=1 网关项目接受到后,会将此请求交给这个配置的路由处理
# 网关项目路由此请求给 http://127.0.0.1:8000/user/getUser?id=1
- Path=/user/*,/consumer/*
- id: cloud-provider-movie123
#uri: http://127.0.0.1:7001
# 负载均衡的配置
uri: lb://CLOUD-PROVIDER-MOVIE
predicates:
# http://localhost:10000/movie/xxx
- Path=/m/*
#- Before=2020-08-11T09:07+08:00 #断言 时间在今天9点7分之前才可以被访问
#- Between=2020-08-11T09:08:00.0+08:00,2020-08-11T09:09+08:00 # 断言,时间在8分和9分之间才可以被访问
#- Method=GET # 断言,请求方式必须为get方式
#- Cookie=token,123456 # 断言 请求必须携带token的cookie,值必须为123456
- Query=pwd,[a-z0-9_-]{6} # 断言 请求参数必须携带pwd 值必须为6位可以包含a-z,0-9,_ - 等任意的组合
9.5 网关的 过滤 功能
1、 GatewayFilter: 局部filter,可以设置给一个或一组路由进行使用
spring:
application:
name: cloud-gateway
# 配置网关的代理和路由功能
cloud:
gateway:
# routes :就是一个 List<RouteDefinition> routes,
routes:
# -表示给集合创建一个元素 RouteDefinition 代表一个代理+路由对象
# id表示 一个代理路由对象的 唯一标志,可以随意指定但是推荐使用要代理的服务名称
- id: cloud-consumer-user
# 表示网关项目接受到访问user服务的请求时将请求路由给哪个地址来处理
uri: http://127.0.0.1:8000
# 断言集合配置:List<PredicateDefinition>
predicates:
# 地址的断言,访问网关的地址项目名后如果是/user/* 地址,则由本路由代理配置来处理
# 如果浏览器访问:http://localhost:10000/user/getUser?id=1 网关项目接受到后,会将此请求交给这个配置的路由处理
# 网关项目路由此请求给 http://127.0.0.1:8000/user/getUser?id=1
- Path=/user/*,/consumer/*
- id: cloud-provider-movie123
#uri: http://127.0.0.1:7001
# 负载均衡的配置
uri: lb://CLOUD-PROVIDER-MOVIE
predicates:
# http://localhost:10000/movie/xxx
- Path=/m/*
filters: # 只对一个或一组路由起作用的filter
- AddResponseHeader=origin,atguigu # 给响应报文设置一个响应头
- AddRequestParameter=shenfen,youke # 给请求添加请求参数
2、 GlobalFilter: 全局filter,无需配置,可以对所有的路由起作用
使用全局filter对所有的路由进行请求报文的检查
检查报文中是否包含atguigutoken 的请求头,如果包含则放行如果不包含则响应未授权
@Component //全局filter需要是一个组件对象
public class MyTokenGlobalFilter implements GlobalFilter , Ordered {
//全局filter的业务代码
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//判断请求报文头中是否包含特定的请求头
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
String atguigutoken = headers.getFirst("atguigutoken");
if(StringUtils.isEmpty(atguigutoken)){
//请求头没有token 拒绝本次访问
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();//结束本次请求,不会路由给目标微服务
}
//放行 将本次请求路由给目标微服务
return chain.filter(exchange);
}
//所有的Globalfilter 都参考order的值决定哪个先执行,值越小优先级越高
@Override
public int getOrder() {
return 0;
}
}
10. Sleuth分布式链路跟踪
10.1 简介
Sleuth整合了zipkin
可以采集并存储 分布式服务处理请求过程中的信息和数据,并支持其他的程序访问获取收集得 数据,也提供了自己的服务端页面供我们直接查询他收集的数据显示
依赖于收集的数据 我们可以判断微服务之间的调用过程,查找慢服务,查找服务调用出现的错误,快速定位微服务系统的问题
下载和启动:
10.2 电影系统整合Sleuth
1、在user和movie的pom文件中引入zipkin启动器
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
2、在application.yml文件中配置zipkin
spring:
application:
name: cloud-provider-movie
# zipkin的配置
zipkin:
# zipkin服务端的访问地址
base-url: http://localhost:9411
sleuth:
sampler:
# 0~1 , 表示采样率,1表示全部采样
probability: 1
3、启动课件中的zipkin的服务端
java -jar zipkin-server-2.12.9-exec.jar
4、启动微服务,并访问
在浏览器中访问zipkin的服务端可以查看到微服务的处理请求的过程以及数据
11. springcloud-alibaba-nacos-注册中心
11.1 简介
启动课件中的nacos/bin/startup.cmd
在浏览器中访问:http://localhost:8848/nacos
默认需要登录,账号密码都为:nacos
11.2 基于nacos的服务提供者
项目名:nacos-provider-movie
配置:application.yml
spring:
application:
name: nacos-provider-movie
cloud:
nacos:
# nacos注册中心的地址
server-addr: localhost:8848
server:
port: 7000
启用nacos客户端: 在主程序类名上添加注解
@EnableDiscoveryClient //启用nacos客户端的功能
功能代码和eureka开发的项目是一样
11.3 基于nacos的服务消费者
项目名:nacos-consumer-user
引入依赖:web、nacos
添加配置:
spring:
application:
name: nacos-consumer-user
cloud:
nacos:
# nacos注册中心的地址
server-addr: localhost:8848
server:
port: 8000
启用nacos客户端: 在主程序类名上添加注解
@EnableDiscoveryClient //启用nacos客户端的功能
功能代码:可以参考eureka 服务消费者
12.springcloud-alibaba-nacos-配置中心
12.1 创建统一配置
12.2 微服务整合nacos配置中心加载配置
电影服务中整合配置中心
1、在电影服务中引入nacos配置中心的场景启动器
<!-- 引入nacos配置中心的场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
2、在resources下创建配置文件: bootstrap.properties
# 引导启动的配置文件,优先级高于application.properties和yml两个文件
# 配置中心的地址
spring.cloud.nacos.config.server-addr=localhost:8848
# 当前应用的名称:影响当前项目加载配置中心中文件的dataId
spring.application.name=nacos-provider-movie
3、在需要使用动态配置文件中的属性的组件类中通过@Value注解加载配置
@RefreshScope //表示当前Controller中有属性值需要动态的从配置中心加载
@RestController
@RequestMapping("/movie")
public class MovieController {
@Autowired
MovieService movieService;
//@Value("${server.port}")
@Value("${myName}")
String myName;
@GetMapping("/getMovie")
public Movie getMovie(@RequestParam("id")Integer id){
System.out.println("动态加载到配置中心的配置myName : " +myName);
return movieService.getMovie(id);
}
}
4、重启项目
访问测试,可以使用配置中心的配置
5、修改配置中心的配置,代码中myName会自动更新
12.3 名称空间切换配置环境
1、新建多个名称空间:每个名称空间对应一个开发中的环境(每个环境都由自己的配置)
2、在其他的环境中准备相同的配置文件[具体的配置内容不同]
3、在项目的 bootstrap.propertie配置文件中指定要使用的配置中心的名称空间
每个名称空间都由自己的一个唯一的id
# 引导启动的配置文件,优先级高于application.properties和yml两个文件
# 配置中心的地址
spring.cloud.nacos.config.server-addr=localhost:8848
# 当前应用的名称:影响当前项目加载配置中心中文件的dataId
spring.application.name=nacos-provider-movie
# 通过配置中心的名称空间的唯一id去指定使用哪个名称空间
spring.cloud.nacos.config.namespace=004741d8-506a-4ac2-b56a-ed2bffc4ce1d
12.4 配置回滚
12.5 加载多配置文件
1、在配置中心中创建多个配置文件:
2、在项目的bootstrap.properties中指定要加载的多配置文件的dataId
# 配置加载多个配置文件[和默认的配置的文件名命名规范不一样]
# 指定一个要加载的配置文件的名称
spring.cloud.nacos.config.ext-config[1].data-id=redis.properties
# 加载到的配置文件是否需要动态的刷新
spring.cloud.nacos.config.ext-config[1].refresh=true
spring.cloud.nacos.config.ext-config[0].data-id=jdbc.properties
spring.cloud.nacos.config.ext-config[0].refresh=true
3、在代码中通过@Value的方式加载配置文件中的内容
@RefreshScope//表示当前Controller中有属性值需要动态的从配置中心加载
@RestController
@RequestMapping("/movie")
public class MovieController {
@Autowired
MovieService movieService;
//@Value("${server.port}")
@Value("${redis.host}")
String redishost;
@Value("${mysql.url}")
String mysqlurl;
@Value("${myName}")
String myName;
@GetMapping("/getMovie")
public Movie getMovie(@RequestParam("id") Integer id){
System.out.println("redis的host:"+redishost);
System.out.println("mysqluel:"+mysqlurl);
System.out.println("动态加载到配置中心的配置myName : " +myName);
return movieService.getMovie(id);
}
}
12.6 配置分组
前面的名称空间已经可以保证我们在不同的环境中加载不同的配置文件了
但是在同一个环境中,多个微服务可能使用的相同配置文件的配置不同[例如:scw-user scw-manager在开发环境中各自连接各自的数据库,自己的redis…]
1、在配置中心中创建相同的配置文件,但是需要设置组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0k2zwCA-1645112208384)(assets/1597131965022.png)]
2、在项目的bootstrap.properties配置文件中指定加载配置文件的组
如果不指定默认加载DEFAULT_GROUP组中的配置
spring.cloud.nacos.config.ext-config[1].data-id=redis.properties
# 加载到的配置文件是否需要动态的刷新
spring.cloud.nacos.config.ext-config[1].refresh=true
spring.cloud.nacos.config.ext-config[1].group=MANAGER_GROUP
spring.cloud.nacos.config.ext-config[0].data-id=jdbc.properties
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.ext-config[0].group=MANAGER_GROUP
13. Sentinel实现熔断和限流
13.1 简介
Sentinel 可以通过控制台对整合了Sentinel的微服务进行限流
Sentinel还可以对整合了自己的微服务进行熔断保护
13.2 Sentinel运行访问
使用课件中的Snetinel进行运行:
在浏览器中访问:http://localhost:8080(有可能端口占用,停掉占用端口的服务)
访问时默认跳转到登录页面,账号密码为:sentinel
13.3 在服务提供者中整合sentinel
1、在项目的pom文件中引入sentinel的场景启动器
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、在application.yml文件中配置sentinel
spring:
application:
name: nacos-provider-movie
cloud:
nacos:
# nacos注册中心的地址
server-addr: localhost:8848
sentinel:
transport:
# 当前微服务和sentinel控制台进行数据传递的端口
port: 8719
# sentinel控制台路径配置
dashboard: http://localhost:8080
3、重启服务,在浏览器中访问后再刷新sentinel的面板页面
13.4 使用Sentinel进行流控
13.4.1 直接流控
浏览器中测试访问:http://localhost:7000/movie/getMovie?id=11 如果一秒访问的次数超过3次则进入流控模式
13.4.2 关联流控
当访问testA的请求数量达到QPS的阈值1时,导致访问getMovie的请求进入流控模式,快速返回默认失败数据
在postman里面使用线程组进行压力测试,模拟访问testA
13.4.3 线程数直接流控
13.4.4 关联流控 warm up 流控
为了防止访问后台的流量突然增加导致系统宕机,可以通过预热模式控制访问系统的请求的数量
使用单击阈值/3 从配置规则开始 可以通过qps的数量从 单击阈值/3逐渐增长到 单击阈值
从最低值到最大阈值中间的预热时间为自己设置的预热时长
13.4.5 排队等待流控
13.5 Sentinel-熔断降级
在系统执行时,人为设置规则让服务降级给用户返回兜底数据
13.5.1 相应时间降级
13.5.2 异常比例降级
13.5.3 异常数降级
异常数时间窗口为60秒以上
13.6 sentinel- 热点key限流
1、修改movie服务的方法
给方法指定资源名+指定异常处理方案[服务降级时使用我们自定义的降级方法处理]
@GetMapping("/testB")
@SentinelResource(value = "hotKey" , blockHandler = "testBExceptionHandle")
public String testB(@RequestParam(value="p1" , required = false)String p1 , @RequestParam("p2")String p2){
return "testB........";
}
//兜底方法
public String testBExceptionHandle(String p1 , String p2, BlockException exception){
System.out.println(exception.getMessage());
return "testBExceptionHandle........";
}
2、在sentinel面板中设置热点key的降级规则
13.7 系统规则流控
rt: 访问整个系统的所有的请求,平均响应时间如果超过阈值则会进行服务降级
线程数:访问整个系统的线程数如果超过阈值则进入服务降级
入口qps:访问系统的请求数量达到阈值会进入服务降级
13.8 @SentinelResource注解
@SentinelResource:
1、可以给controller方法指定资源名,流控可以使用资源名进行流控
2、给controller方法执行异常处理方法,当服务需要降级时可以使用它指定的降级方法返回兜底数据
@SentinelResource(value = “hotKey” , blockHandler = “testBExceptionHandle”)
问题: 异常处理方法和controller的方法返回值类型需要一致
异常处理方法必须接受异常
设置流控规则测试
13.9 sentinel- 规则持久化
1、在配置中心的public名称空间中创建sentinel需要持久化的流控规则配置
[
{
"resource": "testAa",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
2、在movie服务中整合sentinel-datasource-nacos依赖
<!-- 支持sentinel使用nacos配置中心的配置当做sentinel的流控配置 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
3、修改项目的yml配置文件
指定sentinel要加载的持久化配置的nacos注册中心的地址名称空间以及配置文件的dataId
spring:
application:
name: nacos-provider-movie
cloud:
nacos:
# nacos注册中心的地址
server-addr: localhost:8848
sentinel:
transport:
# 当前微服务和sentinel控制台进行数据传递的端口
port: 8719
# sentinel控制台路径配置
dashboard: http://localhost:8080
datasource:
ds1:
# 要加载sentinel默认配置的地方
nacos:
config:
# 名称空间
namespace: 27dd4dc9-be50-44aa-9eb0-5b18d48039eb
#高版本的nacos需要加上用户名密码,不然会出错
username: nacos
password: nacos
# nacos注册中心的地址
server-addr: localhost:8848
# nacos配置中心的配置文件名称
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
# 加载的配置文件中数据的格式
data-type: json
rule-type: flow
运行之后就可以在sentinel控制台看见添加的流控规则