目录
  • 一、背景
  • 二、心跳超时的实现
    • 2.1 通过select case (设计概念比较多)
    • 2.2 通过time.sleep(简单有效)
  • 三、个人的实现观感

    一、背景

    本文描述的是客户端接收心跳信息的超时实现。心跳超时,或者接受信息超过限定时间在分布式系统中出现的次数比较多。常见的就有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()实现超时操作,是比较巧妙的。一般来说心跳超时是一个双方交互的行为。

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

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

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

    下面是代码。

    基本的逻辑是:

            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没有太多的

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。