注册中心与网关 | 广告系统

191 阅读7分钟

简介

一个基于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

ZookeeperEureka
设计原则CPAP
优点数据最终一致服务高可用
缺点选举leader过程中集群不可用服务节点间的数据可能不一致
适用场景对数据一致性要求较高对注册中心服务可用性要求较高

一文了解Zookeeper和Eureka有哪些区别? - 环信 (easemob.com)

保护模式

默认情况下,“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将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。

Eureka常见问题总结 - moonandstar08 - 博客园 (cnblogs.com)

启动类

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,此阶段过滤器不仅可以获取请求信息,还能获得服务实例返回的信息,做一些加工处理。

Spring Cloud Zuul 网关(二) - 掘金 (juejin.cn)

启动类

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

  1. 定义CommonResponseDataAdvice类,实现ResponseBodyAdvice<>接口
@RestControllerAdvice
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object>

ResponseBodyAdvice是对Controller返回的{@code @ResponseBody}or a {@code ResponseEntity} 后,{@code HttpMessageConverter} 类型转换之前拦截, 进行相应的处理操作后,再将结果返回给客户端.

Spring中ResponseBodyAdvice的使用详解_java_脚本之家 (jb51.net)

HttpMessageConverter,报文信息转换器。 作用: 1、将请求报文转化为java对象 2、将java对象转化为响应报文

HttpMessageConverter提供了两个注解和两个类供我们使用:@RequestBody、@ResponseBody;RequestEntity、ResponseEntity。 HttpMessageConverter详解_Keeling1720的博客-CSDN博客

  1. 实现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;
    }
  1. 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接口。我们希望在这个类里面既能实现全局异常处理,又能对后端返回的数据统一封装。

@ControllerAdvice和ResponseBodyAdvice_开源必胜的博客-CSDN博客

统一异常处理

@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 Framework 5.3.22 API)

WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现**WebMvcConfigurer** 接口;

SpringBoot---WebMvcConfigurer详解_zhangpower1993的博客-CSDN博客_webmvcconfigurer

MappingJackson2HttpMessageConverterHttpMessageConverter的一种实现。