文章目录

1. SpringCloud前置知识点

1.1 RestFul API介绍

rest:(Representational State Transfer),直接翻译:表现层状态转移。
用于URL定位资源,用HTTP动词(GET、POST、DELETE、PUT)描述进行操作
在这里插入图片描述
将动作(增删改查)隐藏起来,用请求方式来处理(兼容不同语言后缀)

作用:
rest描述的是在网络中client和server的一种交互形式;REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口)。
Server提供的RESTful API中,URL中只使用名词来指定资源,原则上不使用动词。“资源”是REST架构或者说整个网络处理的核心。

1.1.1 Rest介绍

Rest是用HTTP协议里的动词来实现资源的添加,修改,删除等操作。即通过HTTP动词来实现资源的状态扭转。
通过url地址就知道请求的是什么,通过http的请求方式method就知道具体是干什么,通过http的返回状态码(status code)就知道结果怎么样。
在这里插入图片描述
在这里插入图片描述

1.1.2 RestFul案例

User

@Data   //lombok,可以省略get/set、构造方法、toString等方法
public class User {
    private int id;
    private String uasername;
    private String password;
}

UserController

@RestController
@Slf4j  //lombok日志
public class UserController {

    //普通的地址表示方式(通过具体的地址来访问)  http://localhost:8080/users/find.do?id=1
    @RequestMapping(path = "/users/find.do", method = {RequestMethod.GET})
    public Object find(int id) {
        //到数据库中查询模拟
        User user = new User();
        user.setId(id);
        user.setUasername("strive_day");
        user.setPassword("111111");
        return user;
    }

//  restful表示方式
    //method = get,查询
    //1: 占位符  /user/{id}
    //2: 取path上的值  user/1 使用注解来取        (http://localhost:8080/users/21)
    @RequestMapping(path = "/users/{id}", method = {RequestMethod.GET})
    public Object get(@PathVariable("id") int userId) {//@PathVariable取得id的值1,赋值给与占位符变量的同名
        //模拟数据库查询
        log.info("get id "+userId);
        User user = new User();
        user.setId(userId);
        user.setUasername("strive_day");
        user.setPassword("222222");
        return "Get方式查询"+user;
    }


    //method = delete 删除
    @RequestMapping(path = "/users/{id}", method = {RequestMethod.DELETE})
    public Object delete(@PathVariable int id) {
        //到数据库中查询模拟
        log.info("删除了 id "+id);
        User user = new User();
        user.setId(id);
        user.setUasername("strive_day");
        user.setPassword("333333");
        return "delete方式删除" + user;
    }
}

通过Postman工具来访问

  1. 普通方式,通过url地址不同区分来访问
    在这里插入图片描述
  2. 通过RestFul来访问,选择不同的请求方式,访问不同的方法
    get请求方式
    在这里插入图片描述
    delete请求方式
    在这里插入图片描述
    postman可以选择不同的请求方式来进行测试
    在这里插入图片描述


1.2 RestTemplate介绍

1)一般情况下有如下三种http客户端工具类包都可以方便的进行http服务调用。

  • httpClient apache
  • okHttp
  • JDK原生URLConnection

2)RestTemplate是spring提供的工具类对上述的3种http客户端工具类进行了封装,可在spring项目中使用RestTemplate进行服务调用。

  • 请求地址 + 请求方式
  • 请求参数
  • 返回值

3)RestTemplate的作用
类似postman 可以模拟 http请求(get,post,delete,put等请求)
在这里插入图片描述
4)RestTemplate特点
比如post请求处理:
postForObject(url, requestMap, ResponseBean.class)

  • 参1 请求地址
  • 参2 请求参数
  • 参3 HTTP响应转换被转换成的对象类型
使用RestTemplate模拟调用请求
//RestTemplate(类似postman),可以模拟 http请求,如(get,post,delete,put)
@SpringBootTest
class Demo02RestTemplateApplicationTests {

    @Test
    void test01() {
        RestTemplate template = new RestTemplate();
        //发送delete请求
        template.delete("http://localhost:8080/users/21");
    }

    //restTemplate还可以将返回的json数据直接转换成java对象
    @Test
    void test02() {
        RestTemplate template = new RestTemplate();
        //发送get请求,将返回的json数据直接转换成java对象
        User user=template.getForObject("http://localhost:8080/users/21", User.class);
        System.out.println(user);
    }

}

在这里插入图片描述



1.3 dependencyManager

1.3.1 dependencies介绍

dependencies是pom.xml元素,表示依赖项的集合。
在**父子工程中使用**,所有生命在dependencies里的依赖都会自动引入对应的jar包,如果放到父工程中默认被所有的子项目继承

1.3.2 dependencyManager介绍
  1. dependencyManager是是pom.xml的一个元素,多模块(项目)场景下的版本管理
    多个子项目中,必须确保应用的各个项目的依赖项和版本一致,才能保证测试的和发布的是相同的结果。
    比如mysql8驱动需要的时区参数
  2. dependencyManagement的作用
    用来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。
    原理:Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号。
  3. dependencyManagement特点
    子项目还得写依赖,但不需要写版本
    (在父工程中)本质上是声明依赖与版本(只是说有这个东西,但没有真正加进来,真正的加载进来需要在子工程中手动引入)

在父工程中pom.xml声明,只是管理版本

<!--声明(锁定jar的版本)mysql,但是不将mysql引入到父工程中 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.46</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

在子工程pom.xml中如果需要就引入才能使用(如果没有指定具体版本,才会从父项目中继承,如果子项目中指定了版本号,那么会使用子项目中指定的jar版本)

	<!-- 引入父工程的mysql依赖,不需要版本(父工程指定) -->
	<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
1.3.3 dependencyManagement与dependencies区别【**】

dependencies即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(并且是全部继承)。
dependencyManagement里只是声明依赖并不实现引入,因此子项目需要显示的声明需要用的依赖
如果不在子项目中声明依赖,是不会从父项目中继承下来的。
只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;
另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

在这里插入图片描述



1.4 tk.mybatis(通用mapper)

Mabtis工具包:tk.mybatis
TKmybatis是基于MyBatis框架开发的一个工具,可以极大的提高项目的开发效率,通过调用它提供的方法实现对单表数据的操作(crud),不需要写任何的sql语句
注意事项:
在这里插入图片描述

使用步骤
  1. pom.xml 导入依赖TkMybatis的maven依赖
    pom.xml
        <!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>4.1.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
  1. 实体类javabean的相关配置
    @id主键注解(只能有一个),@Table数据库表名,@Column对应数据库列名(属性名和表列名不一致情况),@Transient是冗余字段,不与数据库任何字段对应,即忽略
import lombok.Data;
import javax.persistence.*;

@Table(name="tb_user")  //表名
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增
    private int id;
    @Column(name="girl_friend")     //属性名和列名不一致
    private String girlFriend;
    private String name;
    private String password;
    private String username;
    @Transient      //@Transient注解:忽略,不与数据库表任何字段对应
    private boolean isCheck; //跟表无关,是否选择
}
  1. Mapper接口继承tkMyBatis的Mapper接口
import tk.mybatis.mapper.common.Mapper;

//tk.mybatis 提供Mapper父接口,继承就可以直接使用
public interface UserDao extends Mapper<User> {
    //update、delete、select、insert方法可以全部省略,由tkmybatis实现
}
  1. 在启动类Application上使用@MapperScan()扫描Mapper接口
@SpringBootApplication
@MapperScan(basePackages = "com.xgf.demo03_tkmybatis.mapper")   //扫描包
public class Demo03TkmybatisApplication {

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

}
  1. application.properties配置文件配置数据库属性
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=861221293
spring.datasource.url=jdbc:mysql://localhost:3306/springboot


# mybatis
mybatis.type-aliases-package=com.xgf.demo03_tkmybatis.domain
# use xml 使用xml配置的时候需要
mybatis.mapper-locations=classpath*:com/xgf/demo03_tkmybatis/domain/mapper/*.xml
  1. 使用TKMybatis提供的sql测试执行
@SpringBootTest
class UserServiceTest {

    @Autowired
    UserService userService;
    @Test
    void test01() {
        //高级搜索页面的表单
        User user = new User();
        //user.setName("strive_day");
        user.setUsername("strive_gf@163.com");
        List<User> list = userService.queryByEntity(user);
        System.out.println(list);
    }
}

测试结果:
在这里插入图片描述

2. Spring Cloud入门

Spring cloud微服务是一种架构方式,最终肯定需要技术架构去实施。
微服务的实现方式很多,Spring Cloud是一种最火的微服务实现方式。
Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,它是一个大的容器,将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。
Spring Cloud是Spring旗下的项目之一
官网地址:http://projects.spring.io/spring-cloud/

2.1 SpringCloud架构

Eureka:注册中心,美[juˈriːkə]
Zuul 或者 Gateway:服务网关
Ribbon: 负载均衡,美[ˈrɪbən]
Feign:服务调用,美[feɪn]
HystrixResilience4j:熔断器,美[hɪst’rɪks][rɪˈzɪliəns]

在这里插入图片描述

2.2 微服务开发三要素

dubbo发布的服务的三个要素
  1. 提供者provider
  2. 消费者consumer
  3. 注册中心(zookeeper)
SpringCloud发布的服务三个要素
  1. 提供者provider
  2. 消费者consumer
  3. 注册中心

在这里插入图片描述

2.3 微服务入门工程实例

创建工程规划

  1. 创建微服务父工程 xxx-parent
    添加 SpringBoot 父坐标和管理其它组件的依赖
  2. 创建用户服务工程demo01_provider_user_8001
    整合mybatis查询数据库中用户数据,提供查询用户服务
  3. 创建服务消费工程 demo02_consumer_user_8002
    利用查询用户服务获取用户数据并输出到浏览器
    在这里插入图片描述
1. 创建微服务父工程

创建一个maven的project空项目:micro_service_spring_cloud_parent

2. 搭建配置服务(provider)工程

2.1 选择起步依赖(web工程):
demo01_provider_user_8001(user是对什么表操作,8001是端口号)

2.2 修改配置文件中的参数application.properties
这里端口号改为8001

# publicsh service name
spring.application.name=demo01_provider_user_8001
# server port
server.port=8001

2.3 编写Controller测试访问
UserController

@RestController
@RequestMapping("/users")
public class UserController {

    //http://localhost:8001/users/21
    @RequestMapping(path = "/{id}",method = {RequestMethod.GET})
    public Object  get(@PathVariable long id){
        //模拟数据库查询一个数据
        User user = new User();
        user.setId(id);
        user.setUsername("strive_day"+id);
        user.setPassword("123456");
        return user;    //RestController将user转成json
    }
}

2.4 通过postman进行测试
http://localhost:8001/users/21
在这里插入图片描述

3. 搭建配置消费(consumer)工程

3.1 选择起步依赖(web工程):demo02_consumer_user_8002(user是对什么表操作,8002是端口号),使用RestTemplate获取provider服务提供者提供的数据

3.2 修改配置文件中的参数application.properties
这里将端口改为8002

spring.application.name=demo02_consumer_user_8002
server.port=8002

3.3 编写Controller,使用RestTemplate获取provider服务提供者提供的数据。

@RestController
@RequestMapping("/consumer")
@Slf4j
public class CustomerController {

    //需要在启动类创建当前对象RestTemplate,然后由Autowired去获取
    @Autowired
    RestTemplate rt;    //当前是使用RestTemplate调用demo01_provider服务提供者的数据

    //
    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    public Object get(@PathVariable long id) {
        //消费工程,只是调用其他服务来获得想要的数据
        String url = "http://localhost:8001/users/"+id;  //参1: 地址
        String json = rt.getForObject(url, String.class);   //参2:数据要转换成什么类型
        return json;
    }
}

3.4 通过postman进行测试
http://localhost:8002/consumer/21
在这里插入图片描述

4. 搭建eureka-server工程
Eureka注册中心说明

Eureka是SpringCloud将它集成在其子项目spring-cloud-netflix 中,以实现SpringCloud的服务发现功能。

Eureka的主要功能:

  • 进行服务管理
  • 定期检查服务状态
  • 返回服务地址列表。

在这里插入图片描述
心跳:检查服务器是否挂掉。

4.1 选择起步依赖Eureka server创建工程:
demo03_eureka_server_10001,Eureka是服务注册中心,只做服务注册,自身并不提供服务也不消费服务。
在这里插入图片描述
4.2 添加启动器依赖(选择Eureka server起步依赖自动增加了)
pom.xml

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

因为当前阿里云平台还没有更新,所以会保留官网的仓库地址(用来下载)

    <!-- 阿里云目前还没有更新,所以需要保留官网的仓库地址 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

4.3 修改配置文件application.properties(端口,应用名称,是否注册自己等)

# 服务器名称
spring.application.name=demo03_register_center_eureka_10001
# 端口号
server.port=10001
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}

# not register myself 不注册自己
eureka.client.register-with-eureka=false
# not query myself  不拉取服务
eureka.client.fetch-registry=false

4.3 在启动引导类Application(添加Eureka的服务注解@EnableEurekaServer)和配置文件

@SpringBootApplication
@EnableEurekaServer   //添加Eureka的服务注解,表示作为一台服务器启动
public class Demo03RegisterCenterEurekaApplication {

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

}

4.5 测试运行

在这里插入图片描述

修改配置文件application.properties(是不注册注册自己)

# not register myself 不注册自己
eureka.client.register-with-eureka=false
# not query myself  不拉取服务
eureka.client.fetch-registry=false

在这里插入图片描述



2.4 优化,将相同的依赖抽取到父工程pom.xml中

  1. 将所有的pom.xml抽出来放到画图等软件中
  2. 找出相同的配置,复制到父工程中

父工程micro_service_spring_cloud_parent的pom.xml

<!-- 父类,自动继承 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!-- 父工程打包方式为pom,给子工程继承 -->
    <packaging>pom</packaging>

    <!-- 子模块 -->
    <modules>
        <module>demo01_provider_user_8001</module>
        <module>demo02_consumer_user_8002</module>
        <module>demo03_register_center_eureka_10001</module>
    </modules>

    <!-- 1.8版本相同放父工程 -->
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.0-M5</spring-cloud.version>
    </properties>

    <!-- dependencyManagement锁定版本,不会自动引入 -->
    <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>

    <!-- dependencies自动引入,所有子工程都继承lombok -->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>

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

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

所有子工程都继承父工程pom.xml

    <parent>
        <!-- 继承父工程 -->
        <groupId>com.xgf</groupId>
        <artifactId>micro_service_spring_cloud_parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

服务提供者provider子工程和消费者consumer子工程的pom.xml是客户端client

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

server子工程时服务端server

        <!-- 从父工程中找,父工程已经提供了仓库 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

3. 服务的注册(provider)与发现(consumer)【**】

在这里插入图片描述
步骤:
(1)将服务注册到eureka并在消费端中可以根据服务名称进行调用。
(2)provider服务注册。
(3)consumer服务发现。

3.1 provider服务注册

在服务提供工程上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。

  1. pom.xml添加Eureka的客户端依赖。
        <!-- 客户端 -->
        <dependency>
           <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 修改配置文件application.properties,设置Eureka 服务地址。
# register address 注册地址
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}
  1. 改造启动引导类,添加开启Eureka客户端发现的注解。@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient         //注册为客户端
public class Demo01ProviderUser8001Application {

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

}
  1. 运行Eureka看是否注册进去
    http://localhost:10001/
    在这里插入图片描述

3.2 consumer通过注册中心Eureka查询到需要的服务provider

服务发现
在服务消费工程consumer上添加Eureka客户端client依赖,可以使用工具类根据服务名称获取对应的服务地址列表。

  1. pom.xml添加Eureka的客户端依赖
        <!-- client端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 修改配置文件application.properties,设置Eureka服务地址。
# query address 查询地址
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}
  1. 改造启动引导类,添加开启Eureka客户端发现的注解。
    @EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient         //注册为客户端
public class Demo02ConsumerUser8002Application {

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

    //将RestTemplate对象放入容器中
    @Bean
    public RestTemplate getRt() {
        RestTemplate rt = new RestTemplate();
        return rt;
    }
}

  1. 重启consumer,运行Eureka看是否注册进去
    http://localhost:10001/
    在这里插入图片描述

  2. 改造处理器类ConsumerController,使用工具类DiscoveryClient根据服务名称获取对应服务地址列表provider。
    通过DiscoveryClient来获取注册中心中的provider服务提供者的信息(比如主机Host、端口号port)
    ConsumerController

@RestController
@RequestMapping("/consumer")
@Slf4j
public class CustomerController {

    //需要在启动类创建当前对象RestTemplate,然后由Autowired去获取
    @Autowired
    RestTemplate rt;    //当前是使用RestTemplate调用demo01_provider服务提供者的数据

    @Autowired
    DiscoveryClient discoveryClient;    //DiscoveryClient,用来获取注册中心provider的信息

    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    public Object get(@PathVariable long id) {

        //通过DiscoveryClient获取 provider的ip与端口
        List<ServiceInstance> list = discoveryClient.getInstances("demo01_provider_user_8001");// 参数:服务id
        //获取运行中的服务对象
        ServiceInstance serviceInstance = list.get(0);  //获取第一个
        //获取主机
        String host = serviceInstance.getHost();
        //获取端口
        int port = serviceInstance.getPort();
        log.info("provider主机host: "+host);
        log.info("provider端口port: "+port);

        //消费工程,只是调用其他服务来获得想要的数据
        String url = "http://"+host+":"+port+"/users/"+id;  //参1: 地址(不用写死主机和端口号)
//        String url = "http://localhost:8001/users/"+id;  //参1: 地址
        String json = rt.getForObject(url, String.class);   //参2:数据要转换成什么类型
        return json;
    }
}
  1. 通过postman访问consumer的controller

http://localhost:8002/consumer/21
在这里插入图片描述
查看控制台输出:
在这里插入图片描述

4. Eureka高可用配置【**】

  1. 为什么需要高可用?
    提供者provider将数据注册到服务中心,消费者consumer从注册中心拿取数据,如果服务Server挂了,那么provider和consumer都不能正常工作。
    热备份:比如有两个服务中心A和B,如果A挂掉,可以访问B(数据是同步的【服务同步】,可以直接访问服务B)。
    冷备份:比如有服务中心A和B,当前只启动了A(B停用),如果A挂掉,马上将数据备份到B中,并启动B提供服务。

  2. 高可用概念
    “高可用性”(High Availability):通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性
    比如:给Eureka Server 搞一个备份。

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

  4. 热备份高可用的实现图
    在这里插入图片描述

4.1 Eureka高可用集群配置(热备份)

描述:
搭建两台EurekaServer的集群(服务注册中心)
端口分别为:10001和10002
如果有一个挂掉,能够直接使用另一个服务处理。

4.1.1 application配置将自己注册的设置为可见

把application.properties的register-with-eurekafetch-registry修改为true或者注释掉。(注释掉)

## not register myself 不注册自己
#eureka.client.register-with-eureka=false
## not query myself  不拉取服务
#eureka.client.fetch-registry=false

然后访问Eureka查看自己是否可以见
http://localhost:10001/
在这里插入图片描述

4.1.2 配置JVM虚拟机参数【**】

通过虚拟机给当前程序传递参数,然后在application配置文件中根据指定的参数来获取值。

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

4.1.3 修改配置文件application

实现从JVM虚拟机参数中读取端口号port和地址

# 端口号 使用JVM虚拟机参数,设置VM options的值(键值对),如果JVM存有值port,就使用port作为端口号,否则使用默认值10001
server.port=${port:10001}
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址,defaultZone是JVM虚拟机参数
# 注册两台(互相注册,通过JVM虚拟机参数配置,如果没有参数就只注册一台)
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka}

${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值
${port:10001} 取VM options的值 -Dport=10086
》把service-url的值改成了另外一台EurekaServer的地址,而不是自己

4.1.4 实现互相注册(10001和10002互相注册)

10001端口JVM参数VM options:
-Dport=10001 -DdefaultZone=http://127.0.0.1:10002/eureka(截图配置少了http://后面的两个斜杆/
在这里插入图片描述

4.1.5 通过复制配置一份注册中心端口10002

修改JVM参数端口号为10002和数据共享10001
10002端口JVM虚拟机参数:-Dport=10002 -DdefaultZone=http://127.0.0.1:10001/eureka(截图配置少了http://后面的两个斜杆/
在这里插入图片描述
在这里插入图片描述

4.1.6 重启10001和10002的服务中心并访问

然后访问:http://localhost:10001/,发现10001和10002互相注册成功
在这里插入图片描述
然后访问:http://localhost:10002/,发现10001和10002互相注册成功
在这里插入图片描述

4.2 客户端注册服务到集群

因为EurekaServer不止一个(这里有10001和10002两个),因此provider项目注册服务或者consumer 获取服务的时候,service-url参数需要为对应的所有Server地址。

4.2.1 服务提供者provider服务注册

application.properties

# register address 注册地址(有两个server10001和10002都需要注册,用逗号隔开)
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka},${defaultZone:http://127.0.0.1:10002/eureka}

4.2.2 服务消费者consumer服务查询

application.properties

# query address 查询地址(两个server,用逗号隔开)
eureka.client.service-url.defaultZone=${defaultZone:http://127.0.0.1:10001/eureka},${defaultZone:http://127.0.0.1:10002/eureka}
4.2.3 测试运行

postman测试调用provider和consumer的controller方法:

测试调用provider的controller方法:
http://localhost:8001/users/21
在这里插入图片描述

测试调用consumer的controller方法:
http://localhost:8002/consumer/21
在这里插入图片描述

5. 微服务Eureka客户端与服务配置(了解)

  1. Eureka客户端工程
    1)服务提供
    a. 服务地址使用ip方式。
    b. 实现续约。
    2) 服务消费:获取服务地址的频率。
  2. Eureka服务端工程 eureka-server
    1)实现失效剔除
    2)实现自我保护

5.1 Eureka客户端provider配置使用ip优先

默认情况下,注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在user-service 中添加配置如下:

# 客户端配置ip地址(当前ip地址)
eureka.instance.ip-address=127.0.0.1
# 开启,设置注册时ip优先,读配置的ip地址(这样consumer读取的时候读取的是配置的ip值,而不是主机名值)
eureka.instance.prefer-ip-address=true

配置完成通过访问consumer获取服务提供者provider的信息
http://localhost:8002/consumer/21
发现host主机已经变成了ip地址(provider注册的ip地址)
在这里插入图片描述

5.2 Eureka客户端provider配置服务续约

服务续约概念:(客户端配置)
在注册服务完成以后,服务提供者provider会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:"我还活着”。这个我们称为服务的续约(renew)。【如果挂掉了,就从服务中心server中去除(防止挂掉还一直被访问)】

两个重要参数可以修改服务续约的行为
可以在中添加如下配置项:
lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒、心跳包的时间间隔。
lease-expiration-duration-in-seconds:服务失效时间,默认值90秒,没收到心跳包的最长时间(超时就认为服务挂掉,就从服务中心去掉)

provider服务提供者配置application.properties

# 心跳包的时间间隔(服务续约(renew)的间隔,默认为30秒)
eureka.instance.lease-renewal-interval-in-seconds=30
# 服务失效时间,默认值90秒,没收到心跳包的最长时间
eureka.instance.lease-expiration-duration-in-seconds=90

5.3 Eureka消费端consumer获取服务列表 fetch

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

consumer端配置application.properties,设置消费端可以定时获取注册中心的服务列表,并缓存时间

# 开启,通过fetch从服务端取数据(注册信息),,如果为true,则会从Eureka Server服务的列表拉取只读备份
eureka.client.fetch-registry=true
# 每隔30秒,会重新拉取并更新数据
eureka.client.registry-fetch-interval-seconds=30

5.4 Eureka服务端server设置

  1. 服务下线
    当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。
  2. 失效剔除
    有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到 “服务下线” 的请求。相对于服务提供者的 “服务续约” 操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
    eureka.server.eviction-interval-timer-in-ms :参数对其进行修改,单位是毫秒
    服务中心:维持有效的服务列表,剔除无效的服务列表(provider列表)【provider服务提供有效保留,无效挂掉就剔除】。

在服务server注册中心的application.properties配置失效剔除时间

# 配置失效剔除:创建定时任务,每隔一段时间(单位:毫秒)将当前清单中provider服务提供者超时(默认为90秒)没有续约的服务剔除
eureka.server.eviction-interval-timer-in-ms=90000

Eureka服务端server配置自我保护是否开启

自我保护
当我们关停一个服务provider,很可能会在Eureka面板看到一条警告信息(超市剔除)
在这里插入图片描述
在服务server注册中心的application.properties配置是否开启自我保护(默认为true开启,缺省为打开)

# 关闭,配置自我保护(默认为true开启),开启自我保护,如果关停一个服务,就会在Eureka面板出现警告信息
eureka.server.enable-self-preservation=false

6. 微服务-负载均衡Ribbon【***】(provider集群)

6.1 负载均衡Ribbon介绍

  1. 负载均衡概念
    负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行(使用特定算法【默认轮询】将请求分配到对应服务提供者provider)。
    本质:负载均衡是一个算法,可以通过该算法实现从地址列表中获取一个地址进行服务调用
    就是一台机器,受到太多的请求,响应不过来,通过负载均衡,分配给不同的执行单元(机器)来处理请求
    Spring Cloud中提供了负载均衡器:Ribbon 美[ˈrɪbən]
  2. 在Ribbon中提供了轮询、随机两种负载均衡算法(默认是轮询,一个个询问)实现,可以实现从地址列表中使用负载均衡算法获取地址进行服务调用。

6.2 Ribbon负载均衡应用案例

实际环境中,往往会开启很多个 user-service(服务提供者provider) 的集群。此时获取的服务列表provider中就会有多个,到底该访问哪一个呢?
可以使用Ribbon负载均衡:在执行RestTemplate发送服务地址请求的时候,使用负载均衡拦截器拦截,根据服务名获取服务地址列表,使用Ribbon负载均衡算法从服务地址列表中选择一个服务地址,访问该地址获取服务数据。
在这里插入图片描述

1. 配置两个provider服务提供者

配置启动两个服务提供者(provider) user-service 实例,一个8000,一个8001.
在这里插入图片描述
复制配置端口为8000的provider
在这里插入图片描述
修改provider的application设置端口获取优先从JVM虚拟机获取

# server port ${}从JVM中获取参数,如果有用port,如果没有用默认值8001
server.port=${port:8001}

启动8000和8001的provider服务提供者,通过Eureka访问
http://localhost:10002/
在这里插入图片描述

2. 修改consumer的启动器添加@LoadBalanced注解

@LoadBalanced注解:设置当前方法的返回对象,使用负载均衡算法。

@SpringBootApplication
@EnableEurekaClient         //注册为客户端
public class Demo02ConsumerUser8002Application {

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

    //将RestTemplate对象放入容器中
    @Bean   //1、产生多个provider实例 8000 和 8001
    @LoadBalanced //2、设置当前方法的返回对象,使用负载均衡算法
    public RestTemplate getRt() {
        RestTemplate rt = new RestTemplate();
        return rt;
    }
}
3. 集群名称不能有下划线

修改provider服务提供者的service name的命名(集群名称),将下划线全部改为-减号或者别的符号
application.properties

# 服务器名称
spring.application.name=demo03_register_center_eureka_10001

在这里插入图片描述

4. 修改CosumerController通过集群调用。

通过调用provider集群名访问(具体调用provider集群的哪一个具体的provider是通过负载均衡实现的)

    //集群调用provider(不能写死主机和端口号)
    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    public Object get(@PathVariable long id) {
        //3、此处不能写死 provider 的 ip和port端口 ,只能写服务名。不能使用_下划线,可以将下划线替换成-
        //调用服务提供者provider的集群demo01-provider-user(这里有两个8000和8001可调用),通过负载均衡调用具体哪一个
        String url = "http://demo01-provider-user/users/"+id;//参1: 地址
        String json = rt.getForObject(url, String.class);//参1:返回数据,参2:数据要转换成什么类型
        return json;
    }

通过postman来测试
http://localhost:8002/consumer/21

在这里插入图片描述

7. 微服务-熔断器Hystrix【***】

7.1 熔断器Hystrix介绍

  1. 熔断器概念:
    熔断器(fuse)是指当电流超过规定值时,以本身产生的热量使熔体熔断,断开电路的一种电器,如(空气开关)。
    在微服务的架构系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务。有的时候某些依赖服务出现故障也是很正常的。
  2. 熔断器Hystrix概念(发音:美[hɪst’rɪks])
    Hystrix是一个延迟和容错库
    Hystrix可以让我们在对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。Hystrix通过将依赖服务进行资源隔离,进而组织某个依赖服务出现故障的时候,这种故障在整个系统所有的依赖服务调用中进行蔓延,同时Hystrix还提供故障时的fallback降级机制。总而言之,Hystrix通过这些方法帮助我们提升系统的可用性和稳定性
Hystrix工作流程

在这里插入图片描述

  1. 构造一个 HystrixCommandHystrixObservableCommand 对象,用于封装请求,并在构造方法配置请求被执行需要的参数。
  2. 执行命令,Hystrix提供的4种执行命令的方法。

K value = command.execute();
Future fValue = command.queue();
Observable ohValue = command.observe(); //hot observable
Observable ocValue = command.toObservable(); //cold observable
执行同步调用execute方法,会调用queue().get()方法,queue()又会调用toObservable().toBlocking().toFuture()。
所以,所有的方法调用都依赖Observable的方法调用,只是取决于是需要同步还是异步调用;

  1. 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动。
    4.判断熔断器是否打开,如果打开,跳到第8步。
  2. 判断线程池/队列/信号量是否已满,已满则跳到第8步;
  3. 执行业务请求 HystrixObservableCommand.construct()HystrixCommand.run(),如果执行失败或者超时,跳到第8步。否则,跳到第9步。
  4. 统计熔断器监控指标。
  5. 走Fallback备用逻辑。
  6. 返回请求响应。

7.2 熔断器Hystrix作用

使用服务降级,线程隔离解决雪崩问题

7.2.1 雪崩问题【***】

微服务业务的某一个服务发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。

微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路。
比如下面这个业务请求,就会调用A、P、H、I四个服务,这四个服务又可能调用其它服务。
在这里插入图片描述

微服务I发生异常时,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,一直卡在这里。
在这里插入图片描述
于是越来越多的用户请求到来(请求服务器I发生阻塞),越来越多的线程会阻塞。
在这里插入图片描述
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应

7.2.2 Hystrix解决雪崩问题的手段
  1. 线程隔离
  2. 服务熔断
    主要是通过服务降级来解决雪崩问题
7.2.3 Hystrix 线程隔离、服务降级【***】

1)线程隔离
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
在这里插入图片描述

2) 服务降级
优先保证核心服务,而非核心服务不可用或弱可用。用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

3)触发Hystix服务降级的情况

  • 线程池已满
  • 请求超时

4)服务降级的分类(两类):

  1. 自动降级

超时、失败次数、故障、限流等情况。
A. 配置好超时时间。
B. 不稳的的api调用次数达到一定数量进行降级。
C. 调用的远程服务出现故障。(dns、http服务错误状态码、网络故障、Rpc服务异常),直接进行降级。

  1. 人工降级

秒杀、双十一大促降级非重要的服务。

6)服务降级 - 超时降级配置

修改Hystrix对请求超时的判断时间(默认为两秒,可修改)
两种修改方式:

  1. 注解配置(加到方法上)
    @HystrixCommand(commandProperties = {@HystrixProperty(name =“execution.isolation.thread.timeoutInMilliseconds” ,value =“2000” )})
  2. 属性配置(application.properties)
    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
7.2.4 服务熔断介绍

服务熔断是为了防止整个系统的故障,而采用了一些保护措施。
一般是某个服务异常引起的,相当于 “保险丝” 的作用,当某个异常条件被触发,直接熔断整个服务,不是等到此服务超时。
比如:A调B,B很慢的话,A也很慢。熔断让A不再调用B,而改成调用C ( 退路方法)

7.2.5 熔断状态机模型(了解)

在这里插入图片描述

熔断状态机的三个状态:

  1. Closed:关闭状态(断路器关闭)
    所有请求都正常访问。
  2. Open:打开状态(断路器打开)
    所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
  3. Half Open:半开状态
    不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断路器,否则继续保持打开,再次进行休眠计时。
7.2.6 服务熔断的application配置
# 错误率,默认50%
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50 
# 滑动窗口的大小,默认为20
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20 
# 过多长时间,熔断器再次检测是否开启(单位:毫秒),默认为5000,即5s钟
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000

上述配置的意思是:
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
达到熔断之后,那么后面它就直接不去调该微服务。

7.2.7 服务熔断和服务降级异同

相同点:

  • 从可用性和可靠性触发,都是为了防止系统崩溃。
  • 最终让用户体验到的是某些功能暂时不能用。

不同点:

  • 服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制。
  • 两者的触发原因不同

8. 微服务- Feign

8.1 Feign介绍

Feign:(音标:美[feɪn],意思是假装,装作,佯装)。
在SpringCloud微服务中Feign是一个开源库,用来编写HTTP请求。
Feign项目的github主页地址:https://github.com/OpenFeign/feign


微服务使用Feign的目的是(Feign makes writing java http clients easier)
让编写Http请求更容易,简化拼接url,拼接参数等等操作
Feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。

8.2 Feign工作流程图

在这里插入图片描述

原理概述:

  1. 启动时,程序会进行包扫描,扫描所有包下所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate
  2. RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。
  3. RequestTemplate声场Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection,也可以是OKhttpApacheHTTPClient等。
  4. 最后client封装成LoadBaLanceClient,结合Ribbon负载均衡地发起调用。
    在这里插入图片描述

8.3常用FeignClient注解属性

属性名默认值作用
value空字符串调用服务名称,和name属性相同
serviceId空字符串服务id,作用和name属性相同
name空字符串调用服务名称,和value属性相同
url空字符串全路径地址或hostname,http或https可选
decode404false配置响应状态码为404时是否应该抛出FeignExceptions
configuration{}自定义当前feign client的一些配置
fallbackvoid.class熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。
path空字符串自动给所有方法的requestMapping前加上前缀,类似与controller类上的requestMapping
primarytrue

8.4 Feign使用案例

  1. pom.xml导入启动器依赖
 		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. Application类通过注解@EnableFeignClients上开启Feign功能
@EnableFeignClients		//注解开启feign
@SpringCloudApplication
public class Demo02ConsumerUser8002Application
  1. 编写Feign客户端(本质上是一个接口,Feign会通过动态代理生成实现类)
//String url = "http://demo01-provider-users/users/"+id;
//调用demo01-provider-user提供者,获取json数据,转成User对象
@FeignClient("demo01-provider-user")
public interface UserClient {
    //定义方法
     @GetMapping("/users/{id}")
     User callProvider(@PathVariable long id);
}
Feign创建动态代理类的流程图

在这里插入图片描述

  1. 编写处理器ConsumerFeignController,注入Feign客户端并使用;
@RestController
@RequestMapping("/feign")
@Slf4j
public class ConsumerFeignController {
    @Autowired
    UserClient userClient;
    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    public Object get(@PathVariable long id) throws InterruptedException {
        User user = userClient.callProvider(id);
        return user;
    }
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐