Eureka高可用集群实现Ribbon负载均衡

Eureka高可用集群实现Ribbon负载均衡

2020.01更新 :

eureka宣布闭源维护。
其实大部分SpringCloud组件都进入了维护状态,
参考: https://blog.csdn.net/u012437781/article/details/85258505 

其实停止更新不代表停止使用,还是要根据实际情况和长远考虑选择合适的服务组件,比如SpringCloud Alibaba 。


以下为原文:

微服务: 核心就是将传统的一站式应用,根据业务功能拆分成一个个的服务,彻底的去耦合。 每一个微服务提供单个业务功能和服务,一个服务做一件事,它关注的的某一个点,是具体解决某个问题/提供落地对应服务的一个应用。 从技术角度看就是一个小而独立的处理过程,类似进程的概念。能够自行单独启动或销毁,也可以拥有自己独立的数据库。

在折腾SpringCloud期间只了解到了单个服务注册中心的创建,不过之后又了解到单模式的部署方式在生产环境下中确实不提倡 :

因为有很多种原因可能导致服务注册中心宕机。
如果宕机就会有一些灾难性的问题出现 比如被杀掉祭天,所以保证服务注册中心处于持续运行状态显得尤为重要!!!

此时就需要搭建高可用集群Eureka服务注册中心

环境:
JDK - 1.8
IDEA - 2018.3.1
SpringBoot - 1.5.9
SpringCloud - Dalston.SR1
MYSQL - 5.5

一、项目结构

具体构建细节不赘述了,不在本文章讨论范围内。目录结构如下:
在microServiceCloud这个maven工程中,有8个由SpringBoot构成的独立服务。

API1.1:整个项目中用到的公共类如部门类,抽取成API以在其他子Maven中直接引入。
7001、7002、7003:实现集群的Eureka注册中心服务端口。
8001、8002、8003:实现负载均衡的生产者服务端口。
9002:消费者服务端口。

数据库方面,使用了三个独立库,分别是db1、db2、db3。专门给 8001、8002、8003 这三个端口服务。

贴一下父POM,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.lzm.springcloud</groupId>
   <artifactId>microservicecloud1</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>pom</packaging>

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

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Dalston.SR1</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>1.5.9.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
         <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.0.4</version>
         </dependency>
         <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
         </dependency>
         <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
         </dependency>

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

         <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
         </dependency>
         <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
         </dependency>
         <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
         </dependency>
      </dependencies>
   </dependencyManagement>

    <build>
        <finalName>microservicecloud1</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <delimiters>
                        <delimit>$</delimit>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <!-- 这是子模块 -->
   <modules>
      <module>microservicecloud1-api1.1</module>
        <module>microservicecloud-eureka-7001</module>
        <module>microservicecloud-eureka-7002</module>
        <module>microservicecloud-eureka-7003</module>
        <module>microservicecloud-provider-8001</module>
        <module>microservicecloud-consumer-9002</module>
        <module>microservicecloud-provider-8002</module>
        <module>microservicecloud-provider-8003</module>
    </modules>
</project>

二、最终实现效果

在注册中心可以观察到三个 Eureka的集群。
端口为7001-7003。


对相同请求实现负载均衡,还可以访问不同的数据库。
注意Spriongclouddb结尾的数字,代表的是1-3号库。
9002端口为消费者,通过RestTemplate,来访问位于8001-8003生产者端口的Rest服务。

三、实现细节

1、 对生产者provider负载均衡

重点1:Pom文件导入依赖

<?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>microservicecloud1</artifactId>
        <groupId>com.lzm.springcloud</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microservicecloud-provider-8001</artifactId>

    <name>microservicecloud-provider-8001</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 引入自己定义的api通用包,可以使用公用Entity -->
        <dependency>
            <groupId>com.lzm.springcloud</groupId>
            <artifactId>microservicecloud1-api1.1</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- actuator监控信息完善 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 将生产微服务provider注册进eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <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>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!-- 修改后立即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

    </dependencies>

    <build>
        <finalName>microservicecloud-provider-8001</finalName>
    </build>

</project>

持久层省略不贴了,controller层很简单:

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

    @Resource(name="deptservice")
    private DeptServiceImpl service;

    @RequestMapping(value = "/add" , method = RequestMethod.POST)
    public boolean add (Dept dept){
        return service.add(dept);
    }

    @RequestMapping( value = "/get/{id}", method = RequestMethod.GET)
    public Dept get( @PathVariable Long id){
        return service.get(id);
    }

    @RequestMapping(value="/list", method=RequestMethod.GET)
    public List<Dept> list() {
        return service.list();
    }
}

主启动类记得加Eureka客户端的注解:

@SpringBootApplication
@EnableEurekaClient /* 本服务启动后会自动注册进eureka服务中, 注意与@EnableEurekaServer区分 */
public class DeptProvider8001_App {

    public static void main(String[] args) {

        SpringApplication.run(DeptProvider8001_App.class, args);

    }

}

重点2: application.yml配置文件

server:
  port: 8001


mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml       # mybatis配置文件所在路径
  type-aliases-package: com.lzm.springcloud.entity         # 所有Entity别名类所在包
  mapper-locations: classpath:mybatis/mapper/*.xml         # mapper映射文件

spring:
  application:   
    name: cloud-dept
  # 数据源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
    url: jdbc:mysql://localhost:3306/springclouddb1         # 数据库名称
    username:                                           # 用户名
    password:                                          # 密码
    dbcp2:
      min-idle: 5                                           # 数据库连接池的最小维持连接数
      initial-size: 5                                       # 初始化连接数
      max-total: 5                                          # 最大连接数
      max-wait-millis: 200                                  # 等待连接获取的最大超时时间
      #test-while-idle: true
      #test-on-borrow: false
      #test-on-return: false

  # 注意 多个Boot热部署工具同时运行会报错端口冲突, 需要在不同应用中设置不同端口. 默认端口为 35729
  devtools:
    livereload:
      port: 35732

debug: true

# 把这个应用客户端注册进eureka服务列表内
eureka:
  instance:
    instance-id: dept-8001       # 自定义此服务的 名称信息
    prefer-ip-address: true      # 鼠标移到访问路径后,在左下角可以显示此应用IP地址和端口
  client:
    service-url:
       # 单机:
      #defaultZone: http://localhost:7001/eureka
      # 集群:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

# 在eureka界面中点微服务后的info内容完整信息
info:
  app.name: microservicecloud
  company.name: lzyz.fun
  build.artifactId: $project.artifactId$
  build.version: $project.version$

8002和8003端口的服务和8001的java代码相同。直接复制即可。


注意不同的是yml中几处配置:

# 如果是8002,端口需要改
server:
  port: 8001

#自定义服务名称信息:
instance-id: cloud-dept8002 
  
#修改数据库ULR:
url: jdbc:mysql://localhost:3306/cloudDB2

# 多个Boot热部署工具同时运行会报错端口冲突, 需要在不同应用中设置不同端口. 默认端口为 35729
  devtools:
    livereload:
      port: 35733

 

还要注意这三个服务的name值:spring.application.name: cloud-dept 
一定要一致。

2、 对Eureka进行集群配置

关键1:Pom文件

<?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>microservicecloud1</artifactId>
        <groupId>com.lzm.springcloud</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microservicecloud-eureka-7001</artifactId>

    <name>microservicecloud-eureka-7001</name>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- eureka-server服务端
             你需要让这个boot具备服务注册与发现的功能,则需要导入相关的依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

        <!-- 修改后立即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

    </dependencies>

</project>

启动类:

/**
 * 注册与发现启动类
 */
@SpringBootApplication
@EnableEurekaServer  /* EurekaServer服务器端启动类, 接受其它微服务注册进来 */
public class EurekaServer7001_App
{
    public static void main( String[] args ) {
        SpringApplication.run(EurekaServer7001_App.class, args);
    }
}

关键2:yml配置文件。

注意我提前在本机的hosts文件做了映射:
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com

server:
  port: 7001

eureka:
  instance:
    #hostname: localhost         # eureka服务器的实例名称( 单机环境下 )
    hostname: eureka7001.com     # eureka服务器的实例名称( 集群环境下 )
  client:
    register-with-eureka: false  # false表示不向注册中心注册自己。
    fetch-registry: false        # false表示自己就是注册中心。我负责维护服务示例,并不需要去检索服务
    service-url:                 # service-url的配置内容是map,map的内容是可以自己发挥的,只要是key: value格式就行(注意有个空格)。
      #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka   #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  # ( 集群情况 )

7002、7003的服务与7001的yml文件不同处在于:

修改端口
server:
  port: 7002

修改实例名称
hostname: eureka7002.com     # eureka服务器的实例名称( 集群环境下 )

关键点!  这里如果是7002. 则应该写7001和7003,手拉手做集群!7003同理。
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/  # ( 集群情况 )

3、 实现消费者

消费者是客户端。

而Ribbon负载均衡, 是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器

所以需要对消费者(客户端)进行负载均衡的配置:

下面的代码都比较关键

POM依赖:


<dependencies>
        <!-- 引入自己定义的api通用包,可以使用Dept部门Entity -->
        <dependency>
            <groupId>com.lzm.springcloud</groupId>
            <artifactId>microservicecloud1-api1.1</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Ribbon相关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <!-- 修改后立即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置类:

@Configuration
public class ConfigBean {

    @Bean
    @LoadBalanced /* 客户端 - 获取Ribbon负载均衡支持 */
    public RestTemplate getTemplate(){
        return new RestTemplate();
    }

}

controller层:

@RestController
public class ConsumerController {

    //private final String REST_URL = "http://localhost:8001";  
    //这是错误写法。既然实现了负载均衡。就应该让其自主选择哪个服务节点。不需要我们手动来定义!

    private static final String REST_URL = "http://cloud-dept"; //所以直接写服务名称。 之前在生产者yml配置文件中定义过,还强调了名字必须相同。

    @Resource
    private RestTemplate template;

    @RequestMapping("/consumer/dept/list")
    public List list(){
        return template.getForObject(REST_URL+"/dept/list" , List.class);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get( @PathVariable Long id){
        return template.getForObject( REST_URL+"/dept/get/"+id , Dept.class );
    }

    @RequestMapping("/consumer/dept/add")
    public boolean add( Dept dept ){
        return template.postForObject( REST_URL+"/dept/add", dept , Boolean.class );
    }


}

yml 配置:

#消费者端口
server:
  port: 9002

# 注册eureka的地址。注意此时eureka已经集群。
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

spring:
  devtools:
    livereload:
      port: 35733

四、稍微总结下

注意 eureka 的自我保护,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话理解:好死不如赖活着 。

注意Boot热部署工具的端口冲突,默认是35729。 每增加一个服务最好将端口号+1。

同样 ,最好不要用8080或80这些容易出问题的端口。

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注