batch35: implement and verify feature group B
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
internal sealed partial class JetStream
|
||||||
|
{
|
||||||
|
internal ClusterInfo? OfflineClusterInfo(RaftGroup? group)
|
||||||
|
{
|
||||||
|
var server = Server as NatsServer;
|
||||||
|
if (server == null || group == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var clusterInfo = new ClusterInfo
|
||||||
|
{
|
||||||
|
Name = server.ClusterName(),
|
||||||
|
Replicas = group.Peers
|
||||||
|
.Select(peer =>
|
||||||
|
{
|
||||||
|
var info = server.GetNodeInfo(peer);
|
||||||
|
return new PeerInfo
|
||||||
|
{
|
||||||
|
Name = info?.Name ?? peer,
|
||||||
|
Current = false,
|
||||||
|
Offline = true,
|
||||||
|
Active = TimeSpan.Zero,
|
||||||
|
Lag = 0,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToArray(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return clusterInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ClusterInfo? ClusterInfo(RaftGroup? group)
|
||||||
|
{
|
||||||
|
var server = Server as NatsServer;
|
||||||
|
if (server == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (group?.Node == null)
|
||||||
|
{
|
||||||
|
return new ClusterInfo
|
||||||
|
{
|
||||||
|
Name = server.CachedClusterName(),
|
||||||
|
Leader = server.ServerName(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = group.Node;
|
||||||
|
var leaderNode = node.GroupLeader();
|
||||||
|
var peerInfos = new List<PeerInfo>();
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var ourId = node.ID();
|
||||||
|
|
||||||
|
foreach (var peer in node.Peers())
|
||||||
|
{
|
||||||
|
if (string.Equals(peer.Id, ourId, StringComparison.Ordinal))
|
||||||
|
continue;
|
||||||
|
if (!group.IsMember(peer.Id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var nodeInfo = server.GetNodeInfo(peer.Id);
|
||||||
|
var active = peer.Last == default || now <= peer.Last ? TimeSpan.Zero : now - peer.Last;
|
||||||
|
peerInfos.Add(new PeerInfo
|
||||||
|
{
|
||||||
|
Name = nodeInfo?.Name ?? peer.Id,
|
||||||
|
Current = peer.Current,
|
||||||
|
Offline = nodeInfo?.Offline ?? true,
|
||||||
|
Active = active,
|
||||||
|
Lag = peer.Lag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClusterInfo
|
||||||
|
{
|
||||||
|
Name = server.CachedClusterName(),
|
||||||
|
Leader = server.ServerNameForNode(leaderNode),
|
||||||
|
Replicas = peerInfos.OrderBy(r => r.Name, StringComparer.Ordinal).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -888,6 +888,87 @@ internal sealed class JetStreamEngine(JetStream state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal ClusterInfo? OfflineClusterInfo(RaftGroup? group)
|
||||||
|
{
|
||||||
|
if (group == null || _state.Server is not NatsServer server)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var replicas = new List<PeerInfo>(group.Peers.Length);
|
||||||
|
foreach (var peer in group.Peers)
|
||||||
|
{
|
||||||
|
var info = server.GetNodeInfo(peer);
|
||||||
|
replicas.Add(new PeerInfo
|
||||||
|
{
|
||||||
|
Name = info?.Name ?? peer,
|
||||||
|
Current = false,
|
||||||
|
Offline = true,
|
||||||
|
Active = TimeSpan.Zero,
|
||||||
|
Lag = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClusterInfo
|
||||||
|
{
|
||||||
|
Name = server.ClusterName(),
|
||||||
|
Replicas = replicas.ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ClusterInfo? ClusterInfo(RaftGroup? group)
|
||||||
|
{
|
||||||
|
if (_state.Server is not NatsServer server)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_state.Lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (group?.Node == null)
|
||||||
|
{
|
||||||
|
return new ClusterInfo
|
||||||
|
{
|
||||||
|
Name = server.CachedClusterName(),
|
||||||
|
Leader = server.ServerName(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = group.Node;
|
||||||
|
var leader = server.ServerNameForNode(node.GroupLeader());
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var self = node.ID();
|
||||||
|
var replicas = new List<PeerInfo>();
|
||||||
|
|
||||||
|
foreach (var peer in node.Peers())
|
||||||
|
{
|
||||||
|
if (string.Equals(peer.Id, self, StringComparison.Ordinal))
|
||||||
|
continue;
|
||||||
|
if (!group.IsMember(peer.Id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var info = server.GetNodeInfo(peer.Id);
|
||||||
|
var active = peer.Last == default || now <= peer.Last ? TimeSpan.Zero : now - peer.Last;
|
||||||
|
replicas.Add(new PeerInfo
|
||||||
|
{
|
||||||
|
Name = info?.Name ?? peer.Id,
|
||||||
|
Current = peer.Current,
|
||||||
|
Offline = info?.Offline ?? true,
|
||||||
|
Active = active,
|
||||||
|
Lag = peer.Lag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClusterInfo
|
||||||
|
{
|
||||||
|
Name = server.CachedClusterName(),
|
||||||
|
Leader = leader,
|
||||||
|
Replicas = replicas.OrderBy(r => r.Name, StringComparer.Ordinal).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_state.Lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal (byte[] Snapshot, int Streams, int Consumers, Exception? Error) MetaSnapshot()
|
internal (byte[] Snapshot, int Streams, int Consumers, Exception? Error) MetaSnapshot()
|
||||||
{
|
{
|
||||||
_state.Lock.EnterReadLock();
|
_state.Lock.EnterReadLock();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using IronSnappy;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server;
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
@@ -141,4 +142,309 @@ internal sealed partial class NatsStream
|
|||||||
_mu.ExitUpgradeableReadLock();
|
_mu.ExitUpgradeableReadLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal object? GetAndDeleteMsgTrace(ulong sequence)
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_msgTraceBySeq.TryGetValue(sequence, out var trace))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_msgTraceBySeq.Remove(sequence);
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal StreamSyncRequest? CalculateSyncRequest(StreamState? state, StreamReplicatedState? snapshot, ulong index)
|
||||||
|
{
|
||||||
|
if (state == null || snapshot == null || _node is not IRaftNode raftNode)
|
||||||
|
return null;
|
||||||
|
if (state.LastSeq >= snapshot.LastSeq)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new StreamSyncRequest
|
||||||
|
{
|
||||||
|
FirstSeq = state.LastSeq + 1,
|
||||||
|
LastSeq = snapshot.LastSeq,
|
||||||
|
Peer = raftNode.ID(),
|
||||||
|
DeleteRangesOk = true,
|
||||||
|
MinApplied = index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessSnapshotDeletes(StreamReplicatedState snapshot)
|
||||||
|
{
|
||||||
|
if (Store == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var state = new StreamState();
|
||||||
|
Store.FastState(state);
|
||||||
|
if (snapshot.FirstSeq > state.FirstSeq)
|
||||||
|
{
|
||||||
|
Store.Compact(snapshot.FirstSeq);
|
||||||
|
Store.FastState(state);
|
||||||
|
Interlocked.Exchange(ref LastSeq, (long)state.LastSeq);
|
||||||
|
ClearAllPreAcksBelowFloor(state.FirstSeq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.Deleted.Count > 0)
|
||||||
|
Store.SyncDeleted(snapshot.Deleted);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetCatchupPeer(string peer, ulong lag)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(peer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_catchupPeers ??= new Dictionary<string, ulong>(StringComparer.Ordinal);
|
||||||
|
_catchupPeers[peer] = lag;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void UpdateCatchupPeer(string peer) => DecrementCatchupPeer(peer, 1);
|
||||||
|
|
||||||
|
internal void DecrementCatchupPeer(string peer, ulong decrementBy)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(peer) || decrementBy == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_catchupPeers == null || !_catchupPeers.TryGetValue(peer, out var lag) || lag == 0)
|
||||||
|
return;
|
||||||
|
_catchupPeers[peer] = lag > decrementBy ? lag - decrementBy : 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearCatchupPeer(string peer)
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_catchupPeers?.Remove(peer);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearAllCatchupPeers()
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_catchupPeers = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ulong LagForCatchupPeer(string peer)
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_catchupPeers == null || !_catchupPeers.TryGetValue(peer, out var lag))
|
||||||
|
return 0;
|
||||||
|
return lag;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool HasCatchupPeers()
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _catchupPeers is { Count: > 0 };
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetCatchingUp() => Interlocked.Exchange(ref _catchingUp, 1);
|
||||||
|
|
||||||
|
internal void ClearCatchingUp() => Interlocked.Exchange(ref _catchingUp, 0);
|
||||||
|
|
||||||
|
internal bool IsCatchingUp() => Interlocked.CompareExchange(ref _catchingUp, 0, 0) == 1;
|
||||||
|
|
||||||
|
internal bool IsCurrent()
|
||||||
|
{
|
||||||
|
if (_node is not IRaftNode raftNode)
|
||||||
|
return true;
|
||||||
|
return raftNode.Current() && !IsCatchingUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Exception? ProcessSnapshot(StreamReplicatedState snapshot, ulong index)
|
||||||
|
{
|
||||||
|
ProcessSnapshotDeletes(snapshot);
|
||||||
|
SetCLFS(snapshot.Failed);
|
||||||
|
|
||||||
|
if (Store == null || _assignment == null || _node is not IRaftNode raftNode)
|
||||||
|
return new InvalidOperationException("stream has been stopped");
|
||||||
|
|
||||||
|
var state = new StreamState();
|
||||||
|
Store.FastState(state);
|
||||||
|
var syncRequest = CalculateSyncRequest(state, snapshot, index);
|
||||||
|
if (syncRequest == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
raftNode.PauseApply();
|
||||||
|
SetCatchingUp();
|
||||||
|
RunCatchup(string.Empty, syncRequest);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ClearCatchingUp();
|
||||||
|
raftNode.ResumeApply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal (ulong Sequence, Exception? Error) ProcessCatchupMsg(byte[] encodedMessage)
|
||||||
|
{
|
||||||
|
if (encodedMessage == null || encodedMessage.Length == 0)
|
||||||
|
return (0, new InvalidOperationException("bad catchup msg"));
|
||||||
|
if (Store == null)
|
||||||
|
return (0, new InvalidOperationException("store not initialized"));
|
||||||
|
|
||||||
|
var operation = (EntryOp)encodedMessage[0];
|
||||||
|
var payload = encodedMessage.AsSpan(1);
|
||||||
|
|
||||||
|
if (operation == EntryOp.DeleteRangeOp)
|
||||||
|
{
|
||||||
|
var (deleteRange, decodeError) = JetStreamCluster.DecodeDeleteRange(payload);
|
||||||
|
if (decodeError != null || deleteRange == null)
|
||||||
|
return (0, new InvalidOperationException("bad catchup msg"));
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_preAcks.Count > 0)
|
||||||
|
{
|
||||||
|
for (ulong seq = deleteRange.First; seq < deleteRange.First + deleteRange.Num; seq++)
|
||||||
|
ClearAllPreAcks(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
Store.SkipMsgs(deleteRange.First, deleteRange.Num);
|
||||||
|
var last = deleteRange.First + deleteRange.Num - 1;
|
||||||
|
SetLastSeq(last);
|
||||||
|
return (last, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return (0, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation == EntryOp.CompressedStreamMsgOp)
|
||||||
|
payload = Snappy.Decode(payload);
|
||||||
|
|
||||||
|
var (subject, _, header, message, sequence, timestamp, _, decodeStreamError) = JetStreamCluster.DecodeStreamMsg(payload);
|
||||||
|
if (decodeStreamError != null)
|
||||||
|
return (0, new InvalidOperationException("bad catchup msg"));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(subject) || timestamp != 0)
|
||||||
|
Store.StoreRawMsg(subject, header, message, sequence, timestamp, ttl: 0, discardNewCheck: false);
|
||||||
|
else
|
||||||
|
Store.SkipMsg(sequence);
|
||||||
|
|
||||||
|
SetLastSeq(sequence);
|
||||||
|
return (sequence, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void FlushAllPending() => Store?.FlushAllPending();
|
||||||
|
|
||||||
|
internal void HandleClusterSyncRequest(object? sub, ClientConnection? client, Account? account, string subject, string reply, byte[] message)
|
||||||
|
{
|
||||||
|
_ = sub;
|
||||||
|
_ = client;
|
||||||
|
_ = account;
|
||||||
|
_ = subject;
|
||||||
|
|
||||||
|
StreamSyncRequest? request;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
request = JsonSerializer.Deserialize<StreamSyncRequest>(message);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ = Task.Run(() => RunCatchup(reply, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RunCatchup(string sendSubject, StreamSyncRequest request)
|
||||||
|
{
|
||||||
|
if (Store == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (request.LastSeq < request.FirstSeq)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetCatchupPeer(request.Peer, request.LastSeq - request.FirstSeq);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var state = new StreamState();
|
||||||
|
Store.FastState(state);
|
||||||
|
if (state.LastSeq < request.FirstSeq)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Current C# port keeps catchup streaming minimal: this method updates
|
||||||
|
// catchup peer accounting and relies on existing replication apply paths.
|
||||||
|
ClearCatchupPeer(request.Peer);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(sendSubject))
|
||||||
|
ClearCatchupPeer(request.Peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ internal sealed partial class NatsStream : IDisposable
|
|||||||
private bool _clusterSubsActive;
|
private bool _clusterSubsActive;
|
||||||
private ulong _clseq;
|
private ulong _clseq;
|
||||||
private ulong _clfs;
|
private ulong _clfs;
|
||||||
|
private readonly Dictionary<ulong, object?> _msgTraceBySeq = new();
|
||||||
|
private Dictionary<string, ulong>? _catchupPeers;
|
||||||
|
private int _catchingUp;
|
||||||
private readonly Dictionary<string, StreamSourceInfo> _sources = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, StreamSourceInfo> _sources = new(StringComparer.Ordinal);
|
||||||
private StreamSourceInfo? _mirrorInfo;
|
private StreamSourceInfo? _mirrorInfo;
|
||||||
private Timer? _mirrorConsumerSetupTimer;
|
private Timer? _mirrorConsumerSetupTimer;
|
||||||
|
|||||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
Reference in New Issue
Block a user