Eureka 包含两个组件:Eureka Server 和 Eureka Client
关系:
元信息存储
ConcurrentHashMap
最外层Map的key是应用名称,value对应的是各种实例形成的Map,其key是实例名称,value是实例的信息(ip、端口号、健康检查等)
Zuul是一个 API Gateway 服务器,本质上是一个 Web Servlet 应用
它提供了动态路由、监控等服务,核心是一系列 filters
Request Context
用于在过滤器之间传递消息,数据存储在每个请求的 ThreadLocal
中,是线程安全的;它扩展了 ConcurrentHashMap。
在请求被路由前调用 pre filters,可用于身份验证,在集群中选择请求的微服务,记录调试信息等
routing filters 将请求路由至微服务
为响应添加标准的 Http Header,收集统一信息与指标,将响应从微服务发送至客户端
在以上阶段发生错误时,会执行 error filters
可自定义过滤器
父工程部分依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.1.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.RELEASEspring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
<scope>providedscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
1、Eureka Server 模块引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
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 拥有了两个副本,总共注册了三个实例
1、依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
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/
需要继承 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 用于身份验证,需要在请求路由前判断,因此使用 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;
}
}