首页 > 编程语言 > 如何用Eureka + Feign搭建分布式微服务
2021
09-10

如何用Eureka + Feign搭建分布式微服务

Eureka

Eureka主要解决了消费者对服务的记忆问题。如果没有Eureka,那么消费者必须记忆每个服务的地址,且一旦服务提供者宕机或地址发生变更,很可能不会收到通知,导致地址失效。加入Eureka后,只需记住Eureka注册中心的地址就能够找到其它所有服务。

此外,Eureka能够接受多个服务的注册,还能够通过其它组件的加持直接替代消费者进行负载均衡,使消费者无需手动选择服务。

Feign

Feign是一个模板化的HTTP客户端。通过Feign,可以做到像调用一个本地方法一样请求远程服务,无需编写繁杂的代码来创建HTTP请求。

创建父项目

项目主要分为三个微服务:服务提供者、服务消费者、Eureka注册中心。为了方便演示,三个项目都在同一个主机上运行,且都放在一个父项目里。

首先在IDEA中创建一个空项目,然后分别在项目中新建对应的三个Spring模块:

  • EurekaServer: 注册中心,依赖为Eureka Server。
  • ServiceProvider: 服务提供者,依赖为Eureka Discovery Client, Spring Web。
  • ServiceConsumer: 服务消费者,依赖为OpenFeign, Eureka Discovery Client, Spring Web。

注册中心

服务提供者通过向注册中心注册,提供它们的地址供调用;服务消费者向注册中心请求来获取可用的服务。

主要依赖

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

配置文件

# 端口
server.port=1000
# 指定应用名称
spring.application.name=server
# 是否拉取其它服务器的注册信息
eureka.client.fetch-registry=false
# 是否向其它服务器注册
eureka.client.register-with-eureka=false
# 指定服务url
eureka.client.service-url.defaultZone=http://localhost:1000/eureka

默认情况下,Eureka服务器假定自己是集群的一部分,会定期向其它Eureka服务器注册自己,并获取其它服务器的注册信息。由于本项目仅部署一个Eureka服务器提供注册服务,所以不需要这两个动作,通过配置文件的eureka.client.fetch-registry和eureka.client.register-with-eureka两个属性禁用。

eureka.client.service-url包含了每个zone的名称和地址。defaultZone是一个特殊的key,如果客户端没有指定所需的zone,就会使用这个默认的zone。一般情况下defaultZone的地址就是Eureka服务器本身。

主类

@SpringBootApplication
@EnableEurekaServer
public class MyEurekaServerApplication {

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

要启动Eureka注册服务,在原生的Spring Boot启动类上注解@EnableEurekaServer即可。

项目启动后,访问localhost:1000就能看到Eureka提供的界面了。如果在界面上看到警告

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

是正常现象。Eureka服务器默认开启了自我保护模式。由于没有收到集群中大部分服务器的心跳(本项目中就一个Eureka服务器,所以该服务器不会收到任何心跳,自我保护模式也没有多少影响),Eureka假定出现了网络问题,开启自我保护模式。在自我保护模式下,已经注册的服务不会因为没有收到心跳而被注销。

如果要关闭自我保护,可以在配置中设置如下属性。

eureka.server.enable-self-preservation=false

服务提供者

服务提供者将自己注册到注册中心。

主要依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

spring-cloud-starter-netflix-eureka-client依赖包含了Eureka客户端(即服务提供者)的实现;spring-boot-starter-web用于将服务暴露为HTTP端点。

配置文件

# 应用端口
server.port=2000
# 应用名称
spring.application.name=service
# 注册中心地址
eureka.client.service-url.defaultZone=http://localhost:1000/eureka

eureka.client.service-url.defaultZone属性告诉Eureka客户端从哪里找到注册中心。

主类及Controller

@SpringBootApplication
public class MyEurekaServiceApplication {

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

}

@RestController
@RequestMapping("/test")
@Slf4j
class MyController {
    @Value("${server.port}")
    private int serverPort;

    @GetMapping
    public String getHandler() {
        log.info("##############received call, port: " + this.serverPort);
        return "test msg";
    }
}

为了展示方便,这些类都写在同一个文件中,下同。

这里的Controller提供了一个简单的服务:只要访问/test路径,就返回一个字符串test msg。如果有需要,可以同时启动多个服务实例,模拟服务器集群提供服务的情况。

@Slf4j是lombok提供的辅助注解,用于在类中方便地声明一个Logger实例log。

@Value是Spring提供的注解,用于获取配置文件中的信息。本例中就获取了之前配置的属性server.port=2000的值2000,注入到域serverPort中。

启动服务提供者后,会自动向配置文件中指定的Eureka服务器进行注册。此时访问之前的Eureka服务器界面,能够看到已经注册的服务信息。这里我分别修改端口号启动了3个实例。

此时访问服务提供者所在端口的/test路径就能够收到服务提供的字符串。

客户端

Feign是一个模板化的HTTP客户端。通过Feign,可以做到像调用一个本地方法一样请求远程服务,无需编写繁杂的代码来创建HTTP请求。

客户端通过Feign的加持,可以方便地发出请求,也可以加入Hystrix的负载均衡、熔断降级等功能。

主要依赖

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

配置文件

# 应用端口
server.port=3000
# 应用名称
spring.application.name=feignClient
# 注册中心地址
eureka.client.service-url.defaultZone=http://localhost:1000/eureka

同上,eureka.client.service-url.defaultZone属性指定了客户端应该到哪个地址寻找注册中心。

主类、Controller及Feign映射

@FeignClient(value = "service")
interface FeignController {
    @GetMapping("/test")
    public String getHandler();
}

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class MyFeignClientApplication {

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

}

@RestController
@RequestMapping("/test")
class MyRestController {
    @Autowired
    FeignController feignController;

    @GetMapping
    public String getHandler() {
        return feignController.getHandler();
    }
}

类中的接口FeignController将发往该端口的HTTP请求映射为向服务的请求。本例中,@FeignClient(value = "service")指定了将调用映射为向service服务的请求。而用@GetMapping("/test")则代表每当调用该方法,就向/test路径请求。综上,每当调用该方法,该服务就会向名为service的服务的/test路径发送HTTP GET请求。这就是Feign的方便之处。

在启动类上需要注解@EnableFeignClients和@EnableDiscoveryClient,启动对Feign接口的扫描和对Eureka服务器的发现。

类中还写了一个MyRestController,用于将对该服务的请求映射到方法调用。调用链为:

  • 浏览器向Controller发送HTTP请求
  • Controller收到请求后调用Feign接口中的方法
  • Feign将对方法的调用映射为对注册中心中服务的请求并返回

这样一来,用户体验到的过程就是:通过Feign发送了一个请求,然后收到了远程服务器上的信息。

启动客户端,访问客户端所在端口的/test路径,正确收到了服务提供的字符串。

编程技巧