SpringCloud概述

SpringCloud是什么

Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、群集状态)。分布式系统的协调会导致锅炉板模式,使用 Spring Cloud 开发人员可以快速启动实现这些模式的服务和应用程序。它们将在任何分布式环境中很好地工作,包括开发人员自己的笔记本电脑、裸机数据中心和云铸造等托管平台。

SpringCloud和SpringBoot的关系

  • SpringBoot专注于快速、方便的开发单个个体为服务
  • SpringCloud关注于全局的服务治理框架

SpringCloud能干嘛

  • Distributed/versioned configuration(分布式/版本配置)
  • Service registration and discovery(服务注册和发现)
  • Routing(路由)
  • Service-to-service calls(服务到服务呼叫)
  • Load balancing(负载平衡)
  • Circuit Breakers(断路 器)
  • Global locks(全局锁)
  • Leadership election and cluster state(领导选举和集群状态)
  • Distributed messaging(分布式消息传送)

参考网站

Spring Cloud Netflix

SpringCloud中文文档

SpringCloud中文网

SpringCloud

SpringCloud版本选择

spring-boot-starter-parentspring-cloud-dependencies
2.0.6.RELEASE(2.0.x)Finchley(芬奇利)(不兼容springboot 1.5.x)
2.1.4.RELEASE(2.1.x)Greenwich(格林威治)

SpringCloud远程调用

一、新建一个maven的父项目

image-20200611230707340

1、添加依赖

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

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.version>4.12</junit.version>
    <log4j.version>1.2.17</log4j.version>
    <lombok.version>1.18.12</lombok.version>
</properties>


<dependencyManagement>
    <dependencies>
        <!--SpringCloud的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!--SpringBoot-->
        <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>8.0.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>



        <!--日志测试-->
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <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>${log4j.version}</version>
        </dependency>

        <!--logback-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>

    </dependencies>
</dependencyManagement>

二、新建API子项目

image-20200611230943496

1、添加依赖

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

2、创建实体类

image-20200611231405389

package com.zhiyu.springcloud.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 {//实体类序列化 orm实体关系映射
    private Long deptno;
    private String dname;
    private String db_source;//此字段存在哪个数据库

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

三、新建Provider子项目

image-20200611231826094

1、添加依赖

<dependencies>
    <!--需要得到实体类,所以需要配置api model-->
    <dependency>
        <groupId>org.zhiyu</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>

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

    <!--jetty-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>

    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>

2、新建配置文件

image-20200611232001498

server:
  port: 8001

#mybatis配置
mybatis:
  type-aliases-package: com.zhiyu.springcloud.pojo
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

#Spring配置
spring:
  application:
    name: SpringCloud-Provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: cxy0920.

image-20200611232100061

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

3、编写Mapper接口及Mapper.xml文件

image-20200611232240487

package com.zhiyu.springcloud.mapper;

import com.zhiyu.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface DeptMapper {
    public boolean addDept(Dept dept);
    public Dept queryById(Long id);
    public List<Dept> queryAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhiyu.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 db01.dept where deptno = #{deptno};
    </select>

    <select id="queryAll" resultType="Dept">
        select *
        from db01.dept;
    </select>
</mapper>

4、编写Service层

image-20200611232400884

package com.zhiyu.springcloud.service;

import com.zhiyu.springcloud.pojo.Dept;

import java.util.List;

public interface DeptService {
    public boolean addDept(Dept dept);
    public Dept queryById(Long id);
    public List<Dept> queryAll();
}
package com.zhiyu.springcloud.service;

import com.zhiyu.springcloud.mapper.DeptMapper;
import com.zhiyu.springcloud.pojo.Dept;
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();
    }
}

5、编写Controller层

image-20200611232511449

package com.zhiyu.springcloud.controller;

import com.zhiyu.springcloud.pojo.Dept;
import com.zhiyu.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

//提供Restful服务(不需要前端)
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

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

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

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

6、编写启动类

image-20200611232723988

package com.zhiyu.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);
    }
}

7、测试接口

​ 访问 localhost:8001/dept/queryAll

image-20200611233052787

四、新建Consumer子项目

image-20200611233337051

1、导入依赖

<!--实体类+web-->
<dependencies>
    <dependency>
        <groupId>org.zhiyu</groupId>
        <artifactId>SpringCloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <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、编写配置文件

image-20200611233507455

server:
  port: 80

3、编写配置类

image-20200611233611251

RestTemplate注册到Spring管理,再通过自动装配使用

package com.zhiyu.springcloud.config;

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

@Configuration
public class ConfigBean {//配置类 ---> spring ApplicationContext.xml
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

3、编写Controller

image-20200611233810852

package com.zhiyu.springcloud.controller;

import com.zhiyu.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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 {
    //消费者没有service层
    @Autowired
    private RestTemplate restTemplate;

    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){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/queryById/"+id,Dept.class);
    }

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

4、编写启动类

image-20200611233903247

package com.zhiyu.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,args);
    }
}

5、测试

image-20200611234134017

同时启动Provider和Consumer,通过80端口调用8001端口服务

访问 localhost:80/consumer/dept/list

image-20200611234337238

Eureka

一、新建EurekaService子项目

image-20200611234530558

1、导入依赖

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

2、编写配置文件

image-20200611234728026

server:
  port: 7001


#Eureka配置
eureka:
  instance:
    hostname: localhost #Eureka服务端实例名称
  client:
    register-with-eureka: false #表示是否向Eureka服务中心注册自己
    fetch-registry: false #如果为false 则表示自己为注册中心
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

3、编写启动类

image-20200611234816936

package com.zhiyu.springcloud;

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

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

4、测试

访问 localhost:7001

image-20200611235007100

二、将Provider注册到Eureka

Eureka项目不用修改,一下配置都在Provider项目中修改

1、导入依赖

<!--Eureka客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<!--完善监控(可不用)-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、添加配置

#Eureka的配置,服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: springcloud-provider-8001

#info配置(一些没啥用的信息)
info:
  app.name: zhiyu-SpringCloud
  company.name: zhiyushijie.work

3、在启动类中添加注解

@EnableEurekaClient//开启客户端,自动注册到Eureka
@EnableDiscoveryClient//服务发现(可选)

4、编写一个获取微服务信息的Controller(可选)

若想使用此方法,必须在启动类中添加@EnableDiscoveryClient注解

		@Autowired
    private DiscoveryClient client;


@GetMapping("/dept/discovery")
public Object discovery(){
    //获取微服务列表清单
    List<String> services = client.getServices();
    System.out.println(services);

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

三、Eureka搭建集群时的问题

若想在单机搭建虚拟集群的话,必须设置域名IP映射,如果不设置的话,直接使用如127.0.0.1这样的本机IP地址的话。Eureka会排出相同的IP地址,即使每个Eureka服务端口号不同也不行。

集群搭建的Eureka服务器端配置文件

server:
  port: 7001


#Eureka配置
eureka:
  instance:
    hostname: eureka7001.com #Eureka服务端实例名称
  client:
    register-with-eureka: false #表示是否向Eureka服务中心注册自己
    fetch-registry: false #如果为false 则表示自己为注册中心
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

其他集群个体配置相同,只需更换Eureka地址即可

Provider端配置文件

#Eureka的配置,服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: springcloud-provider-8001

Ribbon

配置负载均衡

一、添加依赖

在Consumer项目中添加依赖

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

二、编写配置文件

#Eureka配置
eureka:
  client:
    register-with-eureka: false #不向Eureka中注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

三、向启动类中添加注解

@EnableEurekaClient

四、向配置类中添加注解

@LoadBalanced

@Configuration
public class ConfigBean {//配置类 ---> spring ApplicationContext.xml


    @Bean
    @LoadBalanced//配置负载均衡实现RestTemplate(Ribbon)
    public RestTemplate getRestTemplate(){//Restful风格的http请求来实现远程调用等
        return new RestTemplate();
    }
}

五、修改Controller中的URL

//Ribbon实现负载均衡时,应该是一个变量,通过服务名来访问
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

六、创建新的Provider项目

修改application.yaml

需要修改的有,端口号,连接的数据库,在Eureka中显示的ID

image-20200612231247949

遇见的坑!!

配置完成后,访问http://localhost/consumer/dept/list后发现返回的数据没有改变,全部都是db01中的数据。查看数据库中数据,无异常。

原因:编写*mapper.xml文件时,查询语句时不严谨。如:

<select id="queryAll" resultType="Dept">
    select * from db01.dept;
</select>

正确应去掉数据库名db01,否则的话不管访问哪个Provider都会去查询db01数据库

<select id="queryAll" resultType="Dept">
    select * from dept;
</select>

七、自定义负载均衡策略

负载均衡策略实现的接口

image-20200613093926341

常用的策略,IRule接口的实现类

AvailabilityFilteringRule过滤掉崩贵的服务,再轮询

RandomRule随机

RoundRobinRile轮询(默认)

image-20200613094021766

1、使用设置好的负载均衡策略

在配置类中把负载均衡策略添加到Bean中即可

image-20200613095055203

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

2、自定义的负载均衡策略

1、创建自定义策略的配置类

image-20200613100501220

2、由于我们创建的自定义策略不在SpringBoot的启动类同级下,所以不会被SpringBoot扫描注册。

因此我们需要在SpringBoot的启动类上添加@RibbonClient注解,并设置自定义策略的类。

@SpringBootApplication
@EnableEurekaClient
//使微服务启动时加载自定义策略
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}

同时还要删除上一步在配置类中的设置的Random策略,并将策略移动到自定义的策略类中

package com.zhiyu.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

注意点:

为什么要把自定义的策略组件放到SpringBoot扫描不到的包下?

image-20200613102203783

3、正式编写策略算法

新建策略类

image-20200613104830097

继承IRule接口,或者实现AbstractLoadBalancerRule抽象类

package com.zhiyu.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 zhiyuRandomRule extends AbstractLoadBalancerRule {

    private int total = 0;
    private int currentIndex = 0;

    public zhiyuRandomRule() {
    }

    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) {
    }
}

将策略配置类中的策略替换成上一步自定义的策略类

@Configuration
public class MyRule {
    @Bean
    public IRule myRule(){
        return new zhiyuRandomRule();
    }
}

又一个坑!!

配置完成后启动项目,出现报错:

Invalid bean definition with name 'zhiyuRule' defined in com.zhiyu.myrule.zhiyuRule: Bean name derived from @Bean method 'zhiyuRule' clashes with bean name for containing configuration class; please make those names unique!

百度搜索后,结论多为:没有删除ConfigBean中的MyRule

image-20200613111802002

image-20200613111823002

经检查,我已经删除了此方法,还是报此错误

最后,真正原因为:自定义策略配置类类名和方法名重复!

即使大小写不同也不行,必须不同名才可以!

image-20200613112022957 image-20200613112148419

解决方案:

更改方法名

image-20200613112350663

或者显示指定Bean的名字

image-20200613112541811

八、Feign接口式调用服务

一、新建feign消费者客户端

image-20200613134650663

其中application.yamlConfigBean、无需改变

二、导入依赖

<dependencies>
    <!--feign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</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>

    <!--实体类+web-->
    <dependency>
        <groupId>org.zhiyu</groupId>
        <artifactId>SpringCloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

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

三、修改API子项目

添加依赖

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

编写Service层

image-20200613135006677
package com.zhiyu.springcloud.service;

import com.zhiyu.springcloud.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")//Spring应用的name
public interface DeptClientService {

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

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

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

四、编写新的Controller

image-20200613135206875
package com.zhiyu.springcloud.controller;

import com.zhiyu.springcloud.pojo.Dept;
import com.zhiyu.springcloud.service.DeptClientService;
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 {

    @Autowired
    private DeptClientService deptClientService;


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

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

    @RequestMapping("/consumer/dept/list")
    public List<Dept> List(){
        return this.deptClientService.queryAll();
    }
}

五、在启动类上添加注解

image-20200613135320284
@SpringBootApplication
@EnableEurekaClient
//扫描Feign接口
@EnableFeignClients(basePackages = {"com.zhiyu.springcloud"})
//@ComponentScan("com.zhiyu.springcloud")
public class DeptConsumerFeign_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumerFeign_80.class,args);
    }
}

Hystrix

一、Hystrix服务熔断

在一个微服务出现错误时并不直接抛出异常或中断服务,而是返回设置好的错误信息,让其他服务继续运行下去,防止服务雪崩。

1、新建Hystrix项目

复制Provider-8001即可

image-20200613150653504

2、添加依赖

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

3、编写Controller

image-20200613150804356
package com.zhiyu.springcloud.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.zhiyu.springcloud.pojo.Dept;
import com.zhiyu.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.*;

import java.util.List;

//提供Restful服务(不需要前端)
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;


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

    //备选方法
    public Dept hystrixGet (@PathVariable("id") Long id){
        //正常返回Dept对象,但是错误信息被写在对象里
        return new Dept()
                .setDeptno(id)
                .setDname("id=>"+id+",没有对应的信息,null")
                .setDb_source("no database in MySQL");
    }
}

这里只留下了一个查询方法,方便理解思路

4、启动类添加注解

//启动类
@SpringBootApplication
@EnableEurekaClient//开启客户端,自动注册到Eureka
@EnableDiscoveryClient//服务发现(可选)
@EnableCircuitBreaker//添加熔断的支持
public class DeptProviderHystrix_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderHystrix_8001.class,args);
    }
}

二、Hystrix服务降级

由于整体资源不足,关闭不常用的微服务

1、在API项目中添加FallbackFactory

image-20200613153820888
package com.zhiyu.springcloud.service;

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

import java.util.List;

@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public boolean addDept(Dept dept) {
                return false;
            }

            @Override
            public Dept queryById(Long id) {
                return new Dept()
                        .setDeptno(id)
                        .setDname("没有对应信息,服务降级已被关闭")
                        .setDb_source("No data");
            }

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

正常情况应该重写每一个降级方法

2、修改Service中的注解

image-20200613153948681

3、修改Feign的Consumer客户端

修改application配置文件

#开启降级Feign.hystrix
feign:
  hystrix:
    enabled: true

熔断与降级

服务熔断服务降级
服务端客户端
某个服务超时或异常,引起熔断。熔断后可以继续调用此服务,但是只会返回事先设定好的错误信息关闭某个整体服务,也可是单个服务。关闭之后服务不再被调用,客户端将返回事先设置好的缺省值

三、HystrixDashboard服务监控

1、新建Dashboard监控项目

image-20200613163851083

2、导入依赖

<dependencies>

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

    <!--实体类+web-->
    <dependency>
        <groupId>org.zhiyu</groupId>
        <artifactId>SpringCloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

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

3、配置端口

server:
  port: 9001

4、编写启动类

package com.zhiyu.springcloud;

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

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

5、修改Provider服务端

注意:

Dashboard只能监控启动Hystrix服务熔断的服务端请求!!

必须添加以下依赖

<!--hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</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>
<!--完善监控(可不用)若使用dashboard则必须导入此依赖!!-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

在启动类中添加一个Bean

//增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
    registrationBean.addUrlMappings("/actuator/hystrix.stream");
    return registrationBean;
}

6、访问测试

先访问 http://127.0.0.1:7001/http://127.0.0.1:8001/

保证Eureka和服务端正常启动,并且服务端注册成功、请求可用

再访问 http://127.0.0.1:9001/hystrix

查看Dashboard是否启动成功

再访问 http://127.0.0.1:8001/actuator/hystrix.stream
查看Stream流是否存在

最后将 http://127.0.0.1:8001/actuator/hystrix.stream
地址填入,并设置监控间隔时间、名称。

点击进入即可

image-20200613165137912

image-20200613165406605

Zuul

路由网关,对请求做拦截、过滤等。

一、创建Zuul项目

Zuul就是一个微服务项目,可以被注册在Eureka

image-20200613174306909

二、添加依赖

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

    <!--实体类+web-->
    <dependency>
        <groupId>org.zhiyu</groupId>
        <artifactId>SpringCloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

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

三、编写配置文件

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
info:
  app.name: zhiyuSpringCloud-Zuul

zuul:
  routes:
    mydept.serviceId: springcloud-provider-dept #将springcloud-provider-dept路径替换成/mydept/**
    mydept.path: /mydept/**
  ignored-services: springcloud-provider-dept #关闭springcloud-provider-dept路径的访问
  # ignored-services: "*" 隐藏全部微服务
  prefix: /zhiyu #统一的访问前缀

四、编写启动类

package com.zhiyu.springcloud;

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


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

五、测试

image-20200613174709261

image-20200613174739817

其他URL则不能再访问,这里www.zhiyu.com在hosts文件配置了本机映射到127.0.0.1