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 拒絕鏈接的情況 就糟糕了,可用性就無法保證了,我雖然沒有做過嚴格的測試,但是總感覺是一個很大的隱患。

 

 

 

 

 

 

 

 

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