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<String, Object> dubbo_method (Map<String, Object> context);目前只能使用Map 定义定义接口的输入与输出,这个有点限制的太死了。这种方式,就是要绑架后端服务啊,应该是不行的。下面是段样例代码。

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

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

        Map<String, Object> ret = null;
        String testValue = (String)context.get("test");
        if (testValue != null) {
            System.out.println("test value: " + testValue);
            ret = new HashMap<String, Object>();
            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<NameValuePair> params = null;
        Map<String, String> headers = new HashMap<String, String>();
        for (Map.Entry<String, Object> 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<String, Object> sendRequest(String url, String method, List<NameValuePair> params, Map<String, String> headers, byte[] body) {
        Map<String, Object> result = new HashMap<String, Object>();

        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<String, String> 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 拒绝链接的情况 就糟糕了,可用性就无法保证了,我虽然没有做过严格的测试,但是总感觉是一个很大的隐患。

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章