如何开发一个分布式日志系统
开发一个分布式日志系统是一个复杂的任务,涉及多个方面的设计和技术决策。以下是一些关键步骤和考虑因素
1. 一般需求
- 持久性:确保日志条目不会因为单点故障而丢失。
- 一致性:保证所有节点上的日志最终一致。
- 可用性:即使部分节点失效,系统仍然可以处理日志写入和读取。
- 性能:支持高吞吐量的日志写入和快速的查询响应时间。
- 扩展性:能够随着数据增长或用户数量增加而水平扩展。
2. 设计架构
- 分布式一致性协议
- Paxos/Raft/ZAB/Raftx:这些协议用于解决分布式系统中的领导者选举和命令提交问题,确保在多数派节点达成一致的情况下,日志条目的顺序是全局一致的。
- 数据分区与复制
- 分片(Sharding):将日志按照某种规则划分为多个分片,每个分片由一组节点负责,以实现负载均衡和水平扩展。
- 副本(Replication):为每个分片创建多个副本,放置在不同的节点上,以增强系统的容错能力和数据持久性
3. 核心功能
- 日志追加(Log Append):实现高效且原子性的日志追加操作,确保每次写入都是不可分割的整体。
- 日志同步(Log Syncing):设计一种机制让Leader将日志同步到Follower节点,同时保证数据的一致性和完整性
本文将基于这些需求,利用 raftx 协议,为分布式日志系统提供一个基础的框架,使用raftx作为一致性协议的实现来确保跨节点的日志数据一致性和顺序性。
优点
- 一致性:通过使用 Raftx 协议,该系统能够保证日志条目的全局顺序一致性。
- 简单易懂:代码结构清晰,易于理解。
- 功能完整:实现了基本的日志记录功能,并且支持多种日志级别(Debug, Info, Warn, Error)。
- 并发处理:提供了并发写入日志的能力,适用并发场景。
缺点
- 缺乏故障恢复机制:当一个节点重新加入集群时,需要有一个有效的机制来同步缺失的数据,否则可能丢失数据。
- 监控与报警:不具备自我监控能力。
raftx的介绍,可以参考:Github或wiki
核心代码实现
//注册Debug的事件监听 cl.raft.MemWatch([]byte{0}, func(key, value []byte, watchType raft.WatchType) { cl.log.Debug(string(value)) }, true, raft.ADD, raft.UPDATE)
- 这部分代码是Debug方法的核心逻辑实现
- 实现逻辑为通过监听键值的创建与修改即 ADD,UPDATE的事件,捕获写日志的操作,并调用本地日志工具,将事件触发的日志数据,写入本地日志文件,由于raftx保证了各个节点数据的有序性,因此,日志数据也是有序的,这样保证了各个节点的日志数据是相同的。
以下是完整的代码
分布式日志库 logx: 代码地址
import ( "github.com/donnie4w/go-logger/logger" "github.com/donnie4w/raftx" "github.com/donnie4w/raftx/raft" ) type Logx struct { log *logger.Logging raftx raftx.Raftx } func NewLogx(filePath string, listen string, peers []string) (r *Logx) { log := logger.NewLogger().SetOption(&logger.Option{FileOption: &logger.FileSizeMode{Filename: filePath, Maxsize: 1 << 30}, Format: logger.FORMAT_DATE | logger.FORMAT_SHORTFILENAME}) rx := raftx.NewRaftx(&raft.Config{ListenAddr: listen, PeerAddr: peers}) go rx.Open() //启动raftx服务 r = &Logx{log: log, raftx: rx} go func() { rx.WaitRun() //等待raftx集群可正常服务 r.init() }() return } func (lx *Logx) init() { //注册Debug的事件监听 lx.raftx.MemWatch([]byte{0}, func(key, value []byte, watchType raft.WatchType) { lx.log.Debug(string(value)) }, true, raft.ADD, raft.UPDATE) //注册Info的事件监听 lx.raftx.MemWatch([]byte{1}, func(key, value []byte, watchType raft.WatchType) { lx.log.Info(string(value)) }, true, raft.ADD, raft.UPDATE) //注册Warn的事件监听 lx.raftx.MemWatch([]byte{2}, func(key, value []byte, watchType raft.WatchType) { lx.log.Warn(string(value)) }, true, raft.ADD, raft.UPDATE) //注册Error的事件监听 lx.raftx.MemWatch([]byte{3}, func(key, value []byte, watchType raft.WatchType) { lx.log.Error(string(value)) }, true, raft.ADD, raft.UPDATE) } func (lx *Logx) Debug(value []byte) error { return lx.raftx.MemCommand([]byte{0}, value, 0, raft.MEM_PUT) } func (lx *Logx) Info(value []byte) error { return lx.raftx.MemCommand([]byte{1}, value, 0, raft.MEM_PUT) } func (lx *Logx) Warn(value []byte) error { return lx.raftx.MemCommand([]byte{2}, value, 0, raft.MEM_PUT) } func (lx *Logx) Error(value []byte) error { return lx.raftx.MemCommand([]byte{3}, value, 0, raft.MEM_PUT) }
测试代码:以下模拟3个集群节点:
var c1 *Logx var c2 *Logx var c3 *Logx func newlog1() *Logx { return NewLogx("log1.log", ":20001", []string{"127.0.0.1:20001", "127.0.0.1:20002", "127.0.0.1:20003"}) } func newlog2() *Logx { return NewLogx("log2.log", ":20002", []string{"127.0.0.1:20001", "127.0.0.1:20002", "127.0.0.1:20003"}) } func newlog3() *Logx { return NewLogx("log3.log", ":20003", []string{"127.0.0.1:20001", "127.0.0.1:20002", "127.0.0.1:20003"}) }
压测调用
func Benchmark_logx(b *testing.B) { for i := 0; i < b.N; i++ { c1.Debug([]byte("hello--------------->" + strconv.Itoa(i))) //模拟c1节点打印日志 c2.Info([]byte("world--------------->" + strconv.Itoa(i))) //模拟c2节点打印日志 c3.Warn([]byte("hello raftx--------------->" + strconv.Itoa(i))) //模拟c3节点打印日志 } }
并发测试调用
func Test_Parallel(t *testing.T) { for i := range 1 << 17 { //这里将模拟每个节点 13万并发写日志数据 go func() { e1 := c1.Debug([]byte("hello--------------->" + strconv.Itoa(i))) //模拟c1节点打印日志 e2 := c2.Info([]byte("world--------------->" + strconv.Itoa(i))) //模拟c2节点打印日志 e3 := c3.Warn([]byte("hello raftx--------------->" + strconv.Itoa(i))) //模拟c3节点打印日志 if e1 != nil || e2 != nil || e3 != nil { t.Log(e1, e2, e3) } }() } time.Sleep(30 * time.Second) //根据实际环境设置等待时间,确保所有节点都执行结束 TestFileSync(t) //检查各个节点生成的日志文件是否相同 }
注意:
- 如果测试完过早退出,有的节点内部还没有执行完毕,可能导致日志不完整,出现日志缺少数据的情况
对比日志文件数据是否相同
func TestFileSync(t *testing.T) { t.Log("fileByteEq 1&2:", fileByteEq1()) t.Log("fileByteEq 1&3:", fileByteEq2()) } func fileByteEq1() bool { bs1, _ := util.ReadFile("log1.log") bs2, _ := util.ReadFile("log2.log") return bytes.Equal(bs1, bs2) } func fileByteEq2() bool { bs1, _ := util.ReadFile("log1.log") bs2, _ := util.ReadFile("log3.log") return bytes.Equal(bs1, bs2) }
通过系列测试,可以看到,调用TestFileSync
执行,每个节点13万并发写日志生成的3个文件内容完全一致。
log_test.go:55: fileByteEq 1&2: true log_test.go:56: fileByteEq 1&3: true
总结
通过raftx API,可以高效协同各个集群节点的数据操作,需要注意的是:该实现并非绝对保证各个节点的数据一致, 该实现没有进一步检查各个节点的日志文件的差异并实现数据同步等操作。 例如,当一个集群节点宕机后,它的内存数据被清空,当它重新连接上集群时,可能会丢失部分数据无法同步回来,因为raftx易失性数据的日志长度是有限制的,节点断开时间过长,可能缺失的数据将超过日志最大上限,此时,重连或新增的节点将无法同步完整的数据。
raftx 分布式易失性数据服务的特点是数据应当是相对短暂的。如果使用以上的方式开发分布式日志库,并且出现了宕机丢失数据的情况,应当手动将最新的日志数据同步到重启的节点,尽量缩小新加入节点数据与正常节点数据差异。再让该节点重新连上集群。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
USB 全新标识将直接标注传输速度、功率
近日,USB 开发者论坛(USB-IF)正式公布了全新的 USB 标识。 据了解,全新的 USB 标识相较于旧版更加直观,舍弃了旧版的「USB 3.2 Gen1」、「USB 3.2 Gen2」等类似后缀,改为了由传输速度和充电功率作为后缀。其中设备与数据线的传输速度以 Gbps 为单位,而充电功率统一使用「W(瓦)」为单位。 新的 USB 标识采用速度优先的原则,用清晰易懂的数字直接标明数据传输速度,例如 USB 80Gbps、USB 40Gbps 等,取代以往 USB4 v2 等复杂表述。 新的 USB 标识也适用于线缆标识,会在线缆上同时标明数据传输速度和供电能力。 USB-IF 强调此次更新旨在解决长期以来消费者对 USB 标识的困惑,尤其是在 2017 年 USB 3.2 推出时,诸如 Gen 2x2 和 SuperSpeed USB 20Gbps 等让消费者难以理解实际规格的复杂命名。 据 USB-IF 代表透露,戴尔或将会成为首个印刷新标识上产品的厂商。
- 下一篇
“TikTok 难民”突然涌入——小红书内部观点尚未达成一致,国内互联网公司纷纷发英文贴揽客
近日,TikTok“禁令”进入倒计时,而许多美国网友并没有选择使用 Instagram Reels和YouTube Shorts等TikTok在美国竞争对手的平台,而是决定加入另一个中国社交媒体平台:小红书。 小红书也登上了 App Store 美区的下载榜榜首。小红书或许也因此成为有史以来第一款登顶美区下载榜的名字全是汉字的 App。 从多位小红书内部人士处了解到,“内部对此次事件的观点尚不能达成一致”。核心的争议在于全球化的挑战大于流量承接的喜悦,“这个事情是偶发的、突然的”。 据悉,这并非小红书首次承接来自 TikTok 的流量,小红书上一次有大规模外国人出现还是“外国人听劝”系列,事后内部也曾拉数据查看,对 DAU 的带动并不明显。目前尚不清楚有多少海外用户涌入,以及其所对应的笔记量。 但截至发稿,TikTokRefugee 词条下显示有 7 万 + 笔记。甚至有部分人士认为,此举无法等同于小红书的全球化。 值得注意的是,很多国内互联网公司也借机以玩梗名义试图吸引这波流量。阿里巴巴财大气粗:Do you want to make money(你想赚钱吗)?而且阿里巴巴还“阴阳”...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6