微服务技术不是SpringCloud技术,微服务是分布式架构的一种,所谓分布式架构就是把服务做拆分,而拆分过程中会产生各种各样问题,而SpringCloud仅仅是解决了服务拆分时的服务治理问题,更复杂的问题并没有给出解决方案。
微服务技术栈介绍
传统单体架构将所有功能写在一起,升级维护困难。分布式架构中,根据功能模块将单体的应用拆分成许多个独立的项目,每个服务完成一部分业务功能,独立部署和维护。各个服务之间形成一个服务集群。一个业务往往需要调用多个服务完成,服务之间调用关系就会越来越复杂,所以需要一个注册中心记录每个服务的ip,端口,功能。当调用服务时,不用记录ip,只需在注册中心中拉取对应服务信息就行。
同时每个服务都有自己的配置文件,如果配置有变动逐一修改就会很麻烦,所以微服务中还有一个配置中心,统一管理集群中所有服务的配置,如果某个服务配置有变动,只需在配置中心修改,他会通知对应服务更改,实现服务配置热更新。
当服务启动后,用户就可以访问服务了,但是访问哪个服务,谁可以访问呢,此时还需要一个服务网关,他会检查用户身份,把用户请求路由到具体服务,当然路由过程中可以做负载均衡。
数据库无法扛住高并发,所以还会有分布式缓存,海量数据的复杂搜索,统计和分析还需要分布式搜索,还有使用异步通信的消息组件提高服务并发,在真个服务集群中出现问题时为了方便排查,还需要分布式日志服务,他可以统计整个集群中所有服务的运行日志,统一做一个存储,统计和分析,链路监控和追踪,他可以实时监控每个服务运行状态,CPU负载,内存的占用等,一旦出现任何问题,可以快速定位到具体的服务和方法。
搭建完后,微服务还需要做一个自动化的部署,通过JkenKins这样的工具,他可以对服务进行自动化的编译,在基于docker打包形成镜像,在基于k8s和Rancher这些技术进行自动化部署,这一部分叫持续集成。
以上微服务技术+持续集成部分才是完整微服务技术栈。
微服务
认识微服务
单体结构: 将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:架构简单、部署成本低 缺点:耦合度高,扩展性差
分布式架构: 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。
优点:降低服务耦合;利于服务升级和扩展; 缺点:架构复杂,难度大,适合大型互联网项目
分布式架构的要考虑的问题:
服务拆分粒度如何? 服务集群地址如何维护? 服务之间如何实现远程调用? 服务健康状态如何感知?
微服务: 微服务是一种经过良好架构设计的分布式架构方案
优点:拆分粒度更小、服务更独立、耦合度更低
缺点:架构非常复杂,运维、监控、部署难度提高
微服务结构
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。
SpringCloudAlibaba兼容Dubbo和SpringCloud,SpringCloud可以用的技术在SpringCloudAlibaba基本上都能用
SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
服务拆分及远程调用
服务拆分注意事项
1.不同微服务,不要重复开发相同业务
2.微服务数据独立,不要访问其它微服务的数据库
3.微服务可以将自己的业务暴露为接口,供其它微服务调用
案列:
有两个服务:
order-service:做订单方面业务
user-service:做用户方面业务
查询出的订单的信息中用户信息也查询出来,所以订单模块需要向用户模块发请求。
要实现微服务调用之间相互调用,可通过RestTemplate发起的http请求实现远程调用
http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。
1)像容器中注册RestTemplate对象
/**
* 创建RestTemplate并注入Spring容器
*/
@Bean
// @LoadBalanced//复杂均衡注解
public RestTemplate restTemplate() {
return new RestTemplate();
}
2)在order-service中发起请求
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.1.url路径
String url = "http://userService/user/" + order.getUserId();
// 2.2.发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
上述中:order-service是消费者,user-service是提供者
提供者与消费者
- 服务提供者:暴露接口给其它微服务调用
- 服务消费者:调用其它微服务提供的接口
- 提供者与消费者角色其实是相对的
- 一个服务可以同时是服务提供者和服务消费者
Eureka注册中心
服务调用出现的问题
服务消费者该如何获取服务提供者的地址信息?如果有多个服务提供者,消费者该如何选择?
消费者如何得知服务提供者的健康状态?
上述问题可通过注册中心解决
eureka作用:
- 消费者该如何获取服务提供者具体信息?
- 服务提供者启动时向eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向eureka拉取提供者信息
- 如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
- 消费者如何感知服务提供者健康状态?
- 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
- eureka会更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
在Eureka架构中,微服务角色有两类:
- EurekaServer:服务端,注册中心
- 记录服务信息
- 心跳监控
- EurekaClient:客户端
- Provider:服务提供者,例如案例中的user-service
- 注册自己的信息到EurekaServer
- 每隔30秒向EurekaServer发送心跳
- Consumer:服务消费者,例如案例中的order-service
- 根据服务名称从EurekaServer拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
- Provider:服务提供者,例如案例中的user-service
搭建Eureka服务中心
搭建EurekaServer
搭建EurekaServer服务步骤如下:
1.创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
-
编写启动类,添加@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://127.0.0.1:10086/eureka #eureka自己也是一个微服务,也会把自己信息注册到eureka上,所以要配置eureak的信息,为了eureka之间沟通
如果这里报错,需要在settings->build->build tools->maven->ignored files 把忽视的勾勾去掉,这样父依赖就可以匹配到这个工程生效
注册user-service
将user-service服务注册到EurekaServer步骤如下:
1.在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.在application.yml文件,编写下面的配置:
server:
port: 8081
spring:
application:
name: userService #user服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
另外,我们可以将user-service多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置:
访问:http://localhost:10086/ 即可访问eureka的管理界面
以下显示了各服务实例信息:
在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
1.修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
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.1.url路径
String url = "http://userService/user/" + order.getUserId();
// 2.2.发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
/**
* 创建RestTemplate并注入Spring容器
*/
@Bean
@LoadBalanced//负载均衡注解,@LoadBalanced注解标注的类发起的请求要被Ribbon拦截和处理
public RestTemplate restTemplate() {
return new RestTemplate();
}
当访问某订单信息时时,可看见user-Service也在执行,说明order-service成功拉去user-service信息并发起调用
小结:
1.搭建EurekaServer
.引入eureka-server依赖
·添加@EnableEurekaserver注解
·在application.yml中配置eureka地址
2.服务注册
.引入eureka-client依赖
·在application.yml中配置eureka地址
3.服务发现
· 引入eureka-client依赖
·在application.yml中配置eureka地址
·给RestTemplate添加@LoadBalanced注解
·用服务提供者的服务名称远程调用
负载均衡
负载均衡使用Ribbon实现
负载均衡原理
发起http://userservice/user/1时,userservice不是一个域名和ip,中间Ribbon会拦截请求,找到eureka中拉服务列表,然后利用负载均衡算法,从服务列表中挑选一个
当请求进入Ribbon后,请求会被LoadBalancerlnterceptor(负载均衡拦截器)拦住,获取拦截到的请求的服务名称,将名称交给RibbonLoadBanlancerClient(负载均衡客户端),RibbonLoadBanlancerClient又会将服务名称交给DynamicServerListLoadBalancer,DynamicServerListLoadBalancer会去eureka中拉去服务列表得到多个服务的信息,通过IRlue做负载均衡,基于特定规则(如轮询)挑选一个实例,把该实例信息返回给RibbonLoadBanlancerClient,将请求中服务名换成真实信息,最后就请求到了。
负载均衡策略
通过定义IRule实现可以修改负载均衡规则,有两种方式:
1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
//将负载均衡轮询规则设置为随机(全局)
@Bean
public IRule randomRule() {
return new RandomRule();
}
2.配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userService:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
第一种方式是全局配置,所有服务都用该轮询策略,第二种只针对某种服务
饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,加载服务列表,所以耗时请求耗时比较多,之后服务列表被缓存下来,之后再请求速度会比较快。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称
- userService
小结
1. Ribbon负载均衡规则
+ 规则接口是IRule
+ 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
2.负载均衡自定义方式
+ 代码方式:配置灵活,但修改时需要重新打包发布
+ 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
3.饥饿加载
+ 开启饥饿加载
+ 指定饥饿加载的微服务名称
Nacos-注册中心
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
安装nacos启动然后访问Nacos网址:默认账号都是nacos
不管是nacos还是eureka,它们都遵循服务发现,服务注册等接口,使用nacos或是eureka,服务生产者消费者代码都不用做太多变化,只需要改哈依赖,服务地址等即可。
服务注册到Nacos
1.在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:
<!--nacos的管理依赖-->
<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>
2.注释掉order-service和user-service中原有的eureka客户端依赖,添加nacos的客户端依赖
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
SpringCloudAlibaba是SpringCloud之后引入的,没有在SpringCloud中
3.修改user-service&order-service中的application.yml文件,注释eureka地址,添加nacos地址:
spring:
application:
name: orderService
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
4.启动并测试
服务跨集群调用问题
为了实现服务高可用,一个服务往往有多个实例,Nacos将同在一个地方的多个实例作为一个集群
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高本地集群不可访问时,再去访问其它集群
模拟跨集群部署:
1.修改order-service中的application.yml,设置集群为HZ:
spring:
application:
name: orderService
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ #集群名称
# namespace: 2c6aac49-4db0-4b4e-923f-c1667409f19c
# ephemeral: false
-
然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
userService: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
3.注意将user-service的权重都设置为1
访问Nacos,可看到设置的两个集群:HZ,SH。order-service在HZ集群,当访问该服务时,order-service优先调用HZ集群中的user-service
Nacos注册中心-加权负载均衡
实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
但是NacosRule是集群优先,然后随机,当用户请求过来后,他不会管哪个性能好。
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
修改实例权重
1.在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
2.将权重设置为0.1,测试可以发现8081被访问到的频率大大降低
上图中8081权重0.1,8082权重1,权重越高,被访问到的几率越大
环境隔离
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
上图中组不是必须的,多个功能相似的服务会在同一个组中
默认会有一个public命名空间,所有服务都在这个空间中
创建命名空间
1.在Nacos控制台可以创建namespace,用来隔离不同环境。然后填写一个新的命名空间信息:
- 保存后会在控制台看到这个命名空间的id:
3.将order-service添加到新建的命名空间。修改order-service的application.yml,添加namespace:
spring:
application:
name: orderService
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ #集群名称
namespace: 2c6aac49-4db0-4b4e-923f-c1667409f19c #命名空间的id
ephemeral: false
4.重启order-service后,再来查看控制台:
5.此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:
Eureka和Nacos区别
eureka和nacos注册服务和拉取服务功能一样,唯一不同就是健康检测,nacos将服务分为临时实例(采用心跳检测)和非临时实例(nacos主动询问),非临时实例挂了不会被踢掉,临时实例挂了会被踢除
nacos发现服务挂了,会推送消息通知
小结
1. Nacos服务分级存储模型
一级是服务,例如userservice
二级是集群,例如杭州或上海
三级是实例,例如杭州机房的某台部署了userservice的服务器
2.如何设置实例的集群属性
修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可
3.NacosRule负载均衡策略
优先选择同集群服务实例列表
本地集群找不到提供者,才去其它集群寻找,并且会报跨集群访问警告
确定了可用实例列表后,再采用随机负载均衡挑选实例
4.实例的权重控制
Nacos控制台可以设置实例的权重值,0~1之间
同集群内的多个实例,权重越高被访问的频率越高
权重设置为0则完全不会被访问
6. Nacos环境隔离
namespace用来做环境隔离
每个namespace都有唯一id③不同namespace下的服务不可见
7.1
Nacos与eureka的共同点
都支持服务注册和服务拉取
都支持服务提供者心跳方式做健康检测
7.2 Nacos与Eureka的区别
Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos支持服务列表变更的消息推送模式,服务列表
更新更及时
Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式(因为有非临时,所以数据可靠性重要);Eureka采用AP方式
Nacos-配置管理
当微服务很多时,假如某一个服务的配置联系着其他服务,当该服务配置发生了修改,其他服务都要跟着修改,然后重启。这样很麻烦,所以现在希望对这些配置进行统一管理,修改配置时不需要逐个修改,而是在一个地方完成改动,并且改动完后不需要重启,这些配置就立马生效,即热更新。Nacos是提供了配置管理服务。
Nacos演示配置管理
1.在nacos中添加配置文件
配置内容不是把aplication.yml中的所有配置都挪过来,而是只配置有热更新需求的
读取nacos配置文件时需要知道去哪读取,读取谁,所以需要先知道nacos地址。bootstrap.yml优先级高于其他配置,所以nacos地址和与配置文件有关的所有信息都写在这里。
2.引入Nacos的配置管理客户端依赖:
<!--nacos的配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
3. 在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件优先级高于application.yml:
spring:
application:
name: userService
profiles:
active: dev # 开发环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
#服务名称+开发环境+文件后缀名即是nacos配置文件名
测试,获取nacos配置成功!
Nacos实现配置自动刷新
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:使用@ConfigurationProperties注解
@Data
@Component
@ConfigurationProperties(prefix = "pattern")//只要前缀名+属性名与配置文件一致则赋值
public class PatternProperties {//将要获取的属性统一放在该类中
private String dateformat;
// private String envSharedValue;
// private String name;
}
多环境配置共享
提供一个共享配置给所有实例共享,这样对统一配置一些属性时只需在该文件上改即可
微服务启动时会从nacos读取多个配置文件:
[spring.application.name]-[spring.profiles.active].yaml,例如: userservice-dev.yaml
[spring.application.name].yaml,例如: userservice.yaml
无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
企业中强调高可用,所以Nacos要做成集群。略
小结
1.将配置交给Nacos管理的步骤
在Nacos中添加配置文件
在微服务中引入nacos的config依赖
在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
2.Nacos配置更改后,微服务可以实现热更新。
方式:通过@Value注解注入,结合@RefreshScope来刷新
通过@configurationProperties注入,自动刷新
注意事项:
·不是所有的配置都适合放到配置中心,维护起来比较麻烦。建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
3.微服务会从nacos读取的配置文件:
[服务名]-[spring.profile.active].yaml,环境配置
[服务名].yaml,默认配置,多环境共享
优先级:[服务名]-[环境].yaml > [服务名].yaml > 本地配置
http客户端Feign
Feign介绍
使用RestTemplate方式发起远程调用,代码可读性差,编程体验不统一;参数复杂URL难以维护
先来看我们以前利用RestTemplate发起远程调用的代码:
String url = "http: //userservict/user/" + order.getUserId();
User user = restTemplate.getFor0bject(url, User.class);
Feign是一个声明式的http客户端,官方地址: https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
Feign使用
1.引入依赖:
<!--feign客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在order-service的启动类添加注解开启Feign的功能:
3.编写Feign客户端:
主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
(上述信息都要和userservice对应的方法匹配,比如请求方式这里用post,user服务那用的时get这样是没法访问到userservice的方法)
测试:
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.用Feign远程调用
User user = userClient.findById(order.getUserId());
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
使用浏览器查询订单时,由Fegin实现远程调用,并做负载均衡(Fegin继承了负载均衡)调用哪个user-service的服务
Feign相关配置
Feign有以下默认配置
日志级别:
- NONE:没有任何日志
- BASIC:记录请求什么时候发,什么时候结束,耗时多久等基本信息
- HEADERS:除了记录基本信息以外,还有请求头,响应头
- FULL:除了记录基本信息,头信息,还有响应体
可通过自定义配置来覆盖默认配置,一般我们需要配置的就是日志级别
配置Feign日志有两种方式:
方式一:配置文件方式
1)全局生效
2)局部生效
方式二:java代码方式,需要先声明一个Bean:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level logLevel(){
return Logger.Level.BASIC;
}
}
1)如果是全局配置,则把它放到@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
2)如果是局部配置,则把它放到@FeignClient这个注解中:
@FeignClient(value = “userservice”, configuration = FeignClientConfiguration.class)
Feign的性能优化
Feign底层的客户端实现:
-
URLConnection:默认实现,不支持连接池
-
Apache HttpClient:支持连接池
-
OKHttp:支持连接池
有了连接池,就可以减少连接的创建和销毁的性能损耗,因为每次创建都要三次握手,断开要四次挥手,比较浪费性能。
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,最好用basic或none
连接池配置:
Feign添加HttpClient的支持:
引入依赖:
<!--引入HttpClient依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池:
feign:
client:
config:
default: #default全局配置
loggerLevel: BASIC #日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 支持HttpClient的开关
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 单个路径的最大连接数
企业中Feign的最佳使用方案
方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
方式二(抽取)︰为了减少每隔服务都要写一个client去访问其他服务,将Feignclient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
缺点:feign-api中封装了很多接口,但是某服务可能只会用到其中的一两个接口,这样全引进所有接口,浪费资源
以上两种方法有各自优缺点,结合具体情况使用
抽取FeignClient演示
实现最佳实践方式二的步骤如下:
1.首先创建一个module,命名为feign-api,然后引入feign的starter依赖
2.将order-service中编写的UserClient、User、DefaultFeignconfiguration都复制到feign-api项目中
3.在order-service中引入feign-api的依赖
4.修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
5.重启测试
因为FeignClient和order-service不在同一个模块,启动类智能扫描自己所在包下的所有类,所以order-service无法扫描到FeignClient的userClient。
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方式一:指定FeignClient所在包。它会把扫描包的所有客户端都扫描加载进来
EnableFeignClients(basePackages = “cn.itcast.feign.clients”)
方式二:指定Feignclient字节码。精准定位,只加载指定的客户端
@EnableFeignClients(clients = {Userclient.class})
小结
1.Feign的使用步骤
引入依赖
添加@EnableFeignClients注解
编写FeignClient接口
使用Feignclient中定义的方法代替RestTemplate
2.Feign的日志配置:
1)方式一是配置文件,feign.client.config.xxx.loggerLevel
如果xxx是default则代表全局
如果xxx是服务名称,例如userservice则代表某服务
2)方式二是java代码配置Logger.Level这个Bean
如果在@EnableFeignClients注解声明则代表全局
如果在@Feignclient注解中声明则代表某服务
3.Feign的优化:
1) 日志级别尽量用basic
2) 使用HttpClient或OKHttp代替URLConnection
引入feign-httpclient依赖
配置文件开启httpclient功能,设置连接池参数
4.Feign的最佳实践:
让controller和FeignClient继承同一接口
将Feignclient、PoJO、Feign的默认配置都定义到一个项目中,供所有消费者使用
网关
网关介绍
为什么需要网关?
- 身份认证和权限校验
- 服务路由(将请求路由到对应服务)、负载均衡(服务有多个实例,又负载均衡挑一个使用)
- 请求限流
网关的技术实现
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
搭建网关服务
1.创建新的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配置中心,所以要引入nacos依赖
2.编写路由配置及nacos地址
server:
port: 10010
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 每个路由规则要有自己的id,自定义必须唯一
uri: lb://userService # 路由的目标地址 lb就是负载均衡,后面跟服务名称。(即要把请求路由到哪,会取出服务名到eureka中拉去对应服务列表)
predicates: # 路由断言,判断请求是否符合路由规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
- id: order-service
uri: lb://orderService
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
测试:将请求发送给网关
上述过程分析:所有服务都注册到nacos上,当发起请求http://127.0.0.1:10010/user/1时,会找到端口对应进程是Gateway,网关不能处理该请求,会把请求基于路由规则(断言工厂)发送给对应服务进行处理
路由断言工厂
- 我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
- 例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的 - 像这样的断言工厂在SpringCloudGateway还有十几个
Spring提供了11种基本的Predicate工厂:
路由过滤器
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
(可以在网关中添加各种过滤器,形成一个过滤器链,请求经过路由、过滤器链才能到达服务)
测试:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes:
- id: user-service # 路由标示,必须唯一
uri: lb://userService # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
修改代码获取请求头Truth的值
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth", required = false) String truth) {
System.out.println("truth: " + truth);
return userService.queryById(id);
}
访问user服务:
控制台打印出信息:
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes:
- id: user-service # 路由标示,必须唯一
uri: lb://userService # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
上述过滤器都是通过配置来实现的,配置的仅仅是参数,过滤器的业务逻辑是无法控制的。所以有全局过滤器GlobalFilter。
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。
自定义过滤器:自定义类,实现GlobalFilter接口,添加@Order注解:
// @Order(-1)//就比如以后有很多过滤器,值越小越先执行,相当于有个顺序,只用注解或者实现Ordered接口都行
@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;
}
}
过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
-
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
-
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照defaultFilter>路由过滤器>GlobalFilter的顺序执行。可以参考下面几个类的源码来查看:
跨域问题处理
跨域:域名不一致就是跨域,主要包括:
域名不同: www.taobao.com和www.taobao.org和www.jd.com和 miaosha.jd.com域名相同,端口不同: localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:
add-to-simple-url-handler-mapping: true 是网关中特有的,ajax采用的是CORS,CORS是浏览器去问服务器让不让该请求访问,默认该询问会被拦截,设置add-to-simple-url-handler-mapping为true也就是不拦截options请求。
CORS每次访问都要询问以下,有性能损耗,为了减少这个损耗,可以给跨域请求设置一个有效期,有效期范围内浏览器不会发起询问,直接放行。
小结
1.网关搭建步骤:
1)创建项目,引入nacos服务发现和gateway依赖
2)配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
1)路由id:路由的唯一标示
2)路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
3)路由断言( predicates) :判断路由的规则,
4)路由过滤器filters,处理请求或响应
2.
PredicateFactory的作用是什么? 读取用户定义的断言条件,对请求做出判断
Path=/user/**是什么含义? 路径是以/user开头的就认为是符合的
3.过滤器的作用是什么?
对路由的请求或响应做加工处理,比如添加请求头配置在路由下的过滤器只对当前路由的请求生效defaultFilters的作用是什么? 对所有路由都生效的过滤器
3.
全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤?
实现GlobalFilter接口
添加@Order注解或实现Ordered接口
编写处理逻辑
4.路由过滤器、defaultFilter、全局过滤器的执行顺序?
order值越小,优先级越高
当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器
5.CORS跨域要配置的参数包括哪几个?
·允许哪些域名跨域
·允许哪些请求头
允许哪些请求方式
·是否允许使用cookie
·有效期是多久