微服务网关设计

Posted by NotGeek on May 24, 2020

微服务网关设计

TOC

[TOC]

网关整体设计架构图

1590526059670

Spring Cloud 官网:

Zuul

  • Authentication 认证
  • Insights
  • Stress Testing 压力测试
  • Canary Testing 金丝雀发布
  • Dynamic Routing 动态路由
  • Service Migration 服务整合
  • Load Shedding 负载保护
  • Security 安全
  • Static Response handling 静态输出
  • Active/Active traffic management 活动管理

不全是这些


Kong 的方案

  • 有一个 DNS 的方案,比较偷懒的方案

操作日志保存在哪里

操作日志要看重要程度与否,一般来说,可以存在数据库,也可以存在其他地方。

  • 大多数都存在 宽表,NoSQL,HBase 数据库中,或者时序数据库中,因为你需要 memery reduce,因为,你需要记录你登陆多少次,在数据库中是很难做的,你可以 Count , 但是不会这么做,

软负载和硬负载

  • 硬负载
  • 软负载
    • DNS 的 负载
    • Ring 环网的 负载

策略

各种策略都可以自定义,

  • 最小访问次数等等

Kong 的 API,


负载均衡是一个算法,

但是反向代理是一个程序

手写网关

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package com.darian.proxydemo.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/***
 *
 *
 * @author <a href="mailto:[email protected]">Darian</a>
 * @date 2020/5/27  3:57
 */
@WebServlet(name = "proxy", urlPatterns = "/proxy/*")
public class ProyServlet extends HttpServlet {

    // Proxy -> POST GET
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 请求头,请求体
        // header、Body

        // Client(Header + Body proxy forward => IP.PORT)
        // X-forwarded-For如果请求套存在,使用该值
        //Client 127.0.0.1 ->Proxy(168.0.0.)
        // httpServletRequest -> HttpClient > 192.0.0.1

        // 转发客户端
        RestTemplate restTemplate = new RestTemplate();

        // 执行请求到目标服务器(支持https)
        // 当前端口 8080
        // 目标服务器
        String RootURL = "http://www.baidu.com";
        RootURL = "http://localhost:8080";

        // Accept:  Request -> http://localhost:8080/abc -> RequestURI + "/abc"
        // target URL  => http://localhost:9090/abc
        String targetURL = RootURL + request.getRequestURI().substring("/proxy".length());
        // 构造 Request 实体
        RequestEntity requestEntity = null;
        try {
            requestEntity = createRequestEntity(request, targetURL);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }


        ResponseEntity<byte[]> responseEntity = restTemplate
                .exchange(requestEntity, byte[].class);

        wirteHeaders(responseEntity, response);
        writeBody(responseEntity, response);


    }

    private RequestEntity<byte[]> createRequestEntity(
            HttpServletRequest request, String url) throws URISyntaxException, IOException {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        byte[] body = createRequestEntity(request);
        MultiValueMap<String, String> headers = createRequestHeaders(request);
        RequestEntity<byte[]> requestEntity = new RequestEntity(body, headers, httpMethod, new URI(url));

        return requestEntity;
    }


    private MultiValueMap<String, String> createRequestHeaders(HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();

        ArrayList<String> headerNames = Collections.list(request.getHeaderNames());
        for (String headerName : headerNames) {
            ArrayList<String> headerValues = Collections.list(request.getHeaders(headerName));

            for (String headerValue : headerValues) {
                headers.add(headerName, headerValue);
            }
        }
        return headers;
    }

    private byte[] createRequestEntity(HttpServletRequest request) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        return StreamUtils.copyToByteArray(inputStream);
    }


    private void preparedHeaders(HttpServletRequest request,
                                 RequestEntity<byte[]> requestEntity) {
        // 准备请求头
        HttpHeaders headers = requestEntity.getHeaders();

        List<String> headerNames = Collections.list(request.getHeaderNames());

        for (String headerName : headerNames) {
            List<String> headerValues = Collections.list(request.getHeaders(headerName));
            for (String headValue : headerValues) {
                headers.add(headerName, headValue);
            }
        }
    }


    /**
     * 输出 Body 部分
     *
     * @param responseEntity
     * @param response
     * @throws IOException
     */
    private void writeBody(ResponseEntity<byte[]> responseEntity,
                           HttpServletResponse response) throws IOException {
        if (responseEntity.hasBody()) {
            byte[] body = responseEntity.getBody();
            // 输出二进制值
            ServletOutputStream servletOutputStream = response.getOutputStream();
            // 输出 ServletOutputStream
            servletOutputStream.write(body);
            servletOutputStream.flush();

        }
    }

    private void wirteHeaders(ResponseEntity<byte[]> responseEntity,
                              HttpServletResponse httpServletResponse) {
        // 获取响应的头
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        // 输出转发 Response 头
        for (Map.Entry<String, List<String>> entry : httpHeaders.entrySet()) {
            String headerName = entry.getKey();
            List<String> headerValues = entry.getValue();

            for (String headerValue : headerValues) {
                httpServletResponse.addHeader(headerName, headerValue);
            }
        }

    }

    public static void main(String[] args) throws Exception {
        // 创建转发客户端
        RestTemplate restTemplate = new RestTemplate();
        RequestEntity requestEntity = new RequestEntity(HttpMethod.GET, new URI("http://www.baidu.com"));
        ResponseEntity<byte[]> responseEntity = restTemplate.exchange("http://www.baidu.com",
                HttpMethod.GET,
                requestEntity,
                byte[].class);

        System.err.println("responseEntity : \n" + responseEntity);
        System.err.println("responseEntity.getBody() : \n" + new String(responseEntity.getBody()));

    }
}


结果

1
2
3
http://localhost:8080/proxy/abc

-> 转发到   http://localhost:8080/abc

两个的结果都是

1
2
3
4
5
{
  status: "OK",
  msg: "成功",
  url: "/abc"
}
1
2
3
http://localhost:8080/proxy/

-> 转发到   http://localhost:8080/

两个的结果都是

1
2
3
4
5
{
  status: "OK",
  msg: "成功",
  url: "/"
}

Zuul 原理

  • 可以通过我的应用名称反向的寻找我的服务列表
    • 需要借助于 Eureka

Zuul

com.netflix.zuul.filters.ZuulServletFilter

  • com.netflix.zuul.filters.ZuulServletFilter#doFilter

    • this#preRouting

    • this#routing

      • 1
        2
        3
        
          void routing() throws ZuulException {
           this.zuulRunner.route();
          }
        
      • com.netflix.zuul.ZuulRunner#route

        • com.netflix.zuul.FilterProcessor#route
          • List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    • this#postRouting

Servlet30WrapperFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Servlet30WrapperFilter extends ZuulFilter {
    //...
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        if (request instanceof HttpServletRequestWrapper) {
            request = (HttpServletRequest)ReflectionUtils.getField(this.requestField, request);
            ctx.setRequest(new Servlet30RequestWrapper(request));
        } else if (RequestUtils.isDispatcherServletRequest()) {
            ctx.setRequest(new Servlet30RequestWrapper(request));
        }

        return null;
    }
    //  ..
}

RibbonRoutingFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class RibbonRoutingFilter extends ZuulFilter {
//...
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders(new String[0]);

        try {
            RibbonCommandContext commandContext = this.buildCommandContext(context);
            ClientHttpResponse response = this.forward(commandContext);
            this.setResponse(response);
            return response;
        } catch (ZuulException var4) {
            throw new ZuulRuntimeException(var4);
        } catch (Exception var5) {
            throw new ZuulRuntimeException(var5);
        }
    }
//...
        protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
        Map<String, Object> info = this.helper.debug(context.getMethod(), context.getUri(), context.getHeaders(), context.getParams(), context.getRequestEntity());
        RibbonCommand command = this.ribbonCommandFactory.create(context);

        try {
            ClientHttpResponse response = (ClientHttpResponse)command.execute();
            this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
            return response;
        } catch (HystrixRuntimeException var5) {
            return this.handleException(info, var5);
        }
    }
    //...
    
}

HttpClientRibbonCommandFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
    ///...
    
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
        ZuulFallbackProvider zuulFallbackProvider = this.getFallbackProvider(context.getServiceId());
        String serviceId = context.getServiceId();
        RibbonLoadBalancingHttpClient client = (RibbonLoadBalancingHttpClient)this.clientFactory.getClient(serviceId, RibbonLoadBalancingHttpClient.class);
        client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
        return new HttpClientRibbonCommand(serviceId, client, context, this.zuulProperties, zuulFallbackProvider, this.clientFactory.getClientConfig(serviceId));
    }
    
    ///....
    
}

RibbonLoadBalancingHttpClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RibbonLoadBalancingHttpClient extends AbstractLoadBalancingClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse, CloseableHttpClient> {

    //....
    public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
        Builder builder = RequestConfig.custom();
        IClientConfig config = configOverride != null ? configOverride : this.config;
        builder.setConnectTimeout((Integer)config.get(CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
        builder.setSocketTimeout((Integer)config.get(CommonClientConfigKey.ReadTimeout, this.readTimeout));
        builder.setRedirectsEnabled((Boolean)config.get(CommonClientConfigKey.FollowRedirects, this.followRedirects));
        RequestConfig requestConfig = builder.build();
        request = this.getSecureRequest(request, configOverride);
        HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
        HttpResponse httpResponse = ((CloseableHttpClient)this.delegate).execute(httpUriRequest);
        return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
    }

    //....
}

总结

网关都是找,应用,应用找 IP,然后把 RequestHeaders ,RequestBody 写进去,

转发,

最后把 ReponseHeader ,ResponseBody 写出来。