SpringCloud微服务架构实用篇

学习地址以及资料

一、认识微服务

在这里插入图片描述

1. 服务架构演变

单体架构: 将业务的所有功能集中在一个项目中开,打成一个包部署。

  • 优点:

    • 架构简单
    • 部署成低
  • 缺点:

    • 耦合度高

分布式架构: 根据业务功能对系统进行拆分,每个业务模块作为独立的项目开发,称为一个服务。

  • 优点:
    • 降低服务耦合度
    • 有利于服务升级拓展
  • 服务治理:
    分布式架构要考虑的问题:
    • 服务拆分粒度如何?
    • 服务集群地址如何维护?
    • 服务之间如何实现远程调用?
    • 服务健康状态如何感知?

微服务: 微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都应唯一对应的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
    在这里插入图片描述

微服务结构:

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术,国外内最知名的就是SpringCloud和阿里巴巴的Dubbo。

微服务技术对比:

DubboSpringCloudSpringCloudAlibaba
注册中心zookeeper、RedisEureka、ConsulNacos、Eureka
服务远程调用Dubbo协议Feign(http协议)Dubbo、Feign
配置中心SpringCloudConfigSpringCloudConfig、Nacos
服务网关SpringCloudGateway、ZuulSpringCloudGateway、Zuul
服务监控和保护dubbo-admin,功能弱HystrixSentinel

企业需求:

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 TrainBoot Version
2021.0.x aka Jubilee 2.6.x
2020.0.x aka Ilford 2.4.x
Hoxton2.2.x,2.3.x(Starting with SR5)
Grennwich2.1.x
Finchley2.0.x
Edgware1.5.x
Dalston1.5.x

二、服务拆分及远程调用

1.服务拆分

  • 服务拆分注意事项:

    1. 不同的微服务,不重复开发相同的业务
    2. 微服务数据独立,不要访问其他微服务的数据库
    3. 微服务可以将自己的业务暴露为接口,供其他微服务调用
  • 导入服务拆分Demo
    项目结构
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 总结
    • 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同的业务
    • 微服务可以将业务暴露为接口,供其他微服务使用
    • 不同的微服务应该有自己独立的数据库

2. 服务间调用

需求:根据订单id查询订单的同时,把订单属性的用户信息一起返回
在这里插入图片描述

远程调用方式 分析
在这里插入图片描述

  • 步骤:
    • 1)注册RestTemplate
      在order-service的OrderApplicaiton中注册RestTemolate
@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拉取服务列表
        • 基于服务列表做负载均衡,选取其中一个微服务后发起远程调用

3.搭建EurekaServer

搭建EurekaServer服务步骤如下:

  1. 创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
  1. 编写启动类,添加@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);
    }
}

  1. 添加application.yml文件,编写配置
server:
  port: 10086 # 服务端口

spring:
  application:
    name: eurekaserver  # eureka的服务名称
eureka:
  client:
    service-url:  # eureka地址信息
      defaultZone: http://localhost:10086/eureka

4.服务注册

将user-service服务注册到EurekaServer步骤如下:

  1. 在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 在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端,同意可以实现服务注册:

  1. 在order-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 在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完成服务拉取

  1. 修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url = "http://userservice/user/" + order.getUserId();
  1. 在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实现可以修改负载均衡规则,有两种方式:

  1. (作用范围:全体)代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
	return new RandomRule();
}
  1. (只针对某个服务)配置文件方式:在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下安装

  1. GtiHUb的Release下载地址
    在这里插入图片描述
  2. 配置端口
    在这里插入图片描述在这里插入图片描述

在这里插入图片描述
3. 启动
在这里插入图片描述
在这里插入图片描述
执行命令即可

  • windows命令
startup.cmd -m standalone			# 单机启动

执行成功如图:

在这里插入图片描述

在这里插入图片描述

2.服务注册到Nacos

  1. 在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>
  1. 注释掉order-service和user-service中原有的erueka依赖。
  2. 添加nacos的客户端依赖:
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
  1. 修改user-service和order-service中的application.yml文件,注释eureka地址,添加nacos地址:
spring:
  cloud:
    nacos:
      server-addr: localhost:8848	# nacos服务地址

4.启动并测试
在这里插入图片描述
5. 总结

  • Nacos服务搭建
    1. 下载安装包
    2. 解压
    3. 在bin目录下运行指令: startup.cmd -m standalone
  • Nacos服务注册发现
    1. 引入nacos.discovery依赖
    2. 配置nacos地址spring.cloud.nacos.server-addr

3.服务集群属性

Nacos服务分级存储模型
在这里插入图片描述

服务跨集群调用问题
在这里插入图片描述

  1. 修改application.yml,添加如下内:
spring:
  cloud:
    nacos:
      server-addr: localhost:8848   # nacos服务地址
      discovery:
        cluster-name: HZ # 配置集群名称,也就是机房位置,例如杭州:HZ,杭州
  • 在Nacos控制台可以看到集群变化:
    在这里插入图片描述
  • 总结
  • 1.Nacos服务分级存储模型
    1. 一级是服务,例如userservice
    2. 二级是集群,例如杭州或上海
    3. 三级是实例,例如杭州机房的某台部署了userservice的服务器
  • 2.如何设置实例集群属性
    • 修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可

4.根据集群负载均衡

  1. 修改order-service中的application.yml,设置集群为HZ:
spring:
   cloud:
    nacos:
      server-addr: localhost:8848	# nacos 服务端地址
      discovery:
        cluster-name: HZ  # 集群名,也就是机房的位置
  1. 然后在order-service中设置负载均衡的IRule为NacosRule,这个规则会优先寻找自己同集群的服务:
userserver:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
  1. 注意将user-service的权重都设置为1
  2. 总结
  • NacosRule负载均衡策略
    • ①优先选择同集群服务实例列表
    • ②本地集群找不到提供者,才去其它集群寻找,并且会报警告
    • ③确定了可用实例列表后,再采用随机负载均衡挑选实例

5.根据权重负载均衡

实际部署中会出现的场景:

  • 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能较好的机器承担更多的用户请求

Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高

  1. 在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
    在这里插入图片描述

  2. 将权重值设置为0.1,测试可以发现8082被访问到的频率大大降低
    在这里插入图片描述

  3. 总结

  • 实例的权重控制
    • ① Nacos控制台可以设置实例的权重值,0~1之间
    • ②同集群内的多个实例,权重越高被访问的频率越高
    • ③权重设置为0则完全不会被访问

6.环境隔离

Nacos中服务存储和数据存储的最外层都是一个namespace 东西,用来做最外的隔离
在这里插入图片描述

  1. 在Nacos控制台可以创建namespace,用来隔离不同环境
    在这里插入图片描述
  2. 然后填写一个新的命名空间
    在这里插入图片描述
  3. 保存后会在控制台看到这个命名空间的id:
    在这里插入图片描述
  4. 修改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
  1. 重启order-sercice后,再来看控制台
    在这里插入图片描述

  2. 此时访问order-service,因为namespace不同,会导致找不到userservice,控制台报错:
    在这里插入图片描述

  3. 总结

    • namespace用来做环境隔离
    • 每个namespace都有唯一的id
    • 不同的namespace下的服务不可见

7.临时实例和非临时实例

nacos注册中心细节分析

服务注册到Nacos时,可以选择注册为临时或者非临时实例,通过以下的配置设置:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false    # 是否是临时实例

临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会

总结:Nacos与Eureka

  • Nacos与Eureka的共同点

    1. 都支持服务注册和服务拉去
    2. 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别

    1. Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
    2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    3. Nacos支付服务列表变更的消息推送模式,服务列表更新更及时
    4. Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Erueka采用AP方式

六、Nacos配置管理

在这里插入图片描述

1.统一配置管理

在Nacos中添加配置信息:
在这里插入图片描述
在弹出的表单中填写配置信息:
在这里插入图片描述
配置获取的步骤如下:

  1. 引入Nacos的配置管理客户端依赖:
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
  1. 在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是个引导文件,优先级高于application.yml
spring:
  application:
    name: userservice
  profiles:
    active: dev # 环境
  cloud:
    nacos:
      server-addr: localhost:8848
      config:
        file-extension: yaml  # 文件后缀名 

  1. 在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

  1. 总结:将配置交给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节点的地址:

节点ipport
nacos1192.168.150.18845
nacos2192.168.150.18846
nacos3192.168.150.18847

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的步骤如下:

  1. 引入依赖
        <!--feign客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 在order-service的启动类添加注解开启Feign的功能:
@EnableFeignClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
  1. 编写Feign客户端:
在这里插入代码片

主要是基于SpringMVC的注解来声明远程调用的信息,比如

  • 服务名称:userservice
  • 请求方式:GET
  • 请求路径:/user/{id}
  • 请求参数:Long id
  • 返回值类型:User
  1. 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)

总结:

  1. 方式一是配置文件,feign.client.config.xxx.loggerLevel
    ①如果xxx是default则代表全局
    ②如果xxx是服务名称,例如userservice则代表某服务
  2. 方式二是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的优化

  1. 日志级别尽量用basic
  2. 使用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快速入门

在这里插入图片描述

搭建网关服务的步骤:

  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>
  1. 编写路由配置以及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/开头就符合要求
  1. 总结
    网管搭建的步骤:
    1. 创建项目,引入nacos服务发现和gateway依赖
    2. 配置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 # 这次跨域检测的有效期
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

As_theWind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值