package main import ( "flag" "fmt" "github.com/syndtr/goleveldb/leveldb" "os" "os/signal" "simple-kv-store/internal/logprovider" "simple-kv-store/internal/nodes" "strconv" "strings" "syscall" "go.uber.org/zap" ) var log, _ = logprovider.CreateDefaultZapLogger(zap.InfoLevel) func main() { defer func() { if err := recover(); err != nil { log.Info("i get a panic", zap.Any("panic error", err)) } }() // 设置一个通道来捕获中断信号 sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) port := flag.String("port", ":9091", "rpc listen port") cluster := flag.String("cluster", "127.0.0.1:9091,127.0.0.1:9092,127.0.0.1:9093", "comma sep") id := flag.String("id", "1", "node ID") isRestart := flag.Bool("isRestart", false, "new test or restart") // 参数解析 flag.Parse() clusters := strings.Split(*cluster, ",") idClusterPairs := make(map[string]string) idCnt := 1 selfi, err := strconv.Atoi(*id) if err != nil { log.Fatal("figure id only") } for _, addr := range clusters { if idCnt == selfi { idCnt++ // 命令行cluster按id排序传入,记录时跳过自己的id,先保证所有节点互相记录的id一致 continue } idClusterPairs[strconv.Itoa(idCnt)] = addr idCnt++ } // storage/文件夹下为node重要数据持久化数据库,节点一旦创建成功就不能被删除 if !*isRestart { os.RemoveAll("storage/node" + *id) } // 创建每个结点自己的数据库。这里一开始理解上有些误区,状态机的状态恢复应该靠节点的持久化log, // 而用leveldb模拟状态机,造成了状态机本身的持久化,因此通过删去旧db避免这一矛盾 // 因此leveldb/文件夹下为状态机模拟数据库,每次节点启动都需要删除该数据库 os.RemoveAll("leveldb/simple-kv-store" + *id) db, err := leveldb.OpenFile("leveldb/simple-kv-store" + *id, nil) if err != nil { log.Fatal("Failed to open database: ", zap.Error(err)) } defer db.Close() // 确保数据库在使用完毕后关闭 // 打开或创建节点数据持久化文件 storage := nodes.NewRaftStorage("storage/node" + *id) defer storage.Close() // 初始化 node := nodes.InitRPCNode(*id, *port, idClusterPairs, db, storage, !*isRestart) // 开启 raft quitChan := make(chan struct{}, 1) nodes.Start(node, quitChan) sig := <-sigs fmt.Println("node_"+ *id +"接收到信号:", sig) close(quitChan) }