SpringCloud+Eureka初识+Ribbon+Feign+Hystrix(服务熔断,服务降级)+hashbroad

发布时间 2023-05-21 17:53:18作者: 醒醒起来

​Eureka注册中心

 1.导包

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <version>2.1.2.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>

        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->

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

 

2.配置文件

server:
  port: 7001
eureka:
  instance:
    hostname: localhost  #eureka服务器的实例
  client:
    register-with-eureka: false
    fetch-registry: false

    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

 

3.主启动类: 

package com.lian.springcloud;

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

//启动之后,访问http://localhost:7001/
@SpringBootApplication
@EnableEurekaServer//服务端的启动类
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}

 

报错1:Caused by: java.lang.ClassNotFoundException: org.springframework.boot.actuate.health.CompositeHealthContributor

 

解决方法:原因是eureka依赖包的版本和我的spring-boot-starter-web冲突了,讲eureka版本降成2.1.2.RELEASE

 报错2:集成eureka报错 main] org.apache.catalina.core.ContainerBase : A child container failed duri

2023-05-20 10:39:31.626 ERROR 22292 --- [           main] org.apache.catalina.core.ContainerBase   : A child container failed during start

 解决方法:原因是jar包冲突,排除依赖的包servlet-api

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

 

 

访问成功:

 

 

 

 

 

在provider配置注册中心

上面我们已经成功连接上了eureka的服务中心,下面我们需要在启动provider模块时使其自动注册进eureka的服务中心

1.导包:

      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
<!--        #监视器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

 

2.配置类

这里info要导入依赖spring-boot-starter-actuator才能使用

 

3.主启动类开启注解

 

 

测试访问:先启动eureka-7001,再启动provider-8001

成功拿到

 

 


Eureka的自我保护机制:

当某个服务断电了等原因不能用了,eruka不会立即清理数据,依旧会对微服务的信息进行保存

 

 

 扩展:在公司团队协作中:添加微服务列表清单

 测试结果:成功访问

 

 

Eureka集群配置

 

CAP原则:C一致性 A可用性 P容错性

 

 

zookeeper保证一致性和容错性

 eureka保证可用性和容错性

 因此 Eureka可以很好的应对网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使得整个注册服务瘫痪。

 

客户端负载均衡及Ribbon

 

 

 

 

 测试1.保证消费者模块成功注册到eureka注册中心

1.导入依赖包

 

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

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

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <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>
    </dependencies>

</project>

 

 

2.配置文件 application.yaml

 

server:
  port: 80
  #配置 ure
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7003.com:7003/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/

 

3.配置类添加注解开启Ribbon

 3.controller类请求

package com.lian.springcloud.controller;

import com.lian.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,消费者只和前端打交道
    //所以要调用方法,通过RestTemplate..供我们调用
    @Autowired
    private RestTemplate restTemplate;
    //通过ribbon实现,我们这里应该是一个变量
    private static final String REST_URL_PREFIX="http://SPRINGCLOUD-PROVIDER-DEPT";
//    private static final String REST_URL_PREFIX="http://localhost:8080";
    @RequestMapping("/consumer/dept/add")
    //
    public Boolean add(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }
    ////通过id获取用户
    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id")int id){
        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);
    }
}

4.主启动类开启euraka注解

  5.测试顺序:

开启eurka的三个注册中心,开启provider,开启consumer

报错1:

 

springcloud报错Caused by: java.lang.NoClassDefFoundError: org/hibernate/validator/internal/engine/DefaultClockProvider

原因是忘记在主启动类开启注解

 

报错2:

2023-05-20 14:29:39.083 ERROR 9264 --- [p-nio-80-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 null] with root cause

 

org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 null

 原因是,我在前端访问的是http://localhost/consumer/dept/list,但是这个方法我在模块provider的dao底层用的是post请求,解决方法是把底层的post改成get

测试2:Ribbon轮循算法

Ribbon默认的是轮循算法,即假设我们有多个provider服务器,消费者通过80端口访问时候,根据轮循算法去访问一个provider服务器,如消费者相同的url请求,第一次访问到的是1号provider,消费者刷新一次页面,访问到的是2号provider,消费者再刷新一次页面,访问到的又是1号provider,如此循环即为轮循。轮询算法如果一个provider崩了,那么只会对其他provider进行轮循

实现步骤:

1.首先我们建了三个不同数据库,三个库都有一张dept表,这三张表只有字段db_source不同

 

2.接着我们创建了2号provider和3号provider,同时更改配置的yaml文件分别连接2号数据库和3号数据库,

 

启动测试:

启动eruka7001注册中心-->启动provider1号,2号,3号-->启动consumer模块-->访问“http://localhost/consumer/dept/get/1”

开始我并没有看到轮循效果,因为我的DeptMapper.xml文件中,把要查询的数据库定死了,如下把first删掉即可

 

 再刷新:可以看到现在是second数据库了

 

测试3:实现Ribbon自定义算法

负载均衡默认算法是轮循,但是我们可以自定义我们自己的算法,官方文档明确指出我们不应该在主启动类的同一个包下自定义ribbon算法,应该另开一个包

 

 步骤:

1.创建配置类LianRule,思想是实现接口IRule,记得注解

@Configuration
public class LianRule {
    @Bean
    public IRule myRule(){//我们要自定义自己的ribbon算法,只需要实现官方的IRule接口
        //LianRandomRule()对应官方的RandomRule()
        return new LianRandomRule();
    }
}

 

2.具体实现类LianRandomRule,

LianRandomRule()对应官方的RandomRule()
package com.lian.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 LianRandomRule extends AbstractLoadBalancerRule {

    /**
     * Randomly choose from all living servers
     */
//    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    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) {
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            }

            int index = chooseRandomInt(serverCount); //生成随机数
            server = upList.get(index);

            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            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

    }
}

3.参考官方文档在主启动类添加注解

 4.启动测试:启动eruka7001注册中心-->启动provider1号,2号,3号-->启动consumer模块-->访问“http://localhost/consumer/dept/list”

不断刷新,随机连接不同的provider,使用的数据库随机变化

 Feign介绍:

feign是面向接口的编程架构,而前面的resttemplate是restful风格架构;OpenFeign 底层内置了 Ribbon 框架,因此Feign本质还是调用了Ribbon 负载均衡。

 

 

Feign原理

例子演示:我们靠Frign的方式,实现前端获取数据库用户记录 

步骤:

1.给springcloud-api导依赖

 

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

 

 

 

开始的报错:

Caused by: java.io.FileNotFoundException: class path resource [org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfiguration.class] cannot be opened because it does not exist

原因是:导入的feign依赖和原本springboot的依赖版本冲突,换成下面这个就可以了

 

 2.在springcloud-api创建服务接口

 3.为了方便对比,新建一个consumer-dept-feign模块,给consumer-dept-feign模块导入依赖

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

    <groupId>com.lian</groupId>
    <artifactId>springcloud-consumer-dept-feign</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
<!--        替代ribbon-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
        <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>
    </dependencies>

</project>

 

4.controller类方法:

 4.主启动类

 

5.测试访问成功

 服务降级:

客户端从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端,我们可以准备一个FallbackFactory,返回一个默认的值,整体的服务水平下降了,但是系统还能正常运行

 1.springcloud-api写一个降级类

 

2.在springcloud-api模块的service目录下的接口要绑定我们写的降级类

 3.在消费者模块的配置文件中增加服务降级的配置

 

 

4.启动测试

未关闭后端的provider时若前端访问空白的用户id,则网页没有报错只是空白

关闭后端的provider后的页面提示

 

 小总结:这几天学了:

eureka是注册中心,服务提供者和消费者都需要去注册中心注册,关于分布式系统的CAP理论告诉我们:一个分布式系统只能同时满足其中两个。eurka保证的是AP,就是可用性和容错性,我们可以有多个注册中心,防止单个注册中心崩溃;我们可以搭建集群,多个注册中心,多个provider提供者,但是消费者只能一个;

erueka还有自我保护机制,这个机制使得加入有某个服务器断电或者故障了,eureka还是能够保留原来的数据不丢失。

Ribbon和负载均衡,我们通过Ribbon将用户的访问交给到不同的provider处理,类似于分流,防止服务器压力过大,Ribbon本身默认的是轮循算法,我们可以通过实现接口IRule来自定义我们的算法,但是需要注意的是官网提到了不能把自定义的轮循算法放在和主启动类同一个包下,因此我们需要自己建一个包编写我们自定义算法同时把它注册到spring容器里

Feign:开发人员习惯面向接口编程而来,是为了我们在消费者的controller更方便调用,而不使用RestTemplate模板类,实现过程:导包(注意版本)我们需要在api内定义服务接口,同时在接口上开启注解@FeignClient,在消费者模块也要导入feign依赖包,编写更简便的controller方法然后在主启动类开启支持feign注解

服务熔断:服务器之间,依赖包之间等可能存在相互调用的关系,即provider1需要调用provider2,provider2需要调用provider3,当provider2出现故障后,任务无法正常进行,后面用户的请求全部阻塞在了provider1,同时容易级联其他的问题,在高并发情况下非常容易导致整个系统雪崩,因此我们通过Hystrix技术栈有效处理服务熔断的情况。

服务降级:有时候单个服务器收到非常非常多用户的访问,而另些服务器基本无人问津,那么我们就关闭这些无人问津的服务器,把更多资源分配给火爆的服务器,来降低受很多人访问的服务器的压力,我们需要在springcloud-api的service目录写降级类实现接口FallbackFactory,我使用的是feign进行的测试,所以在我原本写的接口DeptClientService需要通过注解绑定降级类,测试:开启eruaka7001->开启provider8001->开启 FeignDeptConsumer_80  当我们关闭provider8001查看页面是否有打印出提示信息。