SpringCloud NetFlix学习笔记(一)

前言:
学习B站UP主狂神说视频笔记整理视频链接

微服务

服务演进

三层架构+MVC >> SSM >> SpringBoot

它们都属于all in one 单体架构

单体架构缺陷:

1. 先天性缺陷:难以分布式部署和扩容
2. 系统性风险:一个组件的缺陷导致真个进程崩溃
3. 运维风险:系统升级、Bug修复、故障排查存在风险
4. 难以可持续发展:业务范围拓展后,难以复用原有的服务,又要重新开发

微服务:

功能化,模块化
将原来的项目进行模块化拆分
在这里插入图片描述

微服务特点:

1. 先天分布式:每个微服务能独立部署和提供服务,通常部署多个实例
2. 无状态:微服务基本都是无状态服务,容易平滑扩容
3. 积木式发展:微服务组成了系统的可复用“积木”,更容易随着业务发展而稳步升级

微服务架构问题

  1. 这么多服务,客户端如何进行访问?
  2. 这么多服务,服务之间如何进行通讯?
  3. 服务之间如何进行治理?
  4. 服务宕机如何进行处理?

基于微服务架构问题,很多公司都给了一套解决方案

解决方案

SpringCloud NetFlix

它不是一个新技术,而是一个生态

服务访问:API网关-->Zuul
服务通信:HTTP通信-->httpClient--> feign
服务治理:注册中心-->Eureka
服务容灾:熔断机制-->Hystrix

SpringCloud NetFlix 在2018年底,宣布无限期停止维护

Apache Dubbo Zookeeper

服务访问:API网关--> 没有
服务通信:RPC-->基于Java实现的RPC框架-->Dubbo
服务治理:注册中心-->Zookeeper (动物园管理者)
服务容灾:没有

Spring Cloud Alibaba

一站式解决方案

微服务与微服务架构

微服务

强调的是服务的大小,他关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,狭义的看,可以看做是IDEA中的一个个微服务工程,或者Moudel

微服务架构

一种新的架构形式,Martin Fowler,2014提出

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务于服务间采用轻量级的通信机制互相协作,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具对其进行构建。

微服务技术栈

微服务条目 落地技术
服务开发 SpringBoot
服务注册 Eureka, Zookeeper, Nacos
服务调用 Rest, RPC ,gRPC
服务熔断 Hystrix, Envoy
负载均衡 Ribbon, Nginx
服务接口调用 Feign
消息队列 RabbitMQ ,Kafka
服务配置中心管理 SpringCloud Config
服务路由 Zuul
服务监控 Zabbix Nagios等
全链路追踪 Zipkin ,Brave ,Dapper
服务部署 Docker ,OpenStack ,Kubernetes
数据量操作开发包 SpringCloud Stream
事件消息总线 SpringCloud Bus

SpringCloud

什么是SpringCloud

在这里插入图片描述
SpringCloud,基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。

SpringCloud与SpringBoot

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

学习环境构建

服务提供者

创建Maven父级项目

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tutony</groupId>
    <artifactId>SpringCloud-NetFlixStudy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--子项目-->
    <modules>
        <module>NetFlix-API</module>
    </modules>
    <!--打包方式-->
    <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>
        <lombok.version>1.18.20</lombok.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <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.22</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.22</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

创建API子级项目

在这里插入图片描述

<?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-NetFlixStudy</artifactId>
        <groupId>com.tutony</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>NetFlix-API</artifactId>


    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

创建数据库表及实体类

@Data
@NoArgsConstructor
@Accessors(chain = true)//支持链式写法
public class dept implements Serializable {
    
    

    private Long id;
    private String dname;
    //来自哪个数据库  在分布式项目中 可能存在多个数据库
    private String dbSource;

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

创建服务提供者子级项目

在这里插入图片描述

<?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-NetFlixStudy</artifactId>
        <groupId>com.tutony</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>NetFilx-provider-8001</artifactId>


    <dependencies>
        <!--拿到自己编写的module-->
        <dependency>
            <groupId>com.tutony</groupId>
            <artifactId>NetFlix-API</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.4</version>
            </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <!--web-->
        <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>

</project>

编写配置文件

server:
  port: 8001
# mybatis
mybatis:
  type-aliases-package: com.tutony.common
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true #驼峰命名
# spring
spring:
  application:
    name: provider-8001
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456

编接口

@Mapper
@Repository
public interface DeptMapper {
    
    


    public Integer addDept(Dept dept);

    @Select("select * from dept where id = #{id}")
    public Dept queryById(Long id);

    @Select("select * from dept")
    public List<Dept> queryDept();

}

mapper映射

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.tutony.mapper.DeptMapper"><!--定义接口全路径名-->

    <insert id="addDept" parameterType="com.tutony.common.Dept">
        insert into dept (dname,db_source) values (#{dname},database())
    </insert>

</mapper>

编写Service

public interface DeptService {
    
    

    public Integer addDept(Dept dept);


    public Dept queryById(Long id);


    public List<Dept> queryDept();
}
@Service
public class DeptServiceImpl implements DeptService{
    
    

    @Autowired
    private DeptMapper deptMapper;

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

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

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

Controller

@RestController
@RequestMapping("/dept")
public class DeptController {
    
    

    @Autowired
    private DeptService deptService;

    @PostMapping("/save")
    public Integer addDept(@RequestBody Dept dept){
    
    
        return deptService.addDept(dept);
    }

    @GetMapping("/get/{id}")
    public Dept getById(@PathVariable Long id){
    
    
        return deptService.queryById(id);
    }
    @GetMapping("/list")
    public List<Dept> list(){
    
    
        return deptService.queryDept();
    }

}

主启动类

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

测试运行:
在这里插入图片描述

服务消费者

创建服务消费者子级项目

在这里插入图片描述
导入依赖

<?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-NetFlixStudy</artifactId>
        <groupId>com.tutony</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>NetFilx-consumer-80</artifactId>
    <!--消费者-->


    <dependencies>
        <!--拿到自己编写的module-->
        <dependency>
            <groupId>com.tutony</groupId>
            <artifactId>NetFlix-API</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

编写配置

server:
  port: 80

spring:
  application:
    name: consumer-80

容器中注入RestTemplate

@Configuration
public class ConfigBean {
    
    

    @Bean
    public RestTemplate getResTemplate(){
    
    
        return new RestTemplate();
    }

}

编写controller

@Controller
@RequestMapping("/consumer")
public class DeptConsumerController {
    
    

    @Autowired
    private RestTemplate restTemplate;
    //远程请求地址
    private static final String REST_URL_PROVIDER = "http://localhost:8001";

    @PostMapping("/save")
    public Integer addDept(@RequestBody Dept dept){
    
    
        //参数: url地址 请求参数  返回值类型
        return restTemplate.postForObject(REST_URL_PROVIDER+"/dept/save",dept,Integer.class);
    }

    @GetMapping("/get/{id}")
    public Dept get(@PathVariable Long id){
    
    
        return restTemplate.getForObject(REST_URL_PROVIDER+"/dept/get/"+id,Dept.class);
    }

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

}

测试访问
在这里插入图片描述

Eureka服务注册与发现

什么是Eureka

Netflix在设计Eureka时,遵循的就是AP原则

Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper;
在这里插入图片描述

原理分析

使用Eureka的客户端连接到EurekaServer并维持心跳连接。
这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑;

Eureka Client是一个Java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除掉(默认周期为90秒)

创建Eureka项目

eureka需要导入的依赖有这些

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

在这里插入图片描述

导入依赖

<?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-NetFlixStudy</artifactId>
        <groupId>com.tutony</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>NetFlix-Eureka</artifactId>

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

</project>

编写配置

server:
  port: 7001
# Eureka server
eureka:
  instance:
    hostname: localhost # 服务端实列名
  client:
    register-with-eureka: false # 是否注册自己
    fetch-registry: false #如果为false 则表示自己为注册中心
    service-url: # 监控页面地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

编写启动类

@EnableEurekaServer //开启Eureka 服务端启动类
@SpringBootApplication
public class EurekaApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(EurekaApplication.class,args);
    }
}

访问地址:http://localhost:7001/
在这里插入图片描述

服务提供者注册到Eureka中

在服务提供者的项目中导入依赖

      <!-- eureka服务提供者 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

编写配置 注册到Eureka中

# eureka 服务提供者
eureka:
  client:
    service-url: # 向这个地址注册
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: springcloud-provider-dept #修改在Eureka中显示的默认描述

编写主启动类 启动服务

@EnableEurekaClient //服务启动后自动注册到Eureka
@SpringBootApplication
public class DeptApplication {
    
    
    public static void main(String[] args){
    
    
        SpringApplication.run(DeptApplication.class,args);
    }
}

扩展:

完善Eureka中服务监控信息
在这里插入图片描述
服务提供者中导入依赖

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

完善配置信息:

# eureka 服务提供者
eureka:
  client:
    service-url: # 向这个地址注册
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: springcloud-provider-dept #修改在Eureka中显示的默认描述

# 完善监控信息
info:
  app.name: kuangshen-spring-add

在这里插入图片描述

获取微服务中的信息

//获取微服务中的信息
    @Autowired
    private DiscoveryClient client;

    //获取微服务信息
    @GetMapping("/discovery")
    public Object discovery(){
    
    
        //获取微服务列表清单
        List<String> services = client.getServices();
        System.out.println("注册到springcloud中的列表清单:"+services);
        //获取某一个微服务具体的信息 参数: spring.application.name的名称
        List<ServiceInstance> instances = client.getInstances("provider-8001");
        instances.forEach(ins -> {
    
    
            System.out.println(ins.getInstanceId());
            System.out.println(ins.getHost());
            System.out.println(ins.getPort());//端口
            System.out.println(ins.getUri());//url
        });
        return instances;
    }

在这里插入图片描述

自我保护机制

在这里插入图片描述
某时刻某一个微服务不可以用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存!

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

在SpringCloud中,可以使用eureka.server.enable-se1f-preservation = false禁用自我保护模式【不推荐关闭自我保护机制】

Eureka集群搭建

在这里插入图片描述

创建项目
在这里插入图片描述
导入依赖:

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

配置Eureka7001
在这里插入图片描述

Eureka7002
在这里插入图片描述

Eureka7003
在这里插入图片描述

修改服务提供者 向三个Eureka注册信息
在这里插入图片描述

CAP原则

在这里插入图片描述

AP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

作为服务注册中心,Eureka比zookeeper好在哪里?

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A (可用性)、P(容错性)。由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡。

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

Zookeeper保证的是CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30~120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得水k集群失去master节点是较大概率会发生的事件,虽然服务最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

Eureka保证的是AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

1.Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
2.Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用)
3.当网络稳定时,当前实例新的注册信息会被同步到其他节点中

Ribbon客户端负载均衡

什么是Ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接在一起。Ribbon的客户端组件提供一系列完整的配置项如:连接超时、重试等等。简单的说,就是在配置文件中列出LoadBalancer(简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法!

Ribbon能干嘛

LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。
负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。常见的负载均衡软件有Nginx,Lvs等等
dubbo、SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义负载均衡

简单分类:

  • 集中式LB
    即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该设施负责把访问请求通过某种策略转发至服务的提供方!
  • 进程式LB
    将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
    Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址!

在项目中集成Ribbon

测试项目客户端导入依赖
在这里插入图片描述

   <!-- 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://eureka7003.com:7003/eureka/,http://eureka7002.com:7002/eureka/

配置负载均衡策略

    //配置负载均衡实现RestTemplate 核心注解@LoadBalanced
    @Bean
    @LoadBalanced
    public RestTemplate getResTemplate(){
    
    
        return new RestTemplate();
    }

通过Ribbon访问服务请求地址应该是服务名

在这里插入图片描述

在这里插入图片描述

Ribbon和Eureka整合以后,客户端可以直接调用,不用关心IP地址

Ribbon实现负载均衡

在这里插入图片描述
新建项目
在配置文件中修改端口号,修改描述
在这里插入图片描述
调用接口时,Ribbon就会自动选择接口下连接的服务进行负载均衡
默认是轮询策略
在这里插入图片描述

自定义负载均衡策略

在Ribbon中默认的负载均衡策略是轮询,有时候我们并不想使用轮询.
于是我们可以自定义负载均衡策略

Ribbon的负载均衡事通过IRule接口实现
在这里插入图片描述
此接口有以下这些默认实现
在这里插入图片描述
调几个重点来说:

  • AvailabilityFilteringRule:会先过滤掉,跳闸,访问故障的服务,对剩下的进行轮询~
  • RandomRule:随机策略
  • RoundRobinRule:轮询
  • WeightedResponseTimeRule:权重
  • RetryRule:先获取轮询服务,如果获取失败,会在规定时间内重试

调一个经典的轮询,分析一波源码

public class RoundRobinRule extends AbstractLoadBalancerRule {
    
    

//这些参数看不懂 没关系
    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
    
    
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
    
    
        this();
        setLoadBalancer(lb);
    }

  //轮询算法 从AbstractLoadBalancerRule获取ILoadBalancer 
    public Server choose(ILoadBalancer lb, Object key) {
    
    
    //如果为空 则没有负载均衡
        if (lb == null) {
    
    
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
    
    
        //获取活着的服务 可能存在多个
            List<Server> reachableServers = lb.getReachableServers();
            //获取所有服务
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();
            //无服务 无法进行轮询 返回空
            if ((upCount == 0) || (serverCount == 0)) {
    
    
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            //以下是算法 如果自定义负载均衡策略 只需要自定义下面的算法即可实现
            int nextServerIndex = incrementAndGetModulo(serverCount);
            //获取下一个节点 轮询
            server = allServers.get(nextServerIndex);

//如果为空 则线程礼让 判断下一个
            if (server == null) {
    
    
                /* Transient. */
                Thread.yield();
                continue;
            }
//如果线程存活 返回当前线程
            if (server.isAlive() && (server.isReadyToServe())) {
    
    
                return (server);
            }

            // Next.
            server = null;
        }
// 如果大于10 则告诉我们 没有可用的
        if (count >= 10) {
    
    
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
    
    
        for (;;) {
    
    
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

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

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    
    
    }
}

定义随机负载均衡策略


  //自定义负载均衡策略
    @Bean
    public IRule myIRule(){
    
    
        //随机
        return new RoundRobinRule();
    }

自定义负载均衡

从官网得知,我们自定义的负载均衡组件不能放在SpringBoot主启动类下的同级目录下,否则会被扫描到,此时我们应该把组件单独提出来
在这里插入图片描述
将组件单独提出来
在这里插入图片描述
在主启动类上引用
在这里插入图片描述
自定义注解只需要重写AbstractLoadBalancerRule修改里面的算法,然后注入到Bean即可

猜你喜欢

转载自blog.csdn.net/weixin_46684099/article/details/117509711