微服务学习笔记

微服务技术不是SpringCloud技术,微服务是分布式架构的一种,所谓分布式架构就是把服务做拆分,而拆分过程中会产生各种各样问题,而SpringCloud仅仅是解决了服务拆分时的服务治理问题,更复杂的问题并没有给出解决方案。

微服务技术栈介绍

在这里插入图片描述
在这里插入图片描述

传统单体架构将所有功能写在一起,升级维护困难。分布式架构中,根据功能模块将单体的应用拆分成许多个独立的项目,每个服务完成一部分业务功能,独立部署和维护。各个服务之间形成一个服务集群。一个业务往往需要调用多个服务完成,服务之间调用关系就会越来越复杂,所以需要一个注册中心记录每个服务的ip,端口,功能。当调用服务时,不用记录ip,只需在注册中心中拉取对应服务信息就行。

同时每个服务都有自己的配置文件,如果配置有变动逐一修改就会很麻烦,所以微服务中还有一个配置中心,统一管理集群中所有服务的配置,如果某个服务配置有变动,只需在配置中心修改,他会通知对应服务更改,实现服务配置热更新。

当服务启动后,用户就可以访问服务了,但是访问哪个服务,谁可以访问呢,此时还需要一个服务网关,他会检查用户身份,把用户请求路由到具体服务,当然路由过程中可以做负载均衡。

数据库无法扛住高并发,所以还会有分布式缓存,海量数据的复杂搜索,统计和分析还需要分布式搜索,还有使用异步通信的消息组件提高服务并发,在真个服务集群中出现问题时为了方便排查,还需要分布式日志服务,他可以统计整个集群中所有服务的运行日志,统一做一个存储,统计和分析,链路监控和追踪,他可以实时监控每个服务运行状态,CPU负载,内存的占用等,一旦出现任何问题,可以快速定位到具体的服务和方法。

搭建完后,微服务还需要做一个自动化的部署,通过JkenKins这样的工具,他可以对服务进行自动化的编译,在基于docker打包形成镜像,在基于k8sRancher这些技术进行自动化部署,这一部分叫持续集成。

以上微服务技术+持续集成部分才是完整微服务技术栈

在这里插入图片描述

微服务

认识微服务

单体结构: 将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:架构简单、部署成本低 缺点:耦合度高,扩展性差

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

优点:降低服务耦合;利于服务升级和扩展; 缺点:架构复杂,难度大,适合大型互联网项目

分布式架构的要考虑的问题:
服务拆分粒度如何? 服务集群地址如何维护? 服务之间如何实现远程调用? 服务健康状态如何感知?

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

优点:拆分粒度更小、服务更独立、耦合度更低

缺点:架构非常复杂,运维、监控、部署难度提高

微服务结构

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是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拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

搭建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>
  1. 编写启动类,添加@EnableEurekaServer注解

    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class, args);
        }
    }
    
  2. 添加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
  1. 然后在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,用来隔离不同环境。然后填写一个新的命名空间信息:

在这里插入图片描述

  1. 保存后会在控制台看到这个命名空间的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
·有效期是多久

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值