SpringCloud

微服务架构的四个核心问题:

1、服务很多,客户端该怎么访问?

2、这么多服务,服务与服务之间如何通信?

3、这么多服务,如何治理?

4、服务挂了怎么办?

spring cloud 生态:解决以上的问题

针对以上的问题,有三套解决方案:

1、Spring cloud NetFlix:一站式解决方案

使用api网关:zuul组件

Feign --HttpClient — http:通信方式,阻塞

服务注册与发现:Eureka

熔断机制:Hystrix

2、Apache Dubbo Zookeeper:半自动,需要整合别人的包

API:没有,找第三方组件或自己实现

服务通信:Dubbo

服务注册与发现:Zookeeper

熔断机制:没有,借助Hystrix

Dubbo这个方案并不完善

3、Spring cloud Alibaba:最新的一站式解决方案,更简单

新概念:服务网格:server Mesh

istio

万变不离其宗:主要核心如下

1、API(解决路由问题)

2、HTTP、RPC(解决通信问题)

3、注册和发现(解决高可用问题)

4、熔断机制(服务降级问题,防止服务雪崩)

以上问题造成的源头:网络不可靠

常见面试问题:

1.什么是微服务?

2.微服务之间是如何独立通讯的?

3.SpringCloud和Dubbo的区别?

4.SpringBoot和SpringCloud,请你谈谈对他们的理解?

  • springboot专注于快速方便的开发单个个体服务 -jar
  • springCloud是关注全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,给各个服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等集成服务
  • SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开springboot,属于依赖关系
  • SpringBoot专注于快速,方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架

5.什么是服务熔断,什么是服务降级?

6.微服务的优缺点分别是什么?请你说说在项目开发中遇到的坑?

7.你所知道的微服务技术栈有哪些?请举例一二

8.Eureka和zookeeper都可以提供服务注册与发现的功能,请你说说区别?

传统互联网架构:

image-20210806152856670

SpringCloud与Dobbo区别:

最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用基于HTTP的Rest方式

参考:

  • https://springcloud.cc/spring-cloud-netflix.html
  • 中文API文档:https://springcloud.cc/spring-cloud-dalston.html
  • SpringCloud中国社区:http://springcloud.cn/
  • SpringCloud中文网:https://www.springcloud.cc/

SpringCloud版本选择:

image-20210806154124225

image-20210806154214152

关联git:

进入E:\workspace\IDEA\Study,把之前的java中的.git拷贝到当前文件夹,并把之前push的文件也放到这里:如果不放到这里,这里之前的push的代码都会从当前的远程仓库中删除。

image-20210806214706226

git remote add origin https://github.com/msj-1995/Java.git
git push -u origin springcloud

一、项目搭建

1.1、提供者服务

1、创建普通Maven项目

image-20210806154539319

这是一个父项目,可以把src给删了

2、导入包

打包方式:pom

<!-- 打包方式 -->
<packaging>pom</packaging>

管理版本:

<properties></properties>

依赖管理:与只是用dependencies是一样的,只用使用了一个总的dependencyManagement来管理。

<dependencyManagement>
    <dependencies>
        <dependency></dependency>
    </dependencies>
</dependencyManagement>

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>com.msj</groupId>
    <artifactId>springcloud</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <!--junit版本管理-->
        <junit.version>4.12</junit.version>
        <lombok.version>1.16.10</lombok.version>
    </properties>
    <!-- 打包方式 -->
    <packaging>pom</packaging>

    <dependencyManagement>
        <dependencies>
            <!--springcloud的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <!--可以删除,也可以改为import(导入)-->
                <scope>import</scope>
            </dependency>
            <!-- 使用springCloud,需要依赖springBoot(注意版本对应):也可以使用父工程的方式导入,即parent-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 连接数据库 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!-- 数据源 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!-- springboot启动器:如果使用mybatis,需要使用mybatis-springboot的启动器 -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
            <!-- 单元测试 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <!-- 引用版本管理里的版本,以后一个方便的在properties中修改版本,而不需要来这里修改 -->
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <!--log4j日志-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
            <!--日志相关-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

如果之后Maven有问题的(资源不能导出时,还需要配置资源过滤问题)

2、新建模块

api模块:新建普通Maven模块

这时我们看到springcloud-api中依赖并没有添加进去,因为我们父项目只是管理了那些依赖,如果我们需要使用的话,在模块中还需要导入,这时导入的依赖就会指向父项目。

image-20210806162816544

导入:

springcloud-api中的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>springcloud</artifactId>
        <groupId>com.msj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-api</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

指向父项目:

image-20210806163003715

这时子项目中也已经导入依赖了,这就是依赖管理的好处(用到了再导入)

<!-- 当前自己Module需要的依赖,如果父依赖中已经配置了版本,这里就不用写了 -->
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

3、连接数据库

创建数据库

image-20210806163455859

连接数据库

创建数据库表:

image-20210806163928451

建表语句:

create table dept
(
   deptno bigint auto_increment,
   dname varchar(60) null,
   db_source varchar(60) null,
   constraint dept_pk
      primary key (deptno)
)
comment '部门表';

db_source:因为是分布式,这个字段的作用是知道以后数据是从那个表中读出的。

插入数据:

insert into dept(dname, db_source) values ('开发部', DATABASE());
insert into dept(dname, db_source) values ('人事部', DATABASE());
insert into dept(dname, db_source) values ('运维部', DATABASE());
insert into dept(dname, db_source) values ('财务部', DATABASE());
insert into dept(dname, db_source) values ('市场部', DATABASE());

DATABASE()函数用于获取数据库名

效果:修改为正确的库

image-20210806165344594

image-20210806165436386

4、实体类

在springcloud-api中新建com.msj.springcloud.api.pojo包

分布式:所有的实体类都有序列化

Dept.java

package com.msj.springcloud.api.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@NoArgsConstructor
// 支持链式写法注解
@Accessors(chain = true)
public class Dept implements Serializable {
    // Dept实体类
    private Long deptno; // 主键
    private String dname;
    // 这个字段存放使用那个数据库字段的字段, 微服务->一个服务对应一个数据库,同一个信息可能存在不同的数据库
    private String db_source;

    public Dept(String dname) {
        this.dname = dname;
    }

    /**
     * 链式写法与非链式写法
     * 非链式写法:
     * Dept dept = new Dept();
     * dept.setDeptno();
     * dept.setDname()
     *
     * 使用链式写法:
     * dept.setDeptno().setDname().setDb_source()
     */
}

以前该实体类写完要去写实现等,但是现在这个模块只写实体类,现在这个模块就是一个微服务,只写实体类。这就是服务提供者。

5、服务提供者模块

新建springcloud-provider-dept-8001的普通Maven模块,由于拆分的微服务比较多,所以我们把端口号加上,方便管理,可以把test删了

导入微服务需要的依赖:

<?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>springcloud</artifactId>
        <groupId>com.msj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-provider-dept-8001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--我们需要实体类:所以我们需要springcloud-api模块-->
        <dependency>
            <groupId>com.msj</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <!-- 日志门面 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- spring test:父工程中到如果springboot dependencies,所以这里不用写版本,与父工程的版本一致:2.1.4.RELEASE -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <!-- spring web 启动器(也是在springboot dependencies中) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 可以使用jetty作为我们的服务器:虽然跟tomcat没什么区别 (springboot dependencies中也有) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

        <!-- 热部署工具(springboot dependencies中) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

</project>

配置:在resources下新建application.yml

目录解构

image-20210806173759690

数据库-命名与实体类驼峰命名的转换:

mybatis:
    configuration:
      map-underscore-to-camel-case: true

由于我们这里没有转换,就不用配置了

数据源我们也可以使用org.gjt.mm.mysql.Driver

image-20210806200539356

该Driver继承自com.mysql.jdbc.Driver,拥有该驱动的全部功能

application.xml

server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  application:
    name: springcloud-provider-dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

mybatis核心配置文件:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 开启缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

编写接口:

包解构:com.msj.springcloud.mapper

DeptMapper接口

package com.msj.springcloud.mapper;

// 注意:这里的Dept是通过pom导入的,因为它属于另一个模块
import com.msj.springcloud.api.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

// 表示这个一个Mapper接口
@Mapper
// 让spring托管
@Repository
public interface DeptMapper {
    public boolean addDept(Dept dept);

    public Dept queryById(Long id);

    public List<Dept> queryAll();
}

编写Mapper文件:

在resources/mybatis/mapper下新建:

DeptMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.msj.springcloud.mapper.DeptMapper">
    <insert id="addDept" parameterType="dept">
        insert into dept(dname, db_source) values(#{dname},DATABASE());
    </insert>
    <select id="queryById" resultType="dept" parameterType="Long">
        select * from dept where deptno = #{deptno};
    </select>
    <select id="queryAll" resultType="dept">
        select * from dept;
    </select>
</mapper>

Service层:

在com.msj.springcloud.service中写:

DeptService接口

package com.msj.springcloud.service;

import com.msj.springcloud.api.pojo.Dept;

import java.util.List;

public interface DeptService {
    public boolean addDept(Dept dept);

    public Dept queryById(Long id);

    public List<Dept> queryAll();
}

DeptServiceImpl实现类

package com.msj.springcloud.service;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.mapper.DeptMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Override
    public boolean addDept(Dept dept) {
        return deptMapper.addDept(dept);
    }

    @Override
    public Dept queryById(Long id) {
        return deptMapper.queryById(id);
    }

    @Override
    public List<Dept> queryAll() {
        return deptMapper.queryAll();
    }
}

controller层:提供Restful服务

包结构:com.msj.springcloud.controller

DeptController.java

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;
    @PostMapping("/dept/add")
    public boolean addDept(Dept dept) {
        return deptService.addDept(dept);
    }

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id) {
        return deptService.queryById(id);
    }

    @GetMapping("/dept/list")
    public List<Dept> queryAll() {
        return deptService.queryAll();
    }
}

在com.msj.springcloud下写一个主启动类:

DeptProvider_8001.java

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 启动类
@SpringBootApplication
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class, args);
    }
}

启动:查询全部

image-20210806210959765

通过id查询:

image-20210806211018514

添加:不能添加,因为通过浏览器?传参属于get提交,而我们后台是post的方式的提交,所以不能提交上,这也比较安全。

待会我们的服务消费者只要能拿到这个生产者的接口就可以操作生产者了。

1.2、消费者服务

新建模块:springcloud-consumer-dept-80

1、导包:只要一些实体类和web相关的包

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>springcloud</artifactId>
        <groupId>com.msj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-dept-80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- 实体类 -->
        <dependency>
            <groupId>com.msj</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

</project>

2、配置文件

application.yml:只用配置一个端口号即可

server:
  port: 80

3、消费者接口

包结构:com.msj.springcloud.controller

类:DeptConsumerController.java

理解:消费者不应该有service层

springboot支持Restful风格的请求:springboot中封装了RestTemplate模板,里面的方法供我们直接调用

image-20210807104313261

RestTemplate并没有@Bean,我们需要手动注册到spring中。

可供使用的方法

手动注册bean,在包com.msj.springcloud.config中新建ConfigBean类:

package com.msj.springcloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

DeptConsumerController.java

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class DeptConsumerController {
    // 注入RestTemplate 参数: (url, 实体:Map, Class<T> responseType),responseType即请求后返回的数据类型,注意是class对象
    @Autowired
    private RestTemplate restTemplate; // 提供多种便捷访问远程http服务的方法,简单的Restful服务模板

    // 请求地址的前缀
    private static final String REST_URL_PREFIX = "http://localhost:8001";

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        // 可以使用getForObject,也可以使用getForEntity
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
    }
}

主启动类:

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class);
    }
}

先启动provider(服务提供者)服务,在启动消费者测试:

localhost/consumer/dept/get/1

image-20210807111434378

localhost/consumer/dept/list

image-20210807111449955

添加:localhost/consumer/dept/add?dname=“小蕾"

image-20210807111617574

数据进来了,但是插入错误,可能是某处配置错误导致的:

二、Eureka

Eureka:服务注册与发现

Eureka是NetFlix的一个子模块。

2.1、原理讲解

image-20210807131433740

  • Eureka的基本架构
    • springcloud封装了NetFlix公司开发的Eureka模块来实现服务注册和 发现(对比Zookeeper)
    • Eureka采用了C-S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心
    • 而系统中的其他微服务,使用Eureka的客户端来年街道EurekaServer并维持心跳连接,这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,springcloud的一些其他模块(比如Zuul)就可以铜鼓偶EurekaServer来发现系统中的其他微服务,并执行相关逻辑。
    • Eureka包含两个组件:EurekaServer和EurekaCLient

image-20210807130658558

  • 三大角色
    • Eureka Server:提供服务的注册与发现。zookeeper
    • Service Provider:将自身的服务注册到Eureka中,从而使消费方能够找到。
    • Service Consumer:服务消费方从Eureka中获取注册服务列表,从而找到消费服务。

之前我们是通过下载zookeeper服务,然后通过bin中的exe启动服务,现在Eureka是通过springcloud的微服务方式(自己写,通过注解即可启动):

新建模块:springcloud-eureka-7001

1、导包:

只需要导入Eureka的包即可:这里到服务的包

<?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>springcloud</artifactId>
        <groupId>com.msj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-eureka-7001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

</project>

2、配置文件:application.yml

image-20210807132750191

image-20210807132811217

使用eureka.client.service-url.defaultZone:覆盖默认的端口

application.yml

server:
  port: 7001

# Eureka的配置
eureka:
  instance:
    hostname: localhost  # Eureka服务端的实列名称
  client:
    register-with-eureka: false  # 表示是否向eureka注册中心注册自己(服务器不用注册自己)
    fetch-registry: false  # 如果fetch-registry为false,表示自己为注册中心
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka  # 这就是监控页面地址:hostname和port(端口)都是从配置文件中动态获取的

主启动类:包解构:com.msj.springcloud

在该包下新建主启动类:EurukaServer_7001.java

这样Eureka服务配置成功了(注册中心)

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
// 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class, args);
    }
}

locahost:7001就进入我们刚才配置的服务监控页面了

image-20210807133746646

2.2、服务注册

1、在springcloud-provider-dept-8001(服务提供者)中加入Eureka的依赖(因为我们要把该服务注册到Eureka中)

<!-- Eureka依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

2、Eureka的配置

server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  application:
    name: springcloud-provider-dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka

3、去主启动类中加开启EurekaClient的注解

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

// 启动类
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class, args);
    }
}

只要我们加了@EnableEurekaClient注解,该服务就会被注册到Eureka的注册中心。

先启动7001,再启动8001测试:

image-20210807135901728

image-20210807135918895

status中的连接指向了一个连接:这个链接待会可以给他去掉

image-20210807140024841

通过配置修改Satus:

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8001

已修改:

image-20210807141130946

当把8001强制停止(7001仍然开着)过了30s(或者是重新启动8001),可以发现监控页面上出现错误提示:

image-20210807141401952

这就是Eureka的自我保护机制:即某个服务突然崩了,但可能还有其他服务在连接着,这时Eureka是不会让这个服务停止的。

自我保护机制:好死不如赖活

某时刻某个微服务不可以用了,eureka不会立刻清理,一九会对该微服务的信息进行保存。

  • 默认情况下,Eureka在一定时间内没有接收到某个服务实例的心跳,Eureka将会注销该实例(默认90秒),但当网络分区发生故障时,微服务与Eureka之间无法进行正常通信,这时注销行为可能变得非常危险,因为微服务本身是健康的,知识网络故障。此时不应该注销这个服务,Eureka通过自我保护机制来解决这个问题-当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式,一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不在删除服务注册表中的信息(也就是不注销任何微服务),当网络故障恢复时,该EurekaServer节点会自动退出自我保护模式。
  • 在springCloud中,可以使用eureka.server.enable-self-preservation = false禁用自我保护机制(不推荐)

配置完善监控信息:

默认指向点击的网址是无法访问的

1、springcloud-provider-dept-8001服务中导入actuator依赖

actuator:执行机构

从org.springframework.boot中导入,不需要写版本号

<!-- actuator:完善监控信息 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、配置

server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  application:
    name: springcloud-provider-dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息

# 完善监控信息配置
info:
  app.name: msj-springcloud  # 项目名字(当然没什么作用,不配也可以)
  company.name: msj  # 公司名字

3、该微服务中增加一个controller

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    // 获得一些配置信息,得到具体的微服务
    @Autowired
    private DiscoveryClient discoveryClient;

    @PostMapping("/dept/add")
    public boolean addDept(Dept dept) {
        return deptService.addDept(dept);
    }

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id) {
        return deptService.queryById(id);
    }

    @GetMapping("/dept/list")
    public List<Dept> queryAll() {
        return deptService.queryAll();
    }

    // 注册进来的微服务,我们可以获取一些消息,可以获取我们配置文件中配置的info
    @GetMapping("/dept/discovery")
    public Object discovery() {
        // 获得微服务列表的清单
        List<String> service = discoveryClient.getServices();
        System.out.println("discover=>service:" + service);

        // 得到一个具体的微服务信息:通过具体的微服务id,也就是applicationName
        List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-provider-dept");
        for(ServiceInstance instance : instances) {
            System.out.println(instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri() + "\t" + instance.getServiceId());
        }
        return this.discoveryClient;
    }

}

主启动类:开启服务发现

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class, args);
    }
}

热更新:

image-20210807150234545

测试:

image-20210807150433319

info信息:localhost:8001/actuator/info

只是Eureka监控界面上的给出的地址不是localhost,而是主机名,以后可以自己修改。

image-20210807163237141

image-20210807150505221

2.3、Eureka集群配置

1、新建springcloud-eureka-7002微服务

springcloud-eureka-7003

2、依赖跟7001是一样的

3、配置文件只是修改一下端口

image-20210807153449279

当前集群:就是要让7001中绑定7002和7003,这样当其中某一个出现问题时才可以通知其他。

修改C:\Windows\System32\drivers\etc下的host文件,使eureka700x.com都映射到127.0.0.1上

image-20210807161557420

集群配置修改:集群需要关联其他注册中心

7001:application.yml

server:
  port: 7001

# Eureka的配置
eureka:
  instance:
    hostname: eureka7001.com  # Eureka服务端的实列名称:eureka7001本质上还是localhost(127.0.0.1)
  client:
    register-with-eureka: false  # 表示是否向eureka注册中心注册自己(服务器不用注册自己)
    fetch-registry: false  # 如果fetch-registry为false,表示自己为注册中心
    service-url:
      # 集群:关联其他的注册中心
      defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka # 关联7002和7003注册中心

7002:application.yml

server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka  # 关联7001和7003注册中心

7003类似:

访问7001:挂在了7002和7003

image-20210807163004654

访问eureka.com:7002

image-20210807163145645

启动服务提供者:8001

image-20210807163442206

这时每个注册中心都会注册上服务,如果某个注册中心突然关了,其他的注册中心任然可以正常运行。

消费者倒是一样的。

2.4、CAP原则

CAP?

  • C(Consistency)强一致性
  • A(Availability)可用性
  • P(Partition tolerance) 分区容错性

CAP的三进二:CA,AP,CP

CAP:一个分布式系统不可能同时很好的满足CAP原则,一般只要满足其中两个。

根据CAP原理,将NoSql数据库分成了满足CA原则,CP原则和AP原则三大类:

  • CA:单点集群,满足一致性、可用性的系统,通常可扩展性差
  • CP原则:满足一致性、分区容错性的系统,通常性能不是特比高
  • AP:满足可用性、分区容错性的系统,通常可能对一致性要求较低。

由于分区容错性P在分布式系统中是必须保证的,一次我们只能在A和C之间进行权衡。

  • Zookeeper:保证的CP
  • Eureka:保证的是AP

image-20210807164934191

三、Ribbon

spring Cloud Ribbon是基于NetflixRibbon实现的一套”客户端负载工具“

Ribbon的客户端提供一系列完整的配置,如:连接超时,重试等等,简单的说,就是在配置文件中列出LoadBalancer(简称:LB),后边所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接服务器,我们也容易使用Ribbon实现自定义的负载均衡算法。

Ribbon是在客户端配置,我们刚才的所有微服务中只有80端口的服务属于客户端,其他都属于服务端。

image-20210807201449547

Ribbon能干什么

  • LB:负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。
  • LB简单的说就是将用户的请求平摊到多个服务上,从而达到系统的HA(高可用),常见的负载均衡软件有Nginx,Lvs等
  • dubbon,SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡可以自定义。
  • 负载均衡的简单分类:
    • 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由于设施负责把访问的请求通过某种策略转发至服务器提供方。
    • 进程式LB:
      • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地中选出一个合适的服务器。
      • Ribbon就属于进程内LB,他只是一个类库,集成与消费方进程,消费方通过他来获取服务方提供的地址。

3.1、集成Ribbon

1、导入依赖

在消费服务方(springcloud-consumer-dept-80)导入依赖:我们要选择springcloud的ribbon,由于Ribbon需要从注册中心去拿到有哪些服务地址,所以消费者服务中还要导入Eureka

<!-- Ribbon -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<!-- Eureka -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

2、编写配置

配置Eureka

server:
  port: 80

# eureka配置
eureka:
  client:
    register-with-eureka: false # 不向eureka注册自己,因为消费者不提供服务
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka,http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka #客户端可以从三个服务种拿服务

主启动类开启Eureka:

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class, args);
    }
}

3、配置Ribbon

在config下的ConfigBena种配置,只需要加入一个注解@LoadBalanced即可

ConfigBean.java

package com.msj.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {
    // 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

修改controller下的DeptConsumerController.java

通过服务名来访问:我们这里只有一个服务提供者,但这个服务提供者有三个Eureka服务都注册类,因此通过服务名,Ribbon就会去找有哪几个注册中心有该服务,然后就可以使用不同的注册中心来访问该服务。

服务名:可以大写,也可以小写(Eureka监控页面的都是大写)

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class DeptConsumerController {
    // 注入RestTemplate 参数: (url, 实体:Map, Class<T> responseType),responseType即请求后返回的数据类型,注意是class对象
    @Autowired
    private RestTemplate restTemplate; // 提供多种便捷访问远程http服务的方法,简单的Restful服务模板

    // 请求地址的前缀
    // private static final String REST_URL_PREFIX = "http://localhost:8001";
    // 使用Ribbon后我们这里的地址应该是一个变量,通过服务名来访问
    private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        // 可以使用getForObject,也可以使用getForEntity
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
    }
}

我们这里测试:7001,7002,8001(先启动),7003就不启动了(防止卡),在启动80

7001和7002都有服务注册了:

image-20210807205235908

启动80消费者:现在还不能体会集群的好处,因为服务方只有一个,所有的服都是到一个数据库种去取东西

访问:localhost/consumer/dept/list

可以查到数据:

image-20210807205607825

Ribbon和Eureka整合以后,客户端可以直接钓友接口,通过服务名调用服务,不用关心IP地址和端口。

3.2、Ribbon实现负载均衡

多写几个提供者服务,让集群可以看到不同请求走不同的服务。即服务提供者变多。

image-20210807210232288

1、再建两个数据库:springclouddb02和springclouddb03

image-20210807211856198

sql语句:

DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept`  (
  `deptno` bigint(20) NOT NULL AUTO_INCREMENT,
  `dname` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `db_source` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`deptno`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
INSERT INTO `dept` VALUES (1, '开发部', DATABASE());
INSERT INTO `dept` VALUES (2, '人事部', DATABASE());
INSERT INTO `dept` VALUES (3, '运维部', DATABASE());
INSERT INTO `dept` VALUES (4, '财务部', DATABASE());
INSERT INTO `dept` VALUES (5, '市场部', DATABASE());

现在三个表的核心是data_source不一样

image-20210807212750646

2、再创建两个服务提供者:8002和8003

导入依赖:依赖与8001一样

修改配置:8002配置,需要修改端口号,instance-id,连接的数据库

注意:他们的服务名都是一样的,三个服务名称一样是前提,但是他们请求的数据库地址不一样了。

server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  application:
    name: springcloud-provider-dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息

# 完善监控信息配置
info:
  app.name: msj-springcloud  # 项目名字(当然没什么作用,不配也可以)
  company.name: msj  # 公司名字

8003配置:

server:
  port: 8003
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  application:
    name: springcloud-provider-dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb03?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8003 # 修改Eureka上的默认描述信息

# 完善监控信息配置
info:
  app.name: msj-springcloud  # 项目名字(当然没什么作用,不配也可以)
  company.name: msj  # 公司名字

把mybatis配置都拷贝过来,不用修改。

同时把java下的东西也拷贝过来。修改启动类的名称,其他都不用改

8002

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProvider_8002 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8002.class, args);
    }
}

8003

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProvider_8003 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8003.class, args);
    }
}

测试:可以只启动一个注册中心(两个,三个也可以)

启动服务提供者:8001,8002,8003

UP(3):三个服务都在线(即都注册到注册中心了)

image-20210807214701901

启动消费者:80

访问list测试:

第一次从db03查到的

image-20210807214950905

第二次是从db02查的

image-20210807214910855

多次测试发现:db01->db02->db03-db01…,循环使用不同的服务(默认:轮询)

3.3、自定义负载均衡算法

我们可以通过IRule接口实现Ribbon的负载均衡算法

IRule接口:

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

实现类如下,默认是轮询:

image-20210807215408895

  • AbstractLoadBalancerRule,抽象实现类,一般使用它
  • AvailabilityFilteringRule:会先过滤掉崩溃的服务(跳闸的服务),对剩下的活着的服务使用轮询。
  • BestAvailableRule
  • ClientConfigEnableRoundRobinRule
  • RoundRobinRule:轮询策略(默认的)
  • RandomRule:随机策略
  • WeightedResponseTimeRule:根据配置的规则权重选服务
  • RetryRule:重试策略,会先按照轮询获取服务,如果获取失败,则会在指定的时间内进行重试

以上常用的几种策略都继承了抽象类:AbstractLoadBalancerRule

我们自定义负载均衡算法时也是集成该抽象类就可以了。

把轮询策略改为随机策略:只需要在ConfigBean中注册随机策略的Bena即可:

随机策略:

package com.msj.springcloud.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {
    // 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule myRule() {
        return new RandomRule();
    }
}

启动测试:这里只启动了一个注册中心,3个服务提供者,和一个消费者:

这里的服务是随机的。

自定义负载均衡规则:

我们自己写的负载均衡规则不能跟主启动类同级或是写在主启动类的下面(这样会被自动扫描,我们就体验不到Ribbon注解的作用),我们应该写在主启动类的上一级:这样的话我们的组件和服务代码实现了分离

官网说明:

image-20210808125357471

我们在com.msj下建一个包:myrule

我们在包com.msj.myrule下新建自己的规则:

官方源码随机策略源码:

RandomRule.java

package com.msj.myrule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class MyRandomRule extends AbstractLoadBalancerRule {
    public MyRandomRule() {
    }
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                // 获得活着的服务
                List<Server> upList = lb.getReachableServers();
                // 获得全部服务
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }

                // 生成区间随机数
                int index = this.chooseRandomInt(serverCount);
                // 从活着的服务中,随机获取一个服务
                server = (Server)upList.get(index);
                // 如果没有服务,线程礼让后继续
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

自己想实现的功能:每个服务访问5次,之后再换下一个服务

MyRandomRule.java

解构:

image-20210808131619706

MyRandomRule.java

package com.msj.myrule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class MyRandomRule extends AbstractLoadBalancerRule {
    // 每个服务访问5次,换下一个任务(我们这里有三个任务)

    // total=0,默认0,如果为5,指向下一个服务节点

    // index=0,默认0,如果total=5,index++,如果index>=0,index置零

    private int total = 0; // 被调用的次数
    private int currentIndex = 0;  // 当前谁在提供服务


    public MyRandomRule() {
    }
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                // 获得活着的服务
                List<Server> upList = lb.getReachableServers();
                // 获得全部服务
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }

                // 生成区间随机数
                // int index = this.chooseRandomInt(serverCount);
                // 从活着的服务中,随机获取一个服务
                // server = (Server)upList.get(index);
                // 如果没有服务,线程礼让后继续

                // -==========自定义代码===============
                if(total < 5) {
                    server = upList.get(currentIndex);
                    total++;
                }else {
                    total = 0;
                    currentIndex++;
                    if(currentIndex >= upList.size()) {
                        currentIndex = 0;
                    }
                    // 从活着的服务中获取指定的服务来操作
                    server = upList.get(currentIndex);
                }
                // -============算法结束===============

                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

在config下的ConfigBena中使用自己定义的负载均衡策略:

package com.msj.springcloud.config;

import com.msj.myrule.MyRandomRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {
    // 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule myRule() {
        return new MyRandomRule();
    }
}

测试也预想的一样。

3.3、使用接口方式调用服务

Feign负载均衡

简介:feign是声明式的web service客户端,他让微服务之间的调用变得更简单了,类似controller调用service。spring cloud继承了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。

使用:只需要创建一个接口,然后添加注解即可:

feign:调用微服务访问的两种方法:

1、微服务名字:【ribbon】

2、接口和注解:【feign】

image-20210808132345360

我们把springcloud-consumer-dept-80代码拷贝一份,还是按80的方式创建消费者(目的是保留原来的代码):该消费之的名字为:springcloud-consumer-dept-feign,端口也可以不变

依赖需要在原来的基础上添加Feign的依赖:

<!-- Feign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

为了区别我们把主启动类修改一线,并且把Ribbon的注解去掉,因为我们不使用Ribbon实现了,要使用Feign:

FeignDeptConsumer_80.java

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class FeignDeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignDeptConsumer_80.class, args);
    }
}

现在已经没有Ribbon的服务了,最多是有一个Ribbon的轮询策略:

ConfigBena.java

package com.msj.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {
    // 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

现在去spring-cloud-api中再建一个包:com.msj.springcloud.api.service,在该包下写我们提供的服务:这是一个接口,不需要实现

把Feig的依赖也导入:

<!-- Feign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

写服务接口:DeptClientService.java

package com.msj.springcloud.api.service;

import com.msj.springcloud.api.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Component
@FeignClient(value = "springcloud-provider-dept")
public interface DeptClientService {
    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id);

    @GetMapping("/dept/list")
    public List<Dept> queryAll();

    @PostMapping("/dept/add")
    public boolean addDept(Dept dept);
}

这里Feign做的工作跟Ribbon是一样的,其底层还是调用Ribbon实现负载均衡,只是把面向接口编程做到了很好。

修改DeptConsumerController.java

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.api.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DeptConsumerController {
    @Qualifier("com.msj.springcloud.api.service.DeptClientService")
    @Autowired
    private DeptClientService service;

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        return this.service.addDept(dept);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        return this.service.queryById(id);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {

        return this.service.queryAll();
    }
}

这里使用注入的方式调用

主启动类开启Feign支持:

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@EnableEurekaClient
// 开启Feign:它会使指定的包下的@FeignClient生效,这里我们从pox.xml中导入了api,所以api服务下的service中的类的FeignClient也会生效。
@EnableFeignClients(basePackages = {"com.msj.springcloud"})
@ComponentScan("com.msj.springcloud")
public class FeignDeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignDeptConsumer_80.class, args);
    }
}

四、Hystrix

4.1、服务降级

Hystrix:服务熔断

复杂分布式体系结构中应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。

服务雪崩

image-20210808143616648

微服务链路中,如果其中某个服务坏了,则整个服务就崩了。

解决办法:弃车保帅

Hystrix:是一个用于处理分布式系统延迟和容错的开源库,许多依赖不可避免的会调用失败,比如超速,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

断路器:本身是一种开关装置,当某个服务单元发生故障的时候,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的,可以处理备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

Hystrix能干嘛:

  • 服务降级
  • 服务熔断
  • 服务限流
  • 接近实时监控

我们也可以自己实现这个组件:

通过异常抛出和异步服务,当服务出现问题的时候去调用其他的服务。

服务熔断:

熔断机制对应雪崩效应的一种微服务链路保护机制。

当扇出链路出现服务不可用或者响应时间太长,会进行服务降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路,在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand

我们针对8001服务重新写一个Hystrix版本的服务提供者:

直接复制一份即可:微服务名字:springcloud-provider-dept-hystrix-8001

修改一下主启动类:

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProviderHystrix_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderHystrix_8001.class, args);
    }
}

复制完成后,要使用Hystrix:

1、导入依赖:

<!-- Hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

2、增加hystrix的配置:修改instance-id一下就可以了

server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  application:
    name: springcloud-provider-dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-hystrix-dept8001 # 修改Eureka上的默认描述信息

# 完善监控信息配置
info:
  app.name: msj-springcloud  # 项目名字(当然没什么作用,不配也可以)
  company.name: msj  # 公司名字

3、修改DeptController

方案一:

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.api.service.DeptClientService;
import com.msj.springcloud.service.DeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
    @Autowired
    private DeptClientService deptClientService;

    @GetMapping("/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        Dept dept = deptClientService.queryById(id);
        // 用户不存在,则抛出一个异常
        if(dept == null) {
            throw new RuntimeException("id=>" + id + ",不存在该用户,或者信息无法找到......");
        }
        return dept;
    }
    
}

如果出现问题,则抛出异常,程序在运行时捕获该异常后继续去执行相关的代码。(如果异常不捕获,代码就直接崩了)

方案二:服务出现异常时调用备用方法

Hystrix熔断

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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.RestController;

// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    @GetMapping("/dept/get/{id}")
    @HystrixCommand(fallbackMethod = "hystrixGet")
    public Dept get(@PathVariable("id") Long id) {
        Dept dept = deptService.queryById(id);
        // 用户不存在,则抛出一个异常
        if(dept == null) {
            throw new RuntimeException("id=>" + id + ",不存在该用户,或者信息无法找到......");
        }
        return dept;
    }

    // 如果出现异常:则调用备选方案
    public Dept hystrixGet(@PathVariable("id") Long id) {
        return new Dept()
                .setDeptno(id)
                .setDname("id=>" + id + "没有对应的信息,null--@Hystrrix")
                .setDb_source("没有这个数据库");
    }
}

使用Hystrix后,遇到异常就会去执行HystrixCommand指向的方法。该方法会返回一个Dept,该Dept最终以RestController的方式显示到页面

主启动类开启Hystrix:

package com.msj.springcloud;

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.netflix.eureka.EnableEurekaClient;

// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
// 开启Hystrix服务
@EnableCircuitBreaker
public class DeptProviderHystrix_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderHystrix_8001.class, args);
    }
}

启动注册中心,启动hystrix_8001,启动80,测试访问不存在的id:

localhost/consumer/dept/get/8

image-20210808153722447

这样的话不报错,直接返回Hystrix的写入数据库的信息(并不会写入数据库)

这样的话:如果有100个controller,就会要写100个备选的方法,比较麻烦。

如果不使用Hystrix,使用抛出异常的方式,修改springcloud-provider-8001中的DeptController:

package com.msj.springcloud.controller;

import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    // 获得一些配置信息,得到具体的微服务
    @Autowired
    private DiscoveryClient discoveryClient;

    @PostMapping("/dept/add")
    public boolean addDept(Dept dept) {
        return deptService.addDept(dept);
    }

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id) {

        Dept dept = deptService.queryById(id);
        if(dept == null) {
            throw new RuntimeException("id=>" + id + ",不存在该用户,或者信息无法找到......");
        }
        return dept;
    }

    @GetMapping("/dept/list")
    public List<Dept> queryAll() {
        return deptService.queryAll();
    }

    // 注册进来的微服务,我们可以获取一些消息,可以获取我们配置文件中配置的info
    @GetMapping("/dept/discovery")
    public Object discovery() {
        // 获得微服务列表的清单
        List<String> service = discoveryClient.getServices();
        System.out.println("discover=>service:" + service);

        // 得到一个具体的微服务信息:通过具体的微服务id,也就是applicationName
        List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-provider-dept");
        for(ServiceInstance instance : instances) {
            System.out.println(instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri() + "\t" + instance.getServiceId());
        }
        return this.discoveryClient;
    }

}

启动测试:访问不存在的id,直接报错

image-20210808155141048

后台也崩了:

image-20210808155153988

服务指向的信息的ip地址的配置:prefer-ip-address: false

image-20210808155756490

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7002/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-hystrix-dept8001 # 修改Eureka上的默认描述信息
    prefer-ip-address: true

显示的ip地址:

image-20210808160302534

点击:

image-20210808160319277

正确跳转到:

image-20210808160347197我们点进prefer-ip-address: true的源码可以看到我们可以走两个地址:

192.168.43.62:8001/actuator/info

192.168.43.62:8001/actuator/health

走后面这个连接会下载一个东西,如果服务正常的时候Status为UP,即服务或者的时候Status为UP

image-20210808160937816

4.2、关于启动的一些问题

上面的代码,所有的服务(8001、8002,8003)都是朝7001注册中心注册服务的,如果我们某个时候没有启动7001注册中心进行测试,则发现所有的服务都不能启动,原因是注册中心连接失败,因为这里的服务都是先注册到7001注册中心,然后再通过集群复制到其他注册中心的,所以这个时候只启动了7002注册中心,我们应该把服务的注册中心地址修改为7002的,其他同理:

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7002/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-hystrix-dept8001 # 修改Eureka上的默认描述信息

4.3、服务降级

图中:A、B、C是三个服务,矩形中的小圆圈表示该服务中的一些组合的微服务。

image-20210808162249880

服务降级:整体的服务资源不够用了,先关闭一些服务,保证一些服务的可用性。

如很多请求访问A,访问B、C请求很少,这时我们可以关闭B、C中的一些服务,让服务器A可以占用更多的资源,从而提高A的高并发,当高并发的时期过了,再把B、C的服务开启。

image-20210808162621737

这也是跟客户端有关,当服务端某个服务关闭后,客户端再去访问的时候,服务降级会告诉该客户的该服务不可用。

使用:

1、我们首先在springcloud-api服务中添加一些跟失败有关的回调函数:

在service包下新建:DeptClientServiceFallbackFactory.java,该类要实现Feign下的FallbackFactory类。

前面Hystrix处理的一个方法,这里处理的是一个类,使用使用工厂方法。

package com.msj.springcloud.api.service;

import com.msj.springcloud.api.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

// 服务降级
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
    @Override
    // 这里是处理一个类,要处理哪个类的回调,就返回哪个类,由于这里的DeptClientService是一个接口,不能new,就不能返回该类的对象,所以我们要先实现DeptClientService接口,或者是重写该接口中的方法,如下
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept queryById(Long id) {
                // 这里的操作跟Hystrix中的一样
                return new Dept()
                        .setDeptno(id)
                        .setDname("id=>" + id + ",没有对应的信息,客户端提供了降级的信息,该服务现在已经被关闭,不可用......")
                        .setDb_source("no Database");
            }

            @Override
            public List<Dept> queryAll() {
                List<Dept> list = new ArrayList<>();
                list.add(new Dept()
                        .setDeptno(1L)
                        .setDname("queryAll=>没有对应的信息,客户端提供了降级的信息,该服务现在已经被关闭,不可用......")
                        .setDb_source("no Database"));
                return list;
            }

            @Override
            public boolean addDept(Dept dept) {
                return false;
            }
        };
    }
}

2、在DeptClientService接口中使用FeignClient注解中的fallbackFactory属性指定请求失败时的回调函数。

package com.msj.springcloud.api.service;

import com.msj.springcloud.api.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Component
@FeignClient(value = "springcloud-provider-dept", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id);

    @GetMapping("/dept/list")
    public List<Dept> queryAll();

    @PostMapping("/dept/add")
    public boolean addDept(Dept dept);
}

3、我们这里使用feign的80端口的客户端中配置开启降级服务

修改springcloud-consumer-dept-fein微服务中的配置文件:增加开启服务降级的配置

server:
  port: 80

# eureka配置
eureka:
  client:
    register-with-eureka: false # 不向eureka注册自己,因为消费者不提供服务
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka,http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka #客户端可以从三个服务种拿服务

# 开启降级服务
feign:
  hystrix:
    enabled: true

4、启动测试:

启动7001注册中心,带Feign的8001服务和80消费者:

image-20210808170212240

现在服务一切正常,现在把8001服务关了:显示服务关闭信息

image-20210808170318802

image-20210808170750129

处理服务异常的两种方式:服务熔断和服务降级,两种方式都是可以的。

服务熔断与服务降级对比:

服务熔断:服务端,某个服务超时或者异常,引起熔断,。

服务降级:客户端,从整体的网站请求负载考虑,当某个服务熔断或者关闭之后,服务不在被调用(直接不走服务器,通过客户端返回),我们要在客户端准备一个fallbackFactory,返回一个默认的值(缺省值),这时整体的服务水平下降了(但是至少能用,比挂掉强)

4.4、DashBoard流量监控

也消费者相关,客户端。他是一个监控服务

1、新建一个微服务:springcloud-consumer-hystrix-dashboard

把80端口的依赖拿过来,并增加如下依赖:Hystrix和Hystrix的Dash board监控相关的依赖。

<!-- Hystrix依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<!-- dashboard依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

2、配置:以上两个依赖导入就可以了,什么也不用配置就可以使用了

修改一下端口号

server:
  port: 9001

3、主启动类:com.msj.springcloud包下

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
// 开启监控页面
@EnableHystrixDashboard
public class DeptConsumerDashBoard_9001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumerDashBoard_9001.class, args);
    }
}

注意:要使客户端能够监控到,服务端8001,8002,8003需要导入晚上监控信息的依赖:actuator

启动:访问Hystrix Dashboard

image-20210808172927120

image-20210808173056026

配置要监控的信息:

1、启动的服务提供者需要增加一个DashBoard的bean:

首先需要在服务提供者中导入对应的依赖:如在hystrix_8001中导入Hystrix的包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

然后在主启动类中通过Servlet注册一个Bean:

Metrics:度量,指标

package com.msj.springcloud;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
// 开启Hystrix服务
@EnableCircuitBreaker
public class DeptProviderHystrix_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderHystrix_8001.class, args);
    }

    // 增加一个servlet
    @Bean
    public ServletRegistrationBean hystrixMetricsStreamServlet() {
        ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new HystrixMetricsStreamServlet());
        // 添加监控的路径:/actuator/hystrix.stream,我们只要填这个路径就行了
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        return registrationBean;
    }
}

addUrlMappings中url地址的来源:

image-20210808184204283

注意:1、DashBoard需要Hystrix服务开启才可以用

2、使用hystrix_8001不能访问/consumer/dept/list和add,因为在该微服务中还没有实现对应的方法。

启动测试:启动7001注册中心,hystrix_8001,80端口

访问:localhost/consumer/dept/get/1

image-20210808190734933

访问了上面的地址后,再访问:localhost:8001/actuator/hystrix.stream

我们就可以看到他监控到的一些流:

image-20210808190839417

去到localhost:9001/hystrix,我们输入刚才的地址:localhost:8001/actuator/hystrix.stream

Delay和Title可以随便填,之后就可以监控了:

image-20210808191038049

点击监控流:Monitor Stream:

image-20210808191126833

我们多次刷新访问:localhost/consumer/dept/get/1

会发现监控六种的圆圈再逐渐变大:

image-20210808191319071

如果不刷新,则该圆圈又会变小:

  • 一圆

实心圆:有两种含义,它通过颜色的变化代表了实例的健康程度

健康程度从绿色<黄色<红色 递减

该实心圆除了颜色的变化外,它的大小也会根据实例的请求流量变化,流量越大,该实心圆就越大。所以通过实心圆的表示,可以在大量的实例中快速发现故障实例和高压实例。

  • 一线

曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。

image-20210808192002592

  • 整图说明

image-20210808192023937

image-20210808192151248

五、路由网关

image-20210808192655011

网关是终端访问服务的第一个关口(即最接近前端的服务)

5.1、Zuul路由网关

概述:Zuul包含了对请求的路由和过滤两个最主要的功能:

其中路由功能负责外部请求转发到具体微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则是负责对请求的处理过程进行干预,是实现请求校验,服务器聚合功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转获得的。

注意:Zuul服务最终还是会注册到Eureka

提供:代理+路由+过滤三大功能

Zuul能干嘛?

  • 路由
  • 过滤

官方文档:https://github.com/Netfilx/zuul

以前通过controller跳转,现在可以通过Zuul跳转。

比如之前有8001、8002、8003三个服务提供者,我们要访问8001的服务,只能通过输入localhost:8001访问,8002只能通过输入localhost:8002访问,8003同理,这时通过路由网关,我们可以屏蔽服务器的真实地址,也可以统一访问方式。

5.2、项目搭建

1、新建springcloud-zuul-9527

2、导入依赖:把dashboard中的依赖放进去,并添加zuul的依赖,如下

<dependencies>
    <!-- zuul -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zuul</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- Hystrix依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- dashboard依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- Ribbon -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- Eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- 实体类 -->
    <dependency>
        <groupId>com.msj</groupId>
        <artifactId>springcloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 热部署 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>

2、配置文件application.yml(这里也可以写bootstrap.yml)

server:
  port: 9527
spring:
  application:
    name: springcloud-zuul  # 微服务的名字
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: zuul9527.com
    prefer-ip-address: true # 详细信息显示ip
info:
  app.name: msj-springcloud
  company.name: msj.com

3、主启动类

在com.msj.springcloud下写:

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// 开启Zuul服务代理
@EnableZuulProxy
public class ZuulApplication_9527 {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication_9527.class, args);
    }
}

启动7001,8001(可以是带熔断的hystrix_8001,也可以是普通的8001),启动80,启动9527

访问:localhost/consumer/dept/get/2

image-20210808201621576

由于刚才我们配置hosts文件,erueka7001.com, erueka7002.com, erueka7003.com都是指向127.0.0.1(即localhost),所以我们也可以通过下面的方式访问: eureka7002.com/consumer/dept/get/2

image-20210808201814900

以上两种方式的默认端口都是80端口,由于我们配置了Zuul路由网关,所以我们通过localhost:9527,erueka7001.com:9527, erueka7002.com:9527, erueka7003.com:9527,在加上微服务的名字也可以访问服务:

localhost:9527/springcloud-provider-dept/dept/get/1

注意:现在是通过微服务去访问服务器的,跳过了80消费端,所以我们访问的路径中少了/consumer,我们通过Zuul直接拿到了服务端hytrix_8001或者是8001,8002,8003中的服务。

注意:只是启动的是hystrix_8001的话:localhost:9527/springcloud-provider-dept/dept/list 和 add方法仍不能使用,因为没有实现。

但是这样的话有一个坏处:就是微服务的名字暴露了,我们可以通过配置屏蔽微服务的名字;

修改9527微服务中的配置:

server:
  port: 9527
spring:
  application:
    name: springcloud-zuul  # 微服务的名字
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: zuul9527.com
    prefer-ip-address: true # 详细信息显示ip
info:
  app.name: msj-springcloud
  company.name: msj.com

# zuul的配置
zuul:
  routes:  # routes是Map(键值对),我们可以随意配置
    mydept.serviceId: springcloud-provider-dept
    mydept.path: /mydept/**

代码解析

# zuul的配置
zuul:
  routes:  # routes是Map(键值对),我们可以随意配置
    mydept.serviceId: springcloud-provider-dept
    mydept.path: /mydept/**
    
# 等价于:
zuul:
  routes:  
    mydept:
    	serviceId: springcloud-provider-dept
    	path: /mydept/**

routes是Map<String, ZuulProperties.ZuulRoute>类型的,而ZuulRoute的定义如下:

public static class ZuulRoute {
    private String id;
    private String path;
    private String serviceId;
    private String url;
    private boolean stripPrefix = true;
    private Boolean retryable;
    private Set<String> sensitiveHeaders = new LinkedHashSet();
    private boolean customSensitiveHeaders = false;
}

以上的配置的意思是:设置mydept的serviceId为springcloud-provider-dept,并把mydept.path设置为/mydept/**

重新启动,发现localhost:9527/mydept/dept/get/3 可以访问服务,并且这时也不会暴露微服务名,但是这时通过微服务名仍然可以访问

image-20210808205105597

配置不能使用微服务名访问:

server:
  port: 9527
spring:
  application:
    name: springcloud-zuul  # 微服务的名字
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: zuul9527.com
    prefer-ip-address: true # 详细信息显示ip
info:
  app.name: msj-springcloud
  company.name: msj.com

# zuul的配置
zuul:
  routes:  # routes是Map(键值对),我们可以随意配置
    mydept:
      serviceId: springcloud-provider-dept
      path: /mydept/**
  ignored-services: springcloud-provider-dept # 不能再使用这个路径访问了

隐藏全部的真实项目:使用通配符:*

zuul:
  routes:  # routes是Map(键值对),我们可以随意配置
    mydept:
      serviceId: springcloud-provider-dept
      path: /mydept/**
  ignored-services: "*"

使用同一的前缀访问:

# zuul的配置
zuul:
  routes:  # routes是Map(键值对),我们可以随意配置
    mydept:
      serviceId: springcloud-provider-dept
      path: /mydept/**
  ignored-services: springcloud-provider-dept # 不能再使用这个路径访问了
  prefix: /msj

现在所有的请求都要加上:/msj/path+/服务提供者的controller访问

localhost:9527/msj/mydept/dept/get/3

image-20210808210241149

六、git配置

文档地址:https://springcloud.cc/spring-cloud-dalston.html

Spring Cloud config:分布式配置

这部分跟Netflix已经没有关系了。

分布式系统面临的问题,配置文件的问题

image-20210808210616159

什么是SpringCloud config分布式配置中心

SpringCloud config可以帮助我们把配置文件统一的放到云端去管理

image-20210808210831743

image-20210808215126386

spring cloud config分为服务端和客户端两部分:

  • 服务端也成为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并未客户端提供获取配置信息,加密,解密信息等访问接口。
  • 客户端:则是通过指定配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。

Spring Cloud config分布式配置中心能干嘛?

  • 集中管理配置文件
  • 不同环境,不同配置,动态化的配置更新,分环境部署,比如:/dev /test /prod /beta /release 等待
  • 运行期间动态调整配置,不需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 当配置发生变动时,服务不需重启,即可感知到配置的变化,并应用新的配置(需要热部署插件的支持)
  • 将配置信息以Rest接口的形式暴露

Spring Cloud config分布式配置中心与Github整合:

可以托管git代码的平台:github,码云,CODING

1、我们这里使用码云

2、登录,新建仓库

开源,语言选java, .gitignore模板选java,开源许可证选GPL3.0,添加Readme和选择单分支模型,点击创建

image-20210808221315070

3、创建后,通过git克隆到本地

image-20210808222510192

里面的东西要删的话都可以删了

我们在该文件夹下新建一个application.yml

spring:
  profiles:
    active:
    
# ---:多文档配置分割线
---
spring:
  profiles: dev
  application:
    name: springcloud-config-dev

---
spring:
  profiles: test
  application:
    name: springcloud-config-test

image-20210808223104836

把该文件添加到远程仓库:

git push origin master:origin代表当前的用户,master代表master分支。

6.1、springcloud-config搭建

spring cloud config 是C/S架构,所以我们要建两个微服务:一个是server,一个是client

1、新建服务端:springcloud-config-3344(端口是随便写的)

2、导入依赖

<dependencies>
    <!-- config -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
</dependencies>

3、编写配置:可以去官网上查看,可以配置的东西很多

server:
  port: 3344
spring:
  application:
    name: springcloud-config-server
  # 连接远程仓库
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/mshunj/springcloud-config.git  # 地址使用码云仓库中的克隆下的https网址

4、主启动类

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
// 开启Config服务
@EnableConfigServer
public class ConfigServer_3344 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServer_3344.class, args);
    }
}

启动:测试是否能够拿到远程的配置文件

启动报错:因为我们到了Eureka的包,它找不到Eureka就会报错,我们把Eureka的依赖删了。同时再加一个完善信息的包:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- config -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

启动:

HTTP服务具有以下格式的资源:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

通过:localhost:3344/application-dev.yml 读取application中的配置文件

image-20210808230846134

通过config-server:我们可以连接

另一种读法:/{application}/{profile}[/{label}],profile是指多文档配置文件中的某一个分支,如dev,test等,label是指分支,我们这里只有master分支

localhost:3344/application/dev/master

image-20210808231246517

/{label}/{application}-{profile}.yml:label放在前面的读法:

localhost:3344/master/application-dev.yml

image-20210808231404366

如果我们多一个不存在的配置文件,则配置都显示空的:http://localhost:3344/master/application-qq.yml

image-20210808231518174

6.2 、配置客户端

1、在我们clone的仓库中在新建一个配置文件(有关客户端的),名字可以随便起:config-client.yml

由于客户端要用到eureka,所以eureka的配置要有:

spring:
  profiles:
    active: dev

---
server:
  port: 8201
spring:
  profiles: dev
  application:
    name: springcloud-provider-dept
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
---
server:
  port: 8202
spring:
  profiles: test
  application:
    name:springcloud-provider-dept
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

2、新建客户端微服务:springcloud-config-client-3355

3、导入依赖

<dependencies>
    <!-- spring cloud config -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

一般服务端使用的是server,客户端使用的是starter,如config就是:

服务端:config-server,客户端:starter-config

4、配置

这里可以使用bootstrap.yml(也可以使用application.yml),spring中有bootstrap和application的类加载器,这两个文件都会被识别为spring的配置文件,其他的yml就不可以。

区别:

bootstrap.yml:系统级别的配置

application.yml:用户级别的配置

一般使用用户级别的配置文件就可以了,但是这里可能会跟远程的配置文件有冲突,所以使用系统级别的配置

配置顺序:服务端连接远程仓库,客户端连接服务端即可。刚才配置的服务端已经连接远程仓库了,所以我们这里的客户端只用连接服务端就可以了。

bootstrap.yml

# 系统级别的配置:spring
spring:
  cloud:
    config:
      name: config-client # 需要从git上读取的资源名称,不需要后缀,这里读取git上的config-client.yml
      profile: dev # 需要那那个配置环境
      label: master # 读取文件的分支
      uri: http://localhost:3344  # 连接服务端

以上配置最终拼接的路径为:http://localhost:3344/config-client/dev/master

appliction.yml

# 用户级别的配置
spring:
  application:
    name: springcloud-config-client-3355

5、为了能保证拿到3344的东西,我们先写一个controller

在msj.com.springcloud.controller下新建ConfigClientController.java

package com.msj.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigClientController {
    // 通过远程配置文件注入值,看看能不能拿到
    // 注入application.name的值
    @Value("${spring.application.name}")
    private String applicationName;
    // 我们远程配置了eureka,而这里并没有配置,看看能不能拿到
    @Value("${eureka.client.service-url.defaultZone")
    private String eurekaServer;

    @Value("${server.port}")
    private String port;

    @RequestMapping("/config")
    public String getConfig() {
        return "application: " + applicationName +
                "eurekaServer: " + eurekaServer +
                "port: " + port;
    }
}

6、主启动类

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

启动3344,我们可以访问到config-client.yml的内容:现在说明server跟git连通了 localhost:3344/master/config-client-dev.yml

image-20210809101002082

启动3355:测试客户端跟服务端是否连通

注意:我们这里没有配置3355的启动端口,但是我们通过bootstrap.yml系统级配置设置3355客户端启动的时候去远程读取client-config的dev配置,而该配置的server-port是8201,所以3355启动的时候端口号为8201:

image-20210809101726162

并且3355启动时也指明了配置是从3344服务端获取的。

image-20210809101843135

所以我们要访问我们客户端的controller时要通过8201访问:

localhost:8201/config

image-20210809101939367

如果我们把bootstrap中的profile修改为test时,我们启动的端口就变为8202了。

6.3、远程配置实战测试

现在的Eureka集群的配置(7001,7002,7003)每个都有,要修改的话也是每一个都要修改,十分的麻烦,我们现在把它放到远程,统一的修改配置。

1、首先编写远程配置文件:即在我们clone的远程仓库中新建文件

新建:config-eureka.yml

我们通过yml的多环境配置配置了两套环境

spring:
  profiles:
    active: dev
---
server:
  port: 7001
spring:
  profiles: dev
  application:
    name: springcloud-config-eureka
# eureka的配置
eureka:
  instance:
    hostname: eureka7001.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
---
server:
  port: 7001
spring:
  profiles: test
  application:
    name: springcloud-config-eureka

# Eureka的配置
eureka:
  instance:
    hostname: eureka7001.com  # Eureka服务端的实列名称:eureka7001本质上还是localhost(127.0.0.1)
  client:
    register-with-eureka: false  # 表示是否向eureka注册中心注册自己(服务器不用注册自己)
    fetch-registry: false  # 如果fetch-registry为false,表示自己为注册中心
    service-url:
      # 集群:关联其他的注册中心
      defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka # 关联7002和7003注册中心

2、服务提供者我们也不想在本地配置,我们也push到远程管理

在clone到本地的仓库中新建配置文件:config-dept.yml

首先把8001服务中的配置文件中的内容拿过去,然后再增加一些配置

spring:
  profiles:
    active: dev
---
server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  profiles: dev
  application:
    name: springcloud-config -dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息

# 完善监控信息配置
info:
  app.name: msj-springcloud  # 项目名字(当然没什么作用,不配也可以)
  company.name: msj  # 公司名字

# 第二套配置环境我们把数据库改为了springclouddb02
---
server:
  port: 8001
mybatis:
  type-aliases-package: com.msj.springcloud.api.pojo
  config-location: classpath:mybatis/mybatis-config.xml   # mybatis的核心配置文件
  mapper-locations: classpath:mybatis/mapper/*.xml   # mybatis的mapper文件位置

# spring的配置
spring:
  profiles: test
  application:
    name: springcloud-config -dept # 为该模块(微服务)起一个名字
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3307/springclouddb02?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 1234

# Eureka的配置:服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息

# 完善监控信息配置
info:
  app.name: msj-springcloud  # 项目名字(当然没什么作用,不配也可以)
  company.name: msj  # 公司名字

3、为了与之前的项目有区别,我们新建一个微服务

springcloud-config-eureka-7001

a、导入依赖:把原来7001的依赖原封不动的拿过来并添加config的包

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- 热部署 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>

b、同时把7001中src复制一份

c、把application.yml中的内容清空,同时新建一个bootstrap.yml

bootstrap.yml

spring:
  cloud:
    config:
      name: config-eureka
      label: master
      profile: dev
      uri: http://localhost:3344

application.yml

spring:
  application:
    name: springcloud-config-eureka-7001

主启动类:也不需要修改,修改一下名字就可以了

package com.msj.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
// 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer
public class EurekaConfigServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaConfigServer_7001.class, args);
    }
}

启动:3344,保证3344能够访问到拿到远程的配置文件

localhost:3344/master/config-eureka-dev.yml

image-20210809125429255

启动:EurekaConfigServer_7001

image-20210809125538586

给微服务成功在7001端口启动

可以访问7001端口的Eureka页面,并且可以看到7002和7003集群,但是这两个集群并不能访问,因为我们并没有启动它

image-20210809125800878

4、新建一个服务端的:springcloud-config-dept-8001(为了区别)

a、导入依赖:与之前的8001的依赖一样并添加config的包

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!-- actuator -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- Eureka依赖:完善监控信息 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.6.RELEASE</version>
    </dependency>
    <!--我们需要实体类:所以我们需要springcloud-api模块-->
    <dependency>
        <groupId>com.msj</groupId>
        <artifactId>springcloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>
    <!-- 日志门面 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <!-- spring test:父工程中到如果springboot dependencies,所以这里不用写版本,与父工程的版本一致:2.1.4.RELEASE -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
    </dependency>
    <!-- spring web 启动器(也是在springboot dependencies中) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 可以使用jetty作为我们的服务器:虽然跟tomcat没什么区别 (springboot dependencies中也有) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>

    <!-- 热部署工具(springboot dependencies中) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>

b、src中的东西拷贝一份

c、清除application中的东西,新建bootstrap

bootstrap.yml

spring:
  cloud:
    config:
      name: config-dept
      label: master
      profile: dev
      uri: http://localhost:3344

application.yml

spring:
  application:
    name: springcloud-config-dept-8001

主启动类也不用改,启动:3344

启动:config-8001:

启动成功

image-20210809132147268

注册信息:远程的config-dept.yml配置

image-20210809132542825

注册中的信息:

image-20210809132627769

我们发现config-8001中也配置application.name,但注册到注册中心后使用的还是远程的application.name,即远程的配置优先级更高。

测试连接数据库:localhost:8001/dept/get/2

数据也是从db01中取的,如果该为test,则会从db02中取

image-20210809132944366

我们修改了远程配置文件,我们只需要在本地build(热部署)一下项目,就可以了。

七、回顾

image-20210809155616854

自己关注学习官网:

搜索spring cloud alibaba

注解

# 支持链式写法注解:Lombok中的注解
@Accessors(chain = true)

# 类似于spring中的applicationContext.xml文件
@Configuration

# 只要我们加了@EnableEurekaClient注解,该服务就会被注册到Eureka的注册中心
@EnableEurekaClient

# 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer

# 开启服务发现:让别人可以看到我们注册了那些服务,以及服务的信息
@EnableDiscoveryClient

# 负载均衡:Ribbon
@LoadBalanced

# 服务启动时去加载我们自定义配置的Ribbon负载均衡策略
# @RibbonClient(name=服务名,cofiguration=自定义的负载均衡策略名)
@RibbonClient(name = "springcloud-provider-dept", configuration = MsjRule.class)

# 使用了@FeignClient这个注解,我们就可以直接调用这里的服务
# @FeignClient(vlaue=服务名)
@FeignClient

# 扫描指定的包
@ComponentScan("com.msj.springcloud")

# @HystrixCommand服务熔断,fallbackMethod指定服务出现异常时调用的方法
@HystrixCommand(fallbackMethod = "hystrixGet")

# 开启Hystrix服务:CircuitBreaker熔断器
@EnableCircuitBreaker

# 开启监控页面
@EnableHystrixDashboard

# 开启Zuul服务代理
@EnableZuulProxy

# 开启Config服务
@EnableConfigServer

思路

springCloud项目思路:

1、导入依赖

2、编写配置文件

3、开启这个功能:@EnabledXXXX

4、配置类

springframework.boot
spring-boot-starter-actuator



org.springframework.cloud
spring-cloud-starter-eureka
1.4.6.RELEASE



com.msj
springcloud-api
1.0-SNAPSHOT



junit
junit
test


mysql
mysql-connector-java


com.alibaba
druid



ch.qos.logback
logback-core


org.mybatis.spring.boot
mybatis-spring-boot-starter



org.springframework.boot
spring-boot-test



org.springframework.boot
spring-boot-starter-web



org.springframework.boot
spring-boot-starter-jetty

<!-- 热部署工具(springboot dependencies中) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>
```

b、src中的东西拷贝一份

c、清除application中的东西,新建bootstrap

bootstrap.yml

spring:
  cloud:
    config:
      name: config-dept
      label: master
      profile: dev
      uri: http://localhost:3344

application.yml

spring:
  application:
    name: springcloud-config-dept-8001

主启动类也不用改,启动:3344

启动:config-8001:

启动成功

[外链图片转存中…(img-w5lh4Dtm-1628496629415)]

注册信息:远程的config-dept.yml配置

[外链图片转存中…(img-PMrcAYE0-1628496629415)]

注册中的信息:

[外链图片转存中…(img-0eBewJxz-1628496629415)]

我们发现config-8001中也配置application.name,但注册到注册中心后使用的还是远程的application.name,即远程的配置优先级更高。

测试连接数据库:localhost:8001/dept/get/2

数据也是从db01中取的,如果该为test,则会从db02中取

[外链图片转存中…(img-cVwocgVk-1628496629416)]

我们修改了远程配置文件,我们只需要在本地build(热部署)一下项目,就可以了。

七、回顾

[外链图片转存中…(img-jypPiv7m-1628496629416)]

自己关注学习官网:

搜索spring cloud alibaba

注解

# 支持链式写法注解:Lombok中的注解
@Accessors(chain = true)

# 类似于spring中的applicationContext.xml文件
@Configuration

# 只要我们加了@EnableEurekaClient注解,该服务就会被注册到Eureka的注册中心
@EnableEurekaClient

# 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer

# 开启服务发现:让别人可以看到我们注册了那些服务,以及服务的信息
@EnableDiscoveryClient

# 负载均衡:Ribbon
@LoadBalanced

# 服务启动时去加载我们自定义配置的Ribbon负载均衡策略
# @RibbonClient(name=服务名,cofiguration=自定义的负载均衡策略名)
@RibbonClient(name = "springcloud-provider-dept", configuration = MsjRule.class)

# 使用了@FeignClient这个注解,我们就可以直接调用这里的服务
# @FeignClient(vlaue=服务名)
@FeignClient

# 扫描指定的包
@ComponentScan("com.msj.springcloud")

# @HystrixCommand服务熔断,fallbackMethod指定服务出现异常时调用的方法
@HystrixCommand(fallbackMethod = "hystrixGet")

# 开启Hystrix服务:CircuitBreaker熔断器
@EnableCircuitBreaker

# 开启监控页面
@EnableHystrixDashboard

# 开启Zuul服务代理
@EnableZuulProxy

# 开启Config服务
@EnableConfigServer

思路

springCloud项目思路:

1、导入依赖

2、编写配置文件

3、开启这个功能:@EnabledXXXX

4、配置类


视频地址:https://www.bilibili.com/video/BV1jJ411S7xr?p=1

Spring Cloud是一个用于构建分布式系统的开发工具集合。它提供了一些常用的组件和框架,包括服务注册和发现、负载均衡、断路器、分布式配置等等。在使用Spring Cloud时,有一些常见的错误和注意事项需要注意。 首先,关于Spring Boot和Spring Cloud版本对应错误。在使用Spring Cloud时,需要确保Spring Boot和Spring Cloud的版本兼容。不同版本之间可能存在依赖冲突或不兼容的情况,因此需要根据官方文档或者相关文档来选择合适的版本。 另外,Spring Cloud Config是一个用于集中管理和动态获取配置的工具。它支持从Git、SVN或本地文件系统中获取配置文件,并提供了服务器和客户端支持。你可以通过官方使用说明文档了解更多关于Spring Cloud Config的详细信息。 此外,关于选择使用Nacos还是Eureka作为服务注册和发现组件的问题。Nacos是一个功能更强大的服务注册和发现组件,它整合了Spring Cloud Eureka、Spring Cloud Config和Spring Cloud Bus的功能。使用Nacos可以实现配置的中心动态刷新,而不需要为配置中心新增集群或使用消息队列。另一方面,Eureka是Spring Cloud原生全家桶的一部分,相对来说更加稳定一些。选择使用哪个组件需要根据具体的需求和项目特点来决定。 综上所述,Spring Cloud是一个用于构建分布式系统的开发工具集合,它提供了一些常用的组件和框架。在使用Spring Cloud时,需要注意Spring Boot和Spring Cloud版本的兼容性,并可以使用Spring Cloud Config来动态获取配置。同时,可以选择使用Nacos或Eureka作为服务注册和发现组件,具体选择需要根据项目需求来决定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值