4.1 Eureka服务注册与服务发现 - 构建 ad-eureka
Eureka 的介绍
核心功能:
ServiceRegistry(服务注册)
ServiceDiscovery(服务发现)
基本架构
Eureka 由三个角色组成:
EurekaServer(这一章实现的功能),提供服务注册与发现
ServiceProvider,服务提供方,将自身服务注册到 EurekaServer 上,从而让EurekaServer 持有服务的元信息,让其他的服务消费方能够找到当前服务
ServiceConsumer,服务消费方,从 EurekaServer 上获取注册服务列表,从而能够消费服务
ServiceProvider/Consumer 相对于 Server,都叫做 Eureka Client
Eureka 的基本架构如下图所示
Eureka Server 的高可用
问题说明:单节点的 Eureka Server 虽然能够实现基础功能,但是存在单点故障的问题,不能实现高可用。因为 EurekaServer 中存储了整个系统中所有的微服务的元数据信息,单节点一旦挂了,所有的服务信息都会丢失,造成整个 系统的瘫痪。
解决办法:搭建 EurekaServer 集群,让各个 Server 节点之间互相注册,从而实现微服务元数据的复制/备份,即使单个节点失效,其他的 Server 节点仍可以继续提供服务
EurekaServer 集群架构如下图所示
application.yml
spring:
application:
name: ad-eureka
server:
port: 8000
eureka:
instance:
hostname: localhost
client:
#表示表示是否从EurekaServer获取注册信息,默认为true。单节点不需要同步其他的EurekaServer节点的数据
fetch-registry: false
#表示是否将自己注册在EurekaServer上,默认为true。由于当前应用就是EurekaServer,所以置为false
register-with-eureka: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
构造应用的 pom.xml 文件及注释信息
<name>ad-eureka</name>
<description>Spring Cloud Eureka</description>
<!-- eureka server: 提供服务发现与服务注册 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
启动EurekaApplication
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
4.2 Zuul网关 - 构建ad-gateway
Zuul 的介绍
在介绍 Zuul 可以提供的功能之前,请大家先考虑一个问题:
微服务系统中往往包含很多个功能不同的子系统或微服务,那么,外部应用怎 样去访问各种各样的微服务呢?这也是 Zuul 所要解决的一个主要问题。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个服务网关 根据请求的 url,路由到相应的服务,即实现请求转发,效果如下图所示。
Zuul 提供了服务网关的功能,可以实现负载均衡、反向代理、动态路由、请求转发等功能。Zuul 大部分功能都是通过过滤器实现的,Zuul 中定义了四种标准的过滤器类型,同时,还支持自定义过滤器(课程中实现了两个自定义过滤 器,用来记录访问延迟)。这些过滤器的类型也对应于请求的典型生命周期, 如下图所示。
pre:在请求被路由之前调用
route:在路由请求时被调用
post:在 route 和 error 过滤器之后被调用
error:处理请求时发生错误时被调用
构造应用的 pom.xml 文件及注释信息
<name>ad-gateway</name>
<description>ad-gateway</description>
<dependencies>
<!--
Eureka 客户端, 客户端向 Eureka Server 注册的时候会提供一系列的元数据信息, 例如: 主机, 端口, 健康检查url等
Eureka Server 接受每个客户端发送的心跳信息, 如果在某个配置的超时时间内未接收到心跳信息, 实例会被从注册列表中移除
-->
<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>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.yml
server:
port: 9000
spring:
application:
name: ad-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka/
#defaultZone: http://server1:8000/eureka/
---
zuul:
prefix: /imooc
routes:
sponsor:
path: /ad-sponsor/**
serviceId: eureka-client-ad-sponsor
strip-prefix: false
search:
path: /ad-search/**
serviceId: eureka-client-ad-search
strip-prefix: false
pre过滤器记录开始时间,post过滤器计算执行时间
@Slf4j
@Component
public class PreRequestFilter extends ZuulFilter {
//过滤器类型
public String filterType() {return FilterConstants.PRE_TYPE;}
//过滤器执行顺序,数字越小级别越高
public int filterOrder() {return 0;}
//是否需要执行过滤器
public boolean shouldFilter() {return true;}
//过滤器执行逻辑:计算执行时间,pre过滤器记录开始时间
public Object run() throws ZuulException {
//请求上下文,将当前请求时间传递给另一个过滤器
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("startTime", System.currentTimeMillis());
return null;
}
}
@Slf4j
@Component
public class AccessLogFilter extends ZuulFilter {
public String filterType() { return FilterConstants.POST_TYPE;}
//最后执行
public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;}
public boolean shouldFilter() { return true;}
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
//获取请求uri
HttpServletRequest request = context.getRequest();
String uri = request.getRequestURI();
//计算执行时间
Long startTime = (Long) context.get("startTime");
long duration = System.currentTimeMillis() - startTime;
log.info("uri: " + uri + ", duration: " + duration / 100 + "ms");
return null;
}
}
启动ZuulGatewayApplication
@EnableZuulProxy
@SpringCloudApplication
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
4.3 Common通用配置模块 - ad-common
通用的代码、配置不应该散落在各个业务模块中,不利于维护与更新
一个大的系统,响应对象需要统一外层格式
各种业务设计与实现,可能会抛出各种各样的异常,异常信息的收集也应该做到统一
//对响应统一拦截,实现统一返回格式
@RestControllerAdvice
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> {
//根据条件判断是否拦截
@SuppressWarnings("all")//忽略可能产生空指针异常的warning
public boolean supports(MethodParameter methodParameter,Class<? extends HttpMessageConverter<?>> aClass) {
//判断类是否被注解声明
if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
//判断方法方法是否被注解声明
if (methodParameter.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}return true;
}
//写入相应前执行
@Nullable
@SuppressWarnings("all")
public Object beforeBodyWrite(@Nullable Object o,MethodParameter methodParameter,MediaType mediaType,Class<? extends HttpMessageConverter<?>> aClass,ServerHttpRequest serverHttpRequest,ServerHttpResponse serverHttpResponse){ CommonResponse<Object> response = new CommonResponse<>(0, "");
if (o == null) {return response;
} else if (o instanceof CommonResponse) {response = (CommonResponse<Object>) o;
} else {response.setData(o);
}
return response;
}
//定义统一异常处理类,通过GlobalExceptionAdvice实现处理
public class AdException extends Exception {
public AdException(String message) { super(message); }
}
//处理AdException捕获的异常
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = AdException.class)
public CommonResponse<String> handlerAdException(HttpServletRequest req,AdException ex) {
CommonResponse<String> response = new CommonResponse<>(-1, "business error");
response.setData(ex.getMessage());
return response;
}
}
//统一配置,http消息转换器:将java实体对象转换成http数据输出流
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.clear();
converters.add(new MappingJackson2HttpMessageConverter());
}
}