赞
踩
HDFS有着一套十分成熟的HA的机制来保证其服务的高可用性。在HA模式下,分别对应有Active和Standby NameNode的服务。Active NameNode用于提供对外数据服务,而Standby NameNode则负责做checkpoint的工作以及随时准备接替变成Active NameNode的角色,假设说当前Active NameNode意外不可用的情况发生的话。其实,Standby NameNode日常的工作并不多,除了定期checkpoint和准实时地同步元数据信息外,它并不处理来自外部client发起的读写请求,所以Standby NameNode服务的一个负载是比较低的。当Active NameNode的服务压力越来越大的时候,那么是否我们可以让Standby的NameNode去分流一部分的读压力呢?Hadoop社区在很早的时候就已经提出过此设想并且实现了这个功能。本文笔者将结合部分代码来简单分析分析HDFS Standby Read的实现原理。
首先我们来说说HDFS Standby Read的背景及功能要求。在Active NameNode随着集群规模的不断扩张下,其服务压力将会越来越大的。对于这种情况下,我们一般的做法是通过组建HDFS Federation的方式来达到服务横向扩展的目标。但是这种方式并没有在NameNode本身的服务能力上做更进一步的挖掘优化,而HDFS Standby Read的功能就是在这块的一个大大的补强。
在Standby Read模式下,HDFS原有的写请求依然是被Active NameNode所处理。Standby服务只是支持了读操作的处理,所以这里不会涉及到NameNode主要逻辑上的大改。不过这里面最需要解决的问题是,Standby一致性读的问题。
我们知道Standby NameNode是通过读取JournalNode上的editlog,从而进行transaction的同步的。在Active NameNode写editlog到出去,再到Standby NameNode去读这这批editlog,中间是存在时间gap的。所以在实现Standby Read功能时,我们并不是简简单单地把读请求直接转向Standby NN就完事了,这里会涉及到transaction的等待同步问题。下面笔者会详细介绍这块社区是怎么做的。
鉴于上小节提到的Standby NameNode状态同步的问题,需要Standby NameNode达到client最近一次的txid后,才能允许其处理client的读请求操作。
上面这句话什么意思呢?对于client而言,在它发起RPC请求时,Active NameNode和Standby NameNode各自有自身当前的txid,且Active NameNode的txid肯定要大于Standby的txid。这里我们标记Active txid为ann txid,Standby为 snn txid。如果这时,client发起后续请求到Active服务,那没有什么数据延时的问题,Active一直都是最新的状态。但是假设我们想让Standby NameNode也能够处理client的请求,那么它至少得达到刚刚client发起RPC时刻起时Active NameNode的txid的状态,即snn txid也达到ann txid值。
此部分过程简单阐述如下所示:
1)Client发起RPC请求前获取到当前Active NameNode的txid值,这里我们叫做lastSeenTxid。
2)随后Client发起读请求到Standby NameNode,在此请求中会带上上步骤的lastSeenTxid的值。
3)Standby NameNode在处理上步骤的RPC请求时,会比较自身当前的txid是否已经达到client的lastSeenTxid值,如果已经达到,则正常处理这个请求,否则将请求重新插入RPC callqueu等待下次被处理。这里的请求重新进queue的操作对于client来说,意味着这个RPC call还没有处理结束。
为了避免Client可能出现长时间等待Standby NameNode达到lastSeenTxid状态的情况,社区在Standby NameNode editlog的同步上做了一部分改进,包括支持editlog_inprogress里的transaction读取以及editlog信息的内存读取等等。
后面笔者来结合实际代码,来对应分析上面的过程。
社区在实现的过程里定义了2个类来存放Client和Server端自身能够“看到”的txid值。
Client对应的类叫做ClientGSIContext,Server端(即NameNode)的叫做GlobalStateIdContext。
我们先来说说Client端的这个类,ClientGSIContext。ClientGSIContext类内部维护有lastSeenStateId这个值,代码如下所示:
/**
* Global State Id context for the client.
* <p>
* This is the client side implementation responsible for receiving
* state alignment info from server(s).
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class ClientGSIContext implements AlignmentContext {
private final LongAccumulator lastSeenStateId =
new LongAccumulator(Math::max, Long.MIN_VALUE);
...
}
lastSeenStateId这个值的更新和获取主要发生在接收到RPC response阶段(更新当前的lastSeenStateId值)和RPC请求发送(设置当前的lastSeenStateId值)的时候,代码如下,还是在这个类的逻辑里。
/** * Client接收到请求response时更新当前的lastSeenStateId值。 */ @Override public void receiveResponseState(RpcResponseHeaderProto header) { lastSeenStateId.accumulate(header.getStateId()); } /** * Client发起请求时设置当前的lastSeenStateId值信息到RPC请求里。 */ @Override public void updateRequestState(RpcRequestHeaderProto.Builder header) { header.setStateId(lastSeenStateId.longValue()); }
然后我们再来看Server端的GlobalStateIdContext是怎么处理的。首先是GlobalStateIdContext的类定义:
/** * This is the server side implementation responsible for passing * state alignment info to clients. */ @InterfaceAudience.Private @InterfaceStability.Evolving class GlobalStateIdContext implements AlignmentContext { /** * Estimated number of journal transactions a typical NameNode can execute * per second. The number is used to estimate how long a client's * RPC request will wait in the call queue before the Observer catches up * with its state id. */ private static final long ESTIMATED_TRANSACTIONS_PER_SECOND = 10000L; /** * The client wait time on an RPC request is composed of * the server execution time plus the com
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。