-
Notifications
You must be signed in to change notification settings - Fork 636
fix(pd): add null-safety and static mapping for leader gRPC address discovery #3195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release-1.7.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ | |
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.CountDownLatch; | ||
|
|
@@ -71,6 +73,10 @@ public class RaftEngine { | |
| private RpcServer rpcServer; | ||
| private Node raftNode; | ||
| private RaftRpcClient raftRpcClient; | ||
| /** Cached leader gRPC address to avoid repeated Bolt RPC calls */ | ||
| private volatile String cachedLeaderGrpcAddress; | ||
| /** Static mapping from Raft endpoint to gRPC address, built from config at init */ | ||
| private Map<String, String> peerGrpcAddressMap = new HashMap<>(); | ||
|
|
||
| public RaftEngine() { | ||
| this.stateMachine = new RaftStateMachine(); | ||
|
|
@@ -86,6 +92,10 @@ public synchronized boolean init(PDConfig.Raft config) { | |
| } | ||
| this.config = config; | ||
|
|
||
| // Build static mapping: Raft endpoint → gRPC address from config | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 The arrow here is a non-ASCII character, and the only one in the file. Worth replacing with |
||
| // This avoids using the broken BoltRpcClient for address resolution | ||
| buildPeerGrpcAddressMap(); | ||
|
|
||
| raftRpcClient = new RaftRpcClient(); | ||
| raftRpcClient.init(new RpcOptions()); | ||
|
|
||
|
|
@@ -131,6 +141,15 @@ public synchronized boolean init(PDConfig.Raft config) { | |
| this.raftGroupService = | ||
| new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true); | ||
| this.raftNode = raftGroupService.start(false); | ||
|
|
||
| // Register listener to invalidate cached leader address on leader change | ||
| this.stateMachine.addStateListener(new RaftStateListener() { | ||
| @Override | ||
| public void onRaftLeaderChanged() { | ||
| cachedLeaderGrpcAddress = null; | ||
| log.info("Raft leader changed, invalidated cached leader gRPC address"); | ||
| } | ||
| }); | ||
|
Comment on lines
+146
to
+152
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Registration also happens after Requested change: add the fan-out to |
||
| log.info("RaftEngine start successfully: id = {}, peers list = {}", groupId, | ||
| nodeOptions.getInitialConf().getPeers()); | ||
| return this.raftNode != null; | ||
|
|
@@ -228,19 +247,57 @@ public PeerId getLeader() { | |
| } | ||
|
|
||
| /** | ||
| * Send a message to the leader to get the grpc address; | ||
| * Build static mapping from Raft endpoint to gRPC address. | ||
| * Assumes all PD nodes use the same grpc.port (standard deployment). | ||
| */ | ||
| private void buildPeerGrpcAddressMap() { | ||
| String grpcPort = String.valueOf(config.getGrpcPort()); | ||
| String[] peers = config.getPeersList().split(","); | ||
| for (String peer : peers) { | ||
| peer = peer.trim(); | ||
| if (peer.isEmpty()) { | ||
| continue; | ||
| } | ||
| Endpoint ep = JRaftUtils.getEndPoint(peer); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
final String[] tmps = StringUtils.split(s, ':');
if (tmps.length != 2) {
throw new IllegalArgumentException("Invalid endpoint string: " + s);
}
Requested change: wrap the per-entry conversion in a try/catch that logs and skips, so a best-effort address cache can never block startup. If you want full parity, |
||
| String grpcAddr = ep.getIp() + ":" + grpcPort; | ||
| peerGrpcAddressMap.put(ep.toString(), grpcAddr); | ||
|
Comment on lines
+254
to
+263
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Four co-located configurations in this repo violate it. On
Requested change: try the RPC first with |
||
| log.info("Mapped Raft endpoint {} -> gRPC {}", ep, grpcAddr); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get the leader's gRPC address. | ||
| * First tries the static config-based mapping (no Bolt RPC needed), | ||
| * then falls back to cached address, then Bolt RPC as last resort. | ||
| */ | ||
| public String getLeaderGrpcAddress() throws ExecutionException, InterruptedException { | ||
| if (isLeader()) { | ||
| return config.getGrpcAddress(); | ||
| } | ||
|
|
||
| // Try cached address first | ||
| String cached = this.cachedLeaderGrpcAddress; | ||
| if (cached != null) { | ||
| return cached; | ||
| } | ||
|
|
||
| if (raftNode.getLeaderId() == null) { | ||
| waitingForLeader(10000); | ||
| } | ||
|
|
||
| return raftRpcClient.getGrpcAddress(raftNode.getLeaderId().getEndpoint().toString()).get() | ||
| .getGrpcAddress(); | ||
| // Use static config-based mapping (avoids broken Bolt RPC) | ||
| String leaderEndpoint = raftNode.getLeaderId().getEndpoint().toString(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
#2961 guards both, at PeerId leader = raftNode.getLeaderId();
if (leader == null) {
throw new ExecutionException(new IllegalStateException("Leader is not ready"));
}and calls Requested change: port both guards here. The static map makes this path rarer but does not close it, and if the RPC is restored to first position it becomes the normal path again. Separately, and outside this diff so treat it as optional: |
||
| String address = peerGrpcAddressMap.get(leaderEndpoint); | ||
| if (address != null) { | ||
| this.cachedLeaderGrpcAddress = address; | ||
| return address; | ||
| } | ||
|
|
||
| // Fallback to Bolt RPC (may fail due to BoltRpcClient bug) | ||
| log.warn("Leader endpoint {} not in static map, falling back to Bolt RPC", leaderEndpoint); | ||
| address = raftRpcClient.getGrpcAddress(leaderEndpoint).get().getGrpcAddress(); | ||
| this.cachedLeaderGrpcAddress = address; | ||
| return address; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -269,8 +326,6 @@ public List<Metapb.Member> getMembers() throws ExecutionException, InterruptedEx | |
| for (PeerId peerId : peers) { | ||
| Metapb.Member.Builder builder = Metapb.Member.newBuilder(); | ||
| builder.setClusterId(config.getClusterId()); | ||
| CompletableFuture<RaftRpcProcessor.GetMemberResponse> future = | ||
| raftRpcClient.getGrpcAddress(peerId.getEndpoint().toString()); | ||
|
|
||
| Metapb.ShardRole role = Metapb.ShardRole.Follower; | ||
| if (PeerUtil.isPeerEquals(peerId, raftNode.getLeaderId())) { | ||
|
|
@@ -285,28 +340,44 @@ public List<Metapb.Member> getMembers() throws ExecutionException, InterruptedEx | |
|
|
||
| builder.setRole(role); | ||
|
|
||
| try { | ||
| if (future.isCompletedExceptionally()) { | ||
| log.error("failed to getGrpcAddress of {}", peerId.getEndpoint().toString()); | ||
| String endpointStr = peerId.getEndpoint().toString(); | ||
|
|
||
| // Use static config-based mapping first | ||
| String grpcAddr = peerGrpcAddressMap.get(endpointStr); | ||
| if (grpcAddr != null) { | ||
| builder.setState(Metapb.StoreState.Up); | ||
| builder.setRaftUrl(endpointStr); | ||
| builder.setGrpcUrl(grpcAddr); | ||
| String host = peerId.getIp(); | ||
| builder.setRestUrl(host + ":" + config.getPort()); | ||
| builder.setDataPath(config.getDataPath()); | ||
| members.add(builder.build()); | ||
|
Comment on lines
+346
to
+354
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Two smaller problems in the same branch. Requested change: keep a real reachability check behind the reported state, and fill |
||
| } else { | ||
| // Fallback to Bolt RPC | ||
| try { | ||
| CompletableFuture<RaftRpcProcessor.GetMemberResponse> future = | ||
| raftRpcClient.getGrpcAddress(endpointStr); | ||
| if (future.isCompletedExceptionally()) { | ||
| log.error("failed to getGrpcAddress of {}", endpointStr); | ||
| builder.setState(Metapb.StoreState.Offline); | ||
| builder.setRaftUrl(endpointStr); | ||
| members.add(builder.build()); | ||
| } else { | ||
| RaftRpcProcessor.GetMemberResponse response = future.get(); | ||
| builder.setState(Metapb.StoreState.Up); | ||
| builder.setRaftUrl(response.getRaftAddress()); | ||
| builder.setDataPath(response.getDatePath()); | ||
| builder.setGrpcUrl(response.getGrpcAddress()); | ||
| builder.setRestUrl(response.getRestAddress()); | ||
| members.add(builder.build()); | ||
| } | ||
| } catch (Exception e) { | ||
| log.error("failed to getGrpcAddress of {}.", endpointStr, e); | ||
| builder.setState(Metapb.StoreState.Offline); | ||
| builder.setRaftUrl(peerId.getEndpoint().toString()); | ||
| members.add(builder.build()); | ||
| } else { | ||
| RaftRpcProcessor.GetMemberResponse response = future.get(); | ||
| builder.setState(Metapb.StoreState.Up); | ||
| builder.setRaftUrl(response.getRaftAddress()); | ||
| builder.setDataPath(response.getDatePath()); | ||
| builder.setGrpcUrl(response.getGrpcAddress()); | ||
| builder.setRestUrl(response.getRestAddress()); | ||
| builder.setRaftUrl(endpointStr); | ||
| members.add(builder.build()); | ||
| } | ||
| } catch (Exception e) { | ||
| log.error("failed to getGrpcAddress of {}.", peerId.getEndpoint().toString(), e); | ||
| builder.setState(Metapb.StoreState.Offline); | ||
| builder.setRaftUrl(peerId.getEndpoint().toString()); | ||
| members.add(builder.build()); | ||
| } | ||
|
|
||
| } | ||
| return members; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ | |
| import io.grpc.ManagedChannel; | ||
| import io.grpc.ManagedChannelBuilder; | ||
| import io.grpc.MethodDescriptor; | ||
| import io.grpc.Status; | ||
|
|
||
| public interface ServiceGrpc extends RaftStateListener { | ||
|
|
||
|
|
@@ -90,6 +91,10 @@ default <ReqT, RespT> void redirectToLeader(ManagedChannel channel, | |
| observer); | ||
| } catch (Exception e) { | ||
| log.warn("redirect to leader with error:", e); | ||
| // Properly complete the observer to avoid hanging client requests | ||
| observer.onError(Status.UNAVAILABLE | ||
| .withDescription("Failed to redirect to leader: " + e.getMessage()) | ||
| .asRuntimeException()); | ||
|
Comment on lines
+94
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Completing the observer is the right call: before this, a redirect failure left the client waiting on its own deadline. The A failure before the call starts, which is the case you are fixing, is unaffected either way. Requested change: track a |
||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹
peerGrpcAddressMapis a plainHashMappopulated ininit()and then read without synchronization from gRPC and REST worker threads, ingetLeaderGrpcAddress()andgetMembers().init()issynchronizedbut the readers are not, so there is no happens-before edge and a reader can observe an empty or half-built map, which silently falls through to the RPC path this change sets out to avoid.Requested change:
private final Map<String, String> peerGrpcAddressMap = new ConcurrentHashMap<>();. Note that this also needsimport java.util.concurrent.ConcurrentHashMap;and the removal of the now-unusedimport java.util.HashMap;added at:25, sincestyle/checkstyle.xml:59enablesUnusedImports.