SpringCloud+Netflix(Eureka+Ribbon+Hystix)入门教程+demo(一)

1.系统架构演变

1.1集中式架构

当网站流量小时,只需一个应用,将所有功能部署在一块。
在这里插入图片描述
在这里插入图片描述
问题:

  • 代码耦合度高,开发维护困难。
  • 无法针对不同模块进行针对性优化。
  • 无法水平扩展。
  • 单点容错率低,并发能力差。

1.2垂直拆分

按照功能区划分,把不同的功能模块独立出来。
在这里插入图片描述
优点:

  • 系统拆分实现了流量分担,解决了并发问题。
  • 可以针对不同模块进行优化。
  • 方便水平扩展,负载均衡,容错率提高

缺点:

  • 系统间相互独立,会有很多重复开发工作,影响开发效率

1.3分布式服务

服务间的调用,服务间首先相互独立,其次相互调用。

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
在这里插入图片描述
优点:

  • 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率。

缺点:

  • 系统间耦合度变高,调用关系错综复杂,难以维护。

1.4流动计算架构(SOA)

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

在这里插入图片描述
服务治理要做什么?

  • 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
  • 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
  • 动态监控服务状态监控报告,人为控制服务状态

缺点:

  • 服务间会有依赖关系,一旦某个环节出错会影响较大。
  • 服务关系复杂,运维、测试部署困难,不符合DevOps思想。

1.5微服务

微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责。
  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  • 面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 自治:自治是说服务间互相独立,互不干扰。
  • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
  • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉。
  • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口。
  • 数据库分离:每个服务都使用自己的数据源。
  • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护。
    在这里插入图片描述

2.服务调用方式

2.1RPC和HTTP

  • RPC:远程过程调用,类似还有RMI(远程方法调用)。基于原生TCP通信。典型代表框架:dubbo、webservice。
  • HTTP:基于TCP,规定了数据传输的格式。缺点是消息封装囊肿,优点是灵活。
    现在热门的Rest风格,就可以通过http协议来实现。

如果公司都采用java技术性,那么使用dubbo作为服务架构是不错的。
如果技术栈多样化,那么SpringCloud搭建微服务是不二之选。

2.2Http客户端工具

java代码间调用,主流客户端:

  • HttpClient(Apache的)
  • OKHttp
  • HttpUrlConnection
    不同客户端,API各不相同。

2.3Spring的RestTemplate

Spring提供了一个RestTemplate模板工具类,对于Http的客户端进行了封装,并实现了对象与json的序列化和反序列化。
RestTemplate并没有限定Http的客户端类型,而是进行了抽象。目前常用的3中都有支持:

  • HttpClient
  • OKHttp
  • JDK原生的HttpUrlConnection(默认的)

2.3Spring的RestTemplate

2.3.1新导入demo工程

2.3.2编写测试类

先在项目中注册一个RestTemplate对象,可以在启动类位置注册:

package com.leyou.httpdemo;

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class HttpDemoApplication {

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

	@Bean
	public RestTemplate restTemplate() {
		//默认的HtppUrlConnection,将Template注册到Spring
		return new RestTemplate();
	}
}

在测试类中直接注入

package com.leyou.httpdemo;

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

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {

	@Autowired
	private RestTemplate restTemplate;

	@Test
	public void httpGet() {
		User user = this.restTemplate.getForObject("http://localhost:8088/user/16", User.class);
		System.out.println(user);
	}

}

通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接受相应,并且帮我们对相应结果进行反序列化。
注意:这个项目和要引用的项目接口要保证tomcat端口不同,否则会报错端口被占用。

3.初始SpringCloud

3.1简介

Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中,
SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:

Netflix

  • Eureka:注册中心
  • Zuul:服务网关
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystix:熔断器

架构图:
在这里插入图片描述

4.微服务场景模拟

4.1创建父工程

4.1.1创建一个maven工程cloud-demo

4.1.2引入依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
        <mapper.starter.version>2.0.3</mapper.starter.version>
        <mysql.version>5.1.32</mysql.version>
        <pageHelper.starter.version>1.2.5</pageHelper.starter.version>
    </properties>
    <!-- dependency管理器中所有子工程都有该依赖-->
<dependencyManagement>
    <dependencies>
        <!--springcloud版本依赖项目-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--通用mapper启动器 -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>${mapper.starter.version}</version>
        </dependency>
        <!-- mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

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

4.2服务提供者

4.2.1新建一个子工程(Module)

假设该工程对外提供查询用户的服务。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2.2引入依赖

    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>

    <dependencies>
        <!-- Spring工程的web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

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>cn.itcast</groupId>
    <artifactId>cloud-demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>user-service</module>
    </modules>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
        <mapper.starter.version>2.0.3</mapper.starter.version>
        <mysql.version>5.1.32</mysql.version>
        <pageHelper.starter.version>1.2.5</pageHelper.starter.version>
    </properties>
    <!-- dependency管理器中所有子工程都有该依赖-->
    <dependencyManagement>
        <dependencies>
            <!--springcloud版本依赖项目-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--通用mapper启动器 -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mapper.starter.version}</version>
            </dependency>
            <!-- mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

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

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>cloud-demo</artifactId>
        <groupId>cn.itcast</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>

    <dependencies>
        <!-- Spring工程的web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>


</project>

4.2.3编写配置文件application.yml

server:
  port: 8081
spring:
  datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost/yum6
      username: root
      password: 123456
mybatis:
  type-aliases-package: cn.itcast.user.pojo

创建cn.itcast.user.pojo包

4.2.4编写代码

4.2.4.1启动类

包:cn.itcast
类:UserApplication

package cn.itcast;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("cn.itcast.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class);
    }
    
}

4.2.4.2实体类

包:cn.itcast.user.pojo
类:User

package cn.itcast.user.pojo;

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Data
@Table(name="tb_user")
public class User {
    //id
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    //用户名
//    @Column(name = "username")
    private String username;
    //密码
    private String password;
    private String phone;
    private Date created;

}

4.2.4.3Mapper

包:cn.itcast.user.mapper
类:UserMapper
使用通用Mapper

package cn.itcast.user.mapper;

import cn.itcast.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}

4.2.4.4Service

包:cn.itcast.user.service
类:UserService

package cn.itcast.user.service;

import cn.itcast.user.mapper.UserMapper;
import cn.itcast.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public User queryById(Long id){
        return userMapper.selectByPrimaryKey(id);
    }
}

4.2.4.5Controller

包:cn.itcast.user.controller
类:UserController

package cn.itcast.user;

import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
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("user")
public class Controller {
    @Autowired
    private UserService userService;

    //@PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id){
        return userService.queryById(id);
    }
}

4.2.4.6测试

http://localhost:8081/user/16访问成功!!
在这里插入图片描述

4.3服务调用者

4.3.1创建工程

在这里插入图片描述

4.3.2引入依赖

该项目作为调用者,不需要再访问数据库了,他是调用服务方user-service。所以在引入依赖时只需要引入web启动器就可以了。
加入依赖:

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

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>cloud-demo</artifactId>
        <groupId>cn.itcast</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer-demo</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

4.3.3编写代码

4.3.3.1启动类

包:cn.itcast
类:ConsumerApplication

package cn.itcast;

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 ConsumerApplication {
    //需要调用,所以需要这个对象
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

4.3.3.2实体类

因为不需要数据库了,所以实体类中不需要@Table、@Id等一些通用Mapper映射注解了。

package cn.itcast.consumer.pojo;

import lombok.Data;
import java.util.Date;

@Data
public class User {
    //id
    private Long id;
    //用户名
//    @Column(name = "username")
    private String username;
    //密码
    private String password;
    private String phone;
    private Date created;

}

4.3.3.3Controller

包:cn.itcast.consumer.web
类:ConsumerController

package cn.itcast.consumer.web;

import cn.itcast.consumer.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        String url = "http://localhost:8081/user/"+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }
}

4.3.4测试

http://localhost:8080/consumer/17访问成功!!
在这里插入图片描述

4.4流程图

在这里插入图片描述

4.5这样调用有没有问题?

服务的调用中地址写死了。若是发生服务迁移或地址被占用,就无法调用了。若是部署集群,写死了就只访问一个,无法访问其他的。

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

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

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

5.Eureka注册中心

5.1原理图

在这里插入图片描述

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

注:消费者是拉取服务列表,而不是注册中心向消费者推送服务列表。

5.2 入门案例

5.2.1创建项目

创建项目:
在这里插入图片描述

5.2.2引入依赖

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

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>cloud-demo</artifactId>
        <groupId>cn.itcast</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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


</project>

该依赖已经将Eureka服务的注册发现等所有功能都实现了。

5.2.3配置application.yml

因为前面user-service占用端口8081;
consumer-service占用端口8080;
所以想要启动该项目,需要为它配置端口,比如配置为10086。

server:
  port: 10086

5.2.4编写EurekaServer启动类

包:cn.itcast
类: EurekaServer

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer.class);
    }
}

@EnableEurekaServer:启动Eureka的服务。

5.2.5测试

http://localhost:10086/
会出现以下问题:

问题一:

启动时报错,可以忽略,它是可以进入注册中心。但是进入后也一直在报错。

注意:
作为一个注册中心,也不能挂,也应该搭建成集群。所以他即是一个服务器,但其实也是客户端。

原因:
此时没有人注册,所以会报错。
解决方案:
要设置自己注册自己。
在application.yml中添加如下配置:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

再次启动:
访问http://localhost:10086/

问题二:

在这里插入图片描述
问题:服务名未知
解决方案:在application.yml配置中添加如下:

spring:
  application:
    name: eureka-server

访问http://localhost:10086/
在这里插入图片描述

问题三:

出现的不是ip
在这里插入图片描述
解决方法:
在application.yml中添加如下配置

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

5.4修改项目

5.4.1服务方:user-service子项目

5.4.1.1引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>

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>cloud-demo</artifactId>
        <groupId>cn.itcast</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>

    <dependencies>
        <!-- Spring工程的web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
    </dependencies>
</project>

5.4.1.2修改启动类UserApplication

在类上添加注解**@EnableDiscoveryClient**

为什么使用@EnableDiscoveryClient?

答:现在我们使用的注册中心是Eureka,但是springcloud内置的注册中心不止一种,它不仅支持Eureka,也支持zookeper等好几种。当添加注解是@@EnableEurekaClient的时候,将来只能注册到Eureka注册中心。但是用@EnableDiscoveryClient注解,更加通用,即注解的对应服务也是客服端,但是内部既能兼容Eureka,也能兼容Zookeper等。

package cn.itcast;


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

@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("cn.itcast.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class);
    }
}

5.4.1.3修改application.yml

完整的application.yml内容:

server:
  port: 8081
spring:
  application:
    name: user-service
  datasource:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost/yum6
      username: root
      password: 123456
mybatis:
  type-aliases-package: cn.itcast.user.pojo

eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka

5.4.1.3测试

启动user-service
访问http://localhost:10086/
在这里插入图片描述

5.4.2调用方:consumer-service子项目

5.4.2.1引入依赖

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

5.4.2.2在启动类上加注解

@EnableDiscoveryClient

package cn.itcast;

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;
//启动类
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
    //需要调用,所以需要这个对象
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

5.4.2.2添加配置application.yml

将该项目改为8088

server:
  port: 8088
spring:
  application:
    name: consumer-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

5.4.2.3测试

在这里插入图片描述
在这里插入图片描述

5.5消费者动态从Eureka获取服务

因为大家都是客户端,所以服务的提供者也有可能消费者,服务的消费者也有可能是提供者。
服务者可能会部署成集群,开启很多个tomcat,但是这些服务者的服务名都是一样的,所以在不同tomcat上部署的同一个服务就叫做服务的实例。

5.5.1添加依赖

若pom.xml不存在下面依赖,就添加进去。

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

5.5.2编写测试类

package cn.itcast.consumer.web;

import cn.itcast.consumer.pojo.User;
import com.netflix.appinfo.InstanceInfo;
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;

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

    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        //从实例中取出ip和端口
        String url = instance.getHost();
        int port = instance.getPort();
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
}

此处测试只有一个服务,所以直接instances.get(0)。但是以后需要些负载均衡算法。

5.5.3测试

http://localhost:8088/consumer/19访问成功!
在这里插入图片描述

5.6Eureka详解

5.6.1基础架构

EurekaServer也是可以是一个集群,形成高可用的Eureka中心。
说个Eureka Server之间会互相注册为服务。当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
在这里插入图片描述
如果有三个Eureka,则每一个Eureka Server都需要注册到其他几个Eureka服务中。

5.6.2高可用的Eureka Server小demo

先修改eureka-server
在这里插入图片描述
复制上述eureka-server项目
在这里插入图片描述
此时复制的这个eureka称为eureka-server2由于无法更改,所以就需要改原先的eureka-server项目。
要注意:因为eureka-server的端口号配置过,为10086,此时变成为eureka-server2的端口,即10086
所以eureka-server的端口需要改为10087。
在这里插入图片描述
总结理解:
eureka-server:
端口10087,向eureka-server2注册。
在这里插入图片描述
eureka-server2:
端口10086,向eureka-server注册
在这里插入图片描述
若是其中一台eureka挂了,要想不影响访问,需要修改user-service、consumer-service的application.yml配置文件。
改为:向两个eureka都注册。多个地址使用“,”隔开。

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

5.6.3Eureka客户端

5.6.3.1服务的注册和发现时长配置

服务注册

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

  • 第一层Map的Key就是服务id,一般是配置中spring.application.name属性。
  • 第二层Map的key是服务的实例id,一般是配置中的实例id,一般host+serviceid+port,例如:locahost:user-service:8081。
  • 值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例。形成集群。
服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”,这个我们成为服务的续约(renew)。
心跳续约默认时间是30s,每隔30s进行一次心跳。也可配置。
eureka:
instance:
#每隔30s发送一次心跳
lease-renewal-interval-in-seconds: 30
#最小过期时长,每隔30秒发一次心跳,如果隔了90s还没有发送心跳,就挂了
lease-expiration-duration-in-seconds: 90

服务列表拉取

当服务消费者启动时,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存到本地,并且每隔30s会重新获取并更新数据。

eureka:
  client:
    #要不要拉取服务
    fetch-registry: true
    #拉取周期
    registry-fetch-interval-seconds: 3

5.6.3.2失效剔除和自我保护

服务下线

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

失效剔除

有时服务可能由于内存溢出或网络故障灯原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求,相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每个一段时间(默认为60s)将当前清单中超时(默认90s)没有续约的服务剔除,这个操作被称为失效剔除。
可以通过eureka.server.eviction-interval-time-in-ms参数对其进行修改,单位是毫秒。
配置到eureka中。

eureka:
  server:
    eviction-interval-timer-in-ms: 300000
自我保护

当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于85%,在生产环境下,因为网络延迟等原因,心跳失败实例有可能超标,但是此时就把服务删除列表并不妥当,因为服务可能没有宕机,Eureka在这段时间内不会删除任何服务实例,直到网络恢复正常。
生产环境下这很有效,保证了大多数服务依然可用,不过可由可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
可以通过下面配置关停自我保护。

eureka:
  server:
    enable-self-preservation: false

6.负载均衡Ribbon

修改前:是只取第一个实例:

package cn.itcast.consumer.web;

import cn.itcast.consumer.pojo.User;
import com.netflix.appinfo.InstanceInfo;
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;

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

    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        //从实例中取出ip和端口
        String url = instance.getHost();
        int port = instance.getPort();
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
        System.out.println(baseUrl);
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
}

修改后:应该变成随机、轮询、hash等负载算法

6.1优化user-service微服务

6.1.1复制项目

使提供者有多个实例。
在这里插入图片描述

6.1.2在consumer-service引入依赖

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

6.1.3修改ConsumerApplication启动类

在RestTemplate方法上加注解@LoadBalanced

package cn.itcast;

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;
//启动类
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
    //需要调用,所以需要这个对象
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

将来它会内置拦截器,用来拦截器RestTemplat请求。

6.1.4修改ConsumerController

package cn.itcast.consumer.web;

import cn.itcast.consumer.pojo.User;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
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;

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

    @Autowired
    private DiscoveryClient discoveryClient;
    //最终方案
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }
    
   /* 方法一:
    @Autowired
    private RibbonLoadBalancerClient client;

    //修改后
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){

        //根据服务id获取实例,该方法帮我们实现了负载均衡,默认是轮询。
        ServiceInstance instance = client.choose("user-service");
          ...
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
    */

/*    修改前
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        //从实例中取出ip和端口
        String url = instance.getHost();
        int port = instance.getPort();
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
        System.out.println(baseUrl);
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }*/
}

6.1.4测试

http://localhost:8088/consumer/17
访问成功!
在这里插入图片描述

6.1.5负载均衡策略

负载均衡默认通过轮询规则,若不想使用轮询,可通过以下配置使用其他规则。
在consumer-service的application.yml中配置

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    #随机

格式:
服务名称。ribbon.NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

7.Hystrix

是一种保护机制。
也是Netflix公司的一款组件。

7.1雪崩问题

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

例如,一次业务请求,需要调用A、B、C、D四个服务,这四个服务可能又调用其他服务。此时,若某个服务发生异常,请求阻塞,用户不会得到想用。而tomcat这个线程不会释放,于是越来越多的用户请求被阻塞,会慢慢占满tomcat可用连接,从而导致其他所有服务都不可用,形成雪崩效应。

Hystrix解决雪崩问题的手段有两个:

  • 线程隔离
  • 服务熔断

7.2 线程隔离,服务降级

每个服务有独立的线程池,例如,为一个服务设置5个线程,当用户请求该服务,出现阻塞时,就只会阻塞他自己的这5个线程,不影响其他。即只占满自己的线程池,这就是线程隔离。

当访问的这个服务线程池占满时,也不会出现无线阻塞,会设置一个时间,在时间内没有得到相应,会返回一个响应到浏览器,该请求会释放,释放线程。这就是服务降级。

总结:
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程访问服务,如果线程池已满,或者请求超时,则会进行降级处理。
服务降级虽然会导致请求失败,但不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其他服务没有响应。
触发服务降级的情况:

  • 线程池已满
  • 请求超时

7.2.1实践

7.2.1.1引入依赖

在消费方配置,在consumer-service中添加依赖:

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

7.2.1.2启用熔断

在启动类上添加注解
@EnableCircuitBreaker

package cn.itcast;

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;
//启动类
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
    //需要调用,所以需要这个对象
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

标准的服务上都要添加
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication这三个注解,因此springcloud提供给我们一个新的注解@SpringCloudApplication可以替代这三个注解。

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
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;
//启动类
//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication
@SpringCloudApplication
public class ConsumerApplication {
    //需要调用,所以需要这个对象
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

7.2.1.3修改ConsumerController

package cn.itcast.consumer.web;

import cn.itcast.consumer.pojo.User;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
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;

@RestController
@RequestMapping("consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    //集群+失败处理=最终方案
    //失败容错的指令,即成功时使用该方法,失败时调用注解中参数指的方法。要求失败和成功时的两个方法返回值和参数完全一样
    @HystrixCommand(fallbackMethod = "queryByIdFallback" )
    @GetMapping("{id}")
    public String queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        //返回的对象转成json字符串
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String queryByIdFallback(@PathVariable("id") Long id){
        return "不好意思,服务器太拥挤了!";
    }

    /*//集群最终方案
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }*/

   /* 方法一:
    @Autowired
    private RibbonLoadBalancerClient client;

    //修改后
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){

        //根据服务id获取实例,该方法帮我们实现了负载均衡,默认是轮询。
        ServiceInstance instance = client.choose("user-service");
          ...
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
    */

/*    修改前
    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        //从实例中取出ip和端口
        String url = instance.getHost();
        int port = instance.getPort();
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
        System.out.println(baseUrl);
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }*/


}

7.2.1.4模拟测试

修改UserService模拟熔断环境。

public class UserService {
    @Autowired
    private UserMapper userMapper;
    public User queryById(Long id){
        try {
            Thread.sleep(2000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userMapper.selectByPrimaryKey(id);
    }

}

在这里插入图片描述

7.2.1.4优化代码

问题一:

由于上述失败是调用方法是为其中一个方法写的,但是以后有许多方法,总不能为每个方法都要写一个失败时调用方法,此时就要优化代码。

package cn.itcast.consumer.web;

import cn.itcast.consumer.pojo.User;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
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;

@RestController
@RequestMapping("consumer")
//该类中所有方法默认失败时调用方法
@DefaultProperties(defaultFallback = "@DefaultProperties(defaultFallback = queryByIdFallback)\n")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    //集群+失败处理=最终方案
    //启用降级和线程隔离,一旦失败,会去找@DefaultProperties中配置的方法
    @HystrixCommand
    @GetMapping("{id}")
    public String queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        //返回的对象转成json字符串
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String queryByIdFallback(){
        return "不好意思,服务器太拥挤了!";
    }
  /*  //方法一:
    //集群+失败处理
    //失败容错的指令,即成功时使用该方法,失败时调用注解中参数指的方法。要求失败和成功时的两个方法返回值和参数完全一样
    @HystrixCommand(fallbackMethod = "queryByIdFallback" )
    @GetMapping("{id}")
    public String queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        //返回的对象转成json字符串
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }*/

    /*//最终方案
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }*/

   /* 方法一:
    @Autowired
    private RibbonLoadBalancerClient client;

    //修改后
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){

        //根据服务id获取实例,该方法帮我们实现了负载均衡,默认是轮询。
        ServiceInstance instance = client.choose("user-service");
          ...
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
    */

/*    修改前
    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        //从实例中取出ip和端口
        String url = instance.getHost();
        int port = instance.getPort();
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
        System.out.println(baseUrl);
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }*/


}

在这里插入图片描述

问题二:

现在是把所有超时统一定义,超时时长1s。但是不同业务其超时时长应该不一样。对于速度慢的,应该单独为其设置超时时长。

单独配:

该注解方法用来单独配置在方法上。
@HystrixCommand(commandProperties = {
@HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”,value = “3000”)

package cn.itcast.consumer.web;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("consumer")
//该类中所有方法默认失败时调用方法
@DefaultProperties(defaultFallback = "queryByIdFallback")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    //集群+失败处理=最终方案
    //启用降级和线程隔离,一旦失败,会去找@DefaultProperties中配置的方法
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    @GetMapping("{id}")
    public String queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        //返回的对象转成json字符串
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String queryByIdFallback(){
        return "不好意思,服务器太拥挤了!";
    }
  /*  //方法一:
    //集群+失败处理
    //失败容错的指令,即成功时使用该方法,失败时调用注解中参数指的方法。要求失败和成功时的两个方法返回值和参数完全一样
    @HystrixCommand(fallbackMethod = "queryByIdFallback" )
    @GetMapping("{id}")
    public String queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        //返回的对象转成json字符串
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }*/

    /*//最终方案
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }*/

   /* 方法一:
    @Autowired
    private RibbonLoadBalancerClient client;

    //修改后
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){

        //根据服务id获取实例,该方法帮我们实现了负载均衡,默认是轮询。
        ServiceInstance instance = client.choose("user-service");
          ...
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }
    */

/*    修改前
    @Autowired
    private DiscoveryClient discoveryClient;
    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id){
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        //从实例中取出ip和端口
        String url = instance.getHost();
        int port = instance.getPort();
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"+id;
        System.out.println(baseUrl);
        User user = restTemplate.getForObject(baseUrl, User.class);
        return user;
    }*/


}

之前设置的是2s的睡眠时间,但在这里设置的是3s超时时长,睡眠时间没有超过2s,所以可以访问成功。
在这里插入图片描述
整理配置:
配置在application.yml文件上。


hystrix:
  command:
  #配置全局变量
    default:
      execution:
        isolation:
          thread:
           timeoutInMilliseconds: 3000
  #配置单个方法或服务的变量
 # user-service:或者queryByIdFallback
 	# execution:
     #   isolation:
      #    thread:
       #    timeoutInMilliseconds: 3000
  

在这里插入图片描述

7.3服务熔断

7.3.1熔断原理

熔断其英文单词:Circuit Breaker
像家里的点了熔断器,如果点了发生短路能立刻熔断电路,避免发生灾难。在分布式系统应用这一模块之后,服务调用方可以自己进行判断某些服务反映慢或者存在大量超时的情况时,能够主动熔断,防止整个系统被拖垮。

不同于电路熔断只能断不能自动重连,Hystrix可以实现弹性容错,当情况好转之后,可以自动重连。

通过断路的方式,可以将后续请求直接拒绝掉,一段时间之后允许部分请求通过,如果调用成功回到正常状态,否则连续断开。
在这里插入图片描述
熔断器的三个状态:

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

7.3.2实践

我们通过手动控制,不需要通过休眠时间。若访问id为偶数,则请求失败,奇数则成功。

7.3.2.1修改UserService

把之前模拟的休眠代码删掉。

package cn.itcast.user.service;

import cn.itcast.user.mapper.UserMapper;
import cn.itcast.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public User queryById(Long id){
        /*try {
            Thread.sleep(2000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        return userMapper.selectByPrimaryKey(id);
    }

}

7.3.2.1修改ConsumerController

模拟测试环境,并设置休眠时间为10s,当熔断器关闭后,访问次数设置为10次,超过50%失败则进入休眠时间。休眠时间一过,释放部分连接,若请求成功,则关闭熔断器,否则进入休眠时间。

package cn.itcast.consumer.web;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("consumer")
//该类中所有方法默认失败时调用方法
@DefaultProperties(defaultFallback = "queryByIdFallback")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;
    //集群+失败处理=最终方案
    //启用降级和线程隔离,一旦失败,会去找@DefaultProperties中配置的方法
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
            //熔断器关闭后,失败次数
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
            //熔断器关闭后,休眠时间
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),

    })
    @GetMapping("{id}")
    public String queryById(@PathVariable("id") Long id){
        //模拟熔断环境测试,id为偶数即请求失败,为奇数就成功
        if(id%2 == 0){
            throw new RuntimeException("");
        }
        //user-service是服务id
        String url = "http://user-service/user/"+id;
        //返回的对象转成json字符串
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String queryByIdFallback(){
        return "不好意思,服务器太拥挤了!";
    }

生产环境下,这些参数不需要调,按照默认的就可以了。

总结:该篇讲了服务间的访问和高可用。

发布了28 篇原创文章 · 获赞 0 · 访问量 900

猜你喜欢

转载自blog.csdn.net/weixin_43876557/article/details/102562938
今日推荐