前言

在微服务-分布式架构中

  • 异步任务:用于降低接口时延或削峰,提升用户体验,降低系统并发压力;
  • 通知类RPC:用于微服务间状态变更,用户行为的联动等场景;

以上2种场景,都十分适合使用事件驱动来实现。

事件总线(Event Bus)在现代软件架构中扮演着重要角色,特别是在微服务架构和分布式系统 中。
事件总线:作为1个中间层,提供了异步的、解耦的通信机制,可以实现系统各组件之间的事件传递和响应。
例如:使用ArgoEvents事件总线集成ArgoWorkflows和ArgoCD。

 

image

 

事件总线的使用还具有以下几方面的重要意义

1. 解耦系统组件

事件总线使得系统组件之间的耦合度降低。

在传统的同步通信中,组件间的依赖关系较强,一个组件的变化可能会导致多个组件的变动。

而事件总线通过发布和订阅机制,使得生产者(例如 Argo Workflows)和消费者(例如 Argo CD)之间的关系解耦。

通过事件驱动的架构,工作流完成后的事件可以触发其他组件(如 Argo CD)的行为,而无需这些组件直接相互调用或依赖。

  • 工作流与部署同步:工作流执行的结果可以通过事件总线通知 Argo CD,而无需直接调用或知道 Argo CD 的内部细节。
  • 灵活扩展:事件总线使得你可以轻松地添加新的事件处理逻辑(例如,添加新的事件消费者),而不需要修改现有组件的代码。

2. 异步处理

事件总线支持异步通信,使得组件之间的交互不再阻塞其他操作。

系统中的1组件(例如 Argo Workflows)可以发布事件而不等待响应,其他组件(例如 Argo CD)可以订阅接收发布事件,在适当的时机处理事件。

这种异步方式有助于提高系统的响应速度和吞吐量

  • 提高性能:由于事件是异步发布的,工作流的执行不会被 Argo CD 的同步操作所阻塞。
  • 并发处理:事件总线可以处理多个并发事件,从而提高整个系统的处理能力。

3. 事件驱动的自动化

事件总线使得系统的自动化更加灵活和响应式。

通过定义事件和触发器,系统可以自动地根据特定条件响应事件,而无需人工干预。

例如:当ArgoWorkflows完成构建任务后,可以通过事件总线将结果传递给ArgoCD,触发自动部署。

  • 自动触发部署:通过事件驱动的方式,当工作流完成后,事件总线可以自动通知 Argo CD 同步应用,而无需人工操作。
  • 低延迟响应:事件总线可以实时捕获工作流的完成事件,并立即触发后续操作(如部署、测试等)。

4. 可扩展性

随着系统的不断扩展,新的事件源和事件处理器可以被动态地加入到系统中。

事件总线提供了1个中心化的事件流,允许多个组件根据自己的需要订阅和发布事件。这样,你可以根据需要添加更多的集成组件,而无需更改现有逻辑。

  • 集成其他服务:可以通过事件总线轻松集成其他系统或服务,例如 CI/CD 工具、监控系统或通知系统。
  • 灵活的订阅机制:消费者可以根据事件类型和条件进行选择性订阅,增强系统的灵活性和可扩展性。

5. 事件日志与监控

事件总线可以充当系统中的日志和监控中心,所有的事件都可以记录下来,便于系统的审计、调试和监控。

通过集中式的事件日志,开发人员和运维人员可以追踪系统中发生的每1个事件,从而提高问题排查和故障恢复的效率。

  • 事件追踪:通过事件总线,可以轻松追踪工作流的执行和状态变化,便于了解应用的当前状态。
  • 集中监控:事件总线允许集中监控和管理系统的状态,可以快速发现异常和瓶颈。

6. 事件总线为复杂工作流提供支持

在复杂的工作流中,可能涉及多个服务或应用之间的交互,事件总线可以在各个服务之间传递事件,确保各个步骤的顺序和依赖关系。

对于需要在不同时间触发多个操作的场景(例如 CI/CD 管道),事件总线提供了一个有效的解决方案。

  • 支持异步任务链:在多阶段的 CI/CD 流程中,事件总线可以协调各个任务的执行顺序,例如先执行单元测试,再执行构建,最后触发应用部署。
  • 多服务协作:事件总线帮助协调不同服务之间的交互,确保整个流程的正确执行。

总结-为什么需要事件总线?

事件总线的引入使得 Argo Workflows 和 Argo CD 之间的集成更加灵活、解耦和高效。其主要优势包括:
  • 解耦组件,使得不同服务之间的耦合性降低,增加系统灵活性。
  • 支持异步处理,避免同步调用的阻塞,提高系统性能。
  • 自动化触发,实现自动化的 CI/CD 流程,减少人工干预。
  • 良好的可扩展性,便于集成新组件或服务。
  • 集中式监控和日志,提高系统的可维护性和可调试性。
  • 支持复杂工作流,特别是在多步骤和多服务协作的场景中。
因此,事件总线不仅能帮助你实现自动化、解耦和异步处理,还能提升系统的可维护性和扩展性,适应更复杂和动态的业务需求。

一、CloudEvents是什么?

虽然事件驱动在分布式框架中应用广泛,但是对于事件的描述现在还缺乏1种统一描述和规范;

服务使用方和提供方往往要花费大量的时间沟通字段定义,凭直觉设计事件属性,并在将来的使用过程中会因为新增或修改事件的属性而增加很大的额外工作量。

随着云原生的到来,急需一种统一的事件定义和描述规范,以提供跨服务、跨平台的交互能力,而这就是CloudEvents项目的诞生背景。

CloudEvents是1个用通用格式描述事件数据的规范;

以提供跨服务、跨平台和跨系统的互操作性。目前得到了大量的行业关注,从主要的云提供商到流行的SaaS公司都有。CloudEvents由云原生计算基金会(CNCF)主办,于2018年5月15日获批为云原生沙盒级项目。

二、事件总线

事件总线是对发布-订阅模式的一种实现。

  • CloudEvents是1个事件数据格式的规范,它定义了事件应该是什么样子(即事件的数据结构和格式),让不同的系统可以理解并处理这些事件。就像是制定了一份通用的“事件格式标准”。

  • ArgoEvents是1个实现,它是1个框架,用于实际捕获、处理这些事件(比如 CloudEvents),并基于事件触发某些动作(比如启动工作流、部署应用等)。就像是一个系统,能根据收到的标准事件来执行具体操作。

它是1种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到1种解耦的目的。

事件总线是实现基于事件驱动模式的方式之一,事件发送者将事件消息发送到1个事件总线上;

  • 事件订阅者向事件总线订阅和接收事件,然后再处理接收到的事件。
  • 事件订阅者不仅可以接收和消费事件,它们本身也可以创建事件,并将它们发送到事件总线上。

1.CloudEvents和ArgoEvents区别和联系

  • CloudEvents:1种标准语言,比如“普通话”,让不同地区的人(系统)可以听懂对方在说什么。
  • ArgoEvents : 1个广播电台,  接收来自各地(事件源)的消息,并按照规则分发给订阅者或触发某些任务。

2.从事件总线接收事件

利用HTTP协议传输事件,在Gin中接收到事件并根据事件源,处理不同事件;

订阅发布总线(/cloudevents)相关事件,并处理相关消息

rootGroup.POST("/cloudevents", events.HandleCloudEvents)

func HandleCloudEvents(c *gin.Context) {
    ctx := c.Request.Context()
    //gin的http请求报文转换为cloudevents的binding.Message
    msg := httpProtocol.NewMessageFromHttpRequest(c.Request)
    var event *cloudevents.Event
    //cloudevents的binding.Message转换为cloudevents事件
    event, err := binding.ToEvent(context.Background(), msg)
    if err != nil {
        log.WithContext(ctx).Warnf("bind message to event failed: %s", err)
        _ = c.AbortWithError(500, err)
        return
    }
    log.WithContext(ctx).Infof(" HandleCloudEvents 接收到消息 ,  => %s", event.Source())

    var resEvent *cloudevents.Event
    //判断收到cloudevents事件源,根据事件源处理不同事件
    switch event.Source() {
    //https://li.chj.cloud/events/licloud-scm-service"
    case model.EventSourceRepoApi.Source:
        go handleRepoApiEvents(ctx, event)
    case model.EventSourceArgoWorkflow.Source:
        resEvent, err = handleArgoWorkflowEvents(ctx, event)
    case model.EventSourceSecurityScan.Source:
        resEvent, err = handleSecurityScanEvents(ctx, event)
    case model.EventSourceIamService.Source:
        resEvent, err = handleIamServiceEvents(ctx, event)
    default:
        log.Infof("no handler found for the event: %s", event.String())
        return
    }
    if err != nil {
        log.Errorf("handle cloud event failed, error: %v", err)
        c.Status(http.StatusInternalServerError)
        return
    }

    if resEvent != nil {
        //err = httpProtocol.WriteResponseWriter(context.Background(), binding.ToMessage(resEvent), http.StatusOK, c.Writer)
        //if err != nil {
        //    log.Errorf("return new cloud event failed, error: %+v", err)
        //    c.Status(http.StatusInternalServerError)
        //    return
        //}

        // 接收事件后处理事件,处理处理事件后响应事件处理结果给事件总线
        client, _ := cloudevents.NewDefaultClient()
        ctx := cloudevents.ContextWithTarget(context.Background(), apollo.Config.EventBridgeEndpoint)
        //响应事件处理结果给事件总线
        if result := client.Send(ctx, *resEvent); cloudevents.IsUndelivered(result) {
            log.WithContext(ctx).Errorf("failed to send, %v", result)
            //响应失败HTTP响应状态码设置为500
            c.Status(http.StatusInternalServerError)
            return
        }
    }

    log.WithContext(ctx).Debugf("handle cloud event successed, subject: %s", event.Subject())
}
View Code

3.发送事件到事件总线

从Apollo配置中心获取事件总线的地址;

func SendWorkflowStartedEvent(ctx context.Context, tenantId string, data *model.WorkflowEventData) {
    //1.创建事件结构体
    resEvent := cloudevents.NewEvent(cloudevents.VersionV1)
    //设置事件的Header
    resEvent.SetSource(model.EventSourceWorkflowService.Source)
    resEvent.SetType(model.EventSourceWorkflowService.TypeStarted)
    resEvent.SetID(uuid.New().String())
    resEvent.SetTime(time.Now())
    //1.1.新增额外的Header信息
    resEvent.SetExtension("tenantid", tenantId)
    //2.设置事件的Body
    err := resEvent.SetData(cloudevents.ApplicationJSON, data)
    if err != nil {
        log.WithContext(ctx).Errorf("set event data failed: %v", err)
    }
    log.WithContext(ctx).Infof("scm 开始回写 ack 状态, event: %+v ", resEvent)
    //3.发送事件到事件总线
    err = events.Send(resEvent)
    if err != nil {
        log.WithContext(ctx).Errorf("send cloud event failed, event: %s || error: %v", resEvent.String(), err)
    }
}
View Code

5.Argoworkflow-SDK

基于grpc协议的golang SDK

package main

import (
    "fmt"
    "github.com/argoproj/argo-workflows/v3/pkg/apiclient"
    "github.com/argoproj/argo-workflows/v3/pkg/apiclient/workflow"
    "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "k8s.io/client-go/tools/clientcmd"
    clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

var WorkflowClientAdmin apiclient.Client
var WorkflowContext context.Context

// argo_admin_token = Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IlByQWExdG55Rm9ianowdlM1dS1Za0hQaHdhUEhLaG1yd3JwOEhRcGlWY1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcmdvIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFyZ28tc2VydmVyLXRva2VuLXh4eDdmIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFyZ28tc2VydmVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYWRlZTM2ZWEtZTZkMi00OTkyLTgwNjItNDdiNGNjNWI0NDM3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmFyZ286YXJnby1zZXJ2ZXIifQ.IeBcvYzGOSQswzvyXSoS4DknxiUxSlFFd4v67LDgwj5sZO8ehUrxx7Aot2lCFuwBwpKnXvS1xNu-VtVL0LGrSmmEe6_5HEF-lWBmadC-dMkbMSl6GCT1xem4zqFGxin9kz7SDJsLYRxavTVZpiS_0OJ9MZ-YzpzSNMLG0Cbm0p6AAIsw_9xWnoSxmbKgxQ28yNCOYRD8am48Zj8_T3vM-OMxDQRV0dpuhzIZ_GZsnkADenRfZC-Y90y0mtTNzpzjQ0rpwXbRvyOD_XRimRyZdqK-jJx8kxdNQGsY2fh_QSwu5MA7Lul-sDTq6oswuA4qXsHpwMdqPsG7ooIDwxz8Pg
// argo_readonly_token = Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IlByQWExdG55Rm9ianowdlM1dS1Za0hQaHdhUEhLaG1yd3JwOEhRcGlWY1EifQ.eyJpc3MiOiJ
func main() {
    //设置登录Argoworkflow系统账号
    optsAdmin := apiclient.Opts{
        ArgoServerOpts: apiclient.ArgoServerOpts{URL: "argo-workflow.dev.k8s.chj.cloud:80",
            HTTP1: true},
        AuthSupplier: func() string {
            return "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IlByQWExdG55Rm9ianowdlM1dS1Za0hQaHdhUEhLaG1yd3JwOEhRcGlWY1EifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcmdvIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFyZ28tc2VydmVyLXRva2VuLXh4eDdmIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFyZ28tc2VydmVyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYWRlZTM2ZWEtZTZkMi00OTkyLTgwNjItNDdiNGNjNWI0NDM3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmFyZ286YXJnby1zZXJ2ZXIifQ.IeBcvYzGOSQswzvyXSoS4DknxiUxSlFFd4v67LDgwj5sZO8ehUrxx7Aot2lCFuwBwpKnXvS1xNu-VtVL0LGrSmmEe6_5HEF-lWBmadC-dMkbMSl6GCT1xem4zqFGxin9kz7SDJsLYRxavTVZpiS_0OJ9MZ-YzpzSNMLG0Cbm0p6AAIsw_9xWnoSxmbKgxQ28yNCOYRD8am48Zj8_T3vM-OMxDQRV0dpuhzIZ_GZsnkADenRfZC-Y90y0mtTNzpzjQ0rpwXbRvyOD_XRimRyZdqK-jJx8kxdNQGsY2fh_QSwu5MA7Lul-sDTq6oswuA4qXsHpwMdqPsG7ooIDwxz8Pg"
        },
        ClientConfigSupplier: func() clientcmd.ClientConfig {
            overridesCustom := &clientcmd.ConfigOverrides{}
            //overridesCustom.Timeout = "2s"
            configEmpty := *clientcmdapi.NewConfig()
            return clientcmd.NewDefaultClientConfig(configEmpty, overridesCustom)
        },
    }
    //根据不同系统账号创建不同的Client类型
    _, WorkflowClientAdmin, _ = apiclient.NewClientFromOpts(optsAdmin)
    //生成操作Argoworkflow的客户端
    client := WorkflowClientAdmin.NewWorkflowServiceClient()
    //设置grpc请求体内容
    in := new(workflow.WorkflowSubmitRequest)
    //CRD资源类型=WorkflowTemplate
    in.ResourceKind = "WorkflowTemplate"
    //WorkflowTemplate名称和CRD资源保持一致
    in.ResourceName = "licloud-inst-dheklh-vue-test-npmrc-pl0925"
    //WorkflowTemplate所在的namespace
    in.Namespace = "argo"
    //提交参数设置workflow工作流的Labels、Annotations、Name字段
    in.SubmitOptions = &v1alpha1.SubmitOpts{
        Parameters: []string{"branch=master", "branch_name=master", "commitId=04b0a8ab", "commitMsg=新增测试用例v5",
            "gitUrl=https://gitlabee.chehejia.com/cd_1322019886_115745/vue-test-npmrc.git",
        },
        Labels:         "sss=1",
        Annotations:    "aaa=2",
        GenerateName:   "licloud-inst-dheklh-flask-test-pip-pl0828-python3-07-13-0906-",
        ServiceAccount: "argo",
    }
    var emptyCallOption grpc.EmptyCallOption
    //根据存在的WorkflowTemplate模板名称提交Workflow工作流
    result, err := client.SubmitWorkflow(WorkflowContext, in, emptyCallOption)
    if err != nil {
        fmt.Println("errr==================", err)
    } else {
        fmt.Println("ok==================", result)
    }

}
View Code

 

 

参考

posted on 2024-09-13 10:58  运维体系建设之路  阅读(20)  评论(0)    收藏  举报