spring cloud gateway网关和链路监控


前言


一、网关

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控(黑白名单)、路由转发等等。

1.1 gateway介绍

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的==统一的 API 路由管理方式==。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且==基于 Filter 链的方式提供了网关基本的功能==,例如:安全,监控和限流。

优点:

l 性能强劲:是第一代网关Zuul的1.6倍

l 功能强大:内置了很多实用的功能,例如转发、监控、限流等

l 设计优雅,容易扩展.

缺点:

l 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高

l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行 web.Jar

l 需要Spring Boot 2.0及以上的版本,才支持.

因为gateway它内置的服务器Netty,所以不能在使用Tomcat服务器。

1.2 如何使用gateway 

创建一个网关模块,引入依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>

修改application.yml文件

server:
  port: 7777
#配置路由
spring:
  cloud:
    gateway:
      routes:
        - id: my-product   #路由的标识,值唯一,默认随机产生一个唯一值
          uri: http://localhost:8080    #它是网关真实转发到的地址
          order: 0    #路由的优先级,值越小优先级越高
          predicates: #断言: 它返回boolean值,如果返回的为true,则转发到真实的地址 http://localhost:8080/product/getById/1
            - Path=/product/**

        - id: my-order
          uri: http://localhost:8090
          order: 0
          predicates:
            - Path=/order/**

 然后编写一个springboot 启动类就能使用了

1.3 网关优化

把网关也放入nacos注册中心

<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

 修改各个微服务之间的负载均衡

server:
  port: 7777
#配置路由
spring:
  cloud:
    gateway:
      routes:
        - id: my-product   #路由的标识,值唯一,默认随机产生一个唯一值
          uri: lb://my-product   #它是网关真实转发到的地址
          order: 0    #路由的优先级,值越小优先级越高
          predicates: #断言: 它返回boolean值,如果返回的为true,则转发到真实的地址 http://localhost:8080/product/getById/1
            - Path=/product/**

        - id: my-order
          uri: lb://my-order  #lb 基于ribbon的负载均衡 默认轮询
          order: 0
          predicates:
            - Path=/order/**

简约模式

 #开启简约模式

spring.cloud.gateway.discovery.locator.enabled=true

然后前端访问的时候路径上必须要加上服务的名称

http://localhost:7777/my-order/order/get/1 

1.4自定义断言和过滤器

1.4.1 自定义断言

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {


    public static final String MIN = "min";
    public static final String MAX = "max";
    public AgeRoutePredicateFactory() {
        super(AgeRoutePredicateFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        //读取配置文件中的内容
        return Arrays.asList("min", "max");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {

        return (t)->{
            ServerHttpRequest request = t.getRequest();
            String age = request.getHeaders().getFirst("age").toString();
            if(StrUtil.isNotEmpty(age)&& NumberUtil.isInteger(age)){
                int i = Integer.parseInt(age);
                if(i>=config.getMin() &&i<=config.getMax()){
                    return true;
                }
            }
            return false;
        };
    }

    @Validated
    public static class Config {
        @NotNull
        private Integer min;
        @NotNull
        private Integer max;

        public Integer getMin() {
            return min;
        }

        public void setMin(Integer min) {
            this.min = min;
        }

        public Integer getMax() {
            return max;
        }

        public void setMax(Integer max) {
            this.max = max;
        }
    }
}

分析:添加年龄的断言 需要在配置文件中使用

     predicates:
            - Path=/product/**
#            - Header=X-Request-Id, \d+ 请求头必须带数字
            - Age=18,28 限制18到28的访问

自定义断言的时候要写成 xxxRoutePredicateFactory 然后使用就 -xxx

 网关过滤器

引入工具依赖

<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

@Component
public class LoginFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //哪些路径允许放行
        RequestPath path = request.getPath();
        if("/login".equals(path)){
            return chain.filter(exchange);
        }

        //获取请求头
        String token = request.getHeaders().getFirst("token");
        if(StrUtil.isNotEmpty(token)&&"admin".equals(token)){
             return chain.filter(exchange);
        }

        //3.2封装返回数据
        Map<String, Object> map = new HashMap<>();
        map.put("msg", "未登录");
        map.put("code", "NOTLOGING");

        //3.3作JSON转换
        byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);

        //3.4调用bufferFactory方法,生成DataBuffer对象
        DataBuffer buffer = response.bufferFactory().wrap(bytes);

        //4.调用Mono中的just方法,返回要写给前端的JSON数据
        return response.writeWith(Mono.just(buffer));

    }

    @Override
    public int getOrder() {
        return 0;
    }
}

二、Sleuth--链路追踪

2.1 链路追踪介绍

在大型系统的微服务化构建中,一个系统被拆分成了许多微服务。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心【区域】,也就意味着这种架构形式也会存在一些问题:

l 如何快速发现问题?

l 如何判断故障影响范围?

l 如何梳理服务依赖?

l 如何分析链路性能问题以及实时容量规划?

分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上IP、每个服务节点的请求状态200 500等等。

2.2 Sleuth介绍

SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google Dapper的设计, 先来了解一下Sleuth中的术语和相关概念。

Trace(一条完整链路--包含很多span(微服务接口))

由一组Trace Id(贯穿整个链路)相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。

*Span*

代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。

*Annotation*

用它记录一段时间内的事件,内部使用的重要注释:

l cs(Client Send)客户端发出请求,开始一个请求的命令

l sr(Server Received)服务端接受到请求开始进行处理, sr-cs = 网络延迟(服务调用的时间)

l ss(Server Send)服务端处理完毕准备发送到客户端,ss - sr = 服务器上的请求处理时间

l cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - cs = 请求的总时间

2.3 使用

在父工程中引入依赖 所有子模块都加入到链路监控中

   <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
    </dependencies>

2.4 Zipkin的集成

Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储展现、查找和我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源

除了面向开发的 API 接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。

Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。

它主要由 4 个核心组件构成:

l Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。

l Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。

l RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。

l Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。

Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。

 2.5 使用可视化zipkin来监控微服务

下载zipkin的jar包 在mysql中创建zipkin的数据库来持久化监控数据

CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `remote_service_name` VARCHAR(255),
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
  PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';

CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT,
  `error_count` BIGINT,
  PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root

在父项目中引入依赖

  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

然后每个微服务模块都配置

# zipkin的配置
spring.zipkin.base-url=http://localhost:9411
#zipkin不注册到nacos上
spring.zipkin.discovery-client-enabled=false
# zipkin要把sleuth产生的日志,抽取的比例。 0.03
spring.sleuth.sampler.probability=1.0

 通过浏览器访问 http://localhost:9411访问 完成微服务的链路追踪


总结

待补充

猜你喜欢

转载自blog.csdn.net/qq_55648724/article/details/129070748