广告系统设计与实现(四) - 广告系统骨架开发和通用模块开发

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

转载于:https://my.oschina.net/u/3635618/blog/3082253

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值