分布式(一致性協議)之領導人選舉( DotNext.Net.Cluster 實現Raft 選舉 )
繼分布式鎖之后的又一高可用技術爽文之分布式領導選舉 或者說 分布式一致性協議的實現
分布式選舉是實現高可用的必備技術,想實現主從,就必須得有選舉的策略,有主從才會有一個真正的管理端進行資源的協調分配。
首先需要明確的是一致性算法的目標是什么,主要面對的問題是在只使用單個服務器時由于發生錯誤導致數據丟失等事情發生。解決這個問題的思路也很簡單,就是備份,集群,多個服務器,將操作重復到多個機器上就不怕單個機器出錯了。但隨之而來的就是,數據不一致、亂序等問題,一致性算法想要做到的是即使有結點出錯,對外仍是一個完整的可以正常工作的整體。
選舉算法
實現一致性協議(選舉)的主要算法有兩種
1.?Raft
2.?Paxos
相當于 Paxos 來講, Raft協議相對來講簡單一點。但是,Raft 實現起來也不是很容易,如果有朋友試圖想去實現可以參考,這個地方
地址 :https://zinglix.xyz/2020/06/25/raft/
我個人也是 簡單的理解了一下。
Raft 是一個非拜占庭的一致性算法,即所有通信是正確的而非偽造的。N 個結點的情況下(N為奇數)可以最多容忍 (N?1)/2個結點故障
為啥需要單獨的選舉算法
我曾經試圖實現一個WEB服務功能,我不能保證這個服務的高可用,我又不想用其他的現有服務,我就想讓服務自己本身能支持高可用。
當時,因為自己認知的問題,并沒有短時間找到一個可用的方案,不過現在有了。
要是當時有,可能就是別樣風景了,不好說。
分布式選舉的大致算法
簡單的來講就是找到一個領頭的,假設有一個leader key,redis里,誰搶到了,誰就是leader,也是可以實現的。這種分布式鎖實現的領導選舉也可以適用于簡單的項目中,并且,支持單個服務主機。
想用分布式選舉算法,機器最少得2臺以上,或者是概念上的兩臺。
Raft中主要有三個角色 Leader(領導人)、Follower (跟隨者)和 Candidate(候選人),當某臺機器成為了領導人,就會成為主要對外對接人,然后把對接的事情同步給下邊的跟隨者或者候選人同步信息。
因為對外只能有一個主服務,起到協調管理的作用。
這樣,就會把相應的指令(日志)分配各個客戶端,起到數據一致性的作用 ,這樣,當領導人廢了,下個人接任,還能繼續起到作用。
Raft 選舉的實例
我找了很多.Net 實現的Raft,很多只能說是個玩具,不能用于生產。
不過幸好,還真有生產級別的。
那就是 DotNext.Net.Cluster 和 DotNext.AspNetCore.Cluster (支持http ) 。
主要是基于 DotNext 中的組件。
DotNext.Net.Cluster 和 DotNext.AspNetCore.Cluster
1.?DotNext.Net.Cluster包含集群編程模型、Raft 算法的傳輸無關實現、Raft 的 TCP 和 UDP 傳輸綁定、HyParView membersip 協議的傳輸無關實現,用于基于 Gossip 的消息傳遞
2.?DotNext.AspNetCore.Cluster是基于DotNext.Net.Cluster庫的 Raft 和 HyParView 算法的具體實現,用于構建 ASP.NET Core 應用程序
支持的功能列表
支持的功能列表:
1. 網絡傳輸:TCP、UDP、HTTP 1.1、HTTP/2、HTTP/3
2. TLS 支持:TCP、HTTP 1.1、HTTP/2、HTTP/3
3.?支持日志壓縮的高性能、通用Persistent Write-Ahead Log
4.?跨集群節點復制日志條目
5.?與 ASP.NET Core 框架緊密集成
6.?對 Docker/LXC/Windows 容器友好
7.?一切都是可擴展的
??7.1 自定義預寫日志
??7.2 自定義網絡傳輸
??7.3 集群成員發現
基于 DotNext.Net.Cluster 的TCP 選舉實例
其實他也是支持http的,當然,更多姿勢,得大佬自己去挖掘了。
項目大致結構

細心的小伙伴就會發現,這個是個.Net 6的項目,因為它的nuget包,只支持.Net 6的。
有需要的可以自己改改。
項目是參考源示例,改了一下,有需要的朋友直接去看官方案例
項目重點
Install-Package?DotNext.Net.Cluster?-Version?4.6.0
DataModifier.cs
internal?sealed?class?DataModifier?:?BackgroundService
{private?readonly?IRaftCluster?cluster;private?readonly?ISupplier<long>?valueProvider;public?DataModifier(IRaftCluster?cluster,?ISupplier<long>?provider){this.cluster?=?cluster;valueProvider?=?provider;}protected?override?async?Task?ExecuteAsync(CancellationToken?stoppingToken){while?(!stoppingToken.IsCancellationRequested){await?Task.Delay(1000,?stoppingToken).ConfigureAwait(false);var?leadershipToken?=?cluster.LeadershipToken;TitleInfo.Show(!leadershipToken.IsCancellationRequested);if?(!leadershipToken.IsCancellationRequested){var?newValue?=?valueProvider.Invoke()?+?500L;Console.WriteLine("保存領導節點生成的值?{0}",?newValue);var?source?=?CancellationTokenSource.CreateLinkedTokenSource(stoppingToken,?leadershipToken);try{var?entry?=?new?Int64LogEntry?{?Content?=?newValue,?Term?=?cluster.Term?};await?cluster.ReplicateAsync(entry,?source.Token);}catch?(Exception?e){Console.WriteLine("未知異常?{0}",?e);}finally{source?.Dispose();}}}}
}
這個應該是核心服務,會與其他客戶端進行通信和具體的選舉,以及日志的傳輸
Program.cs
class?Program
{static?async?Task?Main(string[]?args){await?UseTcpTransport(Path.Combine(AppContext.BaseDirectory,?"raftConfig"));}static?Task?UseTcpTransport(string?path){//獲取所有配置var?jsonConfiguration?=?new?ConfigurationBuilder().SetBasePath(Environment.CurrentDirectory).AddJsonFile("appsettings.json",?optional:?true,?reloadOnChange:?true).Build();var?NodeInfo?=?new?NodeInfo();jsonConfiguration.Bind("NodeInfo",?NodeInfo);Console.WriteLine($"MainNode:{NodeInfo.MainNode}");TitleInfo.Node?=?NodeInfo.MainNode;var?configuration?=?new?RaftCluster.TcpConfiguration(IPEndPoint.Parse(NodeInfo.MainNode)){RequestTimeout?=?TimeSpan.FromMilliseconds(140),LowerElectionTimeout?=?150,UpperElectionTimeout?=?300,TransmissionBlockSize?=?4096,ColdStart?=?false,};//加載全部地址//線上環境自己重寫服務var?builder?=?configuration.UseInMemoryConfigurationStorage().CreateActiveConfigurationBuilder();foreach?(var?item?in?NodeInfo.Nodes){var?address?=?IPEndPoint.Parse(item);builder.Add(ClusterMemberId.FromEndPoint(address),?address);}builder.Build();TitleInfo.Show();return?UseConfiguration(configuration,?path);}static?async?Task?UseConfiguration(RaftCluster.NodeConfiguration?config,?string??persistentStorage){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);}
}
總體來說,項目還是很簡單的。
我把客戶端的地址給配置了
appsettings.json
這個結構應該很容易理解,一個是當前端的地址,一個是所有節點的地址,當然也包含當前地址。
{"NodeInfo":?{"MainNode":?"127.0.0.1:6001"?,"Nodes":?["127.0.0.1:6001","127.0.0.1:6002","127.0.0.1:6003"]}
}
運行方式
我自己是把Bin目錄復制三份,每份的 appsettings.json 修改下,然后,雙擊 RaftDemo.exe 就運行起來了。
注意
如果起用一個節點沒個卵用,最少得兩個節點。
運行效果

總結
這個庫是可以用在生產環境的,所以,還是值得研究一下下的。
代碼地址
https://github.com/kesshei/RaftDemo.git
https://gitee.com/kesshei/RaftDemo.git
參考文檔
https://zinglix.xyz/2020/06/25/raft/
https://github.com/dotnet/dotNext/tree/master/src/cluster
閱
一鍵三連呦!,感謝大佬的支持,您的支持就是我的動力!
另
堅持更了大概一個月,也有幾個鐵粉了,會持續更,但是連續更太累了(按天更吃不消哦)。
感謝大佬的支持。