Bazel使用案例:构建Springboot工程

本文是关于如何使用Bazel搭建Springboot 3.1.0工程(基于JDK17)。为什么使用Bazel,而不是使用Maven或者Gradle?可以看我之前关于Bazel的介绍文章。 前期准备 在根目录加入.bazelversion文件,并加入6.2.0,指定当前工程使用的Bazel的版本。这样,Bazel命令自动使用该版本的Bazel进行构建。 在根目录加入.bazelrc文件,并指定构建和测试时使用JDK17,内容如下: build --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17 test --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17 外部依赖准备 在根目录中创建以下两个文件: WORKSPACE:在Bazel中,所有的外部依赖统一定义WORKSPACE文件中; BUILD.bazel:内容留空即可,用于告诉Bazel当前目录也是一个Package。 Bazel本身是支持多语言的。所以,我们需要特定语言的rule来帮助我们在WORKSPACE中定义外部依赖。 对于Java工程,我们使用rules_jvm_external进行外部依赖的管理。它的使用步骤如下: 步骤1:在WORKSPACE中增加rules_jvm_external配置 以下配置指定了rules_jvm_external的下载位置,并进行rule的初始化: load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") RULES_JVM_EXTERNAL_TAG = "4.5" RULES_JVM_EXTERNAL_SHA = "<sha hash value>" http_archive( name = "rules_jvm_external", strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, sha256 = RULES_JVM_EXTERNAL_SHA, url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, ) load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") rules_jvm_external_deps() load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") rules_jvm_external_setup() load("@rules_jvm_external//:defs.bzl", "maven_install") maven_install( artifacts = [ # The project's dependencies "junit:junit:4.12", "org.hamcrest:hamcrest-library:1.3", ], repositories = [ # Private repositories are supported through HTTP Basic auth # "http://username:password@localhost:8081/artifactory/my-repository", "https://maven.aliyun.com/repository/public", ], ) 以上采用了非Bzlmod的管理rule。 ...

2024-01-19 · 3 min · 461 words · 翟志军 Jack Zhai

DevOps架构师是如何看待Github Actions的共享制品解决方案的?

前言 Github Actions是Github提供的一个CICD Pipeline服务。除了Pipeline,它还提供Secret和简单的配置管理。 本文并不是它的一个完整介绍和知识的罗列。而是我在实际使用Github Actions后,对Github Actions的“共享问题”的解决方案的总结。 不要小看这个问题,它是所有的Pipeline平台(包括Gitlab CI)都会遇到的问题。只要对这一问题深入理解,所有的平台一通百通。 提示1:下文可能会是Workflows和Pipeline两个术语共用。因为它们本质上就是同一个东西,只是不同平台不同的叫法。 提示2:下文可能会共用DevOps平台和Pipeline平台,虽然它们可能是完全不同的平台,但是在本文中,它们都是能提供Pipeline的平台。 共享问题 只要是Pipeline平台,都会遇到共享问题。那么,什么是共享问题? 共享问题就是Pipeline中不同的位置之间共享资源,以实现不重复执行、生成准确结果的目标。 定义听起来有些枯燥。我们列举一个有共享的场景,就比较好理解了: 比如对于一个单仓库,它同时包含多个前端工程,这些前端工程同时依赖于一个common模板。其它前端工程只有等它构建完成,并取得构建,才能开始自己的构建。如果common模板的构建结果不能被其它工程构建共享使用,就会存在构建结果不一致、重复构建的问题; 比如一个Pipeline中,版本号是有特定格式的,需要在第一步骤计算出来后,其它步骤取得这个版本号,进行打包工作。如果没有实现版本号在多个步骤之间共享,很可能会导致版本号不一致问题。 我们稍微对共享问题进行抽象和理解,根据共享的范围,共享问题,可以分为: Workflows之间进行共享; Workflow内的Jobs之间进行共享; Job内的Step之间进行共享。 根据共享的内容,可以分为: 共享源码; 共享制品; 共享变量。 Github Actionsr制品的定义 在Github Actions中的制品(Artifact)的概念和我们平时所说的“制品”有一定的区别。在Github Actions中,制品指的是Job生成的文件或者文件夹。 我们平时所说的,更广意的“制品”,在Github Actions叫Packages。 Workflows之间的共享制品 一般只有在大型项目才会存在Workflows之间的共享。而我个人是不建议将依赖Pipeline实现大型项目的构建的,而是依赖构建工具本身的能力。 由于笔者时间有限,不再亲身做实验,本节内容,请读者自行测试。 如果Workflow是由workflow_run事件触发的情况下,它们就可以直接使用actions/upload-artifact和actions/download-artifact两个actions来实现制品的共享。相关文档:https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts#downloading-artifacts-during-a-workflow-run 有趣的是Github Actions提供了一种reusable的Workflow概念。说到底是一种模板化Workflow的方式。但是这种方式不适合用来实现共享制品。因为它并不是共享制品。相关文档:https://docs.github.com/en/actions/using-workflows/reusing-workflows#using-outputs-from-a-reusable-workflow Jobs之间共享制品 在同一个Workflow中,多个Job进行制品共享。如下代码: jobs: // 生成制品的job build: runs-on: ubuntu-latest steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and export uses: docker/build-push-action@v5 with: context: . tags: myimage:latest outputs: type=docker,dest=/tmp/myimage.tar - name: Upload artifact uses: actions/upload-artifact@v3 with: name: myimage path: /tmp/myimage.tar // 使用制品的job use: runs-on: ubuntu-latest needs: build steps: - name: Download artifact uses: actions/download-artifact@v3 with: name: myimage path: /tmp - name: Load image run: | docker load --input /tmp/myimage.tar docker image ls -a 以上案例来自:https://docs.docker.com/build/ci/github-actions/share-image-jobs/ ...

2024-01-16 · 2 min · 290 words · 翟志军 Jack Zhai

Ebean:一款被低估的ORM框架

ORM框架为什么不香? 对ORM框架的偏见 看了一些MyBaties与Hibernate进行对比的文章。可能是因为一些Hibernate历史原因,国内对于Hibernate普遍存在偏见,我摘抄了几点: hibernate是全自动,而mybatis是半自动 hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。 sql直接优化上,mybatis要比hibernate方便很多 由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql 应用场景 MyBatis 适合需求多变的互联网项目,例如电商项目、金融类型、旅游类、售票类项目等。 Hibernate 适合需求明确、业务固定的项目,例如 OA 项目、ERP 项目和 CRM 项目等。 也不知道是不是因为这些对Hibernate的偏见,导致大家对ORM框架也普遍存在偏见。 现状是不论大小公司,国内清一色地使用MyBaties。有时,我都不敢说,我喜欢使用ORM框架。 本文并不是一篇为Hibernate洗地的文章,而是介绍另一款比较小众的ORM框架:Ebean。 领域问题分析 介绍Ebean之前,我们需要弄清楚一个问题:为什么会有MyBaties和ORM这些框架?对于这个问题,我们无从下手,那么,我们将问题倒置:如果没有这些框架,会怎么样? 问题倒置的好处是我们立马就有了可下手的方向。我们找到了不使用框架的情况下,Java代码与数据库进行交互的代码: public static void viewTable(Connection con) throws SQLException { String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement()) { ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } } 这样的代码存在什么问题呢? ...

2024-01-02 · 2 min · 336 words · 翟志军 Jack Zhai

PostgreSQL15 Public Schema没有权限问题解决

PostgreSQL15后,Public Schema的权限发生了变化:普通用户默认在Public schema中不再有CREATE的权限。当他们执行CREATE TABLE命令时,就会报以下错误: ERROR: permission denied for schema public 所以,我们需要为该用户再分配权限。命令如下: GRANT USAGE, CREATE on SCHEMA PUBLIC to <username>; 因为某些应用程序的sql的migration是自动的,你可能还需要为用户分配更多权限,命令如下: grant all on database <db_name> to <username>; ALTER DATABASE <db_name OWNER to <username>;

2024-01-01 · 1 min · 33 words · 翟志军 Jack Zhai

使用Mxroute和Sendgrid实现邮箱服务和邮件发送服务

最近在做一个产品,需要用邮箱服务和邮件发送服务。本文以Mxroute和Sendgrid为例介绍邮箱服务和邮件发送服务的配置。但是所有的这类产品,思路都应该是一致的。 Mxroute是邮箱服务,类似Web服务,只不过,它是专门为邮箱协议而设计的。Sendgrid就是邮件发送服务,也就是你需要批量向一堆邮箱发送邮件时,就需要用邮件发送服务。本质上Sendgrid与Mxroute是两回事。但是,通常我们先配置邮箱服务,再配置邮件发送服务。 本文只为记录一下,将来忘记了可以重新拾起。 配置邮箱服务 首先,邮箱服务需要MX类型的域名解析记录。这能让邮箱服务能在整个互联网被解析到。 每一个在Mxroute付费的用户,都会被分配到一个独立的MX域名,如first.mxrouting.net 。他们应该会发邮件给你,你需要留意。 在Mxroute上配置的步骤如下: 创建一个域名。比如example.com。如果你使用的是子域名,也可以是mail.example.com。 拿到DKIM Keys等信息。在Mxroute的左边菜单中可以找到链接 在域名提供商中,再配置以下这些DNS记录 记录类型 name content 优先级 MX _dmac v=DMARC1; p=none CNAME mail <mxroute分配的独立MX域名> MX mail <mxroute分配的独立MX域名> 10 MX mail <mxroute分配的独立MX域名> 20 TXT mail <从mxroute上获取> TXT x._domainkey <从mxroute上获取> 如果你使用的是子域名,那么,还需要在 _dmas和x.domainkey 后加上 . 。例如mail子域名,就是 x.domainkey.mail。 通过以上配置,只证明我们的“邮箱服务器”已经配置好了。现在在上面创建账号,并进行测试了。如果你可以向这个账号收发邮件,就证明,你的邮箱服务已经配置完成。 配置邮件发送服务 当你有了一个邮箱账号后,你就可以Sendgrid上配置了。登录后,从左边菜单“Senders”进入列表页。 然后再点击按钮“Create new Sender”,即可创建。这部分就不细说了。因为太简单了。

2023-12-31 · 1 min · 49 words · 翟志军 Jack Zhai

使用Google OSV工具扫描依赖安全漏洞

安全漏洞是软件工程化能力的试金石 2021年年底,Log4j的漏洞陆续被公开。因为该框架被大量的开源软件依赖,所以,漏洞影响面非常大。 面对这个漏洞,我们遇到的第一个问题是:如何知道我们哪些工程使用了Log4j? 在我看来,这个漏洞是企业软件工程化的一颗非常好的试金石。因为: 如何第一时间了解到这个漏洞,反应这家企业的安全能力; 如何第一时间能找到所有使用了Log4j的位置,体现了这家企业第三方软件依赖管理能力; 替换Log4j的速度,体现企业的持续集成、持续部署的能力。 Google的开源软件安全漏洞扫描工具 今天介绍的OSC-Scanner,能加强我们第1项和第2项能力。 OSV-Scanner是Google在2022年12月13日推出的一款免费的安全扫描工具。它具有以下特点: 支持多生态系统,包括:Go、PyPI、RubyGens、Linux、Maven等16个生态系统; 同时支持直接依赖的扫描和间接依赖的扫描; 采用标准的漏洞记录格式; 从当前最大的开源软件漏洞数据库(https://osv.dev/)获取信息。这也是DenpencyTrack和Flutter安全工具的漏洞数据库。 OSV-Scanner是一款命令行工具,我们可以将它集成到我们的构建工具或者CICD Pipeline中。目前它已经被集成到Scorecard中。Scorecard是一款为开发源软件的安全健康度打分的开源软件。我们可以在Github Actions中使用它:https://github.com/ossf/scorecard/tree/main?tab=readme-ov-file#scorecard-github-action OSV-Scanner的安装 Windows: scoop install osv-scanner Mac Homebrew: brew install osv-scanner 也可以直接下载二进制包:https://github.com/google/osv-scanner/releases 具体安装文档:https://google.github.io/osv-scanner/installation/ OSV-Scanner的使用 Keras是一个使用Python编写的开源人工神经网络库。我们以它为例。命令行里运行以下命令: ./osv-scanner_1.3.6_linux_amd64 --format json keras/ 输出内容说明:keras存在一个“潜在内存泄漏”的漏洞。 当拿到json结果后,我们的DevOps平台就可以进行一些告警监控的操作。 后记 osv-scanner目前需要连osv.dev,才能使用。但是,已经开放实验功能,允许用户离线使用osv-scanner。这是自建DevOps平台的福音!

2023-12-25 · 1 min · 39 words · 翟志军 Jack Zhai

不出事故,没有人知道你重要

有同学在知乎上提问:“线上无事故,运维还重要吗?”,描述如下: 本人运维行业,本部门在近几年一直保持效率增长且极少出现重大saas生产事故,并且为其他部门输出提升方法以及友好协同提升,但是最近从各层面接到反馈说对运维的投入减少,着实想不通,线上出了事故要运维背锅,产品出了bug要运维陪着到最晚,为什么把线上环境搞得稳定了,却不重视运维岗了? 这是原贴:https://www.zhihu.com/question/497361582 以上提问的是一个运维的同学。言下之义是不出事故,没有人知道运维重要。 这位同学的的感受,过去几年,我感同深受。我相信因为这个标题而点进这篇博客的同学,也有同样的感受。 但是,为什么出事故后,是运维重要呢?而不是测试、开发或者手机端开发呢? 通常是因为运维这个角色: 线上环境,他们最清楚,通常也只有他们有权限操作线上环境,可以紧急加一个数据库索引; 他们掌握了部署能力,可以发起回滚操作; 有权限查看各个组件的情况,并诊断根因; 为团队准备基础设施能力,如金丝雀发布能力; 搭建告警监控系统、CMDB、DevOps平台等。 等等 但是,这些与是否出事故,有多大的关联性呢?我们应该统计各种事故的根因的类型的比例,才有答案。 就目前而言,我们并不能说因为我们看重运维,就不出事故。 以上的问题是从个人感受出发的提问。只是更深层次问题的表象。 从企业层面上,我的疑问是:为什么在企业里,稳定性建设通常都是一阵阵的。即出一次事故,就立个项,就加班加点去完成“稳定性”项目。 比起讨论个人感受,从企业层面讨论这个问题,似乎更有趣。 其实,除了稳定性,软件的质量建设也是一阵阵的。想想,不是吗?不出Bug,没有人知道测试重要。 也许这是所有企业的正常表现。就像人的身体,痛风(一种慢性病)不发作时,你是不会感受它的存在,也自然就不会想到要去治疗或者预防它。然而,如果平时不注意饮食和锻炼,痛风经常复发。 线上事故就如同企业的痛风。企业应对“痛风”,容易好了伤疤忘了痛。 虽说可能是所有企业的正常表现,但不是一种健康的表现。 预防痛风,只能通过健康的生活方式如: 限制或避免饮酒,尤其是啤酒。 限制或者避免饮用含糖饮料,尤其是含高果糖玉米糖浆的饮料。 限制肉类摄入量,尤其是红肉、内脏和海鲜。 保持健康的体重。如果您需要减肥,请避免断食或过快地减肥,因为这可能会暂时增加尿酸水平。 增加水和低脂乳制品的摄入量。这些有预防痛风的作用。 一个人应对痛风的健康表现应该是采用健康的生活方式。 说回企业的稳定性建设,也是一样的道理。 稳定性不是通过“一阵阵的运动”或者“一阵阵的表演”来建设的,而是通过平时健康的企业活动来实现(我无意指导别人的企业,这只是我个人的思考)。 当然,现实中,对于有些人,要维持健康的生活方式是一件很难的事情(想想有身边有多少人做到早睡早起),而另一些人是一件很自然的事。为什么呢? 相同的,一家企业为什么无法自然地做到健康的企业活动?一定要出事故,才知道X的重要性呢?(X代表任何东西) 这个问题就很大了。希望对各位读者有启发。

2023-11-21 · 1 min · 34 words · 翟志军 Jack Zhai

Bazel作为构建工具之王,将会颠覆你对CI的认知

说到构建工具,不同语言技术栈的人,想起的构建工具不同。 Java程序员想到的是Maven,前端程序员想的是NPM或者Webpack、Android程序员想到的是Gradle、Rust程序想到的是Cargo、C++程序员想到的是Make等等。 然而这些工具在Bazel面前,层次有些低。所以,我愿称Bazel是构建工具之王。 P.S. Android平台的构建,2020年已经开始了迁移到Bazel的工作。 具体地址:https://blog.bazel.build/2020/11/12/aosp_migrating_to_bazel.html Bazel介绍 Bazel是Google在2015年开源的一款构建工具。 目前使用Bazel的知名公司有:Esty、Canva、Databricks、Dropbox、Huawei、Line、LinkedIn、Stripe、Twitter、Tinder、Uber、VMware、Wix等。具体可以看:https://bazel.build/community/users 。 其中Twitter是从自家的Pants迁移到的Bazel的,具体迁移过程介绍:https://opensourcelive.withgoogle.com/events/bazelcon2020/watch?talk=day1-talk2 Facebook使用的是其自研的Buck2,但是,其与Bazel使用的是相同的远程执行的API。 除了公司,某些著名的开源软件也使用Bazel构建,包括自动化测试领域的Selenium,AI领域的TensorFlow,容器编排领域的Kubernetes等。具体还有:https://bazel.build/community/users#open-source-projects-using-Bazel 相对于其它构建工具,它的显著的特点有: 支持多语言; 支持远程分布式构建; 支持增量构建; 支持强大的密闭性; 支持构建缓存; 支持并行构建。 假设存在一个复杂的软件工程 假设存在一个软件工程中,它包含5部分:Web前端、Android端、Java后端、Go后端、嵌入式端。 作为Java后端的程序员,他们修改了一个API。但是他作为个人,他无法预知到底发生了哪些影响。 所以,他把这个问题交给了持续集成(CI),让它去发现集成问题。 在过去很长一段时间里,行业里只有一种CI模式,我称之为传统的CI模式。 殊不知,还有另一种模式。 传统的CI模式 目前行业里比较传统的CI架构,通常如下: 在这样的架构下,实现CI的步骤如下: 开发人员提交代码; Gitlab检测到开发人员提交代码,然后触发Jenkins controller执行; Jenkins controller根据该代码仓库预先设计的pipeline执行; Jenkins controller根据pipeline中的任务所需要的构建环境,将任务分配给不同的Jenkins agent; 在agent构建完成后,将制品release到制品仓库中。 如果开发者希望验证自己写的代码,就必须将代码commit到Gitlab中。因为整个验证环境被定义在CI环境的Pipeline中。而且这个过程,越大的工程,集成速度越慢。开发者也无法在本地进行全量验证。 作为Pipeline的维护者,他需要清楚知道哪些任务是可以并行执行的,并手工配置并行,这样才能加快构建速度。比如前端构建和后端构建可以并行进行。 也就是说在传统的CI模式下,开发者的效率会随着软件的规模越大而降低。换句话,这样的模式,开发效率无法scale。 案例 希望以下案例可以给你一个感性的认知。下图是Google在2010年到2015年的周commit数量。绿线代表commit总数,黄线是人数。我们取离我们最近的2015年的数据来讨论。2015年的代码量如下: 在这个代码量下,每周能达到300左右的commit。如下图: 根据持续集成的原则,每一个commit都必须构建通过。20亿行代码一次全量构建需要多久? 我们以一个开源项目作参考。apitable是一个开源的数据表格项目,它有200万左右的代码,全量构建一次需要20分钟左右。那么,根据不准确的类推,20亿行代码,全量构建一次需要:20/2,000,000 * 2000,000,000=200,000,000分钟,也就是13天左右。 在传统的CI模式下,是尽量避免执行全量构建这样庞大的代码量的。所以,传统CI模式下,通常是多仓库模式管理代码。 那么Bazel呢?Bazel如果真要构建这样庞大的代码量,估计也够呛。但是由于Bazel天然支持并行构建、构建缓存和增量构建,所以,Bazel通常不会遇到真正意义的全量构建的情况。 为什么其它公司不使用Bazel 也许有人会问:为什么阿里2018年新增的代码行(https://zhuanlan.zhihu.com/p/54435171)就有12亿,不也没有使用Bazel吗? 这个是一个好问题。 但是,无法简单的回答这个问题,而是需要深入到各自组织内部才能分析清楚。个人觉得可以从以下维度分析: 在代码仓库上工作的人员的规模:同样的代码量,不同的组织需要不同数量的人维护; 代码管理方式:阿里使用多仓库的管理办法,不需要统一的版本号; 持续集成的程度不同:阿里可能不需要对每一个commit跑一次全量。 为什么Bazel会颠覆你对CI的认知 Bazel是如何解决传统CI模式下开发效率无法scale的问题呢?其主要通过它的六个特性来解决。 首先,Bazel支持远程分布式构建。 在一个使用Bazel构建的仓库中,开发者写好代码后,不用commit代码到Git仓库,只要在本地命令行执行bazel run --remote_executor=grpc://localhost:8980 //... ,代码仓库中所有构建和测试任务都将运行在远程执行服务器。远程执行服务器越多,构建速度越快。 这一特性可以明显地提高开发者本地的开发效率。因为开发者在本地就可以执行全量构建和全量测试。 传统CI模式下,无法提升开发者本地的开发效率。 第二,Bazel支持增量构建和增量测试(精准测试)。 开发者在本地执行build命令时,Bazel检测出修改了a.java文件,所以,Bazel只将构建a.java的任务及其相关的构建任务给远程执行服务器执行。这就是增量构建。 如果开发者执行test命令,Bazel则能检测出被影响的测试,然后只运行这些测试。其实这就是精准测试了。在Bazel中,精准测试实现起来并不难。 传统CI模式下,它是不关心增量构建和增量测试的。所以,每次运行都是全量。这是一种极大的浪费。 ...

2023-11-20 · 1 min · 108 words · 翟志军 Jack Zhai

听说最近的事故都是循环依赖导致的?

​年底了,事故频发。但是都听说是因为循环依赖导致。所以,我决定来写写依赖管理领域中,通常不被重视的循环依赖问题。 循环依赖(circular dependencies)的定义 来自维基百科的定义: 在软件工程中,循环依赖是两个或多个模块之间的关系,这些模块直接或间接地相互依赖才能正常运行。此类模块也称为相互递归。 循环依赖是依赖管理领域中经常出现的一种现象,如下图: 循环依赖的不同层次以及后果 循环依赖可以发生在两个层次: 源码之间相互引用依赖; 服务之间相互调用依赖。 源码之间产生循环依赖带来的问题是:构建工具不知道应该从何处开始构建。因为构建工具无从下手。 构建工具底线这时就体现出来了,如果它可以忽略其中一个节点,“勉强”构建出一个制品,那么这个制品估计你也不敢用,因为该制品存在不确定性。这是源码层次中,循环依赖的后果。 而服务之间调用的循环依赖就更麻烦了。平时非常难发现,是不会出事故的,但一出事故就会雪崩。 因为你无法单独启动循环依赖中的任何一个服务,而循环依赖中的任何一个服务挂了,其它所有的节点都会同时挂。 循环依赖的环越大,影响面越大。 为什么会出现循环依赖 既然循环依赖从任何一个节点都法进行构建或者启动,那么它又为什么会产生?难道定义依赖的人不知道吗?Code Review的人不知道吗? 因为循环依赖是软件系统在长时间发展中,不加以合理的依赖管理所导致的。如下图。一开始只是B的V1版本依赖A的V1版本,最后变成B的V2版本与A的V3版本相互依赖。 软件系统发展的时间足够长,交接的人数足够多,软件依赖关系的规模早就超出了人类能处理的限度。 如何从根上就避免循环依赖 用人力的办法解决循环依赖,是不现实的。依赖管理这种吃力又不讨好,还拿不上台面的术语,没有人会去做。更不会得到KPI的“赏识”。 可以预料得到,如果对依赖管理治理不当,那么每几年,就可能出现一次循环依赖的事故。不发生事故的原因可能只有一个:软件的规模还不够大。 所以,我们必须想办法从根上避免循环依赖,即从依赖的定义的地方开始治理。 源代码层次,构建工具就可以帮我们依赖。只要出现循环依赖,构建就不通过。例如Make工具: make: Circular main.asm.o <- main.asm dependency dropped. 但是,如果是多仓库管理源代码的模式下,你可能还是很难避免循环依赖的情况。如果是单仓库,就不会出现这样的情况。 而服务之间的调用关系,在不同的公司定义的位置不一样。这也决定了查找循环依赖的成本。 服务之间的调用关系的定义,本质上属于配置管理的范畴。因为你总要在某个地方定义这些依赖。 服务之间的循环依赖查找,本质上就是配置管理领域中,查找配置之间的相互引用关系。 这说明配置管理的方式决定了查找服务之间循环依赖的成本。 什么样的配置管理方式才能低成本的实现查找循环依赖呢? 在这里,我提出我的方案:使用Jsonnet定义所有的配置(某些case无法覆盖),然后通过Bazel进行构建。 Jsonnet是一门专为配置定义而设计语言,语言只有一页A4纸; Bazel是一款支持增量构建、分布构建、构建缓存、支持多语言的构建工具。 通过这个方案,Bazel会自动构建一个软件依赖关系图,同时检测其中是否存在循环关系。只要循环关系存在,构建就不通过,当然就无法上线了。这样就从根上就避免了循环依赖。 如果想了解更多具体的落地方式,请关注我。并转发本文,让更多人看到这种神奇的配置管理方式。

2023-11-11 · 1 min · 43 words · 翟志军 Jack Zhai

反思一次效能提升

前天与一个大佬交流。想起自己在6年多前在团队里做的一次小小的效能提升。 改进前 在同一个产品团队,同时有前端工程师和后端工程师。他们经常需要共同协作完成features。 前端是一个传统的多页应用。前端渲染是由后端的velocity模板引擎实现的。 打包后,最终执行就是一个jar包: vm文件后缀名是velocity模板文件。它们内容大概是这样的: <html> <body> Hello $customer.Name! <table> #foreach( $mud in $mudsOnSpecial ) #if ( $customer.hasPurchased($mud) ) <tr> <td> $flogger.getPromo( $mud ) </td> </tr> #end #end </table> </body> </html> 其中一些非html代码就是Velocity Template Language。 默认情况下,一个前端工程师是不懂这门模板语言的。而且,这种模板语言对浏览器不友好,不像Thymeleaf。同时,按前端的开发习惯,他们是不可能先启动你一个Java进程后,再对前端页面进行调试。 总的来说,这样的技术栈是不利于前端本地开发的。 所以,在改进前,前后端的协作的方式是: 前后端同时进行开发; 后端负责在Java工程中实现后端的逻辑; 前端负责在另一个独立的前端工程中开发HTML和CSS; 前端完成页面的开发后,他会把前端页面html文件名和页面的逻辑告诉后端; 后端再把html页面里内容与Java工程中的vm进行对比,然后将不同的部分转换到Java工程的vm模板页面中。 这样的协作方式下,经常出现以下问题: 在将html转换到vm模板的过程中,后端有时会转换漏内容; 转换后,前端调试前端问题会很麻烦。前端需要占用一个后端和他一起调试。因为前端不懂如何启动复杂的后端工程; 命名上经常出现不一致,进而导致前端bug。对于同一个概念的情况下,前端命名叫item,后端叫project; 效能提升措施与效果 经分析,以上问题均由“技术之间的转换”导致,即人工的将html页面从一个前端工程转换到一个后端工程的vm模板语言。 所以,解决以上问题的思路就是:最好能消除html和vm的转换,又或者能减少这个转换过程的失误。 思路有了以后,解决方案有以下几个: 更换前端技术栈,不再使用Java+vm模板; 让前端学习vm模板语言,转换过程由前端完成; 优化后端开发在本地启动的流程,方便前端也能在本地启动; 减小测试环境的部署难度。让任何人都可以部署,方便前端进行调试。 方案1短时间无法做到,而且改动巨大,收益也未知。所以,最终是同时做了2、3、4。 虽然当时没有进行度量,但实际效果就是前文提到的所有问题都得到了减轻。 原因就是方案2、3、4,减少了前端需要向后端传递的信息。很多事情,前端一个人就可以解决了。 关于方案2、3,可能有人会好奇:前端去学习一门新的模板语言,成本大吗?前端在本地启动一个后端的工程去调试前端代码,成本高吗? 首先,vm模板并不是一门难的语言,只要有类C语言的基础(比如Java、JavaScript、C#等),都不难学。无非就是if-else判断、变量定义、循环等。 其次,在后端优化本地开发环境后,前端启动一个Java工程并不是一件难事。 反思 整个事件下来,本质上是前端本地开发习惯与现有技术栈的冲突。这次效能的提升需要前端开发做一些工作习惯上妥协。 而这只是表面上的问题,我们需要去思考更深层的问题: ...

2023-10-23 · 1 min · 68 words · 翟志军 Jack Zhai