Git Product home page Git Product logo

Comments (45)

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024 1

Hello, thank you very much for your collaboration throughout these days, I have already managed to implement the library 100%, I will proceed to close the topic. Thank you!!!

from dotnext.

sakno avatar sakno commented on July 17, 2024

Hi @LCastilloSymbiotic , the link is broken because the project has moved to .NET Foundation. The correct link is https://github.com/dotnet/dotNext/tree/develop/src/examples/RaftNode

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hi @sakno , I try to replicate your example, but when i try to import IClusterMemberLifetime from DotNext.Net.Cluster.Consensus.Raft; it's show a not found exeception. Do know you why?

from dotnext.

sakno avatar sakno commented on July 17, 2024

Could you please provide a full stack trace?

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

I have this code, that's the same of your example:

using DotNext.Net.Cluster;
using DotNext.Net.Cluster.Consensus.Raft;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Services.Replication
{
    internal sealed class ClusterConfigurator : IClusterMemberLifetime
    {
        internal static void LeaderChanged(ICluster cluster, IClusterMember? leader)
        {
            Debug.Assert(cluster is IRaftCluster);
            var term = ((IRaftCluster)cluster).Term;
            var timeout = ((IRaftCluster)cluster).ElectionTimeout;
            Console.WriteLine(leader is null
                ? "Consensus cannot be reached"
                : $"New cluster leader is elected. Leader address is {leader.EndPoint}");
            Console.WriteLine($"Term of local cluster member is {term}. Election timeout {timeout}");
        }

        public void Initialize(IRaftCluster cluster, IDictionary<string, string> metadata)
        {
            cluster.LeaderChanged += LeaderChanged;
        }

        public void Shutdown(IRaftCluster cluster)
        {
            cluster.LeaderChanged -= LeaderChanged;
        }
    }
}

Generate this error:

Severity Code Description Project File Line Suppression State
Error CS0246 The type or namespace name 'IClusterMemberLifetime' could not be found (are you missing a using directive or an assembly reference?) "" C:............\Replication\ClusterConfigurator.cs 11 Active

from dotnext.

sakno avatar sakno commented on July 17, 2024

Check your ProjectReference/PackageReference elements in csproj file and import paths. The interface is definitely public and even visible on FuGet package explorer: https://www.fuget.org/packages/DotNext.AspNetCore.Cluster/3.3.0/lib/netcoreapp3.1/DotNext.AspNetCore.Cluster.dll/DotNext.Net.Cluster.Consensus.Raft/IClusterMemberLifetime

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Yes, I check, but i think the PakageReference is ok:

image

from dotnext.

sakno avatar sakno commented on July 17, 2024

IClusterMemberLifetime interface is in DotNext.AspNetCore.Cluster package

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

In your example you import DotNext.Net.Cluster:

image

from dotnext.

sakno avatar sakno commented on July 17, 2024

No, it has both imports: https://github.com/dotnet/dotNext/blob/master/src/examples/RaftNode/RaftNode.csproj#L25

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Ok, that's work!!!

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hi, when execute this code:

 app.UseConsensusProtocolHandler()
                .RedirectToLeader(LeaderResource)
                .UseRouting()
                .UseEndpoints(endpoints =>
                {
                    endpoints.MapGet(LeaderResource, RedirectToLeader);
                });

raise this error:

No service for type 'DotNext.Net.Cluster.Consensus.Raft.Http.Embedding.RaftEmbeddedCluster' has been registered.',

Do you know why?

This is the complete code of the startup:

 public class Startup
    {
        private readonly IConfiguration configuration;

        public Startup(IConfiguration configuration)
        {
            this.configuration = configuration;
            Newtonsoft.Json.JsonConvert.DefaultSettings = () => new CustomJsonSettings();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Configuration of NewtonsoftJson
            services.AddControllers()
                .AddNewtonsoftJson();
            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Latest)
                .AddJsonOptions(options =>
                    options.JsonSerializerOptions.MaxDepth = 10)
                .AddNewtonsoftJson(options => 
                    options.SerializerSettings.MaxDepth = 10)
                .AddJsonOptions(options =>
                    options.JsonSerializerOptions.MaxDepth = 10);
            Newtonsoft.Json.JsonConvert.DefaultSettings = () => 
                new Newtonsoft.Json.JsonSerializerSettings() {
                    MaxDepth = 10
                };


            // Configuration of Cluster
            services.ConfigureCluster<ClusterConfigurator>()
               .AddSingleton<IHttpMessageHandlerFactory, RaftClientHandlerFactory>()
               .AddOptions()
               .AddRouting();

            var path = configuration[SimplePersistentState.LogLocation];
            if (!string.IsNullOrWhiteSpace(path))
            {
                services.AddSingleton<AppEventSource>();
                services.UsePersistenceEngine<IValueProvider, SimplePersistentState>()
                    .AddSingleton<IHostedService, DataModifier>();
            }
        }



        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // app.UseHttpsRedirection();

            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });


            // Configuration of Cluster
            const string LeaderResource = "/leader";


            // Check
            app.UseConsensusProtocolHandler()
                .RedirectToLeader(LeaderResource)
                .UseRouting()
                .UseEndpoints(endpoints =>
                {
                    endpoints.MapGet(LeaderResource, RedirectToLeader);
                });
        }

        private static Task RedirectToLeader(HttpContext context)
        {
            var cluster = context.RequestServices.GetRequiredService<IRaftCluster>();
            return context.Response.WriteAsync($"Leader address is {cluster.Leader?.EndPoint}. Current address is {context.Connection.LocalIpAddress}:{context.Connection.LocalPort}", context.RequestAborted);
        }
    }

from dotnext.

sakno avatar sakno commented on July 17, 2024

Probably you forgot to call JoinCluster method on WebHost. I recommend you to start from the following article: https://dotnet.github.io/dotNext/features/cluster/aspnetcore.html. There is a good explanation of differences between Embedded and Hosted modes.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hi, not i call this, you can see the code of the Program:

    public class Program
    {
        public static void Main(string[] args)
        {
            var config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: false)
                .Build();

            _ = RaftManager.Main(config);
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

and the code for the RaftManager:

public static class RaftManager
    {
        //private RaftCluster cluster = null;
        
        //public RaftManager(string[][] clusterPeers, int localPort)
        //{

        //    RaftCluster.NodeConfiguration config = 
        //        new RaftCluster.TcpConfiguration(new IPEndPoint(IPAddress.Loopback, localPort));

        //    // Cluster configuration
        //    config.Partitioning = true;


        //    for (int i = 0; i < clusterPeers.Length; i++)
        //    {
        //        config.Members.Add(new IPEndPoint(
        //            System.Net.IPAddress.Parse(clusterPeers[i][0]),
        //            Int32.Parse(clusterPeers[i][1])));
        //    }

        //    cluster = new RaftCluster(config);

        //}

        private static Task UseAspNetCoreHost(int port, string? persistentStorage = null)
        {
            var configuration = new Dictionary<string, string>
            {
                {"partitioning", "false"},
                {"lowerElectionTimeout", "150" },
                {"upperElectionTimeout", "300" },
                {"members:0", "https://localhost:63755"},
                {"requestJournal:memoryLimit", "5" },
                {"requestJournal:expiration", "00:01:00" }
            };
            if (!string.IsNullOrEmpty(persistentStorage))
                configuration[SimplePersistentState.LogLocation] = persistentStorage;
            return new HostBuilder().ConfigureWebHost(webHost =>
            {
                webHost.UseKestrel(options =>
                {
                    options.ListenLocalhost(port, listener => listener.UseHttps(LoadCertificate()));
                })
                .UseStartup<Startup>();
            })
            .ConfigureLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Error))
            .ConfigureAppConfiguration(builder => builder.AddInMemoryCollection(configuration))
            .JoinCluster()
            .Build()
            .RunAsync();
        }

        private static async Task UseConfiguration(RaftCluster.NodeConfiguration config, string? persistentStorage)
        {
            config.Members.Add(new IPEndPoint(IPAddress.Loopback, 63755));
            var loggerFactory = new LoggerFactory();
            var loggerOptions = new ConsoleLoggerOptions
            {
                LogToStandardErrorThreshold = LogLevel.Warning
            };
            loggerFactory.AddProvider(new ConsoleLoggerProvider(new FakeOptionsMonitor<ConsoleLoggerOptions>(loggerOptions)));
            config.LoggerFactory = loggerFactory;

            using var cluster = new RaftCluster(config);
            cluster.LeaderChanged += ClusterConfigurator.LeaderChanged;
            var modifier = default(DataModifier?);
            if (!string.IsNullOrEmpty(persistentStorage))
            {
                var state = new SimplePersistentState(persistentStorage, new AppEventSource());
                cluster.AuditTrail = state;
                modifier = new DataModifier(cluster, state);
            }
            await cluster.StartAsync(CancellationToken.None);
            await (modifier?.StartAsync(CancellationToken.None) ?? Task.CompletedTask);
            using var handler = new CancelKeyPressHandler();
            Console.CancelKeyPress += handler.Handler;
            await handler.WaitAsync();
            Console.CancelKeyPress -= handler.Handler;
            await (modifier?.StopAsync(CancellationToken.None) ?? Task.CompletedTask);
            await cluster.StopAsync(CancellationToken.None);
        }

        private static Task UseUdpTransport(int port, string? persistentStorage)
        {
            var configuration = new RaftCluster.UdpConfiguration(new IPEndPoint(IPAddress.Loopback, port))
            {
                LowerElectionTimeout = 150,
                UpperElectionTimeout = 300,
                DatagramSize = 1024
            };
            return UseConfiguration(configuration, persistentStorage);
        }

        private static Task UseTcpTransport(int port, string? persistentStorage, bool useSsl)
        {
            var configuration = new RaftCluster.TcpConfiguration(new IPEndPoint(IPAddress.Loopback, port))
            {
                LowerElectionTimeout = 150,
                UpperElectionTimeout = 300,
                TransmissionBlockSize = 4096,
                SslOptions = useSsl ? CreateSslOptions() : null
            };

            return UseConfiguration(configuration, persistentStorage);

            static SslOptions CreateSslOptions()
            {
                var options = new SslOptions();
                options.ServerOptions.ServerCertificate = LoadCertificate();
                options.ClientOptions.TargetHost = "localhost";
                options.ClientOptions.RemoteCertificateValidationCallback = RaftClientHandlerFactory.AllowCertificate;
                return options;
            }
        }

        private static Task StartNode(string protocol, int port, string? persistentStorage = null)
        {
            switch (protocol.ToLowerInvariant())
            {
                case "http":
                case "https":
                    return UseAspNetCoreHost(port, persistentStorage);
                case "udp":
                    return UseUdpTransport(port, persistentStorage);
                case "tcp":
                    return UseTcpTransport(port, persistentStorage, false);
                case "tcp+ssl":
                    return UseTcpTransport(port, persistentStorage, true);
                default:
                    Console.Error.WriteLine("Unsupported protocol type");
                    Environment.ExitCode = 1;
                    return Task.CompletedTask;
            }
        }

        public static async Task Main(IConfiguration configuration)
        {
            string protocol = configuration["ReplicationConfig:Protocol"];
            int port = int.Parse(configuration["ReplicationConfig:LocalPort"]);
            string persistentStorage = (configuration["ReplicationConfig:PersistentStorage"] == "") ? null : configuration["ReplicationConfig:PersistentStorage"];

            switch (persistentStorage)
            {
                case null:
                    await StartNode(protocol, port);
                    break;
                default:
                    await StartNode(protocol, port, persistentStorage);
                    break;
            }
        }

        private static X509Certificate2 LoadCertificate()
        {
            var certificate = new X509Certificate2("C:\\Users\\User\\Documents\\Symbiotic\\Git\\ToP-PMS\\PaymentManagementServer\\PaymentManagementServer.Services\\Certificates\\node.pfx",
                "1234", 
                X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
            return certificate;
        }
    }

And the parameters are:

PersistentStorage: null
Protocol: "http"
LocalPort: 63754

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hello,

I managed to execute the code of the example, and I read what you sent me, I have a doubt, the only thing that is synchronized (a consensus is reached) between the nodes is the Term that comes there or is it possible to customize the data to be replicated?

from dotnext.

sakno avatar sakno commented on July 17, 2024

@LCastilloSymbiotic , of course, there is persistent Write-Ahead Log for that and you can implement state machine on top of it. Examine this article for information. Also, the example has a replication process of monotonically increasing int64 counter.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hi @sakno , your implementation of Int64 LogEntry, it is as follows:

    internal sealed class Int64LogEntry : BinaryTransferObject<long>, IRaftLogEntry
    {
        internal Int64LogEntry()
        {
            Timestamp = DateTimeOffset.UtcNow;
        }

        bool ILogEntry.IsSnapshot => false;

        public long Term { get; set; }

        public DateTimeOffset Timestamp { get; }
    }
}

I would like to do something like the following:

    public class ObjectLogEntry : BinaryTransferObject<Object>, IRaftLogEntry
    {
        internal ObjectLogEntry()
    {
        Timestamp = DateTimeOffset.UtcNow;
    }

    bool ILogEntry.IsSnapshot => false;

    public Object Term { get; set; }

    public DateTimeOffset Timestamp { get; }
}

But this does not work, since IRaftLogEntry (which a class in the library) uses a Long term, can I simply not use this interface or what other alternative is there?

from dotnext.

sakno avatar sakno commented on July 17, 2024

No, Term is a part of Raft protocol and it should be int64. BinaryTransferObject<Object> is also invalid because generic parameter T must be of blittable type.

from dotnext.

sakno avatar sakno commented on July 17, 2024

BTW, you don't need to implement IRaftLogEntry manually. You need to build state machine for interpreting log entries in the log. Take a look at this article for more details about how persistent WAL is working.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Perfect, I'm going to read it!!!

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

To be sure if I understand, the structures to be replicated through consensus are serializable structures in JSON like the following, right?

struct SubtractCommand
{
	public int X { get; set; }
	public int Y { get; set; }
}

PD:Excuse me asking so many questions, I've been confused about this.

from dotnext.

sakno avatar sakno commented on July 17, 2024

No, there is no restrictions about the actual binary format. JSON is just an option. For efficient I/O you need to implement IFormatter<T> for particular type according to documentation.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Ok yes, but see the example on the page you told me about:

image

The "SubtractCommand" execute actions on the Value, I want value to be a custom data class, is there a problem with that?

from dotnext.

sakno avatar sakno commented on July 17, 2024

No, but you need to implement serialization logic using IFormatter<T>.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

ok, for example I have the following code:

image

I understand that when applying the operation "var entry = state.CreateJsonLogEntry (new SubtractCommand {X = 10, Y = 20});" This would be replicated in all nodes, right? I must use these commands to replicate the data and the consensus algorithm internally will take care of the consistency of the order of execution of this command in all the nodes, right?

Now in the hypothetical case that n nodes are alive and a node n + 1 connects to the network, this node (n + 1) would receive the last command which would allow it to be congruent with the rest of the nodes?

from dotnext.

sakno avatar sakno commented on July 17, 2024

Yes, you can use built-in JSON support for convenience (but not for performance). Overriding ApplyAsync is necessary but not sufficient. Raft replicates the entire log entry starting from the last known position for the node. It means that if node had lost the connection with the leader at index 10 and then returned back to the cluster and leader has 20 more log entries then the leader will replicate all these 20 log entries. That's why here is a special log entry called Snapshot. Without the snapshot your log will grow indefinitely. Snapshot allows to squash commands in the log. For instance. the two commands X = X + 10 and X = X + 30 can be reduced to a single command X = X + 30. That's why you need to override CreateSnapshotBuilder method as well to provide snapshot construction logic.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Ok, I will try to implement it, in my case it is necessary to apply it like this, since what I need to replicate is a complex object with various data.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hi I've been trying to use your interpreter example, but it generates the following error:

image

Any idea why?

from dotnext.

sakno avatar sakno commented on July 17, 2024

Probably you're trying to register multiple command handlers with the same ID.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

ok, i show you the code:

 public class MyInterpreter : CommandInterpreter
	{
		public static SubtractCommand stateSubtract;
		public static NegateCommand stateNegate;

		[CommandHandler]
		public async ValueTask SubtractAsync(SubtractCommand command, CancellationToken token)
		{
			stateSubtract = command;

			Debug.WriteLine("I received Subtract {0}", command);
		}

		[CommandHandler(IsSnapshotHandler = true)]
		public async ValueTask HandleSnapshotSubtractAsync(SubtractCommand command, CancellationToken token)
		{
		}

		[CommandHandler]
		public async ValueTask NegateAsync(NegateCommand command, CancellationToken token)
		{
			stateNegate = command;

			Debug.WriteLine("I received Negate {0}", command);
		}

		[CommandHandler(IsSnapshotHandler = true)]
		public async ValueTask HandleSnapshotNegateAsync(NegateCommand command, CancellationToken token)
		{
		}
	}
sealed class CommandFormatter : IFormatter<SubtractCommand>, IFormatter<NegateCommand>
	{
		public static readonly CommandFormatter Instance = new CommandFormatter();

		private CommandFormatter()
		{
		}

		async ValueTask IFormatter<SubtractCommand>.SerializeAsync<TWriter>(SubtractCommand command, TWriter writer, CancellationToken token)
		{
			await writer.WriteInt32Async(command.X, true, token);
			await writer.WriteInt32Async(command.Y, true, token);
		}

		async ValueTask<SubtractCommand> IFormatter<SubtractCommand>.DeserializeAsync<TReader>(TReader reader, CancellationToken token)
		{
			return new SubtractCommand
			{
				X = await reader.ReadInt32Async(true, token),
				Y = await reader.ReadInt32Async(true, token)
			};
		}

		async ValueTask IFormatter<NegateCommand>.SerializeAsync<TWriter>(NegateCommand command, TWriter writer, CancellationToken token)
		{
			await writer.WriteInt32Async(command.X, true, token);
		}

		async ValueTask<NegateCommand> IFormatter<NegateCommand>.DeserializeAsync<TReader>(TReader reader, CancellationToken token)
		{
			return new NegateCommand
			{
				X = await reader.ReadInt32Async(true, token),
			};
		}
	}
	[CommandAttribute(1, Formatter = typeof(CommandFormatter), FormatterMember = nameof(CommandFormatter.Instance))]
	public struct NegateCommand
	{
		public int X { get; set; }
	}
	[CommandAttribute(0, Formatter = typeof(CommandFormatter), FormatterMember = nameof(CommandFormatter.Instance))]
	public struct SubtractCommand
	{
		public int X { get; set; }
		public int Y { get; set; }
	}

As I understand the only place where an id is placed is when creating the command, can you see that both id are different, do you see any error in that code?

from dotnext.

sakno avatar sakno commented on July 17, 2024

Yes, you must have only one handler for each type of command. And of course you cannot have multiple handlers for the snapshot.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Ok it worked, I have another question, if I have the following structure:

    [CommandAttribute(2, Formatter = typeof(CommandFormatter), FormatterMember = nameof(CommandFormatter.Instance))]
    public struct DataCommand
    {
        public string StringData { get; set; }

        public int Test { get; set; }
    }

when I go to implement it in CommandFormatter it does not allow me to write strings:

image

Any solution for this problem?

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

I try to use JSON for alternative, in the PersistentState subclass, but show this error:

image

image

from dotnext.

sakno avatar sakno commented on July 17, 2024

IAsyncBinaryWriter supports string serialization using the following method:

ValueTask WriteAsync(ReadOnlyMemory<char> chars, EncodingContext context, LengthFormat? lengthFormat, CancellationToken token = default)

from dotnext.

sakno avatar sakno commented on July 17, 2024

CreateJsonLogEntry is not supported for .NET Standard 2.1 target, only for .NET 5 target.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Do you have some example of WriteAsync?

Because, when I try to use WriteAsync, it allows me to write a single character, but I need to write a complete string.

from dotnext.

sakno avatar sakno commented on July 17, 2024

You can find a lot of examples directly in tests: https://github.com/dotnet/dotNext/blob/master/src/DotNext.Tests/IO/AsyncBinaryReaderWriterTests.cs#L305

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Do the samples work for .net core 3.1?

Bause your code is this:

        [Theory]
        [MemberData(nameof(GetDataForStringEncoding))]
        public static async Task WriteReadStringAsync(IAsyncBinaryReaderWriterSource source, Encoding encoding, LengthFormat? lengthFormat)
        {
            await using (source)
            {
                const string value = "Hello, world!&*(@&*(fghjwgfwffgw Привет, мир!";
                var writer = source.CreateWriter();
                await writer.WriteAsync(value.AsMemory(), encoding, lengthFormat);

                var reader = source.CreateReader();
                var result = await (lengthFormat is null ?
                    reader.ReadStringAsync(encoding.GetByteCount(value), encoding) :
                    reader.ReadStringAsync(lengthFormat.GetValueOrDefault(), encoding));
                Equal(value, result);
            }
        }

And my code show this error:

image

The other problem I have is the constant, that AsMemory () can be used with string variables?

from dotnext.

sakno avatar sakno commented on July 17, 2024

In C#, there is no way to restrict the method to be called on const value only.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

In your example, you use this to read:

 var result = await (lengthFormat is null ?
                    reader.ReadStringAsync(encoding.GetByteCount(value), encoding) :
                    reader.ReadStringAsync(lengthFormat.GetValueOrDefault(), encoding));

but in the library (net.core 3.1) the overload for ReadStringAsync are there:

image

What i need to send in DecodingContext context? Can i send null?

image

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Hi, I got the string send to work, instead of putting null, I put default and it worked.

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

I have a question about the operation of the cluster, doing tests I notice that the commands "sent" by the cluster master are replicated, but if they are sent by the slave this is not done, how can I enable or make the commands to be issued by the slave as well replicate?

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

In you example, leader node is the only node can share or replicate information:

image

A normal node can replicate?

from dotnext.

LCastilloSymbiotic avatar LCastilloSymbiotic commented on July 17, 2024

Or is there a way for a slave node to send a command to the master?

from dotnext.

sakno avatar sakno commented on July 17, 2024

According to Raft protocol only the leader node is responsible for replication. Otherwise, it's a violation of data consistency and linearizability. I recommend you to read basics about Raft here.

from dotnext.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.