1. 程式人生 > >從原始碼看Nacos的設計

從原始碼看Nacos的設計

目錄

  • 客戶端與叢集的互動
  • 資料同步
    • 例項資訊同步
    • 服務叢集資訊
  • 關於priv-raft協議
  • Nacos叢集在k8s中的實踐

這片博文來源於我在公司部門內的分享,我隱去了和公司專案相關的部分,重新整理,從幾個方面談一下Nacos的設計(作為註冊中心,基於此時的develop分支)

客戶端與叢集的互動

首先需要宣告的是Nacos Cluster雖然內部使用了Raft協議但是對於Nacos客戶端,Cluster例項是無狀態的。客戶端配置叢集地址有兩種方式:
1.通過配置serverAddr列表,客戶端將訪問叢集時,隨機從列表中選擇一個例項訪問:

NamingService configService = NacosFactory.createNamingService("10.22.0.137:30253,10.22.0.137:30254,10.22.0.137:30255");

當然,一般情況下我們並不會直接配置Nacos例項的IP,可用用域名,以便能動態發現。

2.通過Properties配置endpoint,定時訪問,感知叢集變化,並隨機從介面返回的列表中選擇一個例項訪問,客戶端會與Endpoint建立LONG PULL。


Properties properties = new Properties();
properties.put(PropertyKeyConst.ENDPOINT,"10.18.90.16");
properties.put(PropertyKeyConst.ENDPOINT_PORT,"8850");
 
NamingService configService = NacosFactory.createNamingService(properties);
  • endpoint環境隔離最佳實踐

資料同步

例項資訊同步

例項資訊的由一個叫 Distro (com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl)的一致性協議維護,有如下幾個特點:

  1. 最終一致性,由例項間通過http同步(com.alibaba.nacos.naming.consistency.ephemeral.distro.TaskDispatcher.TaskScheduler)到除了自己的每個例項節點,且攜帶自己全量資料
  2. 資料不持久化,儲存在記憶體(com.alibaba.nacos.naming.consistency.ephemeral.distro.DataStore)
  3. 每個節點通過演算法,只接受一部分請求(com.alibaba.nacos.naming.web.DistroFilter),如果不屬於例項自己的請求過來則通過演算法確定並轉發。

舉例解釋一下第3點:現有Nacos叢集例項A,B,C 共3個。從客戶端與叢集的互動知道,客戶端隨機從A,B,C中隨機選擇一個例項訪問,客戶端NACOS-DEMO選擇訪問B的註冊例項介面,如果NACOS-DEMO的請求應該屬於C處理的話,本次請求將會被B例項中的DistroFilter攔截掉,並由B轉發到C。理解起來,其實挺繞的,但是為什麼這麼設計呢?畢竟A,B,C的例項資料都會最終一致的,我隨機訪問任意一個例項就好了

我的解釋是:
由於資料是最終一致的,中間會存在同步過程,所以如果存在寫了馬上查的場景,則很有可能查不到的情況(客戶端寫和查兩次請求落在了兩個不同的例項)。但是如果通過演算法,一個例項的增刪改查都在同一個確定的例項,就不會出現這種情況了。

服務叢集資訊

  • 通過Open API服務建立介面建立,則直接通過raft協議 持久化到叢集內所有節點
  • 如果例項註冊時,如果服務叢集不存在,則靜默建立,前面說了,例項註冊時,會通過Distro 協議通過例項資料到叢集內其它節點,由於例項資訊也附帶了服務叢集等資訊,所以例項上也順便同步了服務叢集等資訊。所以,預設情況下,靜默建立的服務叢集等資訊也是沒有持久化的。當例項註冊時ephemeral欄位設定為false,除了上訴的同步方式外,還會呼叫raft協議,該協議會同步到其它節點並持久化(Raft協議中規定,只有leader才能處理客戶端請求,所有當發現自己不是leader時,會轉發請求,實現如下圖所示):

ephemeral欄位的介紹

(Nacos 在 1.0.0版本 instance級別增加了一個ephemeral欄位,該欄位表示註冊的例項是否是臨時例項還是持久化例項。如果是臨時例項,則不會在 Nacos 服務端持久化儲存,需要通過上報心跳的方式進行包活,如果一段時間內沒有上報心跳,則會被 Nacos 服務端摘除。在被摘除後如果又開始上報心跳,則會重新將這個例項註冊。持久化例項則會持久化被 Nacos 服務端,此時即使註冊例項的客戶端程序不在,這個例項也不會從服務端刪除,只會將健康狀態設為不健康)

關於priv-raft協議

Raft協議第8節部分內容:

Clients of Raft send all of their requests to the leader.
When a client first starts up, it connects to a randomlychosen
server. If the client’s first choice is not the leader,
that server will reject the client’s request and supply information
about the most recent leader it has heard from
(AppendEntries requests include the network address of
the leader). If the leader crashes, client requests will time
out; clients then try again with randomly-chosen servers
--------------------------------------------------------------------------
Raft的客戶將所有請求傳送給leader。當客戶機第一次啟動時,
它連線到隨機選擇的伺服器。如果客戶機的首選不是leader,
伺服器將拒絕客戶機的請求,並提供它最近聽到的leader的資訊。
如果leader崩潰,客戶端請求將超時;然後,客戶端再次嘗試隨機選擇的伺服器

Nacos官網說,自己實現的事一個輕量級的raft協議,原因我認為至少有如下兩點:

  1. 客戶端是隨機選擇並訪問Nacos例項的
  2. Nacos例項分為leader和flower,leader負責寫入,leader和flower都可以查詢,標準的raft協議,客戶端所有請求會發送給leader保證強一致性,nacos的實現是最終一致性。

Raft協議論文

Nacos叢集在k8s中的實踐

Nacos在VM環境下,部署叢集就比較簡單了,如下圖:

只需要在部署Nacos 例項時,在conf/Cluster.conf 中把自己和叢集內其它例項的地址整合到一起即可,比如上圖的架構,Cluster.conf檔案可以是這樣:

ip1:8848

ip2:8848

ip3:8848

這樣就構成了叢集。Nacos例項會輪詢Cluster.conf 檔案,以保證叢集在有新的例項加入時能相互發現以實現例項的擴縮容,具體程式碼實現在(com.alibaba.nacos.naming.cluster.ServerListManager.ServerListUpdater)。

所以在k8s環境下Nacos的叢集應該怎麼玩?毋庸置疑的是,我們不再需要手動去更改Cluster.conf檔案來維護叢集,必須使用k8s的機制去完成自動擴縮容,那麼問題來了,k8s完成擴縮容,,叢集自己怎麼知道呢?

其實是peer-finder-plugin 和 上面說的自動輪詢Cluster.conf機制來達到的。

分析:
通過Helm使用官方的chart包部署的nacos叢集,每一個pod有兩個容器:

  • peer-finder-plugin-install : 是一個initContainers ,這個映象唯一的作用是安裝 peer-finder外掛相關的指令碼到指定目錄(plugins/peer-finder/),結束後就死亡。
  • nacos-cluster:nacos本體映象,映象啟動時,除了啟動自身的服務外,還會同時呼叫peer-finder-plugin-install 安裝好的指令碼啟動peer-finder(nacos dockerfile)
    peer-finder這個外掛很簡單,使用go語言寫的一個定時輪詢的程式,核心程式碼:

peer-finder輪詢(1秒)指定的k8s service ,如果service下面的pod地址列表發生變化,則重新寫入Cluster.conf檔案。這裡的k8s service就必須是headless型別的了,因為只有解析headless提供的域名,才能獲取所有pod的地址列表。

pod啟動時序圖:

呼叫關係圖:

相關推薦

原始碼Nacos設計

目錄 客戶端與叢集的互動 資料同步 例項資訊同步 服務叢集資訊 關於priv-raft協議 Nacos叢集在k8s中的實踐 這

Alink漫談(二) : 原始碼機器學習平臺Alink設計和架構

# Alink漫談(二) : 從原始碼看機器學習平臺Alink設計和架構 [TOC] ## 0x00 摘要 Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習演算法平臺,是業界首個同時支援批式演算法、流式演算法的機器學習平臺。本文是漫談系列的第二篇,將從原始碼入手,帶領大傢俱體剖析

原始碼Spring Boot 2.0.1

Spring Boot 命名配置很少,卻可以做到和其他配置複雜的框架相同的功能工作,從原始碼來看是怎麼做到的。 我這裡使用的Spring Boot版本是 2.0.1.RELEASE Spring Boot最重要的註解: @SpringBootApplication 開啟它: 其

原始碼Spark讀取Hive表資料小檔案和分塊的問題

原文連結:https://mp.csdn.net/postedit/82423831  使用Spark進行資料分析和計算早已成趨勢,你是否關注過讀取一張Hive表時Task數為什麼是那麼多呢?它跟什麼有關係呢? 最近剛好碰到這個問題,而之前對此有些模糊,所以做了些整理,希望大家拍磚探討

mybatis-3.4.x 原始碼快取的使用[筆記三]

從原始碼看mybatis快取 簡單看下SqlSession的建立 //DefaultSqlSessionFactory.java private SqlSession openSessionFromDataSource(ExecutorType

原始碼Android】03Android MessageQueue訊息迴圈處理機制(epoll實現)

1 enqueueMessage handler傳送一條訊息 mHandler.sendEmptyMessage(1);經過層層呼叫,進入到sendMessageAtTime函式塊,最後呼叫到enqueueMessageHandler.java public bool

【STL】原始碼map

map 與set相同,map同樣是以紅黑樹RB_Tree為底層機制的關聯式容器。map的每一個元素都擁有兩個值,一個鍵值(key)和一個實值(value)。它的內部實現是用一個pair來儲存這個兩個值。所以,map的每一個元素又是一個pair。下面是STL原始碼中stl_p

原始碼Android】01Looper說起

1 為什麼以這一個點為開頭? 因為面試的時候被問到ThreadLocal完全不懂,前幾天發現Looper內正好使用了ThreadLocal,那麼從哪裡跌倒就從哪裡爬起來。 2 什麼是Looper 首先看/sdk/docs/reference/android/os/Loop

原始碼中學習設計模式系列——單例模式序/反序列化以及反射攻擊的問題(二)

一、前言 這篇文章是學習單例模式的第二篇,之前的文章一下子就給出來看起來很高大上的實現方法,但是這種模式還是存在漏洞的,具體有什麼問題,大家可以停頓一會兒,思考一下。好了,不賣關子了,下面我們來看看每種單例模式存在的問題以及解決辦法。 二、每種Singleton 模式的演進  模式一

原始碼Android】00站得高與挖的深

一直覺得自己的程式設計基礎還可以,也接觸過許多移動實戰開發,至少戰勝了班裡70%的同學, 但是經過慘痛的阿里實習生面試經歷證明,我還太嫩了。 以前覺得一個開源專案,學會如何使用,知道實現思路就已經夠了, 但是如果你不知道每一個技術細節,你完全不會再往前進步。 使用過很多

原始碼Spring bean 生命週期

在Spring中,bean一般都以單例模式存在,除非我們將singleton屬性設為false。 單例在多執行緒的環境下需要考慮執行緒安全的問題,對於一些公共的資源或資料應該怎麼處理才能保證安全,應該在什麼時機訪問這些資源最恰當。 熟悉了spring bean的整個生命週

原始碼hystrix的工作原理

Hystrix是Netflix開源的一個限流熔斷的專案、主要有以下功能: 隔離(執行緒池隔離和訊號量隔離):限制呼叫分散式服務的資源使用,某一個呼叫的服務出現問題不會影響其他服務呼叫。 優雅的降級機制:超時降級、資源不足時(執行緒或訊號量)降級,降級後可以配

原始碼Flask框架配置管理

1 引言 Flask作為Python語言web開發的三大頂樑柱框架之一,對於配置的管理當然必不可少。一個應用從開發到測試到最後的產品釋出,往往都需要多種不同的配置,例如是否開啟除錯模式、使用哪個資料庫等等,這些配置都可能因開發階段和環境而異。 2 Flask配置類:Config 為了達到對配置方便快捷而

原始碼 PHP 7 陣列的實現

> 本文所用原始碼為 PHP 7.4.4 的版本。 ## PHP 7 陣列概述 > PHP 中的陣列實際上是一個有序對映。對映是一種把 values 關聯到 keys 的型別。此型別在很多方面做了優化,因此可以把它當成真正的陣列,或列表(向量),散列表(是對映的一種實現),字典,集合,棧,佇列

Mybatis原始碼分析(6)—— JDBCMybatis的設計

Java資料庫連線,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面,提供了諸如查詢和更新資料庫中資料的方法。 六步流程: 載入驅動(5.x驅動包不需要這步了) 建立

微信小程式開發者工具原始碼實現原理(一)- - 小程式架構設計

使用微信小程式開發已經很長時間了,對小程式開發已經相當熟練了;但是作為一名對技術有追求的前端開發,僅僅熟練掌握小程式的開發感覺還是不夠的,我們應該更進一步的去理解其背後實現的原理以及對應的考量,這可能會解釋我們在開發過程中遇到的一些疑惑,比如為啥小程式不能操作dom、小程式是web技術渲染還是native技術

iOS 11怎樣設計APP圖標

蘋果WWDC2017開發者大會已經塵埃落定,除了新產品的發布,iOS 11也正式亮相。新系統中,地圖、App Store、時鐘、相機、聯系人等等原生應用都換了新的圖標。此次圖標的變化勢必也會激發下一個圖標設計的潮流,如何設計圖標又將成為一個新的熱門話題。 最新版的iOS 11,相比於iOS

Chrome原始碼事件迴圈

我們經常說JS的事件迴圈有微觀佇列和巨集觀佇列,所有的非同步事件都會放到這兩個佇列裡面等待執行,並且微觀任務要先於巨集觀任務執行。實際上事件迴圈是多執行緒的一種工作方式。通常為了提高執行效率會新起一條或多條執行緒進行並行運算,然後算完了就告知結果並退出,但是有時候並不想每次都新起執行緒,而是讓這些執行緒變成常

原始碼角度Spring生命週期(官方最全)

Spring在beanfactory中給出了spring的生命週期的list列表 一、bean初始化前的處理 Bean factory implementations should support the standard bean lifecycle interfaces as

Vue.js原始碼非同步更新DOM策略及nextTick

寫在前面 因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js原始碼,並做了總結與輸出。 文章的原地址:https://github.com/answershuto/learnVue。 在學習過程中,為Vue加上了中文的註釋https:/