Spring Cloud 入门到进阶 - 01 Eureka介绍及服务搭建(上)


博主整理的SpringCloud系列:目录


一、Eureka介绍

Spring Cloud 集成了 Netflix OSS 多个项目,形成了 spring-cloud-netflix 项目。该项目包含多个子模块,这些子模块对集成的 Netflix 旗下的框架进行了封装,我们这里将讲述其中一个较为重要的服务管理框架: Eureka

1.1 关于 Eureka

Eureka 提供基于 REST 的服务,在集群中主要用于服务管理。Eureka 提供了基于 Java 语言 的客户端组件,客户端组件实现了负载均衡的功能,为业务组件的集群部署创造了条件。使用该框架,可以将业务组件注册到 Eureka 容器中,这些组件可进行集群部署,Eureka 主要维护这些服务的列表并自动检查它们的状态

1.2 Eureka 架构

一个简单的 Eureka 集群,包含一个 Eureka 服务器(注册中心)、若干个服务提供者、若干个服务调用者。如下图所示:
Eureka简单集群环境
有两个Eureka服务器注册中心,服务器支持集器部署,每个服务器也可以作为对方服务器的客户端进行互相注册与复制。有三个Eureka客户端,两个用于发布,另一个用与服务调用。不管是服务器还是客户端,都可以部署多个实例,如此一来,很容易构建高可用的服务集群。

1.3 Eureka 服务端

对于注册到服务器端的服务组件,Eureka 服务器并没有提供后台的存储,这些注册的服务实例被保存在内存的注册中心,它们通过心跳来保持其最新状态,这些操作都可以在内存中完成。客户端存在着相同的机制,同样在内存中保存了注册表信息,这样的机制提升了 Eureka 组件的性能,每次服务的请求都不必经过服务器端的注册中心。

1.4 Eureka 服务提供者

作为 Eureka 客户端存在的服务提供者,主要进行以下工作: 第一,向服务器注册服务;第二,发送心跳给服务器;第三 向服务器端获取注册列表。当客户端注册到服务器时,它将会提供一些关于自己的信息给服务器端,例如自己的主机、端口、健康检测连接等。

1.5 Eureka 服务调用者

对于发布到 Eureka 服务器的服务,服务调用者可对其进行服务查找与调用,服务调用者也是作为客户端存在的,但其职责主要是发现与调用服务。在实际情况中,有可能出现本身既是服务提供者,又是服务调用者的情况,例如在传统的企业应用三层架构中,服务层会调用数据访问层的接口进行数据操作,它本身也会提供服务给控制层使用。

二、第一个 Eureka 应用

这里我们将编写一个 Hello World 小程序。其中包括一个 Eureka 服务中心、两个服务提供者、一个服务调用者。

1、Eureka 服务器搭建

先创建一个名称为 first-ek-server 的 Maven 项目作为服务器,在 pom.xml 文件中加入 Spring Cloud 的依赖。

1.1、依赖 pom.xml
<?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.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.swotxu</groupId>
    <artifactId>first-ek-server</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>first-ek-server</name>
    <description>This is the service</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <!-- 此依赖为了方便使用,非必须的 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

因为spring-cloud-starter-eureka-server会自动引入spring-boot-starter-web,因此只需要加入该依赖,我们的项目就具有 Web 容器的功能了。
在这里插入图片描述
接下来,编写一个最简单的启动类,启动我们的 Eureka 服务器。

1.2、服务启动类
package com.swotxu.firstekserver;

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

/**
 * 第一个 Eureka 服务
 *
 * @Date: 2020/6/25 20:32
 * @Author: swotXu
 */
@SpringBootApplication
@EnableEurekaServer // 声明这是一个 Eureka 服务器
public class FirstEkServer {

    public static void main(String[] args) {
        /*
            以下两种写法都可以,
            1、SpringApplication.run(FirstEkServer.class, args)
            内部会创建当前对象实例,并调用其run方法,源码如下:
                (new SpringApplication(sources)).run(args);

            2、new SpringApplicationBuilder(FirstEkServer.class).run(args);
            在创建SpringApplicationBuilder对象的时候,我们查看源码会发现,它内部
            其实依旧是创建了SpringApplication对象,源码如下:
                new SpringApplication(sources);
            当调用其run方法时,最终还是委托给了SpringApplication。

            由此可见,虽然写法不同,但实际上殊途同归。
            同时,SpringApplicationBuilder提供给了我们一定的扩展能力,这个咱们后面再说
         */
        //SpringApplication.run(FirstEkServer.class, args);
        new SpringApplicationBuilder(FirstEkServer.class).run(args);
    }
}

由于本例中并没有配置服务器端口,因此默认端口为 8080 ,我们将端口配置为 8761,在 src/main/resources 目录下创建 application.yml 配置文件,内容如下:

server:
  port: 8761

在这里插入图片描述
可以看到已经启动成功,端口为8761。启动过程中会出现部分异常信息,暂时不需要进行处理,下面再说。成功启动后,打开浏览器,输入 http://localho st:8761 ,可以看到 Eureka 服务器控制台,如下图:
在这里插入图片描述
可以看到,服务实例列表为空。

  • 服务器注册开关
    上面提到过,在服务启动时,会出现如下两个异常信息:
    java.net.ConnectException: Connection refused: connect
    com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
    
    这是由于在服务器启动时,服务器会把自己当作一个客户端,去注册 Eureka 服务器,并且会到 Eureka 服务器抓取注册信息,它自己本身只是一个服务器,而不是服务的提供者(客户端),因此可以修改 application. yml 文件,添加以下两个配置,即可避免异常信息。
1.3、配置 application.yml
server:
  port: 8761
  
eureka:
  client:
    register-with-eureka: false # 是否将自己的信息注册到 Eureka 服务器
    fetch-registry: false  # 是否到 Eureka 服务器抓取注册信息
1.4、项目完整结构图 - first-ek-server



2、搭建服务提供者

这里我们使用 SpringBoot 创建一个名称为 first-ek-server-provider 的 Web 工程,并在里面编写一个 REST 服务。其中 pom.xml 依赖如下所示。

2.1、依赖 pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.swotxu</groupId>
    <artifactId>first-ek-server-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>first-ek-server-provider</name>
    <description>This is the service provider</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 由于 eureka-client 并没有为我们引入 web 组件,因此需要我们手动引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 此依赖为了方便使用,非必须的 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
2.2、配置 application.yml

src/main/resources 目录下创建 application.yml 配置文件,内容如下:

spring:
  application:
    name: first-ek-server-provider
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

以上配置中,将应用名称配置为 first-ek-server-provider,该服务将会被注册到端口为 8761 的 Eureka 服务器,也就是前面搭建的服务器。另外,还使用了 eureka.instance.hostname 来配置该服务实例的主机名。下面,我们编写一个 Controller 类,并提供一个简单的 REST 服务。

2.3、服务提供类
package com.swotxu.firstekserverprovider.web;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Date: 2020/6/25 20:50
 * @Author: swotXu
 */
@RestController
public class FirstController {

    @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public UserInfo findUser(@PathVariable("userId") Integer userId){
        UserInfo userInfo = new UserInfo(userId, "swotxu", 18);
        return userInfo;
    }
}

// ------------------------------------------------

package com.swotxu.firstekserverprovider.web;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Date: 2020/6/25 20:51
 * @Author: swotXu
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private Integer id;
    
    private String username;

    private Integer age;
}
2.4、服务启动类

编写启动类,在启动类中 ,使用了@EnableEurekaClient 注解,声明该应用是一个 Eureka 客户端,代码如下:

package com.swotxu.firstekserverprovider;

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

@EnableEurekaClient
@SpringBootApplication
public class FirstEkServerProviderApplication {

    public static void main(String[] args) {
//        SpringApplication.run(FirstEkServerProviderApplication.class, args);
        new SpringApplicationBuilder(FirstEkServerProviderApplication.class).run(args);
    }
}

完成后,先运行 first-ek-server 服务器,再运行 first-ek-server-provider 服务提供者,在浏览器中访问 Eureka 服务器控制台:http://localho st:8761 ,可以看到我们的服务提供者已经注册到服务器了。
在这里插入图片描述

2.5、项目完整结构图 - first-ek-server-provider

在这里插入图片描述
接下来,我们编写服务调用者。

3、搭建服务调用者

服务被注册、发布到 Eureka 服务器后,需要有程序去发现它,并且进行调用。此处所说的调用者,是指同样注册到 Eureka 的客户端,来调用其他客户端发布的服务。

3.1、依赖 pom.xml

创建一个名称为 first-ek-server-invoker 的项目,在 pom.xml 文件中加入依赖如下所示。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.swotxu</groupId>
    <artifactId>first-ek-server-invoker</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>first-ek-server-invoker</name>
    <description>This is the service caller</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <dependencies>
    	<!-- 由于 eureka-client 并没有为我们引入 web 组件,因此需要我们手动引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- 此依赖为了方便使用,非必须的 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

因为spring-cloud-starter-eureka-client会自动引入spring-cloud-starter-netflix-ribbon,因此只需要加入该依赖,我们的项目就具有 Ribbon 相关功能了。
在这里插入图片描述

3.2、配置 application.yml

src/main/resources 目录下创建 application.yml 配置文件,内容如下:

server:
  port: 9000
spring:
  application:
    name: first-ek-server-invoker
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

在配置文件中,将应用名称配置为 first-ek-server-invoker,这个调用者的访问端口为 9000,需要注意的是,这个调用者本身也可以对外提供服务。与提供者一样,使用 eureka 的配置,将该调用者注册到 first-ek-server 上面。下面,我们编写一个控制器,让调用者对外提供一个测试的服务。

3.3、服务调用类
package com.swotxu.firstekserverinvoker;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Date: 2020/6/27 17:09
 * @Author: swotXu
 */
@Slf4j
@Configuration
@RestController
public class lnvokerController {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

	@RequestMapping(value = "/router", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public String router(){
        RestTemplate restTemplate = getRestTemplate();
        // 根据应用名称调用服务。其中 first-ek-server-provider 是服务提供者配置的服务名(spring.application.name)
        String json = restTemplate.getForObject("http://first-ek-server-provider/user/1", String.class);
        log.info("result: {}", json);
        return json;
    }
}

在控制器中,配置了 RestTemplate 的 Bean,RestTemplate 本来是 spring-web 模块下面的类,主要用来调用 REST 服务。本身并不具备调用分布式服务的能力,但是 RestTemplate 的 Bean 被 @LoadBalanced 注解修饰后,这个 RestTemplate 实例就具有访问分布式服务的能力了。关于该类的机制,我们后面再讲。

在控制器中,新建了一个 router 的测试方法,用来对外发布 REST 服务。该方法只起路由作用,实际上是使用 RestTemplate 来调用 first-ek-server-provider(服务提供者)的服务。

3.4、服务启动类

接下来编写启动类,代码如下。

package com.swotxu.firstekserverinvoker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class FirstEkServerInvokerApplication {

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

在启动类中,使用了 @EnableDiscoveryClient 注解来修改启动类,该注解使得服务调用者有能力去 Eureka 中发现服务。需要注意的是,@EnableEurekaClient 注解己经包含了 @EnableDiscoveryClient 的功能,也就是说,一个 Eureka 客户端,本身就具有发现服务的能力。

  • @EnableEurekaClient@EnableDiscoveryClient 区别
    共同点:两者都够让注册中心能够发现,扫描到该服务。
    不同点:@EnableEurekaClient 只适用于Eureka作为注册中心,@EnableDiscoveryClient 可以是其他注册中心。
3.5、项目完整结构图 - first-ek-server-invoker

在这里插入图片描述

4、运行测试

上面三个项目编写完成后,我们依次启动:

  1. 启动服务器(first-ek-server)
  2. 启动服务提供者(first-ek-server-provider)
  3. 启动服务调用者(first-ek-server-invoker)

全部启动完成后,在浏览器访问 http://localhost:8761/,即可看到注册的客户信息:
在这里插入图片描述

接下来,我们在浏览器中访问服务调用者发布的 router 服务:http://localhost:9000/router,可以看到在浏览器中输出如下信息:
在这里插入图片描述
根据输出可知,实际上调用了服务提供者的 /user/1 服务,第一个 Eureka 应用到此结束,下面对这个应用程序的结构进行简单描述。

5、程序结构

在这里插入图片描述
从上图可以看到,Eureka 服务为本例的 first-ek-server,服务提供者为 first-ek-service-provider,而调用者为 first-ek-service-invoker ,用户通过浏览器访问调用者的 9000 端口的 router 服务、 router 服务中查找服务提供者的服务并进行调用 。在本例中,服务调用有点像路由器的角色。

6、源码下载

项目完整结构图:
在这里插入图片描述
码云Gitee仓库地址:https://gitee.com/swotxu/Spring-Cloud-Study.git
项目路径:Spring-Cloud-Study/01/springcloud01


为了能演示 Eureka 高可用特性,下篇,我们将会以本案例为基础,搭建一个复杂一点集群。

Spring Cloud 入门到进阶 - 02 Eureka集群搭建(下)

别忘了点赞关注收藏~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章