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

简介: 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/

目录
相关文章
|
7月前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
2172 10
|
12月前
|
人工智能 安全 Java
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
391 5
|
12月前
|
人工智能 Java 数据库
飞算 JavaAI:革新电商订单系统 Spring Boot 微服务开发
在电商订单系统开发中,传统方式耗时约30天,需应对复杂代码、调试与测试。飞算JavaAI作为一款AI代码生成工具,专注于简化Spring Boot微服务开发。它能根据业务需求自动生成RESTful API、数据库交互及事务管理代码,将开发时间缩短至1小时,效率提升80%。通过减少样板代码编写,提供规范且准确的代码,飞算JavaAI显著降低了开发成本,为软件开发带来革新动力。
|
9月前
|
IDE Java API
Java 17 新特性与微服务开发的实操指南
本内容涵盖Java 11至Java 17最新特性实战,包括var关键字、字符串增强、模块化系统、Stream API、异步编程、密封类等,并提供图书管理系统实战项目,帮助开发者掌握现代Java开发技巧与工具。
467 1
|
9月前
|
存储 监控 Shell
SkyWalking微服务监控部署与优化全攻略
综上所述,虽然SkyWalking的初始部署流程相对复杂,但通过一步步的准备和配置,可以充分发挥其作为可观测平台的强大功能,实现对微服务架构的高效监控和治理。尽管未亲临,心已向往。将一件事做到极致,便是天分的展现。
|
9月前
|
缓存 监控 API
电商API的微服务架构优化策略
随着电商快速发展,API成为连接用户、商家与系统的核心。本文探讨微服务架构下电商API的优化策略,分析高并发、低延迟与数据一致性等挑战,并提供服务拆分、缓存异步、监控容器化等实践方案,助力构建高性能、高可用的电商系统,提升用户体验与业务效率。
234 0
|
11月前
|
人工智能 数据可视化 JavaScript
颠覆开发效率!国内首个微服务编排框架Juggle开源啦!
Juggle是国内首个开源的微服务编排框架,专注于解决企业微服务进程中接口重复开发、系统对接复杂等问题。它提供零代码、低代码和AI增强功能,通过可视化拖拽快速组装简单API为复杂接口,支持多协议、多语言脚本和流程多版本管理。相比国外框架如Conductor,Juggle更贴合国内需求,具备高效开发、企业级可靠性及信创适配等优势,助力企业实现敏捷创新与数字化转型。
颠覆开发效率!国内首个微服务编排框架Juggle开源啦!
|
10月前
|
Java API 微服务
Java 21 与 Spring Boot 3.2 微服务开发从入门到精通实操指南
《Java 21与Spring Boot 3.2微服务开发实践》摘要: 本文基于Java 21和Spring Boot 3.2最新特性,通过完整代码示例展示了微服务开发全流程。主要内容包括:1) 使用Spring Initializr初始化项目,集成Web、JPA、H2等组件;2) 配置虚拟线程支持高并发;3) 采用记录类优化DTO设计;4) 实现JPA Repository与Stream API数据访问;5) 服务层整合虚拟线程异步处理和结构化并发;6) 构建RESTful API并使用Springdoc生成文档。文中特别演示了虚拟线程配置(@Async)和StructuredTaskSco
1046 0
|
12月前
|
Cloud Native Serverless 流计算
云原生时代的应用架构演进:从微服务到 Serverless 的阿里云实践
云原生技术正重塑企业数字化转型路径。阿里云作为亚太领先云服务商,提供完整云原生产品矩阵:容器服务ACK优化启动速度与镜像分发效率;MSE微服务引擎保障高可用性;ASM服务网格降低资源消耗;函数计算FC突破冷启动瓶颈;SAE重新定义PaaS边界;PolarDB数据库实现存储计算分离;DataWorks简化数据湖构建;Flink实时计算助力风控系统。这些技术已在多行业落地,推动效率提升与商业模式创新,助力企业在数字化浪潮中占据先机。
611 12
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
798 6