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 ---| |
因为现在都是使用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 的结点上的。
这个地方 感觉做的不是太成熟,我使用了Dubbo-sample 中的样例代码,地址如下:
https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-tengine
首先 nginx 配置上,`dubbo_pass`只支持multi模式的upstream,相关upstream,必须通过`multi`指令,配置为多路复用模式,multi指令的参数为,多路复用连接的个数。 这个倒是还是好,因为dubbo 所有的接口基本是支持异步的。
但是接口调用规范如下: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;
}
先说调用链,目前dubbo 支持比较好的 应该就是zipkin,但是使用Tengine后,调用链基本就从Temgine 断开了。不过这也不是什么大问题。这个地方可以用接口调用日志补上。
目前它还不支持调用链,也不支持对API的 限速、路由配置的等功能。
因为这个模块是直接在TCP协议基础上开发的,更大的问题是,高并发的时候,这个地方一旦出现 TCP 拒绝链接的情况 就糟糕了,可用性就无法保证了,我虽然没有做过严格的测试,但是总感觉是一个很大的隐患。