在 Golang 中使用心跳模式

在 golang 中使用心跳模式

在 go 中实现心跳以进行应用程序监控

在平衡数据和软件工程师的过程中,我总是在 golang 中寻找一些不同的东西来学习,了解它是如何工作的,并将其应用到比我在互联网上找到的一些基本传统课程和文章更复杂的事情上。在这篇短文中,我将报告并演示我如何通过 go routines 实现,使用 ticker 来模拟应用程序的心跳(“我还活着”)的时间包,以及通道的使用等。

对于许多人来说,确保调用某个函数的人知道该函数是否正在花费时间、正在处理或处于锁定状态是极其重要的,这对许多人来说并不是新闻。也就是说,出现了其他几个术语,例如跟踪、指标、连接性等,这些术语已在监控应用程序中引入,这些应用程序在大多数情况下使用安装在应用程序服务器上的代理来收集指标并将其发送到可视化所有(或几乎)您的申请状态。这些工具中我们有datadog、newrelic、slack、grafana、jaeger等

我们在这里会得到什么?

当我研究和思考创建一些快速简单的东西来解决一些更高级的 go 概念时,我创建了一个相对简单的应用程序,它利用了心跳模式。无论谁给我打电话,都会收到结果,同时还会收到我是否仍然活跃的信息。在更高级的场景中,根据某些业务特殊性自定义实际上是活动的应用程序可能会很有趣,因为 prometheus 的简单实现可以解决这种情况(应用程序是活动的吗?cpu、内存、打开的 goroutine),但是没有同步和可定制的反馈。

编程一小时!

在结构方面,我只使用 go mod 在包中创建了三个文件:

  • dictionary.go:包含要搜索的函数的名称字典。
  • task.go:任务包含从字典中扫描名字的功能,同时通过频道+时间节拍通知其是否处于活动状态。
  • task_test.go:对task.go中存在的函数执行单元测试,以查看字典数据的响应以及有关应用程序是否仍在运行的反馈!

字典.go

这部分go代码定义了一个名为“dictionary”的变量,它是一个将符文类型字符与字符串关联起来的映射。

每个地图条目都是一个键(符文)和一个值(字符串)。在下面的示例中,键是字母表中的小写字母,值是与每个字母关联的名称。例如,字母“a”与名称“airton”相关联,字母“b”与名称“bruno”相关联,依此类推:

package heartbeat

var dicionario = map[rune]string{
    'a': "airton",
    'b': "bruno",
    'c': "carlos",
    'd': "daniel",
    'e': "eduardo",
    'f': "felipe",
    'g': "gustavo",
}

任务.go

我在完整代码之后更好地解释了代码的每个部分:

package heartbeat

import (
    "context"
    "fmt"
    "time"
)

func processingtask(
    ctx context.context, letras chan rune, interval time.duration,
) (<-chan struct{}, <-chan string) {

    heartbeats := make(chan struct{}, 1)
    names := make(chan string)

    go func() {
        defer close(heartbeats)
        defer close(names)

        beat := time.newticker(interval)
        defer beat.stop()

        for letra := range letras {
            select {
            case <-ctx.done():
                return
            case <-beat.c:
                select {
                case heartbeats <- struct{}{}:
                default:
                }
            case names <- dicionario[letra]:
                lether := dicionario[letra]
                fmt.printf("letra: %s 
", lether)

                time.sleep(3 * time.second) // simula um tempo de espera para vermos o hearbeats
            }
        }
    }()

    return heartbeats, names
}

导入依赖项

package heartbeat

import (
    "context"
    "fmt"
    "time"
)

这里有我的心跳包,它将负责实现在处理任务时以特定时间间隔发送“心跳”的功能。为此,我需要上下文(上下文管理)、fmt(用于字符串格式化)和时间来进行时间控制。

初始函数定义

func processingtask (
    ctx context.context, letras chan rune, interval time.duration,
) (<-chan struct{}, <-chan string) {

这是processingtask函数的定义,它接受ctx上下文、字母通道(接收unicode字符的通道)和时间间隔作为参数。该函数返回两个通道:一个心跳通道,为每个“心跳”发送一个空结构;一个名称通道,发送与收到的每个字符对应的字母名称。

渠道

heartbeats := make(chan struct{}, 1)
names := make(chan string)

这两行创建了两个通道:heartbeats 是一个缓冲通道,容量为一个元素,names 是一个无缓冲通道。

go routine 完成繁重的工作

go func() 
    defer close(heartbeats)
    defer close(names)

    beat := time.newticker(interval)
    defer beat.stop()

    for letra := range letras {
        select {
        case <-ctx.done():
            return
        case <-beat.c:
            select {
            case heartbeats <- struct{}{}:
            default:
            }
        case names <- dicionario[letra]:
            lether := dicionario[letra]
            fmt.printf("letra: %s 
", lether)

            time.sleep(3 * time.second) // simula um tempo de espera para vermos o hearbeats
        }
    }
}()

return heartbeats, names

这是一个匿名goroutine(或在新线程中运行的匿名函数),执行processingtask函数的主要逻辑。它使用 for-range 循环从字母通道读取字符。在循环内,使用 select 从可用选项中选择要执行的操作:

  • case 语句
  • case
  • case names

最后,该函数返回心跳并命名通道。

测试应用程序

task_test.go

package heartbeat

import (
    "context"
    "fmt"
    "testing"
    "time"
)

func TestProcessingTask(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)

    defer cancel()

    letras := make(chan rune)
    go func() {
        defer close(letras)
        for i := 'a'; i <= 'g'; i++ {
            letras <- i
        }
    }()

    heartbeats, words := ProcessingTask(ctx, letras, time.Second)

    for {
        select {
        case <-ctx.Done():
            return
        case <-heartbeats:
            fmt.Printf("Application Up! 
")

        case letra, err := <-words:
            if !err {
                return
            }
            if _, notfound := dicionario[rune(letra[0])]; !notfound {
                t.Errorf("Letra %s não encontrada", letra)
            }
        }
    }
}

这里我为前面解释过的processingtask 函数创建了一个go 单元测试。 testprocessingtask 测试函数创建一个超时为 20 秒的上下文和一个 unicode 字符(字母)通道。然后,匿名 goroutine 将歌词发送到歌词通道。然后使用上下文、unicode 字符通道和时间间隔调用processingtask 函数。它返回两个通道,一个心跳通道和一个单词通道。

然后测试函数使用 select 运行无限循环,该循环从三个通道读取:上下文、心跳通道和单词通道。

如果上下文被取消,测试循环就会终止。如果收到心跳,则会显示“application up!”打印到标准输出。如果接收到单词,则测试检查该单词是否存在于字母词典中。如果不存在,则测试失败并显示错误消息。

因此,这个单元测试测试我们的processingtask函数,该函数从一个通道接收字符,将字母名称发送到另一个通道,并在我使用时间限制的上下文中运行时发出“心跳”。啊……它还会检查发送到单词通道的字母名称是否存在于字典中。

我的结论

这段 go 代码阐释了 go 语言和单元测试的一些重要概念:

  • 上下文
  • 协程
  • 频道
  • 单元测试(使用 select 监控多个通道)

我的 github 上的完整项目:https://github.com/airtonlira/heartbeatsgolang

领英 - airton lira junior

以上就是在 Golang 中使用心跳模式的详细内容,更多请关注其它相关文章!