热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何利用事件溯源思想实现分布式任务编排的容错?

如何利用事件溯源思想实现分布式任务编排的容错?-在做分布式系统集成的时候,当一个功能涉及到多个平台的时候,通常面对的问题都是如果失败了怎么办?今天就给大家分享一个新思路-基于事件溯

在做分布式系统集成的时候,当一个功能涉及到多个平台的时候,通常面对的问题都是如果失败了怎么办?今天就给大家分享一个新思路-基于事件溯源实现分布式协调

我们的挑战

在进行正式开始之前我们需要先介绍下我们的场景是什么,要解决的问题是什么。

场景

在应用管理平台建设中需要整合内部的多个平台,比如容器、虚机、监控、发布、cmdb、负载等多个平台,每个平台都只负责某一部分功能,但是比如我们要做一个虚机扩容、灰度发布等通常就需要操作多个平台;如果是全部都是基于k8s的可能还好一点,但是对于一些公司这种平台建设早于容器平台,这时候就得由应用管理平台来进行协调了

问题

在做一些业务开发时,比如订单支付通常会为了完成这个功能,多个服务会针对业务进行改造,比如使用tcc、saga等分布式事务模型来进行业务的一致性保障,其核心参考ACID的事务模型。而在应用平台建设中,首先对应的业务方不太会配合你进行改造,其次很多业务场景也不可能实现事务。比如你扩容创建了一台虚机,如果后续流程失败了,你总不能把机器给干掉吧?

思考

既然不能像业务一样通过传统的事务模型进行业务完整性保障,那我们何不换一种思路呢?于是基于稳定性的思考,笔者将设计思路转换成提高系统的容错能力,并尽可能的减小爆炸半径,同时尽可能的提升系统的可扩展性,保障高可用。

扩展

提到容错能力比较典型的场景就是数据处理场景了,这里先给大家介绍一下在分布式数据场景中是如何进行容错的。在分布式数据中,通常由source、process、sink三部分组成,而在很多场景中又要实现准确的exactly once,我们看看再flink里面是如何进行设计的, 这里先给大家介绍相关概念

checkpoint

checkpoint通常用于保存某些记录的位置信息用于方便系统故障后快速恢复,在flink中也利用了checkpoint机制来实现exactly once语义,其会按照配置周期性的计算状态生成检查点快照,然后将checkpoint持久化存储下来,这样后续如果崩溃则就可以通过checkpoint来进行恢复

barrier

checkpoint只作用于flink内部,那如果要实现从source到sink整个链路的exactly once,则就会涉及到多个组件同时做checkpoint的同步, 这时候就要让多个组件的checkpoint达到一致性, 为了实现这个功能flink里面引入了Barrier用于切分数据流;就类似编程语言中的内存屏障,通过Barrier让多个组件同时进行对于checkpoint的持久化。每个Barrier都会携带一个checkpoint ID,这样整个数据流的多个组件就会同时进行同一个checkpoint的持久化了

checkpointCoordinator

有了Barrier机制之后则就需要一个触发和管理组件,利用barrier和checkpoit让source、process、sink三者同时进行checkpoint保存,在flink中就引入checkpointCoordinator来协调多个组件, 有了这三个核心的概念,就可以让在flink中的多个分布式组件中实现checkpoint机制了

两阶段提交

前面的设计都是位于flink内部,但是在数据处理中source、sink组件则通常是第三方平台,这个时候如果还要保障exactly once则除了幂等性就需要用到我们这里说的两阶段提交了;要实现两阶段提交,则就需要对应的平台提供事务机制,在preCommit阶段做数据的消费和写入,同时在commit阶段实现事务的提交,由于事务未提交则对应的平台读取不到对应的数据,只有最终都提交成功后,才可以读取到写入的数据

总结

通过上面的我们了解了如何基于利用两阶段提交、checkpoint、barrier结合事务机制实现分布式环境中的exactly once实现机制,后续在数据处理的场景中,我们就可以利用这套机制结合实际业务场景进行落地了

在下一节我们将开始介绍分布式任务编排中的另外一种实现机制,用于实现分布式系统的容错解决上述场景中遇到的问题

基于event sourcing的分布式任务编排

事件溯源

事件溯源保证应用状态的所有改变都保存在事件流中. 这样我们不仅能查询这些事件,我们也可以通过这个事件的日志来重新构建以前的状态, 以些为基础实现自动改变状态来应对追溯过的变化.

其核心关键点:事件、顺序、持久化,通过对持久化存储中的事件按照顺序进行回放,我们就可以得到当前的状态,同理在任务编排的场景下,也可以借鉴类似的思想。

任务编排容错

任务编排的核心是通过编排对应的任务序列实现某个业务功能,在分布式环境中,通常会涉及到workflow任务的编排、task任务分配、运行时数据的存储等。在大多数的任务编排框架中,关注点都是任务调度。而我们今天接下来要介绍的temporal其关键点则是容错,即当对应的workflow、task如果执行失败,系统该如何进行恢复。也是事件溯源利用的主要场景。

任务执行容错语义

在前面的介绍exactly once场景中我们介绍过两阶段对事物机制的依赖,同理在任务编排中的状态,我们这里容错机制实现的语义是at-lease-once,即任务至少被执行一次,并尽可能保障业务不会重复被执行

溯源任务状态


结合事件溯源介绍下temporal里是如何基于事件溯源来实现容错语义的。在temporal一个workflow的当前状态,是由对应的workflow的事件reply来决定的,即通过回放workflow的所有事件来决定接下来该执行那个任务,在temporal里面的任务事件数据都由history服务统一存储,即事件数据的存储都是transaction的,这样就可以保障即使发生网络分区的情况,一个任务的执行结果也会只有一份, 那当我们要恢复任务状态的时候,就只需要通过事件回放,就知道接下来要执行那个任务,以及当前的状态数据

不变性

前面提到通过事件序列来进行事件回放可以得到当前状态,其实在任务编排场景中还有第二个序列-执行序列,即我们要执行的任务列表一定要是顺序的。只有这样才能顺着正确的道路继续恢复。

例如在go里面对slice的for range遍历是固定的,这里包含两部分:恢复slice和遍历slice, 即我再不同的机器上通过历史数据我可以构建出slice, 然后遍历这个slice这两个操作的结果都是一样的。

但是对map则不一定,我们并不能保证在不同机器上恢复和遍历这两个操作的结果都是一样的。所以workflow里面的逻辑和状态数据一定要是不变的

为什么是temporal

除了上面提到的容错,其实选择temporal更多的是就是易于学习和理解,大家可以看下我们创建虚机的workflow。

  • 如果出现异常则temporal会根据我们的重试策略自动进行重试,代码里面只有正常的业务逻辑
  • 如果我们需要等待任务的执行结果,就像写本地代码一样通过future.Get去获取结果
  • 如果执行能力不足,则就只需要加worker节点即可提高系统的分布式能力
  • 如果对于同一个资源申请单想要保障只有一个workflow,只需要在创建workflow的时候传入配置即可
// 创建虚机工作流
func CreateVMWorkflow(ctx workflow.Context, clientToken string, vmRequest cloud.CreateVMRequest, vmGroup ServerGroupt) (*CreateVMWorkflowResponse, error) {
    var (
        tvmTask     TVM
        response    CreateVMWorkflowResponse
        workflowCtx = workflow.WithActivityOptions(ctx, defaultTaskOptions)
    )

    // 创建虚机
    var createResponse *cloud.CreateVMResponse
    if err := workflow.ExecuteActivity(
        workflowCtx, tvmTask.CreateVMActivity, clientToken, vmRequest).Get(workflowCtx, &createResponse); err != nil {
        return nil, err
    }

    if !createResponse.Success() {
        return nil, errorx.StringError("create vm response error: %v", createResponse)
    }

    // 虚机初始化流程
    var futures []workflow.ChildWorkflowFuture
    for _, host := range createResponse.Data.Instance {
        future := workflow.ExecuteChildWorkflow(workflowCtx, WaitAndBindWorkflow, host, vmRequest.IDC, vmGroup)
        futures = append(futures, future)
    }

    // 等待虚机结果
    for _, future := range futures {
        var resp *AddServerLoadResponse
        if err := future.Get(workflowCtx, &resp); err != nil {
            response.Messages = append(response.Messages, err.Error())
            continue
        }
        if resp.Success {
            response.Success = append(response.Success, resp.IP)
            continue
        }
        response.Failure = append(response.Failure, resp.IP)
        response.Messages = append(response.Messages, resp.Message)
    }

    return response, nil
}
总结

temporal当然也有不足的地方,例如

  • 不支持dsl
  • 兼容性:同时针对Mysql分支版本比如BaikalDB就不太支持(致命bug),
  • 基于一致性hash的分片机制可能会存在任务分布不均
  • 资料太少,生产配置没有可以参考的优化(官方社区比较nice,反馈比较及时)

不过想想基于temporal可以快速实现一个分布式、可扩展、高容错、无状态的任务编排系统,其他都是小事情哈哈。后面有时间在给大家从源码上梳理下temporal的是如何实现上述功能的。包括任务分片、ringpop、信号、状态保存等

参考地址

什么是事件溯源:https://www.oschina.net/translate/event-sourcing?print

云原生学习笔记地址: https://www.yuque.com/baxiaoshi/tyado3)
微信号:baxiaoshi2020 公共号: 图解源码

微信号:baxiaoshi2020
关注公告号阅读更多源码分析文章
本文由博客一文多发平台 OpenWrite 发布!

推荐阅读
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 使用C++编写程序实现增加或删除桌面的右键列表项
    本文介绍了使用C++编写程序实现增加或删除桌面的右键列表项的方法。首先通过操作注册表来实现增加或删除右键列表项的目的,然后使用管理注册表的函数来编写程序。文章详细介绍了使用的五种函数:RegCreateKey、RegSetValueEx、RegOpenKeyEx、RegDeleteKey和RegCloseKey,并给出了增加一项的函数写法。通过本文的方法,可以方便地自定义桌面的右键列表项。 ... [详细]
author-avatar
黄ll明雪_742
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有