5 種主流注冊中心技術選型,哪個最香?
講解 5 種常用的註冊中心,對比其流程和原理,無論是面試還是技術選型,都非常有幫助。
對於註冊中心,在寫這篇文章前,我其實只對 ETCD 有比較深入的瞭解,但是對於 Zookeeper 和其它的註冊中心瞭解甚少,甚至都沒有考慮過 ETCD 和 Zookeeper 是否適合作爲註冊中心。
經過近 2 周的學習,原來註冊中心除了 ETCD 和 Zookeeper,常用的還有 Eureka、Nacos、Consul,下面我們就對這些常用的註冊中心,初探它們的異同,便於後續技術選型。
註冊中心基本概念
什麼是註冊中心?
註冊中心主要有三種角色:
-
服務提供者(RPC Server):在啓動時,向 Registry 註冊自身服務,並向 Registry 定期發送心跳彙報存活狀態。
-
服務消費者(RPC Client):在啓動時,向 Registry 訂閱服務,把 Registry 返回的服務節點列表緩存在本地內存中,並與 RPC Sever 建立連接。
-
服務註冊中心(Registry):用於保存 RPC Server 的註冊信息,當 RPC Server 節點發生變更時,Registry 會同步變更,RPC Client 感知後會刷新本地 內存中緩存的服務節點列表。
最後,RPC Client 從本地緩存的服務節點列表中,基於負載均衡算法選擇一臺 RPC Sever 發起調用。
註冊中心需要實現功能
根據註冊中心原理的描述,註冊中心必須實現以下功能,偷個懶,直接貼幅圖:
註冊中心基礎掃盲
這塊知識如果大家已經知道,可以直接跳過,主要是爲了掃盲。
CAP 理論
CAP 理論是分佈式架構中重要理論:
-
一致性 (Consistency):所有節點在同一時間具有相同的數據;
-
可用性 (Availability) :保證每個請求不管成功或者失敗都有響應;
-
分隔容忍 (Partition tolerance) :系統中任意信息的丟失或失敗不會影響系統的繼續運作。
關於 P 的理解,我覺得是在整個系統中某個部分,掛掉了,或者宕機了,並不影響整個系統的運作或者說使用,而可用性是,某個系統的某個節點掛了,但是並不影響系統的接受或者發出請求。
CAP 不可能都取,只能取其中 2 個的原因如下:
-
如果 C 是第一需求的話,那麼會影響 A 的性能,因爲要數據同步,不然請求結果會有差異,但是數據同步會消耗時間,期間可用性就會降低。
-
如果 A 是第一需求,那麼只要有一個服務在,就能正常接受請求,但是對於返回結果變不能保證,原因是,在分佈式部署的時候,數據一致的過程不可能想切線路那麼快。
-
再如果,同時滿足一致性和可用性,那麼分區容錯就很難保證了,也就是單點,也是分佈式的基本核心。
分佈式系統協議
一致性協議算法主要有 Paxos、Raft、ZAB。
Paxos 算法是 Leslie Lamport 在 1990 年提出的一種基於消息傳遞的一致性算法,非常難以理解,基於 Paxos 協議的數據同步與傳統主備方式最大的區別在於:Paxos 只需超過半數的副本在線且相互通信正常,就可以保證服務的持續可用,且數據不丟失。
Raft 是斯坦福大學的 Diego Ongaro、John Ousterhout 兩個人以易理解爲目標設計的一致性算法,已經有了十幾種語言的 Raft 算法實現框架,較爲出名的有 etcd,Google 的 Kubernetes 也是用了 etcd 作爲他的服務發現框架。
Raft 是 Paxos 的簡化版,與 Paxos 相比,Raft 強調的是易理解、易實現,Raft 和 Paxos 一樣只要保證超過半數的節點正常就能夠提供服務。這篇文章 《ETCD 教程 - 2.Raft 協議》 詳細講解了 Raft 原理,非常有意思,感興趣的同學可以看看。
ZooKeeper Atomic Broadcast (ZAB, ZooKeeper 原子消息廣播協議) 是 ZooKeeper 實現分佈式數據一致性的核心算法,ZAB 借鑑 Paxos 算法,但又不像 Paxos 算法那樣,是一種通用的分佈式一致性算法,它是一種特別爲 ZooKeeper 專門設計的支持崩潰恢復的原子廣播協議。
常用註冊中心
這裏主要介紹 5 種常用的註冊中心,分別爲 Zookeeper、Eureka、Nacos、Consul 和 ETCD。
Zookeeper
這個說起來有點意思的是官方並沒有說他是一個註冊中心,但是國內 Dubbo 場景下很多都是使用 Zookeeper 來完成了註冊中心的功能。
當然這有很多歷史原因,這裏我們就不追溯了。ZooKeeper 是非常經典的服務註冊中心中間件,在國內環境下,由於受到 Dubbo 框架的影響,大部分情況下認爲 Zookeeper 是 RPC 服務框架下注冊中心最好選擇,隨着 Dubbo 框架的不斷開發優化,和各種註冊中心組件的誕生,即使是 RPC 框架,現在的註冊中心也逐步放棄了 ZooKeeper。在常用的開發集羣環境中,ZooKeeper 依然起到十分重要的作用,Java 體系中,大部分的集羣環境都是依賴 ZooKeeper 管理服務的各個節點。
Zookeeper 如何實現註冊中心
具體可參考這篇文章 《Zookeeper 用作註冊中心的原理》,下面的內容都出自該文章。
Zookeeper 可以充當一個服務註冊表(Service Registry),讓多個服務提供者形成一個集羣,讓服務消費者通過服務註冊表獲取具體的服務訪問地址(Ip + 端口)去訪問具體的服務提供者。如下圖所示:
每當一個服務提供者部署後都要將自己的服務註冊到 zookeeper 的某一路徑上: /{service}/{version}/{ip:port} 。
比如我們的 HelloWorldService 部署到兩臺機器,那麼 Zookeeper 上就會創建兩條目錄:
-
/HelloWorldService/1.0.0/100.19.20.01:16888
-
/HelloWorldService/1.0.0/100.19.20.02:16888
這麼描述有點不好理解,下圖更直觀:
在 zookeeper 中,進行服務註冊,實際上就是在 zookeeper 中創建了一個 znode 節點,該節點存儲了該服務的 IP、端口、調用方式 (協議、序列化方式) 等。該節點承擔着最重要的職責,它由服務提供者 (發佈服務時) 創建,以供服務消費者獲取節點中的信息,從而定位到服務提供者真正網絡拓撲位置以及得知如何調用。
RPC 服務註冊 / 發現過程簡述如下:
-
服務提供者啓動時,會將其服務名稱,ip 地址註冊到配置中心。
-
服務消費者在第一次調用服務時,會通過註冊中心找到相應的服務的 IP 地址列表,並緩存到本地,以供後續使用。當消費者調用服務時,不會再去請求註冊中心,而是直接通過負載均衡算法從 IP 列表中取一個服務提供者的服務器調用服務。
-
當服務提供者的某臺服務器宕機或下線時,相應的 ip 會從服務提供者 IP 列表中移除。同時,註冊中心會將新的服務 IP 地址列表發送給服務消費者機器,緩存在消費者本機。
-
當某個服務的所有服務器都下線了,那麼這個服務也就下線了。
-
同樣,當服務提供者的某臺服務器上線時,註冊中心會將新的服務 IP 地址列表發送給服務消費者機器,緩存在消費者本機。
-
服務提供方可以根據服務消費者的數量來作爲服務下線的依據。
zookeeper 提供了 “心跳檢測” 功能:它會定時向各個服務提供者發送一個請求(實際上建立的是一個 socket 長連接),如果長期沒有響應,服務中心就認爲該服務提供者已經 “掛了”,並將其剔除。
比如 100.100.0.237 這臺機器如果宕機了,那麼 zookeeper 上的路徑就會只剩 / HelloWorldService/1.0.0/100.100.0.238:16888。
Zookeeper 的 Watch 機制其實就是一種推拉結合的模式:
-
服務消費者會去監聽相應路徑(/HelloWorldService/1.0.0),一旦路徑上的數據有任務變化(增加或減少),Zookeeper 只會發送一個事件類型和節點信息給關注的客戶端,而不會包括具體的變更內容,所以事件本身是輕量級的,這就是推的部分。
-
收到變更通知的客戶端需要自己去拉變更的數據,這就是拉的部分。
Zookeeper 不適合作爲註冊中心
作爲一個分佈式協同服務,ZooKeeper 非常好,但是對於 Service 發現服務來說就不合適了,因爲對於 Service 發現服務來說就算是返回了包含不實的信息的結果也比什麼都不返回要好。所以當向註冊中心查詢服務列表時,我們可以容忍註冊中心返回的是幾分鐘以前的註冊信息,但不能接受服務直接 down 掉不可用。
但是 zk 會出現這樣一種情況,當 master 節點因爲網絡故障與其他節點失去聯繫時,剩餘節點會重新進行 leader 選舉。問題在於,選舉 leader 的時間太長,30 ~ 120s, 且選舉期間整個 zk 集羣都是不可用的,這就導致在選舉期間註冊服務癱瘓。在雲部署的環境下,因網絡問題使得 zk 集羣失去 master 節點是較大概率會發生的事,雖然服務能夠最終恢復,但是漫長的選舉時間導致的註冊長期不可用是不能容忍的。
所以說,作爲註冊中心,可用性的要求要高於一致性!
在 CAP 模型中,Zookeeper 整體遵循一致性(CP)原則,即在任何時候對 Zookeeper 的訪問請求能得到一致的數據結果,但是當機器下線或者宕機時,不能保證服務可用性。
那爲什麼 Zookeeper 不使用最終一致性(AP)模型呢?因爲這個依賴 Zookeeper 的核心算法是 ZAB,所有設計都是爲了強一致性。這個對於分佈式協調系統,完全沒沒有毛病,但是你如果將 Zookeeper 爲分佈式協調服務所做的一致性保障,用在註冊中心,或者說服務發現場景,這個其實就不合適。
Eureka
Eureka 架構圖
什麼,上面這幅圖看起來很複雜?那我給你貼個簡化版:
Eureka 特點
-
可用性(AP 原則):Eureka 在設計時就緊遵 AP 原則,Eureka 的集羣中,只要有一臺 Eureka 還在,就能保證註冊服務可用,只不過查到的信息可能不是最新的(不保證強一致性)。
-
去中心化架構:Eureka Server 可以運行多個實例來構建集羣,不同於 ZooKeeper 的選舉 leader 的過程,Eureka Server 採用的是 Peer to Peer 對等通信。這是一種去中心化的架構,無 master/slave 之分,每一個 Peer 都是對等的。節點通過彼此互相註冊來提高可用性,每個節點需要添加一個或多個有效的 serviceUrl 指向其他節點。每個節點都可被視爲其他節點的副本。
-
請求自動切換:在集羣環境中如果某臺 Eureka Server 宕機,Eureka Client 的請求會自動切換到新的 Eureka Server 節點上,當宕機的服務器重新恢復後,Eureka 會再次將其納入到服務器集羣管理之中。
-
節點間操作複製:當節點開始接受客戶端請求時,所有的操作都會在節點間進行復制操作,將請求複製到該 Eureka Server 當前所知的其它所有節點中。
-
自動註冊 & 心跳:當一個新的 Eureka Server 節點啓動後,會首先嚐試從鄰近節點獲取所有註冊列表信息,並完成初始化。Eureka Server 通過 getEurekaServiceUrls() 方法獲取所有的節點,並且會通過心跳契約的方式定期更新。
-
自動下線:默認情況下,如果 Eureka Server 在一定時間內沒有接收到某個服務實例的心跳(默認週期爲 30 秒),Eureka Server 將會註銷該實例(默認爲 90 秒, eureka.instance.lease-expiration-duration-in-seconds 進行自定義配置)。
-
保護模式:當 Eureka Server 節點在短時間內丟失過多的心跳時,那麼這個節點就會進入自我保護模式。
除了上述特點,Eureka 還有一種自我保護機制,如果在 15 分鐘內超過 85% 的節點都沒有正常的心跳,那麼 Eureka 就認爲客戶端與註冊中心出現了網絡故障,此時會出現以下幾種情況:
-
Eureka 不再從註冊表中移除因爲長時間沒有收到心跳而過期的服務;
-
Eureka 仍然能夠接受新服務註冊和查詢請求,但是不會被同步到其它節點上(即保證當前節點依然可用)
-
當網絡穩定時,當前實例新註冊的信息會被同步到其它節點中。
Eureka 工作流程
瞭解完 Eureka 核心概念,自我保護機制,以及集羣內的工作原理後,我們來整體梳理一下 Eureka 的工作流程:
-
Eureka Server 啓動成功,等待服務端註冊。在啓動過程中如果配置了集羣,集羣之間定時通過 Replicate 同步註冊表,每個 Eureka Server 都存在獨立完整的服務註冊表信息。
-
Eureka Client 啓動時根據配置的 Eureka Server 地址去註冊中心註冊服務。
-
Eureka Client 會每 30s 向 Eureka Server 發送一次心跳請求,證明客戶端服務正常。
-
當 Eureka Server 90s 內沒有收到 Eureka Client 的心跳,註冊中心則認爲該節點失效,會註銷該實例。
-
單位時間內 Eureka Server 統計到有大量的 Eureka Client 沒有上送心跳,則認爲可能爲網絡異常,進入自我保護機制,不再剔除沒有上送心跳的客戶端。
-
當 Eureka Client 心跳請求恢復正常之後,Eureka Server 自動退出自我保護模式。
-
Eureka Client 定時全量或者增量從註冊中心獲取服務註冊表,並且將獲取到的信息緩存到本地。
-
服務調用時,Eureka Client 會先從本地緩存找尋調取的服務。如果獲取不到,先從註冊中心刷新註冊表,再同步到本地緩存。
-
Eureka Client 獲取到目標服務器信息,發起服務調用。
-
Eureka Client 程序關閉時向 Eureka Server 發送取消請求,Eureka Server 將實例從註冊表中刪除。
通過分析 Eureka 工作原理,我可以明顯地感覺到 Eureka 的設計之巧妙,完美地解決了註冊中心的穩定性和高可用性。
Eureka 爲了保障註冊中心的高可用性,容忍了數據的非強一致性,服務節點間的數據可能不一致, Client-Server 間的數據可能不一致。比較適合跨越多機房、對註冊中心服務可用性要求較高的使用場景。
Nacos
以下內容摘抄自 Nacos 官網:https://nacos.io/zh-cn/docs/what-is-nacos.html
Nacos 致力於幫助您發現、配置和管理微服務。Nacos 提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務元數據及流量管理。
Nacos 幫助您更敏捷和容易地構建、交付和管理微服務平臺。Nacos 是構建以 “服務” 爲中心的現代應用架構 (例如微服務範式、雲原生範式) 的服務基礎設施。
Nacos 主要特點
服務發現和服務健康監測:
-
Nacos 支持基於 DNS 和基於 RPC 的服務發現。服務提供者使用原生 SDK、OpenAPI、或一個獨立的 Agent TODO 註冊 Service 後,服務消費者可以使用 DNS TODO 或 HTTP&API 查找和發現服務。
-
Nacos 提供對服務的實時的健康檢查,阻止向不健康的主機或服務實例發送請求。Nacos 支持傳輸層 (PING 或 TCP) 和應用層 (如 HTTP、MySQL、用戶自定義)的健康檢查。對於複雜的雲環境和網絡拓撲環境中(如 VPC、邊緣網絡等)服務的健康檢查,Nacos 提供了 agent 上報模式和服務端主動檢測 2 種健康檢查模式。Nacos 還提供了統一的健康檢查儀表盤,幫助您根據健康狀態管理服務的可用性及流量。
動態配置服務:
-
動態配置服務可以讓您以中心化、外部化和動態化的方式管理所有環境的應用配置和服務配置。
-
動態配置消除了配置變更時重新部署應用和服務的需要,讓配置管理變得更加高效和敏捷。
-
配置中心化管理讓實現無狀態服務變得更簡單,讓服務按需彈性擴展變得更容易。
-
Nacos 提供了一個簡潔易用的 UI (控制檯樣例 Demo) 幫助您管理所有的服務和應用的配置。Nacos 還提供包括配置版本跟蹤、金絲雀發佈、一鍵回滾配置以及客戶端配置更新狀態跟蹤在內的一系列開箱即用的配置管理特性,幫助您更安全地在生產環境中管理配置變更和降低配置變更帶來的風險。
動態 DNS 服務:
-
動態 DNS 服務支持權重路由,讓您更容易地實現中間層負載均衡、更靈活的路由策略、流量控制以及數據中心內網的簡單 DNS 解析服務。動態 DNS 服務還能讓您更容易地實現以 DNS 協議爲基礎的服務發現,以幫助您消除耦合到廠商私有服務發現 API 上的風險。
-
Nacos 提供了一些簡單的 DNS APIs TODO 幫助您管理服務的關聯域名和可用的 IP:PORT 列表。
小節一下:
Nacos 是阿里開源的,支持基於 DNS 和基於 RPC 的服務發現。
Nacos 的註冊中心支持 CP 也支持 AP,對他來說只是一個命令的切換,隨你玩,還支持各種註冊中心遷移到 Nacos,反正一句話,只要你想要的他就有。
Nacos 除了服務的註冊發現之外,還支持動態配置服務,一句話概括就是 Nacos = Spring Cloud 註冊中心 + Spring Cloud 配置中心。
Consul
Consul 是 HashiCorp 公司推出的開源工具,用於實現分佈式系統的服務發現與配置。與其它分佈式服務註冊與發現的方案,Consul 的方案更 “一站式”,內置了服務註冊與發現框 架、分佈一致性協議實現、健康檢查、Key/Value 存儲、多數據中心方案,不再需要依賴其它工具(比如 ZooKeeper 等)。
Consul 使用起來也較爲簡單,使用 Go 語言編寫,因此具有天然可移植性 (支持 Linux、windows 和 Mac OS X);安裝包僅包含一個可執行文件,方便部署,與 Docker 等輕量級容器可無縫配合。
Consul 的調用過程
-
當 Producer 啓動的時候,會向 Consul 發送一個 post 請求,告訴 Consul 自己的 IP 和 Port;
-
Consul 接收到 Producer 的註冊後,每隔 10s(默認)會向 Producer 發送一個健康檢查的請求,檢驗 Producer 是否健康;
-
當 Consumer 發送 GET 方式請求 /api/address 到 Producer 時,會先從 Consul 中拿到一個存儲服務 IP 和 Port 的臨時表,從表中拿到 Producer 的 IP 和 Port 後再發送 GET 方式請求 /api/address;
-
該臨時表每隔 10s 會更新,只包含有通過了健康檢查的 Producer。
Consul 主要特徵
-
CP 模型,使用 Raft 算法來保證強一致性,不保證可用性;
-
支持服務註冊與發現、健康檢查、KV Store 功能。
-
支持多數據中心,可以避免單數據中心的單點故障,而其部署則需要考慮網絡延遲, 分片等情況等。
-
支持安全服務通信,Consul 可以爲服務生成和分發 TLS 證書,以建立相互的 TLS 連接。
-
支持 http 和 dns 協議接口;
-
官方提供 web 管理界面。
多數據中心
這裏純屬瞭解,學習一下 Consul 的多數據中心是如何實現的。
Consul 支持開箱即用的多數據中心,這意味着用戶不需要擔心需要建立額外的抽象層讓業務擴展到多個區域。
在上圖中有兩個 DataCenter,他們通過 Internet 互聯,同時請注意爲了提高通信效率,只有 Server 節點才加入跨數據中心的通信。
在單個數據中心中,Consul 分爲 Client 和 Server 兩種節點(所有的節點也被稱爲 Agent),Server 節點保存數據,Client 負責健康檢查及轉發數據請求到 Server;Server 節點有一個 Leader 和多個 Follower,Leader 節點會將數據同步到 Follower,Server 的數量推薦是 3 個或者 5 個,在 Leader 掛掉的時候會啓動選舉機制產生一個新的 Leader。
集羣內的 Consul 節點通過 gossip 協議(流言協議)維護成員關係,也就是說某個節點了解集羣內現在還有哪些節點,這些節點是 Client 還是 Server。單個數據中心的流言協議同時使用 TCP 和 UDP 通信,並且都使用 8301 端口。跨數據中心的流言協議也同時使用 TCP 和 UDP 通信,端口使用 8302。
集羣內數據的讀寫請求既可以直接發到 Server,也可以通過 Client 使用 RPC 轉發到 Server,請求最終會到達 Leader 節點,在允許數據延時的情況下,讀請求也可以在普通的 Server 節點完成,集羣內數據的讀寫和複製都是通過 TCP 的 8300 端口完成。
ETCD
etcd 是一個 Go 言編寫的分佈式、高可用的一致性鍵值存儲系統,用於提供可靠的分佈式鍵值存儲、配置共享和服務發現等功能。
ETCD 特點
-
易使用:基於 HTTP+JSON 的 API 讓你用 curl 就可以輕鬆使用;
-
易部署:使用 Go 語言編寫,跨平臺,部署和維護簡單;
-
強一致:使用 Raft 算法充分保證了分佈式系統數據的強一致性;
-
高可用:具有容錯能力,假設集羣有 n 個節點,當有 (n-1)/2 節點發送故障,依然能提供服務;
-
持久化:數據更新後,會通過 WAL 格式數據持久化到磁盤,支持 Snapshot 快照;
-
快速:每個實例每秒支持一千次寫操作,極限寫性能可達 10K QPS;
-
安全:可選 SSL 客戶認證機制;
-
ETCD 3.0:除了上述功能,還支持 gRPC 通信、watch 機制。
ETCD 框架
etcd 主要分爲四個部分:
-
HTTP Server:用於處理用戶發送的 API 請求以及其它 etcd 節點的同步與心跳信息請求。
-
Store:用於處理 etcd 支持的各類功能的事務,包括數據索引、節點狀態變更、監控與反饋、事件處理與執行等等,是 etcd 對用戶提供的大多數 API 功能的具體實現。
-
Raft:Raft 強一致性算法的具體實現,是 etcd 的核心。
-
WAL:Write Ahead Log(預寫式日誌),是 etcd 的數據存儲方式。除了在內存中存有所有數據的狀態以及節點的索引以外,etcd 就通過 WAL 進行持久化存儲。WAL 中,所有的數據提交前都會事先記錄日誌。Snapshot 是爲了防止數據過多而進行的狀態快照;Entry 表示存儲的具體日誌內容。
通常,一個用戶的請求發送過來,會經由 HTTP Server 轉發給 Store 進行具體的事務處理,如果涉及到節點的修改,則交給 Raft 模塊進行狀態的變更、日誌的記錄,然後再同步給別的 etcd 節點以確認數據提交,最後進行數據的提交,再次同步。
註冊中心對比 & 選型
註冊中心對比
-
服務健康檢查:Euraka 使用時需要顯式配置健康檢查支持;Zookeeper、Etcd 則在失去了和服務進程的連接情況下任務不健康,而 Consul 相對更爲詳細點,比如內存是否已使用了 90%,文件系統的空間是不是快不足了。
-
多數據中心:Consul 和 Nacos 都支持,其他的產品則需要額外的開發工作來實現。
-
KV 存儲服務:除了 Eureka,其他幾款都能夠對外支持 k-v 的存儲服務,所以後面會講到這幾款產品追求高一致性的重要原因。而提供存儲服務,也能夠較好的轉化爲動態配置服務哦。
-
CAP 理論的取捨:
-
Eureka 是典型的 AP,Nacos 可以配置爲 AP,作爲分佈式場景下的服務發現的產品較爲合適,服務發現場景的可用性優先級較高,一致性並不是特別緻命。
-
而 Zookeeper、Etcd、Consul 則是 CP 類型犧牲可用性,在服務發現場景並沒太大優勢;
-
Watch 的支持:Zookeeper 支持服務器端推送變化,其它都通過長輪詢的方式來實現變化的感知。
-
自身集羣的監控:除了 Zookeeper 和 Nacos,其它幾款都默認支持 metrics,運維者可以蒐集並報警這些度量信息達到監控目的。
-
Spring Cloud 的集成:目前都有相對應的 boot starter,提供了集成能力。
註冊中心選型
關於註冊中心的對比和選型,其實上面已經講的非常清楚了,我給出一些個人理解:
-
關於 CP 還是 AP 的選擇:選擇 AP,因爲可用性高於一致性,所以更傾向 Eureka 和 Nacos;關於 Eureka、Nacos 如何選擇,哪個讓我做的事少,我就選擇哪個,顯然 Nacos 幫我們做了更多的事。
-
技術體系:Etcd 和 Consul 都是 Go 開發的,Eureka、Nacos、Zookeeper 和 Zookeeper 都是 Java 開發的,可能項目屬於不同的技術棧,會偏向選擇對應的技術體系。
-
高可用:這幾款開源產品都已經考慮如何搭建高可用集羣,有些差別而已;
-
產品的活躍度:這幾款開源產品整體上都比較活躍。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/st-y-SQxwKdG-iLyA8ph6g