SpringCloud
学习地址以及资料
一、认识微服务
1. 服务架构演变
单体架构: 将业务的所有功能集中在一个项目中开,打成一个包部署。
-
优点:
- 架构简单
- 部署成低
-
缺点:
- 耦合度高
分布式架构: 根据业务功能对系统进行拆分,每个业务模块作为独立的项目开发,称为一个服务。
- 优点:
- 降低服务耦合度
- 有利于服务升级拓展
- 服务治理:
分布式架构要考虑的问题:- 服务拆分粒度如何?
- 服务集群地址如何维护?
- 服务之间如何实现远程调用?
- 服务健康状态如何感知?
微服务: 微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都应唯一对应的业务能力,做到单一职责,避免重复业务开发
- 面向服务:微服务对外暴露业务接口
- 自治:团队独立、技术独立、数据独立、部署独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务结构:
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术,国外内最知名的就是SpringCloud和阿里巴巴的Dubbo。
微服务技术对比:
Dubbo | SpringCloud | SpringCloudAlibaba | |
---|---|---|---|
注册中心 | zookeeper、Redis | Eureka、Consul | Nacos、Eureka |
服务远程调用 | Dubbo协议 | Feign(http协议) | Dubbo、Feign |
配置中心 | 无 | SpringCloudConfig | SpringCloudConfig、Nacos |
服务网关 | 无 | SpringCloudGateway、Zuul | SpringCloudGateway、Zuul |
服务监控和保护 | dubbo-admin,功能弱 | Hystrix | Sentinel |
企业需求:
1.SpringCloud + Feign
- 使用SpringCloud技术栈
- 服务接口采用Restful风格
- 服务调用采用Feign方式
2.SpringCloudAlibaba + Feign
- 使用SpringCloudAlibaba技术栈
- 服务接口采用Restful风格
- 服务调用采用Feign方式
3.SpringCloudAlibaba + Dubbo
- 使用SpringCloudAlibaba技术栈
- 服务接口采用Dubbo协议标准
- 服务调用采用Dubbo方式
4.Dubbo原始模式
- 基于Dubbo老旧技术栈体系
- 服务接口采用Dubbo协议标准
- 服务调用采用Dubbo方式
2. SpringCloud
SpringCloud
-
SpringCloud是目前国内使用最广泛的微服务架构,官网地址
SpringCloud -
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
- 服务注册发现:Eureka、Nacos、Consul
- 统一配置管理:SpringCloudConfig、Nacos
- 服务远程调用:OpenFeign、Dubbo
- 统一网关路由:SpringCloudGateway、Zuul
- 服务链路监控:Zipkin、Sleuth
- 流控、降级、保护:Hystix、Sentinel
-
SpringCloud与SpringBoot的兼容关系如下:
Release Train | Boot Version |
---|---|
2021.0.x aka Jubilee | 2.6.x |
2020.0.x aka Ilford | 2.4.x |
Hoxton | 2.2.x,2.3.x(Starting with SR5) |
Grennwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
二、服务拆分及远程调用
1.服务拆分
-
服务拆分注意事项:
- 不同的微服务,不重复开发相同的业务
- 微服务数据独立,不要访问其他微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其他微服务调用
-
导入服务拆分Demo
项目结构
- 总结
- 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同的业务
- 微服务可以将业务暴露为接口,供其他微服务使用
- 不同的微服务应该有自己独立的数据库
2. 服务间调用
需求:根据订单id查询订单的同时,把订单属性的用户信息一起返回
远程调用方式 分析
- 步骤:
- 1)注册RestTemplate
在order-service的OrderApplicaiton中注册RestTemolate
- 1)注册RestTemplate
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 2)服务远程调用RestTemplate
修改order-sercice中的OrderService的qurryOrderById方法:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.利用RestTemplate发起http请求,查询用户
// 2.1 url路径
String url = "http://localhost:8081/user/" + order.getUserId();
// 2.2 发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
}
-
总结:
- 基于RestTemplate发起的http请求实现远程调用
- http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。
-
提供者与消费者
服务提供者:一次业务中,被其他微服务调用的服务。(提供接口给其他微服务)
服务消费者:一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)
提供者与消费者角色其实是相对的
- 一个服务可以同时是服务提供者和服务消费者
-
服务A调用服务B,服务B调用服务C,那么服务C是什么角色?
一个服务即可以是提供者又可以是消费者
三、eureka注册中心
1.服务调用出现的问题和eureka原理
Eureka的作用
- 消费者该如何获取服务提供者具体信息?
- 服务提供者启动时向Eureka注册自己的信息
- Eureka保存这些信息
- 消费者根据服务名称向Eureka拉取提供者信息
- 如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表里挑选一个
- 消费者如何感知服务者提供者监控状态?
- 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
- eureka会更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新信息
- 总结:在Eureka中架构中,微服务分为两类:
- EurekaServer:服务端,注册中心
- 记录服务信息
- 心跳监控
- EurekaClient:客户端
- Provider:服务提供者,例如案例里的user-service
- 注册自己的信息到EurekaServer
- 每隔30秒向EurekaServer发送心跳
- consumer:服务消费者,例如案例的order-sercice
- 根据服务名称从EurekaServer拉取服务列表
- 基于服务列表做负载均衡,选取其中一个微服务后发起远程调用
- Provider:服务提供者,例如案例里的user-service
- EurekaServer:服务端,注册中心
3.搭建EurekaServer
搭建EurekaServer服务步骤如下:
- 创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 编写启动类,添加@EnableEurekaServer注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
- 添加application.yml文件,编写配置
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka地址信息
defaultZone: http://localhost:10086/eureka
4.服务注册
将user-service服务注册到EurekaServer步骤如下:
- 在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在application.yml文件,编写配置:
server:
port: 8081 # 服务端口
spring:
application:
name: userserver # user的服务名称
eureka:
client:
service-url: # eureka地址信息
defaultZone: http://localhost:10086/eureka
我们,可以将user-service多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置:
order-service虽然是消费者,但与user-service一样都是eureka的client端,同意可以实现服务注册:
- 在order-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在application.yml文件,编写配置:
server:
port: 8080 # 服务端口
spring:
application:
name: orderserver # user的服务名称
eureka:
client:
service-url: # eureka地址信息
defaultZone: http://localhost:10086/eureka
总结:
- 服务注册
- 引入eureka-client依赖
- 在application.yml中配置eureka地址
- 无论是消费者还是提供者,引入eureka-client依赖、知道eureka地址后,都可以完成服务注册
5.服务发现
在order-service完成服务拉取
- 修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url = "http://userservice/user/" + order.getUserId();
- 在order-service项目启动类OrderApplication中的RestTemplate添加负载均衡注解:
/**
* 创建RestTemplate并注入Spring容器
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
6.总结
- 搭建EurekaServer
- 引入eureka-server依赖
- 添加@EnableEurekaServer注解
- 在application.yml中配置eureka地址
- 服务注册
- 引入eureka-client依赖
- 在application.yml中配置eureka地址
- 服务发现
- 引入eureka-client依赖
- 在application.yml中配置eureka地址
- 给RestTemplate添加@LoadBalanced注解
- 用服务提供者的服务名称远程调用
四、Ribbon负载均衡原理
1.负载均衡原理
负载均衡流程:
2.负载均衡策略
Ribbon的负载均衡规则是一个叫IRule的接口来定义的,每一个子接口都是一种规则:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高了,配置了AvaikabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可由客户端的< clientName >.< clientConfigNameSpace >.ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑。 |
通过定义IRule实现可以修改负载均衡规则,有两种方式:
- (作用范围:全体)代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
- (只针对某个服务)配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userserver:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
3.懒加载(饥饿加载)
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目中启动时创建,降低第一次访问的耗时,通过下面的配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载
五、nacos注册中心
1.Windows下安装
- GtiHUb的Release下载地址
- 配置端口
3. 启动
执行命令即可
- windows命令
startup.cmd -m standalone # 单机启动
执行成功如图:
2.服务注册到Nacos
- 在Cloud-demo父工程里添加spring-cloud-alibaba的管理依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
- 注释掉order-service和user-service中原有的erueka依赖。
- 添加nacos的客户端依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 修改user-service和order-service中的application.yml文件,注释eureka地址,添加nacos地址:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
4.启动并测试
5. 总结
- Nacos服务搭建
- 下载安装包
- 解压
- 在bin目录下运行指令: startup.cmd -m standalone
- Nacos服务注册发现
- 引入nacos.discovery依赖
- 配置nacos地址spring.cloud.nacos.server-addr
3.服务集群属性
Nacos服务分级存储模型
服务跨集群调用问题
- 修改application.yml,添加如下内:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如杭州:HZ,杭州
- 在Nacos控制台可以看到集群变化:
- 总结
- 1.Nacos服务分级存储模型
- 一级是服务,例如userservice
- 二级是集群,例如杭州或上海
- 三级是实例,例如杭州机房的某台部署了userservice的服务器
- 2.如何设置实例集群属性
- 修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可
4.根据集群负载均衡
- 修改order-service中的application.yml,设置集群为HZ:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 集群名,也就是机房的位置
- 然后在order-service中设置负载均衡的IRule为NacosRule,这个规则会优先寻找自己同集群的服务:
userserver:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
- 注意将user-service的权重都设置为1
- 总结
- NacosRule负载均衡策略
- ①优先选择同集群服务实例列表
- ②本地集群找不到提供者,才去其它集群寻找,并且会报警告
- ③确定了可用实例列表后,再采用随机负载均衡挑选实例
5.根据权重负载均衡
实际部署中会出现的场景:
- 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能较好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
-
在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
-
将权重值设置为0.1,测试可以发现8082被访问到的频率大大降低
-
总结
- 实例的权重控制
- ① Nacos控制台可以设置实例的权重值,0~1之间
- ②同集群内的多个实例,权重越高被访问的频率越高
- ③权重设置为0则完全不会被访问
6.环境隔离
Nacos中服务存储和数据存储的最外层都是一个namespace 东西,用来做最外的隔离
- 在Nacos控制台可以创建namespace,用来隔离不同环境
- 然后填写一个新的命名空间
- 保存后会在控制台看到这个命名空间的id:
- 修改order-service的application.yml,添加namespace:
spring:
application:
name: orderserver # order的服务名称
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如杭州:HZ,杭州
namespace: 82874223-5275-4594-867c-b6166d639249 # 命名空间 填ID
-
重启order-sercice后,再来看控制台
-
此时访问order-service,因为namespace不同,会导致找不到userservice,控制台报错:
-
总结
- namespace用来做环境隔离
- 每个namespace都有唯一的id
- 不同的namespace下的服务不可见
7.临时实例和非临时实例
nacos注册中心细节分析
服务注册到Nacos时,可以选择注册为临时或者非临时实例,通过以下的配置设置:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 是否是临时实例
临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会
总结:Nacos与Eureka
-
Nacos与Eureka的共同点
- 都支持服务注册和服务拉去
- 都支持服务提供者心跳方式做健康检测
-
Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支付服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Erueka采用AP方式
六、Nacos配置管理
1.统一配置管理
在Nacos中添加配置信息:
在弹出的表单中填写配置信息:
配置获取的步骤如下:
- 引入Nacos的配置管理客户端依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 在userservice中的resource目录添加一个
bootstrap.yml
文件,这个文件是个引导文件,优先级高于application.yml
spring:
application:
name: userservice
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml # 文件后缀名
- 在user-service中将pattern.dateformat这个属性注入到UserController中做测试:
@RestController
@RequestMapping("/user")
public class UserController {
//注入nacos中的配置属性
@Value("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器来格式化现在时间并返回
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
注意:由于以上在命名空间namespace,创建了一个dev命名空间,这里要把它注释了,如果不注释,在dev里面创建的yaml文件需要在bootstrap.yml文件的spring-cloud-nacos-config-namespace:配置上命名空间的id
- 总结:将配置交给Nacos管理步
①在Nacos中添加配置文件
②在微服务中引入nacos的config依赖
③在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时取nacos读取哪个文件
2.配置热更新
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要下面两种配置实现:
- 方式一:在@Value注入的变量所在的类添加注解
@RefreshScope
- 方式二:使用
@ConfigurationProperties注解
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternPropertie {
private String dateformat;
}
总结:
Nacos配置更改后,微服务可以实现热更新,方式:
①通过@Value注解注入,结合@RefreshScope来刷新
②通过@ConfigurationProperties注入,自动刷新
注意事项:
- 不是所有配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
3.配置共享
微服务启动时会从nacos读取多个配置文件:
- [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dey.yaml
- [spring.application.name].yaml,例如:userservice.yaml
无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
多种配置的优先级:
总结:微服务会从nacos读取的配置文件:
①[服务名]-[spring.profile.active].ymal,环境配置
②[服务名].yaml,默认配置,多环境共享
优先级:
[服务名]-[环境].yaml > [服务名].yaml > 本地配置
4.搭建Nacos集群
Nacos生产环境下一定要部署为集群状态,部署方式:
1.集群结构图
三个nacos节点的地址:
节点 | ip | port |
---|---|---|
nacos1 | 192.168.150.1 | 8845 |
nacos2 | 192.168.150.1 | 8846 |
nacos3 | 192.168.150.1 | 8847 |
2.搭建集群基本步骤
- 搭建数据库,初始化数据库表结构
- 下载nacos安装包
- 配置nacos
- 启动nacos集群
- nginx反向代理
2.1初始化数据库
Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。
以单点的数据库为例:
首先建立一个数据库,命名为nacos,导入SQL:
2.2配置nacos
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
然后添加内容:
本地ip:8845
本地ip:8846
本地ip:8847
然后修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
2.3启动
将nacos文件夹复制三份,分别命名为:Nacos1、Nacos2、Nacos3
分别修改三个文件的application.properties
Nacos1:
server.port=8845
Nacos2:
server.port=8846
Nacos3:
server.port=8847
然后分别启动三个nacos节点:
startup.cmd
2.4Nginx反向代理
修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
启动nginx
start nginx.exe
2.5 总结
集群搭建步骤:
1. 搭建MySQL集群并初始化数据库表
2. 下载解压nacos
3. 修改集群配置(节点信息)、数据库配置
4. 分别启动多个nacos节点
5. nginx反向代理
七、Http客户端Feign,远程调用
1.Feign替代RestTemplate
RestTemplate方式调用存在的问题
利用RestTemplate发起的远程调用的代码:
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url,User.class);
存在以下的问题:
- 代码可读性差,编程体验不统一
- 参数复杂URL难以维护
Feign的介绍
Feign是一个声明式的http客户端,官方地址:链接
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
定义和使用Feign客户端
使用Feign的步骤如下:
- 引入依赖
<!--feign客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在order-service的启动类添加注解开启Feign的功能:
@EnableFeignClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- 编写Feign客户端:
在这里插入代码片
主要是基于SpringMVC的注解来声明远程调用的信息,比如
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
- Feign的使用步骤
①引入依赖
②添加@EnableFeignClients注解
③编写FeignClient接口
④使用FeignClient中定义的方法代替RestTemplate
2.自定义配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign.Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般需要配置的就是日志级别
配置Feign日志有两种方式:
方式一:配置文件方式
①全局生效:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
②局部生效:
feign:
client:
config:
userservice: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
方式二:Java代码方式,需要先声明一个Bean:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level loglevel(){
return Logger.Level.BASIC;
}
}
①而后如果是全局配置,则把它放到@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
②如果是局部配置,则把它放到@FeignClient这个注解中:
@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)
总结:
- 方式一是配置文件,feign.client.config.xxx.loggerLevel
①如果xxx是default则代表全局
②如果xxx是服务名称,例如userservice则代表某服务 - 方式二是java代码配置Logger.Level这个Bean
①如果在@EnableFeignClients注解表示则代表全局
②如果在@FeignClient注解中声明则代表某服务
3.Feign的性能优化
Feign底层的客户端实现:
- URLConnection:默认实现,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
因此优化Feign的性能主要包括:
①使用连接池代替默认的URLConnection
②日志级别,最好用basic或none
Feign性能优化优化-连接池配置
Feign添加HttpClient的支持:
引入依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对Httpclient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
总结:Feign的优化
- 日志级别尽量用basic
- 使用HttpClient或OKHttp代替URLConnection
①引入feign-httpClient依赖
②配置文件开启httpClient功能,设置连接池参数
4.最佳实践
方式一(继承): 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
- 服务紧耦合
- 父接口参数列表中的映射不会被继承
方式二(抽取): 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
总结:
① 让controller和FeignClient继承同一个接口
②将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用
八、Gateway服务网关
1.为什么需要网关
网关功能:
身份认证和权限校验
(对用户请求做身份验证、权限校验)服务路由、负载均衡
(将用户请求路由到微服务,并实现负载均衡)请求限流
(对用户请求做限流)
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实,具备更好的性能。
2.gateway快速入门
搭建网关服务的步骤:
- 创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<!--nacos服务注册发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 编写路由配置以及nacos地址
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/开头就符合要求
- 总结
网管搭建的步骤:- 创建项目,引入nacos服务发现和gateway依赖
- 配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
1. 路由id:路由的唯一标示
2. 路由路标(uri):路由的目标地址,http代表固定地址,lb代表根据服务吗负载均衡
3. 路由断言(predicates):判断路由的规则
4. 路由过滤器(filters):对请求或响应做处理
3.断言工厂
网关路由可以配置的内容包括:
- 路由id:路由唯一标识符
- uri:路由目的地,支持lb和http两种
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或响应
**路由断言工厂Route Predicate Factory** - 在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
-
例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的 -
像这样的断言工厂在SpringGateway还有十几个
-
Spring提供了11种基本的Predicate工厂:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver],2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate,ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id,\d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name,Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
总结:
- PredicateFactory的作用是什么?
作用是读取用户配置的断言规则,而后把他解析成对应的判断条件,将用户请求进来时做判断 - Path=/user/**是什么含义?
对请求路径做判断,只要路径是user开头就是认为符合
4.过滤器工厂
路由过滤器GatewayFilter
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
:
Spring提供了31种不同的路由过滤器工程:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应头结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除一个响应头 |
RequestRateLimiter | 限制请求的流量 |
... |
案例:给所有进入userservice的请求添加一个请求头
给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 这个是按照路径匹配,只要/user/开头就符合要求
filters:
- AddRequestHeader=Truth,Itcast is freaking awesome! # 添加请求头 # 添加请求头
默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下,格式如下:
spring:
application:
name: gateway # 网关名字
cloud:
nacos:
server-addr: localhost:1111 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 这个是按照路径匹配,只要/user/开头就符合要求
- id: order-service
uri: lb://orderserver
predicates:
- Path=/order/**
- Before=2037-01-20T17:42:47.789-07:00[America/Denver]
default-filters: # 默认过滤器,会对所有的路由请求都生效
- AddRequestHeader=Truth,Itcast is freaking awesome!
总结:
- 过滤器的作用是什么?
①对路由的请求或响应做加工处理,比如添加请求头
②配置在理由下的过滤器只对当前路由的请求生效 - defaultFilters的作用是什么?
①对所有的路由都生效的过滤器
5.全局过滤器GlobalFilter
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口
案例:定义全局过滤器,拦截并判断用户身份
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
自定义类,实现GlobalFilter接口,添加@Order注解:
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter{ //, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
//2.获取参数中的 authorization 参数
String auth = params.getFirst("authorization");
//3.判断参数值是否等于 admin
if ("admin".equals(auth)) {
//4.是放行
return chain.filter(exchange);
}
//5.否拦截
//5.1设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//5.2
return exchange.getResponse().setComplete();
}
/*@Override
public int getOrder() {
return -1;
}*/
}
总结:
- 全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑 - 实现全局过滤器的步骤?
①实现GlobalFilter接口
②添加@Order注解或实现Ordered接口
③编写处理逻辑
6.过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
7.跨域问题
跨域:域名不一致就是跨域,主要包括:
- 域名不同:www.taobao.com和www.taobao.org和www.jd.com和miaosha.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发送跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
网关处理跨域采用CORS的方案,并且只需要简单的配置即可实现:
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期