当前位置:主页 > 脚本语言 > Golang >

go语言心跳超时的实现示例

时间:2023-02-28 11:47:35 | 栏目:Golang | 点击:

一、背景

本文描述的是客户端接收心跳信息的超时实现。心跳超时,或者接受信息超过限定时间在分布式系统中出现的次数比较多。常见的就有hadoop中节点超时,或者日志中出现timeout的字样。

在学习go语言中,我也根据go语言的机制实现了心跳超时的这个问题。踩过坑,趟过水。

二、心跳超时的实现

2.1 通过select case (设计概念比较多)

这种方法实现心跳,需要对go语言中的channel和select case 机制有所了解。select代码段中没有包含default条件时,会一直阻塞到有通道操作。

需要注意的是!!!! select语言只会阻塞一次,且执行一次。如果需要多次判断,或者可能有多个case条件需要满足,那就需要增加for语句。

首先需要知道的是select是专为channel设计的,所以说每个case表达式都必须是包含操作通道的表达式。下面这段代码是描述了随机抽取一个channel发消息,正常情况下,不会触发超时。为了触发超时,注释掉通道发送数据操作。超时五秒,则触发超时。

package main
 
import (
    "fmt"
    "math/rand"
    "time"
)
 
func main() {
    // 准备好三个通道。
    intChannels := [3]chan int{
        make(chan int, 1),
        make(chan int, 1),
        make(chan int, 1),
    }
    // 随机选择一个通道,并向它发送元素值。
    index := rand.Intn(3)
    fmt.Printf("The index: %d\n", index)
 
    //?? 取消这行代码的注视,超时条件的选择就会触发。
    //intChannels[index] <- index
    // 哪一个通道中有可取的元素值,哪个对应的分支就会被执行。
    select {
    case <-intChannels[0]:
        fmt.Println("The first candidate case is selected.")
    case <-intChannels[1]:
        fmt.Println("The second candidate case is selected.")
    case elem := <-intChannels[2]:
        fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
    case <-time.After(5 * time.Second):
        fmt.Println("timed out")
    }
}

2.2 通过time.sleep(简单有效)

通过time.sleep()实现超时操作,是比较巧妙的。一般来说心跳超时是一个双方交互的行为。

下面画一个图来描述一下。

 为了方便理解,定义双方都使用共同时间。

下面是代码。

基本的逻辑是:

        1、先给客户端设置一个下次超时的时间

         2、客户端每次收到心跳的时候,更新这个时间

         3、开启一个独立的线程,一致判断当前客户端是否超时。

ps:结合时效和性能,可以间隔一定的时间来进行判断。

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
type Client struct {
    lock sync.Mutex //加锁
    nextTimeOutTime time.Time //下次超时时间
}
 
const tenSec = 10
/**
刷新每次的心跳超时机制
 */
func (client *Client) freshTimeOutTime()  {
    client.lock.Lock()
    defer client.lock.Unlock()
    client.nextTimeOutTime =time.Now().Add(tenSec*time.Second)
}
 
//开启一个gp,每隔500ms判断有没有超时
func (client *Client) judgeTimeOut()  {
    for  {
        time.Sleep(500*time.Millisecond)
        fmt.Printf("%v 在判断是否超时\n", client.nextTimeOutTime)
        if time.Now().After(client.nextTimeOutTime) {
            fmt.Printf("%v 超时了\n", client.nextTimeOutTime)
        }
    }
}
 
//客户端收到以后,修改下次心跳超时时间
func (client *Client) receiveHeart()  {
    client.freshTimeOutTime()
}
 
//开启一个模拟ping 客户端的线程
func pingClient(client *Client)  {
    for true {
        time.Sleep(11*time.Second)
        fmt.Printf("%v 请求发送时间\n", time.Now())
        client.receiveHeart()
    }
 
}
 
func main() {
    client := Client{
        lock:            sync.Mutex{},
        nextTimeOutTime: time.Time{},
    }
    //在当前时刻,更新下次的超时时刻是10s中后
    client.freshTimeOutTime()
 
 
    go pingClient(&client)
 
 
    go client.judgeTimeOut()
 
    for true {
 
    }
}

三、个人的实现观感

使用select case 和 time.sleep实现超时的最大区别在于,time.sleep没有太多的?语言相关的语法和知识,更容易理解和掌握。相对于channel来说,掌握需要了解channel的基本使用方法,一些常见的特性等。

您可能感兴趣的文章:

相关文章