springCloud微服务入门


一.微服务场景模拟

首先,我们需要模拟一个服务调用的场景。方便后面学习微服务架构

为方便管理,先新建一个maven工程做为父工程(springCloud-demo)

1.1 服务提供者(user-service)

在springCloud-demo中创建模块(子工程),新建一个spring Initializr项目,对外提供查询用户的服务;

因为需要连接数据库和通过web访问,所以需要在新建模块时选择添加Web,JDBC,MyBatis,MySQL依赖;

再点击下一步,选择项目路径后点击完成;

1.1.1 pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://http://cdn.xiongsihao.com.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xsh.demo</groupId>
    <artifactId>user-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>user-service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

因为要使用通用mapper,所以我们需要手动加一条依赖:

通用mapper封装了增删改查方法,可以通过继承Mapper接口而直接调用

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

1.1.2 User实体类

@Table(name = "tb_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 用户名
    private String userName;
    // 密码
    private String password;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    // 性别,1男性,2女性
    private Integer sex;
    // 出生日期
    private Date birthday;
    // 创建时间
    private Date created;
    // 更新时间
    private Date updated;
    // 备注
    private String note;
   // 此处已省略省略getters和setters方法
}

1.1.3 mapper接口层

@Repository /*为了在service层注入该接口不报错而加上该注解,也可以不加*/
public interface UserMapper extends Mapper<User> {
    /*继承了通用Mapper接口,可直接调用简单的增删改查方法,User为指定返回的实体类*/
}

1.1.4 service服务层

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User queryUserById(long id){
        return this.userMapper.selectByPrimaryKey(id);
    }
}

1.1.5 controller控制层

@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") long id) {
        return this.userService.queryById(id);
    }
}

1.1.6 属性文件application.yml

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
mybatis:
  type-aliases-package: com.xsh.demo.domain  #指定实体类包路径

1.1.7 完成部署,启动测试

在启动类上加上@MapperScan注解指定接口包路径,方便启动类扫描该包

@MapperScan("com.xsh.demo.mapper")

启动启动类,输入网址http://localhost:8081/user/1访问成功

1571662142929

1.2 服务调用者(user-consumer)

继续在springCloud-demo中创建模块(子工程),新建一个spring Initializr项目,来调用其它接口的服务;

因为是调用user-service的功能,因此不需要mybatis相关依赖了,只需要引入Web依赖

当一个项目有多个启动类时,右下角会提示Run Dashboard,点击同意打开Run Dashboard面板方便管理

1571663459908

1.2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://http://cdn.xiongsihao.com.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xsh.demo</groupId>
    <artifactId>user-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>user-consumer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.2.2 在启动类中注册RestTemplate

@SpringBootApplication
public class UserConsumerApplication {

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

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

1.2.3 User实体类

因为此时的User实体类只是作为一个返回类型,不需要连接数据库,所以只要添加属性不需要添加相关注解

public class User {

    private Long id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;
    private Date birthday;
    private Date created;
    private Date updated;
    private String note;
    // 此处省略getter和setter方法
}

1.2.4 mapper接口

注意:这里不是调用mapper查数据库,而是通过RestTemplate远程查询user-service中的接口

@Component
public class UserMapper {

    @Autowired
    private RestTemplate restTemplate;

    public User queryUserById(Long id){
        String url = "http://localhost:8081/user/" + id;
        return this.restTemplate.getForObject(url, User.class);
    }
}

1.2.5 service服务层

在服务层对接口就行加强: 接口只实现一次查询一个,服务层加强后可一次查询多个

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<User> querUserByIds(List<Long> ids){
        List<User> users = new ArrayList<>();
        for (Long id : ids) {
            User user = this.userMapper.queryUserById(id);
            users.add(user);
        }
        return users;
    }
}

1.2.6 controller控制层

@RestController
@RequestMapping("consume")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping
    public List<User> consume(@RequestParam("ids") List<Long> ids) {
        return this.userService.querUserByIds(ids);
    }
}

1.2.7 完成部署,启动测试

在application.yml配置服务端口(不配置默认为8080),然后启动启动类

server:
  port: 8082

输入网址http://localhost:8082/consumer?ids=1,2,3访问成功

1571665366222

1.3问题

use-service:一个提供根据id查询用户的微服务

user-consumer:一个服务调用者,通过RestTemplate远程调用user-service

存在的问题:

  • 在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
  • consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效
  • consumer不清楚user-service的状态,服务宕机也不知道
  • user-service只有1台服务,不具备高可用性
  • 即便user-service形成集群,consumer还需自己实现负载均衡

上面的问题,概括一下就是分布式服务必然要面临的问题:

  • 服务管理
    • 如何自动注册和发现
    • 如何实现状态监管
    • 如何实现动态路由
  • 服务如何实现负载均衡
  • 服务如何解决容灾问题
  • 服务如何实现统一配置

二. Eureka注册中心

实现了服务的自动注册、发现、状态监控

2.1 原理图

基本架构:

201911202203

  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
  • 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态

2.2 搭建Eureka注册中心

2.2.1 创建Eureka项目

在springCloud-demo中创建模块(子工程),新建一个spring Initializr项目,名字叫eureka

选择依赖:

1571666866048

2.2.2 导入相关依赖

已在创建spring Initializr时添加无需再次导入

完整的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://http://cdn.xiongsihao.com.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xsh.demo</groupId>
    <artifactId>eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- Eureka服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <!-- SpringCloud依赖,一定要放到dependencyManagement中,起到管理版本的作用 -->
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

</project>

2.2.3 更改默认配置

server:
  port: 10086 #当前服务端口
spring:
  application:
    name: eureka-server  # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url: # EurekaServer的地址,现在是自己的地址,因为是自己作为eureka服务
      defaultZone: http://127.0.0.1:10086/eureka
    #register-with-eureka: false  # 此应用为注册中心,false:不向注册中心注册自己
    #fetch-registry: false       # 注册中心职责是维护服务实例,false:不检索服务。

2.2.4 在启动类上添加注解

@SpringBootApplication
@EnableEurekaServer // 声明这个应用是一个EurekaServer
public class EurekaApplication {

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

启动服务并访问: http://localhost:10086 可显示eureka信息

2.3 将user-service注册进Eureka

2.3.1 添加相关依赖

先添加SpringCloud依赖:

<!-- SpringCloud的依赖 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- Spring的仓库地址 -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

再添加Eureka客户端依赖:

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

2.3.2 更改默认配置

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
  application:
    name: user-service # 当前微服务名称
mybatis:
  type-aliases-package: com.xsh.demo.domain #指定实体类包路径
eureka:
  client:
    service-url: # Eureka服务地址
      defaultZone: http://127.0.0.1:10086/eureka

注意: 还添加了spring.application.name属性来指定应用名称,将来会作为应用的id使用。

2.3.3 在启动类上添加注解

@EnableDiscoveryClient // 开启EurekaClient功能

重启user-service项目,刷新eureka服务页面,发现user-service已经注册进去了

2.4 将user-consumer注册进Eureka

2.4.1 添加相关依赖

先添加SpringCloud依赖:

<!-- SpringCloud的依赖 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- Spring的仓库地址 -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

再添加Eureka客户端依赖:

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

2.4.2 更改默认配置

server:
  port: 8082
spring:
  application:
    name: consumer # 应用名称
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka

2.4.3 在启动类上添加注解

@EnableDiscoveryClient // 开启EurekaClient功能

重启user-consumer项目,刷新eureka服务页面,发现consumer也已经注册进去了

1571670618592

2.5 解决地址硬编码的问题

修改user-consumer的UseMapper方法, 用DiscoveryClient类的方法,根据服务名称,获取服务实例

@Component
public class UserMapper {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;// Eureka客户端,可以获取到服务实例信息

    public User queryUserById(Long id){
        // 根据服务名称,获取服务实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取实例
        ServiceInstance instance = instances.get(0);
        System.out.println("当前实例服务的host地址为"+instance.getHost());
        System.out.println("当前实例服务的端口为"+instance.getPort());
        System.out.println("当前实例服务的uri为"+instance.getUri());
        String url = "http://"+instance.getHost()+":"+instance.getPort()+"/user/" + id;
        return this.restTemplate.getForObject(url, User.class);
    }
}

这样就解决了地址硬编码的问题,没有直接写入地址和端口

2.6 高可用的Eureka Server

Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心(一个EurekaServer出现故障还有其它的EurekaServer)。

服务同步

多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

动手搭建高可用的EurekaServer

所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

假设要搭建两条EurekaServer的集群,端口分别为:10086和10087

先开启端口号为10086的服务,把自己注册到10087的eureka服务中

server:
  port: 10086 #当前服务端口
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10087/eureka #因为此时没有10087的eureka服务,所以会正常报错

开启了10086端口的服务后,再拷贝当前设置或新建一个设置 启动类为当前项目的启动类,命名为EurekaApplication2

1571674851997

因为10086端口的服务已在启动状态,所以此时修改application.yml文件不影响已开启的10086服务

将当前服务的端口改为10087,并注册进10086的eureka,运行EurekaApplication2启动类,实现自己注册自己

server:
  port: 10087 #当前服务端口
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

访问http://localhost:10086/ 或者 http://localhost:10087/ 都可以查看eureka监控页面,实现数据同步

1571675185945

2.7 客户端注册服务到集群

因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:

eureka:
  client:
    service-url:  # EurekaServer地址,多个地址以','隔开
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

2.8 Eureka详解

2.8.1 服务提供者

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

服务注册

服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。第一层Map的Key就是服务名称,第二层Map的key是服务的实例id。

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

有两个重要参数可以修改服务续约的行为:

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30
  • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
  • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

eureka:
  instance:
    lease-expiration-duration-in-seconds: 10 # 10秒即过期
    lease-renewal-interval-in-seconds: 5 # 5秒一次心跳

2.8.2 实例id

先来看一下服务状态信息:

在Eureka监控页面,查看服务注册信息:

1571676165840

在status一列中,显示以下信息:

  • UP(1):代表现在是启动了1个示例,没有集群
  • localhost:user-service:8081:是示例的名称(instance-id),
    • 默认格式是:${hostname} + ${spring.application.name} + ${server.port}
    • instance-id是区分同一服务的不同实例的唯一标准,因此不能重复。

我们可以通过instance-id属性来修改它的构成:

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}

2.8.3 服务消费者

获取服务列表

当服务消费者启动时,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:

eureka:
  client:
    registry-fetch-interval-seconds: 5

生产环境中,我们不需要修改这个值。

但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

2.8.4 失效剔除和自我保护

服务下线

当服务进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”,服务中心接收到请求之后,将该服务置为下线状态

失效剔除

有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生产环境不要修改。

这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如10S

自我保护

我们关停一个服务,就会在Eureka面板看到一条警告:

1525618396076

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:

eureka:
  server:
    enable-self-preservation: false # 关闭自我保护模式(默认为打开)
    eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(默认为60*1000ms)

三. Ribbon负载均衡

实际环境中,我们往往会开启很多个user-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?

一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。

不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。

什么是Ribbon:

1525619257397

3.1 启动两个服务实例

将UserServiceApplication的启动配置拷贝一个命名为UserServiceApplication2

因为UserServiceApplication已经在运行状态,所以此时修改UserServiceApplication的配置文件中的端口不影响已开启的服务;

将端口8081改为8083,并启动UserServiceApplication2;此时开启的服务提供者有8081和8083

1571751037689

3.2.开启负载均衡

因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:

在RestTemplate的配置方法上添加@LoadBalanced注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

3.3 测试负载均衡

修改调用方式user-consumer,不再手动获取服务的ip和端口,而是直接通过服务名称调用:

@Component
public class UserMapper {

    @Autowired
    private RestTemplate restTemplate;

    public User queryUserById(Long id){
        String url = "http://user-service/user/"+id;
        return this.restTemplate.getForObject(url, User.class);
    }
}

3.4 负载均衡策略

Ribbon默认的负载均衡策略是简单的轮询,就是当存在多个服务时,按顺序调用,我们可以测试一下:

拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,所以编写一个测试类 :

@SpringBootTest
@RunWith(SpringRunner.class)
public class LoadBalanceTest {
    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getUri());//打印查看调用的哪个服务
        }
    }
}

默认为轮询调用,运行结果:

1571753557621

SpringBoot也帮我们提供了修改负载均衡规则的配置入口:

user-service: #服务提供方的服务id
  ribbon:  #将负载均衡策略从默认的轮询改为随机
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 

格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的实现类。

再次运行测试类,发现调用服务结果从轮询变成了随机:

1571754096383

3.5 重试机制

Eureka为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接收故障实例也不愿丢掉健康实例,正如我们上面所说的自我保护机制。

但是,此时如果我们调用了这些不正常的服务,调用就会失败,从而导致其它服务不能正常工作!这显然不是我们愿意看到的。

现在先关闭一个user-service实例 8083,因为服务剔除的延迟,consumer并不会立即得到最新的服务列表,此时再次访问http://localhost:8082/consumer?ids=1,2,3 并刷新多次,会有几率得到错误提示,因为刚好调用的是8083的服务

1571756600114

但是此时,8081服务其实是正常的。

因此Spring Cloud 整合了Spring Retry 来增强RestTemplate的重试能力,当一次服务调用失败后,不会立即抛出一次,而是再次重试另一个服务。

只需要简单配置即可实现Ribbon的重试, 在user-consumer的配置文件中追加以下配置:

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # 开启Spring Cloud的重试功能
user-service: #服务提供者id
  ribbon:
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 10 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

根据如上配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果切换后还不行,则返回失败。切换实例次数取决于MaxAutoRetriesNextServer参数的值

还需引入spring-retry依赖,支持重试机制

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

重启user-consumer服务并重试 关闭8083服务,再次访问http://localhost:8082/consumer?ids=1,2,3 并刷新多次,发现没有错误提示信息了

四. Hystrix

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

1525658562507

4.1 雪崩问题

微服务中,服务间调用关系错宗复杂,一个请求可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:

当调用链路中的其中一个服务出现异常,请求阻塞,用户得不到响应,则tomcat的这个线程不会被释放,于是越来愈多的用户请求到来,越来越多的线程被阻塞,服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。

Hystrix解决雪崩问题的手段有两个: 1.线程隔离 2. 服务熔断

Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队加速失败判定时间;

用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?

服务降级: 优先保证核心服务,而非核心服务不可用或弱可用。

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)

服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

4.2 服务降级,熔断

4.2.1 添加相关依赖

在user-consumer的pom.xml文件中引入以下依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

4.2.2 在启动类上添加注解

在服务调用方启动类上添加注解

@EnableCircuitBreaker  //开启熔断器

4.2.3 新增服务调用方的controller层方法

增加一个controller方法来测试hystrix

@RestController
@RequestMapping("hystrix")
public class hystrixController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    @HystrixCommand(fallbackMethod = "queryUserFallback")
    public String queryUser(@RequestParam("id") Long id) {
        String url = "http://user-service/user/"+id;
        return this.restTemplate.getForObject(url, String.class);
    }
    
    public String queryUserFallback(Long id){
        return "服务器正忙,请稍后在试!";
    }
}
  • 通过@HystrixCommand注解的fallbackMethod参数来指定对应熔断方法

  • 局部一对一熔断方法的返回值和参数要和被熔断方法一致

4.2.4 测试熔断

将开启的服务提供者全部关闭,这样就模拟了服务提供者不可用的情况下,触发服务调用方的熔断方法;

当我们再次访问控制层的hystrix测试方法时,因为服务提供者已不可用,所以提示自定义的熔断方法:

1571833254880

4.2.5 全局熔断方法

@RestController
@RequestMapping("hystrix")
@DefaultProperties(defaultFallback = "fallBackMethod")
public class hystrixController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    @HystrixCommand
    public String queryUser(@RequestParam("id") Long id) {
        String url = "http://user-service/user/"+id;
        return this.restTemplate.getForObject(url, String.class);
    }
    
    public String fallBackMethod(){
        return "服务请求失败,请稍后重试!";
    }
}
  • 通过在控制层类上注解@DefaultProperties,defaultFallback的值指定全局熔断方法,这样就不用一个方法一个熔断类了,只需要在被熔断方法上加@HystrixCommand注解

  • 全局熔断方法的返回值要和被熔断方法一致,但参数列表必须为空,因为被熔断方法可能参数不一样

  • 定义了全局熔断方法后,也还可以在被熔断方法上注解 @HystrixCommand(fallbackMethod = "")

    来制定其它熔断类

4.2.6 优化

因为Ribbon重试机制默认为1000ms触发,而Hystix默认也是1000ms触发,因此重试机制没有被触发,而是先触发了熔断;

当存在部分服务提供者不可用时,访问到不可用服务,重试机制没有被触发就被熔断了,这是我们不希望看到的;

所以,Ribbon的超时时间一定要小于Hystix的超时时间,这样当访问到不可用服务时先触发重试机制,当重试后还是服务错误就触发熔断。

我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。

hystrix:
  command:
  	default:
        execution:
          isolation:
            thread:
              timeoutInMilliseconds: 6000 # 设置hystrix的熔断触发时间为6000ms

线上环境因为多台服务之间相互调用会存在网络延迟,并不是服务不可用,所以会把hystrix熔断触发时间调大一点,这样就不会在正常网络延迟情况下触发熔断

为了测试熔断触发时间,需要在服务提供方方法内加入Thread.sleep( );线程等待,防止服务提供方方法运行完毕后时间未到触发熔断而直接返回熔断,如已开启Ribbon重试机制需先关闭;

如熔断触发时间设置为6秒,我们可以在服务提供方方法内线程等待15秒,然后运行服务提供方服务,这样服务提供方方法未运行完毕而触发熔断,f12查看熔断响应时间刚好为设置的时间6000ms;

除此之外: 还能优化服务调用方user-consumer的启动类注解

//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker
@SpringCloudApplication //相当于以上3个注解
public class UserConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserConsumerApplication.class, args);
    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

4.2.7 熔断请求优化

以上案例中虽然访问到不可用服务时会触发熔断,但每次访问失败都会发送请求;

1525658640314

熔断状态机的3个状态:

  • Closed: 关闭状态,所有请求都正常访问。.
  • Open: 打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。.
  • Half Open: 半开状态, open状态不是永久的,打开后会进入休眠时间(默认是55) 。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时

模拟失败请求百分比达到阈值,修改user-consumer的controller方法,再去掉前面在user-service中加的Thread.sleep( );和将熔断触发时间改小:

@RestController
@RequestMapping("hystrix")
@DefaultProperties(defaultFallback = "fallBackMethod")
public class hystrixController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    @HystrixCommand
    public String queryUser(@RequestParam("id") Long id) throws InterruptedException {
        if(id == 1){
            throw new RuntimeException();//模拟失败请求百分比达到阈值
        }
        String url = "http://user-service/user/"+id;
        return this.restTemplate.getForObject(url, String.class);
    }
    public String fallBackMethod(){
        return "服务请求失败,请稍后重试!";
    }
}

重启服务提供方和服务调用方,连续访问http://localhost:8082/hystrix?id=1 20次以上,模拟失败请求百分比达到阈值,再访问http://localhost:8082/hystrix?id=2,发现此时id=2也被熔断了,再连续刷新id=2的请求,发现过一段时间后id=2又能正常访问,这就是熔断状态机的Half Open: 半开状态。

默认的熔断触发要求较高,休眠时间较短,为了测试方便,可以通过配置修改熔断策略:

circuitBreaker.requestVolumeThreshold=10 
circuitBreaker.sleepwindowInMilliseconds=10000 
circuitBreaker.errorThresholdPercentage=50

requestVolumeThreshold: 触发熔断的最小请求次数,默认20

sleepwindowInMilliseconds: 休眠时长,默认是5000毫秒

errorThresholdPercentage: 触发熔断的失败请求最小占比,默认50%

五. feign

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

1525652009416

5.1 引入依赖

在服务调用者的pom.xml文件中引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

5.2 在调用者启动类上添加注解

@SpringCloudApplication 
@EnableFeignClients //开启Feign功能
public class UserConsumerApplication {

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

    //因为开启了feign后不再需要RestTemplate,所以可以去掉
/*    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }*/  
}

5.3 新建Feign的客户端接口

@FeignClient("user-service")  //指定调用的服务提供方的微服务id
public interface UserClient {

    @GetMapping("user/{id}")
    public User queryById(@PathVariable("id") long id);//需要调用的方法
}
  • 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
  • @FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称
  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果

5.4 新建一个服务调用方的控制层方法

@RestController
@RequestMapping("feign")
public class feignController {

    @Autowired
    private UserClient userClient;
    @GetMapping
    @HystrixCommand
    public String queryUser(@RequestParam("id") Long id){
        return this.userClient.queryById(id).toString();//通过feign客户端调用远程服务
    }
}

5.5 测试feign

重启user-consumer访问http://localhost:8082/feign?id=2 ,能正常访问

1571846937006

5.6 feign集成Ribbon和hystrix

feign的依赖包中已经集成了Ribbon和hystrix,可以直接使用;

可以通过ribbon.xx来进行全局配置。也可以通过服务名.ribbon.xx来对指定服务配置

虽然feign集成了hystrix,但是默认是关闭的,可以通过配置开启:

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

在feign中使用hystrix的熔断方法:

(1)需要先创建一个熔断类去实现feign的客户端接口:

@Component  //将熔断类注入spring容器
public class UserClientFallback implements UserClient{
    @Override
    public User queryById(long id) {
        User user=new User();
        user.setUserName("服务器正忙,请稍后重试");
        return user;
    }
}

(2)再去客户端接口中指定该熔断类:

通过fallback来指定熔断类

@FeignClient(value = "user-service",fallback = UserClientFallback.class)
public interface UserClient {

    @GetMapping("user/{id}")
    public User queryById(@PathVariable("id") long id);//需要调用的方法
}

将服务提供方全部关闭并将hystrix的熔断触发时间调小,重启user-consumer访问http://localhost:8082/feign?id=2 可正常触发熔断

六.Zuul网关

1525675168152

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。

6.1新建Zuul模块

在项目中新建一个Spring Initializr模块,命名为zuul,并引入Spring Cloud Routing中的Zuul依赖;

1571924694794

点击下一步完成新建zuul模块;

6.2 相关依赖

因为在新建模块时已经引入zuul依赖,所以无需再引入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://http://cdn.xiongsihao.com.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xsh.demo</groupId>
    <artifactId>zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zuul</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

</project>

6.3 修改默认配置,编写路由规则

server:
  port: 10010 #服务端口
spring:
  application:
    name: Zuul #服务名
zuul:
  routes:
    user-service: #路由名称,可以随便写,习惯上写路由到哪个服务就写哪个的服务名称
      path: /user-service/** #将/user-service/**开头的请求路由到http://localhost:8081
      url: http://localhost:8081

6.4 在启动类上添加注解

@EnableZuulProxy  //启用Zuul组件

6.5 测试路由

开启zuul模块启动类,访问 http://localhost:10010/user-service/user/1

可以通过user-service路由到http://localhost:8081

1571926371136

6.6 面向服务的路由

在刚才的路由规则中,我们把路径对应的服务地址写死了,user-service固定为http://localhost:8081!如果同一服务有多个实例的话,这样做显然就不合理了。

我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!

6.6.1 Zuul集成Eureka

  1. 在zuul模块内添加Eureka客户端依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  2. 添加Eureka配置(将当前zuul服务注入到eureka)

    eureka:
      client:
        service-url:
          defaultZone: http://localhost:10086/eureka
    
  3. 修改路由规则

    zuul:
      routes:
        user-service: #路由名称,可以随便写,习惯上写路由到哪个服务就写哪个的服务名称
          path: /user-service/**  #将/user-service/**开头的请求路由到http://localhost:8081
          #url: http://localhost:8081
          serviceId: user-service
    

    将url改为serviceId实现动态路由

  4. 在zuul启动类上添加注解开启Eureka

    @EnableDiscoveryClient  //开启Eureka客户端发现功能
    

6.6.2 Zuul的四种路由

  1. 通过路径访问固定路由

    zuul:
      routes:
        user-service: #路由名称,可以随便写,习惯上写路由到哪个服务就写哪个的服务名称
          path: /user-service/** #将/user-service/**开头的请求路由到http://localhost:8081
          url: http://localhost:8081
    
  2. 通过服务id访问动态路由

    zuul:
      routes:
        user-service: #路由名称,可以随便写,习惯上写路由到哪个服务就写哪个的服务名称
          path: /user-service/**  #将/user-service/**开头的请求路由到http://localhost:8081
          serviceId: user-service
    
  3. 当路由名称和服务id名称一致时(路由名称可定义多个)

    zuul:
      routes:
        user-service: /user-service/** 
        #将/user-service/**开头的请求路由到服务id为user-service的服务
    
  4. 不配置zuul路由(默认的路由规则)

    如果不配置zuul路由,默认情况下,一切服务的映射路径就是服务名本身

    例如不配置zuul也可以通过http://localhost:10010/user-service/user/1正常访问,因为请求路径为/user-service/**,则默认路由到user-service

6.6.3 路由前缀

虽然不配置路由默认为映射路径就是服务名本身,但是一般还是选择使用第三种配置路由方法,可以使用路由前缀方便管理,分辨出哪些服务经过了路由

zuul:
  prefix: /api # 添加路由前缀
  routes:
	user-service: /user-service/** 

6.7 Zuul过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。

6.7.1.ZuulFilter

ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();
    
    boolean shouldFilter();// 来自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

6.7.2 过滤器执行生命周期

1525681866862

  • 正常流程:
    • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

6.7.3 使用场景

场景非常多:

  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
  • 服务调用时长统计:pre和post结合使用。

6.7.4 自定义过滤器类

模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。

@Component
public class LoginFilter extends ZuulFilter {

    /*返回字符串,代表过滤器的类型*/
    @Override
    public String filterType() {
        // 登录校验,肯定是在前置拦截
        return "pre";
    }

    /*执行顺序,返回值越小优先级越高*/
    @Override
    public int filterOrder() {
        return 1;
    }

    /*是否执行该过滤器*/
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /*编写过滤器的业务逻辑*/
    @Override
    public Object run() throws ZuulException {
        // 登录校验逻辑。
        // 1)获取Zuul提供的请求上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        // 2) 从上下文中获取request对象
        HttpServletRequest request = context.getRequest();
        // 3) 从请求中获取access-token
        String token = request.getParameter("access-token");
        // 4) 判断,通过StringUtils的isBlank方法判断token是否为空,如果为空返回true
        if(StringUtils.isBlank(token)){
     // 没有token,登录校验失败,拦截;setSendZuulResponse设置是否转发请求,因为要拦截所以设置为false
            context.setSendZuulResponse(false);
            // 因为浏览器只识别状态码,所以设置一个状态码;返回401状态码。也可以考虑重定向到登录页。
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            //设置响应内容
            context.setResponseBody("request error!");
        }
        // 校验通过,可以考虑把用户信息放入上下文,继续向后执行
        return null;  /*返回值为null,就代表该过滤器什么都不做*/
    }
}

6.4.5 测试自定义过滤器

重启zuul启动类,

访问http://localhost:10010/api/user-service/user/1?access-token=XXX,可正常访问

访问http://localhost:10010/api/user-service/user/1,因为请求中没有access-token参数,所以拦截器拦截,不能正常访问

6.8 zuul集成负载均衡和熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

zuul:
  retryable: true
ribbon:
  ConnectTimeout: 250 # 连接超时时间(ms)
  ReadTimeout: 2000 # 通信超时时间(ms)
  OkToRetryOnAllOperations: true # 是否对所有操作重试
  MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
  MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
  command:
  	default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 熔断超时时长:6000ms
springCloud
  • 作者:管理员(联系作者)
  • 发表时间:2019-11-21 06:32
  • 版权声明:自由转载-非商用-非衍生-保持署名(null)
  • undefined
  • 评论