Spring Cloud

  • Spring Cloud
    • 一、SpringCloud简介
        • 1.1 SpringCloud介绍
        • 1.2 spring cloud 包含的众多子项目
    • 二、微服务架构
        • 2.1 微服务架构简介
        • 2.2 微服务架构与SOA架构的区别
        • 2.3 微服务架构如何拆分
        • 2.4 微服务的优缺点
        • 2.5 微服务 Spring Boot Spring Cloud 三者之间的关系
    • 三、Spring 的RestTemplate
        • 3.1 RestTemplate 介绍
        • 3.2 RestTemplate 入门案例
    • 四、模拟微服务业务场景
        • 4.1创建父工程
        • 4.2 搭建服务提供者
        • 4.3 搭建服务消费者
        • 4.4 服务消费者使用RestTemplate调用服务提供者
        • 4.5 问题点
    • 五、注册中心 Spring Cloud Eureka
        • 5.1 Eureka 简介
        • 5.2 为什么 要用注册中心?
        • 5.3 搭建 Eureka Server 环境,创建一个 eureka_server 工程。
        • 5.4 服务提供者-注册服务
        • 5.5 服务消费者-注册服务中心
        • 5.6 消费者通过Eureka访问提供者
        • 5.7 Eureka配置:
    • 六、 负载均衡 Spring Cloud Ribbon
        • 6.1 Ribbon 简介
        • 6.2 客户端与服务端级别的负载均衡
        • 6.3 入门案例
        • 6.4 客户端开启负载均衡
        • 6.5 其他负载均衡策略配置
        • 6.5 负载均衡源码跟踪探究
    • 七、 熔断器 Spring Cloud Hystrix
        • 7.1 Hystrix 简介
        • 7.2 雪崩效应
        • 7.3 熔断器状态
        • 7.4 局部熔断/服务降级案例
        • 7.5 其他熔断策略配置
        • 7.6 扩展-全局服务降级的fallback方法
    • 八、Spring Cloud Feign
        • 8.1 Feign简介
        • 8.2 快速入门
        • 8.3 负载均衡
        • 8.4 开启Hystrix
        • 8.5 请求压缩
        • 8.6 Feign的日志级别配置
    • 九、网关 Spring Cloud Gateway
        • 9.1 Gateway 简介
        • 9.2 快速入门
        • 9.3 动态路由
        • 9.4 过滤器
    • 关于idea 无法识别对应的配置文件的解决方案

Spring Cloud

一、SpringCloud简介

1.1 SpringCloud介绍

 Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
 它将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装、屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

1.2 spring cloud 包含的众多子项目

   - Netflix Eureka ——服务发现
   - Netflix Ribbon——客服端负载均衡
   - Netflix Hystrix——断路器
   - Netflix Zuul————服务网关
   - Spring Cloud Config——分布式配置

二、微服务架构

2.1 微服务架构简介

  ①微服务架构是从SOA架构中演变过来的,比SOA架构上的粒度更加精细。让专业的人做专业的事情,目的就是为了提高效率。每个服务之间互相不受影响,每个服务必须独立部署(独立数据库、独立Redis等),微服务架构更加轻量级,采用restful风格提供的API,也就是使用HTTP协议+json格式进行传输,更加轻巧,更加适用于互联网公司敏捷开发、快速迭代产品。
  ② 拆分、单一、独立、组件化。将一个复杂的庞大的项目拆分成一个一个的小项目独立的运行,通过接口的方式组装成一个大项目。

2.2 微服务架构与SOA架构的区别

(1)、微服务架构基于SOA架构演变过来,继承SOA架构的优点,在微服务架构中去除SOA架构中的ESB消息总线,采用http+json(restful)进行传输。

(2)、微服务架构对比SOA架构粒度会更加精细,让专业的人干专业的事情,目的是为了提高效率,每个服务于服务之间互不影响。在微服务架构中,每个服务必须独立部署,微服务架构更加轻巧、轻量。

(3)、SOA架构中可能数据库存储会发生共享,微服务强调每个服务都有独立数据库,保证每个服务于服务之间互不影响。

(4)、项目体现特征,微服务架构比SOA架构更加适合于互联网公司的敏捷开发、快速迭代版本。

2.3 微服务架构如何拆分

  1. 微服务把每一个职责,单一功能存放在独立服务器中
  2. 每个服务运行在单独进程中,能够单独启动或销毁。
  3. 每个服务有自己独立的数据库存储,实际上有自己独立的缓存、数据库、消息队列等资源。

2.4 微服务的优缺点

优点

1.集大成者,Spring Cloud 包含了微服务架构的方方面面。

2.约定优于配置,基于注解,没有配置文件。

3.轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。

4.开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。

5.开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。

缺点:

1.项目结构复杂,每一个组件或者每一个服务都需要创建一个项目。

2.部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署,而要想深入了解 Docker,学习成本高。

2.5 微服务 Spring Boot Spring Cloud 三者之间的关系

1.微服务是一种架构的理念,提出了微服务的设计原则,从理论为具体的技术落地提供了指导思想。

2.Spring Boot 是一套快速配置脚手架,可以基于 Spring Boot 快速开发单个微服务。

3.Spring Cloud 是一个基于 Spring Boot 实现的服务治理工具包;

4.Spring Boot 专注于快速、方便集成的单个微服务个体;

5.Spring Cloud 关注全局的服务治理框架。

三、Spring 的RestTemplate

3.1 RestTemplate 介绍

  • RestTemplate是Rest的HTTP客户端模板工具类
  • 对基于Http的客户端进行封装
  • 实现对象与JSON的序列化与反序列化(JSON<——->JavaBean)
  • 不限定客户端类型,目前常用的3种客户端都支持:HttpClient、OKHttp、JDK原生URLConnection(默认方式)

3.2 RestTemplate 入门案例

① RestTemplate

	 spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。


② 搭建springcloud-day01-provider

  • 使用IDEA 新建一个Maven工程
  • 新建完成后,添加pom.xml依赖
<!--父工程-->
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.2.2.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
   <!--web起步依赖-->
   <dependency>
   	<groupId>org.springframework.boot</groupId>
   	<artifactId>spring-boot-starter-web</artifactId>
   </dependency>
</dependencies>
  • 创建User 类
package com.example.domain;

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private String address;
    private Integer age;

    public User() {
    }

    public User(String name, String address, Integer age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }
    
    //..get set toString 自行添加
    
}
  • 添加配置文件
server:
  port: 18081
  • 创建UserController
package com.example.controller;

import com.example.domain.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;


@RestController
@RequestMapping(value = "/user")
public class UserController {

  /***
   * 提供服务
   */
  @RequestMapping(value = "/list")
  public List<User> list(){
      List<User> users = new ArrayList<User>();
      users.add(new User("王五", "深圳", 25));
      users.add(new User("李四", "北京", 23));
      users.add(new User("赵六", "上海", 26));
      return users;
  }
}
  • 创建启动类,并启动工程
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* SpringcloudDay1ProviderApplication
*/
@SpringBootApplication
public class SpringcloudDay1ProviderApplication {
  public static void main(String[] args) {
      SpringApplication.run(SpringcloudDay1ProviderApplication.class, args);
  }
}
  • 访问链接:http://localhost:18081/user/list 结果如下图

    ③ 创建springcloud-day1-resttemplate
  • pom.xml依赖
<!--父工程-->
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.6.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
  <!--web起步依赖-->
  <dependency>
  	<groupId>org.springframework.boot</groupId>
  	<artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!--测试包-->
  <dependency>
  	<groupId>org.springframework.boot</groupId>
  	<artifactId>spring-boot-starter-test</artifactId>
  	<scope>test</scope>
  </dependency>
</dependencies>
  • 创建启动类,并在启动类中创建RestTemplate对象
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * SpringcloudDay1ResttemplateApplication
 *  */
@SpringBootApplication
public class SpringcloudDay1ResttemplateApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudDay1ResttemplateApplication.class,args);
    }
    /***
     * @Bean:创建一个对象实例,并将对象交给Spring容器管理
     * <bean class="restTemplate" class="org.springframework.web.client.RestTemplate" />
     * @return
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

*创建测试类 ,在测试类HttpDemoApplicationTests中@Autowired注入RestTemplate

package com.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;

/**
 * SpringcloudDay1ResttemplateApplicationTests
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringcloudDay1ResttemplateApplicationTests {
    @Autowired
    private RestTemplate restTemplate;

    @Test
    public void testRestTemplateQuery(){
        String url = "http://localhost:18081/user/list";
        String result = restTemplate.getForObject(url,String.class);
        System.out.println(result);
    }
}

  • 运行结果打印在控制台 结果如下:

四、模拟微服务业务场景

4.1创建父工程

(1)新建一个Maven父工程springcloud-parent
(2)引入依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties> 

<!--父工程-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>


<!--SpringCloud包依赖管理-->
    <dependencyManagement>
        <dependencies>
            <!--spring cloud Hoxton.SR1-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

(3) 该服务工程创建数据库 创建数据库springcloud

-- 使用springcloud数据库
USE springcloud;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
CREATE TABLE `tb_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL COMMENT '用户名',
  `password` varchar(100) DEFAULT NULL COMMENT '密码',
  `name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `sex` int(11) DEFAULT NULL COMMENT '性别,1男,2女',
  `birthday` date DEFAULT NULL COMMENT '出生日期',
  `created` date DEFAULT NULL COMMENT '创建时间',
  `updated` date DEFAULT NULL COMMENT '更新时间',
  `note` varchar(1000) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '张三');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '李四');

4.2 搭建服务提供者

(1)选中 springcloud-parent 工程-> New Modul -> Maven ->输入坐标名字

(2)引入pom.xml依赖

<!--依赖包-->
    <dependencies>
        <!--JPA包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--web起步包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--MySQL驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>          
        </dependency>
    </dependencies>

(3)User对象创建

package com.example.domain;

import javax.persistence.*;
import java.util.Date;

/**
 * User
 */
@Entity
@Table(name = "tb_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;//主键id
    private String username;//用户名
    private String password;//密码
    private String name;//姓名
    private Integer age;//年龄
    private Integer sex;//性别 1男性,2女性
    private Date birthday; //出生日期
    private Date created; //创建时间
    private Date updated; //更新时间
    private String note;//备注
    
    //..set get toString 略
    
}

(4)dao 层

package com.example.dao;

import com.example.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * UserDao
 */
public interface UserDao extends JpaRepository<User,Integer> {
}

(5)Service层

package com.example.service;

import com.example.domain.User;

/**
 * UserService
 */
public interface UserService {
    /***
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    User findByUserId(Integer id);
}

service 接口实现类

package com.example.service.impl;

import com.example.dao.UserDao;
import com.example.domain.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * UserServiceImpl
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    /***
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    public User findByUserId(Integer id) {
        return userDao.findById(id).get();
    }
}

(6)控制层

package com.example.controller;

import com.example.domain.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * UserController
 */
@RestController
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private UserService userService;

    /***
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/find/{id}")
    public User findById(@PathVariable(value = "id") Integer id){
        return userService.findByUserId(id);
    }
}

(7) application.yml 配置 链接数据库

server:
  port: 18081
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: ****   #填写自己数据库的密码
    url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC

(8)启动类创建

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * UserProviderApplication
 */
@SpringBootApplication
public class UserProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserProviderApplication.class,args);
    }
}

4.3 搭建服务消费者

(1)选中springcloud-parent工程->New Modul->Maven->输入坐标名字
(2)pom.xml依赖如下:

 <!--依赖包-->
<dependencies>
    <!--web起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

(3)创建User对象

package com.example.domain;

import java.io.Serializable;
import java.util.Date;

/**
 * User
 */
public class User implements Serializable {
    private Integer id;//主键id
    private String username;//用户名
    private String password;//密码
    private String name;//姓名
    private Integer age;//年龄
    private Integer sex;//性别 1男性,2女性
    private Date birthday; //出生日期
    private Date created; //创建时间
    private Date updated; //更新时间
    private String note;//备注
    
    //..set、get、toString 自行添加
    
}
(4)创建启动引导类
```java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * UserConsumerApplication
 */
@SpringBootApplication
public class UserConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerApplication.class,args);
    }

    /***
     * 将RestTemplate的实例放到Spring容器中
     * @return
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

(5)创建 application.yml ,并配置端口为18082

server:
  port: 18082

4.4 服务消费者使用RestTemplate调用服务提供者

(1)创建控制层,在控制层中调用user-provider

package com.example.controller;

import com.example.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * UserController
 */
@RestController
@RequestMapping(value = "/consumer")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    /****
     * 在user-consumer服务中通过RestTemplate调用user-provider服务
     * @param id
     * @return
     */
    @GetMapping(value = "/{id}")
    public User queryById(@PathVariable(value = "id")Integer id){
        String url = "http://localhost:18081/user/find/"+id;
        return restTemplate.getForObject(url,User.class);
    }

}

(2)启动测试:请求地址:http://localhost:18082/consumer/1

4.5 问题点

  1. 服务消费者使用RestTemplate调用服务提供者,使用RestTemplate调用的时候,需要先创建并注入到SpringIOC容器中
  2. 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
  3. 在服务消费者中,不清楚服务提供者的状态(user-provider有可能没有宕机了)
  4. 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
  5. 服务提供者的如果出现故障,不能及时发现。

五、注册中心 Spring Cloud Eureka

5.1 Eureka 简介

   在前后端分离架构中,服务层被拆分成了很多的微服务,微服务的信息如何管理?Spring Cloud中提供服务注册中心来管理微服务信息。

5.2 为什么 要用注册中心?

(1).微服务数量众多,要进行远程调用就需要知道服务端的ip地址和端口,注册中心帮助我们管理这些服务的ip和端口。
(2).微服务会实时上报自己的状态,注册中心统一管理这些微服务的状态,将存在问题的服务踢出服务列表,客户端获取到可用的服务进行调用。
(3).Eureka解决了问题:服务的管理,注册和发现、状态监管、动态路由。
(4)Eureka负责管理记录服务提供者的信息。服务调用者无需自己寻找服务,Eureka自动匹配服务给调用者。
(5).reka与服务之间通过心跳机制进行监控;
基本架构图

Eureka:Eureka服务端应用,提供服务注册发现功能,eureka-server

服务提供者:提供服务的应用,要求统一对外提供Rest风格服务即可

服务消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新

心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态

1、Eureka Server是服务端,负责管理各各微服务结点的信息和状态。

2、在微服务上部署Eureka Client程序,远程访问Eureka Server将自己注册在Eureka Server。

3、微服务需要调用另一个微服务时从Eureka Server中获取服务调用地址,进行远程调用。
Eureka工作流程
1、Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息

2、Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务

3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常

4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例

5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端

6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式

7、Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地

8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存

9、Eureka Client 获取到目标服务器信息,发起服务调用

10、Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

5.3 搭建 Eureka Server 环境,创建一个 eureka_server 工程。

(1)选中 springcloud-parent 工程->New Modul->Maven->输入坐标名字,创建项目 eureka-server ,表示注册中心


(2)pom.xml引入依赖

 <!--依赖包-->
<dependencies>
    <!--eureka-server依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

(3) application.yml 配置

server:
  port: 7001    #端口号
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
  client:
    register-with-eureka: false   #是否将自己注册到Eureka中
    fetch-registry: false   #是否从eureka中获取服务信息
    service-url:
      defaultZone: http://localhost:7001/eureka # EurekaServer的地址

(4)启动类创建
在src下创建com.example.EurekaServerApplication,在类上需要添加一个注解@EnableEurekaServer,用于开启Eureka服务,代码如下:

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
* EurekaServerApplication
*/
@SpringBootApplication
@EnableEurekaServer    //开启Eureka服务
public class EurekaServerApplication {

 public static void main(String[] args) {
     SpringApplication.run(EurekaServerApplication.class,args);
 }
}

(5)启动访问
启动后,访问http://127.0.0.1:7001/,效果如下:

5.4 服务提供者-注册服务

(1)引入依赖
在 user-provider 的 pom.xml 中引入如下依赖

<!--eureka客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)配置Eureka服务地址
修改 user-provider 的 application.yml 配置文件,添加Eureka服务地址,完整代码如下

erver:
  port: 18081
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  application:
    name: user-provider #服务的名字,不同的应用,名字不同,如果是集群,名字需要相同
#指定eureka服务地址
eureka:
  client:
    service-url:
      # EurekaServer的地址
      defaultZone: http://localhost:7001/eureka

(3)开启Eureka客户端发现功能
在 user-provider 的启动类com.example.UserProviderApplication上添加 @EnableEurekaClient 或者 @EnableDiscoveryClient,用于开启客户端发现功能。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
* UserProviderApplication
*/
@SpringBootApplication
@EnableDiscoveryClient     //开启Eureka客户端发现功能,注册中心只能是Eureka
public class UserProviderApplication {

  public static void main(String[] args) {
      SpringApplication.run(UserProviderApplication.class,args);
  }
}

温馨提示

@EnableDiscoveryClient和@EnableEurekaClient都用于开启客户端的发现功能
@EnableEurekaClient的注册中心只能是Eureka。

(4)启动测试
  启动eureka-server,再启动user-provider。
访问Eureka地址http://127.0.0.1:7001/,效果如下:

5.5 服务消费者-注册服务中心

(1)pom.xml引入依赖,修改 user-consumer 的 pom.xml

<!--eureka客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)application.yml中配置eureka服务地址,修改user-consumer工程的application.yml配置,添加eureka服务地址,

server:
  port: 18082
spring:
  application:
    name: user-consumer   #服务名字
#指定eureka服务地址
eureka:
  client:
    service-url:
      # EurekaServer的地址
      defaultZone: http://localhost:7001/eureka

(3)在启动类上开启Eureka服务发现功能,修改 user-consumer 的com.example.UserConsumerApplication启动类,在类上添加@EnableDiscoveryClient注解,

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * UserConsumerApplication
 */
@SpringBootApplication
@EnableDiscoveryClient  //开启Eureka客户端发现功能
public class UserConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerApplication.class,args);
    }

    /***
     * 将RestTemplate的实例放到Spring容器中
     * @return
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

(4)测试
启动user-consumer,然后访问Eureka服务地址http://127.0.0.1:7001/

5.6 消费者通过Eureka访问提供者

(1)让消费者从Eureka那里获取服务提供者的访问地址,然后访问服务提供者,修改 user-consumer 的UserController.

package com.example.controller;
import com.example.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * UserController
 */
@RestController
@RequestMapping("/consumer")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    /****
     * 在user-consumer服务中通过RestTemplate调用user-provider服务
     */
    @GetMapping("/{id}")
    public User queryById(@PathVariable(value = "id")Integer id){
        List<ServiceInstance> instances = discoveryClient.getInstances("user-provider");
        ServiceInstance serviceInstance = instances.get(0);
        String instanceUrl = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/find/"+id;
        return restTemplate.getForObject(instanceUrl,User.class);
    }

}

(2)Debug跟踪运行,访问http://localhost:18082/consumer/1

图中的地址就是Eureka注册中心的名字,浏览器结果如下

拓展小知识
① 上面的请求地址是服务状态名字,其实也是当前主机的名字,可以通过配置文件,将它换成IP,修改application.yml配置文件

 instance:
  #指定IP地址
  ip-address: 127.0.0.1
  #访问服务的时候,推荐使用IP
  prefer-ip-address: true

②重新启动user-provider, Debug跟踪运行,就可以看到主机地址已改

5.7 Eureka配置:

(1)服务续约:
  Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除,具体配置如下:

#租约到期,服务时效时间,默认值90秒
lease-expiration-duration-in-seconds: 150
#租约续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30

(2).获取服务列表:
  服务消费者启动时,会检测是否获取服务注册信息配置,如果是,则会从 EurekaServer服务列表获取只读备份,缓存到本地,每隔30秒,会重新获取并更新数据,每隔30秒的时间可以通过配置registry-fetch-interval-seconds修改,配置如下:

registry-fetch-interval-seconds: 30

(3)失效剔除和自我保护
   当 Eureka Client 和 Eureka Server 不再有心跳时,Eureka Server 会将该服务实例从服务注册列表中删除,即服务剔除。
服务下线:

当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer。
服务中心接受到请求后,将该服务置为下线状态

失效剔除:

服务中心每隔一段时间(默认60秒)将清单中没有续约的服务剔除。
通过eviction-interval-timer-in-ms配置可以对其进行修改,单位是毫秒

剔除时间配置

eviction-interval-timer-in-ms: 5000

自我保护
   Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。
服务中心页面会显示如下提示信息

含义:紧急情况!Eureka可能错误地声称实例已经启动,而事实并非如此。续约低于阈值,因此实例不会为了安全而过期。

关闭自我保护

enable-self-preservation: false

六、 负载均衡 Spring Cloud Ribbon

6.1 Ribbon 简介

  Ribbon是Netflix发布的负载均衡器,有助于控制HTTP客户端行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于负载均衡算法,自动帮助服务消费者请求。

  Ribbon默认提供的负载均衡算法:轮询(默认)随机,重试法,加权。当然,我们可用自己定义负载均衡算法
  Ribbon中还包括以下功能:

  • 易于与服务发现组件(比如Netflix的Eureka)集成
  • 使用Archaius完成运行时配置
  • 使用JMX暴露运维指标,使用Servo发布
  • 多种可插拔的序列化选择

6.2 客户端与服务端级别的负载均衡

  1. 服务器端负载均衡:例如Nginx,通过Nginx进行负载均衡过程如下:先发送请求给nginx服务器,然后通过负载均衡算法,在多个业务服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。
  2. 客户端负载均衡:客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,即在客户端就进行负载均衡算法分配。
    服务端负载均衡

    客户端负载均衡

6.3 入门案例

(1)复制2个工程,分别为user-provider和user-provider-demo1,选中user-provider,按Ctrl+C,然后Ctrl+V

(2)名字改成user-provider-demo1,点击OK


(3)在springcloud-parent的pom.xml中添加一个user-provider-demo1

(4)将user-provider-demo1的artifactId换成user-provider-demo1

(5)将2个工程对应的com.example.controller.UserController都修改一下:
user-provider:

@RequestMapping(value = "/find/{id}")
public User findById(@PathVariable(value = "id") Integer id){
    User user = userService.findByUserId(id);
    user.setUsername(user+"     user-provider");
    return user;
}

user-provider-demo1:

@RequestMapping(value = "/find/{id}")
public User findById(@PathVariable(value = "id") Integer id){
    User user = userService.findByUserId(id);
    user.setUsername(user+"     user-provider-demo1");
    return user;
}

(6)启动eureka-server和user-provider、user-provider-demo1、user-consumer,
访问eureka-server地址http://127.0.0.1:7001/效果如下:

6.4 客户端开启负载均衡

(1)Eureka已经集成Ribbon,直接在RestTemplate的配置方法上添加@LoadBalanced注解即可

(2)采用服务名访问配置
修改user-consumer的com.example.controller.UserController的调用方式,不再手动获取ip和端口,而是直接通过服务名称调用,

package com.example.controller;
import com.example.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * UserController
 */
@RestController
@RequestMapping(value = "/consumer")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    /****
     * 在user-consumer服务中通过RestTemplate调用user-provider服务
     * @param id
     * @return
     */
    @GetMapping(value = "/{id}")
    public User queryById(@PathVariable(value = "id")Integer id){

        String url = "http://user-provider/user/find/"+id;
        return restTemplate.getForObject(url,User.class);
    }

}

(3)测试

启动并访问测试http://localhost:18082/consumer/1,可以发现,数据会在2个服务之间轮询切换。

6.5 其他负载均衡策略配置

配置修改轮询策略:Ribbon默认的负载均衡策略是轮询,通过如下

#修改服务地址轮询策略,默认是轮询,配置之后变随机
user-provider:
  ribbon:
    #轮询
    #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    随机算法
    #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    #重试算法,该算法先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
    #NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule
    #加权法,会根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果同统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则。
    #NFLoadBalancerRuleClassName:com.netflix.loadbalancer.ZoneAvoidanceRule

知识梳理

  1. Ribbon的负载均衡算法应用在客户端(Http请求),只需要提供服务列表,就能帮助消费端自动访问服务端,并通过不同算法实现负载均衡。
  2. Ribbon的轮询、随机算法配置:在application.yml中配置
    {服务名称}.ribbon.NFLoadBalancerRuleClassName
  3. 负载均衡的切换:在LoadBalancerInterceptor中获取服务的名字,通过调用RibbonLoadBalancerClient的execute方法,并获取ILoadBalancer负载均衡器,然后根据ILoadBalancer负载均衡器查询出要使用的节点,再获取节点的信息,并实现调用。

6.5 负载均衡源码跟踪探究

源码跟踪步骤:
通过ctrl + shift + n 快捷键,打开LoadBalancerInterceptor 类,断点打入intercept 方法中

继续跟入execute方法:发现获取了18081发端口的服务

再跟下一次,发现获取的是18081和18083之间切换

七、 熔断器 Spring Cloud Hystrix

7.1 Hystrix 简介

   Hystrix可以防止一个应用程序多次试图执行一个操作,即很可能失败,允许它继续而不等待故障恢复或者浪费 CPU 周期。它可以帮助快速地拒绝对一个操作,即很可能失败,而不是等待操作超时(或者不返回)的请求,以保持系统的响应时间。

Hystrix的作用是什么?:实现服务熔断降级处理,保护微服务,防止雪崩效应发生。

Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库、防止出现级联失败也就是雪崩效应。

7.2 雪崩效应

分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务.单个服务出现问题,调用这个服务就出现线程阻塞.当有大批量请求调用库存服务时,最终可能导致整个商品服务资源耗尽,无法继续对外提供服务。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成不可估量的严重后果,这就是常说的服务故障的“雪崩效应”。

7.3 熔断器状态

熔断器有三个状态 CLOSED、 OPEN、HALF_OPEN 熔断器默认关闭状态,当触发熔断后状态变更为 OPEN,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为HALF_OPEN 半开启状态,熔断探测服务可用则继续变更为 CLOSED关闭熔断器。

① Closed:关闭状态,所有请求正常访问

② Open:打开状态,所有请求都会被降级。

Hystrix会对请求情况计数,当一定时间失败请求百分比达到阈(yu:四声)值(极限值),则触发熔断,断路器完全关闭,默认失败比例的阈值是50%,请求次数最低不少于20次

③ Half Open:半开状态

Open状态不是永久的,打开一会后会进入休眠时间(默认5秒)。休眠时间过后会进入半开状态。
半开状态:熔断器会判断下一次请求的返回状况,如果成功,熔断器切回closed状态。如果失败,熔断器切回open状态。

熔断 三种 状态 流程图:

熔断器的核心:线程隔离和服务降级。

1.线程隔离:是指Hystrix为每个依赖服务调用一个小的线程池,如果线程池用尽,调用立即被拒绝,默认不采用排队。
2.服务降级(兜底方法):优先保证核心服务,而非核心服务不可用或弱可用。触发Hystrix服务降级的情况:线程池已满、请求超时。

7.4 局部熔断/服务降级案例

目标:服务提供者的服务出现了故障,服务消费者快速失败给用户友好提示。体验服务降级

降级:某个方法发生故障,则返回默认的数据给用户,此时叫服务降级。其他方法可用,该方法不可用 ,只是降低权限
(1)引入熔断的依赖坐标:在user-consumer中加入依赖

<!--熔断器-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

(2)开启熔断的注解

(3)服务降级处理

/****
    * 服务降级处理方法
    * 当某个方法发生异常或者执行超时的时候,则直接让该方法处理用户的请求
    * @return
    */
public User failBack(Integer id){
   User user = new User();
   user.setUsername("服务降级,默认处理!");
   return  user;
}

(4)在有可能发生问题的方法上添加降级处理调用,例如在queryById方法上添加降级调用,代码如下:

(5)在 user-provider 的 controller 方法里面,添加一个异常信息

package com.example.controller;

import com.example.domain.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 1. UserController
 */
@RestController
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private UserService userService;

    /***
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/find/{id}")
    public User findById(@PathVariable(value = "id") Integer id){
        if (id==3){
            throw new RuntimeException("aaa");
        }
        User user = userService.findByUserId(id);
        user.setUsername(user + "        user-provider");
        return user;
    }
}

(6)测试
将服务全部停掉,启动eureka-server和user-consumer 还有 user-provider,然后请求http://localhost:18082/consumer/3测试效果如下:

7.5 其他熔断策略配置

(1)超时时间测试
.   ①更改user-consumer application.yml配置文件 ,具体配置如下:

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器 默认false关闭的。测试配置是否生效
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值5秒
        sleepWindowInMilliseconds: 10000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 10
      execution:
        isolation:
          thread:
            # 熔断降级超时设置,默认为2秒
            timeoutInMilliseconds: 2000

②修改user-provider的com.example.controller.UserController的findById方法,让它休眠3秒钟。
③修改user-consumer的application.yml,设置超时时间5秒,此时不会熔断。

④如果把超时时间改成2000,此时就会熔断

⑤运行程序测试 http://localhost:18082/consumer/2

7.6 扩展-全局服务降级的fallback方法

  1. 能实现一个局部方法熔断案例

properties
①.定义一个局部处理熔断的方法failBack()
②.在指定方法上使用@HystrixCommand(fallbackMethod = “failBack”)配置调用

  1. 能实现全局方法熔断案例

properties
①.定义一个全局处理熔断的方法defaultFailBack()
②.在类上使用@DefaultProperties(defaultFallback = “defaultFailBack”)配置调用
③.在指定方法上使用@HystrixCommand

八、Spring Cloud Feign

8.1 Feign简介

  Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。
Feign 的特性:

  1. 集成Ribbon的负载均衡功能
  2. 集成了Hystrix的熔断器功能
  3. 支持请求压缩
  4. 大大简化了远程调用的代码
  5. Feign以更加优雅的方式编写远程调用代码,并简化重复代码

Feign 工作原理 :
  ① 在开发微服务应用时,我们会在主程序入口添加 @EnableFeignClients 配置的扫描包路径。如果没配置,默认为启动类的包路径。

  ② 当程序启动时,会进行包扫描,扫描所有 @FeignClient 的注解的类,并将这些信息注入 Spring IOC 容器中。

  ③ 校验 @FeignClient 修饰的类,包括类必须是 interface ,以及@FeignClient的fallback及fallbackFactory配置的必须是接口的实现类。

8.2 快速入门

实现步骤
①. 导入feign依赖
  在user-consumer中添加spring-cloud-starter-openfeign依赖

<!--配置feign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

②. 编写Feign客户端接口-将请求地址写到该接口上
  在user-consumer中创建com.example.feign.UserClient接口,代码如下:

package com.example.feign;

import com.example.domain.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * UserClient
 */
//@FeignClient(value = "要调用微服务的名字")
@FeignClient(value = "user-provider")
public interface UserClient {
    /***
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/user/find/{id}")
    User findById(@PathVariable(value = "id") Integer id);
}

  在user-consumer中创建ConsumerFeignController类中,使用@Autowired注入FeignClient ,代码如下:

package com.example.controller;
import com.example.domain.User;
import com.example.feign.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * ConsumerFeignController
 */
@RestController
@RequestMapping(value = "/feign")
public class ConsumerFeignController {


    @Autowired
    private UserClient userClient;

    @RequestMapping(value = "/{id}")
    public User queryById(@PathVariable(value = "id")Integer id){
        return userClient.findById(id);
    }
}

③. 消费者启动引导类开启Feign功能注解

@SpringBootApplication
@EnableDiscoveryClient //开启Eureka客户端发现功能
@EnableCircuitBreaker // 开启熔断
@EnableFeignClients(basePackages = {"com.atguigu.feign"})
public class UserConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerApplication.class,args);
    }

    /***
     * 将RestTemplate的实例放到Spring容器中
     * @return
     */
    @Bean
    @LoadBalanced  //开启负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
  }  

④. 访问接口测试
  请求http://localhost:18082/feign/2,效果如下:

8.3 负载均衡

(1)Feign自身已经集成了Ribbon,因此使用Feign的时候,不需要额外引入依赖。

(2)Feign内置的ribbon默认设置了请求超时时长,默认是1000,可以修改
ribbon内部有重试机制,一旦超时,会自动重新发起请求。如果不希望重试可以关闭配置:

# 修改服务地址轮询策略,默认是轮询,配置之后变随机
user-provider:
  ribbon:
    #轮询
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    ConnectTimeout: 1000 # 连接超时时间 没有连接上
    ReadTimeout: 2000 # 数据读取超时时间 连接上了,连上之后开始计时,但读取数据需要花费很长时间
    MaxAutoRetries: 1 # 最大重试次数(第一个服务)在第一次连接超时之后,在重新连接一次
    MaxAutoRetriesNextServer: 0 # 最大重试下一个服务次数(集群的情况才会用到)
    OkToRetryOnAllOperations: false # 是否所有操作都进行重试

(3) 测试 ReadTimeout 读取超时 ,在 UserController 添加连接超时时间

(4) 运行测试连接超时 http://localhost:18082/feign/2,浏览器页面报错ReadTimeout.

8.4 开启Hystrix

(1) 在 user-consumer 配置文件 application.yml 中开启 feign 熔断器支持:默认关闭

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能

(2) 熔断降级类创建
修改user-consumer,创建一个类com.example.feign.fallback.UserClientFallback,实现刚才编写的UserClient,作为FallBack的处理类,代码如下:

package com.example.feign.fallback;

import com.example.domain.User;
import com.example.feign.UserClient;
import org.springframework.stereotype.Component;

/**
 * UserClientFallback
 */
@Component
public class UserClientFallback implements UserClient {
    @Override
    public User findById(Integer id) {
        User user = new User();
        user.setUsername("服务熔断");
        return user;
    }
}

(3)指定Fallback处理类

在@FeignClient注解中,指定FallBack处理类

package com.example.feign;

import com.example.domain.User;
import com.example.feign.fallback.UserClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * UserClient
 */
//@FeignClient(value = "要调用微服务的名字")
//fallback = UserClientFallback.class 指定当前feign接口发生熔断降级处理类
@FeignClient(value = "user-provider",fallback = UserClientFallback.class)
public interface UserClient {
    /***
     * 根据ID查询用户信息
     * @param id
     * @return
     */
    @RequestMapping(value = "/user/find/{id}")
    User findById(@PathVariable(value = "id") Integer id);
}

(4)测试
关闭服务消费方,请求http://localhost:18082/feign/3

8.5 请求压缩

(1) SpringCloudFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
(2) 压缩
①user-consumer通过配置开启请求与响应的压缩功能:(简单压缩)

feign:
	compression:
        request:
            enabled: true # 开启请求压缩
        response:
            enabled: true # 开启响应压缩

② 也可以对请求的数据类型,以及触发压缩的大小下限进行设置(完整压缩)

#  Feign配置
feign:
	compression:
		request:
			enabled: true # 开启请求压缩
			mime-types:	text/html,application/xml,application/json # 设置压缩的数据类型
			min-request-size: 2048 # 设置触发压缩的大小下限
			#以上数据类型,压缩大小下限均为默认值
		response:
            enabled: true # 开启响应压缩	

8.6 Feign的日志级别配置

(1) 实现
  通过loggin.level.xx=debug来设置日志级别。然而这个对Feign客户端不会产生效果。因为@FeignClient注解修饰的客户端在被代理时,都会创建一个新的Feign.Logger实例。我们需要额外通过配置类的方式指定这个日志的级别才可以。
(2) user-consumer的application.yml 配置文件添加如下内容

#com.atguigu 包下的日志级别都为Debug
logging:
  level:
    com.atguigu: debug

(3) Feign日志等级配置
在user-consumer中的application启动类UserConsumerApplication,定义日志级别

@SpringBootApplication
@EnableDiscoveryClient  //开启Eureka客户端发现功能
@EnableCircuitBreaker
@EnableFeignClients(basePackages = {"com.atguigu.feign"})
public class UserConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerApplication.class,args);
    }

    /***
     * 将RestTemplate的实例放到Spring容器中
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    /**
     * 配置日志级别
     * @return
     */
    @Bean
    public Logger.Level feignloggerlevel(){
        return Logger.Level.FULL;
    }
}

日志级别说明

Feign支持4中级别:
	NONE:不记录任何日志,默认值
	BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
	HEADERS:在BASIC基础上,额外记录了请求和响应的头信息
	FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据

(4) 运行程序 http://localhost:18082/feign/2

九、网关 Spring Cloud Gateway

9.1 Gateway 简介

  Spring Cloud Gateway 是Spring Cloud团队的一个全新项目,基于Spring 5.0、SpringBoot2.0、Project Reactor 等技术开发的网关。 旨在为微服务架构提供一种简单有效统一的API路由管理方式。

  Spring Cloud Gateway 作为SpringCloud生态系统中的网关,目标是替代Netflix Zuul。Gateway不仅提供统一路由方式,并且基于Filter链的方式提供网关的基本功能

9.2 快速入门

(1 ) 创建一个子工程 gateway-service

(2)pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache/POM/4.0.0"
         xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache/POM/4.0.0 http://maven.apache/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-parent</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway-service</artifactId>

    <dependencies>
        <!--网关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Eureka客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

(3)启动类
创建启动类com.example.GatewayApplication,代码如下:

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * GatewayApplication
 */
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

(4) application.yml 配置

# 注释版本
server:
  port: 18084
spring:
  application:
    name: api-gateway # 应用名
#Eureka服务中心配置
eureka:
  client:
    service-url:
      # 注册Eureka Server集群
      defaultZone: http://127.0.0.1:7001/eureka

(5) 通过网关配置一个路由功能,用户访问网关的时候,如果用户请求的路径是以/user开始,则路由到user-provider服务去,修改application.yml配置即可实现,配置如下:

spring:
  application:
    # 应用名
    name: api-gateway
  cloud:
    gateway:
      routes:
      # 用户所有以/user开始的请求,都给http://localhost:18081服务处理
      #id唯一标识,可自定义 , 随便写,可以写小红,小花都行
      - id: user-service-route
        #路由的服务地址
        uri: http://localhost:18081
        # 路由拦截的地址配置(断言)
        # /user/**所有以/user开始的请求都将被路由到uri指定的服务地址,
        # 将该请求交给uri指定的服务处理,比如请求:http://localhost:18084/user/find/2会把请求交给http://localhost:18081/user/find/2处理
        predicates:
        - Path=/user/**

概念解释:

  • Route(路由):路由是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。
  • Predicate(谓语、断言):路由转发的判断条件
  • Filter(过滤器):过滤器是路由转发请求时所经过的过滤逻辑,可用于修改请求、响应内容。
    (6) 启动GatewayApplication测试
    访问http://localhost:18084/user/find/2会访问user-provider服务,效果如下:

9.3 动态路由

(1) 修改映射配置:通过服务名称获取:
因为已经配置了Eureka客户端,可以从Eureka获取服务的地址信息,修改application.yml文件

spring:
  application:
    # 应用名
    name: api-gateway
  cloud:
    gateway:
      routes:
        #id唯一标识,可自定义
        - id: user-service-route
          #路由的服务地址
          #uri: http://localhost:18081 请求负载均衡算法Load Balance
          #lb协议表示从Eureka注册中心获取服务请求地址
          #user-provider访问的服务名称。
          #路由地址如果通过lb协议加服务名称时,会自动使用负载均衡访问对应服务
          uri: lb://user-provider
          # 路由拦截的地址配置(断言)
          predicates:
            - Path=/user/**

(2) 路由配置中uri所用的协议为lb时,gateway将把user-provider解析为实际的主机和端口,并通过Ribbon进行负载均衡。http://localhost:18084/user/find/2

9.4 过滤器

(1) 过滤器作为Gateway的重要功能。常用于请求鉴权、服务调用时长统计、修改请求或响应header、限流、去除路径等等…
(2) 过滤器的分类

默认过滤器:出厂自带,实现好了拿来就用,不需要实现
全局默认过滤器
局部默认过滤器
自定义过滤器:根据需求自己实现,实现后需配置,然后才能用哦。
全局过滤器:作用在所有路由上。
局部过滤器:配置在具体路由下,只作用在当前路由上。

(3) 默认过滤器
① 全局默认过滤器:对输出响应头设置属性
设置对输出的响应设置其头部属性名称为X-Response-Default-MyName,

spring:
  cloud:
    gateway:
     # 配置全局默认过滤器
      default-filters:
      # 往响应过滤器中加入信息
        - AddResponseHeader=X-Response-Default-MyName, 加入要设置的值

查看浏览器响应头信息

② 局部过滤器:通过局部默认过滤器,修改请求路径。
局部过滤器在这里介绍两种:添加路径前缀去除路径前缀
  第一:添加路径前缀:
 在gateway中可以通过配置路由的过滤器PrefixPath 实现映射路径中的前缀,配置请求地址添加路径前缀过滤器


注释版本
server:
 port: 18084
spring:
 application:
   name: api-gateway # 应用名
 cloud:
   gateway:
     routes:
     # 用户所有以/user开始的请求,都给http://localhost:18081服务处理
     #id唯一标识,可自定义
       - id: user-service-route
         #路由的服务地址
         uri: lb://user-provider
         # 路由拦截的地址配置(断言)
         # /user/**所有以/user开始的请求都将被路由到uri指定的服务地址,
         # 将该请求交给uri指定的服务处理,比如请求:http://localhost:18084/user/find/2会把请求交给http://localhost:18081/user/find/2处理
         predicates:
         #  - Path=/user/**
           - Path=/**
         # 配置全局默认过滤器
         # 给每次请求都添加一个前缀 /user比如请求:http://localhost:18084/2-->http://localhost:18084/user/2
         filters:
           - PrefixPath=/user
#Eureka服务中心配置
eureka:
 client:
   service-url:
     # 注册Eureka Server集群
     defaultZone: http://127.0.0.1:7001/eureka

–全局和局部的区别:全局是对所有的id有效,局部只是对某一个id有效
请求:http://localhost:18084/find/2 会自动添加/user

第二:去除路径前缀:
  在gateway中通过配置路由过滤器StripPrefix,实现映射路径中地址的去除。通过StripPrefix=1来指定路由要去掉的前缀个数。如:路径/api/user/1将会被路由到/user/1。
 配置去除路径前缀过滤器

spring:
  application:
    # 应用名
    name: api-gateway
  cloud:
    gateway:
      routes:
        #id唯一标识,可自定义
        - id: user-service-route
          #路由的服务地址
          #uri: http://localhost:18081
          #lb协议表示从Eureka注册中心获取服务请求地址
          #user-provider访问的服务名称。
          #路由地址如果通过lb协议加服务名称时,会自动使用负载均衡访问对应服务
          uri: lb://user-provider
          # 路由拦截的地址配置(断言)
          predicates:
            - Path=/**
          filters:
            # 请求地址添加路径前缀过滤器
            #- PrefixPath=/user
            # 去除路径前缀过滤器 添加前缀和去掉前缀不要一起使用
            - StripPrefix=1

测试结果如下:

(4) 自定义过滤器案例
 自定义过滤器也有两个:全局自定义过滤器,和局部自定义过滤器。
全局过滤器自定义:
  全局过滤器作用范围:所有请求都会被拦截。
①在gateway-service中创建com.example.filter.LoginGlobalFilter全局过滤器类,代码如下:

@Component
public class LoginGlobalFilter implements GlobalFilter, Ordered {
    /**
     * 过滤拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("全局拦截器");
        //  request获取
        ServerHttpRequest request = exchange.getRequest();
        // response获取
        ServerHttpResponse response = exchange.getResponse();
        // 获取请求参数
        String token = request.getQueryParams().getFirst("token");
        // 判断token是否为null,如果用户请求参数中没有token,则表示用户未登录
        if (StringUtils.isEmpty(token)){
            // 没有登录,状态设置401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 结束请求
            return response.setComplete();
        }
        // 如果用户传递了一个token,则表示用户已经登录 ,直接放行
        return chain.filter(exchange);
    }

    /***
     * 定义过滤器执行顺序
     * 返回值越小,越靠前执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

测试:不携带token http://localhost:18084/hahah/user/find/2效果如下:

测试:携带token http://localhost:18084/hahah/user/find/2?token=abc 此时可以正常访问

** 局部过滤器定义**
  局部过滤器作用范围:该局部过滤器在哪个id下配置,则该局部过滤器只针对该id的路由规则有效。
① 在gateway_service中编写MyParamGatewayFilterFactory类

@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory {

    /**
     * 拦截当前id对应的请求
     * @param config
     * @return
     */
    @Override
    public GatewayFilter apply(Object config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("局部拦截器");
                return chain.filter(exchange);
            }
        };
    }
    /***
     * 构造函数
     */
    public MyParamGatewayFilterFactory() {
        super(MyParamGatewayFilterFactory.Config.class);
    }

    /****
     * 在该类执行初始化后,可以在这里执行相关初始化操作
     */
    public static class Config {
    }
}

② 修改application.yml配置文件

测试访问,访问http://localhost:18084/hahah/user/find/2?token=aaa会输出

关于idea 无法识别对应的配置文件的解决方案

(1) 从外部导入的Maven项目,idea没有识别处理

解决方案:
点击idea右侧任务栏,选择maven打开

点击 “+”图标

在文件夹内找到我们刚刚添加的文件,选择pom.xml, 点击下方的OK键

将项目添加到maven管理即可,如下图

(2) idea 没有正确识别配置问价的类型
解决方案:

选择Editor,在选择File Types

将idea 不能正确识别的选项点右侧的-号移除 , 重新添加配置文件即可

更多推荐

Spring Cloud 五大组件总结大全