-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract AtomicRegisterPreVoteCollector and StoreHeartbeatService to p…
…roduction (#95296)
- Loading branch information
Showing
12 changed files
with
746 additions
and
153 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
...java/org/elasticsearch/cluster/coordination/stateless/AtomicRegisterPreVoteCollector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
package org.elasticsearch.cluster.coordination.stateless; | ||
|
||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.coordination.PreVoteCollector; | ||
import org.elasticsearch.cluster.node.DiscoveryNode; | ||
import org.elasticsearch.core.Releasable; | ||
|
||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
public class AtomicRegisterPreVoteCollector extends PreVoteCollector { | ||
private final StoreHeartbeatService heartbeatService; | ||
private final Runnable startElection; | ||
|
||
public AtomicRegisterPreVoteCollector(StoreHeartbeatService heartbeatService, Runnable startElection) { | ||
this.heartbeatService = heartbeatService; | ||
this.startElection = startElection; | ||
} | ||
|
||
@Override | ||
public Releasable start(ClusterState clusterState, Iterable<DiscoveryNode> broadcastNodes) { | ||
final var shouldRun = new AtomicBoolean(true); | ||
heartbeatService.runIfNoRecentLeader(() -> { | ||
if (shouldRun.getAndSet(false)) { | ||
startElection.run(); | ||
} | ||
}); | ||
|
||
return () -> shouldRun.set(false); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
server/src/main/java/org/elasticsearch/cluster/coordination/stateless/Heartbeat.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
package org.elasticsearch.cluster.coordination.stateless; | ||
|
||
import org.elasticsearch.cluster.node.DiscoveryNode; | ||
|
||
public record Heartbeat(DiscoveryNode leader, long term, long absoluteTimeInMillis) { | ||
long timeSinceLastHeartbeatInMillis(long nowInMillis) { | ||
return nowInMillis - absoluteTimeInMillis; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
server/src/main/java/org/elasticsearch/cluster/coordination/stateless/HeartbeatStore.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
package org.elasticsearch.cluster.coordination.stateless; | ||
|
||
import org.elasticsearch.action.ActionListener; | ||
|
||
public interface HeartbeatStore { | ||
void writeHeartbeat(Heartbeat newHeartbeat, ActionListener<Void> listener); | ||
|
||
void readLatestHeartbeat(ActionListener<Heartbeat> listener); | ||
} |
130 changes: 130 additions & 0 deletions
130
...src/main/java/org/elasticsearch/cluster/coordination/stateless/StoreHeartbeatService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
package org.elasticsearch.cluster.coordination.stateless; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.ActionRunnable; | ||
import org.elasticsearch.cluster.coordination.LeaderHeartbeatService; | ||
import org.elasticsearch.cluster.node.DiscoveryNode; | ||
import org.elasticsearch.common.settings.Setting; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.threadpool.ThreadPool; | ||
|
||
import java.util.function.LongSupplier; | ||
|
||
public class StoreHeartbeatService implements LeaderHeartbeatService { | ||
public static final Setting<TimeValue> HEARTBEAT_FREQUENCY = Setting.timeSetting( | ||
"heartbeat_frequency", | ||
TimeValue.timeValueSeconds(15), | ||
Setting.Property.NodeScope | ||
); | ||
|
||
public static final Setting<Integer> MAX_MISSED_HEARTBEATS = Setting.intSetting( | ||
"max_missed_heartbeats", | ||
2, | ||
1, | ||
Setting.Property.NodeScope | ||
); | ||
|
||
private static final Logger logger = LogManager.getLogger(StoreHeartbeatService.class); | ||
|
||
private final HeartbeatStore heartbeatStore; | ||
private final ThreadPool threadPool; | ||
private final TimeValue heartbeatFrequency; | ||
private final TimeValue maxTimeSinceLastHeartbeat; | ||
private final LongSupplier currentTermSupplier; | ||
|
||
private volatile HeartbeatTask heartbeatTask; | ||
|
||
public StoreHeartbeatService( | ||
HeartbeatStore heartbeatStore, | ||
ThreadPool threadPool, | ||
TimeValue heartbeatFrequency, | ||
TimeValue maxTimeSinceLastHeartbeat, | ||
LongSupplier currentTermSupplier | ||
) { | ||
this.heartbeatStore = heartbeatStore; | ||
this.threadPool = threadPool; | ||
this.heartbeatFrequency = heartbeatFrequency; | ||
this.maxTimeSinceLastHeartbeat = maxTimeSinceLastHeartbeat; | ||
this.currentTermSupplier = currentTermSupplier; | ||
} | ||
|
||
@Override | ||
public void start(DiscoveryNode currentLeader, long term, ActionListener<Long> completionListener) { | ||
final var newHeartbeatTask = new HeartbeatTask(currentLeader, term, completionListener); | ||
heartbeatTask = newHeartbeatTask; | ||
newHeartbeatTask.run(); | ||
} | ||
|
||
@Override | ||
public void stop() { | ||
heartbeatTask = null; | ||
} | ||
|
||
protected long absoluteTimeInMillis() { | ||
return threadPool.absoluteTimeInMillis(); | ||
} | ||
|
||
void runIfNoRecentLeader(Runnable runnable) { | ||
heartbeatStore.readLatestHeartbeat(new ActionListener<>() { | ||
@Override | ||
public void onResponse(Heartbeat heartBeat) { | ||
if (heartBeat == null | ||
|| maxTimeSinceLastHeartbeat.millis() <= heartBeat.timeSinceLastHeartbeatInMillis(absoluteTimeInMillis())) { | ||
runnable.run(); | ||
} else { | ||
logger.trace("runIfNoRecentLeader: found recent leader"); | ||
} | ||
} | ||
|
||
@Override | ||
public void onFailure(Exception e) { | ||
logger.trace("runIfNoRecentLeader: readLatestHeartbeat failed", e); | ||
} | ||
}); | ||
} | ||
|
||
private class HeartbeatTask extends ActionRunnable<Long> { | ||
private final DiscoveryNode currentLeader; | ||
private final long heartbeatTerm; | ||
private final ActionListener<Void> rerunListener; | ||
|
||
HeartbeatTask(DiscoveryNode currentLeader, long heartbeatTerm, ActionListener<Long> listener) { | ||
super(listener); | ||
this.currentLeader = currentLeader; | ||
this.heartbeatTerm = heartbeatTerm; | ||
this.rerunListener = listener.delegateFailure((l, v) -> { | ||
try { | ||
threadPool.schedule(HeartbeatTask.this, heartbeatFrequency, ThreadPool.Names.GENERIC); | ||
} catch (Exception e) { | ||
l.onFailure(e); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
protected void doRun() throws Exception { | ||
if (heartbeatTask != HeartbeatTask.this) { | ||
// already cancelled | ||
return; | ||
} | ||
|
||
final var registerTerm = currentTermSupplier.getAsLong(); | ||
if (registerTerm == heartbeatTerm) { | ||
heartbeatStore.writeHeartbeat(new Heartbeat(currentLeader, heartbeatTerm, absoluteTimeInMillis()), rerunListener); | ||
} else { | ||
assert heartbeatTerm < registerTerm; | ||
listener.onResponse(registerTerm); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.