An update about Redis developments in 2019

Salvatore Sanfilippo

關於 Redis 在 2019 年開發進展的近況更新

原文由 Salvatore Sanfilippo 發布,訂閱此部落格

昨天,一位關心 Redis 的使用者在 Hacker News 上寫下了以下內容:

https://news.ycombinator.com/item?id=19204436

我很喜歡 Redis,但對於目前正在開發中的一些變更抱持著些許懷疑。respv3 協定有些功能雖然聽起來很酷,但也可能大幅增加客戶端函式庫程式碼的複雜度。另外也有很多心力投入在更細緻的 acl 上。我實在想不透為什麼這會是必要的,或為什麼它的優先順序會高於其他變更,像是多執行緒支援、更好的持久化模型、資料類型等等。

— 使用者留言結束 —

我感覺他/她(不太確定)並不是唯一一個把 ACL 視為某種因應 Redis Labs 目標而被強加的功能的人,理由是什麼「企業用戶」之類的。留言中的其他觀點也很有意思,我認為都很值得回應,才能清楚地向 Redis 社群說明未來的方向。

為了簡單起見,我會把這篇文章分成幾個小節,逐一回應原始留言中提到的每一項功能。

RESP3

如同我先前在這裡寫過的,RESP3 的目標其實是簡化客戶端生態。理想上,每個客戶端都會有一個底層,不再試圖重新發明某種高階介面:redis.call("get","foo")。不再需要費心處理各種轉換,因為現在協定本身就具備足夠的語意,能告訴客戶端某個回覆在呼叫端手上應該呈現成什麼樣子;對大多數指令而言,也不再需要事先知道指令的特徵。我想這位使用者所指的,應該是 RESP3 對帶外通訊(out of band communications)的支援,也就是回覆中的「屬性(attributes)」。

我真心相信,在 Redis 的未來裡「客戶端快取(client side caching)」將會是一件大事。這是每個可擴展系統邏輯上的下一步。然而,若沒有伺服器的協助,客戶端快取的失效處理將會是一場惡夢。這正是 RESP3 之所以在回覆中支援屬性(attributes)的主要原因。不過,Redis 6 *很可能根本不會實作其中的任何一項*。即將成為 Redis 6 的 Redis unstable 已經有了一個幾乎完整的 RESP3 實作,而且裡面並沒有屬性。實作 RESP3 的客戶端只要決定忽略屬性,就能真正做到面向未來,而即使在未來的 Redis 版本中,如果使用者沒有啟用某種特殊功能,屬性很可能根本就不會被傳送。舉例來說,要使用客戶端快取,連線就必須被切換到某種特殊模式。此外,如你所知,Redis 6 將會完全向下相容於 RESP2。事實上,我開始認為 RESP2 的支援可能永遠都不會被移除,因為它的成本幾乎是零,而且既然我們已經花了力氣在 RESP2 和 RESP3 之間實作了抽象層,就沒有什麼好理由去破壞向下相容性。

平常我不喜歡無緣無故去改動東西,不過 RESP2 的限制對客戶端生態造成了很大的影響。我希望使用者在不同客戶端之間切換時,都能有賓至如歸的感覺,而 API 就是 Redis 的 API 本身,而不是客戶端作者自己發明的那一層。話說回來,我並不反對在底層之上再提供一個*更高階*的 API,但在那之外,應該要有一個共同的基礎,而且客戶端應該能夠在對指令一無所知的情況下發送指令。

ACLs

ACL 規格是由我本人在四年前起草的。我等了這麼久才實作,是為了說服自己現在真的是對的時機:我們靠著一些技巧,主要是指令重新命名,已經在沒有任何 ACL 的情況下走了很長一段路。不過,請不要以為 ACL 的主要動機是為了滿足企業客戶對安全性的需求。作為副作用,ACL 也確實能讓使用者為了安全性目的進行驗證,但這個功能的主要目標是*維運(operational)*。

讓我舉個例子。你有一個 Redis 執行個體,打算用它來做一件新的事情:處理延遲任務(delayed jobs)。你從網路上找了一個函式庫,看起來運作得不錯。那麼,憑什麼這個你並未逐行檢視過的函式庫,就應該能夠呼叫「FLUSHALL」並瞬間清空你的資料庫?也許這個函式庫的測試裡就包含了這樣的指令,而你發現時已經太遲了。或者,你剛聘了一位資淺的開發者,他一直在 Redis 執行個體上反覆呼叫「KEYS *」,而你們公司的 Redis 使用規範卻是「不准使用 KEYS 指令」。

另一個情境是雲端服務供應商:他們必須小心地重新命名管理指令,甚至要想辦法避免這類指令因某些原因外洩。更多技巧:例如讓 MONITOR 在輸出中不顯示這些指令。有了 ACL,你就可以把 Redis 設定成讓未經特定驗證的預設使用者,無法執行任何具管理性或危險性的操作。我認為這對維運來說將會是一大進步。

此外,就我所知,ACL 是我在 Redis 中寫過最棒的程式碼之一。幾乎完全沒有 CPU 成本,除非你使用了 key patterns,但即使那樣成本也很小。實作完全自包含在 acl.c 這個檔案裡,核心的其他部分只有少數幾處對 ACL API 的呼叫。沒有為系統增加任何複雜度,因為它是完全模組化的。事實上,ACL 的程式碼還讓我們得以圍繞 AUTH 指令做了一些不錯的重構。

多執行緒

Redis 可能會獲得的多執行緒支援有兩種。我相信這位使用者指的是像 memcached 那樣的多執行緒,也就是讓單一 Redis 執行個體能夠擴展到多個執行緒,以提升像是 GET 或 SET 這類簡單指令每秒可處理的操作數。這涉及讓 I/O、指令解析等都變成多執行緒。所以我們把這種模式稱為「I/O 多執行緒(I/O threading)」。

另一種多執行緒做法,則是讓耗時較長的指令能在不同的執行緒中執行,以免其他客戶端被阻塞。我們把這種執行緒模型稱為「慢指令多執行緒(Slow commands threading)」。

好吧,計畫是這樣的:就我所知,I/O 多執行緒不會在 Redis 中實現,因為經過深思熟慮後,我認為那會帶來大量複雜度卻沒有充分的理由。事實上,許多 Redis 的部署本來就是受限於網路或記憶體。此外,我真心相信無共享(share-nothing)架構,所以我想要擴展 Redis 的方式,是透過改善在同一台主機上執行多個 Redis 執行個體的支援,尤其是透過 Redis Cluster。關於這點,在 2019 年將會發生兩件事:

A) Redis Cluster 的多個執行個體將能夠協調合作,以更明智地使用本地執行個體的磁碟,也就是說,避免同時進行 AOF 重寫。

B) 我們將會作為 Redis 專案的一部分,發布一個 Redis Cluster proxy,讓使用者得以在客戶端沒有良好實作 Cluster 協定的情況下,仍能對叢集進行抽象化。

另一件值得注意的是,Redis 並不是 Memcached,但和 memcached 一樣,它是一個記憶體內系統。把像 memcached 這樣資料模型非常簡單的記憶體內系統做成多執行緒,是很有道理的。一個基於磁碟的多執行緒儲存則是必要的。而一個複雜的記憶體內系統要做多執行緒,就處在中間那個會變得很棘手的位置:Redis 的客戶端並非彼此隔離,而且資料結構很複雜。一個正在執行 LPUSH 的執行緒,需要去服務其他正在執行 LPOP 的執行緒。能獲得的好處較少,卻要增加大量的複雜度。

反而,我*非常想要*的是慢操作多執行緒,而透過 Redis 模組系統,我們已經朝正確的方向前進了。不過在未來(不確定是在 Redis 6 還是 7)我們會在模組系統中加入 key 層級的鎖定,讓執行緒可以完全取得某個 key 的控制權來處理耗時的操作。現在,模組已經可以用完全分離的方式為客戶端實作指令並產生回覆,但要存取共享的資料集,仍然需要一個全域鎖:這一點將會被移除。

更完善的持久化

最近我們在這類 Redis 的基礎功能上做了許多努力。最近實作得最好的事情之一,就是在 AOF 檔案中加入 RDB 前導(preamble)。另外,在 Redis 4 和 5 中,複寫(replication)方面也投入了大量工作,現在的水準已經和過去完全不在同一個層次上。是的,這仍然是我主要關注並持續改進的重點之一。

資料結構

從 Redis 5 開始,Redis 已經有了 Streams。對於 Redis 6 和 7,目前計畫是先透過改變某些實作,讓現有的東西在記憶體使用上更有效率。不過要加入新的資料結構,則有很多需要考量的地方。我花了好幾年才想清楚,如何透過 streams,在時間序列和串流的脈絡下,填補 lists、pub/sub 和 sorted sets 之間的空隙。我真的希望 Redis 是一組正交的資料結構,讓使用者可以自行組合運用,而不是一組現成可用的*工具*。Streams 是一個抽象的日誌,所以我認為它是非常值得加入的。不過,其他東西若沒有經過非常長期的審慎考慮,我就不太確定是否值得放進核心。無論如何,近年來在新增資料結構方面的確投入了更多心力。HyperLogLogs、更進階的位元操作、streams、阻塞式的有序集合操作(ZPOP* 和 BZPOP*),以及 streams 都是很好的例子。

結論

我認為 Redis 社群應該要了解為什麼要做某些事、又為什麼把另一些事延後。我犯了一個錯誤,以為透過 Twitter 溝通就像每個人都在那裡一樣,但很多人其實有自己的生活 :-D,根本不在意。部落格是讓社群掌握資訊好得多的方式,我需要花更多時間寫部落格。碰巧我很喜歡寫文章,所以這是雙贏。需要意識到的一件重要事情是,Redis 並沒有一個穩固的路線圖,多年來我發現機會主義式的開發比起擁有固定路線圖要成功得多。某個功能被需要了?我看到了需求?我有心情寫程式?現在是對的時機,因為沒有其他更重大的優先事項?有一群使用者正在協助設計過程,提供提示、想法、測試東西?那就是對的時機,來做吧。為 Redis 制定一個穩固的路線圖是很愚蠢的,因為開源核心團隊的規模很小,有時我會被某個隨機的當機問題卡住好幾個星期……任何固定的長期計畫都行不通。此外,隨著 Redis 社群給予回饋,我的想法也會有很大的改變,所以我每個月都得重寫路線圖。然而,寫部落格至少是一個好方法,可以展示當前優先事項/想法的版本,並說明為什麼有些想法被放棄了。

最後一點說明:我在 Redis Labs 對於要在開源專案這一端放入什麼,擁有的自由度幾乎是無限的。我覺得這在業界算是某種奇蹟,或者只是我在 Redis Labs 共事的人都是善解人意的好人,他們理解我們所做的一切源自開源運動,並且保持下去是明智的。但這並不是常態。如果我在 Redis 的路線圖上犯了錯,那肯定都是我自己的錯。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言