facebook/swift:构建thrift http server(4)--ThriftXHRDecoder,ThriftXHREncoder

《facebook/swift:构建thrift http server(1)》
《facebook/swift:构建thrift http server(2)–HttpServerCodec》
《facebook/swift:构建thrift http server(3)–CORS跨域》

在上一篇博客中解决了thrift http sever的CORS跨域问题,但前端依然没有服务端的正常响应。看来还存在问题。
继续研究Netty的代码。

ThriftMessage

通过跟踪服务端收到的HTTP POST请求在管道(ChannelPipeline)中的传递流程找到了问题:
如下是实现将网络请求分发到thrift服务实例(NiftyProcessor)的ChannelHandler实例com.facebook.nifty.core.NiftyDispatchermessageReceived方法实现代码:

    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception
    {    	
        if (e.getMessage() instanceof ThriftMessage) {
            ThriftMessage message = (ThriftMessage) e.getMessage();
            if (taskTimeoutMillis > 0) {
                message.setProcessStartTimeMillis(System.currentTimeMillis());
            }
            checkResponseOrderingRequirements(ctx, message);

            TNiftyTransport messageTransport = new TNiftyTransport(ctx.getChannel(), message);
            TTransportPair transportPair = TTransportPair.fromSingleTransport(messageTransport);
            TProtocolPair protocolPair = duplexProtocolFactory.getProtocolPair(transportPair);
            TProtocol inProtocol = protocolPair.getInputProtocol();
            TProtocol outProtocol = protocolPair.getOutputProtocol();

            processRequest(ctx, message, messageTransport, inProtocol, outProtocol);
        }
        else {
        	// 如果不是message是ThriftMessage实例则继续向下传递
            ctx.sendUpstream(e);
        }
    }

请注意第一行的判断语句,只有收到的消息是ThriftMessage实例,才会响应请求。
HttpServerCodec只会将收到的网络请求解析为DefaultHttpRequest

参见 org.jboss.netty.handler.codec.http.HttpRequestDecoder

所以问题搞清楚了。我们需要一个ChannelUpstreamHandlerDefaultHttpRequest转换为ThriftMessage向后传递给NiftyDispatcher,同时也需要一个ChannelDownstreamHandlerThriftMessage转为DefaultHttpResponse向前传递给HttpServerCodecHttpResponseEncoder进一步封装成HTTP响应数据最后发送给前端。

ThriftXHRDecoder

HTTP request解码器ThriftXHRDecoder实现如下:

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.UpstreamMessageEvent;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;

import com.facebook.nifty.core.ThriftMessage;
import com.facebook.nifty.core.ThriftTransportType;

/**
 * XHR(XML Http Request)解码器<br>
 * 将{@link HttpRequest}请求的内容数据(content)转为{@link ThriftMessage},
 * 提供给{@link com.facebook.nifty.core.NiftyDispatcher}
 * @author guyadong
 *
 */
public class ThriftXHRDecoder extends SimpleChannelUpstreamHandler {
	
	public ThriftXHRDecoder() {
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		if(e.getMessage() instanceof HttpRequest){
			HttpRequest request = (HttpRequest)e.getMessage();
			if(request.getContent().readable() && HttpMethod.POST.equals(request.getMethod())){
				// 非FRAME数据
				ThriftMessage thriftMessage = new ThriftMessage(request.getContent(),ThriftTransportType.UNFRAMED);
				ctx.sendUpstream(new UpstreamMessageEvent(ctx.getChannel(), thriftMessage, e.getRemoteAddress()));
				return;
			}
		}
		super.messageReceived(ctx, e);
	}
}

ThriftXHREncoder

ThriftMessage编码器ThriftXHREncoder实现如下:

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelDownstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;

import com.facebook.nifty.core.ThriftMessage;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * XHR(XML Http Request)编码器<br>
 * 将{@link com.facebook.nifty.core.NiftyDispatcher}输出的
 * {@link ThriftMessage}响应数据转为{@link DownstreamMessageEvent},
 * 
 * @author guyadong
 *
 */
public class ThriftXHREncoder extends SimpleChannelDownstreamHandler {

	@Override
	public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		if(e.getMessage() instanceof ThriftMessage){
			ThriftMessage thriftMessage = (ThriftMessage)e.getMessage();
			if(thriftMessage.getBuffer().readable()){
				switch (thriftMessage.getTransportType()) {
				case UNFRAMED:
                    DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK);
                    response.setContent(thriftMessage.getBuffer());
					ctx.sendDownstream(new DownstreamMessageEvent(ctx.getChannel(), 
							Channels.future(ctx.getChannel()), response, e.getRemoteAddress()));
					return;
				default:
	                throw new UnsupportedOperationException(
	                		thriftMessage.getTransportType().name() +" transport is not supported");
				}
			}
		}
		super.writeRequested(ctx, e);
	}

}

修改ChannelPipeline

有了ThriftXHRDecoderThriftXHREncoder就可以将它们添加到frameCodec之后,dispatcher之前,基于上一篇中addCorsHandlerIfHttp方法代码修改如下,

	/**
	 * 添加CORS Handler和XHR编解码器
	 */
	protected void addCorsHandlerIfHttp(){
		if(HTTP_TRANSPORT.equals(thriftServerConfig.getTransportName())){
			try {
				// 反射获取私有的成员NettyServerTransport
				final NettyServerTransport nettyServerTransport = ReflectionUtils.valueOfField(thriftServer, "transport");
				// 反射获取私有的成员ChannelPipelineFactory
				Field pipelineFactory = NettyServerTransport.class.getDeclaredField("pipelineFactory");
				{
					Field modifiersField = Field.class.getDeclaredField("modifiers");
					modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
					modifiersField.setInt(pipelineFactory, pipelineFactory.getModifiers() & ~Modifier.FINAL);
				}
				pipelineFactory.setAccessible(true);
				final ChannelPipelineFactory channelPipelineFactory = (ChannelPipelineFactory) pipelineFactory.get(nettyServerTransport);
				final Netty3CorsConfig corsConfig = Netty3CorsConfigBuilder.forAnyOrigin()
					.allowedRequestMethods(POST,GET,OPTIONS)
					.allowedRequestHeaders("Origin","Content-Type","Accept","application","x-requested-with")
					.build();
				ChannelPipelineFactory factoryWithCORS = new ChannelPipelineFactory(){

					@Override
					public ChannelPipeline getPipeline() throws Exception {
						// 修改 ChannelPipeline,在frameCodec后(顺序)增加CORS handler,XHR编解码器
						ChannelPipeline cp = channelPipelineFactory.getPipeline();
						cp.addAfter("frameCodec", "thriftXHRDecoder", new ThriftXHRDecoder());
						cp.addAfter("frameCodec", "thriftXHREncoder", new ThriftXHREncoder());
						cp.addAfter("frameCodec", "cors", new Netty3CorsHandler(corsConfig));
						return cp;
					}};
				// 修改nettyServerTransport的私有常量pipelineFactory
				pipelineFactory.set(nettyServerTransport, factoryWithCORS);
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
	}

修改完毕后再执行test_js.html,thrift http server终于有响应了。

在这里插入图片描述
而且数据结果正常
在这里插入图片描述

遗留问题

至此,基于facebook/swift构建的thrift http server已经基本可以正常工作,但还是存在一个小问题。HTTP响应只会在前端空闲超时后才会发送响应数据到前端,所以ThriftServerConfig的IdleConnectionTimeout如果设置过大(比如默认值60s),那么前端要在发送请求60秒后才会收到服务端的响应,这个问题一直到目前还没有找到。所以目前的办法是将这个值设置在10ms,就基本不会影响前端的响应延迟。

零零散散的写了好几篇文章,贴出的代码都比较零碎,完整的代码参见码云仓库
https://gitee.com/l0km/common-java/tree/master/common-thrift/src/main/java/com/facebook/swift/service

重要的修改在ThriftServerService

<think>好的,我现在需要帮助用户确认Iceberg Hive Catalog配置中的URI格式和Hive仓库路径的正确设置。用户提到了几个可能的配置选项,我需要结合他们提供的hive-site.xml内容以及之前的回答来给出准确的建议。 首先,用户提供的hive-site.xml中有几个关键的property: 1. hive.metastore.uris的value是thrift://hw-cdh-1:9083,这应该是Hive Metastore的服务地址。用户需要确定在Iceberg的CREATE CATALOG语句中,uri参数是否应该使用这个具体的地址(hw-cdh-1)还是示例中的metastore-host。这里需要指出,实际配置应替换为真实的主机名或IP,但用户示例中的hw-cdh-1可能就是他们实际的主机名,因此应直接使用。 2. hive.metastore.warehouse.dir的value是/user/hive/warehouse,这是一个HDFS路径,但用户示例中给出的warehouse路径是hdfs://nn:8020/warehouse/path,而用户现在想确认应该使用hdfs://namenode:8020/user/hive/warehouse还是直接使用/user/hive/warehouse。这里需要说明,Hive的warehouse.dir配置可能已经包含了HDFS的协议和地址,但有时候也可能只是路径部分,需要根据实际情况补全完整的HDFS URL。例如,如果Hive的配置是相对路径,那么Iceberg中的warehouse参数需要明确指定HDFS的namenode地址和端口,如hdfs://hw-cdh-1:8020/user/hive/warehouse,但用户提供的hive-site.xml中的warehouse.dir是/user/hive/warehouse,可能没有指定HDFS的地址,因此需要结合集群的实际HDFS配置来确定完整的路径。 接下来,用户的问题涉及URI格式和仓库路径的正确配置。需要解释thrift URI的格式,确认是否需要使用具体的主机名,以及仓库路径是否需要完整的HDFS URL。 此外,用户引用了之前的回答中的内容,特别是关于hive.metastore.uris和warehouse的映射。之前的回答中示例使用了thrift://metastore-host:9083和hdfs://namenode:8020/user/hive/warehouse,但用户的实际配置是thrift://hw-cdh-1:9083和/user/hive/warehouse。需要指出用户需要将示例中的占位符(如metastore-host、namenode)替换为他们实际的环境配置,比如hw-cdh-1可能是他们的Metastore主机名,而HDFS的namenode地址可能是hdfs://hw-cdh-1:8020或其他地址。 另外,用户提到的引用[3]和[4]涉及到Hive Metastore的性能问题,但当前问题主要关注URI和路径配置,这部分可能暂时不需要处理,但需要注意在配置正确的情况下,避免因为路径或URI错误导致的连接问题,进而引发性能问题或元数据不一致。 总结用户的需求:确认在Iceberg的Hive Catalog配置中,uri参数应该使用thrift://hw-cdh-1:9083还是其他形式,以及warehouse路径是否需要完整的HDFS URL还是可以相对路径。需要结合用户的hive-site.xml配置和实际集群环境来给出建议,同时参考之前的回答,确保配置正确,避免常见的连接失败或路径权限问题。</think>--- ### **关键配置解析** #### **1. Thrift URI 配置** 在 `hive-site.xml` 中,`hive.metastore.uris` 的值为 `thrift://hw-cdh-1:9083`,这是 **Hive Metastore 的实际服务地址**。在 Iceberg Hive Catalog 的 `CREATE CATALOG` 语句中,`uri` 参数应与此保持一致: ```sql CREATE CATALOG hive_catalog WITH ( 'type'='iceberg', 'catalog-type'='hive', 'uri'='thrift://hw-cdh-1:9083', -- 直接使用实际的主机名 hw-cdh-1 ... ); ``` - **`thrift://metastore-host:9083` 是示例占位符**,需替换为实际地址[^1][^3] - 若 `hive.metastore.uris` 包含多个地址(如高可用场景),需用逗号分隔,例如:`thrift://node1:9083,thrift://node2:9083` --- #### **2. Hive Warehouse 路径配置** `hive.metastore.warehouse.dir` 的值为 `/user/hive/warehouse`,但需根据 HDFS 实际地址补充 **完整的 URI 协议和 NameNode 信息**: ```sql CREATE CATALOG hive_catalog WITH ( ... 'warehouse'='hdfs://hw-cdh-1:8020/user/hive/warehouse', -- 补全 HDFS 协议和 NameNode 地址 ... ); ``` - **`hdfs://namenode:8020` 是示例占位符**,需替换为实际 NameNode 地址(例如 `hdfs://hw-cdh-1:8020`) - **路径格式对比**: | 格式 | 示例 | 适用场景 | |-----|-----|---------| | 完整 HDFS URI | `hdfs://hw-cdh-1:8020/user/hive/warehouse` | 跨集群或非默认文件系统 | | 相对路径 | `/user/hive/warehouse` | 仅当 Hadoop 配置已指定默认文件系统(如 `fs.defaultFS`)时可用 | --- ### **验证配置正确性** 1. **检查 Metastore 连通性**: ```bash telnet hw-cdh-1 9083 # 确认 Thrift 端口可达 ``` 2. **验证 HDFS 路径权限**: ```bash hadoop fs -ls hdfs://hw-cdh-1:8020/user/hive/warehouse ``` 3. **测试 Iceberg 表创建**: ```sql USE CATALOG hive_catalog; CREATE TABLE test_db.sample (id INT) WITH ('format-version'='2'); -- 检查表路径是否生成在正确位置 DESCRIBE EXTENDED test_db.sample; ``` --- ### **常见配置错误与修复** | 错误场景 | 表现 | 解决方案 | |---------|------|----------| | URI 主机名错误 | `Connection refused` 或超时 | 检查 `hive.metastore.uris` 与实际服务地址是否一致[^3] | | HDFS 路径权限不足 | `Permission denied` | 执行 `hadoop fs -chmod` 调整权限 | | 未指定完整 HDFS URI | 表路径指向本地文件系统 | 在 `warehouse` 参数中补全 `hdfs://namenode:port` 前缀 | --- ### **总结配置模板** 根据您的 `hive-site.xml` 配置,**最终 Iceberg Catalog 定义应为**: ```sql CREATE CATALOG hive_catalog WITH ( 'type'='iceberg', 'catalog-type'='hive', 'uri'='thrift://hw-cdh-1:9083', -- 来自 hive.metastore.uris 'warehouse'='hdfs://hw-cdh-1:8020/user/hive/warehouse', -- 补全 HDFS 地址和端口 'clients'='5', 'property-version'='1' ); ``` --- ### 相关问题 1. 如何诊断 Iceberg 与 Hive Metastore 的连接超时问题? 2. Iceberg 表存储路径与 Hive 默认路径不一致时如何处理? 3. 如何配置 Iceberg 使用 Kerberos 认证的 Hive Metastore?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值