Go 实现心跳

Go 实现心跳

心跳最典型的应用场景是是探测服务是否存活,比如在 Zookeeper 中,会使用心跳探测服务是否存货,如果服务已经死亡,会将服务从注册表中删除,避免服务请求路由到一个已经宕机的服务中。

Go 中实现心跳机制可以通过 time.NewTimeTicker(), 配合 channel 使用,就可以实现一个简单的心跳程序:

go 复制代码
import (
	"code.byted.org/gopkg/logs"
	"context"
	"fmt"
	"testing"
	"time"
)

func sendHeartbeat(heartbeatChan chan<- time.Time) {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	for {
		select {
		case t := <-ticker.C:
			heartbeatChan <- t
		}
	}
}

func TestHeartbeat(t *testing.T) {

	heartbeatChan := make(chan time.Time)
	go doWork([]int{1, 2, 3, 5, 5}, heartbeatChan)
	for t := range heartbeatChan {
		// 上报心跳包的逻辑,可以在这里实现
		fmt.Println("Received heartbeat at", t)
	}
    fmt.Println("heartbeat finished")
}

func doWork(nums []int, heartbeatChan chan time.Time) {
	go sendHeartbeat(heartbeatChan)
	defer func() {
		close(heartbeatChan)
	}()
	for num := range nums {
        time.Sleep(1 * time.Second)
		fmt.Println(num)
	}
}

执行结果如下:

go 复制代码
=== RUN   TestHeartbeat
0
Received heartbeat at 2024-01-31 11:30:46.3363 +0800 CST m=+1.002830252
Received heartbeat at 2024-01-31 11:30:47.335975 +0800 CST m=+2.002513164
1
Received heartbeat at 2024-01-31 11:30:48.336252 +0800 CST m=+3.002795914
2
Received heartbeat at 2024-01-31 11:30:49.33622 +0800 CST m=+4.002768883
3
Received heartbeat at 2024-01-31 11:30:50.336222 +0800 CST m=+5.002775378
4
heartbeat finished
--- PASS: TestHeartbeat (5.00s)
PASS

心跳程序

客户端发送心跳请求, 并通过重试机制。判断重试X次失败认为服务离线 服务端响应心跳请求,通过超时机制。超时X秒未收到心跳则判断客户端离线

go 复制代码
package main

import (
	"fmt"
	"net"
	"time"
)

func main() {
	// 启动服务端
	go startServer()

	// 启动客户端
	go startClient()

	// 保持 main goroutine 活跃,避免程序退出
	select {}
}

func startServer() {
	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer ln.Close()

	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println(err)
			continue
		}
		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	// 使用 bufio 包中的 ReadWriter 类型,方便读写字符串
	rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))

	// 启动一个 goroutine,定时发送心跳消息
	go func() {
		for {
			time.Sleep(time.Second)
			if _, err := rw.WriteString("heartbeat\n"); err != nil {
				fmt.Println(err)
				return
			}
			if err := rw.Flush(); err != nil {
				fmt.Println(err)
				return
			}
		}
	}()

	// 循环读取客户端发送的消息
	for {
		line, err := rw.ReadString('\n')
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("received:", line)
	}
}

func startClient() {
	conn, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()

	//
}

参考资料

相关推荐
再吃一根胡萝卜5 小时前
微服务治理的“四大护法”:从 Django 视角理解 Spring Cloud 核心组件
后端
再吃一根胡萝卜5 小时前
面试终极挑战:如何用 Django 经验,回答 Java 微服务实战问题?
后端
To_OC7 小时前
装完 ESLint 它一声不吭?我还以为代码写得多好
后端·node.js·eslint
凤山老林8 小时前
精细化流量治理:Spring Boot 动态特性开关与灰度发布体系
java·spring boot·后端
vipbic8 小时前
一个前端的 9 天重构:我是怎么用 Codex 重做航栈的
前端·javascript·后端
考虑考虑11 小时前
Excel导入时产生特殊字符处理
java·后端·java ee
IT_陈寒11 小时前
Redis的DEL命令竟然没删掉数据?我踩的这个坑你得知道
前端·人工智能·后端
用户9385156350711 小时前
Docker + Nginx + Node.js:从“我的电脑能跑,你的电脑跑不了”到一键部署
后端·nginx·docker
陆枫Larry11 小时前
两步验证(2FA )到底是什么?
后端
JavaGuide11 小时前
我用 Claude Code/ZCode+GLM-5.3 从零做了一款 Agent 游戏!
前端·后端