天天看點

五分鐘技術小分享 - 2022Week04

五分鐘技術小分享 - 2022Week04

2022-01

2022-01-24 CNCF-Linkerd

今天我們來看 Orchestration & Management 編排和管理 層最後一個核心項目 - Linkerd。從嚴格意義上來說,我們應稱它為Linkerd2,差別于原來的1.0版本。

Linkerd是Service Mesh的第一個産品,但在Google的Istio入場後在功能與性能上完全超越。這一段的曆史很有意思,大家可以自行搜尋了解。

關于Service Mesh,我們已經聊過兩款CNCF中的軟體了 - Envoy/Contour,這個Linkerd是兩者的結合。我們來看一下它的架構示意圖,整體來說分為三塊:

五分鐘技術小分享 - 2022Week04
  1. CLI - 用戶端,對Linkerd2進行管理
  2. Control Plane 控制平面
    1. destination 擷取各類資訊,如服務發現、網絡政策、性能和監控名額
    2. indentity 主要是TLS安全相關
    3. proxy-injector 是一種Kubernetes的Admission Controller,用于對初始化pod注入linkerd相關的資訊
  3. Data Plane 資料平面
    1. linkerd-proxy 核心的功能實作,包括代理、路由、TLS、限流等等
    2. linkerd-init 是一種Kubernetes的Init Containter,用iptables的特性将Pod的流量都導向linkerd-proxy

Linkerd的架構非常清晰明了,與Kubernetes的特性緊密結合。我們也不難看到,它的核心能力非常依賴linkerd-proxy這個元件。linkerd-proxy采用了Rust語言編寫,而對應的Envoy使用的是C++,從性能來看兩者相差無幾,更多的是語言生态上的選擇不同。

我們再一起讀一段Linkerd官方對Service Mesh的定義:

A service mesh like Linkerd is a tools for adding observability, security, and reliability features to “cloud native” applications by transparently inserting this functionality at the platform layer rather than the application layer.
  • observability - 可觀察性:logging、metrics、tracing
  • security - 安全性:TLS等特性
  • reliability - 可靠性:展現在對網絡層的統一管理

從目前來看,Linkerd仍處于一個比較早期的階段,對标Istio還有大量的功能缺失,我在短期内不太看好。不過它引入了Rust語言有可能吸引一批優秀的人才,成為突破口。

2022-01-25 Go1.18的兩個教程

在1月初,我們已經一起看了Go官方對1.18的新特性講解,想回顧的朋友可以點選這個連結:Go1.18概覽。前幾天,官方又釋出了對泛型和Fuzzing的兩個教程,我們再一起浏覽下,查漏補缺。

  • Tutorial: Getting started with generics
  • Tutorial: Getting started with fuzzing

Generics

func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}
           

複制

  • comparable 是個關鍵詞,指的是支援操作符

    ==

    !=

  • int64 | float64 則用簡潔的文法表示了兩種支援的類型

但第二點,如果支援的類型太多,就需要做一次抽象,如

type Number interface {
    int64 | float64
}

func SumNumbers[K comparable, V Number](m map[K]V) V {
  //
}
           

複制

Go語言的泛型表示方法非常簡單,其支援的能力也很有限。相對于C++與JAVA中的泛型,無疑遜色了很多。我們可以簡單地歸納Go泛型的使用場景:用于 基礎類型 的通用操作,如int/int32/int64/float64等這種重複性很高的基本運算。

作為一種标準,Go的泛型落地非常坎坷,短期内官方也不太可能在這塊擴增新的特性,是以Go的泛型适用性會比較窄。

随着1.18的完全落地,我們可以在很多基礎庫中看到泛型的實踐,到時候我們再可以根據具體case進行了解。

Fuzzing

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}
           

複制

相對于傳統的單元測試,Fuzzing Test更強調一種 不确定性的輸入 理念 - 由于輸入的資料是随機的,輸出往往是不确定的,那我們最好可以通過一定的操作,減少甚至消除輸出的不确定性,才能保證測試的完備性:

比如說,示例中對字元串的反轉,轉變成了兩個測試點:

  1. Reversing a string twice preserves the original value 即兩次反轉後成為原字元串,
  2. The reversed string preserves its state as valid UTF-8 字元串依然為UTF-8編碼格式

從輸入和輸出來看,如果每個輸入都對應枚舉出一個輸出,那就是單元測試;而Fuzzing Test的理念是盡可能地将輸出做到可控,更友善地寫各種測試。

在實際工程中,能用到Fuzzing特性的地方很少,更多的還是依賴簡單的單元測試保障我們的代碼品質。

2022-01-26 如何避免分布式事務

最近,有朋友和我交流分布式事務的實踐心得,而我的建議是:盡量避免分布式事務。

這裡的避免并非完全的不要使用,畢竟像金融場景中,這還是一個必要的特性。但對于絕大多數系統,分布式事務帶來的複雜度是非常高的,也需要很高的維護成本與了解成本,遠超其收益,我不太建議大家刻意地使用這個技術。

舉一個簡單的case - 使用者下了一個訂單,經過如下步驟:

  1. 訂單服務生成訂單
  2. 庫存服務扣去庫存
  3. 付費服務完成扣款
  4. 使用者積分服務增加積分

這時,最直覺的解法是要有一套成熟的分布式事務的方案。但事實上,我更推薦在工程上采用下面兩種解決方案,而其中的關鍵詞就是 - 補償。

在MQ中重試

我們經常會利用MQ來解耦服務,那麼自然會用它來驅動大量的消息。

例如,我們将扣款請求放到MQ裡,扣款服務處理成功後通過另一個MQ通知成功。而當扣款服務出現問題時、也就是扣款失敗,常見的有2種選擇:

  1. 如果要求是必須成功的,消費時就不要傳回成功,在服務中反複重試,即便MQ積壓産生告警、再人工恢複;
  2. 如果允許失敗,那就設定一個最大重試次數,超過最大重試次數則通知給對應的補償服務;

利用trace-id+ELK

trace-id是分布式鍊路追蹤的關鍵資訊,用于串聯資訊;而ELK又通過日志收集系統,将這塊收集到了一個系統。

我們可以在生成訂單時,同時記錄這個關鍵性的trace-id,然後調用各個服務。有任何一個服務失敗,我們就将訂單狀态修改為失敗或逾時;而資料不一緻的問題,就由對應的補償服務,根據這些有問題的訂單的trace-id去分析。

其實可以從這個方案延伸出類似的,比如直接将錯誤通過trace-id+資訊發送給補償服務,統一收集。

注意點

  1. 補償不代表隻能手動,我們可以在補償服務内根據錯誤碼,實作一定的自動化;
  2. 補償更多展現的是一種最終一緻性的思想,會有延時,我們要保證中間狀态的資料不會污染系統;

在微服務+雲原生時代,我們非常提倡 面向錯誤程式設計,正是為了能更好地面對各種不确定的異常case。分布式事務帶來了大量的複雜度,目前也沒有一套跨語言、跨元件的通用解決方案,目前主流幾個方案對應用的侵入性很強,是以我不太建議大部分朋友在生産環境使用,而花更多時間學習相關理論、應付面試就行了。

2022-01-27 CNCF-TiKV

了解完核心的 排程與管理 相關的軟體後,我們接下來開始接觸 應用定義與開發 的相關軟體,這部分與我們實際開發接觸最為緊密,也更容易了解。

  • 官網 - https://tikv.org/
  • Github - https://github.com/tikv/tikv

官方的定義為:

TiKV provides both raw and ACID-compliant transactional key-value API, which is widely used in online serving services, such as the metadata storage system for object storage service, the storage system for recommendation systems, the online feature store, etc.

也就是TiKV支援 簡單的與滿足ACID事務性的KV存儲,被應用在各種存儲系統上,如關系型資料庫、非關系型資料庫、分布式檔案系統,最具有代表性的即同屬一個公司的TiDB。按官方的定義,我們可以将它對标Redis。

我們結合TiKV的核心特性來看看。

Low and stable latency

RawKV’s average response time less than 1 ms (P99=10 ms).

延遲是IO相關的軟體很重要的特性。但對于這個特性,我們要注意兩點:

  1. 隻針對簡單KV,而不針對事務
  2. 真實延遲很依賴存儲媒體

從這點來看,在TiKV層面引入事務的特性前,需要我們要斟酌一下它對延遲的影響。

High scalabilit

With the Placement Driver and carefully designed Raft groups, TiKV excels in horizontal scalability and can easily scale to 100+ terabytes of data. Scale-out your TiKV cluster to fit the data size growth without any impact on the application.

強調了高擴充性,可支援100TB+的資料。

TiKV采用了Raft作為分布式一緻性的協定,這一點與Etcd一緻。關于Raft這塊是目前工程化的主流,相對于Paxos更容易落地。不過,各家在實作Raft時都或多或少有一些變種,這塊我們暫時不細聊。

Consistent distributed transactions

Similar to Google’s Spanner, TiKV (TxnKV mode) supports externally consistent distributed transactions.

支援一緻性的分布式事務。

分布式事務對強一緻性的業務非常有價值,但它的實作必然會帶來一定的性能問題,尤其展現在延遲上。以金融服務為例,分布式事務能保證資金的一緻性,不産生資損;但延遲問題又會帶來一些異常case,是以需要做好權衡。

Adjustable consistency

In RawKV and TxnKV modes, you can customize the balance between consistency and performance.

對簡單KV模式與事務性的KV模式,提供了可調節的一緻性功能。

這就是一緻性與性能上的權衡。關于這點,大家可以了解一下ACID與BASE對業務的價值。從我的觀察來看,目前越來越多的服務傾向于最終一緻性,主要有以下優點:

  1. 對外部服務來說視角清晰,更容易了解 - 從外部服務視角來看,本服務最終會趨于一緻,而不需要關心各種異常的中間狀态,這非常有助于微服務的邊界劃分;
  2. 服務更具健壯性 - 軟體系統的不穩定因素很多,最終一緻性可以更好地處理這些異常。

當然,對應的代價是該服務需要引入重試、幂等、異步校驗、狀态機、恢複日志等特性,自身的複雜度是比較高的。這些技術我也會在後面和大家分享。

2022-01-28 CNCF-Vitess

今天我們來聊聊一款和關系型資料庫相關的産品 -

Vitess

Vitess

的定位很簡潔:

A database clustering system for horizontal scaling of MySQL

我們直接從架構圖入手,來了解它是怎麼實作 MySQL橫向擴充 的。

五分鐘技術小分享 - 2022Week04

我們關注最核心的兩個子產品:

VTTablet

A tablet is a combination of a

mysqld

process and a corresponding

vttablet

process, usually running on the same machine. Each tablet is assigned a tablet type, which specifies what role it currently performs.

一個

Tablet

對應到一個具體的

MySQL

執行個體,類似于sidecar模式。我之前基于VTTablet做過一定的二次開發,和大家分享一下我對這塊的認識:

VTTablet

最核心實作,是 模拟一個MySQL,與真正的MySQL進行連接配接。是以,可以展現如下的特點:

  1. 無侵入式 - 充分利用了MySQL叢集間通信的協定,不會侵入原MySQL。這點能衍生出很多價值,例如相容多版本的MySQL。
  2. 性能較優 - 通過MySQL内部通信的協定互動。
  3. 可擴充性強 - 從原先對大MySQL叢集的維護,轉變成了相對輕量級的

    VTTablet

    叢集的維護

VTGate

VTGate is a lightweight proxy server that routes traffic to the correct VTTablet servers and returns consolidated results back to the client. It speaks both the MySQL Protocol and the Vitess gRPC protocol. Thus, your applications can connect to VTGate as if it is a MySQL Server.

VTGate

是網關層的角色,主要分三塊功能:

  1. 對外暴露出原生的MySQL協定與gRPC協定;
  2. 對内維護與

    VTTablet

    叢集的連接配接;
  3. 核心依賴Topology服務中的資料,主要是

    VTTablet

    的狀态資料和Admin的配置資料

小結

Vitess

是用Go語言編寫的軟體,我比較推薦對資料庫原理感興趣的朋友去閱讀

VTTablet

相關的源碼,從中你可以了解到很多MySQL的關鍵性功能,會比直接閱讀MySQL的C++簡單很多。比如我曾經做過的:

  1. SQL解析 - 用于自研的查詢平台
  2. Binlog同步 - 用于MySQL到異構資料庫的同步平台