• 微服务学习笔记


    微服务技术不是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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上述中: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
    • 2
    • 3
    • 4
    1. 编写启动类,添加@EnableEurekaServer注解

      @EnableEurekaServer
      @SpringBootApplication
      public class EurekaApplication {
          public static void main(String[] args) {
              SpringApplication.run(EurekaApplication.class, args);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    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之间沟通
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果这里报错,需要在settings->build->build tools->maven->ignored files 把忽视的勾勾去掉,这样父依赖就可以匹配到这个工程生效

    注册user-service
    将user-service服务注册到EurekaServer步骤如下:
    1.在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖

    
    
        org.springframework.cloud
        spring-cloud-starter-netflix-eureka-client
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.在application.yml文件,编写下面的配置:

    server:
      port: 8081
    spring:
      application:
      	name: userService  #user服务名称
    eureka:
      client:
        service-url:  # eureka的地址信息
          defaultZone: http://127.0.0.1:10086/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    另外,我们可以将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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:

    /**
      * 创建RestTemplate并注入Spring容器
      */
        @Bean
        @LoadBalanced//负载均衡注解,@LoadBalanced注解标注的类发起的请求要被Ribbon拦截和处理
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当访问某订单信息时时,可看见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注解
    	·用服务提供者的服务名称远程调用
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    负载均衡

    负载均衡使用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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:

    userService:
      ribbon:
        NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  # 负载均衡规则
        #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第一种方式是全局配置,所有服务都用该轮询策略,第二种只针对某种服务

    饥饿加载

    Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,加载服务列表,所以耗时请求耗时比较多,之后服务列表被缓存下来,之后再请求速度会比较快。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

    ribbon:
      eager-load:
        enabled: true # 开启饥饿加载
        clients: # 指定饥饿加载的服务名称
          - userService
    
    • 1
    • 2
    • 3
    • 4
    • 5

    小结

    1. Ribbon负载均衡规则
    + 规则接口是IRule
    + 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
    2.负载均衡自定义方式
    + 代码方式:配置灵活,但修改时需要重新打包发布
    + 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
    3.饥饿加载
    + 开启饥饿加载
    + 指定饥饿加载的微服务名称
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Nacos-注册中心

    Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

    在这里插入图片描述

    安装nacos启动然后访问Nacos网址:默认账号都是nacos

    在这里插入图片描述

    在这里插入图片描述

    不管是nacos还是eureka,它们都遵循服务发现,服务注册等接口,使用nacos或是eureka,服务生产者消费者代码都不用做太多变化,只需要改哈依赖,服务地址等即可。

    服务注册到Nacos

    1.在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:

                
                <dependency>
                    <groupId>com.alibaba.cloudgroupId>
                    <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                    <version>2.2.5.RELEASEversion>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.注释掉order-service和user-service中原有的eureka客户端依赖,添加nacos的客户端依赖

            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    SpringCloudAlibaba是SpringCloud之后引入的,没有在SpringCloud中

    3.修改user-service&order-service中的application.yml文件,注释eureka地址,添加nacos地址:

    spring:
      application:
        name: orderService
      cloud:
        nacos:
          server-addr: localhost:8848 # nacos服务地址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:

      userService:
        ribbon:
          NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  # 负载均衡规则
         
      
      • 1
      • 2
      • 3
      • 4

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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方式
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    Nacos-配置管理

    当微服务很多时,假如某一个服务的配置联系着其他服务,当该服务配置发生了修改,其他服务都要跟着修改,然后重启。这样很麻烦,所以现在希望对这些配置进行统一管理,修改配置时不需要逐个修改,而是在一个地方完成改动,并且改动完后不需要重启,这些配置就立马生效,即热更新。Nacos是提供了配置管理服务。

    Nacos演示配置管理

    1.在nacos中添加配置文件

    在这里插入图片描述

    配置内容不是把aplication.yml中的所有配置都挪过来,而是只配置有热更新需求的

    在这里插入图片描述

    在这里插入图片描述

    读取nacos配置文件时需要知道去哪读取,读取谁,所以需要先知道nacos地址。bootstrap.yml优先级高于其他配置,所以nacos地址和与配置文件有关的所有信息都写在这里。

    2.引入Nacos的配置管理客户端依赖:

            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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配置文件名
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试,获取nacos配置成功!

    在这里插入图片描述

    在这里插入图片描述

    Nacos实现配置自动刷新

    Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:
    方式一:在@Value注入的变量所在类上添加注解@RefreshScope

    在这里插入图片描述

    方式二:使用@ConfigurationProperties注解

    @Data
    @Component
    @ConfigurationProperties(prefix = "pattern")//只要前缀名+属性名与配置文件一致则赋值
    public class PatternProperties {//将要获取的属性统一放在该类中
        private String dateformat;
    //    private String envSharedValue;
    //    private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    多环境配置共享

    提供一个共享配置给所有实例共享,这样对统一配置一些属性时只需在该文件上改即可

    微服务启动时会从nacos读取多个配置文件:
    [spring.application.name]-[spring.profiles.active].yaml,例如: userservice-dev.yaml
    [spring.application.name].yaml,例如: userservice.yaml
    无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    在这里插入图片描述

    企业中强调高可用,所以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 > 本地配置
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    http客户端Feign

    Feign介绍

    使用RestTemplate方式发起远程调用,代码可读性差,编程体验不统一;参数复杂URL难以维护
    先来看我们以前利用RestTemplate发起远程调用的代码:

    String url = "http: //userservict/user/" + order.getUserId();
    User user = restTemplate.getFor0bject(url, User.class);
    
    • 1
    • 2

    Feign是一个声明式的http客户端,官方地址: https://github.com/OpenFeign/feign

    其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

    在这里插入图片描述

    Feign使用

    1.引入依赖:

            
    		<dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用浏览器查询订单时,由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
    • 2
    • 3
    • 4
    • 5
    • 6

    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的支持:

    引入依赖:

            
            <dependency>
                <groupId>io.github.openfeigngroupId>
                <artifactId>feign-httpclientartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置连接池:

    feign:
      client:
        config:
          default: #default全局配置
            loggerLevel: BASIC  #日志级别,BASIC就是基本的请求和响应信息
      httpclient:
        enabled: true # 支持HttpClient的开关
        max-connections: 200 # 最大连接数
        max-connections-per-route: 50 # 单个路径的最大连接数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    企业中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的默认配置都定义到一个项目中,供所有消费者使用
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    网关

    网关介绍

    在这里插入图片描述

    为什么需要网关?

    • 身份认证和权限校验
    • 服务路由(将请求路由到对应服务)、负载均衡(服务有多个实例,又负载均衡挑一个使用)
    • 请求限流

    网关的技术实现

    在SpringCloud中网关的实现包括两种:

    • gateway
    • zuul

    Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

    搭建网关服务

    1.创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:

            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-gatewayartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    网关也属于一个微服务,也需要注册到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!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    测试:将请求发送给网关

    在这里插入图片描述

    在这里插入图片描述

    上述过程分析:所有服务都注册到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!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    修改代码获取请求头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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    访问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!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上述过滤器都是通过配置来实现的,配置的仅仅是参数,过滤器的业务逻辑是无法控制的。所以有全局过滤器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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    过滤器执行顺序
    请求进入网关会碰到三类过滤器:当前路由的过滤器、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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    数据结构·双向链表
    李宏毅2021《机器学习/深度学习》——学习笔记(4)
    Python实现多子图绘制系统
    c++实现键盘hook
    嵌入式系统工程师错题总结
    算法之回溯
    APScheduler 环境与基础
    OpenCV实现手势虚拟拖拽
    怎么在Windows10中找回Windows7的照片查看器(Windows 照片查看器)win10新的照片查看器太难用了
    JS前端读取文件夹中文件(基于webpack)
  • 原文地址:https://blog.csdn.net/weixin_46129192/article/details/126414832