springcloud五大组件永远滴神——成神之路

      这两天趁着没事时偷偷又干了个springcloud,这技术真的流弊,我是佩服,好了,废话少说,继续给大家扔干货——被称为springcloud永远滴神的五大组件。下面开始正式分享
      springcloud常用五大组件:
            1、服务发现——Netflix Eureka
            2、负载均衡——Netflix Ribbon
            3、断路器——Netflix Hystrix
            4、服务网关——Netflix Zuul
            5、分布式配置——Spring Cloud Config
      先让大家看下下面的这张SpringCloud图片,大家可以先看下,然后我进行讲解具体组件用法和功能及其实现:
在这里插入图片描述

一、服务发现:Eureka

  1.1  Eureka简介

      Eureka是SpringCloud Netflix的一个子模块,也是其核心模块之一;用于云端服务发现,一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务的注册和发现对于微服务架构来说是非常重要的,有了服务注册和发现,只需要使用服务标识符,就可以访问到服务,而不需要修改服务的配置文件了,其功能类似于dubbo的注册中心(zookeeper)。

  1.2  Eureka原理

      ★  基本架构:
          1、SpringCloud封装了Netflix公司开发的Eureka模块来实现服务注册和发现
          2、Eureka采用了C-S架构设计,EurekaServer作为服务注册功能的服务器,它就是服务注册中心。
          3、系统中的其他服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否能够正常运行,Springcloud的一些其他微服务就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑。
在这里插入图片描述
      ★  Eureka两大组件: Eureka ServerEureka Client
      ★  Eureka Server提供服务注册:各个微服务启动后,会在EurekaServer中进行注册,这样Eureka Server中的服务注册表中将会存储所有可以使用的服务节点信息,而且服务节点信息可以在界面中直观的看到。
      ★  Eureka Client是Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的,默认使用轮询负载均衡器。在启动应用后,将会向EurekaServer发送心跳(默认周期为30s)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,那么EurekaServer将会从服务注册表中把这个服务节点移除掉(默认周期为90s)。
      ★  三大角色:
            Eureka Server:提供服务注册和发现
            Service Provider:服务提供方,将自身服务注册到Eureka中,从而使服务消费者能够找到该服务。
            Service Consumer:服务消费方,从Eureka中获取注册服务列表,从而找到服务提供方。

  1.3  构建注册中心集群

         这里我们进行创建三个注册服务中心,构成一个Eureka Server集群,配置如下:
首先在pom.xml引入Eureka Server依赖:

 <!-- eureka 的server端 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka-server</artifactId>
     <version>1.4.6.RELEASE</version>
 </dependency>

然后编写yml配置文件:

server:
  port: 7001

#Eureka配置
eureka:
  instance:
    hostname: eureka7001.com  #Eureka服务端的实例名称
  client:
    register-with-eureka: false  #表示是否向eureka注册中心注册自己
    fetch-registry: false  #fetch-registry如果为false,则表示自己为注册中心,
    service-url:  #监控页面
      #单机: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      #集群(关联):
	  # defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
	  #这个http://eureka7002.com我在本地的host做了地址映射,类似127.0.0.1,大家可以更改成这个127.0.0.1
      defaultZone: http://eureka7002.com:7002/,http://eureka7003.com:7003/

注意: 我们这里配置个Eureka Server 集群,包括三个注册中心,1、注意更改端口号分别为7001,7002,7003; 2、更改Eureka 服务端实例名称:eureka: instance :hostname: xxxxxxx 3、更改其监控页面地址defaultZone。4、注意这个http://eureka7002.com:7002/ 我在本地做了地址映射,为了更好的看出不是本地的效果。大家记得更改下。具体配置含义,大家可以在配置文件的注解中进行理解
最后编写启动类:

package com.ygl.springcloud;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/11 11:03
 */

//启动之后访问 http://localhost:7001/
@SpringBootApplication
@EnableEurekaServer  //服务端的启动类,可以接受别人注册进来~
public class EurekaServer_7001 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}

启动类主要是 @EnableEurekaServer开启Eureka服务端注解。
好了,这些就把Eureka Server 集群搭建好了,其目录结构如下图所示:
在这里插入图片描述
启动,访问地址:http://eureka7001.com:7001/,如下图所示:
在这里插入图片描述

  1.4  将生产者入驻注册服务中心

首先导入依赖:

 <!-- eureka提供者 既不是客户端也不是服务端 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka</artifactId>
     <version>1.4.6.RELEASE</version>
 </dependency>

然后配置yml文件:

server:
  port: 8001

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


spring:
  application:
    name: springcloud-provider-dept  # 3个服务名称一致
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource #数据源
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root

#eureka配置,服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    prefer-ip-address: true  #显示当前主机地址
    instance-id: springcloud-provider-dept-8001  #修改eureka上的默认描述信息


#info配置
info:
  app.name: ygl-springcloud
  company.name: www.baidu.com

注意在配置文件中各个配置含义在注解中有详细说明,不懂的可以看注解。
最后配置启动类,代码如下:

package com.ygl.springcloud;

import com.netflix.hystrix.HystrixMetrics;
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.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.annotation.ApplicationScope;

/**
 * @author ygl
 * @description 启动类
 * @date 2020/12/10 19:47
 */
@SpringBootApplication
@EnableEurekaClient //服务在启动后自动注册到Eureka中
public class DeptProvider_8001 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DeptProvider_8001.class,args);
    }
}

注意:启动类上记得开启Eureka Client注解 @EnableEurekaClient
下面我在说下我的三层架构吧,不进行详述,直接贴代码,我相信大家的水平的,哈哈。
先放张目录层次架构图:
在这里插入图片描述
controller代码如下所示:

package com.ygl.springcloud.controller;

import com.ygl.springcloud.pojo.Dept;
import com.ygl.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.cloud.client.hypermedia.DiscoveredResource;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 19:38
 */
@RestController
@RequestMapping("/dept")
public class DeptController {
    
    
    @Autowired
    private DeptService deptService;

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

    //增加
    @PostMapping("/addDept")
    public Boolean addDept(@RequestBody Dept dept){
    
    
        return deptService.addDept(dept);
    }

    //根据id进行查询
    @GetMapping("/queryById")
    public Dept queryById(Integer id){
    
    
        return deptService.queryById(id);
    }
    //查询所有
    @GetMapping("/queryAll")
    public List<Dept> queryAll(){
    
    
        return deptService.queruAll();
    }

    //注册进来的微服务~获取一些信息~
    @GetMapping("/discovery")
    public Object discovery(){
    
    
        //获取微服务列表的清单
        List<String> services = client.getServices();
        System.out.println("discovery=>"+services);
        //得到一个具体的微服务信息,通过具体的微服务id,applicationName
        List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
        for (ServiceInstance s: instances) {
    
    
            System.out.println(s.getHost()+"\t"+
            s.getPort()+"\t"+
            s.getUri()+"\t"+
            s.getServiceId()
            );
        }
        return this.client;
    }

}

service接口如下所示:

package com.ygl.springcloud.service;

import com.ygl.springcloud.pojo.Dept;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 19:33
 */
public interface DeptService {
    
    
    public boolean addDept(Dept dept);
    public Dept queryById(Integer id);
    public List<Dept> queruAll();
}

service接口实现类如下图所示:

package com.ygl.springcloud.service.impl;

import com.ygl.springcloud.dao.DeptDao;
import com.ygl.springcloud.pojo.Dept;
import com.ygl.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 19:36
 */
@Service
public class DeptServiceImpl implements DeptService {
    
    
    @Autowired
    private DeptDao deptDao;

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

    @Override
    public Dept queryById(Integer id) {
    
    
        return deptDao.queryById(id);
    }

    @Override
    public List<Dept> queruAll() {
    
    
        return deptDao.queruAll();
    }
}

dao层接口代码如下所示:

package com.ygl.springcloud.dao;

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

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 17:44
 */
@Mapper
@Repository //在service注入DepaDao的时候不会报红线
public interface DeptDao {
    
    
    public boolean addDept(Dept dept);
    public Dept queryById(Integer id);
    public List<Dept> queruAll();
}

mapper.xml代码如下图所示:

<?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.ygl.springcloud.dao.DeptDao">
    <insert id="addDept" parameterType="Dept">
        INSERT INTO dept(dname,db_source)
        VALUES(#{dname},DATABASE())
    </insert>

    <select id="queruAll" resultType="com.ygl.springcloud.pojo.Dept">
        select *
        from dept
    </select>
    <select id="queryById" parameterType="Integer"  resultType="com.ygl.springcloud.pojo.Dept">
        select *
        from dept
        where deptno = #{deptno}
    </select>
</mapper>

顺便给大家说下开启mybatis开启二级缓存吧,首先写个mybatis的config配置文件,然后在mybatis.xml文件中记得写< cache/>标签就行,还有记得在实体类上进行序列化,否则开启二级缓存失败。
在这里插入图片描述好,以上都准备好后,接下来启动提供者启动类,在eureka页面如下图所示:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20201216173831483.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTE1MDEwNA==,size_16,color_FFFFFF,t_70

  1.5  将消费者入驻注册服务中心,且可以获取注册服务列表

老方法,先来导入依赖:

<?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>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

        <!-- ribbon依赖,别导错了包,是springCloud -->
        <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>

        <!-- 拿到实体类,我们需要配置api module -->
        <dependency>
            <groupId>org.example</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>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
    </dependencies>
</project>

配置yml文件:

server:
  port: 80

#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/
#    fetch-registry: true
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/
#  instance:
#    prefer-ip-address: true

编写自己configBean(也可以不编写,使用默认):

package com.ygl.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;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 20:32
 */
@Configuration
public class ConfigBean {
    
     //@Configuration ---> 类似于spring中的  application.xml
    //配置负载均衡实现RestTemplate
    @Bean(name = "abc")
    public RestTemplate getRestTemplate(){
    
    
        return new RestTemplate();
    }
}

编写controller,这里消费者不需要service,直接从注册中心中获取数据即可。代码如下:

package com.ygl.springcloud.controller;

import com.ygl.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 20:25
 */
@RestController
public class DeptConsumerController {
    
    
    //消费者不应该有service层
    //RestTemplate...供我们直接调用即可了,注册到spring中
    //(url,实体:map,Class<T> responseType)
    @Autowired
    @Qualifier(value = "abc")
    private RestTemplate restTemplate; //提供多种便捷访问远程http的方法,简单的restful模板
    //前缀
    //private static final String REST_URL_PREFIX = "http://localhost:8001/dept";
    //Ribbon。我们这里的地址应该是一个变量,通过服务名来访问
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

    @Autowired
    private DiscoveryClient client;

//    private static final String REST_URL_PREFIX = "http://eureka7001.com:7001/eureka";

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

    @RequestMapping("/consumer/dept/queryById")
    public Dept queryById(Integer id){
    
    
        String a=REST_URL_PREFIX+"/queryById?id="+id;
        System.out.println("AAA:"+a);
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/queryById?id="+id,Dept.class);
    }
    @RequestMapping("/services")
    public Object services() {
    
    
        List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
        for (ServiceInstance s: instances) {
    
    
            System.out.println(s.getHost()+"\t"+
                    s.getPort()+"\t"+
                    s.getUri()+"\t"+
                    s.getServiceId()
            );
        }
        return this.client;
    }
    @RequestMapping("/consumer/dept/queryAll")
    public List<Dept> queryAll(){
    
    
        List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
        String host = null;
        int port = 0;
        if (instances != null){
    
    
            ServiceInstance instance = instances.get(0);
             host = instance.getHost();
             port = instance.getPort();
        }
        System.out.println("测试地址:"+"http://"+host+":"+port+"/dept/queryAll");
        String s = REST_URL_PREFIX+"/dept/queryAll";
        System.out.println("进来了!");
        System.out.println("地址:"+s);
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/queryAll",List.class);
    }
}

注意:代码中包含注解,那些不懂可以直接看注解。
启动类代码如下所示:

package com.ygl.springcloud;

import com.ygl.myrule.YglRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

/**
 * @author ygl
 * @description 启动类
 * @date 2020/12/10 19:47
 */
@SpringBootApplication
@EnableEurekaClient   //开启eureka
public class DeptConsumer_80 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}

注意:开启Eureka Client,其注解如下所示:@EnableEurekaClient
消费者目录结构如下图所示:
在这里插入图片描述
启动启动类,如下图所示:
在这里插入图片描述通过消费者调用生产者的操作,可以看出基本上就以下几步:
      1、导入依赖
      2、编写配置文件
      3、在启动类上编写启动注解

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

        一句话总结:某时某刻当某一个服务不可以使用的时候,eureka不会立即清理删除,依旧会对该微服务的信息进行保存。

                1、默认情况下,eureka server当在一段时间内没有收到实例的心跳,便会把该实例从注册列表中清除(默认90s),但是在短时间内丢失大量实例心跳,便会触发eureka的自我保护机制。比如我们在开发测试时,多次频繁重启微服务实例,但是我们很少会把eureka server重启,当一分钟内收到心跳数大量减少时,会触发该保护机制。 可以在eureka管理界面看到Renews threshold大于Renews (last min)的时候,就会触发eureka server 的自我保护机制,就会在管理界面上显示一行红字EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. 从警告中可以看出,eureka server虽然收不到实例心跳,但是它还是认为实例是健康的,eureka会保护这些实例,不会把这些实例从注册表中删除。
                2、该保护机制是为了避免网络连接故障,在发生故障的时候,会导致微服务无法建立与注册中心正常通信,但是服务是健康的,不应该注销该服务,如果eureka把这个实例给误删除了,那即使网络通畅了,那么这个微服务也无法注册到服务中心,因为只有微服务启动的时候才会发起注册请求,后面只会发送心跳和服务列表请求,这样的话,虽然实例运行着,但是永远不会让其他服务所发现。因此,在eureka在短时间内丢失大量实例心跳的时候,会进入自我保护模式,在该模式下,eureka会保护注册表上的信息,不会注销其微服务,当网络恢复的时候,eureka会退出自我保护模式,自我保护模式会让集群更加健壮。
                3、在开发过程中,如果我们频繁的重启发布,触发了自我保护机制,则旧的实例没有删除,这时请求可能跑到旧的实例中,而该实例已经关闭,这就导致了请求错误,影响开发测试。所以在开发阶段,可以把自我保护模式关闭,只需要在eureka server配置文件中加上如下关闭自我保护配置即可:eureka.server.enable-self-preservation=false

  1.7  Eureka和Zookeeper区别

         1、先回顾下ACID和CAP:
                  ★  RDBMS (MySQL\Oracle\sqlServer) ===> ACID
                  ★  NoSQL (Redis\MongoDB) ===> CAP
         2、ACID具体指什么:
                  ★  A (Atomicity) 原子性
                  ★  C (Consistency)一致性
                  ★  I(Isolation)隔离性
                  ★  D(Durability)持久性
         3、CAP具体指什么:
                  ★  C(Consistency)强一致性
                  ★  A(Availability)可用性
                  ★  P(Partition tolerance)分区容错性
      CAP只能三进二:CA,CP,AP
         4、eureka和Zookeeper区别在那:
                  ★  Zookeeper满足的是CP(一致性,分区容错性)
                  ★  eureka满足的是AP(可用性,分区容错性)
         5、eureka比Zookeeper好在那?
                  Zookeeper保证的是CP:
                     当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接收服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30-120s,且选举期间整个zookeeper集群是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得zookeeper集群失去master节点是较大概率发生的事件,虽然服务最终能够恢复,但是,漫长的选举时间导致注册长期不可用,是不可容忍的。在集群中,Zookeeper会选择一个节点为主节点。
                  Eureka保证的是AP:
                     Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有之中自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
        ★  Eureka不在从注册列表中移除因为长时间没收到心跳而应该过期的服务
        ★  Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上 (即保证当前节点依然可用)
        ★   当网络稳定时,当前实例新的注册信息会被同步到其他节点中
     因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪

二、负载均衡:Ribbon(基于客户端)

  2.1  负载均衡以及Ribbon

           Ribbon是什么?
               ★  springcloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
               ★  简单来说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon的客户端组件提供一系列完整的配置项,如:连接超时、重试等等。简单的来说:就是消费方去注册中心获取指定服务名称下保存的服务列表中所有地址,然后Ribbon再采用指定规则(轮询、随机、权重和自定义等等算法)去选择具体的某一地址,去连接机器。
           Ribbon能干什么?
               ★  LB,即负载均衡(LoadBalancer),在微服务或分布式集群中经常用的一种应用。
               ★  负载均衡简单的说就是将用户请求分摊到多个服务上,从而达到系统的HA(高可用性)
               ★  常用的负载均衡软件有Nginx、Lvs等等
               ★  Dubbo和SpringCloud均给我们提供了负载均衡,SpringCloud负载均衡算法我们可以自定义
               ★  负载均衡分为集中式LB和进程式LB,Ribbon就是属于进程式LB,就是将LB逻辑集成到消费方,消费方从服务注册中心获取那些地址可以使用,然后自己再从这些地址中选取一个合适的服务器。

  2.2  负载均衡实现

          首先在消费方导入Ribbon依赖:

   <!-- ribbon依赖,别导错了包,是springCloud -->
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-ribbon</artifactId>
       <version>1.4.6.RELEASE</version>
   </dependency>

          然后在config配置文件中的bean中加入负载均衡的注解 @LoadBalanced开启负载均衡,代码如下:

package com.ygl.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;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 20:32
 */
@Configuration
public class ConfigBean {
    
     //@Configuration ---> 类似于spring中的  application.xml
    //配置负载均衡实现RestTemplate
    @Bean(name = "abc")
    @LoadBalanced //Ribbon 负载均衡
    public RestTemplate getRestTemplate(){
    
    
        return new RestTemplate();
    }

}

然后再controller层中,使用RestTemplate,代码如下:

package com.ygl.springcloud.controller;

import com.ygl.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 20:25
 */
@RestController
public class DeptConsumerController {
    
    
    //消费者不应该有service层
    //RestTemplate...供我们直接调用即可了,注册到spring中
    //(url,实体:map,Class<T> responseType)
    @Autowired
    @Qualifier(value = "abc")
    private RestTemplate restTemplate; //提供多种便捷访问远程http的方法,简单的restful模板
    //前缀
    //private static final String REST_URL_PREFIX = "http://localhost:8001/dept";
    //Ribbon。我们这里的地址应该是一个变量,通过服务名来访问
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

    @Autowired
    private DiscoveryClient client;

//    private static final String REST_URL_PREFIX = "http://eureka7001.com:7001/eureka";

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

    @RequestMapping("/consumer/dept/queryById")
    public Dept queryById(Integer id){
    
    
        String a=REST_URL_PREFIX+"/queryById?id="+id;
        System.out.println("AAA:"+a);
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/queryById?id="+id,Dept.class);
    }
    @RequestMapping("/services")
    public Object services() {
    
    
        List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
        for (ServiceInstance s: instances) {
    
    
            System.out.println(s.getHost()+"\t"+
                    s.getPort()+"\t"+
                    s.getUri()+"\t"+
                    s.getServiceId()
            );
        }
        return this.client;
    }
    @RequestMapping("/consumer/dept/queryAll")
    public List<Dept> queryAll(){
    
    
        List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
        String host = null;
        int port = 0;
        if (instances != null){
    
    
            ServiceInstance instance = instances.get(0);
             host = instance.getHost();
             port = instance.getPort();
        }
        System.out.println("测试地址:"+"http://"+host+":"+port+"/dept/queryAll");
        String s = REST_URL_PREFIX+"/dept/queryAll";
        System.out.println("进来了!");
        System.out.println("地址:"+s);
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/queryAll",List.class);
    }
}

最后在主启动类中加入使用Ribbon注解,代码如下:

package com.ygl.springcloud;

import com.ygl.myrule.YglRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

/**
 * @author ygl
 * @description 启动类
 * @date 2020/12/10 19:47
 */
@SpringBootApplication
@EnableEurekaClient   //开启eureka
//在微服务启动的时候就能去加载我们自定义Ribbon类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = YglRule.class)
public class DeptConsumer_80 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}

注意: 注解@RibbonClient中属性name指的是服务节点名称,configuration属性指的是使用我们自定义负载均衡算法,也可以使用默认轮询算法。
下面我给大家说下自定义算法的代码,及其设置:
YglRule.calss代码如下:

package com.ygl.myrule;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/12 16:03
 */
@Configuration
public class YglRule {
    
    

    @Bean
    public IRule myRule(){
    
    
        return new YglRandomRule(); //默认采用轮询算法,现在我们采取自己编写的算法
    }
}

YglRandomRule.class代码如下:

/*
 *
 * Copyright 2013 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.ygl.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 YglRandomRule extends AbstractLoadBalancerRule {
    
    


    //total=0,默认=0,如果=5,我们指向下一个服务节点
    //index=0,默认是0,如果total=5,index++

    private int total; //服务被调用的次数
    private int index;//当前是谁在提供服务

    public Server choose(ILoadBalancer lb, Object key) {
    
    
        if (lb == null) {
    
    
            return null;
        }
        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 = chooseRandomInt(serverCount); //生成区间随机数
//            server = upList.get(index); //从活着的服务获得随机一个

            //======================================

            if (total<5){
    
    
               server = upList.get(index);
                total++;
            }else {
    
    
                total=0;
                total++;
                index++;
                if (index>=upList.size()){
    
    
                    index=0;
                }
                server = upList.get(index); //从活着的服务中,获取指定的服务来进行操作
            }


            //======================================




            if (server == null) {
    
    
                Thread.yield();
                continue;
            }

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

        return server;

    }

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

	@Override
	public Server choose(Object key) {
    
    
		return choose(getLoadBalancer(), key);
	}

	@Override
	public void initWithNiwsConfig(IClientConfig clientConfig) {
    
    
		// TODO Auto-generated method stub
		
	}
}

目录层次架构如下:
在这里插入图片描述
注意: 自定义的算法,一定在最高目录结构中,不要弄错哈。
展示结果如图所示:
在这里插入图片描述
在这里插入图片描述
这个算法没用使用Ribbon默认的轮询算法,这里使用了我们自己写的算法。
其Ribbon流程图如下图所示:
在这里插入图片描述负载均衡的几种策略:
https://images1.pianshen.com/93/37/3708d3fddbc99c4a9b0391674cffb0fd.png

三、Feign

Feign主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问的两种方法:1、微服务名字(Ribbon);2、接口和注解(Feign)
      在上面的文章中,我们可以发现我们可以通过ResTemplate调用其他服务的API,所需要的参数必须在请求的URL拼接,如果参数少的话还可以接收,一旦参数多的话,我们肯定无法忍受(什么玩意,真的恶心死了。)
      为了更好的解决这种问题,Netflix给我们提供了一个框架:Feign;它是一个声明式的Web Service客户端,它的目的就是让Web Service调用的更加简单。Feign提供了Http请求的模板,通过编写简单接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
      Feign则会完全代理HTTP请求,我们可以像调用方法一样调用它就可以完成服务请求和相关处理。而且Feign默认整合了Ribbon和Hystrix,可以让我们不需要显示的使用这两个组件。
它看起来有点像我们springmvc模式的Controller层的RequestMapping映射。这种模式是我们非常喜欢的。Feign是用@FeignClient来映射服务的。
使用步骤:
      1、在api模块的pom文件中导入feign依赖:

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

      2、创建service接口,同时添加@FeignClient(value = “服务节点名称”)注解

package com.ygl.springcloud.service;

import com.ygl.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.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/12 17:13
 */
@Component
//@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT" , fallbackFactory=DeptClientServiceFallbackFactory.class)
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")  //此注解是开启feign;value是feign组件访问服务名称
public interface DeptClientService {
    
    
    @GetMapping("/dept/queryById")
    public Dept queryById(@RequestParam("id") Integer id);
    @GetMapping("/dept/queryAll")
    public List<Dept> queryAll();
    @PostMapping("/dept/addDept")
    public boolean addDept(Dept dept);
}

      3、编写controller层:

package com.ygl.springcloud.controller;

import com.ygl.springcloud.pojo.Dept;
import com.ygl.springcloud.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 20:25
 */
@RestController
public class DeptConsumerController {
    
    

    @Autowired
    private DeptClientService service = null;


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

    @RequestMapping("/consumer/dept/queryById")
    public Dept queryById(@RequestParam("id") Integer id){
    
    
        return this.service.queryById(id);
    }
    @RequestMapping("/consumer/dept/queryAll")
    public List<Dept> queryAll(){
    
    
        return this.service.queryAll();
    }
}

      4、在启动类中开启feign,代码如下所示:

package com.ygl.springcloud;


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

/**
 * @author ygl
 * @description 启动类
 * @date 2020/12/10 19:47
 */
@SpringBootApplication
@EnableEurekaClient   //开启eureka
@EnableFeignClients(basePackages = {
    
    "com.ygl.springcloud"})   //开启feign
@ComponentScan("com.ygl.springcloud")
public class FeignDeptConsumer_80 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(FeignDeptConsumer_80.class,args);
    }
}

运行结果如图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过这三次测试可以发现,feign组件默认集成了Ribbon,并且采用轮询算法。
        Feign是面向接口编程,更加符合java程序员的编程习惯,当然各有各的好处,可以根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.

四、断路器:Hystrix

分布式系统所面临的问题:
     复杂的分布式体系结构中的应用程序有很多个依赖关系,每个依赖关系不可能任何时候都能够依赖成功。

  4.1  服务雪崩

         在多个微服务调用的时候,假设微服务A调用B,微服务B又去调用C,C又去调用其他微服务,这就是所谓的“扇出”,如果在扇出的链路上,某个服务响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”。
         对高流量的应用来说,单一的后端依赖可能在十几秒内就将服务器的资源给应用满。比调用失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他资源紧张,导致整个系统更多的级联故障,这些都需要对级联故障和延迟进行隔离和管理,以便单个依赖的失败不会导致整个系统的失败。也就是我们需要,弃车保帅。

  4.2  什么是Hystrix?

         Hystrix是应用于处理分布式系统的延迟和容错的开源库,在分布式系统中,有许多不可避免的会调用失败,比如超时,异常等等。Hystrix能够保证在一个依赖出现问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的监控故障(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延乃至整个系统崩溃。

  4.3  Hystrix能干什么?

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

  4.4  服务熔断

什么是服务熔断?
       熔断机制是赌赢雪崩效应的一种微服务链路保护机制。
       例如在高压电路中,如果某个地方电压过高,保险丝就会熔断,确保安全,保证不会因电压过高而使电路起热烧坏引起火灾,对电路进行保护。同样熔断机制在微服务架构中起着同样的作用。当调用一个服务响应时间过长或者不可用时,会进行熔断,不再有该节点微服务调用,快速返回错误的相应信息。当监测到该节点微服务调用响应正常后,恢复调用链路。
       当扇出电路中某个服务不可用或者响应时间过长时,会进行服务降级,进而熔断该节点的微服务调用,快速返回错误信息。监测到该节点的微服务调用响应正常的时候,会恢复调用链路。在Spring Cloud框架里面熔断机制是通过Hystrix实现。Hystrix会监控服务调用的状况,当失败的调用到一定阈值是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是:@HystrixCommand
       服务熔断解决的问题:1、当所依赖的服务对象不稳定的时候,能够起到快速失败的目的;2、快速失败后,能够根据一定的算法动态试探对象是否恢复。
熔断实现:
老规矩,先生产者中服务引入hystrix依赖:

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

然后在controller的方法上添加注解 @HystrixCommand,代码如下所示:

package com.ygl.springcloud.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.ygl.springcloud.pojo.Dept;

import com.ygl.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

/**
 * @author ygl
 * @description
 * @date 2020/12/10 19:38
 */
@RestController
@RequestMapping("/dept")
public class DeptController {
    
    
    @Autowired
    private DeptService deptService;

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


    //根据id进行查询
    @GetMapping("/queryById")
    //出现hystrix时返回调用的方法
    @HystrixCommand(fallbackMethod = "queryHystrixById")
    public Dept queryById(Integer id){
    
    
        Dept dept = deptService.queryById(id);
        System.out.println("查询的值为:"+dept);
        if (dept == null){
    
    
            throw new RuntimeException("id===>"+id+",该id不存在,或者信息无法找到~~~");
        }

        return dept;
    }

    //出现问题,备选方法
    public Dept queryHystrixById(Integer id){
    
    
        return new Dept()
                .setDeptno(id)
                .setDname("所查的id"+id+"不存在,信息为null")
                .setDb_source("没有这个数据库");
    }

}

注意: 注解@HystrixCommand中的fallbackMethod 代表的是调用服务失败后调用的方法。
最后在启动类上添加对熔断支持的注解 @EnableCircuitBreaker,代码如下:

package com.ygl.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.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;

/**
 * @author ygl
 * @description 启动类
 * @date 2020/12/10 19:47
 */
@SpringBootApplication
@EnableEurekaClient //服务在启动后自动注册到Eureka中
@EnableDiscoveryClient //服务发现    发现自己
@EnableCircuitBreaker //添加对熔断的支持
@EnableHystrixDashboard //开启监控支持
public class DeptProviderHystrix_8001 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DeptProviderHystrix_8001.class,args);
    }
}

在服务端我们称之为服务熔断,且这里我们只针对其中某一个方法。
而在客户端,我们针对一个类进行服务降级。

  4.5  服务降级

什么是服务降级?
      服务降级是指当服务器压力剧增的时候,根据实际业务情况及流量,对一些服务和页面有策略的不处理或者简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了就是尽可能的把系统资源让给优先级高的服务。 资源有限,而请求是无限的。如果在高并发的情况下,不做降级处理,一方面肯定会影响整体服务的性能,严重的话可能导致宕机某些重要的服务不可用。所以一般在高峰期,为了保证核心功能服务可用性,都要对某些服务降级处理。比如在刚刚过去不久的双十二活动,在当天肯定把交椅无关的服务系统统统降级,比如退款服务,查看历史订单等等。
      服务降级主要用于什么场景呢?当整个微服务架构整体的负载超出了预设的上限阈值或者即将来的流量预计会超过预设的阈值时,为了保证重要和基本的服务能够正常运行,可以将一些不重要或不紧急的服务进行延迟使用或者暂停使用。 降级的方式可以根据业务来,可以延迟服务,比如延迟给用户增加积分,只是放到一个缓存中,等高并发过了之后再执行;或者在粒度范围内关闭服务,比如相关文章的推荐等等。
在这里插入图片描述由上图可知:当某一时间内,服务A的访问量暴增,而B和C的访问量较少,为了缓解A服务的压力,这个时候可以将B和C暂时关闭一些服务功能,去承担A的部分服务,从而为A分担压力,叫做服务降级。
服务降级需要考虑的问题:
       1、那些服务是核心服务,那些服务是非核心服务
       2、那些服务支持降级,那些服务不支持降级,降级策略是什么?
自动降级分类
       1、超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测恢复情况。
       2、失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阈值自动降级,同样要使用异步机制探测恢复情况。
       3、故障降级:比如要调用的远程服务挂掉了,则直接降级。
       4、限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行访问限制访问量,当达到限流阈值,后续请求会被降级;降级处理的方案可以是:排队页面、无货、错误页等等。
实现降级步骤
     以springcloud-consumer-feign为例,这里已经引入feign的依赖,feign中已经包含了hystrix,因此不需要进行单独引入hystrix依赖。先设置yml文件,在yml文件中设置开启hytrix,依赖如下:

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

然后在service层编写一个类并实现FallbackFactory 接口,重写方法,再返回service接口的实现类,针对其中一个方法进行编写fallback函数,代码如下所示:

package com.ygl.springcloud.service;

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

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/14 15:35
 */
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
    
    
    @Override
    public DeptClientService create(Throwable throwable) {
    
    
        return new DeptClientService() {
    
    
            @Override
            public Dept queryById(Integer id) {
    
    
                return new Dept()
                        .setDeptno(id)
                        .setDname("id=>"+id+"没有对应的信息,客户端提供了降级的信息,这个服务已经关闭")
                        .setDb_source("没有数据");
            }

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

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

然后service接口与刚刚编写的实现类建立联系,通过 @FeignClient 注解,代码实现如下:

package com.ygl.springcloud.service;

import com.ygl.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.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * @author ygl
 * @description
 * @date 2020/12/12 17:13
 */
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT" , fallbackFactory=DeptClientServiceFallbackFactory.class)
//@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")  //此注解是开启feign;value是feign组件访问服务名称
public interface DeptClientService {
    
    
    @GetMapping("/dept/queryById")
    public Dept queryById(@RequestParam("id") Integer id);
    @GetMapping("/dept/queryAll")
    public List<Dept> queryAll();
    @PostMapping("/dept/addDept")
    public boolean addDept(Dept dept);
}

这样如果调用服务出现失败或者异常时,就会降级服务,执行fallback函数,并返回相关信息。

  4.6  熔断和降级

服务熔断——>服务端: 某个服务超时或异常,引起熔断,类似于保险丝,是自我熔断。
服务降级——>客户端: 从网站整体请求负荷进行考虑,当某个服务熔断或者关闭之后,服务将不再进行调用,此时客户端,我们准备了一个FallBackFactory,返回一个默认值。会导致整体服务下降,但是总比挂掉强。
       触发原因不一样:服务熔断是当一个服务故障引起的,而服务降级一般是从整体负荷考虑;管理层次不一样:熔断是一个框架级的处理,而降级一般需要对业务有层级之分。
       实现方式不一样:熔断一般是自我熔断,服务降级具有代码侵入性。
限流:
       限制并发的请求访问量,超过阈值则拒绝。降级:服务分优先级,牺牲非核心服务,保证核心服务的运行;从整体符合考虑。熔断:依赖的下游服务出现故障触发熔断,避免引起本系统的崩溃;系统自行执行和恢复。

  4.7  Dashboard流监控

       新建一个springcloud-consumer-hystrix-dashboard模块,专门进行流监控。
       首先引入依赖,代码如下:

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

配置yml文件,代码如下:

server:
  port: 9001

最后在启动类中添加启动Hystrix注解:

package com.ygl.springcloud;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/14 16:42
 */
@SpringBootApplication
//开启监控注解
@EnableHystrixDashboard
public class DeptConsumerDashboard_9001 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DeptConsumerDashboard_9001.class,args);
    }
}

这样就完成了Dashboard的微服务。
接下来,如果你想监控什么,只需要把它注册进去就行,比如想监测8001端口的生产者,只需要在启动类上添加servlet,和添加开启注解,代码如下:

package com.ygl.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.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;

/**
 * @author ygl
 * @description 启动类
 * @date 2020/12/10 19:47
 */
@SpringBootApplication
@EnableEurekaClient //服务在启动后自动注册到Eureka中
@EnableDiscoveryClient //服务发现    发现自己
@EnableCircuitBreaker //添加对熔断的支持
@EnableHystrixDashboard //开启监控支持
public class DeptProviderHystrix_8001 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DeptProviderHystrix_8001.class,args);
    }

    //添加一个bean ,增加一个servlet,配合监控使用,这是个死代码,固定写法
    @Bean
    public ServletRegistrationBean hystrixMetricsStreamServlet(){
    
    
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        return registrationBean;
    }
}

然后将其注册进去就行:
在这里插入图片描述
在这里插入图片描述监控页面
在这里插入图片描述好了,Hystrix就分享到这。

五、服务网关:Netflix Zuul

        什么是Zuul?
               Zuul包含了对请求的路由(用来跳转)和过滤两个主要功能。
               其中路由功能负责将外部的请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤功能负责的是对请求处理过程进行干预,是实现请求校验,服务聚合等功能的基础。 Zuul和Eureka进行整合,将Zuul注册到Eureka中,同时从注册中心中获取其他服务信息,以后获取的数据就是经过Zuul跳转获得的。
注意:Zuul需要注册到Eureka中
提供:代理+路由+过滤

        Zuul实现?
首先引入Zuul依赖,代码如下:

<?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>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-zuul-9527</artifactId>
    <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>
        <!-- 监控依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <!-- ribbon依赖,别导错了包,是springCloud -->
        <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>

        <!-- 拿到实体类,我们需要配置api module -->
        <dependency>
            <groupId>org.example</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>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
    </dependencies>

</project>

在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: zuul-9527 #注册中心描述信息
    prefer-ip-address: true #是否显示ip

info:
  app.name: ygl-springcloud
  company.name: www.baidu.com

zuul:
  routes:
    mydept.serviceId: springcloud-provider-dept  #将原来的服务名称换成我们自己的服务名称
    mydept.path: /mydept/** #这里是我们更改后的访问路径
  ignored-services: springcloud-provider-dept #忽略掉这个服务名称,不能再使用这个路径去访问了
  # ignored-services: “*” #这个是隐藏全部的服务名称
  prefix: /ygl #这个是统一访问前缀  设置公共前缀

在启动类中添加开启zuul代理,代码如下:

package com.ygl.springcloud;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/15 14:20
 */
@SpringBootApplication
@EnableZuulProxy //开启zuul代理
public class ZuulApplication_9527 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ZuulApplication_9527.class,args);
    }
}

访问结果如下图所示:
在这里插入图片描述

六、Spring Cloud Config

        大家都明白,一个项目越大,用到的配置文件越多,如果这些配置文件都放在项目中,那么就会产生冗余,而且管理起来比较麻烦,因此就有了config模块。
        Config拥有服务端和客户端,服务端就是从码云或者github上获取配置文件,当然也可以从本地获取哈,然后客户端从服务端获取这些文件。
        当然服务端从云端获取的文件是我们自己上传好的,格式大概如下所示:
在这里插入图片描述

spring:
    profiles:
        active: dev

---
server:
  port: 7001

#spring配置
spring:
    profiles: dev
    application:
        name: springcloud-config-eureka


#Eureka配置
eureka:
  instance:
    hostname: eureka7001.com  #Eureka服务端的实例名称
  client:
    register-with-eureka: false  #表示是否向eureka注册中心注册自己
    fetch-registry: false  #fetch-registry如果为false,则表示自己为注册中心,
    service-url:  #监控页面
      #单机: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      #集群(关联):
#      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
      defaultZone: http://eureka7002.com:7002/,http://eureka7003.com:7003/
      
      
---
server:
  port: 7001
  
spring:
    profiles: test
    application:
        name: springcloud-config-eureka

#Eureka配置
eureka:
  instance:
    hostname: eureka7001.com  #Eureka服务端的实例名称
  client:
    register-with-eureka: false  #表示是否向eureka注册中心注册自己
    fetch-registry: false  #fetch-registry如果为false,则表示自己为注册中心,
    service-url:  #监控页面
      #单机: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      #集群(关联):
#      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
      defaultZone: http://eureka7002.com:7002/,http://eureka7003.com:7003/

从云端获取配置文件大概就是:客户端==》 服务端==》云端
在这里插入图片描述1、先配置config服务端:
创建springcloud-config-server-3344模块
导入config-server依赖:

<?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>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-config-server-3344</artifactId>

    <dependencies>
        <!-- config的server依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
            <version>2.1.1.RELEASE</version>
        </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>

</project>

编写yml配置文件:

server:
  port: 3344

spring:
  application:
    name: springcloud-config-server
  #连接远程配置
  cloud:
    config:
      server:
        git:
          # 通过config-server可以连接到git,访问其中的资源及配置
          uri: https://gitee.com/chinahnygl/springcloud-config.git

在启动类上编写开启config注解:

package com.ygl.springcloud;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/15 16:03
 */
@SpringBootApplication
@EnableConfigServer
public class Config_Server_3344 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Config_Server_3344.class,args);
    }
}

运行成功页面如下所示:
在这里插入图片描述新建Config模块,建立springcloud-config-client-3355模块
首先引入config客户端依赖:

<?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>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-config-client-3355</artifactId>
    <dependencies>
        <!-- config依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
            <version>2.1.1.RELEASE</version>
        </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>

</project>

然后配置yml配置文件,注意这里需要配置两个yml,一个是用户级别,一个是系统级别
在这里插入图片描述bootstrap.yml配置文件:

#系统级别配置
spring:
  cloud:
    config:
      # server端连接云端 ; client连接server端
      uri: http://localhost:3344
      name: config-client # 需要从git上读取的资源名称,不要带后缀
      profile: test
      label: master

application.yml配置文件代码:

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

server:
  port: 3355

controller代码如下:

package com.ygl.springcloud.controller;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/15 17:16
 */
@RestController
public class ConfigClientController {
    
    

    @Value("${spring.application.name}")
    private String applicationName;

    @Value("${eureka.client.service-url.defaultZone}")
    private String eurekaServer;

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

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

运行代码如下所示:
在这里插入图片描述新建注册中心,先创建个springcloud-config-eureka-7001模块。
引入config客户端依赖:

<?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>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <!-- 导包 -->
    <dependencies>

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

        <!-- eureka 的server端 -->
        <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>

同样,yml配置文件要配置两个,一个是用户级别,一个是系统级别,application.yml代码如下:

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

系统级别配置bootstrap.yml代码如下:

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

启动类如下:

package com.ygl.springcloud;

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

/**
 * @author ygl
 * @description
 * @date 2020/12/11 11:03
 */

//启动之后访问 http://localhost:7001/
@SpringBootApplication
@EnableEurekaServer  //服务端的启动类,可以接受别人注册进来~
public class EurekaServer_7001 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}

        好了,经过这两天的编写,把spring cloud的五大组件给大家分享完了,中间肯定有很多瑕疵和问题,请大家把疑问写在下方评论中,和大家一起探讨,对了,项目代码我已经放在我的github上了,下载地址是:https://github.com/ygl01/springclod.git
        请大家点赞 转发 评论三连哦,谢谢大家,让我们一起进步

猜你喜欢

转载自blog.csdn.net/weixin_45150104/article/details/111279847
今日推荐