备份之道:企业如何构筑防灾屏障

2017 年 1 月 31 日晚上,GitLab.com 运维人员在深夜进行数据库维护时,由于疲劳操作失误,使用 rm -rf 命令错误地删除了约300GB的生产环境数据。在意识到错误并尝试恢复时,只有 4.5GB 的数据得以保留。GitLab.com 官方在 2 月 1 日凌晨 1 点左右完成了数据恢复,网站恢复正常访问。恢复过程中使用的是故障发生前 6 个小时的备份数据,因此在这 6 个小时内的数据丢失。尽管 GitLab.com 声称拥有五重备份机制,包括常规备份、自动同步、LVM 快照、Azure 备份和 S3 备份,但在这次事件中,所有备份均未能有效防止数据丢失。

2023 年 10 月 23 日,语雀服务出现了一次重大故障,中断时间超过 7 小时。具体来说,这次故障的起因是在执行系统升级操作期间,新的运维升级工具出现了 bug,导致华东地区生产环境的存储服务器被误下线。由于存储系统使用的机器类别过于陈旧,无法直接将其上线,这导致了恢复过程时间延长。尽管语雀的工程师们立刻进行了恢复工作,但由于数据量大,恢复方案有限,恢复过程耗时 4 小时,数据检验耗时耗时 2 小时。

在这些个案例中我们看到了什么问题?

备份不可用,恢复方案有限,恢复备份数据长,数据丢失等等,有点平时说起来很强大很完善,真有大事就扛不住了。

所以,今天聊聊备份,不局限于数据备份。

作为一名从业多年的老兵,我们深知备份的重要性,它事关系统的安全、业务的连续性和数据的完整性,可以说是任何系统不可或缺的一部分。

行业内有一句话:「备份不做,日子甭过

1 为什么需要备份

我们为什么需要去做备份呢?主要有以下几点原因:

1.保证数据安全:在信息时代,数据已经成为企业和个人最宝贵的资产之一。而各种硬件故障、人为错误、网络攻击、自然灾害等都可能导致数据丢失或损坏。定期备份数据,可以最大限度地降低数据丢失的风险,即使遇到问题,也可以从备份中恢复数据,将损失降到最低。

2.防范风险,确保业务连续性:对于企业来说,IT 系统的可用性直接关系到业务的正常运转。一旦关键系统发生故障,没有可用的备份,就可能导致业务中断,带来巨大的经济损失和声誉影响。而有了完善的备份和灾备机制,即使发生故障,也可以快速恢复系统,确保业务连续性。

3.满足合规性要求:在某些行业,比如金融、医疗、政府部门等,由于涉及敏感数据和关键业务,往往有严格的合规性要求,需要有完善的备份存档机制。定期备份数据、留存备份一定时间都是合规性的硬性规定。没有备份,可能面临违规处罚的风险。

4.提高竞争力:在当今数字化时代,数据已经成为企业的核心竞争力。拥有完善的备份和容灾能力,意味着企业能够更好地保护和利用数据,从数据中提炼价值,增强市场竞争力。同时,这种能力也能提升客户信任,赢得更多业务机会。比如信息产品安全等级资格认证三级认证需要:本地备份与恢复+异地数据实时备份+本地业务高可用

2 备份什么

看到备份这个词,马上能想到的就是数据备份,数据库备份这些,这些固然重要,但如果你作为一个企业的技术负责人所考虑的备份就不仅仅是这些了,以下是常见的需要备份的内容。

2.1 业务数据

业务数据是备份的重中之重。

对企业来说,最需要备份的就是各种业务数据:

  1. 数据库:销售数据、客户数据、财务数据……这些数据通常存储在 Oracle、MySQL 等数据库中,是企业的核心资产,必须优先备份。如果用的是云数据库,云数据库虽然有一定的冗余,但我们仍需有自己备份数据逻辑。
  2. 文件数据:合同、报表、设计图、多媒体素材等形式多样的非结构化数据,也包含了大量的业务信息,切不可轻视。
  3. 应用数据:ERP、CRM、OA 等业务系统中的数据,与业务流程紧密相关,也需要专门备份。

2.2 系统数据

系统数据是保证业务连续性的关键

除了业务数据,我们还要备份各种系统层面的数据:

  1. 操作系统:Windows、Linux 的系统盘,包含了服务器的重要配置,一定要定期备份,特别有一些特殊的配置,最好是有一个内部的 DevOps 系统来承接。
  2. 虚拟机:很多业务系统部署在虚拟机上,备份整个虚拟机可以实现快速恢复,如果用的是云服务,境像的备份是一个标准操作。

2.3 配置数据

配置数据是还原系统的关键

系统的各种配置也需要备份,主要包括:

  1. 应用配置:各个业务系统的配置参数,如果丢失,恢复系统将非常困难。如果应用配置是存储在一个配置信息中,那就需要对这个系统做备份,包括上面的数据备份。
  2. 网络配置:交换机、路由器、防火墙等网络设备的配置,对维持业务连续性至关重要。
  3. 安全配置:防病毒、访问控制等安全系统的配置,攸关企业信息安全。

2.4 代码、文档和日志

代码、文档和日志主要是对研发团队来说的,对于软件企业或开发部门,代码和文档是核心资产,日志是问题定位,监控审计的利器:

  1. 源代码:软件的生命线,必须做好版本管理和备份。
  2. 开发文档:需求、设计、接口等文档,是沟通和维护的基础。
  3. 日志:系统和应用的各种日志数据,虽然不直接服务于业务,但对问题定位和事后审计很有价值
    1. 系统日志:记录了系统的运行状况和重大事件。
    2. 应用日志:反映了业务系统的运转细节和潜在问题。
    3. 审计日志:涉及敏感操作和数据访问,对合规和安全审计非常重要。

2.5 第三方服务

我们很多应用会调用第三方提供的服务,如支付、短信、社交登录、地图等。虽然这些服务由第三方负责运维,但作为调用方,我们也需要做一些防范:

  • 服务冗余:对于关键服务,不要依赖单一供应商,要有备选方案。比如支付可以同时接入支付宝和微信支付,短信可以同时使用两家供应商等。这样即使一家出问题,也可以快速切换。
  • 数据备份:有些第三方服务可能会存储我们的业务数据,比如客服系统中的客户对话记录。这部分数据最好在自己的系统中也保留一份,定期从第三方服务同步,这样即使第三方服务数据丢失,我们也不会受太大影响。
  • 接口契约:和第三方服务的调用要有明确的接口契约,包括接口规范、SLA等。这些内容要备份保存,一旦第三方服务变更接口或者停止服务,我们可以据此追究责任。
  • 替代预案:要评估每个第三方服务的可替代性,制定替代预案。一旦服务长时间不可用,我们要能够快速启动替代方案,比如临时切换到备用供应商,或者使用本地降级方案等。

除此之外,对于调用第三方服务的 API 密钥、账号密码等敏感信息,要安全备份,防止丢失。

以上就是企业需要备份的主要内容。当然,具体到每个企业,还要根据自身业务特点和 IT 架构来定制备份方案。我们需要全面梳理数据资产,评估数据的重要性和变更频率,搭建分层的备份体系。

3 如何备份

3.1 备份的 3-2-1 原则

在备份领域,有一个著名的 3-2-1 原则,包括:

  • 至少保留 3 个副本
  • 使用 2 种不同的存储介质
  • 将 1 个副本存放在异地

这个原则看似简单,却涵盖了备份的关键要点:

  • 多副本可以应对副本损坏的风险,是冗余的基本保证;
  • 多介质可规避单一介质故障的影响,比如同时使用磁盘和磁带;
  • 异地存放可以防范本地灾难,是灾备的必要手段。

在实践中,我们要根据数据的重要性和预算,灵活采用 3-2-1 原则。比如增加副本数量、类型,利用多个异地机房,甚至上云备份等。

3.2 备份的分类和技术

备份主要可以分为几类:

  1. 完全备份与增量备份

    1. 完全备份每次对整个数据集做一个完整的备份,优点是恢复简单,缺点是备份时间长、空间占用大
    2. 增量备份则是在上一次完全备份的基础上,只备份变化的数据,备份时间短,空间利用率高,但恢复时需要依次应用所有的增量包,略显复杂。
  2. 物理备份与逻辑备份

    1. 物理备份在底层复制数据文件,对文件系统/磁盘做快照,优点是简单高效,缺点是与平台/格式绑定,缺乏可读性。
    2. 逻辑备份是从上层应用备份数据,比如 SQL dump,优点是与存储解耦,可读性好,易于编辑和跨平台迁移,缺点是备份恢复速度慢。
  3. 冷备份、温备份与热备份:这主要是指备份过程对业务的影响程度。

    1. 冷备份需要暂停业务,关闭数据库,确保数据一致性,适用于允许一定业务中断窗口的场景。
    2. 温备份是在业务运行中备份,会有一定的性能影响,需要控制备份速度和资源占用。
    3. 热备份可以实时同步备份,几乎不影响业务,但实现起来最复杂,对软硬件要求也最高。

除了上述分类,备份领域还有很多技术手段,比如:

  • 复制:全量复制、增量复制、双向复制等
  • 连续数据保护 CDP:通过记录数据修改日志来连续备份
  • 去重:备份时过滤重复数据,节省存储空间
  • 压缩:减小备份的体积,传输和存储更高效

3.3 灾备预案与流程

除了备份技术本身,做好灾备还需要全局的规划。我们要制定完善的灾备预案与流程,主要包含:

  1. 灾备需求与目标设定:不同的系统有不同的可用性要求,核心系统当然需要更高的可用性。我们要评估每个系统的重要性,制定恰当的灾备目标,权衡投入产出。常见的指标有 RTO(恢复目标时间)和 RPO(恢复点目标),前者是最大恢复时长,后者是允许丢失的最大数据量。指标设定要务实,太低达不到,太高又成本太大。

  2. 编写灾备预案文档:灾备预案文档需要明确定义灾难等级、各种场景下的应急流程、通知上报机制、系统恢复步骤、人员角色分工、联系方式等各个细节。使得整个应急响应有章可循,文档要定期评审和更新。

  3. 灾备演练与测试:光有预案还不行,必须落到实处。要定期组织演练,模拟各种场景,让团队熟悉灾备流程。比如模拟机房断电、网络中断等场景,检查应急预案的可行性,评估 RTO、RPO 的达成情况。演练中发现的问题要及时修正预案或改进系统。

  4. 灾备监控和验证:除了演练,平时也要做好灾备系统的监控,确保灾备系统是随时可用的。比如监控备份的成功率、备份存储的容量使用率等,一旦发现异常要立即处理。

此外,我们还要定期做数据恢复验证,确保备份的数据确实是可用的。俗话说「备份是无用的,恢复才是关键」,定期做恢复测试是验证备份有效性的重要手段。

3.4 云灾备和异地多活

随着云计算的兴起,灾备也有了新的手段。很多公有云平台提供了灾备和备份服务,比如 AWS 的 S3、Glacier,阿里云的 HBR、OSS 等。将备份放到云上是个不错的选择,一是降低了本地存储的投入,二是借助了云的地理分布优势,备份天然就是多地域的。

此外,云平台还提供了跨可用区、跨地域的数据复制功能,以及多活架构支持。利用这些服务,可以打造「异地多活」的架构,也就是在多个地理位置部署服务,互为备份,同时对外提供服务。这样即便一个数据中心发生灾难,其他活跃的数据中心也可以无缝接管服务,实现了很高的可用性。当然,异地多活对技术水平要求极高,网络延迟、数据一致性都是不小的挑战。

4 备份的小结

总结来说,可以分为如下的几个点:

  1. 全面规划:灾备不是某个部分的事,而是一个系统工程,需要全局统筹规划。
  2. 分级备份:根据数据重要性分级备份,核心数据要更频繁备份,也要留存更长时间。
  3. 自动化:备份和恢复要尽可能自动化,减少人工操作可能带来的错误。
  4. 实时监控:对备份全程监控,确保备份连续性和及时发现问题。
  5. 定期演练:演练不可少,且要尽可能模拟真实场景,检验预案的有效性。
  6. 云灾备:合理利用云服务,可以显著提升灾备水平,但切忌过度依赖。
  7. 专人负责:安排专人负责灾备,统筹协调,并持续优化灾备体系。

备份灾备是一个复杂的课题,需要持续的投入和改进。做好灾备,并不一定能避免所有灾难,但可以最大限度地降低损失。它不是锦上添花,而是雪中送炭。对个人、对企业而言,灾备都是系统安全、可靠的基石,是我们必须重视、持之以恒去做好的功课。

备份之路,道阻且长。

以上

关于研发效能在组织层面的一些思考和总结

组织结构决定了信息流动、资源分配和决策过程的效率。在研发效能的提升中,扁平化的管理层级、跨功能的团队配置以及灵活的人员动态管理,都能够促进创新和加速决策过程。

最近在思考关于软件研发效能的事情,而其中关于组织层面有一些想法和总结,如下:

康威定律和逆康威定律

在 1968年,梅尔·康威在《Datamation》杂志发表文章《How Do Committees Invent?》,探讨了组织结构和系统设计的关系。其中一句话后来被总结为康威定律:「任何组织所设计的系统架构,都不可避免的反映为该组织沟通结构的副本。

康威在开发早期电子计算机系统的组织中观察到,组织的真实沟通路径(即价值创造架构)与最终的软件架构之间存在强相关性。这种同态力将软件架构和团队结构塑造成相同的「形状」。也就是说,构建软件需要理解团队沟通路径,以更加实际地考虑什么样的软件架构是可行的。如果理论上的系统架构与组织模型不符,就需要对其中之一做出改变。

当组织结构变化时,系统架构就会被影响,身在局中的我们感触非常深刻,本来负责 A 业务,调整后负责 B 业务,原来的工作就不想动了;本来在重构某块技术,想把历史的债务还清先,调整后也没有了动力。

作为一个技术人面对这种情况,有时也无能为力,调整后大量的时间是投入在当前的业务中,而看到的历史问题不在你的工作边界之中,只能看着历史债务越集越多,直到某一天,雪崩。

特别是后端,作为业务核心的技术部分,一个 24×7 要在线的,其稳定性,可用性都是非常重要的。

如何通过系统化的机制保障后续架构的稳定演进,并且不会随着组织结构的频繁变动而频繁变动?

要想抵抗组织结构对系统架构的影响,一种办法是适度隔离,比如采用内部开源的方式,让系统脱离具体业务组织而存在,代码由核心小组统一维护。这种虚拟组织不会随业务变动而变动,适用于底层框架、中间件等非业务模块。

但对于业务属性较高的模块,内部开源并不可行。因为其开发和维护需要对业务有深入理解,跨部门的认知成本太高。这时就需要换一个思路:利用康威定律,通过优化团队架构来引导系统架构,减少沟通和认知成本,实现理想的、高内聚低耦合的架构闭环。这就是逆康威定律的应用。

逆康威定律是 2015 年微服务兴起之际,ThoughtWorks 技术总监 James Lewis 提出的概念,即组织要根据他们想得到的软件架构来设计团队结构,而不是期望团队盲从一个被设计好的团队结构。

其中的核心观点在于,将软件架构看作一个独立的概念,认为它可以被孤立设计,然后交由任何团队来实现,这从根本上就是错误的。软件架构和团队结构的差异在所有架构类型中比比皆是,无论是客户端-服务端、SOA 还是微服务架构。这也是为什么为了让团队聚焦,单体架构需要拆分的原因。

通过逆康威定律的应用,我们可以先设计出理想的软件架构,然后根据这个架构来调整和优化团队结构。这样做的好处是可以更好地匹配业务需求,提高系统的内聚性和可维护性,减少不必要的沟通和协调成本。

具体来说,我们可以采取以下措施:

  1. 根据业务领域划分微服务,每个微服务由一个独立的团队负责开发和维护。这样可以让团队对自己的服务有更清晰的认知和掌控,减少跨团队沟通。
  2. 建立跨团队的架构治理机制,制定统一的架构原则、接口规范、质量标准等,确保微服务之间的一致性和互操作性。
  3. 加强团队内部的自治和责任心,鼓励团队自主决策、快速迭代、持续改进。同时赋予团队端到端的交付职责,避免因职责割裂导致推诿扯皮。
  4. 建设公共的技术平台和工具链,提供标准化的开发、测试、部署、监控等功能,减轻团队的基础设施负担,提高研发效率。
  5. 营造开放协作的文化氛围,打破部门墙,鼓励团队之间主动沟通和知识共享,形成学习型组织。

康威定律揭示了组织结构与系统设计的同构关系,而逆康威定律则为我们指明了一条突破之路:从组织架构入手,持续优化团队分工,才能推动系统架构向理想方向演进。这需要我们在组织设计上投入更多思考,用系统思维来对待研发效能问题。

团队的认知负载

认知负载是指在特定时间内,工作记忆中的信息负荷。对于个人而言,认知负载就是大脑需要同时处理的信息量。当我们将视角拓展到团队层面时,认知负载就变成了团队在执行任务时所承担的信息处理总量。

一个团队的认知负载并不等于团队成员认知负载的简单加总。团队协作过程中产生的交流、同步、协调等活动,都会带来额外的认知负载。同时,团队成员之间知识和技能的差异,也会影响到认知负载在团队内部的分配。

团队的认知负载也是影响软件研发效能的重要因素。

当系统复杂度超出团队的认知极限时,就会导致生产力下降、质量恶化等问题。

控制认知负载的关键,在于团队内部的”通晓全局”程度,即每个成员对系统的整体理解。

那如何判断团队是否处于认知过载状态?

可以从任务执行、团队氛围、个人状态三个维度来观察。

从任务执行的角度看,如果团队在面对新需求时响应速度明显变慢,对变更的适应能力下降,出现更多交付物质量问题,需要更多的返工和修复,这就是认知过载的典型信号。团队投入了更多的时间和精力,但产出的绩效却在下降,说明认知资源已经难以支撑高质量高效率的工作。

从团队氛围的角度看,如果团队成员之间的沟通变得低效和困难,产生更多的误解和冲突,知识共享和协作的意愿下降,整个团队的创新能力和解决问题的能力下降,团队士气低落,抱怨和消极情绪在蔓延,这也提示团队正在经历认知过载。当团队的认知资源捉襟见肘时,成员往往会降低对他人和整体的关注,转而专注应对自己的工作压力。

从个人状态的角度看,如果团队成员普遍感到疲惫和倦怠,对工作失去热情和主动性,工作与生活难以平衡,出现失眠、健康问题等身心状况,这往往是认知过载的结果。当个人长期处于高认知负荷状态,既要应对本职工作,又要参与大量协调和沟通,还要不断学习新知识,就很容易产生持续的压力感,陷入职业倦怠。

团队认知过载不是孤立的问题,而是会从任务执行、团队氛围、个人状态等方面综合反映出来。作为团队管理者,需要保持敏锐的洞察力,及时捕捉这些危险信号,采取针对性的优化措施,从而保障团队的可持续发展。

为了降低认知负载,我们可以从组织设计和架构设计时都做一些考虑。

在组织设计时,可以考虑如下几点:

  1. 适度聚焦。每个团队应该有明确的、聚焦的职责范围,避免过度分散精力。职责范围要与团队的认知能力相匹配,既不能太窄导致产能不足,也不能太宽导致认知过载。
  2. 职责自治。团队对自己的职责范围应该有较大的自主权,可以独立做出决策和优化。过多的外部依赖会增加认知负载。
  3. 边界清晰。团队之间、系统模块之间应该有清晰的边界和接口,减少耦合和认知依赖。
  4. 知识共享。鼓励团队之间主动分享知识和经验,建立共同的认知基础。但要注意,知识共享不等于职责共担。
  5. 弹性冗余。在关键领域,适当保留一定的冗余和备份,避免单点依赖。这样可以在保证产出的同时,降低认知负载的风险。

在架构设计时,可以考虑如下几点:

  1. 模块化和解耦。将系统划分为逻辑清晰、职责单一的模块,并通过松耦合的方式连接,减少模块之间的认知依赖。每个团队只需深入理解自己负责的模块。
  2. 接口驱动。通过定义清晰、稳定的接口规范,将模块的内部实现和外部用途解耦。调用方只需了解接口,而无需关心内部细节。
  3. 领域建模。从业务领域出发,识别出稳定的核心业务概念和流程,并据此设计系统模型。让软件架构尽量贴近真实世界,减少认知转换。
  4. 约定优于配置。通过制定统一的架构原则、编码规范、工具约定等,在团队之间形成一致的认知基础,减少沟通成本。
  5. 演进式架构。架构不是一成不变的,而是随着业务和技术的发展而不断演进的。因此要为变化留出空间,通过持续重构等手段,让系统在可控的范围内优化,避免大规模的推倒重来。

通过对认知负载的有效管理,我们可以显著提升团队的工作效率和整体协同能力。

认知负载,说到底是对人的真正尊重。而尊重,恰恰是最好的管理。

三种常见的团队组织

业务交付团队

业务交付团队,是组织中最主要和基础的团队组织。

业务交付团队是围绕清晰目标和职责,匹配持续变化的业务价值交付任务,形成独立高效工作流的长期、稳定、跨职能团队。

一个业务交付团队通常对应一条单一、完整的价值交付流,可以是一个产品、服务、功能集合、用户故事或用户画像等。团队拥有端到端的能力,能够独立完成从概念到交付的全部工作,快速、安全地持续创造用户价值,而无需依赖其他团队。

业务交付团队要尽可能贴近最终用户,通过生产环境实时监控,获得快速反馈,并据此迅速响应变化和问题。团队规模适中,由高素质的跨职能成员组成,保持长期稳定性,而不是随项目起起落落。

组织内通常存在多种业务交付团队,各自负责不同的业务流,如特定用户、业务领域、地理区域、产品条线等。无论承接何种业务流,团队都应围绕明确的待办事项和优先级开展工作,确保工作流的清晰和聚焦。

一个高效能的业务交付团队应该是这样的:

  • 他们的工作就像一条流水线,特性开发的各个环节衔接顺畅,没有太多卡壳和浪费
  • 团队时刻关注用户反馈和业务变化,能够灵活调整开发计划和优先级
  • 他们勇于尝试新事物,通过小步快跑的试错方式来推动改进,并善于从成功和失败中吸取教训
  • 团队内部各司其职、协同高效,很少需要跟其他团队交接工作
  • 他们交付的速度又快又稳,代码质量也有保障,还能兼顾技术升级和团队成员的健康
  • 团队会投入时间优化系统架构和代码,「修修补补」以防「房子」越来越破
  • 他们懂得借助专业团队的力量,定期跟架构、基建、工具等团队沟通,补齐短板,让自己更专注
  • 团队成员有足够的自主权,在技能上精益求精,工作目标明确,从而获得成就感和价值感

业务交付团队是组织的一线力量和价值核心,其他辅助型团队如技术智囊团和基建平台等,都是为了补齐能力短板、降低认知负载,让业务交付团队得以更高效地运转和创新。这种以业务交付为中心的团队组织模式,是相对于传统的职能式、项目式组织的一种进化。

组建长期、稳定、高度自治的业务交付团队,打造清晰的端到端业务流,是实现快速、频繁、可靠价值交付,适应快速变化的关键所在,代表了现代软件开发组织的进化方向。

平台团队

平台团队的目标是赋能业务交付团队,使其能够高度自治地交付工作。平台团队提供的内部服务,使业务交付团队无须开发底层服务,降低了认知负荷。这里的平台是指其作为公司内部的基础产品,向开发团队提供自服务的 API、工具、服务、知识和支持。借助平台,业务交付团队获得了自主性,可以更快地交付产品特性,减少开发过程中的各种协调、沟通。

业务交付团队可以方便地使用平台团队提供的自服务的网站门户和/或编程API(而非厚厚的使用手册)。「方便使用」是采纳平台的基本要求,并且平台团队必须将他们所提供的服务视为一种产品,无论这些服务是被内部还是外部用户所使用,要具有可信赖的、易用的、量身定做的特征。

平台团队可以提供不同级别的服务,但如果所有业务交付团队都要求高等级服务(如零停服务时间、自动扩缩容、自动修复),平台团队则难以支持。为避免人人都要求高等级服务,可以为这些内部基础设施和服务定价,向使用团队收费。

作为基础设施工程团队,平台团队需要聚焦于开发团队的工作流,以及应用和基础设施的改变如何影响用户。为了帮助开发团队用户更高效地使用平台,平台团队需要做到:

  1. 把内部平台视为线上/生产系统,计划和管理维护时间
  2. 引入软件产品管理和服务管理技术

一个高效能的平台团队应该有以下行为和产出:

  • 与业务交付团队密切协作,理解其需求
  • 依赖快速原型,尽早引入业务交付团队以获得快速反馈
  • 重点关注服务的可用性和可靠性,将平台视为产品,定期回访用户以确认服务是否满足需求
  • 自己也应是所提供服务的用户,与业务交付团队并肩战斗
  • 明白新的内部服务将像创新曲线那样被逐步引入,而非一蹴而就

平台团队和传统的基础设施团队略有不同,突破了传统基础设施团队与业务团队之间的隔阂,通过提供自助式的平台服务,赋能业务团队快速构建和交付应用。与传统基础设施团队相比,平台团队更加贴近业务,团队成员除了技术能力外,还需要具备产品管理、用户体验设计等技能。

专业系统团队

在软件开发过程中,有一些模块或逻辑涉及高深专业领域知识而显得特别复杂,开发和维护都需要相关领域的资深专家(人也特别贵),对于这样的专业系统,我们通常会单独构建团队,称为为「专业系统团队」。

专业系统团队的成员都是某个领域的能手,精通子系统涉及的核心技术。比如 AI 算法模型、视频编解码、特定数学模型、实时交易算法、财务报表系统、人脸识别引擎等,都是典型的复杂子系统,需要专业系统团队来专门负责。

组建专业系统团队的主要目的,是为了给使用这些核心子系统的业务团队减负。本来这些「绝世武功」需要专业系统团队的「武林高手」倾囊相授,现在业务团队只管安心用就行了,不必自己还得练上几年。这样不仅能让每个团队专注自己最擅长的事情,也能节省组织的时间和成本。

需要注意的是,专业系统团队和传统的「组件团队」有本质区别。后者的设立往往是为了让多个团队共用同一个组件,而专业系统团队纯粹是为了「解决疑难杂症」,和代码复用无关。

那么,一个高效能的专业系统团队应该是什么样的呢?

  • 在子系统的设计开发阶段,专业系统团队要和相关业务团队齐心协力,共同定义需求、制定计划、开发功能、测试验证,即「同进同出、战略合作」。到了后期子系统逐渐稳定了,专业系统团队就可以相对独立,专注于系统演进、接口优化等核心工作,和其他团队的互动会少一些。
  • 有了专业系统团队的加持,子系统的开发质量和速度都要明显好于单靠业务团队的情况。这可以作为考核专业系统团队绩效的一个重要指标。
  • 专业系统团队的工作安排要以服务好业务团队为宗旨。要紧贴一线,及时响应需求,灵活调整计划,按业务优先级来排期交付。

一些观点

  • 并非所有的沟通和协作都是有价值的。
  • 保持团队规模的相对稳定。
  • 在实现软件交付之前,先统一团队语言和共同协作方式。
  • 团队的代码所有权并非是在划分地盘。团队对代码的责任,不应该是「这是我的地盘,别人不能进来」,而应该是「这是我负责打理的一亩三分地,要让它生机勃勃」。
  • 软件设计不是非黑即白的选择题,而是一种平衡,如在选择架构时,不是要在单体架构和微服务架构之间做出选择,而是要适配团队的最大认知负载。

以上。

聊下缓存

在当今的互联网应用中,缓存作为一种提高系统性能的关键技术,扮演着至关重要的角色。无论是日常浏览网页、使用 APP,还是企业级应用的后台处理,缓存的存在无处不在。那么,什么是缓存?我们应该如何有效地利用缓存来优化系统性能呢?

什么是缓存?

从本质上来看,缓存是将数据暂时存储在比原始数据源更快的存储介质中,以便快速访问。其主要目的是减少数据访问的延迟,提高系统的性能,从而提升用户体验。

从一个 Web 请求的链路来看,主要有以下几种类型的缓存:

  1. 浏览器缓存:浏览器本地的缓存,包括内存缓存和磁盘缓存。可以通过 HTTP 响应头控制缓存策略,如 Cache-Control、Expires 等。
  2. DNS 缓存:本地机器或 DNS 服务器上对域名解析结果的缓存,可以加快后续对同一域名的访问速度。
  3. CDN 缓存:CDN 的边缘节点上的缓存,可以让用户从距离最近的节点获取资源,减轻服务器压力,提高响应速度。
  4. Web 服务器缓存:如 Nginx、Apache 等 Web 服务器自带的缓存功能,或者专门的缓存服务器如 Varnish 等。对请求进行缓存,可减轻后端服务器的负载。
  5. 应用层缓存:在应用程序中自己实现的缓存,如使用 Redis、Memcached 等内存型数据库对数据进行缓存,或者在代码中实现对特定数据的缓存。
  6. 数据库缓存:数据库本身的查询缓存,如 MySQL 的 Query Cache。会对 SELECT 语句及其结果进行缓存。
  7. 操作系统缓存:操作系统级别的文件系统缓存,如 Linux 的 Page Cache。可以加快对磁盘上同一文件的重复读取速度。

这些缓存按照其位置和类型,可以分为客户端缓存、网络缓存和服务端缓存。合理地利用各种缓存,可以显著提升 Web 应用的性能和用户体验。同时也要注意缓存的更新策略,以免数据不一致问题的出现。

1 客户端缓存

客户端缓存是指存储在客户端本地的缓存数据,主要是浏览器缓存。浏览器缓存是最常见的客户端缓存形式,它可以显著减少网络传输,加快页面加载速度,提升用户体验。

浏览器缓存主要包括以下两种类型:

  1. 内存缓存:
    • 内存缓存是指存储在内存中的缓存数据,读取速度非常快。
    • 但是内存缓存的生命周期较短,会在浏览器关闭时被清除。
    • 内存缓存一般用于存储当前页面中已经下载的资源,如页面文档、图片、脚本、样式表等。
  2. 磁盘缓存:
    • 磁盘缓存是指存储在磁盘上的缓存数据,读取速度相对内存缓存慢一些,但是容量更大。
    • 磁盘缓存的生命周期更长,可以在浏览器关闭后继续保留。
    • 磁盘缓存主要用于存储那些可能在将来的请求中重复使用的资源,如静态图片、脚本、样式表等。

浏览器缓存的工作原理涉及到两个重要的概念:强缓存和协商缓存

  1. 强缓存
    • 当资源在缓存有效期内时,浏览器会直接从缓存中读取,不会向服务器发送请求
    • 强缓存由 HTTP 响应头中的 Cache-Control 或 Expires 字段控制。都是表示资源的缓存有效时间。
    • Expires 是 HTTP 1.0 的规范,值是一个 GMT 格式的时间点字符串。缺点是失效时间是一个绝对时间,服务器时间与客户端时间偏差较大时会导致缓存混乱。
    • Cache-Control 是 HTTP 1.1 的规范,一般常用该字段的 max-age 值来进行判断,它是一个相对时间。
    • 常见的应用场景有静态资源缓存,如 CSS、JavaScript、图片等,可以设置较长的缓存时间。
  2. 协商缓存
    • 协商缓存是由服务器来确定缓存资源是否可用,协商缓存会向服务器发送请求,询问资源是否有更新。
    • 协商缓存由 HTTP 响应头中的 Last-Modified/If-Modified-Since 或 ETag/If-None-Match 字段控制。
    • 服务器根据资源的最后修改时间或内容生成的唯一标识(ETag)来判断资源是否有更新。
    • 如果资源没有更新,服务器会返回 304 状态码,告诉浏览器可以直接从缓存中读取。
    • 常见的应用场景有动态内容缓存,如新闻、博客文章等,以及 API 响应缓存。

在以上提到的这些缓存控制标签中,优先级:Cache-Control > Expires > ETag > Last-Modified

浏览器在处理缓存时,会优先执行强缓存策略,如果强缓存失效,则执行协商缓存策略。通过合理设置 HTTP 缓存头部,可以有效利用浏览器缓存,减少不必要的网络传输,提升 Web 应用的性能。

这里想多聊一点关于 stale-while-revalidate 和 stale-if-error。它们都是 HTTP Cache-Control 响应头的扩展指令,用于控制浏览器如何处理陈旧的缓存资源,是两个略小众的指令。

1. stale-while-revalidate

语法:Cache-Control: max-age=, stale-while-revalidate=

stale-while-revalidate 指令用于指定在缓存过期后,允许客户端在异步 revalidate 的同时,继续使用陈旧的缓存资源的最长时间。

工作原理

  • 在 max-age 时间内,浏览器直接从缓存中获取资源,不会发送请求。
  • 当缓存过期后,在 stale-while-revalidate 指定的时间内,浏览器会发送请求到服务器进行revalidate,但同时会立即返回陈旧的缓存资源给客户端使用。
  • 如果 revalidate 成功(即服务器返回304 Not Modified),则缓存更新,并且下次请求会直接从缓存中获取。
  • 如果 revalidate 失败(即服务器返回 200 或其他状态码),则缓存更新为新的响应内容,并且下次请求会直接从缓存中获取新内容。

使用场景

  • 适用于更新不太频繁,但又希望用户总是能看到最新内容的资源,如 CSS、JavaScript 等。
  • 提高了用户体验,避免因等待revalidate而延迟页面呈现。

示例:Cache-Control: max-age=600, stale-while-revalidate=30
说明:资源可以在 600 秒内直接从缓存中获取,在接下来的30秒内,虽然缓存已过期,但浏览器仍可以显示陈旧的缓存资源,同时在后台进行异步revalidate。

2. stale-if-error

语法:Cache-Control: max-age=, stale-if-error=

stale-if-error 指令用于指定在发生错误(如网络错误、服务器错误等)时,允许客户端使用陈旧的缓存资源的最长时间。

工作原理:

  • 在 max-age 时间内,浏览器直接从缓存中获取资源,不会发送请求。
  • 当缓存过期后,浏览器会发送请求到服务器获取最新资源。
  • 如果请求过程中发生错误,并且在 stale-if-error 指定的时间内,浏览器会返回陈旧的缓存资源给客户端使用。
  • 如果请求成功,则缓存更新,并且下次请求会直接从缓存中获取。

使用场景:

  • 适用于更新频率较高,但又希望在请求出错时能显示旧内容,而不是错误页面的资源,如用户生成的内容、新闻文章等。
  • 提高了用户体验,避免因请求错误而显示错误页面。

示例:Cache-Control: max-age=600, stale-if-error=1200
说明:资源可以在 600 秒内直接从缓存中获取,在接下来的 1200 秒内,如果请求发生错误,浏览器仍可以显示陈旧的缓存资源。

这两个指令可以单独使用,也可以组合使用,以实现更灵活的缓存控制策略。它们的目的都是在一定条件下允许使用过期的缓存,以提高性能和用户体验,同时还能保证一定的数据更新。

需要注意的是,这两个指令都是 HTTP Cache-Control 响应头的扩展,并非所有浏览器都支持。目前,Chrome、Firefox 等主流浏览器已经实现了对这两个指令的支持。在实际应用中,还需要进行充分的测试和评估,以确保缓存策略的有效性和可靠性。

并且尽管浏览器已经支持了,但是服务器也需要正确地设置响应头才能生效。

2 服务器缓存

服务器缓存是指将数据临时存储在服务器的内存或磁盘上,以便后续的请求可以直接从缓存中获取数据,而不必每次都重新生成或计算数据。服务器缓存的目的是提高服务器的响应速度,减少数据处理的开销,从而提升整个系统的性能和吞吐量。

服务器缓存可以分为多个层次和类型,包括:

1.Web 服务器缓存:在 Web 服务器上配置的缓存机制,如:

  • Nginx 的 FastCGI 缓存、Proxy 缓存等。
  • Apache 的 mod_cache 模块。
  1. 应用层缓存:在应用程序中实现的缓存机制,通常使用内存缓存技术,如:
    • 对象缓存:将频繁访问的数据对象缓存在内存中,如 Java 的 Guava Cache 等。
    • 查询缓存:将数据库查询的结果缓存起来,下次相同的查询可以直接从缓存中获取,如 Hibernate Second Level Cache。
    • 页面缓存(Page Caching):将动态生成的网页内容缓存起来,下次请求可以直接返回缓存的页面。
  2. 数据库缓存:在数据库系统中实现的缓存机制,如 MySQL Query Cache
  3. 分布式缓存:将数据缓存在独立的分布式缓存服务器上,供多个应用服务器共享使用,如常用的 Redis。

这些服务器缓存的类型可以独立使用,也可以组合使用,形成多级缓存架构。不同类型的服务器缓存在系统架构中的位置不同,缓存的数据类型和粒度也不同,但它们的共同目标都是提高服务器的性能,加快数据访问速度。

在实际应用中,需要根据具体的业务场景和性能瓶颈,选择适当的服务器缓存策略。这包括合理设计缓存的粒度、缓存的过期策略、缓存的更新机制、缓存的容量规划等。同时也要注意缓存的一致性问题,确保缓存的数据与原始数据源保持同步,避免出现脏读或数据不一致的问题。

服务器缓存是提高系统性能的重要手段之一。合理利用服务器缓存,可以显著减少数据处理的开销,提高服务器的响应速度,从而提升整个系统的吞吐量和并发处理能力。但同时也要权衡缓存的成本和收益,避免过度使用缓存而带来的内存开销和维护成本。

3 缓存实现策略或模式

从应用程序角度来看,缓存的实现模式主要涉及如何在应用程序中使用和集成缓存,以提高数据访问的效率和性能。

常用的缓存模式有以下几种:

  1. 旁路缓存(Cache Aside)
    • 应用程序先查询缓存,如果缓存中有数据,则直接返回。
    • 如果缓存中没有数据,则从数据源(如数据库)查询,并将查询结果存入缓存,然后返回。
    • 更新数据时,先更新数据源,然后再删除缓存,保证数据一致性。
    • 优点:简单易懂,应用程序可以控制缓存的生命周期。
    • 缺点:可能出现缓存和数据源不一致的情况,需要合理设计缓存的过期策略和并发策略。
  2. 读写穿透(Read/Write Through)
    • 应用程序只与缓存交互,不直接访问数据源。
    • 缓存服务负责与数据源交互,并将数据在缓存和数据源之间同步。
    • 读取数据时,缓存服务先查询缓存,如果缓存中没有数据,则从数据源查询,并将结果存入缓存,然后返回。
    • 更新数据时,缓存服务先更新缓存,然后再更新数据源,保证数据一致性。
    • 优点:应用程序无需关心缓存和数据源的同步,缓存服务保证了数据一致性。
    • 缺点:缓存服务需要实现与数据源的交互,增加了复杂性;写操作的性能可能较低,因为需要同时更新缓存和数据源。
  3. 异步写入(Write Behind)
    • 应用程序只与缓存交互,不直接访问数据源。应用程序更新缓存,缓存服务在后台异步地将数据更新到数据源。
    • 写入数据时,只更新缓存,并将更新操作加入队列。
    • 异步线程或进程从队列中取出更新操作,并批量写入数据源。
    • 优点:写入操作的性能很高,因为只需要更新缓存;数据源的写入可以批量进行,提高效率。
    • 缺点:缓存和数据源之间可能存在一定的延迟,需要合理设计队列的大小和刷新策略;如果缓存服务崩溃,可能导致数据丢失,因此需要着重考虑缓存服务的可靠性
  4. 预刷新(Refresh Ahead)
    • 定期或在特定条件下,异步地从数据源加载数据到缓存中。
    • 可以通过定时任务、事件触发或者智能预测等方式来触发预刷新操作。
    • 优点:避免了缓存 miss 导致的性能下降,提高了读取操作的响应速度。
    • 缺点:需要额外的计算资源和存储空间来执行预刷新操作;如果预刷新的数据无法准确预测,可能会浪费资源。

在业务场景中我们往往不局限于只使用某一种策略,可能会是使用以上多种模式,,根据不同的数据特点和访问模式,采用不同的策略。例如,对于读多写少的数据,可以使用「旁路缓存」或「读写穿透」策略;对于写多读少的数据,可以使用「异步写入」策略。

计算机领域有个名言警句:

There are only two hard problems in Computer Science: cache invalidation, and naming things.(计算机领域只有有两大难题,「让缓存失效」和「给东西命名」)

接下来我们聊一下缓存过期策略。

4 缓存过期策略

缓存过期策略是指确定缓存数据何时失效并从缓存中移除的规则。合理的缓存过期策略可以帮助控制缓存的数据鲜度,并优化缓存的空间利用率。以下是一些常见的缓存过期策略:

  1. TTL 策略
    • 定义:TTL(Time To Live)策略为每个缓存条目设置一个固定的生存时间。当数据存入缓存时,指定一个过期时间。到达过期时间后,缓存条目将被自动移除,即使它在这段时间内没有被访问过。
    • 优点:TTL 策略实现简单,易于配置,适用于对数据新鲜度有严格要求的场景。
    • 缺点:容易导致缓存抖动,即频繁的缓存失效和重新加载,可能增加系统负载。
    • 场景:TTL策略适用于那些数据变化频繁且需要确保数据新鲜度的场景。例如,实时新闻数据、股票价格、天气预报等。
  2. LRU 策略
    • 定义:LRU(Least Recently Used)策略根据使用频率来决定缓存条目的去留。当缓存空间不足时,会移除最近最少使用的条目,以腾出空间存储新的数据。
    • 优点:LRU 策略能有效利用缓存空间,适用于访问模式有局部性的场景。
    • 缺点:实现较复杂,可能会增加缓存管理的开销,特别是在高并发环境下。
    • 场景:LRU 策略广泛应用于需要频繁访问的大型数据集,例如 Web 服务器的页面缓存、数据库查询缓存等。
  3. LFU 策略
    • 定义:LFU(Least Frequently Used)策略根据访问频率来决定缓存条目的去留。当缓存空间不足时,会移除访问频率最低的条目,以腾出空间存储新的数据。
    • 优点:LFU 策略适用于访问频率有明显差异的场景,能有效缓存高频访问的数据。
    • 缺点:实现复杂度较高,频繁更新访问计数可能会增加系统负载。
    • 场景:LFU 策略适用于用户访问行为具有明显模式的应用,如推荐系统、热点新闻或视频的缓存。
  4. FIFO 策略
    • 定义:FIFO(First In, First Out)策略按照条目加入缓存的顺序来决定去留。最早加入缓存的条目最先被移除,不考虑条目的使用频率或时间。
    • 优点:FIFO 策略实现简单,适用于数据访问模式较为均匀的场景。
    • 缺点:可能会导致热门数据被过早移除,不适合需要缓存热点数据的场景。
    • 场景:FIFO 策略适用于缓存数据生命周期较短且频繁更新的场景,例如某些实时数据流的缓冲。
  5. ARC 策略
    • 定义:ARC(Adaptive Replacement Cache)策略结合了 LRU 和 LFU 的优点,通过动态调整缓存策略来适应不同的访问模式。ARC 维护两个 LRU 列表,一个用于最近访问过的数据,另一个用于以前访问过的数据,并根据缓存命中情况在这两个列表之间调整权重。
    • 优点:ARC策略能够自适应调整缓存替换策略,既考虑了最近使用的频率,又考虑了访问频率,从而提高缓存命中率。
    • 缺点:实现复杂,需要维护多个列表和动态调整算法,可能增加缓存管理的开销。
    • 场景:ARC 策略适用于访问模式多变且无法预知的场景,如混合型工作负载的缓存管理。它在需要高效利用缓存空间且保持高命中率的系统中表现尤为出色,例如数据库管理系统、操作系统的页面缓存等。
  6. SLRU 策略
    • 定义:SLRU(Segmented Least Recently Used)是一种缓存替换算法,它是LRU(Least Recently Used)算法的一个变体。SLRU(Segmented LRU)策略将缓存分为两个段:一个是保护段(probation segment),另一个是优选段(protected segment)。新加入的条目首先进入保护段,如果条目在保护段中被再次访问,则移动到优选段。优选段中的条目如果再次被访问,则保持在优选段,否则会被移除。
    • 优点:SLRU 策略通过分段管理缓存条目,既能保留最近访问的数据,也能保护多次访问的数据,提高缓存命中率。
    • 缺点:实现复杂度较高,需要维护多个段和管理策略,可能增加系统开销。
    • 场景:SLRU 策略适用于需要平衡最近访问和频繁访问需求的场景,例如Web浏览器的缓存管理、文件系统的缓存管理等。

5 一些注意事项

在应用开发中使用缓存虽然可以显著提升系统性能和用户体验,但如果不当使用,也可能导致一些问题和陷阱。

  1. 缓存与数据源的一致性: 缓存数据和原始数据源之间的不一致是常见的问题之一。当数据被更新时,如果缓存没有同步更新,就会出现旧数据被重复使用的情况。
  2. 缓存穿透:缓存穿透指查询不存在的数据时,请求直接穿过缓存访问数据库,如果这种请求非常频繁,将严重影响数据库的性能。
  3. 缓存雪崩:缓存雪崩是指在缓存层面发生大规模的缓存失效,导致所有的请求都去打数据库,可能会因此使数据库压力过大而崩溃。
  4. 缓存预热:系统启动后缓存是空的,直接面对大流量可能会导致短时间内数据库请求量激增。
  5. 脏读问题:在分布式环境中,如果多个节点同时对缓存进行读写操作,可能会读到过期或不一致的数据。

6 小结

缓存不是解决性能问题的银弹,而是一种在适当的场景下能够显著提升系统响应速度和处理能力的工具。在实际应用中,缓存的引入需要仔细考虑其适用性、一致性问题、资源管理和安全性等多方面因素。

缓存最适合用于读操作远多于写操作的数据,以及那些数据更新不频繁、但需要快速访问的场景。然而,对于高度动态的数据,缓存可能不仅无法提供预期的性能提升,反而因为频繁的缓存更新和失效处理增加了额外的复杂性和开销。

在使用缓存的过程中,数据一致性是引入缓存时必须面对的一个挑战。无论是在单体应用还是分布式系统中,如何保证缓存中的数据与数据库中的数据保持一致,是设计缓存策略时必须仔细考虑的问题。不恰当的缓存策略可能导致数据过时或错误,影响业务的正确性。

并且,缓存的管理和维护也是一项不可忽视的任务。正确的缓存大小、适宜的过期策略、有效的内存管理等都是确保缓存系统高效运作的关键。缓存过大可能会消耗过多的内存资源,影响系统的稳定性;缓存过小则可能无法发挥缓存的性能优势。

缓存是一种强大的优化工具,但它并不适合所有情况。只有在正确的场景下,配合合适的策略和周到的管理,才能发挥出缓存的最大效能,帮助提升应用的性能和用户体验。