go ntp时间同步

go ntp时间同步

直接上代码

ntp_sync_time.go

go 复制代码
package ntp

import (
	"context"
	"fmt"
	"math"
	"sync"
	"sync/atomic"
	"time"

	"github.com/beevik/ntp"
)

/*
 * 通过ntp方式去同步时间
 *
 * CreateTime: 2023-05-19 17:31
 */

// ntpServers 用于ntp同步时间的服务器域名列表
var ntpServers = []string{
	"time.windows.com",
	"time.tencent.com",
	"ntp.tencent.com",
	"0.centos.pool.ntp.org",
	"0.rhel.pool.ntp.org",
	"0.debian.pool.ntp.org",
	"time.izatcloud.net",
	"time.gpsonextra.net",
	"0.beevik-ntp.pool.ntp.org",
	"0.cn.pool.ntp.org",
	"ntp.aliyun.com",
	"time.apple.com",
	"pool.ntp.org",
	"0.pool.ntp.org",
	"asia.pool.ntp.org",
	"cn.pool.ntp.org",
	"ntp0.ntp-servers.net",
	"time.pool.aliyun.com",
	"time.cloud.tencent.com",
	"time.asia.apple.com",
	"time.euro.apple.com",
	"time.google.com",
	"time.facebook.com",
	"time.cloudflare.com",
	"clock.sjc.he.net",
	"clock.fmt.he.net",
	"clock.nyc.he.net",
	"ntp.ix.ru",
	"ntp.jst.mfeed.ad.jp",
	"ntp.ntsc.ac.cn",
	"ntp1.nim.ac.cn",
	"stdtime.gov.hk",
	"time.hko.hk",
	"time.smg.gov.mo",
	"tick.stdtime.gov.tw",
	"time.kriss.re.kr",
	"ntp.nict.jp",
	"s2csntp.miz.nao.ac.jp",
	"time.nist.gov",
	"time.nplindia.org",
	"ntp.pagasa.dost.gov.ph",
	"ntp1.sirim.my",
	"ntp1.vniiftri.ru",
	"vniiftri.khv.ru",
	"master.ntp.mn",
	"slave.ntp.mn",
	"ntp.mn",
	"ntp.atlas.ripe.net",
	"time.ustc.edu.cn",
	"ntp.sjtu.edu.cn",
	"ntp.neu.edu.cn",
	"time.jmu.edu.cn",
	"ntp.neusoft.edu.cn",
	"time.edu.cn",
	"ntp.nc.u-tokyo.ac.jp",
	"ntp.kuins.kyoto-u.ac.jp",
	"ntp.tohoku.ac.jp",
}

// ntpOffSetTime 经过ntp同步后确认的时间差值
var ntpOffSetTime time.Duration

// ntp同步时间结果
var syncTimeCh = make(chan bool, 1)

const (
	ntp_Sync_Time           = time.Hour * 3    // 每3个小时同步一次时间
	max_Sync_Cnt            = 10               // 最大同步协程数
	check_system_time       = time.Second * 30 // 检查系统时间是否发生变化间隔时间
	system_time_valid_range = 200              // 系统时间误差时间范围
	max_retry_sync_cnt      = 5                // 最多重试同步次数
)

func init() {
	// 开协程,进行首次的同步时间操作
	go retrySyncNtpTime()
	// 开协程,定期做一次同步时间操作
	go loopSyncNtpTime()
	// 开协程,定期检查系统时间是否发生变化
	go loopCheckSystemTime()
}

// 重试同步时间
func retrySyncNtpTime() {
	fmt.Println("开始同步ntp时间-------------------")
	var bSyncTime bool
	for i := 0; i < max_retry_sync_cnt; i++ {
		// 同步时间操作, 同步到数据, 则直接退出
		if asyncNtpTime() {
			bSyncTime = true
			break
		}
	}

	// 写入同步结果
	select {
	case syncTimeCh <- bSyncTime:
	default:
	}
	fmt.Println("结束同步ntp时间-------------------")
}

// 系统时间发生变化管道
var chSystemTimeChange = make(chan interface{}, 1)

// 定期检查系统时间是否发生变化
func loopCheckSystemTime() {
	// 获取当前的毫秒时间
	lastCheckTime := time.Now().UnixMilli()
	// 定时器
	ticker := time.NewTicker(check_system_time)
	// 检查次数
	var nCheckTime int64
	for {
		<-ticker.C
		// 获取当前时间
		nowTime := time.Now().UnixMilli()
		// 自增检查次数
		nCheckTime++
		// 获取期望时间
		nExpectTime := lastCheckTime + nCheckTime*check_system_time.Milliseconds()
		// 检查实际时间和期望时间的差值
		nDiffTime := nExpectTime - nowTime

		// 如果误差时间大于指定的值, 则会触发更新ntp时间操作, 同时会立刻更改ntp时间差值
		if math.Abs(float64(nDiffTime)) >= system_time_valid_range {
			// engine.Log.Error("更改时间 diffTime:%d", nDiffTime)
			offsetTime := ntpOffSetTime.Milliseconds() + nDiffTime

			// 更新ntp时间差值
			atomic.StoreInt64((*int64)(&ntpOffSetTime), offsetTime*int64(time.Millisecond))

			// 更新最后检测时间及次数
			lastCheckTime = nowTime
			nCheckTime = 0

			// 触发同步时间操作
			select {
			case chSystemTimeChange <- struct{}{}:
			default:
			}
		}
	}
}

/*
 * loopSyncNtpTime 定时触发同步ntp时间操作
 */
func loopSyncNtpTime() {
	// 定时器
	ticker := time.NewTicker(ntp_Sync_Time)
	defer ticker.Stop()

	// 通过定时器触发同步
	for {
		// 等待定时器触发
		select {
		case <-ticker.C:
		case <-chSystemTimeChange:
			// 因为系统时间触发同步操作
			ticker.Reset(ntp_Sync_Time)
		}

		// 同步ntp服务器时间
		syncNtpTime()
	}
}

/*
 * syncNtpTime ntp同步服务器时间
 *
 * 依次遍历ntp服务器进行同步,只要同步到,就退出
 */
func syncNtpTime() {
	// 依次遍历ntp服务器,只要有一个能获取到,就会退出
	for i := 0; i < len(ntpServers); i++ {
		ntpResp, err := ntp.Query(ntpServers[i])
		if err != nil {
			// engine.Log.Error("[%s] 无法访问了, err:%s", ntpServers[i], err)
			continue
		}
		// engine.Log.Info("同步到时间[%s] %d", ntpServers[i], ntpResp.ClockOffset)

		// 获取到ntp信息,保存时间差值
		atomic.StoreInt64((*int64)(&ntpOffSetTime), int64(ntpResp.ClockOffset))
		break
	}
}

/*
 * asyncNtpTime 异步进行ntp同步服务器时间
 *
 * 开启10个协程, 同时去同步ntp时间, 只要有一个同步到就可以退出了
 */
func asyncNtpTime() bool {
	getValue := false                                       // 是否获取到值标识
	ctx, cancel := context.WithCancel(context.Background()) // cancel的context
	offSetCh := make(chan time.Duration, 1)                 // 获取时间差值管道

	// 启动协程,获取时间差值内容
	go func() {
		select {
		case offSetTime := <-offSetCh: // 获取到结果,更新ntp服务器时间差值
			// engine.Log.Info("Get value!!! %s", offSetTime)
			atomic.StoreInt64((*int64)(&ntpOffSetTime), int64(offSetTime))
			getValue = true // 触发获取数据状态
			cancel()        // 触发context关闭
		case <-ctx.Done(): // 所有的请求都执行完,却没有获取到时间差值
			// engine.Log.Info("启动协程,获取时间差值内容 ctx.Done()!!!")
		}
	}()

	taskCh := make(chan string, 1) // 任务管道
	var wg sync.WaitGroup
	// 启动指定的协程,从任务管道中竞争资源,进行获取ntp时间操作
	for i := 0; i < max_Sync_Cnt; i++ {
		wg.Add(1)
		go func(i int, ctx context.Context) {
			defer wg.Done()
			// defer engine.Log.Error("11111 [goroutine%d] 退出!!!!\n", i)
			// var nIndex int

			for v := range taskCh {
				// nIndex++
				// engine.Log.Info("11111 [goroutine%d] 开始处理第%d个数据", i, nIndex)

				var ntpResp *ntp.Response                                // ntp时间同步返回值
				var err error                                            // 获取错误信息
				ctx2, cancel2 := context.WithTimeout(ctx, time.Second*5) // 超时context
				chNetReq := make(chan interface{}, 1)                    // 获取连接结果管道
				// 启动协程,获取请求,拿到结果后,写入连接结果管道
				go func() {
					ntpResp, err = ntp.Query(v)
					select {
					case chNetReq <- struct{}{}:
					default:
					}
				}()

				// 等待结果、超时、或运行结束标识
				select {
				case <-chNetReq: // 等到请求结果返回
					cancel2()
					if err != nil || ntpResp == nil {
						// engine.Log.Info("11111 [goroutine%d][%d] %s 无法访问了 %s", i, nIndex, v, err)
						continue
					}
					// engine.Log.Info("11111 [goroutine%d][%d] %s 获取到数据", i, nIndex, v)

					// 结算时间差值
					offSetTime := ntpResp.ClockOffset

					// 尝试将时间差值写入结果中
					select {
					case offSetCh <- offSetTime:
						// engine.Log.Info("11111 [goroutine%d][%d] %s 成功写入数据", i, nIndex, v)
					default:
					}
					return
				case <-ctx2.Done(): // 超时,或触发结束标识
					// engine.Log.Info("11111 [goroutine%d][%d] %s 超时或触发结束标识", i, nIndex, v)
					cancel2()
				}
			}
		}(i, ctx)
	}

	// 依次将请求写入到任务管道中
	for i := 0; i < len(ntpServers); i++ {
		taskCh <- ntpServers[i]
		// engine.Log.Info("22222 写入到channel中 %d", i)
		if getValue {
			cancel()
			// engine.Log.Info("22222 get value!!! break for range")
			break
		}
	}
	// 关闭管道,从而使所有的协程处理完任务后,不再继续等待任务,退出执行协程
	close(taskCh)

	// 等待所有任务执行完毕
	wg.Wait()

	cancel() // 运行结束,关闭context

	return getValue
}

/*
 * GetNtpTime 获取ntp同步之后的当前时间
 *
 * @return	ntpNowTime	time.Time	同步ntp后的当前时间
 */
func GetNtpTime() time.Time {
	// 当前时间,加上同步的时间差,就可以得到同步的时间
	offSetTime := atomic.LoadInt64((*int64)(&ntpOffSetTime))
	t := time.Now()
	ntpNowTime := t.Add(time.Duration(offSetTime))

	return ntpNowTime
}

/*
 * GetNtpOffsetTime 获取ntp的时间差值
 *
 * @return	offSetTime	time.Duration	ntp时间差值
 */
func GetNtpOffsetTime() time.Duration {
	return time.Duration(atomic.LoadInt64((*int64)(&ntpOffSetTime)))
}

/*
 * TriggerNtpSync 主动触发ntp进行时间同步, 不需要等待是否执行成功的结果
 */
func TriggerNtpSync() {
	// 触发ntp时间同步操作
	go asyncNtpTime()
}

/*
 * TriggerNtpSyncWaitRes 主动触发ntp进行时间同步, 并返回触发是否成功标识
 *
 * @return res		bool	是否同步成功
 */
func TriggerNtpSyncWaitRes() bool {
	// 触发ntp时间同步操作
	return asyncNtpTime()
}

/*
 * WaitSyncTimeFinish 等待ntp时间同步结果
 *
 * @return	res		bool	是否同步成功
 */
func WaitSyncTimeFinish() bool {
	// 获取同步结果
	res := <-syncTimeCh

	// 写回同步结果
	select {
	case syncTimeCh <- res:
	default:
	}

	return res
}

测试代码

go 复制代码
package ntp

import (
	"testing"
	"time"

	"gitee.com/prestonTao/libp2parea/engine"
)

func TestNtp(t *testing.T) {
	time.Sleep(time.Second * 5)

	t1 := time.Now()
	ntpt := GetNtpTime()
	t2 := time.Now()
	engine.Log.Info(ntpt.String())
	engine.Log.Info(t1.String())
	engine.Log.Info(t2.String())
	engine.Log.Info(GetNtpOffsetTime().String())

	for i := 0; i < 10; i++ {
		time.Sleep(time.Second * 30)
		t := GetNtpTime()
		engine.Log.Info(t.String())
		engine.Log.Info(GetNtpOffsetTime().String())
		engine.Log.Info("----------------")
	}
}

func TestNtpSync(t *testing.T) {
	time.Sleep(time.Second * 5)

	t1 := time.Now()
	ntpt := GetNtpTime()
	t2 := time.Now()
	engine.Log.Info(ntpt.String())
	engine.Log.Info(t1.String())
	engine.Log.Info(t2.String())
	engine.Log.Info(GetNtpOffsetTime().String())

	for i := 0; i < 10; i++ {
		time.Sleep(time.Second * 30)
		// TriggerNtpSync()
		TriggerNtpSyncWaitRes()
		t := GetNtpTime()
		engine.Log.Info(t.String())
		engine.Log.Info(GetNtpOffsetTime().String())
		engine.Log.Info("----------------")
	}
}

func TestNtpSystemTimeChange(t *testing.T) {
	// 测试修改系统时间, ntp的同步机制是否正常
	time.Sleep(time.Second * 5)

	engine.Log.Info("Ntp 差值:%s", GetNtpOffsetTime().String())

	for {
		time.Sleep(time.Second * 5)
		engine.Log.Info("----------------")
		engine.Log.Info("Ntp 时间:%s", GetNtpTime().String())
		engine.Log.Info("Ntp 差值:%s", GetNtpOffsetTime().String())
		engine.Log.Info("----------------")
	}
}
相关推荐
hkNaruto13 小时前
【P2P】【Go】采用go语言实现udp hole punching 打洞 传输速度测试 ping测试
golang·udp·p2p
入 梦皆星河13 小时前
go中常用的处理json的库
golang
海绵波波10715 小时前
Gin-vue-admin(2):项目初始化
vue.js·golang·gin
每天写点bug15 小时前
【go每日一题】:并发任务调度器
开发语言·后端·golang
一个不秃头的 程序员15 小时前
代码加入SFTP Go ---(小白篇5)
开发语言·后端·golang
基哥的奋斗历程16 小时前
初识Go语言
开发语言·后端·golang
ZVAyIVqt0UFji1 天前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
唐墨1231 天前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang
老大白菜1 天前
FastAPI vs Go 性能对比分析
开发语言·golang·fastapi
千年死缓1 天前
golang结构体转map
开发语言·后端·golang