微服务网关层的调用

  • Post author:
  • Post category:其他




网关层

集群之后涉及到网关接收客户端请求,向下游业务逻辑层发起调用,并将请求处理结果返回给客户端

问题: 为什么有了Nginx还需要Gateway?

—— nginx和Spring Cloud Gateway其实有许多功能重叠的地方,例如:限流、服务转发、数据缓存等,那为什么还要使用gateway来做网关呢?答案其实很简单:专人专职。其实,网关在大型微服务系统中是一个很重要的角色:

API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。

它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。

API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。

而nginx只能实现一些上面所说的一部分功能,所以一般都是选择nginx做静态资源缓存和首页web服务器,在基于Nginx的首页高可用方案这篇文章中有详细描述,典型的架构图如下

在这里插入图片描述



动态路由

在这里插入图片描述

路由网关提供了外部请求的访问入口,所有的外部请求都只需要知道路由网关的地址就行了,无需知道每个微服务的访问地址,路由网关可以将外部的请求负载分发给不同的微服务,这样可以对外屏蔽微服务架构内部的结构。因为路由网关往往是外部请求访问微服务架构的入口,所以可以在路由网关做请求过滤的工作。

SpringCloud默认提供了2个路由网关,

Zuul和Gateway

,Zuul是网飞(netflix)提供的路由组件,而Gateway是SpringCloud团队自己开发的一款路由组件,用来替换Zuul的

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true   #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_routh    #路由的ID,没有固定规则但要求唯一,简易配合服务名
          #uri: http://localhost:8001         #匹配后提供服务的路由地址
          uri: lb://cloud-provider-service   #匹配后提供服务的路由地址,lb后跟提供服务的微服务的名,不要写错
          predicates:
            - Path=/payment/get/**          #断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_routh   #路由的ID,没有固定规则但要求唯一,简易配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-provider-service     #匹配后提供服务的路由地址,lb后跟提供服务的微服务的名,不要写错
          predicates:
            - Path=/payment/lb/**             #断言,路径相匹配的进行路由



请求鉴权



1.身份鉴权

在这里插入图片描述

由于在网关鉴权中会使用到JWT令牌

在这里插入图片描述



2、数据检查



请求转发

是为了统一程序入口,通过一个端口,可以访问所有的服务,方便后续的鉴权、授权等功能。

package cn.seaboot.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

/**
 * @author Mr.css
 */
@SpringBootApplication
public class GatewayApplication {

  public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
  }

  @Bean
  public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    //常见的demo,拦截get请求,往请求头添加一个参数,然后转到http://httpbin.org:80
    return builder.routes()
        .route(p -> p
            .path("/get")
            .filters(f -> f.addRequestHeader("Hello", "World"))
            .uri("http://httpbin.org:80"))
        .build();
//    return builder.routes()
//        .route(p -> p
//            .path("/get")
//            .filters(f -> f.addRequestHeader("Hello", "World"))
//            .uri("http://httpbin.org:80"))
//        .build();
  }
}



协议转换

协议转换是为了后端有很多服务提供了不同的方式,包括dubbo 协议,和dubbo 上提供的各种访问协议等(dubbo服务上协议的支持)

package com.neo.config;
 
import com.alibaba.fastjson.JSONObject;
import io.netty.buffer.UnpooledByteBufAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
 
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
 
/**
 * @ClassName DubboResponseGlobalFilter
 * @Desription 协议转换的过滤器类
 * @Author zhangzexu
 * @Date 2019/11/28 17:14
 * @Version V1.0
 */
@Configuration
public class DubboResponseGlobalFilter implements GlobalFilter, Ordered {
 
    @Value("${plugin.calssName}")
    private String className;
 
    private static Logger LOGGER = LoggerFactory.getLogger(DubboResponseGlobalFilter.class);
    private volatile List<HttpHeadersFilter> headersFilters;
 
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
 
    public DubboResponseGlobalFilter() {
 
    }
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
 
 
        final String scheme = requestUrl.getScheme();
        if (isAlreadyRouted(exchange) || "http".equals(scheme) || "https".equals(scheme) || "lb".equals(scheme) || "ws".equals(scheme)) {
            return chain.filter(exchange);
        }
        LOGGER.info("请求的url为{},协议为{}",requestUrl,scheme);
        setAlreadyRouted(exchange);
        /**
         * 获取请求的url 对路径进行重新编码
         */
        final String url = requestUrl.toASCIIString();
 
        Flux<DataBuffer> flux = exchange.getRequest().getBody();
        AtomicReference<byte[]> atomicReference = new AtomicReference<>();
        /**
         * 获取客户端请求的数据,body体
         */
        flux.subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            atomicReference.set(bytes);
        });
        return chain.filter(exchange)
                .then(Mono.defer(() -> {
                    ServerHttpResponse response = exchange.getResponse();
                    return response.writeWith(Flux.create(sink -> {
                        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                        JSONObject json = new JSONObject();
                        Class c = null;
                        DataBuffer dataBuffer = null;
                        String charset = "UTF-8";
                        try {
                            /**
                             * 初始化反射数据,将要调用的类反射获取,反射的类的名称结构,
                             * 用 dubbo 协议举例 
                             * 则插件的类名组合为 DubboGatewayImpl
                             */
                            StringBuilder sb = new StringBuilder(className);
                            sb.append(".");
                            char[] name = scheme.toCharArray();
                            name[0] -= 32;
                            sb.append(String.valueOf(name));
                            sb.append("GatewayPluginImpl");
                            c = Class.forName(sb.toString());
                            c.getMethods();
                            Method method = c.getMethod("send", String.class, byte[].class);
                            Object obj = c.getConstructor().newInstance();
                            Object result = method.invoke(obj, url, atomicReference.get());
                            HttpStatus status = HttpStatus.resolve(500);
                            /**
                             * 判断结果是否返回,如果没有数据则直接返回
                             */
                            if (result == null) {
 
                            } else {
                                json = (JSONObject) result;
                                status = HttpStatus.resolve(json.getInteger("code"));
                                json.remove("code");
                                /**
                                 * 获取字符集编码格式 默认 utf-8
                                 */
                                if (!StringUtils.isEmpty(json.getString("charset"))) {
                                    charset = json.getString("charset");
                                }
                            }
                            response.setStatusCode(status);
                            try {
                                dataBuffer = nettyDataBufferFactory.wrap(json.toJSONString().getBytes(charset));
                            } catch (UnsupportedEncodingException e) {
                                dataBuffer = nettyDataBufferFactory.wrap(e.toString().getBytes(charset));
                                LOGGER.error("返回调用请求数据错误{}",e);
                                e.printStackTrace();
                            }
                        } catch (Exception e) {
                            try {
                                dataBuffer = nettyDataBufferFactory.wrap(e.toString().getBytes(charset));
                                LOGGER.error("获取远程数据错误{}",e);
                            } catch (UnsupportedEncodingException ex) {
                                ex.printStackTrace();
                                LOGGER.error("返回调用请求数据错误{}",ex);
                            }
                            e.printStackTrace();
                        }
                        /**
                         * 将数据进行发射到下一个过滤器
                         */
                        sink.next(dataBuffer);
                        sink.complete();
                    }));
 
                }));
    }



过滤器链

限流的实现方案

请求计数限流

漏桶算法限流

令牌桶算法限流



业务逻辑层

1、接收网关层请求

2、业务逻辑处理

3、向数据访问层发起调用

在这里插入图片描述



数据访问层

1、屏蔽数据访问逻辑

2、屏蔽存储系统差异

3、屏蔽分库分表逻辑

4、屏蔽缓存逻辑

在这里插入图片描述



版权声明:本文为qq_44949002原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。