Skip to content

Commit 7a85170

Browse files
committed
Client and Server AlignmentContext
1 parent 2702db5 commit 7a85170

8 files changed

Lines changed: 281 additions & 11 deletions

File tree

‎hadoop-hdds/common/src/main/java/org/apache/hadoop/ipc_/AlignmentContext.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.io.IOException;
2222

23+
import com.google.protobuf.Message;
2324
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcRequestHeaderProto;
2425
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcResponseHeaderProto;
2526

@@ -86,7 +87,8 @@ long receiveRequestState(RpcRequestHeaderProto header, long threshold)
8687
*
8788
* @param protocolName the name of the protocol
8889
* @param method the method call to check
90+
* @param payload the payload to check
8991
* @return true if this method is async, false otherwise.
9092
*/
91-
boolean isCoordinatedCall(String protocolName, String method);
93+
boolean isCoordinatedCall(String protocolName, String method, Message payload);
9294
}

‎hadoop-hdds/common/src/main/java/org/apache/hadoop/ipc_/ProtobufRpcEngine.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,10 @@ RequestHeaderProto getRequestHeader() throws IOException {
582582
return requestHeader;
583583
}
584584

585+
Message getPayload() throws IOException {
586+
return payload;
587+
}
588+
585589
@Override
586590
public void writeTo(ResponseBuffer out) throws IOException {
587591
requestHeader.writeDelimitedTo(out);

‎hadoop-hdds/common/src/main/java/org/apache/hadoop/ipc_/Server.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2699,16 +2699,18 @@ private void processRpcRequest(RpcRequestHeaderProto header,
26992699
(call.rpcRequest instanceof ProtobufRpcEngine.RpcProtobufRequest)) {
27002700
// if call.rpcRequest is not RpcProtobufRequest, will skip the following
27012701
// step and treat the call as uncoordinated. As currently only certain
2702-
// ClientProtocol methods request made through RPC protobuf needs to be
2702+
// OzoneManagerProtocol methods request made through RPC protobuf needs to be
27032703
// coordinated.
27042704
String methodName;
27052705
String protoName;
27062706
ProtobufRpcEngine.RpcProtobufRequest req =
27072707
(ProtobufRpcEngine.RpcProtobufRequest) call.rpcRequest;
2708+
Message payload;
27082709
try {
27092710
methodName = req.getRequestHeader().getMethodName();
27102711
protoName = req.getRequestHeader().getDeclaringClassProtocolName();
2711-
if (alignmentContext.isCoordinatedCall(protoName, methodName)) {
2712+
payload = req.getPayload();
2713+
if (alignmentContext.isCoordinatedCall(protoName, methodName, payload)) {
27122714
call.markCallCoordinated(true);
27132715
long stateId;
27142716
stateId = alignmentContext.receiveRequestState(

‎hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public final class OMConfigKeys {
5151
public static final int OZONE_OM_DB_MAX_OPEN_FILES_DEFAULT
5252
= -1;
5353

54+
public static final String OZONE_OM_STATE_CONTEXT_ENABLED_KEY =
55+
"ozone.om.state.context.enabled";
56+
public static final boolean OZONE_OM_STATE_CONTEXT_ENABLED_DEFAULT = false;
57+
5458
public static final String OZONE_OM_INTERNAL_SERVICE_ID =
5559
"ozone.om.internal.service.id";
5660

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.apache.hadoop.ozone.om.ha;
2+
3+
import org.apache.hadoop.ipc.AlignmentContext;
4+
import org.apache.hadoop.ipc.protobuf.RpcHeaderProtos.RpcRequestHeaderProto;
5+
import org.apache.hadoop.ipc.protobuf.RpcHeaderProtos.RpcResponseHeaderProto;
6+
import java.io.IOException;
7+
import java.util.concurrent.atomic.LongAccumulator;
8+
9+
/**
10+
* Global State Id context for the client.
11+
* <p>
12+
* This is the client side implementation responsible for receiving
13+
* state alignment info from server(s).
14+
*/
15+
public class ClientAlignmentContext implements AlignmentContext {
16+
17+
private final LongAccumulator lastSeenStateId;
18+
19+
public ClientAlignmentContext(LongAccumulator lastSeenStateId) {
20+
this.lastSeenStateId = lastSeenStateId;
21+
}
22+
23+
@Override
24+
public long getLastSeenStateId() {
25+
return lastSeenStateId.get();
26+
}
27+
28+
@Override
29+
public boolean isCoordinatedCall(String protocolName, String method) {
30+
throw new UnsupportedOperationException(
31+
"Client should not be checking uncoordinated call");
32+
}
33+
34+
/**
35+
* Client side implementation only receives state alignment info.
36+
* It does not provide state alignment info therefore this does nothing.
37+
*/
38+
@Override
39+
public void updateResponseState(RpcResponseHeaderProto.Builder header) {
40+
// Do nothing.
41+
}
42+
43+
/**
44+
* Client side implementation for receiving state alignment info
45+
* in responses.
46+
*/
47+
@Override
48+
public synchronized void receiveResponseState(RpcResponseHeaderProto header) {
49+
lastSeenStateId.accumulate(header.getStateId());
50+
}
51+
52+
/**
53+
* Client side implementation for providing state alignment info in requests.
54+
*/
55+
@Override
56+
public synchronized void updateRequestState(RpcRequestHeaderProto.Builder header) {
57+
if (lastSeenStateId.get() != Long.MIN_VALUE) {
58+
header.setStateId(lastSeenStateId.get());
59+
}
60+
}
61+
62+
/**
63+
* Client side implementation only provides state alignment info in requests.
64+
* Client does not receive RPC requests therefore this does nothing.
65+
*/
66+
@Override
67+
public long receiveRequestState(RpcRequestHeaderProto header, long threshold)
68+
throws IOException {
69+
// Do nothing.
70+
return 0;
71+
}
72+
}

‎hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/ha/OMFailoverProxyProviderBase.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ protected T createOMProxy(InetSocketAddress omAddress) throws IOException {
146146
NetUtils.getDefaultSocketFactory(hadoopConf),
147147
(int) OmUtils.getOMClientRpcTimeOut(getConf()),
148148
connectionRetryPolicy
149+
// TODO: Implement and add alignment context here
149150
).getProxy();
150151
}
151152

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package org.apache.hadoop.ozone.om;
2+
3+
import com.google.protobuf.Message;
4+
import org.apache.hadoop.ipc_.AlignmentContext;
5+
import org.apache.hadoop.ipc_.RetriableException;
6+
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcRequestHeaderProto;
7+
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcResponseHeaderProto;
8+
import org.apache.hadoop.ozone.OmUtils;
9+
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
10+
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
11+
import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
import java.io.IOException;
15+
import java.util.concurrent.TimeUnit;
16+
17+
/**
18+
* This is the server side implementation responsible for passing
19+
* state alignment info to clients.
20+
* </p>
21+
* Unlike HDFS's ClientNamenodeProtocol that has a RPC method for each
22+
* distinct call, OM (OzoneManagerService) only contains a single RPC method
23+
* (i.e. submitRequest(OMRequest)). Therefore, we need to query the OMRequest
24+
* to get the OMRequest parameter from the RPC method get the corresponding cmdType
25+
* to check whether we can the request to be run on non-leader OMs.
26+
*/
27+
public class OmAlignmentContext implements AlignmentContext {
28+
29+
private static final Logger LOG =
30+
LoggerFactory.getLogger(OmAlignmentContext.class);
31+
/**
32+
* Estimated number of journal transactions a typical OM can execute
33+
* per second. The number is used to estimate how long a client's
34+
* RPC request will wait in the call queue before the Observer catches up
35+
* with its state id.
36+
*/
37+
private static final long ESTIMATED_TRANSACTIONS_PER_SECOND = 10000L;
38+
39+
/**
40+
* The client wait time on an RPC request is composed of
41+
* the server execution time plus the communication time.
42+
* This is an expected fraction of the total wait time spent on
43+
* server execution.
44+
*/
45+
private static final float ESTIMATED_SERVER_TIME_MULTIPLIER = 0.8f;
46+
47+
private final OzoneManager ozoneManager;
48+
49+
/**
50+
* Server side constructor.
51+
* @param ozoneManager server side state provider
52+
*/
53+
OmAlignmentContext(OzoneManager ozoneManager) {
54+
this.ozoneManager = ozoneManager;
55+
}
56+
57+
/**
58+
* Server side implementation for providing state alignment info in responses.
59+
*/
60+
@Override
61+
public void updateResponseState(RpcResponseHeaderProto.Builder header) {
62+
// Using getCorrectLastAppliedOrWrittenTxId will acquire the lock on
63+
// FSEditLog. This is needed so that ANN will return the correct state id
64+
// it currently has. But this may not be necessary for Observer, may want
65+
// revisit for optimization. Same goes to receiveRequestState.
66+
header.setStateId(getLastSeenStateId());
67+
}
68+
69+
/**
70+
* Server side implementation only provides state alignment info.
71+
* It does not receive state alignment info therefore this does nothing.
72+
*/
73+
@Override
74+
public void receiveResponseState(RpcResponseHeaderProto header) {
75+
// Do nothing.
76+
}
77+
78+
/**
79+
* Server side implementation only receives state alignment info.
80+
* It does not build RPC requests therefore this does nothing.
81+
*/
82+
@Override
83+
public void updateRequestState(RpcRequestHeaderProto.Builder header) {
84+
// Do nothing.
85+
}
86+
87+
/**
88+
* Server-side implementation for processing state alignment info in
89+
* requests.
90+
* For Follower/Listener it compares the client and the server states and determines
91+
* if it makes sense to wait until the server catches up with the client
92+
* state. If not the server throws RetriableException so that the client
93+
* could retry the call according to the retry policy with another Follower/Listener
94+
* or the Leader.
95+
*
96+
* @param header The RPC request header.
97+
* @param clientWaitTime time in milliseconds indicating how long client
98+
* waits for the server response. It is used to verify if the client's
99+
* state is too far ahead of the server's
100+
* @return the minimum of the state ids of the client or the server.
101+
* @throws RetriableException if Observer is too far behind.
102+
*/
103+
@Override
104+
public long receiveRequestState(RpcRequestHeaderProto header,
105+
long clientWaitTime) throws IOException {
106+
RaftPeerRole selfRole;
107+
if (ozoneManager.getOmRatisServer() == null) {
108+
selfRole = RaftPeerRole.LEADER;
109+
} else {
110+
selfRole = ozoneManager.getSelfRole(ozoneManager.getOmRatisServer().getLeaderId());
111+
}
112+
113+
if (!header.hasStateId() && !RaftPeerRole.LEADER.equals(selfRole)) {
114+
// This could happen if client configured with non-follower proxy provider
115+
// (e.g., ConfiguredFailoverProxyProvider) is accessing a cluster with.
116+
// In this case, we should let the client failover to the
117+
// leader node, rather than potentially serving stale result (client
118+
// stateId is 0 if not set).
119+
throw new IOException("Node received request without "
120+
+ "stateId. This mostly likely is because client is not configured "
121+
+ "with ObserverReadProxyProvider");
122+
}
123+
long serverStateId = getLastSeenStateId();
124+
long clientStateId = header.getStateId();
125+
LOG.trace("Client State ID= {} and Server State ID= {}",
126+
clientStateId, serverStateId);
127+
128+
if (clientStateId > serverStateId &&
129+
RaftPeerRole.LEADER.equals(selfRole)) {
130+
LOG.warn("The client stateId: {} is greater than "
131+
+ "the server stateId: {} This is unexpected. "
132+
+ "Resetting client stateId to server stateId",
133+
clientStateId, serverStateId);
134+
return serverStateId;
135+
}
136+
if ((RaftPeerRole.FOLLOWER.equals(selfRole) || RaftPeerRole.LISTENER.equals(selfRole)) &&
137+
clientStateId - serverStateId >
138+
ESTIMATED_TRANSACTIONS_PER_SECOND
139+
* TimeUnit.MILLISECONDS.toSeconds(clientWaitTime)
140+
* ESTIMATED_SERVER_TIME_MULTIPLIER) {
141+
throw new RetriableException(
142+
"Follower / Listener Node is too far behind: serverStateId = "
143+
+ serverStateId + " clientStateId = " + clientStateId);
144+
}
145+
return clientStateId;
146+
}
147+
148+
@Override
149+
public long getLastSeenStateId() {
150+
return ozoneManager.getOmRatisServer().getLastAppliedTermIndex().getIndex();
151+
}
152+
153+
@Override
154+
public boolean isCoordinatedCall(String protocolName, String methodName, Message payload) {
155+
// See OzoneManagerProtocolPB @ProtocolInfo annotation for the protocol name
156+
if (!protocolName.equals(OzoneManagerProtocol.class.getCanonicalName()) ||
157+
!(methodName.equals("submitRequest")) ||
158+
!(payload instanceof OMRequest)) {
159+
return false;
160+
}
161+
162+
OMRequest omRequest = (OMRequest) payload;
163+
164+
return OmUtils.isReadOnly(omRequest);
165+
}
166+
167+
}

‎hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_READ_THREADPOOL_KEY;
7878
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_S3_GPRC_SERVER_ENABLED;
7979
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_S3_GRPC_SERVER_ENABLED_DEFAULT;
80+
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_STATE_CONTEXT_ENABLED_DEFAULT;
81+
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_STATE_CONTEXT_ENABLED_KEY;
8082
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SERVER_DEFAULT_REPLICATION_DEFAULT;
8183
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SERVER_DEFAULT_REPLICATION_KEY;
8284
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SERVER_DEFAULT_REPLICATION_TYPE_DEFAULT;
@@ -215,6 +217,7 @@
215217
import org.apache.hadoop.ipc_.ProtobufRpcEngine;
216218
import org.apache.hadoop.ipc_.RPC;
217219
import org.apache.hadoop.ipc_.Server;
220+
import org.apache.hadoop.ipc_.Server.Call;
218221
import org.apache.hadoop.metrics2.util.MBeans;
219222
import org.apache.hadoop.ozone.OmUtils;
220223
import org.apache.hadoop.ozone.OzoneAcl;
@@ -1445,6 +1448,16 @@ private RPC.Server startRpcServer(OzoneConfiguration conf,
14451448
final int readThreads = conf.getInt(OZONE_OM_READ_THREADPOOL_KEY,
14461449
OZONE_OM_READ_THREADPOOL_DEFAULT);
14471450

1451+
boolean enableStateContext = conf.getBoolean(
1452+
OZONE_OM_STATE_CONTEXT_ENABLED_KEY,
1453+
OZONE_OM_STATE_CONTEXT_ENABLED_DEFAULT);
1454+
LOG.info("Enable OzoneManager state context:" + enableStateContext);
1455+
1456+
OmAlignmentContext stateIdContext = null;
1457+
if (enableStateContext) {
1458+
stateIdContext = new OmAlignmentContext(this);
1459+
}
1460+
14481461
RPC.Server rpcServer = new RPC.Builder(conf)
14491462
.setProtocol(OzoneManagerProtocolPB.class)
14501463
.setInstance(clientProtocolService)
@@ -1454,6 +1467,7 @@ private RPC.Server startRpcServer(OzoneConfiguration conf,
14541467
.setNumReaders(readThreads)
14551468
.setVerbose(false)
14561469
.setSecretManager(delegationTokenMgr)
1470+
.setAlignmentContext(stateIdContext)
14571471
.build();
14581472

14591473
HddsServerUtil.addPBProtocol(conf, OMInterServiceProtocolPB.class,
@@ -3292,14 +3306,7 @@ public List<ServiceInfo> getServiceList() throws IOException {
32923306
selfRole = RaftPeerRole.LEADER;
32933307
} else {
32943308
leaderId = omRatisServer.getLeaderId();
3295-
RaftPeerId selfPeerId = omRatisServer.getRaftPeerId();
3296-
if (leaderId != null && leaderId.equals(selfPeerId)) {
3297-
selfRole = RaftPeerRole.LEADER;
3298-
} else if (omNodeDetails.isRatisListener()) {
3299-
selfRole = RaftPeerRole.LISTENER;
3300-
} else {
3301-
selfRole = RaftPeerRole.FOLLOWER;
3302-
}
3309+
selfRole = getSelfRole(leaderId);
33033310
}
33043311
OMRoleInfo omRole = OMRoleInfo.newBuilder()
33053312
.setNodeId(getOMNodeId())
@@ -3373,6 +3380,17 @@ public List<ServiceInfo> getServiceList() throws IOException {
33733380
return services;
33743381
}
33753382

3383+
public RaftPeerRole getSelfRole(RaftPeerId leaderId) {
3384+
RaftPeerId selfPeerId = omRatisServer.getRaftPeerId();
3385+
if (leaderId != null && leaderId.equals(selfPeerId)) {
3386+
return RaftPeerRole.LEADER;
3387+
} else if (omNodeDetails.isRatisListener()) {
3388+
return RaftPeerRole.LISTENER;
3389+
} else {
3390+
return RaftPeerRole.FOLLOWER;
3391+
}
3392+
}
3393+
33763394
@Override
33773395
public ServiceInfoEx getServiceInfo() throws IOException {
33783396
return serviceInfo.provide();

0 commit comments

Comments
 (0)