SpringCloud入门

SpringCloud

一、系统架构演变之路(回顾)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sC0GovPR-1687953616373)(…/%E8%AF%BE%E4%BB%B6/day02-SpringCloud-%E8%AE%B2%E4%B9%89.assets/%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E6%BC%94%E5%8F%98%E4%B9%8B%E8%B7%AF.jpg)]

1.1 单一应用架构

当网站流量很小时,只需要一个应用,所有功能部署在一起,减少部署节点成本的框架称之为集中式框架。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。

1.2 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

1.3 分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

1.4 面向服务(SOA)架构

典型代表有两个:流动计算架构和微服务架构;

流动计算架构:

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。流动计算架构的最佳实践阿里的Dubbo。

微服务架构

与流动计算架构很相似,除了具备流动计算架构优势外,微服务架构中的微服务可以独立部署,独立发展。且微服务的开发不会限制于任何技术栈。微服务架构的最佳实践是SpringCloud。

二、初识Spring Cloud

大家谈起的微服务,大多来讲说的只不过是种架构方式。其实现方式很多种:Spring Cloud,Dubbo,华为的Service Combo,Istio…。那么这么多的微服务架构产品中,我们为什么要用Spring Cloud?因为它后台硬、技术强、群众基础好,使用方便;

2.1 Spring Cloud简介

Spring Cloud从技术架构上降低了对大型系统构建的要求和难度,使我们以非常低的成本(技术或者硬件)搭建一套高效、分布式、容错的平台,但Spring Cloud也不是没有缺点,小型独立的项目不适合使用。

Spring Cloud是一系列分布式微服务技术的有序整合,把非常流行的微服务的技术整合到一起。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d5XnWDGW-1687953616376)(02-SpringCloud-讲义.assets/1564788675707.png)]

2.2 Spring Cloud的版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iJid7tlz-1687953616377)(day02-SpringCloud-讲义.assets\1557971730121.png)]

  • SpringCloud是一系列框架组合,为了避免与框架版本产生混淆,采用新的版本命名方式,形式为大版本名+子版本名称
  • 大版本名用伦敦地铁站名:
  • 子版本名称三种
    • SNAPSHOT:快照版本,尝鲜版,随时可能修改
    • M版本,MileStone,M1表示第一个里程碑版本,一般同时标注PRE,表示预览版
    • SR,Service Release,SR1表示第一个正式版本,同时标注GA(Generally Available),稳定版

2.3 SpringCloud与SpringBoot版本匹配关系

SpringBootSpringCloud
1.2.xAngel版本
1.3.xBrixton版本
1.4.xCamden版本
1.5.xDalston版本、Edgware
2.0.xFinchley版本
2.1.xGreenwich GA版本 (2019年2月发布)

鉴于SpringBoot与SpringCloud关系,SpringBoot建议采用2.1.x版本

三、模拟微服务业务场景

模拟开发过程中的服务间关系。抽象出来,开发中的微服务之间的关系是生产者和消费者关系。

目标:模拟一个最简单的服务调用场景,场景中保护微服务提供者(Producer)和微服务调用者(Consumer),方便后面学习微服务架构

注意:实际开发中,每个微服务为一个独立的SpringBoot工程。

3.1 创建服务的父工程

**目标:**新建一个父项目itheima_parent

实现步骤:

  1. 创建springboot的工程
  2. 配置依赖坐标,SpringBoot的parent、以及SpringCloud的依赖管理坐标

实现过程:

  1. 创建maven工程,itheima_parent

  2. 配置依赖坐标[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYlsBhx7-1687953616378)(day02-SpringCloud-讲义.assets\1559000095864.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qrz1BDcv-1687953616379)(day02-SpringCloud-讲义.assets\1559000146982.png)]

  3. 添加起步依赖坐标:SpringBoot、SpringCloud

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    

3.2 创建服务提供者(provider)工程

**目标:**新建一个项目provider_service,对外提供查询用户的服务

实现步骤:

  1. 创建SpringBoot工程
  2. 勾选依赖坐标
  3. 数据库连接信息
  4. 创建User表、创建实体UserBean
  5. 编写三层架构:Mapper、Service、controller,编写查询所有的方法
  6. 配置Mapper映射文件
  7. 在application.properties中添加MyBatis配置,扫描mapper.xml和mapper
  8. 访问测试地址

实现过程:

  1. 创建SpringBoot工程[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WkRqR06x-1687953616380)(02-SpringCloud-讲义.assets/1564724624018.png)]

  2. 勾选依赖坐标[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGQvqaeT-1687953616380)(02-SpringCloud-讲义.assets/1564759393975.png)]

  3. 数据库连接信息

  4. 创建User表、创建实体User

    -- 创建数据库
    CREATE database springcloud CHARACTER SET utf8 COLLATE utf8_general_ci;
    -- 使用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', '李四');
    

    实体bean:

    public class User {
        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;//备注
    	//getter setter
    }
    
  5. 编写三层架构:Mapper、Service、controller,编写查询所有的方法

    @Repository
    @Mapper
    public interface UserMapper {
        User findById();
    }
    

    service类:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public List<User> findAll() {
            return userMapper.findAll();
        }
    
        @Override
        public User findById(Integer id) {
            return userMapper.findById(id);
        }
    
    }
    

    controller:

    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        UserService userService;
        /**
         * 查询所有
         * @return
         */
        @RequestMapping("/findAll")
        public List<User> findAll() {
            return userService.findAll();
        }
        /**
         * 根据id查询
         * @param id
         * @return
         */
        @RequestMapping("/findById")
        public User findById(Integer id) {
            return userService.findById(id);
        }
    }
    
  6. 配置Mapper映射文件

    <?xml version="1.0" encoding="utf-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.itheima.mapper.UserMapper">
    	
        <select id="findAll" resultType="user">
    		select * from tb_user
    	</select>
        <select id="findById" parameterType="Integer" resultType="user">
    		select * from tb_user where id = #{id}
    	</select>
    </mapper>
    
  7. 在application.properties中添加MyBatis配置,扫描mapper.xml和mapper

    server:
      port: 9091
    # DB 配置
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
        password: root
        username: root
    # 扫描实体
    mybatis:
      type-aliases-package: com.itheima.domain
      # mapper.xml配置文件路径
      mapper-locations: classpath:mapper/*Mapper.xml
    
  8. 访问测试地址

    http://localhost:9091/user/findById?id=1

3.3 创建服务消费者(consumer)工程

**目标:**新建一个项目consumer_service,调用用户微服务提供的查询用户服务

实现步骤:

  1. 创建消费者SpringBoot工程consumer_service
  2. 勾选starter:开发者工具devtools、web
  3. 注册http请求客户端对象RestTemplate
  4. 编写Controller,用RestTemplate访问服务提供者
  5. 启动服务并测试

实现过程:

  1. 创建consumer_service的SpringBoot工程[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tA616O4A-1687953616381)(02-SpringCloud-讲义.assets/1564760940122.png)]

  2. 勾选需要的相关依赖[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oVHKaiKV-1687953616381)(02-SpringCloud-讲义.assets/1564760978742.png)]

  3. 编写代码

    1. 在启动类中注册RestTemplate

      @SpringBootApplication
      public class ConsumerApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(ConsumerApplication.class,args);
          }
          //注册RestTemplate
          @Bean
          public RestTemplate restTemplate(){
              return new RestTemplate();
          }
      }
      
    2. 编写ConsumerController,在Controller中直接调用RestTemplate,远程访问User-service

      @RestController
      @RequestMapping("/consumer")
      public class ConsumerController {
          @Autowired
          private RestTemplate restTemplate;
          @GetMapping("{id}")
          public User queryById(@PathVariable Long id){
              String url = String.format("http://localhost:9091/user/%d", id);
              return restTemplate.getForObject(url,User.class);
          }
      }
      
  4. 启动并测试,访问:http://localhost:8080/consumer/1[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0DOcmwp-1687953616382)(day02-SpringCloud-讲义.assets\1557987811610.png)]

3.4 思考问题

producer_service:对外提供用户查询接口

consumer_service:通过RestTemplate访问接口查询用户数据

存在的问题:

  1. 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
  2. 在服务消费者中,不清楚服务提供者的状态
  3. 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
  4. 服务提供者的如果出现故障,是否能够及时发现。
  5. RestTemplate这种请求调用方式是否还有优化空间?
  6. 多服务权限拦截如何实现?怎么保证服务的可用性?
  7. 配置文件每次都修改好多个是不是很麻烦!?

其实上面说的问题,概括一下就是微服务架构必然要面临的问题

  • 服务管理:自动注册与发现、状态监管 --Spring Cloud Eureka
  • 服务负载均衡 --Spring Cloud Ribbon
  • 熔断器 – Spring Cloud Hystrix
  • 远程过程调用
  • 网关
  • 配置中心以及消息总线

四、注册中心 Spring Cloud Eureka

4.1 Eureka 简介

Eureka解决了第一个问题:服务的管理,注册和发现、状态监管、动态路由。

Eureka负责管理记录服务提供者的信息。服务调用者无需自己寻找服务,Eureka自动匹配服务给调用者。

Eureka与服务之间通过心跳机制进行监控;

4.2 原理图

基本架构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPyugr6c-1687953616383)(day02-SpringCloud-讲义.assets\1557988231880.png)]

Eureka:就是服务注册中心

服务提供者:启动后向Eureka注册自己的信息(地址,提供什么服务)

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

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

4.3 整合注册中心Eureka

**步骤:**分三步

  • 第一步:eureka-serve搭建工程eureka_server
  • 第二步:服务提供者-注册服务,user_service工程
  • 第三步:服务消费者-发现服务,consumer_service工程

4.3.1 搭建eureka-server工程

  1. 创建eureka_server的springboot工程。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHRBxDhW-1687953616384)(02-SpringCloud-讲义.assets/1564784551870.png)]

  2. 勾选坐标[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-spM3LO1f-1687953616385)(02-SpringCloud-讲义.assets/1564784629049.png)]

  3. 在启动类EurekaServerApplication声明当前应用为Eureka服务使用@EnableEurekaServer注解

  4. 编写配置文件application.yml

    • # 端口
      server:
        port: 10086
      spring:
        application:
          name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
      eureka:
        client:
          service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
            defaultZone: http://127.0.0.1:10086/eureka
      
  5. 启动EurekaServerApplication

  6. 测试访问地址http://127.0.0.1:10086,如下信息代表访问成功

  7. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O0PBALqd-1687953616386)(day02-SpringCloud-讲义.assets\1561653396022.png)]

  8. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DgJrrRgA-1687953616387)(day02-SpringCloud-讲义.assets\1561653583141.png)]

  9. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IJ7C7KM0-1687953616387)(day02-SpringCloud-讲义.assets\1561653748123.png)]

  10. 关闭注册自己

# 是否抓取注册列表
# 是否注册服务中心Eureka
eureka:
 client:
     fetch-registry: false 
     register-with-eureka: false 

4.3.2 服务提供者-注册服务中心

  1. 在服务提供者user_service工程中添加Eureka客户端依赖

    • <!--eureka客户端starter-->
      <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
          </dependency>
      </dependencies>
      <!--SpringCloud所有依赖管理的坐标-->
      <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-dependencies</artifactId>
                  <version>Greenwich.SR1</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
          </dependencies>
      </dependencyManagement>
      
  2. 在启动类上开启Eureka客户端发现功能@EnableDiscoveryClient

    • @SpringBootApplication
      @EnableDiscoveryClient // 开启Eureka客户端发现功能
      public class UserApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(UserApplication.class,args);
          }
      }
      
  3. 修改配置文件:spring.application.name指定应用名称,作为服务ID使用

    • # 配置应用基本信息 和 DB
      server:
          port: 9091
      spring:
          application:
              name: user-service
          datasource:
              driver-class-name: com.mysql.jdbc.Driver
              password: root
              url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
              username: root
      # 配置eurekaserver
      eureka:
          client:
              service-url:
                  defaultZone: http://127.0.0.1:10086/eureka
      
  4. 完成之后重启项目

  5. 客户端代码会自动把服务注册到EurekaServer中

  6. 在Eureka监控页面可以看到服务注册成功信息

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QsO3WZEv-1687953616388)(day02-SpringCloud-讲义.assets\1557994266301.png)]

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

  1. 在服务消费者spring_cloud_itcast_consumer_service工程中添加Eureka客户端依赖

    • <!-- Eureka客户端 -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <!--SpringCloud所有依赖管理的坐标-->
      <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-dependencies</artifactId>
                  <version>Greenwich.SR1</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
          </dependencies>
      </dependencyManagement>
      
  2. 在启动类开启Eureka客户端@EnableDiscoveryClient

    • @SpringBootApplication
      @EnableDiscoveryClient//开启服务发现
      public class ConsumerApplication {
          public static void main(String[] args) {
              SpringApplication.run(ConsumerApplication.class,args);
          }
          @Bean
          public RestTemplate restTemplate(){
              return new RestTemplate();
          }
      }
      
  3. 修改配置文件:加入EurekaServer地址

    • # 配置应用基本信息
      server:
          port: 8080
      spring:
          application:
              name: consumer-demo
      # 配置eurekaserver
      eureka:
          client:
              service-url:
                  defaultZone: http://127.0.0.1:10086/eureka
      
  4. 启动服务,在服务中心查看是否注册成功

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

修改代码

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("{id}")
    public User queryById(@PathVariable Long id){
        String url = String.format("http://localhost:9091/user/%d", id);

        //1、获取Eureka中注册的user-service实例列表
        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service");
        //2、获取实例
        ServiceInstance serviceInstance = serviceInstanceList.get(0);
        //3、根据实例的信息拼接的请求地址
        url = String.format("http://%s:%s/user/%d", serviceInstance.getHost(), serviceInstance.getPort(), id);
        //发生请求
        return restTemplate.getForObject(url,User.class);
    }
}

Debug跟踪运行

服务提供者地址拼接成功[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4hr1iSz-1687953616388)(day02-SpringCloud-讲义.assets\1557995896939.png)]

这里服务的host地址有什么问题!?

# 默认注册时使用的是主机名,想用ip进行注册添加如下配置
eureka:
    instance:
		# ip地址
        ip-address: 127.0.0.1
		# 更倾向于使用ip,而不是host名
        prefer-ip-address: true

修改配置之后[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNlrMEnG-1687953616389)(02-SpringCloud-讲义.assets/1564786319994.png)]

4.4 Eureka详解

4.4.1 基础架构

Eureka架构中的三个核心角色

  • 服务注册中心:Eureka服务端应用,提供服务注册发现功能,eureka-server
  • 服务提供者:提供服务的应用
    • 要求统一对外提供Rest风格服务即可
    • 本例子:user-service
  • 服务消费者:从注册中心获取服务列表,知道去哪调用服务方,consumer-demo

4.4.2 Eureka客户端

服务提供者要向EurekaServer注册服务,并完成服务续约等工作

服务注册过程:
  1. 当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
  2. 服务在启动时,会检测是否有@DiscoveryClient注解和配置信息
  3. 如果有,则会向注册中心发起注册请求,携带服务元数据信息
  4. Eureka注册中心会把服务的信息保存在Map中。
服务续约过程:

服务每隔30秒会向注册中心续约(心跳)一次,如果没有续约,租约在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为:心跳检测。

#向Eureka服务中心集群注册服务
eureka:
  instance:
   # 租约续约间隔时间,默认30秒
    lease-renewal-interval-in-seconds: 30 
  	# 租约到期,服务时效时间,默认值90秒
    lease-expiration-duration-in-seconds: 90 
   
  • 两个参数可以修改服务续约行为
    • lease-renewal-interval-seconds:90,租约到期时效时间,默认90秒
    • lease-expiration-duration-in-seconds:30,租约续约间隔时间,默认30秒
  • 服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
获取服务列表:

每隔30秒服务会从注册中心中拉取一份服务列表

这个时间可以通过配置修改

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

4.4.3 失效剔除和自我保护

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

服务中心每隔一段时间(默认60秒)将清单中没有续约的服务剔除。

通过eviction-interval-timer-in-ms配置可以对其进行修改,单位是毫秒

自我保护:

Eureka会统计服务实例最近15分钟心跳续约的比例是否低于85%,如果低于则会触发自我保护机制。

服务中心页面会显示如下提示信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clRtyj4M-1687953616391)(day02-SpringCloud-讲义.assets\1558056004897.png)]

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

  • 自我保护模式下,不会剔除任何服务实例

  • 自我保护模式保证了大多数服务依然可用

  • 通过enable-self-preservation配置可用关停自我保护,默认值是打开

    #向Eureka服务中心集群注册服务
    eureka:
      server:
        enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    

五、负载均衡 Spring Cloud Ribbon

5.1 Ribbon 简介

解决了集群服务中,多个服务高效率访问的问题。

什么是Ribbon?

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

Ribbon默认提供的负载均衡算法:轮询,随机其他…。当然,我们可用自己定义负载均衡算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CJhm0K0l-1687953616391)(02-SpringCloud-讲义.assets/1564761498131.png)]

5.2 入门案例

实现负载均衡访问用户服务

如果想要做负载均衡,我们的服务至少2个以上。所有第一步目标

实现步骤:

第一步:启动两个user_service服务

  1. 修改配置文件端口获取方式
  2. 编辑应用启动配置
  3. 启动两个提供者服务
  4. 在注册中心查询是否启动成功

第二步:开启消费者负载均衡

  1. 在RestTemplate的注入方法上加入@LoadBalanced注解
  2. 修改调用请求的Url地址,改为服务名称调用
  3. 访问页面查看效果

实现过程:

第一步:启动两个user_service应用

  1. 修改UserServiceApplication的application.yml配置文件
    • 端口9091改为,${port:9091}
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fj9mYHYT-1687953616394)(day02-SpringCloud-讲义.assets\1558056614463.png)]
  2. 编辑应用启动配置
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fwJLjYP0-1687953616397)(day02-SpringCloud-讲义.assets\1558056672192.png)]
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ak5PP2Si-1687953616397)(day02-SpringCloud-讲义.assets\1558056769410.png)]
  3. 复制一份UserServiceApplication
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmH4bXDZ-1687953616398)(day02-SpringCloud-讲义.assets\1558056924137.png)]
  4. 启动两个UserServiceApplication应用
  5. Eureka服务中心可查看,注册成功[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cjEqpBha-1687953616398)(day02-SpringCloud-讲义.assets/1562968167562.png)]

第二步:开启消费者调用负载均衡

Eureka已经集成Ribbon,所以无需引入依赖。

  1. 在RestTemplate的配置方法上添加@LoadBalanced注解即可

    @Bean
    @LoadBalanced//开启负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    
  2. 修改ConsumerController调用方式,不再手动获取ip和端口,而是直接通过服务名称调用

    @GetMapping("{id}")
    public User queryById(@PathVariable Long id){
        String url = String.format("http://user-service/user/%d", id);
        return restTemplate.getForObject(url,User.class);
    }
    
  3. 访问页面查看结果;并在9091和9092的控制台查看执行情况

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

# 修改服务地址轮询策略,默认是轮询,配置之后变随机
user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

SpringBoot可以修改负载均衡规则,配置为ribbon.NFLoadBalancerRuleClassName

格式{服务提供者名称}.ribbon.NFLoadBalancerRuleClassName

5.3 负载均衡源码跟踪探究(了解)

为什么只输入了Service名称就可以访问了呢?不应该需要获取ip和端口吗?

负载均衡器动态的从服务注册中心中获取服务提供者的访问地址(host、port)

显然是有某个组件根据Service名称,获取了服务实例ip和端口。就是LoadBalancerInterceptor

这个类会对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真正服务地址信息,替换服务id。

源码跟踪步骤:

  1. 打开LoadBalancerInterceptor类,断点打入intercept方法中

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkLnlO6R-1687953616399)(day02-SpringCloud-讲义.assets\1558059731124.png)]
  2. 继续跟入execute方法:发现获取了9092发端口的服务

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aHhLfhyt-1687953616399)(day02-SpringCloud-讲义.assets\1558059973068.png)]

    • 获取负载均衡器

    • 使用负载均衡器从服务列表获取服务

  3. 再跟下一次,发现获取的是9091和9092之间切换

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czGi4A2n-1687953616400)(day02-SpringCloud-讲义.assets\1558060049703.png)]
  4. 通过代码断点内容判断,果然是实现了负载均衡

六、熔断器 Spring Cloud Hystrix

6.1 Hystrix 简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wa3D5DzU-1687953616400)(day02-SpringCloud-讲义.assets\hystrix-logo-tagline-640.png)]

Hystrix,英文意思是豪猪,全身是刺,刺是一种保护机制。Hystrix也是Netflix公司的一款组件。

Hystrix的作用是什么?

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBqnOLZR-1687953616401)(02-SpringCloud-讲义.assets/1564761547269.png)]

6.2 雪崩效应

  • 微服务中,一个请求可能需要多个微服务接口才能实现,会形成复杂的调用链路。
  • 如果某服务出现异常,请求阻塞,用户得不到响应,容器中线程不会释放,于是越来越多用户请求堆积,越来越多线程阻塞。
  • 单服务器支持线程和并发数有限,请求如果一直阻塞,会导致服务器资源耗尽,从而导致所有其他服务都不可用,从而形成雪崩效应;

Hystrix解决雪崩问题的手段,主要是服务降级**(兜底)**,线程隔离;

6.3 熔断案例

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

实现步骤:

  1. 引入熔断的依赖坐标
  2. 开启熔断的注解
  3. 编写服务降级处理的方法
  4. 配置熔断的策略
  5. 模拟异常代码
  6. 测试熔断服务效果

实现过程:

  1. 引入熔断的依赖坐标:

    • consumer_service中加入依赖
    <!--熔断Hystrix starter-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  2. 开启熔断的注解

    //注解简化写法:微服务中,注解往往引入多个,简化注解可以使用组合注解。@SpringCloudApplication =等同于@SpringBootApplication+@EnableDiscoveryClient+@EnableCircuitBreaker
    @SpringBootApplication
    @EnableDiscoveryClient//开启服务发现
    @EnableCircuitBreaker//开启熔断
    public class ConsumerApplication {
        @Bean
        @LoadBalanced//开启负载均衡
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApplication.class,args);
        }
    }
    
  3. 编写服务降级处理方法:使用@HystrixCommand定义fallback方法。

    @RequestMapping("{id}")
    @HystrixCommand(fallbackMethod ="queryByIdFallback")
    public String queryById(@PathVariable Long id){
        String url = String.format("http://user-service/user/%d", id);
        return restTemplate.getForObject(url,String.class);
    }
    
    public String queryByIdFallback(Long id){
        return "对不起,网络太拥挤了!";
    }
    
  4. 配置熔断策略

    1. 常见熔断策略配置
    2. 熔断后休眠时间:sleepWindowInMilliseconds
    3. 熔断触发最小请求次数:requestVolumeThreshold
    4. 熔断触发错误比例阈值:errorThresholdPercentage
    5. 熔断超时时间:timeoutInMilliseconds
    # 配置熔断策略:
    hystrix:
      command:
        default:
          circuitBreaker:
          	# 原理分析中解释配置含义
            # 强制打开熔断器 默认false关闭的。测试配置是否生效
            forceOpen: false
            # 触发熔断错误比例阈值,默认值50%
            errorThresholdPercentage: 50
            # 熔断后休眠时长,默认值5秒
            sleepWindowInMilliseconds: 5000
            # 熔断触发最小请求次数,默认值是20
            requestVolumeThreshold: 10
          execution:
            isolation:
              thread:
                # 熔断超时设置,默认为1秒
                timeoutInMilliseconds: 2000
    
  5. 模拟异常代码

    @GetMapping("{id}")
    @HystrixCommand(fallbackMethod ="queryByIdFallback")
    public String queryById(@PathVariable Long id){
        //如果参数为1抛出异常,否则 执行REST请求返回user对象
        if (id == 1){
            throw new RuntimeException("too busy!!!");
        }
        String url = String.format("http://user-service/user/%d", id);
        return restTemplate.getForObject(url,String.class);
    }
    
  6. 测试熔断的情况(模拟请求超时):

    1. 访问超时:服务提供者线程休眠超过2秒,访问消费者触发fallback方法。
    @Service
    public class UserService {
        @Autowired
        private UserMapper userMapper;
        
        public User queryById(Long id){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return userMapper.selectByPrimaryKey(id);
        }
    }
    
    1. 服务不可用:停止user-service服务提供者。访问消费者触发fallback方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fstbm6m0-1687953616401)(day02-SpringCloud-讲义.assets\1558063351797.png)]

6.4 熔断原理分析

熔断器的原理很简单,如同电力过载保护器。

熔断器状态机有3个状态:

  • 关闭状态,所有请求正常访问
  • 打开状态,所有请求都会被降级。
    • Hystrix会对请求情况计数,当一定时间失败请求百分比达到阈值,则触发熔断,断路器完全关闭
    • 默认失败比例的阈值是50%,请求次数最低不少于20次
  • 半开状态
    • 打开状态不是永久的,打开一会后会进入休眠时间(默认5秒)。休眠时间过后会进入半开状态。
    • 半开状态:熔断器会判断下一次请求的返回状况,如果成功,熔断器切回关闭状态。如果失败,熔断器切回打开状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2n6gY9DD-1687953616401)(…/%E8%AF%BE%E4%BB%B6/day02-SpringCloud-%E8%AE%B2%E4%B9%89.assets/1561680726566.png)]

​ 【Hystrix熔断状态机模型:配图】

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

  • 线程隔离。
  • 服务降级(兜底方法)。

线程隔离和服务降级之后,用户请求故障时,线程不会被阻塞,更不会无休止等待或者看到系统奔溃,至少可以看到执行结果(熔断机制)。

什么时候熔断:

  1. 访问超时
  2. 服务不可用(死了)
  3. 服务抛出异常(虽然有异常但还活着)
  4. 其他请求导致服务异常到达阈值,所有服务都会被降级

模拟异常测试:http://localhost:8080/consumer/1失败请求发送10次以上。再请求成功地址http://localhost:8080/consumer/2,发现服务被熔断,会触发消费者fallback方法。

6.5 扩展-服务降级的fallback方法:

两种编写方式:编写在类上,编写在方法上。在类的上边对类的所有方法都生效。在方法上,仅对当前方法有效。

  1. 方法上服务降级的fallback兜底方法

    • 使用HystrixCommon注解,定义
    • @HystrixCommand(fallbackMethod=“queryByIdFallBack”)用来声明一个降级逻辑的fallback兜底方法
  2. 类上默认服务降级的fallback兜底方法

    • 刚才把fallback写在了某个业务方法上,如果方法很多,可以将FallBack配置加在类上,实现默认FallBack

    • @DefaultProperties(defaultFallback=”defaultFallBack“),在类上,指明统一的失败降级方法;

    • @RestController
      @RequestMapping("/consumer")
      @DefaultProperties(defaultFallback = "defaultFallback")//开启默认的FallBack,统一失败降级方法(兜底)
      public class ConsumerController {
          @GetMapping("{id}")
          @HystrixCommand
          public String queryById(@PathVariable Long id){
              //如果参数为1抛出异常,否则 执行REST请求返回user对象
              if (id == 1){
                  throw new RuntimeException("too busy!!!");
              }
              String url = String.format("http://user-service/user/%d", id);
              return restTemplate.getForObject(url,String.class);
          }
      	/**
      	 * queryById的降级方法
      	 */
          public String queryByIdFallback(Long id){
         return "对不起,网络太拥挤了!";
          }
          /**
      	 * 默认降级方法
      	 */
          public String defaultFallback(){
              return "默认提示:对不起,网络太拥挤了!";
          }
      }
      
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ieQzVvr-1687953616402)(day02-SpringCloud-讲义.assets\1558063933774.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

TorlesseLiang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值