简介
一个基于springcloud的分布式广告系统。
本文介绍ad-eureka模块、ad-gateway模块、ad-common模块。
项目结构
- ad
- ad-eureka
- ad-gateway
- ad-service
- ad-binlog-common
- ad-binlog-kafka
- ad-common
- ad-dao
- ad-dashboard
- ad-dump-data
- ad-search
- ad-sponsor
ad是整个项目的父模块,只有一个pom.xml文件作为整个项目的父pom,配置子模块的公共依赖。
ad-eureka是注册中心微服务。
ad-gateway是网关微服务。
ad-service是业务微服务模块的父模块,只有一个pom.xml文件作为父pom。
ad-binlog-common中是处理binlog的通用代码。
ad-binlog-kafka负责处理监听binlog的微服务,处理binlog信息,发送到kafka中,供ad-search消费,构建增量索引。
ad-common是其他微服务通用的功能,如:统一响应格式、全局异常处理、通用代码定义、通用配置定义。
ad-dao中存放操作数据库的代码,这不是一个微服务。
ad-dump-data是数据转存微服务,用于将MySQL数据库中的数据转存到自定义文件中,供ad-search构建全量索引。
ad-search是广告检索微服务。
ad-sponsor是广告投放微服务。
ad-eureka
Zookeeper Eureka 设计原则 CP AP 优点 数据最终一致 服务高可用 缺点 选举leader过程中集群不可用 服务节点间的数据可能不一致 适用场景 对数据一致性要求较高 对注册中心服务可用性要求较高
保护模式
默认情况下,“EurekaClient客户端”会定时(默认5秒)向“EurekaServer注册中心”发送心跳包,就是说EurekaClient会定时向EurekaServer发送一个请求,如果EurekaServer能收到,就知道当前这个EurekaClient是存活状态。
如果EurekaServer在一定时间内(一般默认是90秒),没有收到EurekaClient发送的心跳包,就会从服务列表中剔除该服务。但是如果在短时间内丢失了大量服务实例的心跳,这时候EurekaServer就会开启自我保护机制,不会剔除服务。 eureka服务心跳检测和自我保护机制_wangJiaLun-china的博客-CSDN博客_eureka心跳检测时间
如果在Eureka Server的首页看到以下这段提示,则说明Eureka已经进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN >THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES >ARE NOT BEING EXPIRED JUST TO BE SAFE.一般出现此模式时,服务返回错误。即如果真实的服务已经Down掉,但在注册中心界面服务却一直存在,且显示为UP状态。 产生原因:Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),Eureka Server会将当前的实例注册信息保护起来,同时提示这个警告。保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。
启动类
在EurekaApplication.java启动类上添加@EnableEurekaServer注解、@SpringBootApplication注解。
配置文件
eureka:
instance:
hostname: localhost # 应用实例主机名
client:
fetch-registry: false # 是否从Eureka获取注册信息,缺省:true
register-with-eureka: false # 是否向注册中心注册自己,缺省:true
service-url: # Eureka Client 的请求地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
注册中心客户端每隔一定的时间向注册中心服务端发送心跳,这时会有一行日志Resolving eureka endpoints via configuration,我们可以修改配置文件将该日志禁止。
#提高日志级别为WARN
logging:
level:
com.netflix.discovery.shared.resolver.aws.ConfigClusterResolver: WARN
ad-gateway
微服务架构的两种方式
- 点对点的方式:服务之间直接调用,每个微服务都发放REST API,并调用其他微服务接口。
- API-网关方式:业务接口通过API网关暴露,是所有客户端接口的唯一入口。微服务之间的通信也通过API网关。
请求到达网关时首先被 pre 类型的过滤器处理,主要目的是在请求路由前做一些请求校验等前置加工。
完成 pre 阶段后进入 routing 请求转发阶段,将外部请求转发到具体服务实例。
routing 之后进入 post,此阶段过滤器不仅可以获取请求信息,还能获得服务实例返回的信息,做一些加工处理。
启动类
在ZuulGatewayApplication.java启动类上添加@EnableZuulProxy注解、@SpringCloudApplication注解。
@SpringCloudApplication包含
@SpringBootApplicationSpringBoot注解@EnableDiscoveryClient注册服务中心Eureka注解@EnableCircuitBreaker断路器注解
配置文件
zuul:
prefix: /daijizai # 设置公共前缀
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
SpringCloud中Zuul路由配置——prefix,routes_RollingTune的博客-CSDN博客_zuul.prefix
过滤器
实现功能:收到请求到返回响应的用时。
继承ZuulFilter,重写以下方法:
String filterType()定义过滤器的类型,有四种类型(error、post、pre、route)int filterOrder()同一类型的过滤器中,数字越小,越先被执行。boolean shouldFilter()是否执行该过滤器Object run()
PreRequestFilter
filterType:PRE_TYPE filterOrder:0(尽早执行) shouldFilter:true
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.set("startTime", System.currentTimeMillis());
return null;
}
com.netflix.zuul.context.RequestContext请求上下文,这个对象在整个过滤器链上一直传递下去。
AccessLogFilter
filterType:POST_TYPE filterOrder:SEND_RESPONSE_FILTER_ORDER - 1(尽量晚执行) shouldFilter:true
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
Long startTime = (Long) context.get("startTime");
long duration = System.currentTimeMillis() - startTime;
HttpServletRequest request = context.getRequest();
String uri = request.getRequestURI();
log.info("uri: " + uri + ", duration: " + duration / 100 + "ms");
return null;
}
ad-common
ad-common是ad-service的子模块,实现广告系统微服务通用的功能。
统一响应处理
统一响应的vo类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> implements Serializable {
private Integer code;
private String message;
private T data;
public CommonResponse(Integer code, String message) {
this.code = code;
this.message = message;
}
}
忽略统一响应的注解
@Target({ElementType.TYPE, ElementType.METHOD})//可以修饰类、可以修饰方法
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
}
- 定义
CommonResponseDataAdvice类,实现ResponseBodyAdvice<>接口
@RestControllerAdvice
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object>
ResponseBodyAdvice是对Controller返回的
{@code @ResponseBody}or a {@code ResponseEntity}后,{@code HttpMessageConverter}类型转换之前拦截, 进行相应的处理操作后,再将结果返回给客户端.
HttpMessageConverter,报文信息转换器。 作用: 1、将请求报文转化为java对象 2、将java对象转化为响应报文
HttpMessageConverter提供了两个注解和两个类供我们使用:@RequestBody、@ResponseBody;RequestEntity、ResponseEntity。 HttpMessageConverter详解_Keeling1720的博客-CSDN博客
- 实现
supports方法
返回false不执行beforeBodyWrite方法,返回true执行beforeBodyWrite方法。通过该方法可以选择对哪些类或哪些方法的response不进行处理。
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
//拿到类的声明,如果类被IgnoreResponseAdvice注解修饰,代表该类不应该被CommonResponseDataAdvice影响
return false;
}
if (methodParameter.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class)) {
//拿到方法,如果方法被IgnoreResponseAdvice注解修饰,代表该方法不应该被CommonResponseDataAdvice影响
return false;
}
return true;
}
beforeBodyWrite方法
beforeBodyWrite方法中实现对response的具体操作。
对响返回对象进行处理:
- 返回对象为空,返回新建的
CommonResponse<Object> response - 返回对象本身就是一个
CommonResponse,不需要再包装一层,直接返回形参body - 其他情况,
response.setData(body)实现对body进行包装,返回CommonResponse<Object> response
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
//body:返回对象
CommonResponse<Object> response = new CommonResponse<>(0, "");
if (null == body) {
return response;
} else if (body instanceof CommonResponse) {
response = (CommonResponse<Object>) body;
} else {
response.setData(body);
}
return response;
}
新建一个Advice类,它被@ControllerAdvice修饰,又实现了ResponseBodyAdvice接口。我们希望在这个类里面既能实现全局异常处理,又能对后端返回的数据统一封装。
统一异常处理
@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;
}
}
AdException是自定义的错误类。
@RestControllerAdvice包含@ControllerAdvice和@ResponseBody
统一配置
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.clear();
converters.add(new MappingJackson2HttpMessageConverter());
}
}
default void configureMessageConverters(List<HttpMessageConverter<?>> converters)Configure the
HttpMessageConverters for reading from the request body and for writing to the response body.
WebMvcConfigurer配置类其实是
Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现**WebMvcConfigurer** 接口;SpringBoot---WebMvcConfigurer详解_zhangpower1993的博客-CSDN博客_webmvcconfigurer
MappingJackson2HttpMessageConverter是HttpMessageConverter的一种实现。