夜天之書 #110 涓滴開源:Cronexpr 的故事

在年初的一篇關于商業開源的博文當中,我介紹了在開發商業軟件的過程中,衍生出開源公共軟件庫的模式。在那篇博文里面,我只是簡單羅列了相關開源庫的名字及一句話總結。近期,我會結合商業開源實踐的最新進展,對其中一些案例做詳細展開。

首先介紹的是?Cronexpr[1],一個小巧的 Rust Crontab 解析庫。其背后的開源模式,我稱之為“涓滴開源”,即將商業軟件依賴的微小但完整的功能模塊開源出來,供其他人使用。

ScopeDB 的實際需求

開源軟件的源頭活水是有人需要

“只是為了好玩”(Just For Fun)固然可以成為某人一時的動力,但是開源項目能夠長期持續維護,肯定是因為有用戶長期使用,倒推上游保持更新。作為互動的另一環,如果上游不再更新,用戶也會逐漸流失。

Cronexpr 的需求來源于我和幾位伙伴從去年開始開發的?ScopeDB 云數據庫[2]。ScopeDB 是一個商業數據庫,構建在云計算彈性且廉價的資源之上,通過一致的接口體驗和查詢語言將原本疊床架屋的數據流水線,簡化為從應用直接寫入 ScopeDB 后即可執行任意查詢。在實現 ScopeDB 的過程中,我們發現類似數據保留策略的執行、簡單的物化視圖構建,以及數據存儲的整理,都可以用定時后臺作業的模型來建模:

CREATE?JOB archive_table_tSCHEDULE =?'4 2 * * * Asia/Shanghai'NODEGROUP =?'background'
ASDELETE?FROM?tWHERE?created_at <?NOW() -?INTERVAL?'720 hours';

可以看到,這里在后臺作業的調度周期的時候,用的是形如?4 2 * * * Asia/Shanghai?的 Cron 表達式。這也是定時后臺作業常用的調度邏輯定義方案了。

于是,為了支持解析 Cron 表達式,并在 ScopeDB 調度后臺作業的邏輯當中嵌入 Cron 表達式的計算邏輯,我第一時間想到的就是尋找一個現成的開源庫來解決需求。

Cronexpr 的誕生

很快,我就找到了 Rust 生態里兩個看起來不錯的 Cron 表達式軟件庫:

  • croner[3]

  • saffron[4]

其中 croner 看起來是獨立開發者發布的軟件庫,不過當時已經發布了 2.x 版本,今天再看已經發布了 3.x 版本,看起來作者是對軟件的成熟度比較有信心的。saffron 雖然只發了一個 0.1.0 版本,但是它是 Cloudflare 出品,且看起來接口也比較正經,應該也相對成熟。

不過,這兩者都不支持帶時區信息的 Cron 表達式,這對用戶體驗來說有比較大的差別。同時,這兩者對 Cron 表達式解析時的一些實現細節和擴展,都有奇特的“村規”。最后,由于這兩個庫實現時間較早,它們所采用的時間庫是舊的 chrono 庫,而不是現在更可靠的 jiff 庫。至于其他 Cron 表達式相關的開源庫,要么是一看就不靠譜,要么是完全實現成 Unix 下的 Cron 程序,自帶不能去掉的命令執行功能,然而 ScopeDB 只需要能解析 Cron 表達式即可。

因此,考慮到解析 Cron 表達式并不復雜,且即使使用 croner 或 saffron 也需要對其進行一些修改以滿足 ScopeDB 的需求,我決定自己實現一個 Cron 表達式解析庫。

Cronexpr 由此誕生。它的原型接口非常簡單:

let?crontab = cronexpr::parse_crontab("2 4 * * * Asia/Shanghai").unwrap();// case 0. match timestamp
assert!(crontab.matches("2024-09-24T04:02:00+08:00").unwrap());
assert!(!crontab.matches("2024-09-24T04:01:00+08:00").unwrap());// case 1. find next timestamp with timezone
assert_eq!(crontab.find_next("2024-09-24T10:06:52+08:00").unwrap().to_string(),"2024-09-25T04:02:00+08:00[Asia/Shanghai]"
);

實現的細節就不展開了,這里討論一下對依賴的選用。

Cronexpr 的依賴只有兩個,一個是上面提到的 jiff 庫,用來處理時間戳相關的邏輯,另一個是 winnow 庫,用來處理 Cron 表達式的解析。

選用 jiff 的道理非常簡單,它是目前最可靠的 Rust 日期時間庫,對時區、夏令時、各種日期計算都有很好的支持。歷法的制定實際上是一種話語權的爭奪,歷法與日期變更的計算充滿了人類世界的不靠譜特質,能把里面各種爛坑填好的庫不可多得。jiff 庫的作者有長期良好的聲譽,他是 Golang 生態當中 toml 庫的作者,也是 Rust Team 的官方成員,是 Rust Regex 庫的作者,也還創作過 ripgrep 和 csv 等高質量開源庫。

選用 winnow 的理由就相對隨機。其實 Rust 生態里比較有名的 Parser Combinator 是 nom 庫。但是當時 ScopeDB 已經用過 nom 庫來解析 ScopeQL 了,我感覺使用的過程中有一些不爽的點。正好當時 nom 一年多沒發新版本了,我想著 winnow 號稱是它的積極維護的分支,維護者是 Rust Team 的活躍成員,或許可以試試。實際情況是也沒有好到哪里去,而且我寫的時候不是完全按組合子的味道寫的,最終結果有點半手寫半組合子的風格,可能還不如純手寫。不過好在 Cron 表達式的結構非常固定,而且可見的迭代需求不多,大致知道怎么回事,代碼一直能看懂就差不多了。客觀來說,最初采用 winnow 還是節省了不少 while-if-match 式的樣板代碼。

文檔與測試

由于 Cronexpr 的邏輯相對簡單,經歷過大約兩周的迭代后,所有接口和實現基本就已經穩定了。這期間主要修正接口和實現的反饋,來自于 ScopeDB 的實用情況,以及參考現有軟件庫的接口設計方式。例如,我就是在看到 croner 和 saffron 的接口設計后,才想到可以做一個?iter_after?的接口。

同時,由于 Cron 表達式有參考實現,而且在開發的過程中,我遇到了很多具體“村規”的理解和處理,疊加上當時受到 jiff 庫詳實文檔的啟發,我把 Cronexpr 開發過程當中所有的設計理念跟概念都用文檔的形式記錄了下來。后來發布到 Hacker News 上的時候,也有讀者回應稱從文檔里了解到許多此前不知道的 Cron 表達式的細節。

詳實的 Cronexpr 文檔

除此以外,實現一個成熟的功能,也很容易找到前人寫過的測試集。Cronexpr 最適合的測試模式,當然是所謂的快照測試(Snapshot Testing),即把解析和匹配 Cron 表達式的返回值作為快照記錄下來,在迭代過程中保證這些返回值總是一致。因為返回值的文本,尤其是報錯時的文本經常相對冗長,所以用手寫?assert_eq!?的方式可能會難以維護。我用?insta[5]?工具維護了近一百個測試結果的快照。

基于 insta 的快照測試

順帶一提,ScopeDB 也重度使用 insta 做快照測試,包括 ScopeQL 的 Parsing 測試,以及端到端的類 sqllogictest 的測試。實際效果跟 sqllogictest 基本一樣,而且能夠跟 Rust 原生的功能做更緊密且可定制化的集成,不用依賴一個新的 DSL 跟每次需要新功能就要對 DSL 做擴展開發。

實際效果與現狀

ScopeDB 從第一個測試版本開始就支持?CREATE JOB?功能。

Cronexpr 首先被用在?CREATE JOB?的執行上,即在創建后臺作業時先校驗 Cron 表達式是否是合法的。隨后,ScopeDB 的 server 進程本身會啟動一個線程,實時監督哪些 JOB 已經到了需要再次調度的時刻,并觸發 JOB 執行。雖然涉及的代碼行數不多,但卻是 ScopeDB 能夠高效運維的核心能力之一。

目前,Cronexpr 已經發布了 1.0 版本,除了偶爾跟進一下依賴庫的版本,平常基本沒有什么需要再做開發的地方。我能想到的迭代需求,可能還有以下幾個:

  1. 有開發者提出過給 Cronexpr 做一套 Fuzz 測試。我覺得沒啥必要,因為實際的 case 非常有限,目前基本都枚舉完了。但是如果有人做了一個不錯的 Fuzz 方案,可以拿 Cronexpr 做實驗。

  2. 把 winnow 的依賴去掉。這樣,繼我在寫作本文之前把 thiserror 依賴去掉以后,Cronexpr 就可以僅依賴不可能去掉的 jiff 庫,盡可能的減少不必要的依賴。如前所述,winnow 在 Cron 表達式這樣一個語法非常固定的場景里作用有限。

  3. 有人可能想要支持可選的秒級、年份級 Cron 表達式。不過這些都是比較小眾的需求,我在自己有實際需求之前就不實現了。

涓滴開源與開源的可持續性

在 ScopeDB 的開發過程中,不只有 Cronexpr 一個開源庫誕生。上面提到的 server 進程本身會啟動一系列后臺線程,這些后臺線程的調度就是由?Fastimer[6]?庫支持的。此外,ScopeDB 使用的?Fastrace[7]?+?Logforth[8]?方案,實際上是當前 Rust 生態非常先進且可靠的一套日志追蹤方案。這些案例或許我在后續文章中還會展開。

要說跟 Cronexpr 相似的,應當是?StackSafe[9]?這個用于避免遞歸調用和遞歸數據結構棧溢出的小公共庫。庫作者 Andy 老師還寫了一篇博文[10]介紹其設計理念跟使用方式。

其他還有一些體量不大的軟件庫,比如 Mea 和 Fastpool 等等。但是它們設計到我很想吐槽的 Async Rust 生態,所以可能會單獨寫幾篇文章討論它們的情況。

回到開源項目的可持續性上來,這些項目的源頭活水,首先就是被 ScopeDB 商業產品所需求。在開源以后,例如 Cronexpr、Fastrace 和 Logforth 等軟件庫,也逐漸有了第三方的下游依賴。這些新的用戶為我們開源的項目提供了寶貴的反饋,有些幫我們提前解決了將要遇到的問題,有些幫助我們更好的設計接口。用戶反饋本身也是對工程師編寫軟件的正向反饋:看到自己寫作的軟件能夠幫到更多的人,被更多的人認可,是一件非常開心的事情。

因此,涓滴開源是一種可持續的開源模式。開源開發者通過商業軟件已經實現了經濟可持續,將開發商業軟件過程中,微小但完整的功能模塊,且其本身不產生商業價值,反而最好開源尋求同行評審,以公共庫的形式開源發布出來,這是商業開源生產軟件的其中一種形式。

這個世界上這樣產生的開源軟件有很多,包括前文提到的 Cloudflare 開源的 saffron 也屬于此類。只要公司不做禁止,這類軟件就可自由生長;而如果企業能夠稍加引導,這些軟件就能成為技術品牌影響力的一部分,其價值將在長時間跨度上不斷為企業帶來正面的回報。

參考資料

[1]?

Cronexpr:?https://crates.io/crates/cronexpr

[2]?

ScopeDB 云數據庫:?https://www.scopedb.io/

[3]?

croner:?https://docs.rs/croner/latest/croner/

[4]?

saffron:?https://docs.rs/saffron/latest/saffron/

[5]?

insta:?https://insta.rs/

[6]?

Fastimer:?https://github.com/fast/fastimer

[7]?

Fastrace:?https://github.com/fast/fastrace

[8]?

Logforth:?https://github.com/fast/logforth

[9]?

StackSafe:?https://github.com/fast/stacksafe

[10]?

博文:?https://fast.github.io/blog/stacksafe-taming-recursion-in-rust-without-stack-overflow/

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92080.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92080.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92080.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

完整的登陸學生管理系統(配置數據庫)

目錄 要求 思路 1. 登錄模塊&#xff08;LoginFrame.java&#xff09; 2. 學生信息管理模塊&#xff08;StudentFrame.java&#xff09; 3. 數據層&#xff08;StudentDAO.java&#xff09; 4. 業務層&#xff08;StudentService.java / UserService.java&#xff09; 5…

譯 | 在 Python 中從頭開始構建 Qwen-3 MoE

文章出自&#xff1a;基于 2個Expert 的 MoE 架構分步指南 本篇適合 MoE 架構初學者。文章亮點在于詳細拆解 Qwen 3 MoE 架構&#xff0c;并用簡單代碼從零實現 MoE 路由器、RMSNorm 等核心組件&#xff0c;便于理解內部原理。 該方法適用于需部署高性能、高效率大模型&#x…

Spring Boot + ShardingSphere 分庫分表實戰

&#x1f680;Spring Boot ShardingSphere 實戰&#xff1a;分庫分表&#xff0c;性能暴增的終極指南&#xff01; ? 適用場景&#xff1a;千萬級大表、高并發、讀寫分離場景 ? 核心框架&#xff1a;Spring Boot 3.x ShardingSphere-JDBC 5.4.1 ? 數據庫&#xff1a;MySQL…

MaxKB 使用 MCP 連接 Oracle (免安裝 cx_Oracle 和 Oracle Instant Client)

一、背景 安裝cx_Oracle包和Oracle Instant Client來操作數據庫&#xff0c;比較繁瑣同時容易沖突&#xff0c;不同的 Oracle 版本都需要安裝不同的插件。這篇文章將介紹使用 MCP 協議的連接方法。 二、操作步驟 1、使用 1Panel 安裝 DBhub a) 數據庫類型選擇 Oracle 類型。…

基于Python的超聲波OFDM數字通信鏈路設計與實現

基于Python的超聲波OFDM數字通信鏈路設計與實現 摘要 本文詳細介紹了使用Python實現的超聲波OFDM(正交頻分復用)數字通信鏈路系統。該系統能夠在標準音響設備上運行&#xff0c;利用高于15kHz的超聲波頻段進行數據傳輸&#xff0c;采用48kHz采樣率。文章涵蓋了從OFDM基本原理、…

滑動窗口相關題目

近些年來&#xff0c;我國防沙治沙取得顯著成果。某沙漠新種植N棵胡楊&#xff08;編號1-N&#xff09;&#xff0c;排成一排。一個月后&#xff0c;有M棵胡楊未能成活。現可補種胡楊K棵&#xff0c;請問如何補種&#xff08;只能補種&#xff0c;不能新種&#xff09;&#xf…

Java 工具類的“活化石”:Apache Commons 核心用法、性能陷阱與現代替代方案

在上一篇文章中&#xff0c;我們回顧了 Apache Commons 的經典組件。但作為 Java 世界中資歷最老、影響最深遠的工具庫&#xff0c;它的價值遠不止于此。許多開發者可能只使用了它 10% 的功能&#xff0c;卻忽略了另外 80% 能極大提升代碼質量的“隱藏寶石”。本文將提供一個更…

數據結構——圖及其C++實現 多源最短路徑 FloydWarshall算法

目錄 一、前言 二、算法思想 三、代碼實現 四、測試 五、源碼 一、前言 前兩篇學習的Dijkstra算法和Bellman-Ford算法都是用來求解圖的單源最短路徑&#xff0c;即從圖中指定的一個源點出發到圖中其他任意頂點的最短路徑。Dijkstra算法不能求解帶有負權重的圖的最短路徑&…

解決微軟應用商店 (Microsoft store) 打不開,無網絡連接的問題!

很多小伙伴都會遇見微軟應用商店 (Microsoft store)打開后出現無網絡的問題&#xff0c;一般出現這種問題基本都是因為你的電腦安裝了某些銀行的網銀工具&#xff0c;因為網銀工具為了安全會關閉Internet 選項中的最新版本的TLS協議&#xff0c;而微軟商店又需要最新的TLS協議才…

Android—服務+通知=>前臺服務

文章目錄1、Android服務1.1、定義1.2、基本用法1.2.1、定義一個服務1.2.2、服務注冊1.2.3、啟動和停止服務1.2.4、活動和服務進行通信1.3、帶綁定的服務示例1.3.1、定義服務類1.3.2、客戶端&#xff08;Activity&#xff09;綁定與交互?1.3.3、AndroidManifest.xml 注冊?1.3.…

從基礎功能到自主決策, Agent 開發進階路怎么走

Agent 開發進階路線大綱基礎功能實現核心模塊構建環境感知&#xff1a;傳感器數據處理&#xff08;視覺、語音、文本等輸入&#xff09;基礎動作控制&#xff1a;API調用、硬件驅動、簡單反饋機制狀態管理&#xff1a;有限狀態機&#xff08;FSM&#xff09;或行為樹&#xff0…

《動手學深度學習》讀書筆記—9.6編碼器-解碼器架構

本文記錄了自己在閱讀《動手學深度學習》時的一些思考&#xff0c;僅用來作為作者本人的學習筆記&#xff0c;不存在商業用途。 正如我們在9.5機器翻譯中所討論的&#xff0c;機器翻譯是序列轉換模型的一個核心問題&#xff0c;其輸入和輸出都是長度可變的序列。為了處理這種類…

DocBench:面向大模型文檔閱讀系統的評估基準與數據集分析

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 一、數據集概述與核心目標 DocBench 是由研究團隊于2024年提出的首個…

Python高級排序技術:非原生可比對象的自定義排序策略詳解

引言&#xff1a;超越原生比較操作的排序挑戰在Python數據處理中&#xff0c;我們經常需要處理不原生支持比較操作的對象。根據2024年《Python開發者生態系統報告》&#xff0c;在大型項目中&#xff0c;開發者平均需處理28%的自定義對象排序需求&#xff0c;這些對象包括&…

低代碼系統的技術深度:超越“可視化操作”的架構與實現挑戰

在很多非開發者眼中&#xff0c;低代碼平臺似乎只是簡化流程、快速搭建頁面的工具。然而&#xff0c;在真實的企業級應用中&#xff0c;低代碼系統必須面對高并發請求、復雜業務規則、多角色權限、跨系統集成與持續演進等一系列工程挑戰。高效交付&#xff08;Rapid Delivery&a…

【NLP輿情分析】基于python微博輿情分析可視化系統(flask+pandas+echarts) 視頻教程 - 詞云圖-微博評論詞云圖實現

大家好&#xff0c;我是java1234_小鋒老師&#xff0c;最近寫了一套【NLP輿情分析】基于python微博輿情分析可視化系統(flaskpandasecharts)視頻教程&#xff0c;持續更新中&#xff0c;計劃月底更新完&#xff0c;感謝支持。今天講解詞云圖-微博評論詞云圖實現 視頻在線地址&…

Webpack核心技能:Webpack安裝配置與模塊化

一、webpack 的安裝和使用1. webpack 簡介webpack 是基于模塊化的打包 (構建)工具&#xff0c;它把一切視為模塊&#xff08;包括 JS、CSS、圖片等資源文件&#xff09;。工作原理&#xff1a;以開發時態的入口模塊為起點遞歸分析所有依賴關系經過壓縮、合并等處理最終生成運行…

數據結構---二級指針(應用場景)、內核鏈表、棧(系統棧、實現方式)、隊列(實現方式、應用)

一、二級指針的應用場景1、在被調函數中&#xff0c;想要修改主調函數中的指針變量&#xff0c;需要傳遞該指針變量的地址&#xff0c;形參用二級指針接收。2、指針數組的數組名是一個二級指針&#xff0c;指針數組的數組名作為參數傳遞時&#xff0c;可用二級指針接收。指針數…

NodeJs學習日志(1):windows安裝使用node.js 安裝express,suquelize,sqlite,nodemon

windows安裝使用node.js 安裝express&#xff0c;suquelize&#xff0c;sqlite 系統是win10&#xff0c;默認已經安裝好nodejs與npm包名作用expressWeb應用框架suquelize數據庫ORMsqlite數據庫nodemon代碼熱重載安裝express 添加express生成器 npm add express-generator4安裝e…

Cervantes:面向滲透測試人員和紅隊的開源協作平臺

Cervantes 是一個專為滲透測試人員和紅隊打造的開源協作平臺。它提供了一個集中式工作區&#xff0c;用于集中管理項目、客戶端、漏洞和報告。通過簡化數據組織和團隊協調&#xff0c;它有助于減少規劃和執行滲透測試所需的時間和復雜性。 作為 OWASP 旗下的開源解決方案&…