SpringCloud案例准备+SCN第一代组件

1. 案例说明

  • 建立一个商品上架的微服务项目,当服务提供者上架新商品,返回商品ID给服务消费者

  • 完整业务流程图:

  

 

 

 1.1 案例数据库环境准备(使用Mysql 5.7.x

CREATE TABLE products(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50), #商品名称
price DOUBLE,
flag VARCHAR(2), #上架状态
goods_desc VARCHAR(100), #商品描述
images VARCHAR(400), #商品图片
goods_stock INT, #商品库存
goods_type VARCHAR(20) #商品类型
);

 

 

1.2 工程搭建

1.2.1 父工程 lagou-parent

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>lagou-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--父工程打包方式-->
    <packaging>pom</packaging>
    <!--spring boot 父启动器依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </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-logging</artifactId>
        </dependency>
        <!--测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--lombok工具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- Actuator可以帮助你监控和管理Spring Boot应用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!--打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

1.2.2 公共组件微服务

  • pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>lagou-parent</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>lagou-service-common</artifactId>
        <dependencies>
            <!-- 加强版的mybatis,让具体的Mapper接口继承BaseMapper即可完成相应功能-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.3.2</version>
            </dependency>
            <!--pojo持久化使用-->
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>javax.persistence-api</artifactId>
                <version>2.2</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    
    </project>
  • 实体类
package entity;


import lombok.Data;

import javax.persistence.Id;
import javax.persistence.Table;

@Data //自动生成getSet方法
@Table(name = "products")//对应数据表名称
public class Products {

  @Id //标识主键
  private long id;
  private String name;
  private double price;
  private String flag;
  private String goodsDesc;
  private String images;
  private long goodsStock;
  private String goodsType;


}

1.2.3 商品微服务

  • pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>lagou-parent</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>lagou-service-product</artifactId>
        
        <dependencies>
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>lagou-service-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
    </project>
  • yml文件配置信息
    server:
      port: 9000  # 后期该微服务多实例,9000(10个以内)
    spring:
      application:
        name: lagou-service-product
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: 123456
  • mapper接口
    package com.rf.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import entity.Products;
    
    /**
     * 现在使用的Mybatis-plus组件是Mybatis的加强版
     * 能够与SpringBoot进行非常友好的整合,对比Mybatis框架只有使用便捷的改变
     * 没有具体功能的改变
     * 具体使用:让具体的Mapper接口继承BaseMapper即可
     */
    public interface ProductMapper extends BaseMapper<Products> {
    }
  • service开发
    package com.rf.service;
    
    import entity.Products;
    
    public interface ProductService {
        /**
         * 根据ID查找商品
         * @param id
         * @return
         */
        public Products queryById(Integer id);
    }
    
    
    package com.rf.service.impl;
    
    import com.rf.mapper.ProductMapper;
    import com.rf.service.ProductService;
    import entity.Products;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ProductServiceImpl implements ProductService {
        @Autowired
        private ProductMapper productMapper;
        @Override
        public Products queryById(Integer id) {
            return productMapper.selectById(id);
        }
    }
  • controller开发
    package com.rf.controller;
    
    import com.rf.service.ProductService;
    import entity.Products;
    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;
    
    @RestController
    @RequestMapping("/product")
    public class ProductController {
        @Autowired
        private ProductService productService;
        @GetMapping("/query/{id}")
        public Products queryByID(@PathVariable Integer id){
            return productService.queryById(id);
        }
    }
  • 启动类
    package com.rf;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan("com.rf.mapper")
    public class ProductApplication {
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class,args);
        }
    }
  • 测试

 

1.2.4 页面静态微服务

  • pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>lagou-parent</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>lagou-service-page</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>lagou-service-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </project>
  • yml文件配置信息
    server:
      port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内)
    spring: 
      application:
        name: lagou-service-page 
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: 123456
  • 编写PageController,在PageController中调用商品微服务对应的URL
package com.rf.controller;

import entity.Products;
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;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/page")
public class PageController {
    @Autowired  //封装httpClient对象,执行HTTP请求
    private RestTemplate restTemplate;
    @RequestMapping("/getProduct/{id}")
    public Products getProduct(@PathVariable Integer id){
        String url ="http://localhost:9000/product/query/"; //URL地址硬编码
        Products products = restTemplate.getForObject(url + id, Products.class);
        return products;
    }

}
  • 编写启动类,注入RestTemplate
package com;

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

@SpringBootApplication
public class PageApplication {
    @Bean  //封装httpClient对象,执行HTTP请求
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(PageApplication.class,args);
    }
}
  • 测试

1.3 存在问题与解决方案

  • 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护===》服务管理:自动注册与发现、状态监管
  • 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡===》服务负载均衡
  • 在服务消费者中,不清楚服务提供者的状态===》熔断机制
  • 服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?===》远程过程调用
  • RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?==-》网关拦截、路由转发
  • 这么多的微服务统一认证如何实现?===》网关拦截、路由转发、统一认证
  • 配置文件每次都修改好多个很麻烦!?===》集中式配置管理,配置信息实时自动更新

2. 第一代 Spring Cloud 核心组件

网关组件Zuul性能一般,未来将退出Spring Cloud 生态圈,GateWay划分到第一代Spring Cloud 核心组件进行讲解

 

 

一个Feign=RestTemplate+Ribbon+Hystrix三个起到的作用

2.1 Eureka服务注册中心

  • 服务注册中心

  常见的服务注册中心包括EurekaNacosZookeeperConsul

  任何一个微服务原则上存在或支持多个服务提供者,为了支持弹性扩、缩容特性,服务提供者的数量和分布往往是动态变化的,需要引入服务注册中心管理微服务提供者的注册与发现

  分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息。

  消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由。

 

 

 

  • 步骤:
    • 服务注册中心启动,开启注册与发现功能
    • 服务提供者启动,将相关服务信息主动注册到服务注册中心
    • 服务消费者启动,获取服务注册列表信息
      • pull模式:服务消费者主动拉取可用的服务提供者清单
      • push模式:服务消费者订阅服务,当服务提供者有变化时,服务注册中心主动推送更新后的服务清单给消费者
    • 服务消费者直接调用服务提供者
    • 服务注册中心每个30s进行心跳监测,完成服务提供者的健康监控,每隔90s进行剔除检测,当发现服务提供者失效时进行及时剔除
  • 主流服务中心对比
    • Zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
      • 简单来说zookeeper本质 = 存储 + 监听通知。
      • Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用
        Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。
      • 另外,Zookeeper可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的,最少节点数为3
    • EurekaNetflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件。
    • Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件, 采用Raft算法保证服务的一致性,且支持健康检查。
    • Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
      • 简单来说Nacos 就是 注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册与发现,服务配置,服务管理等问题。
      • Nacos Spring Cloud Alibaba 核心组件之一,负责服务注册与发现,还有配置
组件名 语言 CAP 对外暴露接口
Eureka Java AP(自我保护机制,保证可用) HTTP
Consul Go CP HTTP/DNS
Zookeeper Java CP 客户端
Nacos Java 支持AP/CP切换 HTTP
  • CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。
    • P:分区容错性:分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务(一定的要满足的)
    • C:数据一致性:all nodes see the same data at the same time
    • A:高可用:Reads and writes always succeed
  • Eureka架构

 

  • Eureka交互流程

  Eureka 包含两个组件:Eureka Server Eureka ClientEureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka ClientEureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;

    • 图中us-east-1cus-east-1dus-east-1e代表不同的区也就是不同的机房
    • 图中每一个Eureka Server都是一个集群
    • 图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从Eureka
      Server中获取到服务注册信息,进行服务调用
    • 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30,默认Eureka Server90S会将还没有续约的给剔除)以续约自己的信息
    • Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
    • 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步
    • Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者(自我保护机制)
    • Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。
  • 搭建Eureka Server服务注册中心
    • 搭建Eureka Server服务 lagou-cloud-eureka ,lagou-parent中引入Spring Cloud 依赖
    <!--lagou-parent中引入Spring Cloud 依赖-->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    • lagou-cloud-eureka工程pom.xml中引入依赖
    <!--在父工程的pom文件中手动引入jaxb的jar,因为Jdk9之后默认没有加载该模块,Eureka Server使用到,所以需要手动导入,否则EurekaServer服务无法启动-->
            <!--引入Jaxb,开始-->
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-core</artifactId>
                <version>2.2.11</version>
            </dependency>
            <dependency>
                <groupId>javax.xml.bind</groupId>
                <artifactId>jaxb-api</artifactId>
            </dependency>
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-impl</artifactId>
                <version>2.2.11</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.jaxb</groupId>
                <artifactId>jaxb-runtime</artifactId>
                <version>2.2.10-b140310.1920</version>
            </dependency>
            <dependency>
                <groupId>javax.activation</groupId>
                <artifactId>activation</artifactId>
                <version>1.1.1</version>
            </dependency>
    <dependencies>
            <!--Eureka server依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    • yml文件中配置Eureka server服务端口,服务名等信息
    #Eureka server服务端口
    server:
      port: 9200
    spring:
      application:
        name: lagou-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识 (serviceId)
    eureka:
      instance:
        hostname: localhost
      client:
        service-url:  # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          register-with-eureka: false # 自己就是服务不需要注册自己,默认为true
        fetch-registry: false  #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
    • 编写启动类,声明当前服务为Eureka注册中心
    package com.rf;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer  //服务注册中心,是一个EurekaServer
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class,args);
        }
    }
    • 访问http://127.0.0.1:9200,如果看到如下页面(Eureka注册中心后台),则表明EurekaServer发布成功
    • 商品微服务和页面静态化微服务注册到Eureka
      • pom文件中添加Eureka Client依赖
<!--Eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
      • yml配置Eureka服务端信息
server:
  port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内)
spring:
  application:
    name: lagou-service-page
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456

eureka:
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${sping.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
  client:
    service-url: # eureka server的路径
      defaultZone: http://localhost:9200/eureka/
    fetch-registry: true
    register-with-eureka: true
      • 修改启动类
package com.rf;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@MapperScan("com.rf.mapper")
@EnableDiscoveryClient
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class,args);
    }
} 
  • 搭建Eureka Server 高可用集群

在生产环境中,我们会配置Eureka Server集群实现高可用。Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表。

我们开启两台 Eureka Server 以搭建集群,Eureka配置server集群时需要执行host地址,需要修改个人电脑中host地址:

127.0.0.1 LagouCloudEurekaServerA
127.0.0.1 LagouCloudEurekaServerB

lagou-cloud-eureka复制一份为lagou-cloud-eureka9201,修改 lagou-cloud-eureka-server 工程中的yml配置文件

#Eureka server服务端口
server:
  port: 9200
spring:
  application:
    name: lagou-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识 (serviceId)
eureka:
  instance:
    hostname: LagouCloudEurekaServerA
  client:
    service-url:  # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
      defaultZone: http://LagouCloudEurekaServerB/9201/eureka/
    fetch-registry: false  #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
    register-with-eureka: false # 自己就是服务不需要注册自己,默认为true

9201

#Eureka server服务端口
server:
port: 9201
spring:
application:
name: lagou-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识
(serviceId)
eureka:
instance:
hostname: LagouCloudEurekaServerB
client:
register-with-eureka: true
fetch-registry: true
serviceUrl:
defaultZone: http://LagouCloudEurekaServerA:9200/eureka

商品微服务和页面静态微服务

server:
  port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内)
spring:
  application:
    name: lagou-service-page
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456

eureka:
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
  client:
    service-url: # eureka server的路径
      #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server  可以同步注册表
      defaultZone: http://LagouCloudEurekaServerA:9200/eureka/,http://LagouCloudEurekaServerB:9201/eureka/
    fetch-registry: true
    register-with-eureka: true

改造页面静态化微服务:之前是直接通过RestTemplate写死URL进行调用,现在通过Eureka方式进行调

package com;

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;

@SpringBootApplication
//@EnableEurekaClient  说明是EurekaClient,不能支持其他的服务注册中心
@EnableDiscoveryClient  //说明是服务注册中心的客户端,可以支持不同的服务注册中心
public class PageApplication {
    @Bean  //封装httpClient对象,执行HTTP请求
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(PageApplication.class,args);
    }
}
  • Eureka元数据有两种:标准元数据和自定义元数据。
    • 标准元数据:主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。

 

    • 自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问。

    

package com.rf.controller;


import entity.Products;
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;
import java.util.Map;
import java.util.Set;

@RestController
@RequestMapping("/page")
public class PageController {
    @Autowired  //封装httpClient对象,执行HTTP请求
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/getProduct/{id}")
    public Products getProduct(@PathVariable Integer id){
        List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product");  //集群中服务会有多个
        ServiceInstance serviceInstance = instances.get(0);
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        String url="http://"+host+":"+port+"/product/query/";
        // String url ="http://localhost:9000/product/query/";  //URL地址硬编码
        Products products = restTemplate.getForObject(url + id, Products.class);
        Map<String, String> metadata = serviceInstance.getMetadata();
        Set<Map.Entry<String, String>> entries = metadata.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }
        return products;
    }

}
server:
  port: 9000  # 后期该微服务多实例,9000(10个以内)
spring:
  application:
    name: lagou-service-product
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/smd?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456
eureka:
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    metadata-map:
      ip: 192.168.200.128
      port: 10000
      user: rf
      pwd: 123456
  client:
    service-url: # eureka server的路径
      #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server  可以同步注册表
      defaultZone: http://LagouCloudEurekaServerA:9200/eureka/,http://LagouCloudEurekaServerB:9201/eureka/
    fetch-registry: true
    register-with-eureka: true

    

2.2 Ribbon负载均衡

  • 负载均衡一般分为服务端负载均衡和客户端负载均衡
    • 服务端负载均衡:比如NginxF5这些,请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理
    • 客户端负载均衡:比如Ribbon,服务消费者客户端会有一个服务器地址列表,调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行
  • Ribbon:Netflix发布的负载均衡器Eureka一般配合Ribbon进行使用,Ribbon利用从Eureka中读取到服务信息,在调用服务提供者提供的服务时,会根据一定的算法进行负载
    • 复制商品微服务9001,在90009001编写Controller,返回服务实例端口,Page微服务中通过负载均衡策略调用lagou-service-productcontroller
    • 在微服务中使用Ribbon不需要额外导入依赖坐标,微服务中引入过eureka-client相关依赖,会自动引入Ribbon相关依赖坐标,可以直接RestTemplate上添加对应注解
    package com;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    //@EnableEurekaClient  说明是EurekaClient,不能支持其他的服务注册中心
    @EnableDiscoveryClient  //说明是服务注册中心的客户端,可以支持不同的服务注册中心
    
    public class PageApplication {
        @Bean  //封装httpClient对象,执行HTTP请求
        @LoadBalanced  //开启Ribbon负载均衡
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(PageApplication.class,args);
        }
    }
    • 创建lagou-serivce-product-9001微服务,创建ServerConfigController,定义方法返回当前微服务所使用的容器端口号
    package com.rf.controller;
    
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/service")
    public class ServerConfigController {
    
        @Value("${server.port}")
        private String port;
    
        @RequestMapping("/getPort")
        public String getProductServicePort(){
            return port;
        }
    
    }
    • 修改服务提供者api返回值,返回当前实例的端口号,便于观察负载情况
        @RequestMapping("/getPort")
        public String getProductServerPort(){
            String url = "http://lagou-service-product/service/getPort";
            return restTemplate.getForObject(url,String.class);
        }
  • Ribbon负载均衡策略
    • Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为 com.netflix.loadbalancer.IRule
    package com.netflix.loadbalancer;
    /**
    * Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
    * as a Strategy for loadbalacing. Well known loadbalancing strategies include
    * Round Robin, Response Time based etc.
    * *
    @author stonse
    * *
    /
    public interface IRule{
    /*
    * choose one alive server from lb.allServers or
    * lb.upServers according to key
    * *
    @return choosen Server object. NULL is returned if none
    * server is available
    */
    public Server choose(Object key);
    public void setLoadBalancer(ILoadBalancer lb);
    public ILoadBalancer getLoadBalancer();
    }

     

     

     

负载均衡策略 描述
RoundRobinRule:轮询策略 默认超过10次获取到的server都不可用,会返回一个空的server
RandomRule:随机策略 如果随机到的servernull或者不可用的话,会while不停的循环选取
RetryRule:重试策略 一定时限内循环重试。默认继承RoundRobinRule,也支持自定义注入,RetryRule会在每次选取之后,对选举的server进行判断,是否为null,是否alive,并且在500ms内会不停的选取判断。RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂。
BestAvailableRule:最小
连接数策略
遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的运行状况和连接数。如果选取到的servernull,那么会调用RoundRobinRule重新选取。
AvailabilityFilteringRule
可用过滤策略
扩展了轮询策略,会先通过默认的轮询选取一个server,再去判断该server是否超时可用,当前连接数是否超限,都成功再返回。
ZoneAvoidanceRule:区
域权衡策略
(默认策略)
扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicateAvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域里面的所有节点, 在一个区域/机房内的服务实例中轮询。先过滤再轮询
#针对的被调用方微服务名称,不加就是全局生效
lagou-service-product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机策略
  • Ribbon核心源码剖析

    • spring.factories配置文件

     

  • 添加了注解的RestTemplate对象会被添加一个拦截器LoadBalancerInterceptor,该拦截器就是后续拦截请求进行负载处理的。

 

  •  服务雪崩与解决方案

    是一种容错机制,微服务中,一个请求可能需要多个微服务接口才能实现,服务雪崩效应是指一种因“服务提供者的不可用”原因导致服务调用者也不可以,并将这种不可用现象逐渐放大。

 

 

    扇入:该微服务被调用的次数,扇入大,说明该模块复用性好

    扇出:该微服务调用其他微服务的个数,扇出大,说明业务逻辑复杂

    一般要求扇入大,而扇出小

    在微服务架构中,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的扇出。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的雪崩效应

    服务雪崩的原因和过程:服务提供者不可用;重试加大请求流量;服务调用者不可用

解决方案:

    服务熔断:熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,熔断该节点微服务的调用,进行服务的降级,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路,即直接切断对下游服务的调用

    服务降级:先将一些不关紧的服务停掉,返回兜底数据fallback待渡过难关高峰过去,再把那些服务打开。

    服务限流:针对特殊场景,比如秒杀业务这样的核心功能,结合服务限流来限制这些场景的并发/请求量限流措施也很多,比如限制总并发数(比如数据库连接池、线程池);限制瞬时并发数(如nginx限制瞬时并发连接数)
限制时间窗口内的平均速率(如GuavaRateLimiternginxlimit_req模块,限制每秒的平均速率);限制远程接口调用速率、限制MQ的消费速率等

  • Hystrix(熔断器)

  是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

    • 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑。 页面静态化微服务方法(@HystrixCommand 添加Hystrix控制)
    • 跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间。
    • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)。如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。
    • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
    • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
    • 自我修复:断路器打开一段时间后,会自动进入半开状态(探测服务是否可用,如还是不可用,再次退回打开状态)。  

 

 

熔断处理应用:
  目的:商品微服务长时间没有响应,服务消费者—>页面静态化微服务快速失败给用户提示

    • 引入依赖:服务消费者工程(静态化微服务)中引入Hystrix依赖坐标开启熔断
<!--熔断器Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency
    • 服务消费者工程(静态化微服务)的启动类中添加熔断器开启注解@EnableCircuitBreaker
     
  1. package com;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * 注解简化写法
     * @SpringCloudApplication =
     @SpringBootApplication+@EnableDiscoveryClient+@EnableCircuitBreaker
     */
    @SpringBootApplication
    //@EnableEurekaClient  说明是EurekaClient,不能支持其他的服务注册中心
    @EnableDiscoveryClient  //说明是服务注册中心的客户端,可以支持不同的服务注册中心
    @EnableCircuitBreaker
    public class PageApplication {
        @Bean  //封装httpClient对象,执行HTTP请求
        @LoadBalanced  //开启Ribbon负载均衡
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(PageApplication.class,args);
        }
    }
    • 定义服务降级处理方法:业务方法上使用@HystrixCommandfallbackMethod属性关联到服务降级处理方法,降级处理:配置@HystrixCommand注解,定义降级处理方法
    package com.rf.controller;
    
    
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
    import entity.Products;
    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;
    import java.util.Map;
    import java.util.Set;
    
    @RestController
    @RequestMapping("/page")
    public class PageController {
        @Autowired  //封装httpClient对象,执行HTTP请求
        private RestTemplate restTemplate;
    
        @Autowired
        private DiscoveryClient discoveryClient;/**
         * 提供者模拟处理超时,调用方法添加Hystrix控制
         */
        // 使用@HystrixCommand注解进行熔断控制
        @HystrixCommand(
                // 线程池标识,要保持唯一,不唯一的话就共用了
                threadPoolKey = "getProductPort2",
                threadPoolProperties = {
                        // 线程池细节属性配置
                        @HystrixProperty(name = "coreSize", value = "1"),//线程数
                        @HystrixProperty(name = "maxQueueSize", value = "10") //最大等待队列数
                },
                // commandProperties熔断的一些细节属性配置
                commandProperties = {
                        //超时时间 2 秒
                        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
                        // hystrix高级配置,定制工作过程细节
                        // 统计时间窗口定义
                        @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
                        // 统计时间窗口内的最小请求数
                        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
                        // 统计时间窗口内的错误数量百分比阈值
                        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                        // 自我修复时的活动窗口长度
                        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
                }, fallbackMethod = "myfallback"
        )
    
        @RequestMapping("/getPort2")
        public String getProductServerPort2() {
            String url = "http://lagou-service-product/service/getPort2";
            return restTemplate.getForObject(url, String.class);
        }
    
        //    * 定义回退方法,返回预设默认值
    //* 注意:该方法形参和返回值与原始方法保持一致
        public String myfallback() {
            return "-1"; //兜底数据
        }
    }
    • Hystrix舱壁模式即:线程池隔离策略如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),如果方法A的请求把10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用。
    • 为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数,而是单独的为每一个控制方法创建一个线程池的方式,这种模式叫做舱壁模式",也是线程隔离的手段。
      • 当调用出现问题时,开启一个时间窗(10s
      • 在这个时间窗内,统计调用次数是否达到最小请求数
      • 如果没有达到,则重置统计信息,回到第1步;如果达到了,则统计失败的请求数占所有请求数的百分比,是否达到阈值
      • 如果达到,则跳闸(不再请求对应服务);如果没有达到,则重置统计信息,回到第1
      • 如果跳闸,则会开启一个活动窗口(默认5s),每隔5sHystrix会让一个请求通过,到达那个问题服务,看是否调用成功,如果成功,重置断路器回到第1步,如果失败,回到第3
  // hystrix高级配置,定制工作过程细节
                    // 统计时间窗口定义
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
                    // 统计时间窗口内的最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
                    // 统计时间窗口内的错误数量百分比阈值
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                    // 自我修复时的活动窗口长度
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")

可以通过配置文件的方式定义hystrix属性

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值5秒
        sleepWindowInMilliseconds: 3000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 2
    execution:
      isolation:
        thread:
          # 熔断超时设置,默认为1秒
          timeoutInMilliseconds: 2000

# springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        include: "*"
        # 暴露健康接口的细节
  endpoint:
    health:       show-details: always

Hystrix还有一个queueSizeRejectionThreshold属性,这个属性是控制队列最大阈值的,而Hystrix默认只配置了5个,不论maxQueueSize的值设置再大,也是不起作用的。因此两个属性必须同时配置

 

  • Feign远程调用组件

    FeignNetflix开发的一个轻量级RESTfulHTTP服务客户端(用它来发起请求,远程调用的)是以Java接口注解的方式调用Http请求,不需要通过封装HTTP请求报文的方式直接调用,不需要去拼接url,调用restTemplateapiSpringCloudFeign进行了增强,使Feign支持了SpringMVC注解(OpenFeign),在服务调用者工程(消费)创建接口(添加注解)(效果)Feign = RestTemplate+Ribbon+Hystrix

    • 服务消费者工程(页面静态化微服务)中引入Feign依赖(或者父类工程)
         <!-- openfeign-->
            <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    • 服务消费者工程(静态化微服务)启动类使用注解@EnableFeignClients添加Feign支持此时去掉Hystrix熔断的支持注解@EnableCircuitBreaker即可包括引入的依赖,因为Feign会自动引入
    package com;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * 注解简化写法
     * @SpringCloudApplication =
     @SpringBootApplication+@EnableDiscoveryClient+@EnableCircuitBreaker
     */
    @SpringBootApplication
    //@EnableEurekaClient  说明是EurekaClient,不能支持其他的服务注册中心
    @EnableDiscoveryClient  //说明是服务注册中心的客户端,可以支持不同的服务注册中心
    //@EnableCircuitBreaker //熔断器hystrix
    @EnableFeignClients// 开启Feign
    public class PageApplication {
     public static void main(String[] args) {
            SpringApplication.run(PageApplication.class,args);
        }
    }
    • 在消费者微服务中创建Feign
package com.rf.feign;
import entity.Products;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "lagou-service-product")
public interface ProductFeign {
     /**
     * 通过id获取商品信息
     * @param id
     * @return
     */
    @RequestMapping("/product/query/{id}")
    public Products queryByID(@PathVariable Integer id);
    /**
     * 获得端口号
     * @return
     */
    @RequestMapping("/service/getPort")
    public String getProductServicePort();
}
      • 注意:@FeignClient注解的name属性用于指定要调用的服务提供者名称,和服务提供者yml文件中spring.application.name保持一致
      • 接口中的接口方法,就好比是远程服务提供者Controller中的Handler方法(只不过如同本地调用了),在进行参数绑定的时,可以使用@PathVariable@RequestParam@RequestHeader等,这也是OpenFeignSpringMVC注解的支持,但是需要注意value必须设置,否则会抛出异常
      • @FeignClient(name = "lagou-service-product")name在消费者微服务中只能出现一次。(升级Spring Boot 2.1.0 Spring Cloud Greenwich.M1 版本后,在2Feign接口类内定义相同的名字,@FeignClient(name = 相同的名字 就会出现报错,在之前的版本不会提示报错),所以最好将调用一个微服务的信息都定义在一个Feign接口中
      package com.rf.controller;
      
      
      //import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
      //import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
      import com.rf.feign.ProductFeign;
      import entity.Products;
      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;
      import java.util.Map;
      import java.util.Set;
      
      @RestController
      @RequestMapping("/page")
      public class PageController {
      //    @Autowired  //封装httpClient对象,执行HTTP请求
      //    private RestTemplate restTemplate;
          @Autowired
          private ProductFeign productFeign;
      
      //    @Autowired
      //    private DiscoveryClient discoveryClient;
      
          @GetMapping("/getProduct/{id}")
          public Products getProduct(@PathVariable Integer id) {
      //        List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product");  //集群中服务会有多个
      ////        ServiceInstance serviceInstance = instances.get(0);
      ////        String host = serviceInstance.getHost();
      ////        int port = serviceInstance.getPort();
      ////        String url = "http://" + host + ":" + port + "/product/query/";
      ////        // String url ="http://localhost:9000/product/query/";  //URL地址硬编码
      ////        Products products = restTemplate.getForObject(url + id, Products.class);
              Products products = productFeign.queryByID(id);
      //        Map<String, String> metadata = serviceInstance.getMetadata();
      //        Set<Map.Entry<String, String>> entries = metadata.entrySet();
      //        for (Map.Entry<String, String> entry : entries) {
      //            System.out.println(entry.getKey() + ":" + entry.getValue());
      //        }
              return products;
          }
      
          @RequestMapping("/getPort")
          public String getProductServerPort() {
      //        String url = "http://lagou-service-product/service/getPort";
      //        return restTemplate.getForObject(url, String.class);
              return productFeign.getProductServicePort();
          }
      
      
          /**
           * 提供者模拟处理超时,调用方法添加Hystrix控制
           */
          // 使用@HystrixCommand注解进行熔断控制
      //    @HystrixCommand(
      //            // 线程池标识,要保持唯一,不唯一的话就共用了
      //            threadPoolKey = "getProductPort2",
      //            threadPoolProperties = {
      //                    // 线程池细节属性配置
      //                    @HystrixProperty(name = "coreSize", value = "1"),//线程数
      //                    @HystrixProperty(name = "maxQueueSize", value = "10") //最大等待队列数
      //            },
      //            // commandProperties熔断的一些细节属性配置
      //            commandProperties = {
      //                    //超时时间 2 秒
      //                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
      //                    // hystrix高级配置,定制工作过程细节
      //                    // 统计时间窗口定义
      //                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
      //                    // 统计时间窗口内的最小请求数
      //                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
      //                    // 统计时间窗口内的错误数量百分比阈值
      //                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
      //                    // 自我修复时的活动窗口长度
      //                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
      //            } , fallbackMethod = "myfallback"
      //    )
      //
      //    @RequestMapping("/getPort2")
      //    public String getProductServerPort2() {
      //        String url = "http://lagou-service-product/service/getPort";
      //        return restTemplate.getForObject(url, String.class);
      //    }
      //
      //    //    * 定义回退方法,返回预设默认值
      ////* 注意:该方法形参和返回值与原始方法保持一致
      //    public String myfallback() {
      //        return "-1"; //兜底数据
      //    }
      }
    • Feign对负载均衡的支持:Feign 本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,可以通过ribbon.xx 来进行全局配置,也可以通过服务名.ribbon.xx 来对指定服务进行细节配置,Feign默认的请求处理超时时长1s,有时候我们的业务确实执行的需要一定时间,那么这个时候,我们就需要调整请求处理超时时长,Feign自己有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准
    #针对的被调用方微服务名称,不加就是全局生效
    lagou-service-product:
      ribbon:
        #请求连接超时时间
        #ConnectTimeout: 2000
        #请求处理超时时间
        #ReadTimeout: 5000
        #对所有操作都进行重试
        OkToRetryOnAllOperations: true
        ####根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
        MaxAutoRetries: 0
        ####如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),
        MaxAutoRetriesNextServer: 0
        ####如果依然不行,返回失败信息。
        # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机策略
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    • Feign对熔断器的支持:
      • Feign客户端工程配置文件(application.yml)中开启Feign对熔断器的支持
      • Feign的超时时长设置遵循Ribbon的超时时长设置
      #开启对熔断器的支持
      feign:
        hystrix:
          enabled: true
      • Hystrix超时设置(就按照之前Hystrix设置的方式就OK了)
      hystrix:
       command:
        default:
         circuitBreaker:
            # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
          forceOpen: false
            # 触发熔断错误比例阈值,默认值50%
          errorThresholdPercentage: 50
          # 熔断后休眠时长,默认值5秒
          sleepWindowInMilliseconds: 3000
            # 熔断触发最小请求次数,默认值是20
          requestVolumeThreshold: 2
         execution:
          isolation:
            thread:
              # 熔断超时设置,默认为1秒
           timeoutInMilliseconds: 2000
      • 开启Hystrix之后,Feign中的方法都会被进行一个管理了,一旦出现问题就进入对应的回退逻辑处理
      • 针对超时这一点,当前有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最小值来进行的,即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑
      package com.rf.feign;
      
      import entity.Products;
      import org.springframework.stereotype.Component;
      
      @Component
      public class ProductFeignFallBack implements ProductFeign{
          @Override
          public Products queryByID(Integer id) {
              return null;
          }
      
          @Override
          public String getProductServicePort() {
              return "-1";
          }
      }
      package com.rf.feign;
      
      
      import entity.Products;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      @FeignClient(name = "lagou-service-product",fallback = ProductFeignFallBack.class)
      public interface ProductFeign {
           /**
           * 通过id获取商品信息
           * @param id
           * @return
           */
          @RequestMapping("/product/query/{id}")
          public Products queryByID(@PathVariable Integer id);
          /**
           * 获得端口号
           * @return
           */
          @RequestMapping("/service/getPort")
          public String getProductServicePort();
      }
    • Feign对请求压缩和响应压缩的支持:Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能
    #开启对熔断器的支持
    feign:
      hystrix:
        enabled: true
      compression:
        request:
          enabled: true  #默认不开启
          mime-types: text/xml, application/xml, application/json # 默认设置压缩的数据类型
          min-request-size: 2048  # 默认设置触发压缩的大小下限
        response:
          enabled: false #m默认不开启

     



 

 

posted @ 2021-08-17 21:20  forever_fate  阅读(92)  评论(0)    收藏  举报