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