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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 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/

目录
相关文章
|
5天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
26 5
|
9天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
9天前
|
存储 NoSQL 分布式数据库
微服务架构下的数据库设计与优化策略####
本文深入探讨了在微服务架构下,如何进行高效的数据库设计与优化,以确保系统的可扩展性、低延迟与高并发处理能力。不同于传统单一数据库模式,微服务架构要求更细粒度的服务划分,这对数据库设计提出了新的挑战。本文将从数据库分片、复制、事务管理及性能调优等方面阐述最佳实践,旨在为开发者提供一套系统性的解决方案框架。 ####
|
7天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
8天前
|
Kubernetes Cloud Native Docker
云原生技术探索:容器化与微服务的实践之道
【10月更文挑战第36天】在云计算的浪潮中,云原生技术以其高效、灵活和可靠的特性成为企业数字化转型的重要推手。本文将深入探讨云原生的两大核心概念——容器化与微服务架构,并通过实际代码示例,揭示如何通过Docker和Kubernetes实现服务的快速部署和管理。我们将从基础概念入手,逐步引导读者理解并实践云原生技术,最终掌握如何构建和维护一个高效、可扩展的云原生应用。
|
10天前
|
监控 API 持续交付
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在后端开发中的应用,分析了其优势、面临的挑战以及最佳实践策略。不同于传统的单体应用,微服务通过细粒度的服务划分促进了系统的可维护性、可扩展性和敏捷性。文章首先概述了微服务的核心概念及其与传统架构的区别,随后详细阐述了构建微服务时需考虑的关键技术要素,如服务发现、API网关、容器化部署及持续集成/持续部署(CI/CD)流程。此外,还讨论了微服务实施过程中常见的问题,如服务间通信复杂度增加、数据一致性保障等,并提供了相应的解决方案和优化建议。总之,本文旨在为开发者提供一份关于如何在现代后端系统中有效采用和优化微服务架构的实用指南。 ####
|
12天前
|
消息中间件 设计模式 运维
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在现代后端开发中的应用,通过实际案例分析,揭示了其在提升系统灵活性、可扩展性及促进技术创新方面的显著优势。同时,文章也未回避微服务实施过程中面临的挑战,如服务间通信复杂性、数据一致性保障及部署运维难度增加等问题,并基于实践经验提出了一系列应对策略,为开发者在构建高效、稳定的微服务平台时提供有价值的参考。 ####
|
12天前
|
消息中间件 监控 数据管理
后端开发中的微服务架构实践与挑战####
【10月更文挑战第29天】 在当今快速发展的软件开发领域,微服务架构已成为构建高效、可扩展和易于维护应用程序的首选方案。本文探讨了微服务架构的核心概念、实施策略以及面临的主要挑战,旨在为开发者提供一份实用的指南,帮助他们在项目中成功应用微服务架构。通过具体案例分析,我们将深入了解如何克服服务划分、数据管理、通信机制等关键问题,以实现系统的高可用性和高性能。 --- ###
36 2
|
10天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
51 6
|
10天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
27 1