1,微服务概述
服务之间通过HTTP协议相互通信。
微服务应用属于分布式系统。分布式系统是集群部署的,不同的多台服务器(计算机)上面部署不同的服务模块(工程)
为了防止"雪崩效应"事件的发生,分布式系统采用了熔断机制。
易于开发和维护,但运维要求高,服务部署难度加大
2,SpringCloud概述
1,SpringCloud简介
Java语言的微服务框架,提供了一系列开发组件,帮助开发者迅速搭建一个分布式的微服务系统,依赖于SpringBoot,有快速开发、持续交付和容易部署等特点。
2, SpringCloud版本问题
每个版本的SpringCloud对应相应版本的SpringBoot。
https://start.spring.io/actuator/info(火狐浏览器)
3. SpringCloud组件
1>服务的注册和发现:eureka、nacos、consul
2>服务的负载均衡:ribbon
3>服务的相互调用:openFeign
4>服务的容错:hystrix、sentinel
5>服务网关:gateway、zuul
6>服务配置的统一管理:configServer
7>服务消息总线:bus
8>服务安全组件:security、Oauth2.0
9>服务监控:admin、jvm
10>链路追踪:sleuth+zipkin
目前开发中微服务常见的落地实现有三种:
1> Dubbo+Zookeeper:半自动化的微服务实现架构
2> SpringCloud Netflix:一站式微服务架构
3> SpringCloud Alibaba:新的一站式微服务架构
3,SpringCloud Eureka
一个服务注册与发现的组件。
1,分布式系统的CAP定理
C(Consistency)强一致性:
多个副本保持一致性
A(Availability)可用性:
系统提供的服务必须一直处于可用的状态,请求能响应(不保证获取的数据为最新数据);
P(Partition tolerance)分区容错性(可靠性):
网络分区故障的时候,仍然能够对外提供可用性的服务
因此CAP定理分成了满足CA原则、满足CP原则和满足AP原则三大类:
1)CA:满足一致性和可用性,放弃分区容错;说白了,就是一个单体的应用。
2)CP:满足一致性和分区容错性,放弃了可用性;当系统被分区,为了保证一致性,必须放弃可用性,让服务停用。
3)AP:满足可用性和分区容错性;当出现分区,同时为了保证可用性,必须让节点继续对外服务,必然导致失去一致性。
2,Eureka和Zookeeper的区别
Zookeeper注重数据的强一致性。若主机挂了,则Zookeeper集群整体就不对外提供服务了,需要花费120秒左右选一个新的主机出来才能继续对外提供服务。CP
Eureka不是很注重数据的一致性。Eureka集群只要有一台活着,它就能对外提供服务。AP
3, SpringCloud Eureka入门使用
1,创建项目,搭建Eureka-Server模块
1,主运行程序加@EnableEurekaServer注解开启eureka服务端(注册中心)
//开启eureka服务端(注册中心)
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer01Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer01Application.class, args);
}
}
2,配置application.yml文件
#设置eureka服务端应用名称
spring:
application:
name: eureka-server-01
#设置eureka服务端(注册中心)端口 -- 默认端口8761
server:
port: 8761
2,搭建Eureka-Client模块
1,主运行程序加@EnableEurekaServer注解开启eureka服务端(注册中心)同上
2,配置application.yml文件
#设置eureka客户端应用名称
spring:
application:
name: eureka-client-01
#设置eureka服务端(注册中心)端口 -- 默认端口8761
server:
port: 8080
#将我们的应用(服务)注册到注册中心 -- 值为eureka服务端(注册中心)的url地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
3,运行两个模块访问localhost:8761/
4,配置文件常见设置
1,Eureka服务端常见设置
eureka:
server:
#注册中心定期剔除服务列表中未续约服务的时间周期--60000毫秒
eviction-interval-timer-in-ms: 60000
#是否开启保护机制--避免因为网络等原因客户端未及时续约,而导致误剔除
enable-self-preservation: true
#未续约服务的阈值(百分比)--未续约的服务只要不超过这个百分比就不做剔除--体现了eureka的高可用性
renewal-percent-threshold: 0.85
#是否注册当前服务(eureka服务端应用)到注册中心
client:
register-with-eureka: false
2,Eureka客户端常见设置
eureka:
client:
#是否将当前服务(我们的应用(服务))注册到注册中心
register-with-eureka: true
#将当前应用(服务)注册到注册中心--值为eureka服务端(注册中心)的url地址
service-url:
defaultZone: http://localhost:8761/eureka
#是否从注册中心拉取服务列表
fetch-registry: true
#从注册中心拉取服务列表的时间周期--每30秒钟拉取更新一次
registry-fetch-interval-seconds: 30
##############################
instance:
#服务续约的时间周期--每30秒续约一次
lease-renewal-interval-in-seconds: 30
#服务续约的超时时间,超出该时间没有续约,注册中心则剔除该服务--90秒
lease-expiration-duration-in-seconds: 90
#服务在注册中心的注册信息的主机名称--localhost
hostname: localhost
#服务在注册中心的注册信息的显示格式--主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示服务部署到的主机的IP
prefer-ip-address: true
5,注册中心集群
1,实现目标和方案
2, 修改hosts文件
通过修改C:\Windows\System32\drivers\etc下的hosts文件,给本地计算机配置三个主机名来充当三台计算机。
如果配置没有生效,在DOS下执行命令ipconfig /flushdns,刷新一下DNS解析缓存。
3,编写程序
1,创建三个eureka服务端(注册中心)
配置application.yml文件:
#设置当前eureka服务端应用名称 -- 统一为eureka-server
spring:
application:
name: eureka-server
#设置当前eureka服务端(注册中心)端口 -- 第一个为8761
server:
port: 8761
#设置当前eureka服务端的主机名 -- 第一个为peer1
eureka:
instance:
hostname: peer1
#当前eureka服务端应用在注册中心的注册信息的显示格式--主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示当前eureka服务端应用部署到的主机的IP
prefer-ip-address: true
#将当前eureka服务端应用注册到主机名peer2端口8762和主机名peer3端口8763的eureka服务端(注册中心)
client:
service-url:
defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka
#设置当前eureka服务端应用名称 -- 统一为eureka-server
spring:
application:
name: eureka-server
#设置当前eureka服务端(注册中心)端口 -- 第二个为8762
server:
port: 8762
#设置当前eureka服务端的主机名 -- 第二个为peer2
eureka:
instance:
hostname: peer2
#当前eureka服务端应用在注册中心的注册信息的显示格式--主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示当前eureka服务端应用部署到的主机的IP
prefer-ip-address: true
#将当前eureka服务端应用注册到主机名peer1端口8761和主机名peer3端口8763的eureka服务端(注册中心)
client:
service-url:
defaultZone: http://peer1:8761/eureka,http://peer3:8763/eureka
#设置当前eureka服务端应用名称 -- 统一为eureka-server
spring:
application:
name: eureka-server
#设置当前eureka服务端(注册中心)端口 -- 第三个为8763
server:
port: 8763
#设置当前eureka服务端的主机名 -- 第三个为peer3
eureka:
instance:
hostname: peer3
#当前eureka服务端应用在注册中心的注册信息的显示格式--主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示当前eureka服务端应用部署到的主机的IP
prefer-ip-address: true
#将当前eureka服务端应用注册到主机名peer1端口8761和主机名peer2端口8762的eureka服务端(注册中心)
client:
service-url:
defaultZone: http://peer2:8761/eureka,http://peer2:8762/eureka
在主运行程序上都开启eureka服务端(注册中心)
2,创建 eureka客户端
配置application.yml文件:
#设置当前eureka客户端应用名称
spring:
application:
name: my-server-01
#设置当前eureka服务端(注册中心)端口
server:
port: 8080
eureka:
client:
#是否将当前服务(我们的应用(服务))注册到注册中心
register-with-eureka: true
#将当前应用(服务)分别向主机名peer1端口8761、主机名peer2端口8762、主机名peer3端口8763的
#三个注册中心都注册
service-url:
defaultZone: http://localhost:8761/eureka,http://peer2:8762/eureka,
http://peer3:8763/eureka
#服务在注册中心的注册信息的主机名称--localhost
instance:
hostname: localhost
#服务在注册中心的注册信息的显示格式--主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示服务部署到的主机的IP
prefer-ip-address: true
分别启动eureka服务端(注册中心)eureka-server-01、eureka-server-02、eureka-server-03,然后启动应用(服务)my-server-01,集群搭建成功
6,服务发现
服务发现是指,一个服务(集群下就是服务的一个实例)通过从注册中心拉取的服务列表,根据服务(应用)名称,发现并找到另一个服务(集群下就是服务的一个实例)。
1, 创建eureka服务端(注册中心)
2,创建eureka-1客户端(应用)
1,主运行程序标记@EnableEurekaClient
//标记此应用(服务)为eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class MyServer01Application {
public static void main(String[] args) {
SpringApplication.run(MyServer01Application.class, args);
}
}
2,配置文件
#设置应用(服务)名称 -- my-server-1
spring:
application:
name: my-server-1
#设置应用(服务)的端口 -- 8080
server:
port: 8080
#将当前应用(服务)注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
3,编写controller-1
@RestController
public class Controller-1 {
//处理/info的请求,然后响应字符串文本"服务my-server-03被发现了"
@RequestMapping("/info")
public String info(){
return "服务my-server-1被发现了";
}
}
3,创建eureka-2客户端(应用)
1,主运行程序标记@EnableEurekaClient
//标记此应用(服务)为eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class MyServer02Application {
public static void main(String[] args) {
SpringApplication.run(MyServer02Application.class, args);
}
//配置RestTemplate的bean对象添加到容器,其可以发送rest风格的请求
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2,配置文件
#设置应用(服务)名称 -- my-server-2
spring:
application:
name: my-server-2
#设置应用(服务)的端口 -- 8081
server:
port: 8081
#将当前应用(服务)注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
3,编写controller-2
@RestController
public class Controller-2 {
//注入DiscoveryClient,使用其可以发现服务
@Autowired
private DiscoveryClient discoveryClient;
//注入RestTemplate,使用其可以发送rest风格的请求
@Autowired
private RestTemplate restTemplate;
/*
处理/find的请求并传参serverName -- 值给服务(应用)my-server-03的名称;
然后响应字符串文本给客户端;
*/
@RequestMapping("/find")
public String find(String serverName){
//拿到参数名称的服务(应用)的所有实例(非集群下是单个实例,集群下就是多个实例)
List<ServiceInstance> instances = discoveryClient.getInstances(serverName);
//拿到0角标的服务(应用)的实例(目前是非集群,服务只有一个实例)
ServiceInstance instance = instances.get(0);
//拿到服务实例的IP和端口
String ip = instance.getHost();
int port = instance.getPort();
//拼接出请求url -- my-server-03服务(应用)的/info请求的url
String url = "http://"+ip+":"+port+"/info";
//发出请求,并接收my-server-03服务(应用)的/info请求响应的字符串文本
String result = restTemplate.getForObject(url, String.class);
//将接收的my-server-03服务(应用)的/info请求响应的字符串文本再响应给客户端
return result;
}
}
4,运行
先启动eureka服务端(注册中心)eureka-server-01,再启动eureka客户端(应用)my-server-01,最后启eureka客户端(应用)my-server-02
4,组件 RestTemplate
1,RestTemplate的介绍
RestTemplate是Spring提供的一个HTTP请求工具,它提供了很多用于发送REST请求(GET请求、POST请求、PUT请求、DELETE请求)的模版方法。
常用方法:
T | getForObject(String url, Class<T> responseType)
发送get请求,参数一请求url,参数二请求的响应数据类型,返回值接收的请求响应的数据。
ResponseEntity<T> | getForEntity(String url, Class<T> responseType)
发送get请求,参数一请求url,参数二请求的响应数据类型,返回值ResponseEntity对象(响应实体,就是整个响应对象,包含了响应协议的所有内容)。
T | postForObject(String url, Object request, Class<T> responseType)
发送post请求,参数一请求url,参数二为请求数据,参数三请求的响应数据类型,返回值接收的请求响应的数据。
ResponseEntity<T> | postForEntity(String url, Object request, Class<T> responseType)
发送post请求,参数一请求url,参数二为请求数据,参数三请求的响应数据类型,返回值ResponseEntity对象(响应实体,就是整个响应对象,包含了响应协议的所有内容)。
2,RestTemplate的使用
1,创建实体类User,属性id,name
2,创建HelloController控制类
@RestController
public class HelloController {
/*
处理/项目/hello1的get请求,并接收请求参数name,最后向客户端响应
字符串文本"ok"
*/
@GetMapping("/hello1")
public String hello1(String name){
System.out.println(name);
return "ok";
}
/*
处理/项目/hello2/id的get请求,并将路径占位符id的值赋值给方法入参id,
最后向客户端响应字符串文本"ok"
*/
@GetMapping("/hello2/{id}")
public String hello2(@PathVariable Integer id){
System.out.println(id);
return "ok";
}
/*
处理/项目/hello3的post请求,并接收表单请求数据,最后向客户端响应字符串
文本"ok"
*/
@PostMapping("/hello3")
public String hello3(User user){
System.out.println(user);
return "ok";
}
/*
处理/项目/hello4的post请求,并接收请求发送的json数据,最后向客户端响应
字符串文本"ok"
*/
@PostMapping("/hello4")
public String hello4(@RequestBody User user){
System.out.println(user);
return "ok";
}
}
3,创建测试类RestTemplateApplicationTests
@Test
public void testGet1(){
//创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
//组装url
String url = "http://localhost:8080/hello1?name=lisi";
/*
发送get请求,请求url即为http://localhost:8080/hello1?name=lisi,
并接收请求响应的字符串文本;
*/
String result = restTemplate.getForObject(url, String.class);
System.out.println(result);
}
控制台输出:lisi ok
@Test
public void testGet2(){
//创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
//组装url
String url = "http://localhost:8080/hello2/101";
/*
发送get请求,请求url即为http://localhost:8080/hello2/101,
并接收请求响应的字符串文本;
*/
String result = restTemplate.getForObject(url, String.class);
System.out.println(result);
}
控制台输出:101 ok
@Test
public void testPost1(){
//创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
//组装url
String url = "http://localhost:8080/hello3";
//发送表单数据得使用LinkedMultiValueMap来组装表单数据
LinkedMultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
map.add("id", 102);
map.add("name", "wangwu");
map.add("age", 20);
/*
发送post请求,请求url即为http://localhost:8080/hello3,
并发送表单数据(LinkedMultiValueMap对象),并接收请求响应的字符串文本;
*/
String result = restTemplate.postForObject(url, map, String.class);
System.out.println(result);
}
控制台输出:User{id=102, name='wangwu', age=20} ok
@Test
public void testPost2(){
//创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
//组装url
String url = "http://localhost:8080/hello4";
//发送json数据直接发送实体对象即可,其会自动转成json数据
User user = new User();
user.setId(103);
user.setName("zhaoliu");
user.setAge(21);
/*
发送post请求,请求url即为http://localhost:8080/hello4,
并发送请求数据user对象(其会转成json数据发送),并接收请求响应的字符串文本;
*/
String result = restTemplate.postForObject(url, user, String.class);
System.out.println(result);
}
控制台输出:User{id=103, name='zhaoliu', age=21} ok
@Test
public void testResponseEntity(){
//创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
//组装url
String url = "http://localhost:8080/hello4";
//发送json数据直接发送实体对象即可,其会自动转成json数据
User user = new User();
user.setId(103);
user.setName("zhaoliu");
user.setAge(21);
/*
换成postForEntity()方法发送http://localhost:8080/hello4的
post请求,并接收响应实体ResponseEntity对象,其就包含了响应协议的所有内容;
*/
ResponseEntity<String> entity = restTemplate.postForEntity(url, user, String.class);
System.out.println(entity.getHeaders());//获取响应头
System.out.println(entity.getBody());//获取响应体
System.out.println(entity.getStatusCodeValue());//获取响应状态码
}
控制台输出:
User{id=103, name='zhaoliu', age=21}
[Content-Type:"text/plain;charset=UTF-8",Content-Length:"2",
Date:"Fri, 08 Jul 2021 15:06:59 GMT",Keep-Alive:"timeout=60",Connection:"keep-alive"]
ok
200
5, SpringCloud Ribbon
1,负载均衡
将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,从而协同完成工作任务。
2,SpringCloud Ribbon的概述
一个基于HTTP和TCP的客户端的负载均衡工具,它基于Netflix Ribbon实现;通过Spring Cloud的封装,将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
主要功能是提供客户端负载均衡算法和服务调用
对比Nginx:
1,Nginx的负载均衡,是在服务器端完成的负载均衡,是对服务器的负载均衡:
2,Ribbon的负载均衡,是在客户端完成的负载均衡。
3,客户端负载均衡实现
1,创建eureka服务端(注册中心)
2,创建eureka客户端[服务(应用)]service-a-1
1,配置文件
#设置应用(服务)名称 -- 统一为service-a
spring:
application:
name: service-a
#设置应用(服务)的端口
server:
port: 8080
#将当前应用(服务)注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
#设置当前应用(服务)部署的主机名 -- 为peer1
instance:
hostname: peer1
#设置应用(服务)注册信息的显示格式 -- 主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示服务部署到的主机的IP
prefer-ip-address: true
2,创建控制类Controller-1
@RestController
public class Controller-1 {
//处理/info的请求,然后向客户端响应字符串文本
@RequestMapping("/info")
public String info(){
return "我是service-a集群的service-a-1";
}
}
3,创建eureka客户端[服务(应用)]service-a-2
1,配置文件
#设置应用(服务)名称 -- 统一为service-a
spring:
application:
name: service-a
#设置应用(服务)的端口
server:
port: 8081
#将当前应用(服务)注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
#设置当前应用(服务)部署的主机名 -- 为peer1
instance:
hostname: peer2
#设置应用(服务)注册信息的显示格式 -- 主机名:应用名称:端口
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
#显示服务部署到的主机的IP
prefer-ip-address: true
2,创建控制类Controller-1
@RestController
public class Controller-2 {
//处理/info的请求,然后向客户端响应字符串文本
@RequestMapping("/info")
public String info(){
return "我是service-a集群的service-a-2";
}
}
4,创建服务(应用)[eureka客户端]service-b
1,引入Ribbon依赖
<!--引入的ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2,修改主运行程序类
/标记此应用(服务)为eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class ServiceBApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceBApplication.class, args);
}
//配置RestTemplate的bean对象到容器,使用其可以发送请求
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
3,配置文件
基本配置,服务名,服务端口号,注册到注册中心
4,创建Controller-b类
@RestController
public class Controller-b {
//注入DiscoveryClient,用其发现服务
@Autowired
private DiscoveryClient discoveryClient;
//注入RestTemplate,用其发送请求
@Autowired
private RestTemplate restTemplate;
private int n = 1;
/*
处理/find的请求并传参serverName -- 值给服务(应用)名称service-a;
然后响应字符串文本给客户端;
*/
@RequestMapping("/find")
public String find(String serverName){
//调用自定义负载均衡算法拿到服务实例
ServiceInstance instance = chooseService(serverName);
//拿到服务实例的IP和端口
String ip = instance.getHost();
int port = instance.getPort();
//拼接出请求url -- service-a服务的/info请求的url
String url = "http://"+ip+":"+port+"/info";
//发出请求,并接收service-a服务的/info请求响应的字符串文本
String result = restTemplate.getForObject(url, String.class);
//将接收的service-a服务的/info请求响应的字符串文本再响应给客户端
return result;
}
/*
自定义负载均衡算法 -- 轮询:
切换访问部署在peer1主机和peer2主机上的service-a服务
*/
public ServiceInstance chooseService(String serverName){
//拿到参数名称的服务(应用)的所有实例(集群下是多个实例) -- service-a服务有两个实例
List<ServiceInstance> instances = discoveryClient.getInstances(serverName);
n++;//n自增
if(n%2==0){//如果n为偶数,则返回List<ServiceInstance>中的0下标的服务实例
return instances.get(0);
}else{//如果n为奇数,则返回List<ServiceInstance>中的1下标的服务实例
return instances.get(1);
}
}
5,启动测试
先启动eureka服务端eureka-server,再启动eureka客户端service-a-1和service-a-2,最后再启动eureka客户端service-b
轮询实现
4, Ribbon实现客户端负载均衡
1,注入方法修改Controller类
@RestController
public class FindController {
//注入LoadBalancerClient,其持有ribbon提供的负载均衡算法
@Autowired
private LoadBalancerClient loadBalancerClient;
/*
处理/find2的请求并传参serverName -- 值给服务(应用)名称service-a;
然后响应字符串文本给客户端;
*/
@RequestMapping("/find2")
public String find2(String serverName){
//调用ribbon提供的负载均衡算法拿到服务实例
ServiceInstance instance = loadBalancerClient.choose(serverName);
//拿到服务实例的IP和端口
String ip = instance.getHost();
int port = instance.getPort();
//拼接出请求url -- service-a服务的/info请求的url
String url = "http://"+ip+":"+port+"/info";
//发出请求,并接收service-a服务的/info请求响应的字符串文本
String result = restTemplate.getForObject(url, String.class);
//将接收的service-a服务的/info请求响应的字符串文本再响应给客户端
return result;
}
ribbon默认使用的也是轮询算法。
2,注解方法修改Controller类
1,修改主运行程序类配置
//标记此应用(服务)为eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class ServiceBApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceBApplication.class, args);
}
//配置RestTemplate的bean对象到容器,使用其可以发送请求
@Bean
//标注@LoadBalanced注解,表示让ribbon来管理RestTemplate
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2,修改controller类
@RestController
public class FindController {
//注入RestTemplate,用其发送请求
@Autowired
private RestTemplate restTemplate;
/*
处理/find3的请求并传参serverName -- 值给服务(应用)名称service-a;
然后响应字符串文本给客户端;
*/
@RequestMapping("/find3")
public String find3(String serverName){
//组装url -- 协议之后不需要ip和端口,直接拼接服务名,ribbon会通过负载均衡算法处理
String url = "http://"+serverName+"/info";
//发出请求,并接收service-a服务的/info请求响应的字符串文本
String result = restTemplate.getForObject(url, String.class);
//将接收的service-a服务的/info请求响应的字符串文本再响应给客户端
return result;
}
}
5,Ribbon实现机制
- 拦截请求
- 获取请求的url地址:http://服务名/info
- 截取url地址中的服务名
- 从服务列表中找到该名称的服务的实例集合(服务发现)
- 根据负载均衡算法选出一个实例(默认也是轮询算法)
- 拿到该实例的ip和port,替换原来url中的服务名
- 发送真正的请求:restTemplate.getForObject("http://ip:port/info", String.class)
6, 修改Ribbon的负载均衡算法
1,修改配置文件
#service-a被访问的服务名称;值负载均衡算法实现类的完整类路径
service-a:
ribbon:
NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule
2,修改配置类
//配置IRule接口的具体的实现类的bean对象到容器
@Bean
public IRule myRule() {
//指定访问所有服务都使用此算法
return new RandomRule();
}
6,SpringCloud OpenFeign
1,OpenFeign的概述
OpenFeign集成了Ribbon,Ribbon又集成了eureka,通过OpenFeign基于注解可以快速简单的开发服务客户端,并完成服务发现和客户端负载均衡。
2,OpenFeign的使用
1,服务发现
2,搭建eureka服务端(注册中心)eureka-server
3,搭建eureka客户端(提供者服务)order-service
1,创建控制类OrderController
@RestController
public class OrderController {
//处理/doOrder的请求,然后向客户端响应字符串文本
@RequestMapping("/doOrder")
public String doOrder(){
return "下单---买的北京烤鸭";
}
}
4,搭建eureka客户端(消费者服务)user-service
1,添加OpenFeign依赖
<!--引入的OpenFeign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2,启动类标注@EnableFeignClients注解
//标注@EnableFeignClients注解,表示此应用(服务)是被feign管理的客户端服务
@EnableFeignClients
//标记此应用(服务)为eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
3,定义feign接口类
/*
1.接口名称为UserOrderFeign,表示是user-service服务访问order-service服务
的feign接口;
2.@FeignClient(value = "order-service")指定提供者服务(被访问的服务)的
应用(服务)名称;
*/
@FeignClient(value = "order-service")
public interface UserOrderFeign {
/*
3.指定提供者服务处理请求的方法签名:
指定提供者服务处理请求的方法的名称、参数、返回值类型、标注的注解、
处理的请求的url -- 除了没有方法体以外,其它的和提供者服务处理请求
的方法一模一样;
*/
@RequestMapping("/doOrder")
public String doOrder();
}
4,创建客户端消费者controller类
@RestController
public class UserController {
//注入UserOrderFeign
@Autowired
private UserOrderFeign userOrderFeign;
//处理/userDoOrder的请求,并向客户端响应字符串文本
@RequestMapping("/userDoOrder")
public String userDoOrder(){
/*
调用UserOrderFeign的doOrder()方法,即向order-service服务发出了
/doOrder的请求,并接收了其响应的字符串文本
*/
String result = userOrderFeign.doOrder();
//将接收的order-service服务的/doOrder请求响应的字符串文本再响应给客户端
return result;
}
}
5,启动
启动eureka服务端(注册中心)eureka-server,再启动提供者服务order-service,最后启动消费者服务user-service
6, 请求超时设置
在客户端消费者服务配置文件内修改
#设置请求超时时长为3秒
ribbon:
ReadTimeout: 3000
#设置连接超时时长也为3秒
ConnectTime: 3000
7,OpenFeign实现机制
1)OpenFeign通过反射机制为feign接口创建出一个代理对象
2)拿到feign接口上的@FeignClient注解的value值,解析出url:http://order-service
3)拿到feign接口中的方法签名上的@RequestMapping注解,解析出url:http://order-service/doOrder
4)将解析出的url交给ribbon
5)ribbon截取出url地址中的服务名
6)从服务列表中找到该名称的服务的实例集合(服务发现)
7)根据负载均衡算法选出一个实例(默认是轮询算法)
8)拿到该实例的ip和port,替换原来url中的服务名
9)发送真正的请求:restTemplate.getForObject("http://ip:port/doOrder", String.class)
3, OpenFeign调用传参
1)GET请求,通过URL占位符传参,参数列表使用@PathVariable。
2)GET请求,每个基本参数必须加@RequestParam。
3)POST请求,对象集合等参数,必须加@Requestbody或者@RequestParam。
1,创建提供者服务order-service:
1,创建实体类Oder,属性id,name
2,创建controller类
@RestController
public class OrderController {
/*
get方式url传参:
处理/urlParam/{id}的get请求,并将url占位符id的值赋值给请求
处理方法入参id;
*/
@GetMapping("/urlParam/{id}")
public void urlParam(@PathVariable("id") Integer id){
System.out.println(id);
}
/*
get方式传递单个参数:
处理/oneParam的get请求,并将请求参数name的值赋值给请求处理
方法入参name;
*/
@GetMapping("/oneParam")
public void oneParam(@RequestParam("name") String name){
System.out.println(name);
}
/*
get方式传递多个参数:
处理/twoParam的get请求,并将请求参数name和age的值分别赋值给请
求处理方法入参name和age;
*/
@GetMapping("/twoParam")
public void twoParam(@RequestParam("name") String name,
@RequestParam("age") Integer age){
System.out.println(name+" "+age);
}
/*
post方式传参json数据:
处理/objectParam的post请求,并将请求传递的json数据封装到请求处
理方法入参Order对象中;
*/
@PostMapping("/objectParam")
public void objectParam(@RequestBody Order order){
System.out.println(order);
}
/*
post方式传参json数据外加一个参数:
处理/oneObjOneParam的post请求,并将请求传递的json数据封装到请求处
理方法入参Order对象,将请求参数name赋值给方法入参name;
*/
@PostMapping("/oneObjOneParam")
public void oneObjOneParam(@RequestBody Order order,
@RequestParam("name") String name){
System.out.println(order+" "+name);
}
}
2,创建消费者服务user-service:
1,创建Order实体类
2,创建feign接口类
@FeignClient(value = "order-service")
public interface UserOrderFeign {
@RequestMapping("/doOrder")
String doOrder();
//--------------------------------------------------------------------------
/*
3.指定提供者服务处理请求的方法签名:
和提供者服务的请求处理方法的方法名、参数、返回值类型、标注的注解、
处理的请求的url保持一致;
*/
//order-service服务的请求处理方法urlParam()的方法签名
@GetMapping("/urlParam/{id}")
void urlParam(@PathVariable("id") Integer id);
//order-service服务的请求处理方法oneParam()的方法签名
@GetMapping("/oneParam")
void oneParam(@RequestParam("name") String name);
//order-service服务的请求处理方法twoParam()的方法签名
@GetMapping("/twoParam")
void twoParam(@RequestParam("name") String name, @RequestParam("age") Integer age);
//order-service服务的请求处理方法objectParam()的方法签名
@PostMapping("/objectParam")
void objectParam(@RequestBody Order order);
//order-service服务的请求处理方法oneObjOneParam()的方法签名
@PostMapping("/oneObjOneParam")
void oneObjOneParam(@RequestBody Order order, @RequestParam("name") String name);
}
3,创建controller类
@RestController
public class UserController {
//注入UserOrderFeign
@Autowired
private UserOrderFeign userOrderFeign;
//处理/userDoOrder的请求,并向客户端响应字符串文本
@RequestMapping("/userDoOrder")
public String userDoOrder(){
/*
调用UserOrderFeign的doOrder()方法,即向order-service服务发出了
/doOrder的请求,并接收了其响应的字符串文本
*/
String result = userOrderFeign.doOrder();
//将接收的order-service服务的/doOrder请求响应的字符串文本再响应给客户端
return result;
}
//------------------------------------------------------------------------
//处理/testParam的请求,然后向客户端响应字符串文本
@RequestMapping("/testParam")
public String testParam(){
/*
调用UserOrderFeign的urlParam()方法,即向order-service服务发出了
/urlParam/{id}的请求,并向url占位符id传参101
*/
userOrderFeign.urlParam(101);
userOrderFeign.oneParam("zhangsan");
userOrderFeign.twoParam("lisi", 20);
Order order1 = new Order(1, "IPhone", new Date(), 101);
userOrderFeign.objectParam(order1);
Order order2 = new Order(2, "IPad", new Date(), 102);
userOrderFeign.oneObjOneParam(order2, "wangwu");
return "ok";
}
}
3,启动
启动eureka服务端(注册中心)eureka-server,再启动提供者服务order-service,最后启动消费者服务user-service
控制台输出:
101
zhangsan
lisi 20
Order{orderId=1, orderName='IPhone', orderTime=Sun Jul 10 19:28:51 CST 2022, userId=101}
Order{orderId=2, orderName='IPad', orderTime=Sun Jul 10 19:28:51 CST 2022, userId=102} wangwu
4,OpenFeign日志配置
1)向容器中添加枚举Logger.Level的枚举值,开启OpenFeign的日志功能。
其有四个值:
NONE:默认的,不显示日志。
BASE:仅记录请求方法、URL、响应状态码及执行时间。
HEADERS:在BASE之上增加了请求和响应头的信息。
FULL:在HEADERS之上增加了请求和响应的正文及无数据,即所有信息。
2)在配置文件中通过logging.level.feign接口完整类路径设置日志级别。
1,修改消费者配置类
/*
向容器中添加枚举Logger.Level的枚举值FULL,开启OpenFeign的日志功能,
并打印所有信息;
*/
@Bean
public Logger.Level feignLogger(){
return Logger.Level.FULL;
}
2,修改消费者配置文件
#设置日志级别为debug级别
logging:
level:
com.mmy.feign.UserOrderFeign: debug
7,SpringCloud Hystrix
1,服务雪崩的概述
1,什么是服务雪崩
1)当用户访问A服务的一个url接口时,部署A服务的tomcat会给用户分配一个线程,支持用户访问。
2)A服务要完成用户的操作,又需要访问B服务。
3)A服务又去访问B服务,部署B服务的tomcat会给A服务分配一个线程,支持A服务的访问。
4)B服务要完成A服务的操作,又需要访问C服务。
5)B服务又去访问C服务,但是C服务挂了;B服务在访问C服务之前不知道C服务挂了,B去访问,直到超时,才知道C服务无法访问。
因为C服务不可用,导致B服务的线程不能及时回收,从而导致A服务的线程也不能及时回收,最终导致整个服务链的线程池中没有线程可用了;
2,服务雪崩解决思路
服务雪崩的本质其实是线程没有及时回收
1,方案一
将服务间的调用超时时长改小,这样就可以让线程及时回收
优点:简单,有效解决服务雪崩。
缺点:不灵活,有的服务需要等待较长时间,调用超时小,没访问到服务,线程就回收了。
2,方案二
B服务调用C服务时会先经过拦截器,拦截器知道C服务的状态,在拦截器中会对C服务的状态进行判断,如果C服务正常则继续调用,如果C服务挂了则直接return。Hystrix的实现方案就是使用拦截器。
2, SpringCloud Hystrix简介
1,熔断器
熔断器,也叫断路器(正常情况下,断路器是关着的,只有出了问题才会打开),是用来保护微服务不雪崩的方法,思路就是上面的拦截器思路。
2, Hystrix
Hystrix是Netflix公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。Hystrix 是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。
3, SpringCloud Hystrix使用
1,OpenFeign中使用Hystrix
1,搭建eureka服务端(注册中心)eureka-server
2,搭建eureka客户端(提供者服务)order-service
@EnableEurekaClient标记启动类
配置文件设置服务名称和端口号,注册到注册中心
控制器编写doOrder方法
3,搭建eureka客户端(消费者服务)user-service
1,修改配置文件
#开启hystrix断路器
feign:
hystrix:
nabled: true
2,UserOrderFeign接口的实现类作为备选方案
/将UserOrderFeign接口的实现类UserOrderHystrix的bean对象添加到容器
@Component
public class UserOrderHystrix implements UserOrderFeign {
//重写UserOrderFeign接口的doOrder()方法 -- 备选方案
@Override
public String doOrder() {
return "下单失败了";
}
}
3,创建feign接口类
/*
1.接口名称为UserOrderFeign,表示是user-service服务访问order-service服务
的feign接口;
2.@FeignClient(value = "order-service")指定提供者服务(被访问的服务)的
应用(服务)名称;
fallback = UserOrderHystrix.class指定备选方案为实现类UserOrderHystrix;
*/
@FeignClient(value = "order-service", fallback = UserOrderHystrix.class)
public interface UserOrderFeign {
/*
3.指定提供者服务处理请求的方法签名:
指定提供者服务处理请求的方法的名称、参数、返回值类型、标注的注解、
处理的请求的url -- 除了没有方法体以外,其它的和提供者服务处理请求
的方法一模一样;
*/
@RequestMapping("/doOrder")
public String doOrder();
}
4,控制器类
@RestController
public class UserController {
/*
注入UserOrderFeign:
会爆红,因为容器中目前有两个UserOrderFeign的bean对象,一个是OpenFeign
为其提供的代理对象,一个是我们定义的UserOrderFeign接口的实现类UserOrderHystrix
的bean对象 --- 不用管
*/
@Autowired
private UserOrderFeign userOrderFeign;
//处理/userDoOrder的请求,并向客户端响应字符串文本
@RequestMapping("/userDoOrder")
public String userDoOrder(){
/*
调用UserOrderFeign的doOrder()方法,即向order-service服务发出了
/doOrder的请求,并接收了其响应的字符串文本;
如果order-service服务正常,则执行order-service服务中处理/doOrder
请求的doOrder()方法;如果order-service服务出现问题,则执行我们定义
的UserOrderFeign接口的实现类UserOrderHystrix中重写的doOrder()方法;
*/
String result = userOrderFeign.doOrder();
//将接收的order-service服务的/doOrder请求响应的字符串文本再响应给客户端
return result;
}
}
5,运行
启动eureka服务端(注册中心)eureka-server,再启动消费者服务user-service
4, Hystrix相关概念
1,降级
是指当请求超时、资源不足等情况发生时,进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
2,熔断
当一定时间内,异常请求(请求超时、网络故障、服务异常等)比例达到阈值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据(降级),保证服务链的完整,避免服务雪崩。会持续发送新的请求。
降级仅是出错了返回托底数据;而熔断是出错后开启熔断器返回托底数据,且在一定时间内不再访问服务。
3,资源隔离策略
Hystrix的资源隔离策略有两种:线程池和信号量。
1,线程池
2,信号量
采用信号量隔离策略,每接收一个请求,都是服务自身线程去直接调用被访问服务,信号量就相当于一个关卡,每个线程通过关卡后,信号量数量减1,当为0时不再允许线程通过,而是直接执行fallback返回托底数据,仅仅做了一个限流。
3,对比
5,Feign的工程化实例
1,创建feign-project主工程
引入相关依赖
2,创建eureka-server服务端生产者工程
依赖继承父maven工程
3,创建domain实体工程
编写实体类
4,创建api接口工程
1,引入domain依赖
<dependencies>
<!--引入domain工程(的依赖)-->
<dependency>
<groupId>com.pn</groupId>
<artifactId>domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2,创建FeignClient接口,代理order-service服务,并创建降级对象UserOrderHystrix
@FeignClient(value = "order-service",fallback = UserOrderHystrix.class)
public interface UserOrderFeign {
@RequestMapping("/doOrder")
public String doOrder(@RequestBody User user);
}
@Component
public class UserOrderHystrix implements UserOrderFeign {
@Override
public String doOrder(User user) {
return "服务出错了";
}
}
5,创建order-service客户端提供者工程
1,引入api依赖
<dependencies>
<dependency>
<groupId>com.pn</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2,创建controller类
@RestController
public class OrderController {
@RequestMapping("/doOrder")
public String doOrder(@RequestBody User user){
return "苹果"+user;
}
}
6,创建user-service客户端消费者工程
1,引入api依赖
<dependencies>
<dependency>
<groupId>com.pn</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2,创建controller类
@RestController
public class UserController {
//注入代理对象
@Autowired
private UserOrderFeign userOrderFeign;
@RequestMapping("/toOrder")
public String toOrder(){
String data = userOrderFeign.doOrder(new User(1, "admin"));
return "user-service:"+data;
}
}
3,配置文件
spring:
application:
name: user-service
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
#开启hystrix对openfeign的支持
feign:
hystrix:
enabled: true
7,测试运行
正常访问UserController接口显示OrderController返回值
停止order-service服务,访问UserController接口显示重写UserOrderFeign接口的降级方法
8,SpringCloud Gateway
1,SpringCloud Gateway概述
网关是微服务最边缘的服务,直接暴露给用户,用来做用户和微服务的桥梁。
是微服务架构系统的统一入口,实现负载均衡token拦截、权限验证,限流、监控日志等操作。
1,Route(路由)
一个路由由一个id、一个目的url、一组断言、一组过滤器组成;如果路由断言为真,说明请求url和配置的路由匹配。
2,Predicate(断言)
断言就是一些布尔表达式,满足条件的返回true,不满足条件的返回false。
3,Filter(过滤器)
gateway里面的过滤器和Servlet里面的过滤器功能差不多,都有拦截请求,在请求前后进行处理的作用。
4,Nginx和Gateway的区别
网关是介于nignx以及应用服务之间的中间层,主要负责将请求路由到不同的微服务中以及对请求的合法
性进行校验。Gateway是前端服务到后台服务之间的一个对内网关(路由),nginx是用户到前端服务的一个
对外网关(路由)。
2, SpringCloud Gateway使用
1,需求
2, 配置文件路由方式
1,搭建eureka服务端(注册中心)eureka-server
2,搭建eureka客户端(应用服务)user-service
创建控制器
@RestController
public class UserController {
//处理/info请求,并向客户端响应字符串文本
@RequestMapping("/info")
public String info(){
return "这是User-Service";
}
}
3,搭建Gateway网关gateway-80
1,引入依赖
<!--引入的gateway的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2,配置文件
#设置Gateway网关服务名称 -- 为gateway-80
spring.application.name=gateway-80
#设置Gateway网关端口 -- 为80
server.port=80
#将Gateway网关服务注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#---------------------------Gateway的配置--------------------------
#开启网关(默认就是开启的)
spring.cloud.gateway.enabled=true
#配置路由(spring.cloud.gateway.routes属性值是List):
#设置路由id(任意,唯一即可)
spring.cloud.gateway.routes[0].id=user-service-router
#设置路由到的服务的uri(ip:port)
spring.cloud.gateway.routes[0].uri=http://localhost:8081
#设置路由断言(路由到的服务中的请求路径的匹配规则)
spring.cloud.gateway.routes[0].predicates[0]=Path=/toOrder
3,访问localhost/toOrdrer就能路由到user控制器
3,代码路由方式
Gateway网关配置类
@Configuration
public class GatewayConfig {
//配置RouteLocator的Bean对象到IOC容器
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
//创建RouteLocatorBuilder.Builder对象
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//调用RouteLocatorBuilder.Builder对象的route()方法配置路由
routes.route("user-service-router",//路由id
r -> r.path("/info/**")//路由断言(路由到的服务中的请求路径的匹配规则)
.uri("http://localhost:8081")//路由到的服务的uri(ip:port)
).build();
//调用RouteLocatorBuilder.Builder对象的build()方法创建RouteLocator对象并返回
return routes.build();
}
}
3,Gateway动态路由、负载均衡
Gateway根据注册中心的服务列表,以注册中心上微服务名称创建动态uri进行转发,从而实现动态路由的功能。
uri的协议为lb(loadBalance),表示启用Gateway的负载均衡功能。
lb://serviceName是SpringCloud Gateway在微服务中自动为我们创建的负载均衡uri。
1,搭建应用服务user-service的集群
#设置应用(服务)名称 -- 为user-service
spring.application.name=user-service
#设置应用(服务)的端口 -- 8081
server.port=8081
#将当前应用(服务)注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#设置当前应用(服务)部署的主机名 -- 为peer1
eureka.instance.hostname=peer1
#设置应用(服务)注册信息的显示格式 -- 主机名:应用名称:端口
eureka.instance.instance-id=${eureka.instance.hostname}:${spring.application.name}:
${server.port}
2,网关配置文件
#设置Gateway网关服务名称 -- 为gateway-80
spring.application.name=gateway-80
#设置Gateway网关服务端口 -- 为80
server.port=80
#将Gateway网关服务注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#---------------------------Gateway的配置--------------------------
#开启网关(默认就是开启的)
spring.cloud.gateway.enabled=true
#配置路由(spring.cloud.gateway.routes属性值是List):
#设置路由id(任意,唯一即可)
spring.cloud.gateway.routes[0].id=user-service-router
#设置路由到的服务的uri(ip:port) -- lb协议,服务名user-service
spring.cloud.gateway.routes[0].uri=lb://user-service
#设置路由断言(路由到的服务中的请求路径的匹配规则)
spring.cloud.gateway.routes[0].predicates[0]=Path=/info/**
#设置路由到的服务的uri(ip:port) -- lb协议,服务名user-service
spring.cloud.gateway.routes[0].uri=lb://user-service以此实现动态路由但弊端是有多个服务就要求在配置文件中配置多少个路由
3,终极动态路由玩法
修改网关配置文件
#设置Gateway网关服务名称 -- 为gateway-80
spring.application.name=gateway-80
#设置Gateway网关服务端口 -- 为80
server.port=80
#将Gateway网关服务注册到注册中心 -- 值为eureka服务端(注册中心)url地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#---------------------------Gateway的配置--------------------------
#开启网关(默认就是开启的)
spring.cloud.gateway.enabled=true
#开启动态路由
spring.cloud.gateway.discovery.locator.enabled=true
#允许服务名小写
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
客户端请求方式必须是http://网关主机名(ip):网关端口/目标服务名/url接口
localhost:80/user-service/info
4,断言(工厂)
1,断言(工厂)的概述
断言就是一些布尔表达式,满足条件的返回true,不满足条件的返回false。
2,断言(工厂)的使用
增加配置文件内容
#请求路径为服务中的/info/**则路由可用
spring.cloud.gateway.routes[0].predicates[0]=Path=/info/**
#请求方式为post则路由可用
spring.cloud.gateway.routes[0].predicates[1]=Method=GET
#带有请求参数username则路由可用
spring.cloud.gateway.routes[0].predicates[2]=Query=username
5,过滤器
1,过滤器的概述
按生命周期分两种:
pre:在业务逻辑之前执行。
post:在业务逻辑之后执行。
按种类分也是两种:
GatewayFilter:局部过滤器,需要配置给某个路由,才能过滤。
GlobalFilter:全局过滤器,不需要配置路由,系统初始化时会作用到所有路由上。
2, 自定义全局过滤器
@Component
public class TestGlobalFilter implements GlobalFilter, Ordered {
//指定过滤器的执行内容
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/*
请求处理:
*/
//拿到请求对象
ServerHttpRequest request = exchange.getRequest();
//获取请求路径
RequestPath requestPath = request.getPath();
String path = requestPath.value();
System.out.println(path);
//获取请求参数 -- username
MultiValueMap<String, String> params = request.getQueryParams();
String username = params.getFirst("username");
System.out.println(username);
//获取客户端ip
InetSocketAddress remoteAddress = request.getRemoteAddress();
String ip = remoteAddress.getAddress().getHostAddress();
System.out.println(ip);
//如果请求参数username为null或值不为zhangsan
if(username==null||!username.equals("zhangsan")){
/*
响应处理:
*/
//拿到响应对象
ServerHttpResponse response = exchange.getResponse();
//设置响应状态码 -- HttpStatus.UNAUTHORIZED【401认证失败】
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//做出响应
return response.setComplete();
}
//放行
return chain.filter(exchange);
}
//指定过滤器在过滤器链中的执行顺序
@Override
public int getOrder() {
return 1;
}
}
3, Gateway限流
Gateway内置了一个局部过滤器RequestRateLimiterGatewayFilterFactory,其结合Redis并通过令牌桶算法已经实现了限流,我们可以直接使用。
1,创建配置类
@Configuration
public class RequestRateLimiterConfig {
//向IOC容器中添加 限流的键(依据)的解析器的Bean对象
@Bean
public KeyResolver ipKeyResolver(){
//以客户端ip为键(ip限流)
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress()
.getAddress().getHostAddress());
}
}
2,配置文件
#----------------Gateway网关的配置-------------------------
cloud:
gateway:
enabled: true #开启gateway网关
#配置路由
routes:
- id: search-service-router #路由id
uri: lb://search-service #设置路由到的服务的uri--lb协议,服务名search-service
#路由断言
predicates:
- Path=/doSearch #请求路径为服务中的/doSearch则路由可用
#给该路由配置限流过滤器RequestRateLimiterGatewayFilterFactory
filters:
#过滤器的引用名称,必须是RequestRateLimiter
- name: RequestRateLimiter
args:
#限流的键的解析器的引用名称,通过它可以拿到限流的键(依据) -- ip限流
key-resolver: '#{@ipKeyResolver}'
#令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
#令牌桶总容量
redis-rate-limiter.burstCapacity: 3
3,配置类
@Configuration
public class RequestRateLimiterConfig {
/*
//向IOC容器中添加 用于限流的键(依据)的解析器的Bean对象
@Bean
public KeyResolver ipKeyResolver(){
//以客户端ip为键(ip限流)
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress()
.getAddress().getHostAddress());
}*/
//向IOC容器中添加 限流的键(依据)的解析器的Bean对象
@Bean
public KeyResolver urlKeyResolver(){
//以请求url为键(url限流)
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
4,配置文件
#----------------Gateway网关的配置-------------------------
cloud:
gateway:
enabled: true #开启gateway网关
#配置路由
routes:
- id: search-service-router #路由id
uri: lb://search-service #设置路由到的服务的uri--lb协议,服务名search-service
#路由断言
predicates:
- Path=/doSearch #请求路径为服务中的/doSearch则路由可用
#给该路由配置限流过滤器RequestRateLimiterGatewayFilterFactory
filters:
#过滤器的引用名称,必须是RequestRateLimiter
- name: RequestRateLimiter
args:
#限流的键的解析器的引用名称,通过它可以拿到限流的键(依据) -- url限流
key-resolver: '#{@urlKeyResolver}'
#令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
#令牌桶总容量
redis-rate-limiter.burstCapacity: 3
6,跨域配置
因为网关是微服务的边缘,所有的请求都要走网关,跨域的配置只需要写在网关即可:
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
9,SpringCloud Config
1,SpringCloud Config概述
2,SpringCloud Config使用
1,在gitee上创建配置文件远程仓库搭建配置服务器
2,搭建config服务端
1,引入依赖
<!--config服务端的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
2,创建主程序类
//标记为config服务端
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
3,配置文件
#配置文件中心的服务名称
spring.application.name=config-server
#配置文件中心的端口 -- 一般都是8888 8889
server.port=8888
#--------------------和git的关联配置-----------------------------------------------
#gitee上的配置文件远程仓库的url地址
spring.cloud.config.server.git.uri=https://gitee.com/mmym2l/springcloud-config.git
#gitee上的配置文件远程仓库中存放配置文件的目录路径
spring.cloud.config.server.git.search-paths=/config
#登录gitee的用户名
spring.cloud.config.server.git.username=mmym2l
#登录gitee的密码
spring.cloud.config.server.git.password=mmy1122mel
#将gitee上的配置文件远程仓库中的配置文件缓存到本地的位置
spring.cloud.config.server.git.basedir=D:\\local\\config
3,搭建config客户端
1,引入依赖
<!--config客户端的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2, 配置文件bootstrap.properties
#config客户端的服务名称
spring.application.name=config-client
#config客户端的端口
server.port=8080
#---config客户端从配置中心(config服务端)拉取配置文件的配置---
#配置中心(config服务端)的url地址
spring.cloud.config.uri=http://localhost:8888
#配置文件的读取规则是:
#/{label}/{name}-{profile}.properties 或 /{label}/{name}-{profile}.yml
#label是配置文件位于的分支名称,例如master主分支
#name是配置文件名称的前缀,例如config-dev.properties
#profile是配置文件名称的环境,例如config-dev.properties
spring.cloud.config.label=master
spring.cloud.config.name=config
spring.cloud.config.profile=dev
10,SpringCloud Bus
1,SpringCloud Bus概述
把轻量级消息系统与分布式系统的节点连接起来的框架,整合了java的事件处理机制和消息中间件的功能,用于广播状态更改。
SpringCloud Bus配合SpringCloud Config使用可以实现配置的大量动态刷新。
1,进程间怎么通讯?
MQ HTTP TCP/IP协议。
2,线程间怎么通讯?
生产者消费者模型(等待唤醒)。
2,Bus结合RabbitMQ实现消息总线
1,导入服务端依赖
1,Bus和rabbitmq的集成依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
2,actuator的依赖
actuator是spring提供的一个监控模块,监控管理微服务应用;其暴露了很多端点(url),通过这些不同的端点,我们可以全方面监控微服务应用,使用actuator主要用于监控和刷新配置文件。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2,服务端配置文件
#配置文件中心的服务名称
spring.application.name=config-server
#配置文件中心的端口 -- 一般都是8888 8889
server.port=8888
#--------------------和git的关联配置-----------------------------------------------
#gitee上的配置文件远程仓库的url地址
spring.cloud.config.server.git.uri=https://gitee.com/mmym2l/springcloud-config.git
#gitee上的配置文件远程仓库中存放配置文件的目录路径
spring.cloud.config.server.git.search-paths=/config
#登录gitee的用户名
spring.cloud.config.server.git.username=mmym2l
#登录gitee的密码
spring.cloud.config.server.git.password=mmy1122mel
#将gitee上的配置文件远程仓库中的配置文件缓存到本地的位置
spring.cloud.config.server.git.basedir=D:\\local\\config
#----------------------rabbitmq的配置--------------------
#RabbitMQ的服务器ip(linux的ip)
spring.rabbitmq.host=192.168.40.128
#RabbitMQ的服务器端口
spring.rabbitmq.port=5672
#用户名
spring.rabbitmq.username=mmy
#密码
spring.rabbitmq.password=123456
#虚拟主机
spring.rabbitmq.virtual-host=/myhost
#暴露actuator的所有监控端点(url)
management.endpoints.web.exposure.include=*
3,客户端依赖
<!--config客户端的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--Lombok的依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--bus和rabbitmq的集成依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!--actuator的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4,客户端配置文件
#config客户端的服务名称
spring.application.name=config-client
#config客户端的端口
server.port=8080
#---config客户端从配置中心(config服务端)拉取配置文件的配置---
#配置中心(config服务端)的url地址
spring.cloud.config.uri=http://localhost:8888
#配置文件的读取规则是:
#/{label}/{name}-{profile}.properties 或 /{label}/{name}-{profile}.yml
#label是配置文件位于的分支名称,例如master主分支
#name是配置文件名称的前缀,例如config-dev.properties
#profile是配置文件名称的环境,例如config-dev.properties
spring.cloud.config.label=master
spring.cloud.config.name=config
spring.cloud.config.profile=dev
#----------------------rabbitmq的配置--------------------
#RabbitMQ的服务器ip(linux的ip)
spring.rabbitmq.host=192.168.9.128
#RabbitMQ的服务器端口
spring.rabbitmq.port=5672
#用户名
spring.rabbitmq.username=mmy
#密码
spring.rabbitmq.password=123456
#虚拟主机
spring.rabbitmq.virtual-host=/myhost
#暴露actuator的所有监控端点(url)
management.endpoints.web.exposure.include=*
5, 刷新演示
修改云端配置文件
以post方式向config服务端config-server发送刷新请求:
http://localhost:8888/actuator/bus-refresh
3,消息总线的局部刷新
修改云端配置文件
以post方式向config服务端config-server发送刷新请求:
http://localhost:8888/actuator/bus-refresh/config-client-b:8081
4,gitee钩子实现消息总线的全局自动刷新
1,主程序类
/标记为config服务端
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
//配置RestTemplate的bean对象到IOC容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5,配置文件中心的集群
1,eureka服务端(注册中心)eureka-server
2,两个config服务端(配置文件中心)
3,两个config客户端
4,一个网关开启动态路由
spring.cloud.gateway.discovery.locator.enable=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true