SpringCloud学习笔记(一)—— Eureka、Zuul

xiaoxiao2025-02-14  10

文章目录

一、原理概念EurekaZuul 二、使用Eureka单实例多实例 Zuul搭建网关模块自定义 Zuul 过滤器自定义通用抽象过滤器类Token 校验过滤器限流过滤器日志过滤器 使用的 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。

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

routing filters 将请求路由至微服务

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

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

可自定义过滤器

二、使用

父工程部分依赖

<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>

Eureka

单实例

1、Eureka Server 模块引入依赖

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

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/

启动项目,访问 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/

打包

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

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

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

此时访问 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>

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

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

3、配置

server: port: 9000 spring: application: name: coupon-gateway eureka: client: service-url: defaultZone: http://server1:8000/eureka/

启动后

自定义 Zuul 过滤器

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

filterType:过滤器的类型,对应 Zuul 生命周期的四个阶段 =》pre、post、route、errorfilterOrder:过滤器的优先级,数字越小,优先级越高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; }

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; } }

AbstractPreZuulFilter

public abstract class AbstractPreZuulFilter extends AbstractZuulFilter{ @Override public String filterType() { return FilterConstants.PRE_TYPE; } }

AbstractPostZuulFilter

public abstract class AbstractPostZuulFilter extends AbstractZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } }
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; } }
限流过滤器

在网关中可对请求进行限流,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; } }
日志过滤器

访问日志中记录请求地址、请求时间,需要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; } }

默认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; } }
转载请注明原文地址: https://www.6miu.com/read-5024728.html

最新回复(0)