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("----------------")
	}
}
相关推荐
pursue_my_life2 小时前
Golang中间件的原理与实现
开发语言·后端·中间件·golang
HashFlag4 小时前
Go常用的设计模式
开发语言·设计模式·golang
谦虚使人发胖4 小时前
Golang使用 ip2region 查询IP的地区信息
服务器·开发语言·golang
demonlg01124 小时前
Go 语言标准库中database模块详细功能介绍与示例
开发语言·数据库·golang
demonlg01124 小时前
Go 语言标准库中reflect模块详细功能介绍与示例
开发语言·后端·golang
demonlg01126 小时前
Go 语言标准库中log模块详细功能介绍与示例
开发语言·后端·云原生·golang
demonlg01128 小时前
Go 语言标准库中strings和strconv详细功能介绍与示例
开发语言·后端·云原生·golang
YGGP9 小时前
Golang 的 GMP 调度机制常见问题及解答
开发语言·网络·golang
LuckyLay10 小时前
LeetCode算法题(Go语言实现)_15
算法·leetcode·golang
景天科技苑12 小时前
【go微服务】如何快速掌握grpc开发
开发语言·微服务·golang·grpc框架·grpc加密传输·go grpc