当前位置:   article > 正文

SpringCloud学习笔记(一)—— Eureka、Zuul_zuul eureka

zuul eureka


使用的 Spring Boot 版本是 2.2.1.RELEASE,Spring Cloud 版本是 Hoxton.RELEASE。

一、原理概念

Eureka

Eureka 包含两个组件:Eureka Server 和 Eureka Client
在这里插入图片描述
关系:

  • Eureka Server:服务注册中心(可以是一个集群),对外暴露自己的地址。
  • 提供者:微服务启动后向 Eureka Server 注册自己信息(如主机, 端口, 健康检查url等)。
  • 消费者:向 Eureka Server 订阅服务,Eureka Server 会将对应服务的所有提供者地址列表发送给消费者,并且定期更新,消费者使用该地址调用提供者的接口。
  • 心跳(续约):提供者定期(30s)通过http方式向Eureka刷新自己的状态,如在某个配置超时时间内(默认90s)Eureka Server 未接收到实例的心跳信息,就会将该实例从注册列表中移除。
  • 下线:向 Eureka Server 发起通知下线,清理其元信息

元信息存储
ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>
最外层Map的key是应用名称,value对应的是各种实例形成的Map,其key是实例名称,value是实例的信息(ip、端口号、健康检查等)

Zuul

Zuul是一个 API Gateway 服务器,本质上是一个 Web Servlet 应用
它提供了动态路由、监控等服务,核心是一系列 filters
在这里插入图片描述
Request Context 用于在过滤器之间传递消息,数据存储在每个请求的 ThreadLocal 中,是线程安全的;它扩展了 ConcurrentHashMap。

  1. 在请求被路由前调用 pre filters,可用于身份验证,在集群中选择请求的微服务,记录调试信息等

  2. routing filters 将请求路由至微服务

  3. 为响应添加标准的 Http Header,收集统一信息与指标,将响应从微服务发送至客户端

  4. 在以上阶段发生错误时,会执行 error filters

  5. 可自定义过滤器

二、使用

父工程部分依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <!-- 通用依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!-- 统一 Spring Cloud 版本 -->
    <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>

    <!-- 配置远程仓库 (提供 Spring Cloud 的相关依赖)-->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
   
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

Eureka

单实例

1、Eureka Server 模块引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、启动类上加注解@EnableEurekaServer
3、配置 application.yml

spring:
  application:
    name: coupon-registry
server:
  port: 8000

eureka:
  instance:
    hostname: localhost
    lease-expiration-duration-in-seconds: 90 # 过期时长
    lease-renewal-interval-in-seconds: 30    # 续约周期
  client:
    fetch-registry: false  # 是否从 Eureka Server 获取注册中心, 默认 true; 单节点设置为 false,无需同步其它节点
    register-with-eureka: false # 是否将自己注册到 Eureka Server,默认 true; 单节点设置为 false
    # Eureka Server 所在的地址,用于注册服务、查询服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

启动项目,访问 http://127.0.0.1:8000/ 可看到 Eureka Server 界面

多实例

配置文件:
三个 Eureka Server 之间需要互相注册

spring:
  application:
    name: coupon-registry
  profiles: server1
server:
  port: 8000
eureka:
  instance:
    hostname: server1
    prefer-ip-address: false # 默认为 true,不允许同一个 ip 部署多实例;这里设置为 false,单机多实例
  client:
    service-url:
      defaultZone: http://server2:8001/eureka/,http://server3:8002/eureka/

---
spring:
  application:
    name: coupon-registry
  profiles: server2
server:
  port: 8001
eureka:
  instance:
    hostname: server2
    prefer-ip-address: false
  client:
    service-url:
      defaultZone: http://server1:8000/eureka/,http://server3:8002/eureka/

---
spring:
  application:
    name: coupon-registry
  profiles: server3
server:
  port: 8002
eureka:
  instance:
    hostname: server3
    prefer-ip-address: false
  client:
    service-url:
      defaultZone: http://server1:8000/eureka/,http://server2:8001/eureka/

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

打包

mvn clean package -Dmaven.test.skip=true -U
  • 1

切换到 target 目录下,启动 jar 包,通过配置文件中设置的 spring.profiles 来指定激活的配置,分别运行这三个实例

java -jar coupon-registry-1.0-SNAPSHOT.jar --spring.profiles.active=server1 
  • 1

在这里插入图片描述
此时访问 http://127.0.0.1:8000/,可看到 server1 拥有了两个副本,总共注册了三个实例
在这里插入图片描述
在这里插入图片描述

Zuul

搭建网关模块

1、依赖

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 服务网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、启动类上添加 @SpringCloudApplication @EnableZuulProxy

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication // 是一个 Spring Boot 应用
@EnableDiscoveryClient // 允许客户端发现该服务
@EnableCircuitBreaker  // 允许启用熔断器
public @interface SpringCloudApplication {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3、配置

server:
  port: 9000
spring:
  application:
    name: coupon-gateway
eureka:
  client:
    service-url:
      defaultZone: http://server1:8000/eureka/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

启动后
在这里插入图片描述

自定义 Zuul 过滤器

需要继承 ZuulFilter,并实现其四个抽象方法

  • filterType:过滤器的类型,对应 Zuul 生命周期的四个阶段 =》pre、post、route、error
  • filterOrder:过滤器的优先级,数字越小,优先级越高
  • shouldFilter:方法返回 boolean 类型,true 表示执行过滤器的 run 方法;false 不执行
  • run:过滤器的过滤逻辑
自定义通用抽象过滤器类

getOrDefault() 是 ConcurrentHashMap 中的方法
如果 key 不存在,返回设定的默认值;key 存在,返回 key 对应的 value 值

    public V getOrDefault(Object key, V defaultValue) {
        V v;
        return (v = get(key)) == null ? defaultValue : v;
    }
  • 1
  • 2
  • 3
  • 4

AbstractZuulFilter

public abstract class AbstractZuulFilter extends ZuulFilter {

    RequestContext requestContext;

    /**
     * 标志,请求是否需要继续执行下一个过滤器
     */
    private final static String NEXT = "next";

    /**
     * 判断过滤器是否需要执行
     * @return
     */
    @Override
    public boolean shouldFilter() {
        // 获取当前线程的请求上下文
        RequestContext context = RequestContext.getCurrentContext();
        // 第一次请求经过过滤器,上下文中不存在自己设置的 NEXT 标识,需要默认返回 true,继续执行 run()
        // 之后则根据 NEXT 对应的 value 的实际值 true/false 决定是否执行
        return (boolean) context.getOrDefault(NEXT, true);
    }

    @Override
    public Object run() throws ZuulException {
        requestContext = RequestContext.getCurrentContext();
        return cusRun();
    }

    protected abstract Object cusRun();

    Object fail(int code, String msg) {
        requestContext.set(NEXT, false);
        requestContext.setSendZuulResponse(false); // zuul 响应
        requestContext.getResponse().setContentType("text/html;charset=UTF-8");
        requestContext.setResponseStatusCode(code);
        requestContext.setResponseBody(String.format("{\"result\": \"%s!\"}", msg));
        return null;
    }

    Object success() {
        requestContext.set(NEXT, true);
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

AbstractPreZuulFilter

public abstract class AbstractPreZuulFilter extends AbstractZuulFilter{
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

AbstractPostZuulFilter

public abstract class AbstractPostZuulFilter extends AbstractZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
Token 校验过滤器

Token 用于身份验证,需要在请求路由前判断,因此使用 pre filter

@Slf4j
@Component
public class TokenFilter extends AbstractPreZuulFilter{
    @Override
    protected Object cusRun() {
        // requestContext 是 AbstractZuulFilter 类中初始化完成的 =》RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
         log.info(String.format(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString())));
        String token = request.getParameter("token");
        if (StringUtils.isEmpty(token)) {
            log.error("error: token is empty");
            return fail(401, "error: token is empty");
        }
        // TODO token 具体校验略
        return success();
    }

    @Override
    public int filterOrder() {
        return 1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
限流过滤器

在网关中可对请求进行限流,guava 中提供了限流工具类 RateLimiter,根据令牌桶算法实现。

限流也是在请求访问微服务前需要被过滤器执行

@Slf4j
@Component
public class RateLimiterFilter extends AbstractPreZuulFilter{

    /**
     * 限流器,2.0 表示每秒获取两个令牌
     */
    RateLimiter rateLimiter = RateLimiter.create(2.0);

    @Override
    protected Object cusRun() {
        HttpServletRequest request = requestContext.getRequest();
        // 尝试获取令牌
        if (rateLimiter.tryAcquire()) {
            log.info("get rate token success");
            return success();
        } else {
            log.error("rate limit: {}", request.getRequestURL());
            return fail(402, "error: rate limit");
        }
    }

    @Override
    public int filterOrder() {
        return 2;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
日志过滤器

访问日志中记录请求地址、请求时间,需要pre filter 和 post filter 结合

在过滤器中存储客户端发起请求的时间戳:
(优先级设置最高)

@Slf4j
@Component
public class PreRequestFilter extends AbstractPreZuulFilter{
    @Override
    protected Object cusRun() {
        HttpServletRequest request = requestContext.getRequest();
        request.setAttribute("startTime", System.currentTimeMillis());
        return success();
    }

    @Override
    public int filterOrder() {
        return 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

默认response filter 优先级是 1000,因此我们将它之前一个优先级的过滤器时间戳与最开始请求发起的时间戳相减即可

@Slf4j
@Component
public class AccessLogFilter extends AbstractPostZuulFilter{
    @Override
    protected Object cusRun() {
        HttpServletRequest request = requestContext.getRequest();
        // 从请求上下文中获取之前设置的开始时间戳
        Long startTime = (Long) requestContext.get("startTime");
        String requestURI = request.getRequestURI();
        long duration = System.currentTimeMillis() - startTime;
        log.info("url: {}, duration: {}", requestURI, duration);
        return success();
    }

    @Override
    public int filterOrder() {
        return FilterConstants.SEND_RESPONSE_FILTER_ORDER -1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/506094
推荐阅读
相关标签
  

闽ICP备14008679号