SpringCloud Eureka注册中心与Robbin负载均衡如何实现高效集群管理?

2026-06-11 12:422阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5367个文字,预计阅读时间需要22分钟。

SpringCloud Eureka注册中心与Robbin负载均衡如何实现高效集群管理?

学习目标+了解系统架构的演变+知道什么是SpringCloud+独立搭建Eureka注册中心+独立搭建Ribbon负载均衡+系统架构演变+要学微服务,我们先来看看系统架构的演变,从而对微服务架构有更深入的理解。


学习目标

  • 了解系统架构的演变
  • 知道什么是SpringCloud
  • 独立搭建Eureka注册中心
  • 独立配置Robbin负载均衡

系统架构演变

要学微服务,我们先来看看系统架构的演变史,从而对微服务架构进行更深层次的了解。

随着互联网的发展,网站应用的规模不断扩大,需求的剧增,带来了系统架构不断的演进、升级和迭代。

系统架构的演变,大体上划分为:传统架构、负载均衡架构,分布式服务架构,SOA架构,微服务架构。

传统架构:单一应用

当网站流量很小时,只需一个应用,将所有功能都部署在一起。

集群:负载均衡

随着访问量的变大,一个Tomcat服务器已经不能满足需求。

需要配置多个Tomcat,做Tomcat集群。通过负载均衡服务器访问不同的Tomcat服务器,每个Tomcat中部署完整项目即可。

分布式服务

每一个Tomcat存放完整项目,会存在部分业务模块长时间用不到,部分业务模块不够用的现象。

我们将按照功能模块拆分项目,每一台服务器仅存放某一个模块,通过多系统的配合完成整体业务逻辑,此种方式成为:分布式服务。

SpringCloud Eureka注册中心与Robbin负载均衡如何实现高效集群管理?

SOA架构:面向服务架构

SOA(Service Oriented Architecture)面向服务架构,就是将工程拆分成表现层和服务层两个工程。

服务层中包含业务逻辑,只需要对外提供服务即可。

表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。


微服务

微服务是SOA的一种实现,也可以说是微服务是去ESB(取中心)的SOA。

微服务架构是一种将单个应用程序作为一套小型服务开发的方法。每种应用程序都可以独立运行。应用程序之间远程调用进行通信。

  • 微服务总结:(特性)
  • 完全独立的一个最小个体。(可以独立运行)
  • 个体与个体之间,通过远程调用进行访问。例如:基于RESTFul风格的。
  • 通过注册中心,将不同个体可以进行整合。
  • 通过网关,可以进行统一的入口访问。

    初始SpringCloud

    什么是微服务

    微服务是一种架构风格,即将单体应用划分为小型的服务单元。

    微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?

    • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
    • 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了
    • 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
    • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建

    什么是SpringCloud

    SpringCloud是一系列框架的集合,它利用SpringBoot的开发便利性,简化了分布式系统开发,如:服务注册、服务发现、配置中心。消息总线、负载均衡、熔断器、数据监控等。

    SpringCloud主要贡献者是Netflix,也就是SpringCloud是对Netflix贡献的框架的二次封装或优化。

    通俗的讲,SpringCloud就是用于构建微服务开发和治理的框架集合。

    SpringCloud常见模块

    SpringCloud主要涉及的组件包括:

    • Eureka:服务注册中心,用于管理服务
    • Ribbon:负载均衡(集群)
    • Hystrix:熔断器,能够防止服务的雪崩效应。
    • Zuul:服务网关,提供路由转发、请求过滤等功能。
    • Feign:服务调用,简化Http接口的调用。

    以上只是其中一部分,架构图:

    版本

    SpringCloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,它的命名是以A到Z的为首字母的一些单词组成:

    我们的学习,将以Hoxton.SR9的版本。(最新版本,需要同步升级相关软件)

    参考文档:​​Spring Cloud​​

    入门案例

    父项目

    创建项目

    创建父项目:cloud-parent-1007

    pom配置文件

    修改pom.xml,确定springcloud的版本

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="maven.apache.org/POM/4.0.0"
    xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="maven.apache.org/POM/4.0.0 maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.czxy.cloud</groupId>
    <artifactId>cloud-parent-1007</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 1 确定spring boot的版本-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    </parent>

    <!--2 确定版本-->
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <spring-cloud-release.version>Hoxton.SR9</spring-cloud-release.version>
    </properties>

    <!-- 3 锁定sprig cloud版本-->
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud-release.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <!-- 4 确定spring cloud私有仓库
    <repositories>
    <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>repo.spring.io/milestone</url>
    <snapshots>
    <enabled>false</enabled>
    </snapshots>
    </repository>
    </repositories>
    -->

    </project>

    原理分析

    基本架构:

    • Eureka:就是服务注册中心,用于管理所有注册服务。
    • 班级服务,服务的提供者,启动后向Eureka注册自己地址,方便服务调用方获取。
    • 学生服务,服务的调用者,定期从eureka注册中心中获得服务列表。
    • 心跳(续约):提供者定期通过localhost:${server.port}/eureka/ # 注册中心的地址
      register-with-eureka: false # 是否注册自己的信息到注册中心,默认是true
      fetch-registry: false # 是否拉取其它服务的信息,默认是true

      步骤四:编写启动类,添加注解 @EnableEurekaServer

      package com.czxy;

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

      /**
      * Created by liangtong.
      */
      @SpringBootApplication
      @EnableEurekaServer
      public class EurekaApplication {
      public static void main(String[] args) {
      SpringApplication.run(EurekaApplication.class, args );
      }
      }

      步骤五:访问

      ​​localhost:10086​​

      编写服务(客户端)

      编写班级服务 服务提供方(9010)

      步骤一:编写项目(服务),cloud-classes-service-1007

      步骤二:修改pom.xml文件,添加 eurekaclient依赖

      <dependencies>
      <!--web起步依赖-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- Eureka客户端 -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <!--spring boot监控(可选)-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      </dependencies>

      步骤三:创建yml文件,确定eureka注册中心的位置

      server:
      port: 9010
      spring:
      application:
      name: classes-service
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10086/eureka/
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true

      步骤四:编写启动类,添加注解

      package com.czxy;

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


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

      步骤五:启动项目,并测试

      步骤六:提供查询所有班级

      package com.czxy.controller;

      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

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


      @RestController
      @RequestMapping("/classes")
      public class ClassesController {

      @GetMapping
      public List<String> findAll(){
      List<String> list = new ArrayList<>();
      list.add("Java12班");
      list.add("Java34班");
      return list;
      }
      }

      编写学生服务 服务调用方(9020)

      步骤一:编写项目(服务),cloud-student-service-1007

      步骤二:修改pom.xml文件, (和eureka_service一样)

      <dependencies>
      <!--web起步依赖-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- Eureka客户端 -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <!--spring boot监控(可选)-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      </dependencies>

      步骤三:创建yml文件

      server:
      port: 9020
      spring:
      application:
      name: student-service
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10086/eureka/
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true

      步骤四:编写启动类

      package com.czxy;

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


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

      步骤五:启动,并测试

      功能:学生服务调用班级服务

      步骤一:编写HttpConfig,用于配置RestTemplate

      package com.czxy.config;

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



      @Configuration
      public class HttpConfig {

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

      步骤二:编写ClassesDao,完成远程调用

      package com.czxy.dao;

      import org.springframework.localhost:8082/classes", List.class);
      List<String> list = entity.getBody();
      return list;
      }

      }

      步骤三:编写StudentService,调用ClassesDao

      package com.czxy.service;

      import com.czxy.dao.ClassesDao;
      import org.springframework.stereotype.Service;

      import javax.annotation.Resource;
      import java.util.List;


      @Service
      public class StudentService {

      @Resource
      private ClassesDao classesDao;

      public List<String> findAll() {
      return classesDao.findAll();
      }
      }

      步骤四:编写StudentController,调用StudentService直接显示数据

      package com.czxy.controller;

      import com.czxy.service.StudentService;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import java.util.List;

      @RestController
      @RequestMapping("/student")
      public class StudentController {

      @Resource
      private StudentService studentService;

      @GetMapping
      public List<String> findAll(){
      return studentService.findAll();
      }
      }

      步骤五:测试

      优化:服务调用

      修改HttpConfig类,使RestTemplate支持通过服务名调用

      修改Dao,使用服务名调用

      @GetMapping
      public List<String> findAll(){
      ResponseEntity<List> entity = restTemplate.getForEntity("classes-service/classes", List.class);
      List<String> list = entity.getBody();
      return list;
      }

      Eureka高级

      优化集群:高可用的Eureka Server

      Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个Eureka Server,事实上Eureka Server也可以是一个集群,形成高可用的Eureka中心。

    • 服务同步
    • 多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

    • 动手搭建高可用的EurekaServer
    • 我们假设要搭建两条EurekaServer的集群,端口分别为:10086和10087

      步骤一:分别为两个端口配置yml文件

      application-10086.yml 配置

      # 端口号
      server:
      port: 10086
      # 服务名称
      spring:
      application:
      name: cloud-eureka-1007
      # eureka的配置
      eureka:
      client:
      service-url:
      defaultZone: localhost:10087/eureka/ # 注册中心的地址
      register-with-eureka: true # 是否注册自己的信息到注册中心,默认是true
      fetch-registry: true # 是否拉取其它服务的信息,默认是true

      application-10087.yml 配置

      # 端口号
      server:
      port: 10087
      # 服务名称
      spring:
      application:
      name: cloud-eureka-1007
      # eureka的配置
      eureka:
      client:
      service-url:
      defaultZone: localhost:10086/eureka/ # 注册中心的地址
      register-with-eureka: true # 是否注册自己的信息到注册中心,默认是true
      fetch-registry: true # 是否拉取其它服务的信息,默认是true

      所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

      • 删除了register-with-eureka=false和fetch-registry=false两个配置。因为默认值是true,这样就会吧自己注册到注册中心了。
      • 把service-url的值改成了另外一台EurekaServer的地址,而不是自己
    • 步骤二:配置启动器
    • -Dspring.profiles.active=10086

      步骤三:测试

      注意:10086端口启动后,将一直报错,再等待10087端口,10087启动后错误消失

      步骤四:将“classes-service”注册到10086,将自动同步到10087

      步骤七:手动将服务注册到Eureka集群

      因为EurekaServer不止一个,可以通过service-url设置多个注册地址

      eureka:
      client:
      service-url: #注册中心位置,多个地址以','隔开
      defaultZone: localhost:10086/eureka/,localhost:10087/eureka

      优化:服务提供者配置

      服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

    • 服务注册
    • 服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,完成注册操作。

    • 服务续约
    • 在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

      有两个重要参数可以修改服务续约的行为:

      eureka:
      client:
      lease-renewal-interval-in-seconds: 5 #服务续约(renew)的间隔,默认值90秒
      lease-expiration-duration-in-seconds: 10 #服务失效时间,默认为30秒
      • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
      • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒

      也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

      但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

      server:
      port: 8080
      spring:
      application:
      name: service
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10086/eureka/
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true
      lease-renewal-interval-in-seconds: 5 #5秒一次心跳
      lease-expiration-duration-in-seconds: 10 #10秒及过期

      优化:服务消费者配置

    • 获取服务列表
    • 当服务消费者启动是,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:

      eureka:
      client:
      registry-fetch-interval-seconds: 5server:
      port: 9090
      spring:
      application:
      name: client
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10087/eureka/
      registry-fetch-interval-seconds: 5 #从注册中心,获得列表的间隔时间
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true

      生产环境中,我们不需要修改这个值。

      但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

      注册中心优化:失效剔除和自我保护

      服务端配置

      eureka.server.enable-self-preservation ,是否开启自我保护模式,默认为true。

      eureka.server.eviction-interval-timer-in-ms, 清理无效节点的时间间隔,默认60000毫秒

      # eureka的配置
      eureka:
      server:
      enable-self-preservation: true #是否开启自我保护模式
      eviction-interval-timer-in-ms: 4000 # 清理无效节点的时间间隔

    • 失效剔除
    • 有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

      可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生成环境不要修改。

      这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如10S

      # eureka的配置
      eureka:
      server:
      eviction-interval-timer-in-ms: 4000 # 清理无效节点的时间间隔(缺省为60*1000ms)

      自我保护

      我们关停一个服务,就会在Eureka面板看到一条警告:

      这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

      但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:

      # eureka的配置
      eureka:
      server:
      enable-self-preservation: true #是否开启自我保护模式(缺省为true)

      优化总结

      负载均衡

      在刚才的案例中,我们启动了一个classes-service,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。

      但是实际环境中,我们往往会开启很多个eureka-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?

      一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。

      不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。

      什么是Ribbon:

      接下来,我们就来使用Ribbon实现负载均衡。

      启动两个服务实例

      首先我们启动两个classes-service实例,一个9010,一个9011。

      步骤一:为两个端口号配置yml文件

      application-9010.yml


      application-9011.yml

      步骤二:配置两个启动项


      测试

      开启负债均衡

      因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:

      在RestTemplate的方法上添加@LoadBalanced注解即可

      修改 ClassesController ,添加标识内容

      package com.czxy.controller;

      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import javax.servlet.localhost:10086/eureka/,localhost:10087/eureka
      registry-fetch-interval-seconds: 5 #从注册中心,获得列表的间隔时间
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true
      lease-renewal-interval-in-seconds: 5 #服务续约(renew)的间隔,默认值90秒
      lease-expiration-duration-in-seconds: 10 #服务失效时间,默认为30秒
      # 指定服务设置负载均衡策略
      #classes-service:
      # ribbon:
      # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
      # NFLoadBalancerRuleClassName : com.netflix.loadbalancer.BestAvailableRule #并发最少
      # NFLoadBalancerRuleClassName : com.netflix.loadbalancer.WeightedResponseTimeRule #请求时间权重

      classes-service:
      ribbon:
      ConnectTimeout: 250 # Ribbon的连接超时时间
      ReadTimeout: 1000 # Ribbon的数据读取超时时间
      OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
      MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
      MaxAutoRetries: 1 # 对当前实例的重试次数

      我们重启eureka_client,测试,发现即使user-service2宕机,也能通过另一台服务实例获取到结果!


    本文共计5367个文字,预计阅读时间需要22分钟。

    SpringCloud Eureka注册中心与Robbin负载均衡如何实现高效集群管理?

    学习目标+了解系统架构的演变+知道什么是SpringCloud+独立搭建Eureka注册中心+独立搭建Ribbon负载均衡+系统架构演变+要学微服务,我们先来看看系统架构的演变,从而对微服务架构有更深入的理解。


    学习目标

    • 了解系统架构的演变
    • 知道什么是SpringCloud
    • 独立搭建Eureka注册中心
    • 独立配置Robbin负载均衡

    系统架构演变

    要学微服务,我们先来看看系统架构的演变史,从而对微服务架构进行更深层次的了解。

    随着互联网的发展,网站应用的规模不断扩大,需求的剧增,带来了系统架构不断的演进、升级和迭代。

    系统架构的演变,大体上划分为:传统架构、负载均衡架构,分布式服务架构,SOA架构,微服务架构。

    传统架构:单一应用

    当网站流量很小时,只需一个应用,将所有功能都部署在一起。

    集群:负载均衡

    随着访问量的变大,一个Tomcat服务器已经不能满足需求。

    需要配置多个Tomcat,做Tomcat集群。通过负载均衡服务器访问不同的Tomcat服务器,每个Tomcat中部署完整项目即可。

    分布式服务

    每一个Tomcat存放完整项目,会存在部分业务模块长时间用不到,部分业务模块不够用的现象。

    我们将按照功能模块拆分项目,每一台服务器仅存放某一个模块,通过多系统的配合完成整体业务逻辑,此种方式成为:分布式服务。

    SpringCloud Eureka注册中心与Robbin负载均衡如何实现高效集群管理?

    SOA架构:面向服务架构

    SOA(Service Oriented Architecture)面向服务架构,就是将工程拆分成表现层和服务层两个工程。

    服务层中包含业务逻辑,只需要对外提供服务即可。

    表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。


    微服务

    微服务是SOA的一种实现,也可以说是微服务是去ESB(取中心)的SOA。

    微服务架构是一种将单个应用程序作为一套小型服务开发的方法。每种应用程序都可以独立运行。应用程序之间远程调用进行通信。

  • 微服务总结:(特性)
  • 完全独立的一个最小个体。(可以独立运行)
  • 个体与个体之间,通过远程调用进行访问。例如:基于RESTFul风格的。
  • 通过注册中心,将不同个体可以进行整合。
  • 通过网关,可以进行统一的入口访问。

    初始SpringCloud

    什么是微服务

    微服务是一种架构风格,即将单体应用划分为小型的服务单元。

    微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?

    • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
    • 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了
    • 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
    • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建

    什么是SpringCloud

    SpringCloud是一系列框架的集合,它利用SpringBoot的开发便利性,简化了分布式系统开发,如:服务注册、服务发现、配置中心。消息总线、负载均衡、熔断器、数据监控等。

    SpringCloud主要贡献者是Netflix,也就是SpringCloud是对Netflix贡献的框架的二次封装或优化。

    通俗的讲,SpringCloud就是用于构建微服务开发和治理的框架集合。

    SpringCloud常见模块

    SpringCloud主要涉及的组件包括:

    • Eureka:服务注册中心,用于管理服务
    • Ribbon:负载均衡(集群)
    • Hystrix:熔断器,能够防止服务的雪崩效应。
    • Zuul:服务网关,提供路由转发、请求过滤等功能。
    • Feign:服务调用,简化Http接口的调用。

    以上只是其中一部分,架构图:

    版本

    SpringCloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,它的命名是以A到Z的为首字母的一些单词组成:

    我们的学习,将以Hoxton.SR9的版本。(最新版本,需要同步升级相关软件)

    参考文档:​​Spring Cloud​​

    入门案例

    父项目

    创建项目

    创建父项目:cloud-parent-1007

    pom配置文件

    修改pom.xml,确定springcloud的版本

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="maven.apache.org/POM/4.0.0"
    xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="maven.apache.org/POM/4.0.0 maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.czxy.cloud</groupId>
    <artifactId>cloud-parent-1007</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 1 确定spring boot的版本-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    </parent>

    <!--2 确定版本-->
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <spring-cloud-release.version>Hoxton.SR9</spring-cloud-release.version>
    </properties>

    <!-- 3 锁定sprig cloud版本-->
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud-release.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <!-- 4 确定spring cloud私有仓库
    <repositories>
    <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>repo.spring.io/milestone</url>
    <snapshots>
    <enabled>false</enabled>
    </snapshots>
    </repository>
    </repositories>
    -->

    </project>

    原理分析

    基本架构:

    • Eureka:就是服务注册中心,用于管理所有注册服务。
    • 班级服务,服务的提供者,启动后向Eureka注册自己地址,方便服务调用方获取。
    • 学生服务,服务的调用者,定期从eureka注册中心中获得服务列表。
    • 心跳(续约):提供者定期通过localhost:${server.port}/eureka/ # 注册中心的地址
      register-with-eureka: false # 是否注册自己的信息到注册中心,默认是true
      fetch-registry: false # 是否拉取其它服务的信息,默认是true

      步骤四:编写启动类,添加注解 @EnableEurekaServer

      package com.czxy;

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

      /**
      * Created by liangtong.
      */
      @SpringBootApplication
      @EnableEurekaServer
      public class EurekaApplication {
      public static void main(String[] args) {
      SpringApplication.run(EurekaApplication.class, args );
      }
      }

      步骤五:访问

      ​​localhost:10086​​

      编写服务(客户端)

      编写班级服务 服务提供方(9010)

      步骤一:编写项目(服务),cloud-classes-service-1007

      步骤二:修改pom.xml文件,添加 eurekaclient依赖

      <dependencies>
      <!--web起步依赖-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- Eureka客户端 -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <!--spring boot监控(可选)-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      </dependencies>

      步骤三:创建yml文件,确定eureka注册中心的位置

      server:
      port: 9010
      spring:
      application:
      name: classes-service
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10086/eureka/
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true

      步骤四:编写启动类,添加注解

      package com.czxy;

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


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

      步骤五:启动项目,并测试

      步骤六:提供查询所有班级

      package com.czxy.controller;

      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

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


      @RestController
      @RequestMapping("/classes")
      public class ClassesController {

      @GetMapping
      public List<String> findAll(){
      List<String> list = new ArrayList<>();
      list.add("Java12班");
      list.add("Java34班");
      return list;
      }
      }

      编写学生服务 服务调用方(9020)

      步骤一:编写项目(服务),cloud-student-service-1007

      步骤二:修改pom.xml文件, (和eureka_service一样)

      <dependencies>
      <!--web起步依赖-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!-- Eureka客户端 -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <!--spring boot监控(可选)-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      </dependencies>

      步骤三:创建yml文件

      server:
      port: 9020
      spring:
      application:
      name: student-service
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10086/eureka/
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true

      步骤四:编写启动类

      package com.czxy;

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


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

      步骤五:启动,并测试

      功能:学生服务调用班级服务

      步骤一:编写HttpConfig,用于配置RestTemplate

      package com.czxy.config;

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



      @Configuration
      public class HttpConfig {

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

      步骤二:编写ClassesDao,完成远程调用

      package com.czxy.dao;

      import org.springframework.localhost:8082/classes", List.class);
      List<String> list = entity.getBody();
      return list;
      }

      }

      步骤三:编写StudentService,调用ClassesDao

      package com.czxy.service;

      import com.czxy.dao.ClassesDao;
      import org.springframework.stereotype.Service;

      import javax.annotation.Resource;
      import java.util.List;


      @Service
      public class StudentService {

      @Resource
      private ClassesDao classesDao;

      public List<String> findAll() {
      return classesDao.findAll();
      }
      }

      步骤四:编写StudentController,调用StudentService直接显示数据

      package com.czxy.controller;

      import com.czxy.service.StudentService;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import java.util.List;

      @RestController
      @RequestMapping("/student")
      public class StudentController {

      @Resource
      private StudentService studentService;

      @GetMapping
      public List<String> findAll(){
      return studentService.findAll();
      }
      }

      步骤五:测试

      优化:服务调用

      修改HttpConfig类,使RestTemplate支持通过服务名调用

      修改Dao,使用服务名调用

      @GetMapping
      public List<String> findAll(){
      ResponseEntity<List> entity = restTemplate.getForEntity("classes-service/classes", List.class);
      List<String> list = entity.getBody();
      return list;
      }

      Eureka高级

      优化集群:高可用的Eureka Server

      Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个Eureka Server,事实上Eureka Server也可以是一个集群,形成高可用的Eureka中心。

    • 服务同步
    • 多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

    • 动手搭建高可用的EurekaServer
    • 我们假设要搭建两条EurekaServer的集群,端口分别为:10086和10087

      步骤一:分别为两个端口配置yml文件

      application-10086.yml 配置

      # 端口号
      server:
      port: 10086
      # 服务名称
      spring:
      application:
      name: cloud-eureka-1007
      # eureka的配置
      eureka:
      client:
      service-url:
      defaultZone: localhost:10087/eureka/ # 注册中心的地址
      register-with-eureka: true # 是否注册自己的信息到注册中心,默认是true
      fetch-registry: true # 是否拉取其它服务的信息,默认是true

      application-10087.yml 配置

      # 端口号
      server:
      port: 10087
      # 服务名称
      spring:
      application:
      name: cloud-eureka-1007
      # eureka的配置
      eureka:
      client:
      service-url:
      defaultZone: localhost:10086/eureka/ # 注册中心的地址
      register-with-eureka: true # 是否注册自己的信息到注册中心,默认是true
      fetch-registry: true # 是否拉取其它服务的信息,默认是true

      所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

      • 删除了register-with-eureka=false和fetch-registry=false两个配置。因为默认值是true,这样就会吧自己注册到注册中心了。
      • 把service-url的值改成了另外一台EurekaServer的地址,而不是自己
    • 步骤二:配置启动器
    • -Dspring.profiles.active=10086

      步骤三:测试

      注意:10086端口启动后,将一直报错,再等待10087端口,10087启动后错误消失

      步骤四:将“classes-service”注册到10086,将自动同步到10087

      步骤七:手动将服务注册到Eureka集群

      因为EurekaServer不止一个,可以通过service-url设置多个注册地址

      eureka:
      client:
      service-url: #注册中心位置,多个地址以','隔开
      defaultZone: localhost:10086/eureka/,localhost:10087/eureka

      优化:服务提供者配置

      服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

    • 服务注册
    • 服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,完成注册操作。

    • 服务续约
    • 在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

      有两个重要参数可以修改服务续约的行为:

      eureka:
      client:
      lease-renewal-interval-in-seconds: 5 #服务续约(renew)的间隔,默认值90秒
      lease-expiration-duration-in-seconds: 10 #服务失效时间,默认为30秒
      • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
      • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒

      也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

      但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

      server:
      port: 8080
      spring:
      application:
      name: service
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10086/eureka/
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true
      lease-renewal-interval-in-seconds: 5 #5秒一次心跳
      lease-expiration-duration-in-seconds: 10 #10秒及过期

      优化:服务消费者配置

    • 获取服务列表
    • 当服务消费者启动是,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:

      eureka:
      client:
      registry-fetch-interval-seconds: 5server:
      port: 9090
      spring:
      application:
      name: client
      eureka:
      client:
      service-url: #注册中心位置
      defaultZone: localhost:10087/eureka/
      registry-fetch-interval-seconds: 5 #从注册中心,获得列表的间隔时间
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true

      生产环境中,我们不需要修改这个值。

      但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

      注册中心优化:失效剔除和自我保护

      服务端配置

      eureka.server.enable-self-preservation ,是否开启自我保护模式,默认为true。

      eureka.server.eviction-interval-timer-in-ms, 清理无效节点的时间间隔,默认60000毫秒

      # eureka的配置
      eureka:
      server:
      enable-self-preservation: true #是否开启自我保护模式
      eviction-interval-timer-in-ms: 4000 # 清理无效节点的时间间隔

    • 失效剔除
    • 有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

      可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生成环境不要修改。

      这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如10S

      # eureka的配置
      eureka:
      server:
      eviction-interval-timer-in-ms: 4000 # 清理无效节点的时间间隔(缺省为60*1000ms)

      自我保护

      我们关停一个服务,就会在Eureka面板看到一条警告:

      这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

      但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:

      # eureka的配置
      eureka:
      server:
      enable-self-preservation: true #是否开启自我保护模式(缺省为true)

      优化总结

      负载均衡

      在刚才的案例中,我们启动了一个classes-service,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。

      但是实际环境中,我们往往会开启很多个eureka-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?

      一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。

      不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。

      什么是Ribbon:

      接下来,我们就来使用Ribbon实现负载均衡。

      启动两个服务实例

      首先我们启动两个classes-service实例,一个9010,一个9011。

      步骤一:为两个端口号配置yml文件

      application-9010.yml


      application-9011.yml

      步骤二:配置两个启动项


      测试

      开启负债均衡

      因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:

      在RestTemplate的方法上添加@LoadBalanced注解即可

      修改 ClassesController ,添加标识内容

      package com.czxy.controller;

      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import javax.servlet.localhost:10086/eureka/,localhost:10087/eureka
      registry-fetch-interval-seconds: 5 #从注册中心,获得列表的间隔时间
      instance: #web页面显示效果和访问路径
      instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
      prefer-ip-address: true
      lease-renewal-interval-in-seconds: 5 #服务续约(renew)的间隔,默认值90秒
      lease-expiration-duration-in-seconds: 10 #服务失效时间,默认为30秒
      # 指定服务设置负载均衡策略
      #classes-service:
      # ribbon:
      # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
      # NFLoadBalancerRuleClassName : com.netflix.loadbalancer.BestAvailableRule #并发最少
      # NFLoadBalancerRuleClassName : com.netflix.loadbalancer.WeightedResponseTimeRule #请求时间权重

      classes-service:
      ribbon:
      ConnectTimeout: 250 # Ribbon的连接超时时间
      ReadTimeout: 1000 # Ribbon的数据读取超时时间
      OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
      MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
      MaxAutoRetries: 1 # 对当前实例的重试次数

      我们重启eureka_client,测试,发现即使user-service2宕机,也能通过另一台服务实例获取到结果!