一:微服务远程调用方式:
1.概念:
微服务提供者,提供web接口,供微服务消费者调用
2.同步远程调用
1.调用方式一:(RestTemplate)
在JAVA代码中基于RestTemplate发起的http请求实现远程调用,在这里需要注意的是http请求做远程调用是与语言无关的调用,只要知道对方的IP,端口,以及请求参数即可。
实现过程:
(1)注册RestTemplate(在微服务消费者服务中创建RestTemplate配置类,并通过@Bean方式将RestTemplate交给spring管理)具体代码如下:
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
(2)在需要调用微服务接口的类中注入RersTemplate,调用get/postForObject(url,实体类.class)方法,参数一为访问路劲,参数二为需要将远程调用后返回数据封装到某个实体类对象的字节码。具体代码如下:
String url = "http://userservice/user/" + userId;
User user = restTemplate.getForObject(url, User.class);
存在问题:
1.不优雅
2.当发送post请求时需要json格式比较麻烦
2.调用方式二(Feign)
netflix公司的远程调用插件,可以优雅的在java代码中发送http请求,并且集成了Ribbon负载均衡器
实现过程
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在服务消费者的启动类上添加@EnableFeignClients,标明开启Feign远程调用
3.定义feign接口
@FeignClient("userservice")//在该注解的属性值为服务提供者的服务名
public interface UserFeign {
//该接口的方法体为服务提供者提供web接口的方法声明(方法不包括参数和方法体)
@GetMapping("/user/{id}")
public User findUserById(@PathVariable("id") Long id);
}
4.在需要远程调用的类中注入feign接口的代理对象
//实现远程调用,直接调用feign接口中的方法
User user = userFeign.findUserById(order.getUserId());
5.Feign使用的注意点:
(1)Feign接口一定要放在启动类所在包或者子包;否则框架将来不会生成接口的代理对象并且将代理对象放入IOC容器;
(2)如果Feign接口不放在启动类所在包或者子包下面通过一下注解之一解决
@EnableFeignClients("feign接口所在包")
@EnableFeignClients(basePackageClasses = {Feign接口的.class})
6.自定义配置feign
一般用来配置日志级别
配置文件方式实现
局部设置
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
全局设置
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
日志的级别分为四种:
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
-
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
7.feign使用的优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
实现方式
(1)引入依赖
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
(2)配置连接池
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
8.Feign的最佳实现
(1)将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。
(2)在使用该模块的服务中引入该依赖
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。
方式一:
指定Feign应该扫描的包:@EnableFeignClients(basePackages = "cn.itcast.feign.clients"
方式二:
指定需要加载的Client接口:
@EnableFeignClients(clients = {UserClient.class})
3.异步远程调用(RabbitMQ)
1.什么是RabbitMQ
实现过程:RabbitMQ消息中间件,当支付服务进行远程调用其他服务时,会将请求消息发送给RabbitMQ,
其他服务会从消息中间件拉取消息,然后其他服务会根据拉取的消息之心各自的接口功能,
RabbitMQ中的一些角色:
-
publisher:生产者
-
consumer:消费者
-
exchange个:交换机,负责消息路由
-
queue:队列,存储消息
-
virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离
实现过程:消息生产者publisher会将消息发送到交换机exchange,交换机将消息路由的队列queue,队列负责存储消息,消息消费者从队列中获取消息处理消息
2.RabbitMQ有啥作用
故障隔离: 当某个服务提供者出现宕机时,不会影响服务消费者的正常运行,并且宕机的服务能够正常运行后继续从消息中间件拉取消息,继续执行对应的接口功能
解除耦合: 如果消费服务者还需要远程调用不存在的服务提供者接口时,只需要编写服务提供者的web接口,并不会影响服务消费者的java代码
提升性能: 一次远程调用花费的时间只有服务消费者自身程序运行时间和给消息中间件发送消息的时间,一次进程时间短,性能高
吞吐量提升: 无需等待订阅者处理完成,响应更快速
流量削峰:
3.RabbitMQ该怎么做
SpringAMQP是一种应用程序之间传递业务消息遵循的一种规范,,RabbitMQ遵循了AMQP规范,SpringAMQP提供了一套实现RabbitMQ的一套API:
-
自动声明队列、交换机及其绑定关系
-
基于注解的监听器模式,异步接收消息
-
封装了RabbitTemplate工具,用于发送消息
(1)SpringAMQP发送接收消息模型之(basicQueue)简单消息
SpringAMQP发送基本消息实现过程:
(1)<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
(2)配置MQ地址,在consumer服务的application.yml中添加配置:
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 # 密码
(3)然后在publisher服务中,利用RabbitTemplate实现消息发送:
package cn.itcast.mq.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue() {
// 队列名称
String queueName = "simple.queue";
// 消息
String message = "hello, spring amqp!";
// 发送消息
rabbitTemplate.convertAndSend(queueName, message);
}
}
SpringAMQP接收基本消息实现过程:
(1)<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
(2)配置MQ地址,在consumer服务的application.yml中添加配置:
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 # 密码
(3)onsumer服务的cn.itcast.mq.listener
包中新建一个类SpringRabbitListener,代码如下:
package cn.itcast.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component//交给spring管理
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")//表示需要监听哪个消息队列,参数为消息队列名称
public void listenSimpleQueueMessage(String msg) throws InterruptedException {
System.out.println("spring 消费者接收到消息:【" + msg + "】");
}
}
(2)SpringAMQP发送接收消息模型之workQueue工作消息
注意一:当生产者生产消息数量大于消费者处理消息的,那么消息就会在消息队列中堆积,但是消息队列空间大小是有上限的,如果在消息消息队列中的消息堆积过多导致消息队列溢出,就会造成消息丢失,出现业务问题,所以需要多添加几个消费者来共同处理消息队列中的消息,保证消息队列中的消息不会堆积过多.
注意二:通过设置prefetch来控制消费者预取的消息数量:(RabbitMQ默认分配给消费者处理消息的方式是轮循,并没有考虑到每个消费者处理消息的能力,所以导致处理消息慢的消费者预取消息过多,造成消息堆积,而处理消息能力强的消费者过早的处理完消息处于空闲状态不能充足利用消费者处理消息的能力,所以需要配置预取模式)
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
(3)SpringAMQP发布订阅模型(由交换机的种类决定将消息发送到几个消息队列中,并且exchange只负责消息的转发,并不负责消息的存储,queue才负责存储消息)
(1)SpringAMQP发布订阅模型之Fanout广播模型
在广播模式下,消息发送流程是这样的:
-
1) 可以有多个队列
-
2) 每个队列都要绑定到Exchange(交换机)
-
3) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
-
4) 交换机把消息发送给绑定过的所有队列
-
5) 订阅队列的消费者都能拿到消息
在consumer中创建一个类,声明队列和交换机
package cn.itcast.mq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {
/**
* 声明交换机
* @return Fanout类型交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
/**
* 第1个队列
*/
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
/**
* 第2个队列
*/
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
消息发送 :在publisher服务的SpringAmqpTest类中添加测试方法:
@Test
public void testFanoutExchange() {
// 队列名称
String exchangeName = "itcast.fanout";
// 消息
String message = "hello, everyone!";
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
消息接收:在consumer服务的SpringRabbitListener中添加两个方法,作为消费者:
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}
(2)SpringAMQP发布订阅模型之Direct路由模型
在Direct模型下:
-
队列与交换机的绑定,不能是任意绑定了,而是要指定一个
RoutingKey
(路由key) -
消息的发送方在 向 Exchange发送消息时,也必须指定消息的
RoutingKey
。 -
Exchange不再把消息交给每一个绑定的队列,而是根据消息的
Routing Key
进行判断,只有队列的Routingkey
与消息的Routing key
完全一致,才会接收到消息
描述下Direct交换机与Fanout交换机的差异?
-
Fanout交换机将消息路由给每一个与之绑定的队列
-
Direct交换机根据RoutingKey判断路由给哪个队列
-
如果多个队列具有相同的RoutingKey,则与Fanout功能类似
基于@RabbitListener注解声明队列和交换机有哪些常见注解?
-
@Queue
-
@Exchange
(3)SpringAMQP发布订阅模型之Topic话题模型
描述下Direct交换机与Topic交换机的差异?
-
Topic交换机接收的消息RoutingKey必须是多个单词,以
**.**
分割 -
Topic交换机与队列绑定时的bindingKey可以指定通配符
-
#
:代表0个或多个词 -
*
:代表1个词
(4)SpringAMQP消息转换器
Spring会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。 只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
-
数据体积过大
-
有安全漏洞
-
可读性差
JSON方式来做序列化和反序列化:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
4.同步调用和异步调用的优缺点对比
同步调用:
优点:时效性强,可以获取服务提供者相应的结果
缺点:
服务间耦合度高,不利于后期程序维护和功能拓展
程序性能低,每次远程调用的耗费时间是调用每个服务用时总和
同步调用:
优点:
故障隔离: 当某个服务提供者出现宕机时,不会影响服务消费者的正常运行,并且宕机的服务能够正常运行后继续从消息中间件拉取消息,继续执行对应的接口功能
解除耦合: 如果消费服务者还需要远程调用不存在的服务提供者接口时,只需要编写服务提供者的web接口,并不会影响服务消费者的java代码
提升性能: 一次远程调用花费的时间只有服务消费者自身程序运行时间和给消息中间件发送消息的时间,一次进程时间短,性能高
吞吐量提升: 无需等待订阅者处理完成,响应更快速
缺点:
不能获取服务提供者的返回结果
架构复杂了,业务没有明显的流程线,不好管理
需要依赖于Broker(消息中间件)的可靠、安全、性能
二:Eureka注册中心
1.远程调用出现的问题
(1)服务消费者如何获取服务提供者的地址信息
(2)如果有多个服务提供者,消费者该如何选择
(3)消费者如何得知服务提供者的的健康信息
2.Eyreka原理
(1)服务提供者启动时,向Eureka注册自己的地址信息以及服务名
(2)Eureka保存这些信息
(3)消费者根据服务的提供者的服务名发现服务(也称为服务拉取)
(4)Eureka根据负载均衡算法,从服务列表中挑选一个服务
(5)并且服务提供者的每隔一段时间会向EurekaService发送心跳请求,报告健康状态
(6)Eureka会更新记录的服务列表,心跳不正常则会被踢出,保证消费者拉去最新的服务列表
3.搭建EurekaSercixe微服务
(1)创建EurekaService微服务
(2)引入eureka-service依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
(3)编写启动类
添加@EnableEurekaServer注解,开启eureka的注册中心功能
(4)在application.yml文件中配置(具体配置如下)
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
4.服务注册
(1)给微服务中引入eureka-clien依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)在application.yml文件中配置(具体配置如下)
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
5.服务发现
也称服务拉取,是基于服务提供者的服务名获取服务列表,然后根据负载均衡挑选服务
(1)修改通过RestTemplate方式进行远程调用方法中的URL路径
(2)在服务消费者的的配置类RestTemplate中的方法上添加@Loadbalanced注解
三:Ribbon负载均衡
1.负载均衡的流程
当微服务消费者向服务提供者发起请求后,负载均衡先存Eureka-service服务中拉去服务列表并返回到负载均衡内存中,负载均衡通过不同的算法挑选一个某个服务,然后修改URL路径,向服务提供者发起请求
2.负载均衡策略
负载均衡类型
(1)默认的策略:在同一个区域同一个机房,对集群服务器进行轮询访问
(2)根据服务器性能,进行权重分配访问
(3)不存在同一机房同一区域,还有简单轮询
实现方式:
(1)不同的负载均衡策略都实现的是同一个顶层接口IRule,可以通过定义一个配置类IRule,通过@Bean方式将IRule的实现类交给spring管理,这样就进行了全局配置。
@Bean
public IRule randomRule(){
return new RandomRule();
}
(2)配置文件的方式(具体配置如下)
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
3.负载均衡的俩种加载方式
(1)懒加载(默认方式),当第一次访问服务提供者的时候才会从Eureka-service服务中拉去服务列表,并将这些服务列表保存到自己的内存中,这样导致第一次请求时间过长。
(2)饥饿加载,是当Eureka-service服务启动后,自动拉去该服务中的服务列表,并保存咋到改内存中,不管是第一次访问还是后续访问,都会到负载均衡内存中寻找服务列表,这样提高了访问效率。
实现方式:在Eureka-sercice配置文件配置
四:Nacos注册中心
1.概念及实现:
阿里巴巴产品,相对于Eureka内容更丰富,既能做服务注册中心,又能做服务配置中心
实现方式:
(1)在父工程版本控制引入父工程依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
(2)在服务中添加客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(3)注册服务,修改application.yml配置
spring:
cloud:
nacos:
server-addr: localhost:8848
(4)启动测试程序,查看nacos服务管理中是否注册成功
2.服务跨集群调用问题
保证服务尽可能选择本地集群服务,跨集群调用延迟较高,除非本地集群不可用,在访问其他集群
实现方式:
修改application.yml配置,设置集群属性
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
3.根据集群负载均衡
(1)修改服务消费者配置文件,设置集群属性
(2)设置服务消费者负载均衡策略
4.环境隔离namespace
(1)每个namespace都有唯一的ID
(2)不同的namespace下的服务互相不可见
4.Nacos和Eureka注册中心的区别
相同点:都支持服务注册和服务拉取,都支持服务提供者心跳方式健康检测
不同点:
(1)nacos支持服务端主动检测提供者状态,临时实例采用心跳模式,非临时实例采用主动检测模式
(2)临时实例心跳不正常会被剔除,非临时实例不会被剔除
(3)nacos支持服务列表变更的消息推送模式,服务列表更新更及时
(4)nacos集群默认采用AP,当集群中存在临时实例时,采用CP集群,但是Eureka采用的是AP集群
五:Nacos配置中心
在微服务系统中,服务有很多,如果在生产环境中要改变某个服务的配置信息,非常的麻烦,需要在开发环境源码中修改,然后打包部署。但是在nacos配置中心,可以对服务的配置信息进行同意管理,当配置中心配置信息发生改变后,及时通知微服务,实现配置的热更新
1.配置中心原理
(1)将我们在服务中经常发生改变的配置信息放到配置中心的配置文件中,微服务在启动后,连接配置中心,从配置中心中获取这些经常发生变化的配置信息
(2)如果配置中心的配置需要更改,我们可以通过访问nacos配置中心的管理控制台修改配置文件
(3)修改完成后,配置中心会通知微服务,微服务接受到通知后会重新读取配置文中心的配置信息,而不需要重新启动服务,实现配置热更新
2.配置文件
DataID:配置文件的名字,格式为微服务名称-环境.扩展名(目前扩展名仅支持yaml或者properties)
Group:分级存储模型
3.配置读取
服务启动后,会先到bootstrap.yml读取文件(配置中心的配置信息会通过配置将这些配置信息拉取到该文件中),然后在读取application.yml文件
实现流程:
(1)引入nacos配置管理依赖
(2)创建bootstrap.yml文件,在该文件中配置配置中心地址,开发环境以及服务名称,扩展名
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
(3)通过springboot中在配置文件中读取配置信息方式读取配置
通过@value或者@CconfigurationProperties注解读取
4.配置热更新
nacos配置文件发生变化后,微服务无需重启服务,就可以感知到
实现方式
(1)当使用@value该注解读取配置信息时,需要在该类上添加注解@RefershshScope才能实现热更新
(2)使用@ConfigurationProperties该注解不需要添加任何注解就可以实现热更新
5.配置共享
如果在开发,测试,生产环境都需要配置一些共同的配置信息,在每一个环境中都配置一份有些冗余,这时我们可以进行共享配置,在配置中心的DataID中不需要添加环境
多服务共享配置优先级
当前环境配置>nacos中配置>本地配置
6.搭建Nacos集群
六:网关服务(spring cloud gateway)
1.什么是网关服务
介于微服务和前端之间的一个组件,前端请求的统一入口,前端所有的请求,必须首先经过网关,有网关转发指定微服务,并且有了网关之后,前端是不能直接访问为服务的
2.网关服务有什么作用
(1)路由和负载均衡:前端发送请求后,网关会根据前端发送的请求内容,路由到指定的微服务,这样前端就不需要知道微服务的地址,只需要知道网关的地址,这样减少了前后端交互的复杂性,并且避免了微服务地址的暴露,怎敢服务的安全性
(2)过滤器
认证:判断用户是否已经登录
鉴权:判断登录后的用户是否有指定的权限
限流:可以限制前端访问服务器的频率,如果频率过高可以从网关处直接拦截并返回给前端,避免服务压力过大
缓存:
3.网关服务该怎么实现
(1)搭建网关服务
网关本身是一个微服务,所以先创建网关服务module,引入依赖
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
编写启动类
编写基础配置和路由配置
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
4.网关的断言工厂
在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
5.网关过滤器工厂
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
过滤器的作用是什么?
① 对路由的请求或响应做加工处理,比如添加请求头
② 配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
① 对所有路由都生效的过滤器
自定义全局过滤器
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
-
参数中是否有authorization,
-
authorization参数值是否为admin
如果同时满足则放行,否则拦截
实现:
在gateway中定义一个过滤器:
package cn.itcast.gateway.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Order(-1)//对全局过滤器优先级设置,数字越小优先级越高
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
6.网关跨域问题
如果前后端程序都部署到同一台服务器,前端在nginx,后端在tomcat,端口不一样,如果部署在不同服务器,前后端的IP不一样,所以前后端分离开发,前端通关ajax发送请求,但是ajax并不支持跨域请求,解决方式有俩种
1.nginx反向代理
2.通过网关允许ajax跨域发送请求,具体实现如下
在gateway服务的application.yml文件中,添加下面的配置:
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
七:Docker
1.什么是Docker
核心概念
(1)镜像:docker将程序及所有的依赖,函数库,配置等文件打包在一起,就是一个镜像
(2)容器:镜像中的程序运动后形成的进城就是容器,只是docker会给容器做隔离,对外不可用
(3)dockerHub镜像管理平台,
(4)docker属于CS架构:
服务端:Docker守护进程,负责处理Docker指令,管理镜像和容器
客户端:通过命令或RestAP向docker服务发送指令
项目部署问题:
大型项目组件多,运行环境复杂,部署会碰到一些问题,如依赖兼容性问题以及开发,测试,运行环境问题,导致软件版本兼容性问题
Docker解决项目部署问题:
Docker将项目的操作系统函数库,Deps(依赖),配置一起打包形成镜像,并且利用沙箱机制将项目放到一个隔离容器中运行,避免互相干扰。
实现原理:Docker运行不同的操作系统时,直接基于打包的操作系统函数库,有完整的运行环境操作linux内核,不通过各个厂商提供的操作系统操作内核。
Docker和虚拟机的差异:
docker是一个系统进程,虚拟机是在操作系统中的操作系统;
docker体积小,性能好,虚拟机体积大,启动速度慢,性能一般
2.常见的Docker操作命令
1.镜像相关命令
镜像一般由俩部分组成
repository和tag:
repository表示镜像名
tag代表镜像的版本,若果没有指定版本号,默认是latest代表最新版本镜像
docker build 构建镜像
docker push推送镜像到dockerhub服务器
docker pull 镜像名称(repository:tag) 从服务器拉取镜像
docker images查看镜像
docker save -o(压缩包名称) 镜像名称(repository:tag) 保存镜像为一个压缩包
docker load -i 加载压缩包为镜像
docker rmi 镜像名称(repository:tag) 删除镜像
2.容器相关操作命令
docker run创建并运行一个容器
例如docker run --name containerName -p 80:80 -d nginx
--name给容器起一个名字
-p将宿主机端口与容器端口映射,左侧为宿主机端口,右侧为容器端口
-d后台运行容器,不添加则当前界面运行程序
镜像名称(repository:tag)
docker start 启动容器
docker stop 停止容器
docker pause 挂起容器
docker unpause 启动容器
docker exec -it 容器名称 bash 进入容器执行命令
docker exec -it 容器名称 ls 查看容器文件系统目录
docker logs [-f] 查看容器运行日志/或者实时跟踪容器日志
docker ps [-a] 查看所有运行的容器及状态/或者查看所有容器
docker rm [-f] 删除容器/或者强行删除正在运行的容器
3.数据卷基本命令
数据卷作用:将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全
docker volume create 数据卷名称 //创建数据卷
docker volume ls //查看所有数据卷
docker volume inspect 数据卷名称 //查看数据卷详细信息卷
docker volume rm //删除一个或多个指定的volume
docker volume prune //删除未使用的volume
数据卷挂载方式: -v volumeName: /targetContainerPath 如果容器运行时volume不存在,会自动被创建出来
例如:docker run \ --name mn \ -v html:/root/html \ -p 8080:80 nginx \ //挂载数据卷
ocker run :就是创建并运行容器
-- name mn :给容器起个名字叫mn
-v html:/root/htm :把html数据卷挂载到容器内的/root/html这个目录中
-p 8080:80 :把宿主机的8080端口映射到容器内的80端口
nginx :镜像名称
Dockerfile自定义镜像:一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像
Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器
ocker镜像仓库
推送镜像到私有镜像服务必须先tag,步骤如下:
重新tag本地镜像,名称前缀为私有仓库的地址:192.168.150.101:8080/
docker tag nginx:latest 192.168.150.101:8080/nginx:1.0
推送镜像docker push 192.168.150.101:8080/nginx:1.0
拉取镜像docker pull 192.168.150.101:8080/nginx:1.0