Nginx 配置最佳实践

Nginx 配置最佳实践 Buffer 注意Nginx作为代理服务器时的几个关键行为: 默认会缓存 request 的 body,直到接收完所有body后才会转发请求到后端 默认会缓存上游服务的 response,直到接收完所有 response 的body or body超过了设定值才会将请求转发给后端 这两个行为的目的都是为了克服慢client带来的影响,比如 client 在发起请求时很慢,大量的连接会贯穿到 upstream,而接收响应也是类似原理,nginx期望尽可能快地接收完 upstream 响应,以释放它的相关资源。 现在我们来详细看看过程中涉及哪些参数,以一个请求发起为例 client 发起请求,nginx 根据 proxy_request_buffering 判断是否需要缓存请求 body,默认为 on,即缓存请求 如果开启缓存,那么 nginx 会不断缓存 body 到内存中,大小为 client_body_buffer_size 所指定的大小,默认为一个内存页大小 4k(32bit os)/8k(64bit),不过这个参数不一定适合现在的硬件了,参考 这里,如果超过这个大小,则会写入临时文件,路径为 client_body_temp_path 所配置。 1,2步骤完毕后,开始向后端转发,然后等待后端请求返回 开始接收 response ,根据 proxy_buffering 判断是否需要缓存响应 body,默认为 on,即缓存响应 如果开启缓存,和请求类似的是也有一个 proxy_buffers 来决定缓冲区大小,默认 8 4k(8 * 4k大小),当body大于这个buffer时会转储本地文件,文件大小为 proxy_max_temp_file_size 限制,每次写入大小为 proxy_temp_file_write_size,默认是 proxy_buffer_size 和 proxy_buffers 之和,当大于临时文件限制时将转为同步传输,同步传输将使用 proxy_buffers 定义的空间作为 buffer,同时还有一个 proxy_busy_buffers_size 用于控制 buffer 中的哪一部分用于回传给 client,因为传输的过程中,整个 buffer 都将被标记为 busy,不可用,因此为了实现边接边回传的均衡,建议 proxy_busy_buffers_size 不要大于 proxy_buffers 的一半。 如果关闭 nginx 的 buffering,那么nginx将使用 proxy_buffer_size 配置的 size 作为 buffer 来传输文件,可以通过提升这个值来加快传输速度
查看更多 →

SqlMap用法

查看更多 →

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。 这两种伸缩方式,前者是不需要软件进行任何修改的,天然支持;而后者是处理海量数据、请求时所必须实现的。 可伸缩性的评估可以按耗时: 不可伸缩:顾名思义,只能单实例运行,不具备可伸缩性 秒级:扩缩容能够在秒级别完成 分钟级:扩缩容能够在分钟级别完成 小时级:扩缩容能够在小时级别完成 天级:扩缩容能够在天级别完成 可能有同学疑问,为啥伸缩性只看时间,不看成本,比如是全自动,还是半自动或者纯人肉扩容,确实对于可伸缩性来说,自动化也很重要,但自动化是比较难量化的工作,时间是可以量化的。 另外换个角度看待这个问题,能在分钟级别甚至秒级别完成的扩缩容机制,它能靠人肉实现吗?
查看更多 →

golang如何更优雅地处理Error

golang如何更优雅地处理Error 今天想要分享的是,golang中如何更优雅地处理错误。 怎么处理Error是golang中一个非常关键的事情,因为golang的设计导致代码中到处都是类似以下的代码 if err != nil { ... } 如果处理不得当,会导致代码膨胀得非常快且难以维护,比如: if err != nil { metrics.Emit(...) log.Print(...) event.Emit(...) ... ... return err } 面对这样的代码,可能错误处理所占的代码行数都会多于逻辑代码,显然不是我们愿意看到的。 上面描述的代码膨胀现象只是错误处理中常出现的问题之一,接下来我们聊聊日常开发中该如何优雅地处理错误。 错误的处理方式 错误的传递方式无非两种: 返回 不返回 选择返回or不返回的场景无非几种: 当函数位于顶层,比如 API的接入层、conumer的handle 等,此时无法返回 发生的错误是 致命的,会影响到整个程序的运行,此时应该抛出panic,阻止程序发生更加不可控的事情 发生的错误是 预期的,比如查重动作中查询数据库的数据不存在时,得到了一个 NotFound 的错误 其他情况均应该返回错误 而面对错误发生时,常见有以下的处理方式: 记录(日志、metrics、事件) 做一些业务逻辑 直接返回 这里只对 记录 单独展开说下,其他两项暂时没什么需要注意的。 记录 记录的常见手段有三种:log, metrics, event。 通常来说我们只需要记录其中一种即可,有的同学可能会有疑问,明明这三种不同的技术都有不同的适用场景,为什么说通常了一种就够了。 首先我们要聊聊这三个记录的核心目标是什么: log: 最传统的形式,是为了在程序运行时留下可追溯的信息,来辅助人类了解程序发生了什么,一般都会进行分级处理。 metrics: 由于不同的 metrics 体系,实现不同,这里只概述下,metrics是用于观测某个属性的趋势 event: 用于敏感操作的审计 or 广播变更 我在过往的工作中,见过某些同学,在某个很关键的场景出错后采用了以下的做法:
查看更多 →

golang context的正确打开姿势

查看更多 →

领域驱动设计(DDD)入门实践

领域驱动设计(DDD)入门实践 背景 我从 2016 年接触到 DDD 开始,到目前( 2022年 )为止,在各种项目中实践过 DDD,包括 医疗设备系统、用户社区、容器云,在这些系统中或深或浅都是用了 DDD 的一些概念模型,因此对 DDD 虽然谈不上熟练,也算是积累了一些宝贵的经验。 本文的出发点: 这些年利用 DDD 在解决软件复杂性上所获得的经验,希望能够帮助到更多小伙伴 国内互联网近些年都热衷于聊架构、算法、性能,却鲜有在工作中关注代码设计的氛围,希望通过本文能够让读者意识到到代码设计的价值所在。 为什么是DDD 我们先来回答这个问题,为什么本文不讲设计模式,不讲重构,不讲架构风格,而偏偏是 DDD。 答案是:实用性,为什么说实用呢,我们先来回顾下设计上最广为人知的 设计模式。出身的同学想必早就在学校学习过各种设计模式,策略模式,工厂模式,责任链模式等等,但是回顾一下你们的工作生涯中,你们有多少场景用到了这些模式,在整个项目中又占到了多少比例呢,如果你细细回顾下,我相信你使用了设计模式的代码内容在你的项目比重中一定很低。 究其原因在于,设计模式是面向局部场景的经验集合,而非系统性的方法论,在我了解的范围内,DDD 是最全面的设计理论,它能够覆盖到软件开发过程的大多数问题,而其他的一些设计理论大多都是面向特定问题的。 DDD是什么 领域驱动设计(Domain Driven Design)是一种设计理论,可以先看看 Wiki 的定义: Domain-driven design (DDD) is a software design approach focusing on modelling software to match a domain according to input from that domain’s experts. One concept is that the structure and language of software code (class names, class methods, class variables) should match the business domain.
查看更多 →

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的服务商那里根本获取不到啥有帮助的信息,所以调查了下目前网站统计接入的方式,记录下如何快速为自己的网站接入统计功能
查看更多 →

mysql invalid connection 错误

mysql invalid connection 错误 最近团队有同学遇到了 mysql 会偶发"invalid connection" 错误,在之前从没遇到过,因此稍微调查一下。 原因 查看 github.com/go-sql-driver/mysql@1.6.0 的源码可以看出来会返回这个错误的地方不是很多,大致可以归类以下几个case: TCP连接已关闭:这里的连接关闭是指被动关闭,通常对数据库的连接池都由 go 的 sql 包来管理,无论是 MaxIdleTime, MaxLifetime 都是它来管理生命周期,如果取出了已过期的连接,它会重新再去取,然后关闭掉已过期的连接,这是主动关闭,源码参考 sql。被动的关闭,指从server端发起的,比如连接空闲时间 > wait_timeout,再比如 mysql or mysql proxy重启或者故障,这些都导致 invalid connection。从代码来看,导致这个错误的另一种情况是对一个已经关闭的mysqlConn 进行操作,这个case我想不到什么情况下会出现,因为逻辑上来说当这个对象被调用 Close 后,将没有机会被调用,不知道要什么情况会被复用到。 读写异常: 查看 readPacket 和 writePacket 代码可以发现,如果在读写过程发生错误,且 client 端没有 cancel context,那么会返回 invalid connection,具体的原因可以在标准输出里查看,mysql会输出 [mysql] 前缀的日志,最常见的是读写超时。 当开启插值参数后,并发使用连接:正常 mysql driver走的 prepare + exec 的过程,会产生多一次的 rtt,有些情况我们可以通过dsn参数 interpolateParams=true 来开启插值(占位符替换),而减少一次rtt。但是插值的过程会使用 conn 上的buffer,如果并发访问导致 buffer 繁忙则会返回该错误。那么什么时候会在并发下使用同一条连接呢?答案是你当你使用了一些 session性质的功能,比如 transaction or user-level lock。 插值相关的参考资料:database/sql 一点深入理解
查看更多 →