Droplet——一款轻量的Golang应用层框架
Droplet——一款轻量的Golang应用层框架 Github地址
如标题所描述的,Droplet 是一个 轻量 的 中间层框架,何为中间层呢? 通常来说,我们的程序(注意这里我们仅仅讨论程序的范围,而非作为一个系统,因此这里不设计如 LB、Gateway、Mesh等内容,因为它们都处于程序以外)按不同的职责可以分为不同的层次,而按照不同的设计风格,常见的如下:
三层架构:UIL(UserInterfaceLayer), BLL(BusinessLogicLayer), DAL(DataAccessLayer) DDD分层架构(参考ddd-oriented-microservice):ApplicationLayer,DomainLayer,InfrastructureLayer 洋葱架构(参考Onion Architecture ):Application, Infrastructure, ApplicationService, DomainService, DomainModel。 Tips
洋葱架构其实也是基于DDD的,它是DDD分层架构的升级版本。
但是今天我想用于解释中间层的架构并非以上的任何一种,它也源自于DDD的分层架构,不过我配合了六边形架构来说明它,分层图如下:
在六边形架构中有个规则:依赖只能是由外部指向内部。 因此从外层到最内层分别是:
分层 职责 Access 程序的接入层(在六边形架构中这被称为输入适配器),通常位于整个请求 or 任务的起点,它可能是某种Web框架,也可能是一些队列的消费框架等。 Application 程序应用层,包含了一些非业务的逻辑,如:业务逻辑的编排、参数绑定、校验、请求日志、链路上报、状态读取等等 Domain & Utils 在最中心的地方我放入了两个层次描述:Domain 与 Utils,这两个分层都应该是位于依赖的最底层,意味着他们不应该引用本项目的其他层次。Domain层主要包含核心的业务逻辑,而Utils则是一些程序任何地方都可能会引用的代码段,比如常量定义、数据结构和语法糖等等 Infrastructure 基础设施层(在六边形架构中这被称为输出适配器),程序所有需要对外进行信息交换 or 功能依赖时都会放置在这一层实现,通常来说这些功能都是被依赖的那部分,因此我们如果要满足依赖约束的话,这里必须要引入 DIP(Dependency inversion principle),即在Application、Domain中定义依赖,而 Infrastructure 来实现它们,这样保证了它们是可被替换的 六边形架构优点在于解耦程序中业务无关的部分,以保证它们都是可被替换与扩展的。 而 Droplet 就工作在 Application 层,它的核心能力只有一个:提供基于pipeline的请求/响应处理能力。 可能有人会疑问,几乎每个框架都会实现类似的能力,为什么我们需要 Droplet 呢? 别急,我们来看看这些框架自带的 pipeline/middleware 存在什么弊端。 根据上面的架构图我们可以知道诸如 gin、go-restful、fasthttp 之类的http框架都是工作在 Access 层,因此框架自带的 pipeline/middleware 存在以下两个弊端:
Fastflow——基于golang的轻量级工作流框架
Fastflow——基于golang的轻量级工作流框架 Fastflow 是什么?用一句话来定义它:一个 基于golang协程、支持水平扩容的分布式高性能工作流框架。 它具有以下特点:
易用性:工作流模型基于 DAG 来定义,同时还提供开箱即用的 API,你可以随时通过 API 创建、运行、暂停工作流等,在开发新的原子能力时还提供了开箱即用的分布式锁功能 高性能:得益于 golang 的协程 与 channel 技术,fastflow 可以在单实例上并行执行数百、数千乃至数万个任务 可观测性:fastflow 基于 Prometheus 的 metrics 暴露了当前实例上的任务执行信息,比如并发任务数、任务分发时间等。 可伸缩性:支持水平伸缩,以克服海量任务带来的单点瓶颈,同时通过选举 Leader 节点来保障各个节点的负载均衡 可扩展性:fastflow 准备了部分开箱即用的任务操作,比如 http请求、执行脚本等,同时你也可以自行定义新的节点动作,同时你可以根据上下文来决定是否跳过节点(skip) 轻量:它仅仅是一个基础框架,而不是一个完整的产品,这意味着你可以将其很低成本融入到遗留项目而无需部署、依赖另一个项目,这既是它的优点也是缺点——当你真的需要一个开箱即用的产品时(比如 airflow),你仍然需要少量的代码开发才能使用 为什么要开发 Fastflow 组内有很多项目都涉及复杂的任务流场景,比如离线任务,集群上下架,容器迁移等,这些场景都有几个共同的特点:
流程耗时且步骤复杂,比如创建一个 k8s 集群,需要几十步操作,其中包含脚本执行、接口调用等,且相互存在依赖关系。 任务量巨大,比如容器平台每天都会有几十万的离线任务需要调度执行、再比如我们管理数百个K8S集群,几乎每天会有集群需要上下节点、迁移容器等。 我们尝试过各种解法:
硬编码实现:虽然工作量较小,但是只能满足某个场景下的特定工作流,没有可复用性。 airflow:我们最开始的离线任务引擎就是基于这个来实现的,不得不承认它的功能很全,也很方便,但是存在几个问题 由 python 编写的,我们希望团队维护的项目能够统一语言,更有助于提升工作效率,虽然对一个有经验的程序员来说多语言并不是问题,但是频繁地在多个语言间来回切换其实是不利于高效工作的 airflow 的任务执行是以 进程 来运行的,虽然有更好的隔离性,但是显然因此而牺牲了性能和并发度。 公司内的工作流平台:你可能想象不到一个世界前十的互联网公司,他们内部一个经历了数年线上考证的运维用工作流平台,会脆弱到承受不了上百工作流的并发,第一次压测就直接让他们的服务瘫痪,进而影响到其他业务的运维任务。据团队反馈称是因为我们的工作流组成太复杂,一个流包含数十个任务节点才导致了这次意外的服务过载,随后半年这个团队重写了一个新的v2版本。 当然 Github 上也还有其他的任务流引擎,我们也都评估过,无法满足需求。比如 kubeflow 是基于 Pod 执行任务的,比起 进程 更为重量,还有一些项目,要么就是没有经过海量数据的考验,要么就是没有考虑可伸缩性,面对大量任务的执行无法水平扩容。
分布式系统下的可用性与可伸缩性
分布式系统下的可用性与可伸缩性 背景 最近我们组开展了一个针对异地多活场景的通用解决方案——单元化,旨在解决分布式系统的灾备问题,在调查中了解到了很多业务有趣的解决方案,记录在本文中,方便回顾。
写下本文时,我正就职于字节跳动的中台架构组,负责为字节的各个业务中台的共性问题提供解决方案,比如 异地多活、多租户治理 等。
为什么要讨论这个话题 通过输出本文,让自己将杂乱的相关知识进行整理 随着互联网市场逐渐饱和,未来各个软件都将会更加注重自己的服务质量,比如可用性、性能、可拓展性等等,所以将会有越来越多的团队关注自己的服务质量,其中首当其冲的就是可用性,希望我的一些经验和见解能够帮助到后来的人 概念对齐 在开始正文前,我们有必要先对齐几个本文涉及到的概念:可用性与可伸缩性。
可用性 可用性是软件系统在一定时间内持续可用的时间,比如IT服务(像一些云平台or基础设施)会对他们的用户承诺 3个9, 4个9 之类,可用性相关的SLA,它们是什么含义? 这里的几个9指的是在一年时间内,可用时间所占比的百分率,比如 3个9 指, 在一年时间内,99.9% 时间都是可用的,那么对应的服务中断时间就是:
3个9:(1-99.9%)36524=8.76小时,表示该系统在连续运行1年时间里最多可能的业务中断时间是8.76小时。 4个9:(1-99.99%)36524=0.876小时=52.6分钟,表示该系统在连续运行1年时间里最多可能的业务中断时间是52.6分钟。 5个9:(1-99.999%)36524*60=5.26分钟,表示该系统在连续运行1年时间里最多可能的业务中断时间是5.26分钟。
有人可能会有疑问,软件跑的好好的为啥会中断?中断的原因可能会有很多种:
服务更新:如果版本升级时没有实现平滑升级,那么会造成短暂的中断 机器故障:比如机器太老,需要裁撤,突然网卡坏了,机房散热器故障等等,如果你的服务没有健全的健康检查以及切流机制,往往就会带来灾难性的影响 通常来说,软件设计层面上的缺陷都是可以避免的,比如服务更新造成的中断。所以一般评估可用性都是在物理层面的影响范围上来看待,因为物理层面上的故障原因是不可控的,比如机器老化、自然灾害等。 按照范围来说,我们可以简单对软件的可用性进行一个分级
单实例:顾名思义,单个实例运行,最低的可用性级别,没法防范任何范围的故障 机房级别容灾:可以抵御机房级别的故障,通常来说都是多个实例分散在不同机房来实现 数据中心级别容灾:一般一个数据中心由多个机房组成,这个级别的容灾可以容忍一个数据中心故障 城市级别容灾:跟名字一样,能够容忍一个城市的所有数据中心出现灾害 国家级别容灾:不解释了。 这几个级别容灾能力从上往下依次递增,所需要的解决的问题和付出的代价也有很大不同,这里多说几句:
机房级别容灾 一般很少见,因为多数基础设施的提供商都不会暴露机房信息给你,而且这个粒度也偏细了 数据中心级别容灾 在各个云上可以类比成可用区(AZ:AvailiableZone),每个可用区都是独立的物理资源,以尽量做到故障隔离,但实际以我在腾讯游戏维护容器平台时的经验来看,其实很难做到 可伸缩性 可伸缩性其实比较容易理解,就是指一个服务能否扩缩容,其中又包含了两个方向:
垂直伸缩: 简单来说就是加资源,4CPU换8CPU,8G换16G,这在虚拟机时代是个比较重的操作,但是在容器环境下已经非常轻量了,但是受限于单台服务器的配置,你不可能无限大地垂直扩容 水平伸缩:需要架构支持,能够通过添加实例来分担负载,这里架构上要克服的问题就是当多个实例同时运行时,并发所带来的各种bad case。 这两种伸缩方式,前者是不需要软件进行任何修改的,天然支持;而后者是处理海量数据、请求时所必须实现的。 可伸缩性的评估可以按耗时:
不可伸缩:顾名思义,只能单实例运行,不具备可伸缩性 秒级:扩缩容能够在秒级别完成 分钟级:扩缩容能够在分钟级别完成 小时级:扩缩容能够在小时级别完成 天级:扩缩容能够在天级别完成 可能有同学疑问,为啥伸缩性只看时间,不看成本,比如是全自动,还是半自动或者纯人肉扩容,确实对于可伸缩性来说,自动化也很重要,但自动化是比较难量化的工作,时间是可以量化的。 另外换个角度看待这个问题,能在分钟级别甚至秒级别完成的扩缩容机制,它能靠人肉实现吗?
Git的分支管理模型
Git的分支管理模型 Git的分支管理模型大致有三种:
GitFlow GithubFlow GitlabFlow 这里大致介绍下
GitFlow 分支分为:
master develop 各类开发分支(feat, chore, fix)等都是合往 develop,稳定的版本发布定期从 develop 合往 master。 这个开发流程十分简单,但是问题也很明显,它是基于版本发布的,不适用当下公司内部快速迭代的节奏。
GithubFlow 和你在 Github 上面维护代码是相同的方式,各个开发分支都通过 pr 合入到 master,然后自动部署到目标环境,这个流程在很多公司都得到了实践。 缺点在于如果你有多个环境要持续部署,那么需要维护多个环境分支,如何在多个分支间保持一致性就成了一个问题。
GitlabFlow GitlabFlow 融合了 GitFlow 和 GithubFlow 两种模式,它首先给出了在 GithubFlow 下多环境分支的解法:规定上下游。你永远只能从上游同步到下游。 详细的模型可以参考上文的链接,我这里不展开了,有点多。
个人思考 通过上面的一些标准 Flow,其实我们能看出一些问题了,在这些各类的flow上,其实有几点本质上的不同:
版本发布:你交付到客户的产品都是版本化的产物,比如客户端产品。这种方式讲究质量控制和版本规划,但是对于公司内部不断迭代的产品来说,很有可能会失控。 持续发布:在公司内部不断迭代的产品会很热爱这个方式,比如内部系统(中台,CRM等) Gitflow 其实只满足了 版本发布 的需求,而 GithubFlow 只考虑了持续发布的需求,因此才出现了 GitlabFlow 来结合两者的优势。 但是 GitlabFlow 其实只是一个理论指导,什么意思?就是说缺乏具体的规范,比如从上游合入到下游,到底用 merge 还是 cherry-pick ?怎么确定一个良好的上下游?
个人思考过后,沉淀一些经验在这里。 首先考虑自己产品形态是怎样的?
需不需要持续发布:如果你是一个开源的产品,没有需要持续发布的环境,那么你肯定没有这样的需求。但是如果你是个公司项目,先不考虑生产环境,你的测试环境一定是持续发布会更好。 需不需要版本发布:通常来公司内部系统其实不太注重版本,但是如果你的系统举足轻重,那么将生产环境作为版本发布来看会是一个不错的选择。 如果你需要持续发布,那么你需要管理 环境分支(env branch),类似 prod(duction), pre(-production), test等,这些分支应该只能通过 mr/pr 合入,当合入后则自动发布到对应环境。 如果你需要版本发布,那么你可能需要管理 发布分支(release branch),这个可以参考 GitFlow 中的定义,便于以后进行 hotfix。
给你的站点加上统计分析
最近比较好奇自己的个人网站、博客啥的,是不是真的有人看过,平时写的笔记能被搜索到吗、如果搜索不到要考虑做下SEO了,发现光从VPS的服务商那里根本获取不到啥有帮助的信息,所以调查了下目前网站统计接入的方式,记录下如何快速为自己的网站接入统计功能
M3db
背景 我们的云平台需要采集所有集群正在运行的容器的性能数据与业务指标,最开始采用的每个集群一个独立Promethues的配置,如下图:
结构比较简单,这里就不累述了。需要解释下的是 prom-nanny 这个组件是个啥,其实就是对社区版本 prometheus 的封装,它主要实现以下功能:
watch 用户配置的自定义告警规则,然后更新到配置文件中 更新配置文件后,调用 prometheus 接口热更新配置 以上则是以前老的监控体系下的拓扑图。很明显这里很多问题:
存储、采集、查询单点:这里 Prometheus 承担了三个责任,但是它仅有一个实例,一旦故障,我们将失去所有功能 无法水平扩容:由于只有一个实例,所以我们只能垂直扩容,而母机的配置是有上限的,你不可能无线扩下去,对于有的集群动则几千个节点,单一实例是不可能满足的。有的同学可能会提到 hashmod 、federation 和 remote_read,其实这三个功能可以解决采集和查询的水平扩容,但是解决不了存储的水平扩容,Prometheus 提出了 remote_write 来解决存储单点的问题 权限未分开:由于我们不但采集性能数据,还采集业务系统自行暴露的业务指标,因此存在一些敏感数据,但是 grafana 和 prometheus 都共用一个的情况下,权限都是共用的,这带来的一定的安全风险 方案对比 由于现在集群的指标都是通过 Prometheus 的 exporter 来暴露,所以方案已经限定以 Prometheus 为基础了。 基于这个前提,在作者进行方案选型时(2019年中),了解到的有以下几种解决方案:
Prometheus + influxdb Thanos cortex M3 这里其中第一个方案由于此前隔壁组的同事以前做过预研,发现性能存在较大缺陷,所以不在考虑范围内,最终他们选择了 Thanos。
这里列个对比图:
简述 优点 缺点 Thanos 开源社区很有名的集群方案,它的核心点在于将查询最近时间的查询请求路由到各个 Prom 分片进行查询,同时通过 SideCar 将数据同步到对象存储中,作为历史存储。 已经有了很多案例资料可参考,查询快,功能成熟 历史查询慢,由于是是从对象存储转换过来,很显然这里存在复杂的转换过程,同时对象存储的数据库也不会没有针对时序型数据的优化;同一 Prom 分片承担采集与查询的责任,会互相影响 cortex WeaveCloud 公司搞的开源项目,通过 Prometheus 的 remote_write 写入自研的组件后将其转换为块存储,然后提供兼容 PromQL 的查询组件来提供外部使用 虽然使用的块存储,但是它还利用了额外的存储来简历索引,同时提供了优化查询的缓存组件,可以是说是煞费苦心了;功能应该是我了解的方案中最为丰富的一个 太复杂,它的优点其实也是它的缺点,为了能优化查询的速度,让整个拓扑图的复杂度增高了,每多一个组件在分布式系统里面其实都是一种负担。 m3 这是 Uber 在2014 年开始自研的一个方案,他们在开始之前,也尝试了很多开源组件作为底层存储,发现都无法满足他们的要求,于是他们从底层存储开始自研了这套方案,有兴趣的同学可以看下 这篇文章,m3 的理念非常简单,直接通过 remote_write 写到转换组件,然后存入自研的时序数据库,并且提供兼容 PromQL 和 Graphite 语法的查询组件 拓扑简单,落地方便;由于自研的时序数据专门针对时序型数据进行了优化,所以无论短期还是历史数据查询效率和压缩率都非常高;已经在 Uber 内部经过大量数据的验证 案例太少,由于 2018 年才刚刚对外开源,网上关于 m3 实践的资料非常少,偶尔有几篇文章也只是在浅显地跑了起来而已,规模不可考;还没有稳定,版本还是0.