最近发现用于为用户提供下载服务的服务器带宽承载压力逐渐增大,我建议使用阿里云的 OSS 来解决这个问题,毕竟 OSS 具备高效的存储和分发能力。
然而,老板坚持选择多台服务器结合 CDN 进行内容分发。
虽然这种方案可以缓解部分流量压力,但每次有内容更新时都需要手动同步到每台服务器。
为了简化这一过程,我编写了一个自动同步的小脚本,来实现内容的快速分发
话不多说直接上代码
当前脚本为检测特定目录下
.apk文件的变更,根据实际需要调整第66行即可
package main
import (
"fmt"
"io"
"log"
"sort"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/fsnotify/fsnotify"
)
var lastEventTime = make(map[string]time.Time)
var debounceDuration = 10 * time.Second // 可调整的去重时间间隔
// 监测路径
var rootDir = "/www/wwwroot/ftp"
// 目标服务器列表
var servers = []string{
"同步服务器1登录账号@同步服务器1IP:/",
"同步服务器2登录账号@同步服务器2IP:/",
"同步服务器3登录账号@同步服务器3IP:/",
"root@47.98.152.221:/",
}
// 子目录与目标路径的映射
var pathMap = map[string]string{
"本地服务器路径标识": "同步服务器路径(从跟路径起算)",
"本地服务器路径标识": "同步服务器路径(从跟路径起算)",
"Anxin": "www/wwwroot/Anxin",
}
func main() {
// 设置日志输出到文件
logFile, err := os.OpenFile("script.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("无法打开日志文件: %v", err)
}
defer logFile.Close()
log.SetOutput(logFile)
// 创建文件监控器
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// 监测更多事件类型
if (event.Op&fsnotify.Write == fsnotify.Write ||
event.Op&fsnotify.Create == fsnotify.Create ||
event.Op&fsnotify.Rename == fsnotify.Rename ||
event.Op&fsnotify.Chmod == fsnotify.Chmod) &&
strings.HasSuffix(event.Name, ".apk") { // 判断是否是.apk文件
// 去重逻辑
if lastTime, exists := lastEventTime[event.Name]; exists {
if time.Since(lastTime) < debounceDuration {
// 如果上次处理时间在去重时间间隔内,跳过此事件
continue
}
}
lastEventTime[event.Name] = time.Now()
log.Printf("监测到文件变更: %s, 事件类型: %v\n", event.Name, event.Op)
// 调用 scp 同步文件
err := syncFile(event.Name)
if err != nil {
log.Printf("文件同步失败: %v", err)
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("错误:", err)
}
}
}()
// 监控目录及子目录
err = filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
err = watcher.Add(path)
if err != nil {
log.Fatal(err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
<-done
}
// syncFile 使用 scp 同步文件到多台服务器的不同路径,并复制到本地对应目录
func syncFile(filePath string) error {
// 找到文件所属的子目录
subDir := ""
// 先按路径长度降序排序,确保长名称优先匹配
for _, dir := range sortedKeys(pathMap) {
if strings.Contains(filePath, dir) {
subDir = dir
break
}
}
if subDir == "" {
return fmt.Errorf("文件没有匹配的目标目录: %s", filePath)
}
// 获取同步目标路径
targetPath := pathMap[subDir]
fmt.Println("匹配目录:", targetPath)
// 执行 scp 命令同步文件到每个服务器
for _, server := range servers {
// 组合目标路径
destination := fmt.Sprintf("%s%s", server, targetPath)
// 构建并执行 scp 命令
cmd := exec.Command("scp", filePath, destination)
// 捕获输出和错误信息
output, err := cmd.CombinedOutput()
if err != nil {
log.Printf("执行 scp 命令失败: %v, 输出: %s", err, output)
return err
}
log.Printf("文件 %s 同步到 %s\n", filePath, destination)
}
// 复制文件到本地对应的目录
localDestination := filepath.Join(rootDir, targetPath)
err := copyFileToLocal(filePath, localDestination)
if err != nil {
log.Printf("文件复制到本地失败: %v", err)
return err
}
log.Printf("文件 %s 复制到本地目录 %s\n", filePath, localDestination)
return nil
}
// copyFileToLocal 将文件复制到本地目录
func copyFileToLocal(srcPath, dstDir string) error {
// 确保目标目录存在
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
return fmt.Errorf("创建本地目录失败: %v", err)
}
// 获取文件名
fileName := filepath.Base(srcPath)
// 目标文件路径
dstPath := filepath.Join(dstDir, fileName)
// 打开源文件
srcFile, err := os.Open(srcPath)
if err != nil {
return fmt.Errorf("打开源文件失败: %v", err)
}
defer srcFile.Close()
// 创建目标文件
dstFile, err := os.Create(dstPath)
if err != nil {
return fmt.Errorf("创建目标文件失败: %v", err)
}
defer dstFile.Close()
// 复制文件内容
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("复制文件内容失败: %v", err)
}
return nil
}
// sortedKeys 返回按长度降序排序的键列表
func sortedKeys(m map[string]string) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return len(keys[i]) > len(keys[j])
})
return keys
}
plaintext