当前位置:首页 > 技术分析 > 正文内容

微服务 Spring Cloud 实战 Eureka+Gateway+Feign+Hystrix

ruisui883个月前 (02-03)技术分析26

前言

我所在项目组刚接到一个微服务改造需求,技术选型为 Spring Cloud,具体需求是把部分项目使用 Spring Cloud 技术进行重构。本篇文章 中介绍了 Eureka、Gateway、Feign 和 Hystrix 这些组件的用途,我整合这几个组件写了一个 demo,涉及四个工程分别是:注册中心 Eureka、网关 Gateway、服务 1、服务 2,涉及的功能分别有:使用 Zuul 配置动态路由、使用 ZuulFilter 过滤器实现 IP 白名单、使用 Feign 实现负载均衡的服务调用、使用 Hystrix 实现服务隔离、熔断、降级。

概念介绍

Eureka

Eureka 是 Spring Cloud 集合的重要组件之一,作为服务注册及发现的中心,即全部服务都会注册到 Eureka,其他工程需要使用服务时也是从 Eureka 得到(即服务发现)。Eureka 区分 server 和 client 两个性质,一般来说除 Eureka 工程外都是 client,比如:Gateway 工程是作为 Eureka 的 client。

Eureka 是可以集群部署,防止单个 Eureka 场景下宕机后导致 client 获取不了最新的服务列表(每个 client 都会缓存一份服务列表到本地),也可以防止 Eureka 宕机后 client 工程重启后获取不了服务列表,而导致无法使用服务。生产环境一般都是部署两个或者三个 Eureka 节点。

Gateway

Gateway 也是 Spring Cloud 集合的组件之一,主要是做动态路由(类似 Nginx)和请求过滤。动态路由是根据自定义的配置,把请求转发到指定的工程,类似于 Nginx;请求过滤基于 Filter 链的方式实现鉴权(比如:调用接口时校验是否携带 token)、限流(比如:固定哪些 IP 才可以调用接口)、监控(记录所有请求记录加以分析)等功能。

Feign

Feign 也是 Spring Cloud 集合的组件之一,是用来实现负载均衡的服务调用。提起 Feign 往往会想起 Ribbon 这个组件,Ribbon 也是实现负载均衡的服务调用,Ribbon 需要结合 RestTemplate 模板使用,每个服务调用的类都要注入 RestTemplate,用起来比较麻烦,Feign 是在 Ribbon 的基础上再进行封装,只需创建一个接口并使用注解方式配置对应的哪个实例及路径即可。

Hystrix

Hystrix 也是 Spring Cloud 集合的组件之一,是用来实现服务隔离、熔断、降级。隔离是把每个请求在不同的线程上执行,从而实现服务调用过程中出现问题也不影响其他服务的调用;熔断是在服务调用过程中,如果失败次数比例超过阈值(默认 50%),此时熔断器会切换到 open 状态(即所有请求都直接失败),熔断器在 open 状态保持一段时间后(默认 5 秒),会自动切换到 half-open 状态,此时如果有请求过来,则会根据请求结果来切换熔断器的状态,如果请求成功则把状态切换到 close,如果失败则切换到 open;降级是在请求过程中出现超时或者异常的情况下,直接中断请求避免拖垮整个微服务,返回自定义的信息(比如:服务繁忙),从而达到服务降级的效果。

Eureka 工程代码分析

代码结构

pom.xml 文件配置

下面配置是工程需要使用的所有 jar 和 maven 打包策略。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        1.5.3.RELEASE
         
    
    com.example
    demo-Eureka
    0.0.1-SNAPSHOT
    demo-Eureka
    Demo project for Spring Boot


    
        1.8
        Dalston.SR1
    

    
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.cloud
            spring-cloud-starter-Eureka-server
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    true
                
            
        
    

启动类的配置

启动类代码非常简单,加上 @SpringBootApplication 和 @EnableEurekaServer 即可,@SpringBootApplication 是开启自动配置;@EnableEurekaServer 是开启 EurekaServer 服务,即此工程作为 Eureka 的服务端,即服务注册及发现的中心。

@SpringBootApplication
@EnableEurekaServer     //开启 EurekaServer 服务
public class DemoEurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoEurekaApplication.class, args);
    }
}

application.yml 配置

application.yml 文件中配置了注册中心的实例名为 registry-server,端口为 8888,主机名为 127.0.0.1(也可以是域名),不优先使用 ip,唯一 id 是由 ip 地址 + 端口(比如:127.0.0.1:8888/)等等,具体配置说明请看下面的代码。

spring:
  application:
    name: registry-server

server:
  port: 8888

Eureka:
  instance:
    hostname: 127.0.0.1
    prefer-ip-address: false   #是否优先使用 ip 地址(与 hostname 相比)
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}
  client:
    register-with-Eureka: false   #实例是否在 Eureka 服务器上注册自己的信息以供其他服务发现,默认为 true
    fetch-registry: false         #此客户端是否获取 Eureka 服务器注册表上的注册信息,默认为 true
    service-url:
      defaultZone: http://${Eureka.instance.hostname}:${server.port}/Eureka/    #与 Eureka 注册服务中心的通信地址
  server:
    enable-self-preservation: false          #服务端开启自我保护模式。无论什么情况,服务端都会保持一定数量的服务。避免 client 与 server 的网络问题,而出现大量的服务被清除。
    response-cache-update-interval-ms: 1000  #多长时间更新一次缓存中的服务注册数据(单位:毫秒)
    eviction-interval-timer-in-ms: 3000      #开启清除无效服务的定时任务并设置时间间隔(单位:毫秒),默认 1 分钟

Gateway 工程代码分析

代码结构

pom.xml 文件配置

下面配置是工程需要使用的所有 jar 和 maven 打包策略。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    
    com.example
    demo-Gateway
    0.0.1-SNAPSHOT
    demo-Gateway
    Demo project for Spring Boot

    
        UTF-8
        UTF-8
        1.8
        Greenwich.SR2
    

    
        
            org.springframework.cloud
            spring-cloud-starter-netflix-Eureka-client
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-zuul
        
        
            io.springfox
            springfox-swagger2
            2.6.1
        
        
            io.springfox
            springfox-swagger-ui
            2.6.1
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    true
                
            
        
    

启动类的配置

启动类代码非常简单,加上 @SpringBootApplication 和 @EnableEurekaServer 即可,@SpringBootApplication 是开启自动配置;@EnableEurekaServer 是开启网关服务。

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

application.yml 配置

application.yml 文件中配置了注册中心的实例名为 Gateway,端口为 9999,Eureka 地址(比如:127.0.0.1:8888/Eureka/),路由规则等等,具体配置说明请看下面的代码。

spring:
  application:
    name: Gateway

server:
  port: 9999

Eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/Eureka/
  instance:
    hostname: com.example.Gateway
    prefer-ip-address: true        #是否优先使用 ip 地址
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    non-secure-port-enabled: true  #是否启用 http 通信端口
    secure-port-enabled: false     #是否启用 https 通信端口

zuul:
  routes:
    local:          #本地工程路由配置
      path: /**
      stripPrefix: false
      serviceId: forward:/
    user1:         #service-user1 工程路由配置
      path: /user1/**
      stripPrefix: false
      serviceId: service-user1
    user2:         #service-user2 工程路由配置
      path: /user2/**
      stripPrefix: false
      serviceId: service-user2

IP 白名单代码分析

下面代码是使用 ZuulFilter 过滤器机制实现 IP 限流功能,在把请求转发到具体实例之前,判断请求的 ip 是否在白名单内,如果是则允许访问,否则禁止访问,以达到 IP 限流效果。

package com.example.demoGateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * IP 白名单,即 IP 限流
 */
@Component
public class IpWhitelistFilter extends ZuulFilter {

    private Logger logger = LogManager.getLogger(getClass());
    private static List ipWhiteList = new ArrayList<>();

    /**
     * 为了方便演示,此处固定写死白名单内容,正常来说是读外部数据库,比如 mysql、redis 等
     */
    static {
        ipWhiteList.add("127.0.0.1");
        ipWhiteList.add("localhost");
    }

    /**
     * pre:在请求被路由(转发)之前调用
     * route:在路由(请求)转发时被调用
     * error:服务网关发生异常时被调用
     * post:在路由(转发)请求后调用
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String remoteHost = request.getRemoteHost();    //获取请求 IP
        logger.log(Level.INFO, request.getMethod() + " request ip : {}", remoteHost);
        if (!(ipWhiteList.contains(remoteHost))) {
            logger.log(Level.WARN, request.getMethod() + " request from " + remoteHost + " is unauthorized.");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            return null;
        }
        logger.log(Level.INFO, "access ip ok");
        return null;
    }
}

service-user1 服务工程(Feign + Hystrix)代码分析

代码结构

pom.xml 文件配置

下面配置是工程需要使用的所有 jar 和 maven 打包策略。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    
    com.example
    demo-service-user1
    0.0.1-SNAPSHOT
    demo-service-user1
    Demo project for Spring Boot

    
        UTF-8
        UTF-8
        1.8
        Greenwich.SR2
    

    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.cloud
            spring-cloud-starter-netflix-Eureka-client
        

        
            org.springframework.cloud
            spring-cloud-starter-openFeign
        

        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-Hystrix
        

    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    true
                
            
        
    

启动类的配置

启动类代码非常简单,加上 @SpringBootApplication、@EnableFeignClients 和 @EnableHystrix 即可,@SpringBootApplication 是开启自动配置;@EnableFeignClients 是开启 Feign 服务,即负载均衡的服务调用;@EnableHystrix 是开启熔断服务。

@EnableHystrix         //开启熔断服务
@EnableFeignClients    //开启 Feign 服务,即负载均衡的服务调用
@SpringBootApplication
public class DemoServiceUser1Application {
    public static void main(String[] args) {
        SpringApplication.run(DemoServiceUser1Application.class, args);
    }
}

application.yml 配置

application.yml 文件中配置了注册中心的实例名为 service-user1,端口为 8081,Eureka 地址(比如:127.0.0.1:8888/Eureka/),是否开启 Hystrix 服务等等,具体配置说明请看下面的代码。

spring:
  application:
    name: service-user1
server:
  port: 8081

Eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/Eureka/
  instance:
    hostname: com.example.user1
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    non-secure-port-enabled: true
    secure-port-enabled: false

Feign:
  Hystrix:
    enabled: true

Controller 类和 Service 类

下面代码是微服务中的 service-user1 工程调用 service-user2 工程接口,使用 Feign 组件进行服务调用,使用 Hystrix 组件实现熔断功能。

package com.example.demo.controller;

import com.example.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @Autowired
    private DemoService demoService;

    /**
     * 本工程接口
     * @return
     */
    @GetMapping(value = "/user1/test1")
    public String test1() {
        return "this is service-user1 service test1 method";
    }

    /**
     * 调用 service-user2 服务接口
     * @return
     */
    @GetMapping(value = "/user1/test2")
    public String test2() {
        return demoService.user2test2();
    }

    /**
     * 调用 service-user2 服务接口,验证熔断功能
     * @return
     */
    @RequestMapping(value = "/user1/test3")
    public String test3() {
        return demoService.user2test3();
    }

}


package com.example.demo.service;

import org.springframework.cloud.openFeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * service-user2 服务接口
 */
@Component
@FeignClient(name = "service-user2", fallback = DemoServiceFallback.class)
public interface DemoService {

    @RequestMapping(value = "/user2/test2", method = RequestMethod.GET)
    String user2test2();

    @RequestMapping(value = "/user2/test3", method = RequestMethod.GET)
    String user2test3();
}


package com.example.demo.service;

import org.springframework.stereotype.Component;

/**
 * DemoService 熔断实现
 */
@Component
public class DemoServiceFallback implements DemoService{

    @Override
    public String user2test2() {
        return "this is service-user2 service test2 fallback method";
    }

    @Override
    public String user2test3() {
        return "this is service-user2 service test3 fallback method";
    }
}

service-user2 服务工程代码分析

代码结构

pom.xml 文件配置

下面配置是工程需要使用的所有 jar 和 maven 打包策略。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    
    com.example
    demo-service-user2
    0.0.1-SNAPSHOT
    demo-service-user2
    Demo project for Spring Boot

    
        UTF-8
        UTF-8
        1.8
        Greenwich.SR2
    

    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.cloud
            spring-cloud-starter-netflix-Eureka-client
        

        
            org.springframework.cloud
            spring-cloud-starter-openFeign
        

        
            org.springframework.boot
            spring-boot-starter-web
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    true
                
            
        
    

启动类的配置

启动类代码非常简单,加上 @SpringBootApplication 即可,@SpringBootApplication 是开启自动配置。

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

application.yml 配置

application.yml 文件中配置了注册中心的实例名为 service-user2,端口为 8082,Eureka 地址(比如:127.0.0.1:8888/Eureka/)等等,具体配置说明请看下面的代码。

spring:
  application:
    name: service-user2
server:
  port: 8082

Eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/Eureka/
  instance:
    hostname: com.example.user2
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    non-secure-port-enabled: true
    secure-port-enabled: false

Controller 类

下面代码是两个非常简单的接口,单纯用来测试服务调用是否成功及熔断是否起效。

package com.example.demo.controller;

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

@RestController
public class DemoController {

    @GetMapping(value = "/user2/test2")
    public String test2() {
        return "this is service-user2 service test2 method";
    }

    @GetMapping(value = "/user2/test3")
    public String test3() {
        int i = 1/0;  //故意抛异常,测试熔断功能
        return "this is service-user2 service test3 method";
    }
}

接口测试

1. 查看 Eureka 基本信息和注册实例信息。

url:http://127.0.0.1:8888/

2. 测试接口 test1,经过统一网关,仅涉及 service-user1 工程。

url:http://127.0.0.1:9999/user1/test1

响应结果:

this is service-user1 service test1 method

3. 测试接口 test2,经过统一网关,涉及 service-user1 和 service-user2 工程,测试 Feign。

url:http://127.0.0.1:9999/user1/test2

响应结果:

this is service-user2 service test2 method

4. 测试接口 test3,经过统一网关,涉及 service-user1 和 service-user2 工程,测试 Feign + Hystrix。

url:http://127.0.0.1:9999/user1/test3

响应结果:

this is service-user2 service test3 fallback method

注意事项

1. Eureka 工程必须把是否在 Eureka 上注册自己标识设置为 false,该标识默认为 true,因为本身就是 Eureka 服务端,不需要自己注册自己;把是否获取 Eureka 上的服务列表标识设置为 false,该标识默认为 true,因为 Eureka 客户端才需要获取服务列表。

2. 权限控制模块应该放在网关 Gateway 工程上实现,因为网关是统一入口,所以请求都经过这里,最适合做统一鉴权。

3. 如果网关 Gateway 工程也提供接口,而且使用了 ZuulFilter 过滤器(比如:上面提到的 IP 白名单),必须配置 serviceId 为 forward:/,不然调用接口时不经过 ZuulFilter 过滤器。

    zuul:
      routes:
        local:     #本地工程路由配置,名字随意起
          path: /**
          stripPrefix: false
          serviceId: forward:/

4. 如果使用 Hystrix 组件实现熔断功能,则需要配置 Hystrix 的 enabled 属性为 true。

    Feign:
      Hystrix:
        enabled: true

总结

通过这次的微服务 Spring Cloud 实战,让我们认识了 Eureka、Gateway、Feign 和 Hystrix 是什么,可以实现什么功能;让我们掌握了如何配置注册中心 Eureka,如何配置网关 Gateway 的动态路由,如何使用 ZuulFilter 过滤器实现相关业务,如何使用 Feign 实现服务之间的负载均衡调用,如何使用 Hystrix 实现熔断机制等技术。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1193.html

标签: feign 使用
分享给朋友:

“微服务 Spring Cloud 实战 Eureka+Gateway+Feign+Hystrix” 的相关文章

gitlab常用命令大全

GitLab常用命令大全GitLab是一个基于Git的Web平台,它不仅提供代码托管,还集成了持续集成/持续交付(CI/CD)、代码审查、问题追踪等功能。在日常使用GitLab的过程中,我们常常需要使用一系列命令来管理代码仓库、处理分支和标签等。以下是GitLab常用的Git命令大全,并附上详细解释...

理解virt、res、shr之间的关系(linux系统篇)

前言想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES和SHR三个重要的指标,他们分别代表什么意思呢?这是本文需要跟大家一起探讨的问题。...

我的VIM配置

写一篇关于VIM配置的文章,记录下自己的VIM配置,力求简洁实用。VIM的配置保存在文件~/.vimrc中(Windows下是C:\Users\yourname \_vimrc)。VIM除了自身可配置项外,还可插件扩展。VIM的插件一般用vundle或vim-plug来管理,但我力求简单,不打算装太...

JavaScript数组操作:掌握常用方法,提升开发效率

JavaScript数组操作:从增删改查到高级应用本文深入解析JavaScript中常用的数组方法,包括push、unshift、pop、shift、map、filter、reverse、at 和 slice。通过详细的例子和应用场景,帮助开发者快速掌握这些方法,提升代码效率和可读性。开篇点题作为J...

12种JavaScript中最常用的数组操作整理汇总

数组是最常见的数据结构之一,我们需要绝对自信地使用它。在这里,我将列出 JavaScript 中最重要的几个数组常用操作片段,包括数组长度、替换元素、去重以及许多其他内容。1、数组长度大多数人都知道可以像这样得到数组的长度:const arr = [1, 2, 3]; console.log(a...

电工也体会到英文也博大精深了意思一样字母不相同而且还经常遇到

在学习电工电路中,往往和电路图有密切关系,之前老师常说要学会看图,认识电气符号。我觉得这还是不够的。因为在一些电路中存在很多英文字母,它们并不代表电器元件。而是一种电路名称或命名。以下是我在工作中看到过字母常见的意思一样。 如:UVW=RST=ABC=T1T2T3=L1L2L3三相黄绿红 .交流火线...