Tengine ---- Dubbo API网关K8S 环境部署与使用

1 概述

Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。在2.3.2版本后 新增加了一个支持dubbo的模块:ngx_http_dubbo_module,添加改模块后调用流程如下所示:

User                 tengine (dubbo_pass)                         Dubbo Service Provider
  |                          |                                              |
  |--- GET github.com:443 -->|                                              |
  |                          |--- Dubbo Multiplexing Binary RPC Request  -->|
  |                          |                                              |
  |                          |<-- Dubbo Multiplexing Binary RPC Response ---|
  |<--    HTTP/1.1 200    ---|                                              |

2  K8S 部署说明

因为现在都是使用K8S的部署应用的,我这里就尝试使用K8S 部署了下 Dubbo sample 中的代码。一般Dubbo 服务都是使用 zookeeper  或者Nacos 作为注册中心,但是无论使用那种中间件作为注册中心,Consumer服务都会在注册中心获取Provider 调用地址,然后通过RPC 接口规范调用,同时在 RPC接口中 一定会带有微服务的IP,这时如果使用K8S部署服务就要考虑网关和服务之间的网络联通性问题了。我在本地部署的部署方式

(1)Tengine  肯定是不能部署在 K8S 里面的,但是应该部署在K8S的某个节点上,因为没有在生产实验,索引慎重选择吧。

(2)所有的服务应该使用POD的IP注册到zookeeper上,其实dubbo 默认就是这么做的。

(3)zookeeper  集群最好由K8S 托管,或者部署到K8S 的结点上的。

 

3  接口调用

这个地方 感觉做的不是太成熟,我使用了Dubbo-sample 中的样例代码,地址如下:

https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-tengine

首先 nginx  配置上,`dubbo_pass`只支持multi模式的upstream,相关upstream,必须通过`multi`指令,配置为多路复用模式,multi指令的参数为,多路复用连接的个数。 这个倒是还是好,因为dubbo 所有的接口基本是支持异步的。

但是接口调用规范如下:Map dubbo_method (Map context);目前只能使用Map 定义定义接口的输入与输出,这个有点限制的太死了。这种方式,就是要绑架后端服务啊,应该是不行的。下面是段样例代码。

 @Override
    //a sample for dubbo to http test
    public Map  dubbo2Http(Map context) {
        for (Map.Entry entry : context.entrySet()) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }

        String destUrl = "http://127.0.0.1:9280";

        Map ret = null;
        String testValue = (String)context.get("test");
        if (testValue != null) {
            System.out.println("test value: " + testValue);
            ret = new HashMap();
            if (testValue.equals("null")) {
                System.out.println("dubbo test: null");
                return null;
            } else if (testValue.equals("body empty")) {
                System.out.println("dubbo test: body empty");
                ret.put("status", "302");
            } else if (testValue.equals("status empty")) {
                System.out.println("dubbo test: status empty");
                ret.put("body", "dubbo failed");
            } else {
                System.out.println("dubbo test: unkown test");
            }

            return ret;
        }

        //prepare params
        String method = null;
        String url = null;
        byte[] body = null;
        List params = null;
        Map headers = new HashMap();
        for (Map.Entry entry : context.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            System.out.println("get key = " + key + ", value = " + value);
            if (key.equals("args")) {
                params = URLEncodedUtils.parse((String)value, Charset.forName("UTF-8"));
            } else if (key.equals("method")) {
                method = (String)value;
            } else if (key.equals("body")) {
                body = (byte[])value;
            } else if (key.equals("uri")) {
                url = destUrl + value;
            } else {
                headers.put(key, (String)value);
            }
        }

        //do http request
        ret = sendRequest(url, method, params, headers, body);

        return ret;
    }

// 注意请求参数的拼接方式是有严格限制的,必须要跟Consumer的接口定义对应,Dubbo Client 可定是不支持的,需要调用者自实现Cluster集群调用方式
    Map sendRequest(String url, String method, List params, Map headers, byte[] body) {
        Map result = new HashMap();

        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        try{
            client = HttpClients.createDefault();

            URIBuilder uriBuilder = new URIBuilder(url);

            uriBuilder.addParameters(params);

            System.out.println("get to: " + uriBuilder.build().toString());

            HttpRequestBase request = null;

            if (method.equals("GET")) {
                request = new HttpGet(uriBuilder.build());
            } else if (method.equals("POST")) {
                request = new HttpPost(uriBuilder.build());
                if (body != null) {
                    HttpEntity entity = new ByteArrayEntity(body);
                    ((HttpPost)request).setEntity(entity);
                }
            } else {
                result.put("status", "500");
                return result;
            }

            for (Map.Entry entry : headers.entrySet()) {
                System.out.println("header key = " + entry.getKey() + ", value = " + entry.getValue());
                if (entry.getKey().equalsIgnoreCase("content-length")) {
                    System.out.println("skip content-length");
                    continue;
                }
                request.setHeader(new BasicHeader(entry.getKey(), entry.getValue()));
            }

            RequestConfig.Builder reqConf = RequestConfig.custom();
            reqConf.setRedirectsEnabled(false);
            request.setConfig(reqConf.build());

            response = client.execute(request);

            int statusCode = response.getStatusLine().getStatusCode();
            result.put("status", String.valueOf(statusCode));

            HttpEntity entity = response.getEntity();

            byte[] responseBody = EntityUtils.toByteArray(entity);
            result.put("body", responseBody);

            System.out.println("body byte");

            Header[] responseHeaders = response.getAllHeaders();
            for (Header h : responseHeaders) {
                System.out.println("header key = " + h.getName() + ", value = " + h.getValue());
                result.put(h.getName(), h.getValue());
            }
        }catch (Exception e){
            System.out.println("error: " + e.getCause().getMessage());
            e.printStackTrace();
            result.put("status", "502");
            result.put("body", e.getCause().getMessage().getBytes());
        }

        return result;
    }

 

4 监控

先说调用链,目前dubbo 支持比较好的 应该就是zipkin,但是使用Tengine后,调用链基本就从Temgine 断开了。不过这也不是什么大问题。这个地方可以用接口调用日志补上。

目前它还不支持调用链,也不支持对API的 限速、路由配置的等功能。

 

5 性能问题

因为这个模块是直接在TCP协议基础上开发的,更大的问题是,高并发的时候,这个地方一旦出现 TCP 拒绝链接的情况 就糟糕了,可用性就无法保证了,我虽然没有做过严格的测试,但是总感觉是一个很大的隐患。

 

 

 

 

 

 

 

 

你可能感兴趣的:(微服务,网关,java,负载均衡)