Lyft 微服务研发效能提升实践 | 2. 优化快速本地开发

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: Lyft 微服务研发效能提升实践 | 2. 优化快速本地开发

怎样才能提高研发效率?是依赖于各自独立的本地开发测试环境,还是依赖完整的端到端测试?Lyft 的这一系列文章介绍了其开发环境的历史和发展,帮助我们思考如何打造一套适合大规模微服务的高效研发环境。本系列共 4 篇文章,这是第 2 篇。原文:Scaling productivity on microservices at Lyft (Part 2): Optimizing for fast local development[1]


image.png


本系列介绍的是 Lyft 在面对越来越多的开发人员和服务时,如何高效扩展开发实践,本文是第二篇。



本文将专注于我们如何将优秀的开发体验带到笔记本电脑上,从而实现超快的迭代。


缺少内部开发循环


开发人员在更改代码时可能会将流程分解为内部开发循环和外部开发循环。内部开发循环是快速迭代的循环,即进行代码更改并测试其是否有效。理想情况下,开发人员会多次执行内部开发循环以使保证开发的特性工作,大部分时间应该花在编辑代码上,然后在 10 秒内快速运行测试。外部开发循环通常包括将代码更改同步到远程 git 分支,在 CI 上运行测试,并在部署更改之前进行代码检查。外部开发循环通常需要至少 10 分钟的时间,理想情况下只需要少量的时间来处理代码评审和注释。


image.png


正如我们在前一篇文章中提到的,执行内部开发循环需要将代码更改同步到开发者自己运行的一个名为 Onebox 的远程虚拟机环境中。这些环境是出了名的变化无常,启动时间很长,需要经常重建,用户总是因为内部开发循环经常被这些环境问题所阻碍而感到沮丧。环境调整和同步代码变更使得这个过程看起来更像外部开发循环,开发人员通常会回到真正的外部开发循环,并使用 CI 为每个迭代运行测试。


每次运行一个服务


所以我们开始构建一个简单快速的内部开发循环。需要进行的核心转变是从 Onebox(许多服务)的完全集成环境,转向只运行一个服务及其测试的隔离环境。这种新的隔离环境将在开发人员的笔记本电脑上运行,这又回到了上图所示的内部开发循环,在这个循环中,用户只是简单的编辑代码并运行测试,中间没有额外的步骤。我们努力使绝大多数测试独立于单个服务。我们还创建了在笔记本电脑上启动单个服务并向其发送测试请求的能力。


我们决定在 MacOS 上直接运行服务代码,而不使用容器或 VM。从以前的经验中,我们了解到在容器中运行代码并不是一种自由的抽象,虽然设置执行环境变得更容易了,但会导致用户困惑,并在容器网络或文件系统安装出现问题时带来额外的调试挑战。与在容器中运行相比,本机运行也能得到更好的 IDE 支持。我们仍然在某些情况下使用容器,比如运行只能在 Linux 上运行的数据存储或服务。


设置笔记本电脑环境


在 MacOS 上本地运行代码的最大代价是必须在每个开发人员的笔记本电脑上配置和维护环境。为了克服这个问题,我们投资了一些工具,让 Lyft 的新开发人员能够很快启动和运行。


后端服务是用 Python 和 Go 编写的(少数例外),前端服务是用 Node 编写的,每个服务都有自己的 Github 库和依赖集。


Python


对于 Python,我们为每个服务构建一个虚拟环境(也称为 venv)。我们开发了一个工具来帮助创建和管理 venv。该工具分发特定的受支持的 Python 版本,并进行一些操作系统设置,例如通过 Homebrew 安装共享库,以及设置 SSL 以使用正确的证书,并且强制使用内部 PyPi 仓库。


当用户运行命令构建 venv 时,它将会:

  • 查看元数据,为该服务选择正确的 Python 版本
  • 创建一个新的 venv
  • 通过 pip 安装定义在 requirements.txt 中的依赖项


一旦 venv 被构建,必须被激活(添加到 $PATH 中)。每次用户进入服务目录时,我们使用 aactivator[2]来自动激活 venv,并在他们离开时失效。创建的 venv 是不可变的(pip install 被禁用)。当对 requirements.txt 进行更改时,将构建一个新的 venv。这确保 venv 中的依赖项与 requirements.txt 文件和将要部署的内容精确匹配。之前完全构建的 venvs 会被缓存,所以如果用户将更改恢复到 requirements.txt,它将使用之前构建的版本。它还支持创建可变的 venv,以便在不触发完全重新构建的情况下轻松尝试新的依赖项。我们还支持为内部 Python 库创建 venv,以便轻松的在本地运行测试。


Go


对于 Go 来说,设置非常简单。用户安装 Go 运行时,设置一些环境变量(例如用于下载依赖关系的代理),然后可以使用 go run go test。多亏了超棒的 Go modules[3]工具链,每次运行这些命令时,都可以自动下载并链接所有依赖项。


Node


对于 Node,我们使用围绕 nodeenv[4]的自定义包装器,根据元数据为服务下载并安装正确的 node npm,防止用户需要手动安装 nvm[5]之类的版本管理器,并在运行不同的服务时切换到正确的 Node 版本。


除了少数服务以外,开发人员都可以使用上面描述的环境在笔记本电脑上直接运行服务代码。一些服务依赖于仅在 Linux 上支持的库,对于这个非常小的子集,开发人员可以轻松的下载由 CI 系统构建的 Docker 镜像,挂载本地代码目录,从而为服务运行测试。虽然这个过程更麻烦,但仍然可以实现快速迭代。


运行服务


对于开发人员来说,能够快速迭代一个完全运行的服务是很重要的,所以我们努力在笔记本电脑上启用用例。我们创建工具来协调启动服务、发送测试请求以及代理由该服务发出的任何请求。为了确保数据隔离,服务使用的数据存储每次都在本地启动,并带有新数据。在启动时运行脚本来创建表并插入测试所需的任何数据。团队负责维护这个测试数据集,以允许对其特性进行适当的测试。


下面是运行服务所需步骤示例:


  • 运行环境检查(例如,工具安装正确,签出必要的 git 仓库,确保端口空闲)
  • 激活虚拟环境(Python 和 Node 服务)
  • 启动数据存储(例如 dynamodb, elasticsearch, postgres)
  • 启动代理应用程序(稍后详细介绍)
  • 运行数据存储填充脚本
  • 运行服务


让开发人员手动运行所有这些操作非常繁琐且容易出错,因此我们需要工具来编排这些检查,并使用声明性配置管理必要的流程,为此我们决定使用 Tilt[6]。虽然 Tilt 经常用于测试 Kubernetes 集群中的代码,但我们目前使用它来进行纯粹的本地工作流管理。每个服务都有一个 Tiltfile[7],指定启动服务之前必须运行的步骤。Tiltfile 是用 Starlark 编写的,是 Bazel 使用的一种 Python 方言,为服务所有者提供了很大的灵活性。我们提供了常用的函数(例如 ensure_venv(), launch_dynamodb())),所以服务 Tiltfiles 主要由这些预定义的函数调用组成。


为了启动服务,用户在终端上执行 tilt up,Tilt 将解析 Tiltfile 并创建一个内部执行计划,按照 Tiltfile 中指定的顺序运行所有检查和处理。Tilt 有一个本地网页应用,可以显示所有正在运行的东西的状态。用户可以点击 web 应用中的选项卡来显示每个进程的日志输出。这允许用户跟踪正在运行的进程状态,并使用日志调试任何错误。


一旦服务开始运行,当用户在 IDE 中编辑代码时,它将自动重新加载。这是缩短内部循环的一大优势,因为用户甚至不需要触发任何操作来重新加载服务。


处理对其他服务的请求


Lyft 由一个很大的服务网络组成,几乎任何服务都会调用这个网络中至少一个其他服务。有两种主要的方法来处理本地服务发出的请求:


  1. 构造并返回一个模拟的响应
  2. 将请求转发到另一个真实环境中


我们使用自己开发的内部工具,以非常灵活的方式支持这两种方式。


几年前,Lyft 开发了一款代理应用,作为一种帮助移动应用开发者将开发工作流程与后端服务团队分离开来的工具。它是一个 Electron 应用程序,在移动应用程序对预发环境 API 的调用之间充当代理。开发人员将移动应用程序连接到代理服务器,为每个用户提供一个唯一的 URL。默认情况下,代理将把所有请求转发到预发环境。用户可以选择覆盖特定的调用并返回完全模拟的数据,或者在预发环境的响应中改变某个字段。这使得手机开发者能够在后端 API 仍处于开发阶段时测试应用的变化。设置是这样的:


image.png


与 charlesproxy[8]等其他工具相比,这个代理应用带来的最大优势是与 Lyft 的接口定义语言(IDL[9])深度集成,IDL 是通过 protocol buffers[10]实现的。在 IDL 中,我们为后端服务端点指定请求和响应结构。在代理应用中,用户通过一个 Typescript 代码编辑器(使用 VSCode 中的 Monaco Editor[11])来编写响应,从而与 IDL 集成并为用户提供类似于 IDE 的体验,可以进行类型检查,并自动补全模拟数据响应的结构。代码接口还允许复杂的交互,比如将字段从请求设置为响应。它还显示了通过代理的所有请求的可读请求和响应体,使用户能够很好的可视化来自移动应用程序的所有请求。


当我们开发本地运行后端服务的工具时,代理应用程序非常适合处理本地服务向其他服务发出的请求。我们重用了代理功能,将请求转发给预发环境,或者根据用户的意图返回模拟数据。设置过程是这样的:


image.png

向本地服务发出请求


接下来,我们需要一个能够让用户直接编写并向本地运行的服务发送请求的工具。这在 Devbox/Onebox 时代并不常见,因为大多数测试请求都来自移动客户端,所以我们必须想出新的解决方案。构造 API 请求的工具有很多,比如 curl[12]或 Postman[13]。但我们需要在 Lyft 支持几种 RPC 传输格式,包括 GRPC、JSON(基于 HTTP)和 protobuf(基于 HTTP)。没有任何现有工具可以无缝处理这些不同的格式、利用我们的定义以及轻松的组合请求。


代理程序对我们来说是最好的选择,我们添加了帮助用户使用 Typescript 代码编辑器编写请求并按下按钮将请求发送到本地服务的能力,再次利用了与 IDL 的集成来提供 URL 路径以及实现了请求体字段的自动补完功能。


结果


自从我们在今年早些时候向整个公司推出这个工具以来,反馈一直非常积极。开发人员喜欢在笔记本电脑和 IDE 上运行测试,而不需要任何远程环境。创建一个新的 Onebox 环境通常需要大约一个小时,但现在笔记本电脑环境总是可以运行测试,使用 Tilt 在本地启动服务只需要几分钟。


因为开发者花了更多的时间来测试自己的服务,我们还观察到他们的行为发生了转变。在本地运行服务时,用户直接向服务 API 发送请求,而不是通过移动应用程序和公共 API 进行测试。这增加了开发人员对服务 API 的熟悉程度,并减少了出错时的调试范围。


虽然成本并不是这个项目的主要驱动因素,但由于不需要为每个开发人员配置支持 Onebox 的强大的 AWS 实例,因此最终节省了可观的开销。让用户在自己的笔记本电脑上独立运行服务意味着真正减少所需的总计算资源。


未来的工作


上面所描述的是工具的第一次迭代。关于下一步该怎么做,我们有很多令人兴奋的想法。以下是其中的一些:


支持苹果芯片


我们很高兴即将开始向开发人员交付带有 M1 芯片的新款 Macbook Pro。早期的基准测试显示,即使是在仿真环境下,这些机器仍然提供了开箱即用的巨大的性能提升!那些确保我们将所有东西都在本地原生运行的额外工作将帮助我们获取性能提升的好处。


将请求从预发环境中的服务路由到本地服务


目前,用户必须直接调用本地运行的服务,而无法调用客户端 API,并在路由到本地服务之前,让请求通过预发服务。我们计划很快启用这个功能,这将允许用户测试完整的端到端用户流(如运行一款手机应用),以测试后端服务的新功能,同时仍然拥有本地开发的快速内部开发循环。


改进发送请求 UI


代理应用程序中用于编写请求的代码接口非常灵活和强大,但对于新手用户来说,正确构造请求仍然具有挑战。我们希望为这个用例创建一个更像 Postman[13]的定制 UI,同时保持代码接口的强大功能。我们还计划创建一个 API 平台,用户可以很容易的发现并体验 Lyft 的任何服务。


远程开发环境


在像 Github Codespaces 这样的完全远程开发环境中,已经有了一些令人兴奋的发展。随着这些解决方案的成熟,我们肯定会密切关注,看看它们是否适合我们的用例。


本系列的下一篇文章将展示如何安全的将 PR 中的代码部署到预发环境中并对其进行测试。


References:

[1] Scaling productivity on microservices at Lyft (Part 2): Optimizing for fast local development: https://eng.lyft.com/scaling-productivity-on-microservices-at-lyft-part-2-optimizing-for-fast-local-development-9f27a98b47ee

[2] aactivator: https://github.com/Yelp/aactivator

[3] Using Go Modules: https://go.dev/blog/using-go-modules

[4] Node.js virtual environment: https://github.com/ekalinin/nodeenv

[5] Node Version Manager: https://github.com/nvm-sh/nvm

[6] Tilt: https://tilt.dev/

[7] Writing Your First Tiltfile: https://docs.tilt.dev/tiltfile_authoring.html

[8] Charles Web Debugging Proxy: https://www.charlesproxy.com/

[9] IDL: https://en.wikipedia.org/wiki/IDL_(programming_language)

[10] Protocol Buffers: https://developers.google.com/protocol-buffers

[11] Monaco Editor: https://microsoft.github.io/monaco-editor/index.html

[12] curl: https://curl.se/

[13] Postman: https://www.postman.com/

目录
相关文章
|
3天前
|
存储 监控 API
构建高效微服务架构:后端开发的现代实践
【5月更文挑战第9天】 在本文中,我们将深入探讨如何在后端开发中构建一个高效的微服务架构。通过分析不同的设计模式和最佳实践,我们将展示如何提升系统的可扩展性、弹性和维护性。我们还将讨论微服务架构在处理复杂业务逻辑和高并发场景下的优势。最后,我们将分享一些实用的工具和技术,以帮助开发者实现这一目标。
|
2天前
|
监控 数据库 开发者
构建高效可靠的微服务架构:策略与实践
【5月更文挑战第11天】在当今软件开发的世界中,微服务架构已经成为构建可扩展、灵活且容错的系统的首选方法。本文深入探讨了设计、部署和维护微服务系统时面临的挑战,并提出了一系列实用的策略和最佳实践。我们将从服务的划分原则出发,讨论如何确保每个微服务的自治性,以及如何通过容器化和编排技术实现服务的高效运行。文章还将涉及监控、日志记录和故障恢复的策略,旨在帮助开发人员构建一个既高效又可靠的微服务环境。
|
2天前
|
Kubernetes API 开发者
构建高效微服务架构:后端开发的新范式
【5月更文挑战第11天】 在现代软件开发的快速演变中,微服务架构已成为企业追求敏捷性、可扩展性和技术多样性的关键解决方案。本文旨在探讨如何构建高效的微服务架构,并分析其对后端开发的影响。我们将通过一系列最佳实践和策略,展示如何优化服务的独立性、弹性和性能,同时确保系统的整体稳定性和安全性。文章还将介绍容器化、API网关、服务发现和分布式追踪等关键技术的应用,为后端开发者提供一份全面的微服务实施指南。
|
2天前
|
设计模式 监控 API
构建高效的微服务架构:后端开发的新范式
【5月更文挑战第11天】 在当今的软件开发领域,微服务架构已经成为一种流行的设计模式。它通过将应用程序分解为一组小型、松散耦合的服务来提供高度可扩展和灵活的解决方案。本文将探讨如何构建一个高效的微服务架构,包括选择合适的技术栈、设计原则以及应对常见挑战的策略。我们将深入讨论如何确保系统的可维护性、可靠性和性能,同时考虑到安全性和监控的需求。
|
3天前
|
缓存 负载均衡 API
微服务架构下的API网关性能优化实践
【5月更文挑战第10天】在微服务架构中,API网关作为前端和后端服务之间的关键枢纽,其性能直接影响到整个系统的响应速度和稳定性。本文将探讨在高并发场景下,如何通过缓存策略、负载均衡、异步处理等技术手段对API网关进行性能优化,以确保用户体验和服务的可靠性。
|
3天前
|
监控 持续交付 开发者
构建高效微服务架构:后端开发的新范式
【5月更文挑战第10天】在现代软件开发领域,微服务架构已经成为一种流行的设计模式,它通过将大型应用程序拆分为一组小型、独立和松散耦合的服务来提供更高的可伸缩性和灵活性。本文深入探讨了微服务架构的设计理念、实施步骤以及面临的挑战,并提出了一套实用的策略和最佳实践,帮助后端开发者构建和维护高效的微服务系统。
|
4天前
|
监控 API 持续交付
构建高效可靠的微服务架构:策略与实践
【5月更文挑战第8天】在当今快速演进的软件开发领域,微服务架构已经成为实现敏捷开发、持续交付和系统弹性的关键模式。本文将探讨构建一个高效且可靠的微服务系统所必须的策略和最佳实践。我们将从服务的划分与设计原则出发,讨论如何通过容器化、服务发现、API网关以及断路器模式来优化系统的可伸缩性和鲁棒性。此外,我们还将涉及监控、日志管理以及CI/CD流程在确保微服务架构稳定运行中的作用。
|
5天前
|
Kubernetes 持续交付 开发者
构建高效微服务架构:后端开发的新趋势
【5月更文挑战第8天】 随着现代软件开发的不断演进,微服务架构已成为众多企业解决复杂系统问题的首选方案。本文深入探讨了微服务架构的核心概念、设计原则以及实施策略,旨在为后端开发者提供一种清晰、高效的技术路径。通过分析微服务的优势与挑战,结合具体的应用实例,文章将展示如何通过容器化、服务网格和持续集成/持续部署(CI/CD)等先进技术手段,实现后端服务的高可用性、可扩展性和敏捷性。
|
5天前
|
消息中间件 监控 Java
构建高效微服务架构:后端开发的新趋势
【5月更文挑战第8天】随着现代软件开发的复杂性日益增加,传统的单体应用架构逐渐难以满足快速迭代和灵活部署的需求。微服务架构作为一种新的解决方案,以其模块化、独立性强和易于扩展的特点,正在成为后端开发领域的重要趋势。本文将深入探讨如何构建一个高效的微服务架构,并分析其对后端开发实践的影响。
|
5天前
|
API 持续交付 开发者
构建高效微服务架构:后端开发的新视角
【5月更文挑战第8天】 随着现代软件开发的演变,微服务架构已经成为了企业追求敏捷、可扩展和灵活部署的重要解决方案。本文将深入探讨如何构建一个高效的微服务架构,包括关键的设计原则、技术栈选择以及持续集成与部署的最佳实践。我们还将讨论微服务带来的挑战,如数据一致性、服务发现和网络延迟,并提出相应的解决策略。通过本文,后端开发者将获得构建和维护微服务系统所需的深度知识,并了解如何在不断变化的技术环境中保持系统的健壮性和可维护性。
41 8