發(fā)布于:2020-12-24 16:14:26
0
79
0
在短短20年的時(shí)間里,軟件工程已經(jīng)從設(shè)計(jì)具有單一數(shù)據(jù)庫(kù)和集中式狀態(tài)的整體式架構(gòu)轉(zhuǎn)變?yōu)槲⒎?wù),在微服務(wù)中,所有內(nèi)容都分布在多個(gè)容器,服務(wù)器,數(shù)據(jù)中心甚至大洲上。 分發(fā)事物解決了擴(kuò)展問題,但引入了一個(gè)全新的問題世界,其中許多問題以前是由整體解決的。
在本文中,我將簡(jiǎn)要介紹網(wǎng)絡(luò)應(yīng)用程序的歷史,討論如何達(dá)到這一點(diǎn)。 之后,我們將討論Temporal的狀態(tài)執(zhí)行模型及其如何解決面向服務(wù)的體系結(jié)構(gòu)(SOA)引入的問題。 全面披露我是Temporal產(chǎn)品的負(fù)責(zé)人,所以我可能會(huì)有偏見,但我認(rèn)為這種方法是未來(lái)。
簡(jiǎn)短的歷史課
二十年前,開發(fā)人員幾乎總是構(gòu)建單片應(yīng)用程序。 該模型簡(jiǎn)單,一致,并且類似于您在本地環(huán)境中進(jìn)行編程的經(jīng)驗(yàn)。
巨石本質(zhì)上依賴于單個(gè)數(shù)據(jù)庫(kù),這意味著所有狀態(tài)都是集中的。 整體可以在一次交易中更改其任何狀態(tài),這意味著它會(huì)產(chǎn)生二進(jìn)制結(jié)果,無(wú)論它是否起作用。 不一致的空間為零。 因此,整體式為開發(fā)人員提供了很好的經(jīng)驗(yàn),因?yàn)檫@意味著沒有失敗的交易導(dǎo)致狀態(tài)不一致的機(jī)會(huì)。 反過來(lái),這意味著開發(fā)人員不必不斷編寫代碼來(lái)猜測(cè)事物的狀態(tài)。
長(zhǎng)期以來(lái),整體式設(shè)計(jì)才有意義。 沒有大量的連接用戶,這意味著軟件的規(guī)模要求很小。 即使是最大的軟件巨頭,其經(jīng)營(yíng)規(guī)模如今看來(lái)也很小。 像亞馬遜和谷歌這樣的少數(shù)公司正在“規(guī)?!苯?jīng)營(yíng),但是它們是罕見的例外,而不是常規(guī)。
人們喜歡軟件
在過去的20年中,對(duì)軟件的需求不會(huì)停止增長(zhǎng)。如今,預(yù)計(jì)應(yīng)用程序?qū)牡谝惶扉_始服務(wù)于全球市場(chǎng)。 Twitter和Facebook等公司已經(jīng)實(shí)現(xiàn)了24/7全天候在線賭注。軟件不再只是幕后的力量,它已成為最終用戶體驗(yàn)本身?,F(xiàn)在期望每個(gè)公司都有軟件產(chǎn)品??煽啃院涂捎眯圆辉偈枪δ?,而是必要條件。
不幸的是,當(dāng)規(guī)模和可用性成為需求時(shí),整體式裝置便開始崩潰。開發(fā)人員和企業(yè)需要尋找方法來(lái)跟上全球快速增長(zhǎng)和苛刻的用戶期望。他們開始尋找可減輕他們所遇到的可伸縮性問題的替代體系結(jié)構(gòu)。
他們找到的答案是微服務(wù)(很好的面向服務(wù)的體系結(jié)構(gòu))。最初,微服務(wù)似乎很棒,因?yàn)樗鼈兪箲?yīng)用程序可以分解為相對(duì)獨(dú)立的單元,這些單元可以獨(dú)立擴(kuò)展。而且由于每個(gè)微服務(wù)都保持其自己的狀態(tài),這意味著您的應(yīng)用程序不再局限于一臺(tái)計(jì)算機(jī)上適合的狀態(tài)!開發(fā)人員最終可以構(gòu)建滿足日益連接的世界的規(guī)模需求的應(yīng)用程序。微服務(wù)還為團(tuán)隊(duì)和公司帶來(lái)了靈活性,因?yàn)樗鼈優(yōu)榻M織架構(gòu)提供了明確的責(zé)任線和分離線。
沒有免費(fèi)的午餐
盡管微服務(wù)解決了從根本上阻礙軟件增長(zhǎng)的可伸縮性和可用性問題,但并非一切都很好。開發(fā)人員開始意識(shí)到微服務(wù)具有一些嚴(yán)重的缺陷。
對(duì)于整體而言,通常只有一個(gè)數(shù)據(jù)庫(kù)實(shí)例和一臺(tái)應(yīng)用程序服務(wù)器。而且由于無(wú)法分解整體,因此只有兩個(gè)實(shí)際的縮放選項(xiàng)。第一種選擇是垂直擴(kuò)展,這意味著升級(jí)硬件以增加吞吐量/容量。垂直擴(kuò)展可以提高效率,但成本高昂,而且如果您的應(yīng)用程序需求不斷增長(zhǎng),那么絕對(duì)不是永久解決方案。如果垂直擴(kuò)展足夠,最終將耗盡硬件以進(jìn)行升級(jí)。第二種選擇是水平縮放,在整體式的情況下,這意味著僅創(chuàng)建其自身的副本,以便每個(gè)副本都可服務(wù)于一組特定的用戶/請(qǐng)求等。水平縮放式的整體式會(huì)導(dǎo)致資源利用不足,并且在足夠高的比例下,普通不會(huì)工作。微服務(wù)不是這種情況,微服務(wù)的價(jià)值來(lái)自擁有多種“類型”的數(shù)據(jù)庫(kù),隊(duì)列和其他可獨(dú)立擴(kuò)展和操作的服務(wù)器的能力。但是人們轉(zhuǎn)向微服務(wù)時(shí)注意到的第一個(gè)問題是,他們突然開始對(duì)許多不同類型的服務(wù)器和數(shù)據(jù)庫(kù)負(fù)責(zé)。長(zhǎng)期以來(lái),微服務(wù)的這一方面一直沒有得到解決,開發(fā)人員和運(yùn)營(yíng)商不得不自己解決。解決微服務(wù)隨附的基礎(chǔ)架構(gòu)管理問題非常困難,這使應(yīng)用程序充其量是不可靠的。
需求是改變的最終工具。隨著微服務(wù)的采用迅速增加,開發(fā)人員變得越來(lái)越有動(dòng)力來(lái)解決其潛在的基礎(chǔ)結(jié)構(gòu)問題。慢慢地但可以肯定的是,解決方案開始出現(xiàn),而Docker,Kubernetes和AWS Lambda等技術(shù)則填補(bǔ)了空白。這些技術(shù)中的每一種都大大減輕了運(yùn)行微服務(wù)基礎(chǔ)架構(gòu)的負(fù)擔(dān)。開發(fā)人員不必編寫用于處理容器和資源的自定義代碼,而可以依靠工具來(lái)為它們完成工作。現(xiàn)在,到2020年,我們終于達(dá)到了一個(gè)點(diǎn),即基礎(chǔ)架構(gòu)的可用性不會(huì)破壞應(yīng)用程序的可靠性。做得好!
當(dāng)然,我們還沒有進(jìn)入完美穩(wěn)定軟件的烏托邦?;A(chǔ)架構(gòu)不再是應(yīng)用程序不可靠的根源;應(yīng)用程序代碼是。
微服務(wù)的另一個(gè)問題
使用整體,開發(fā)人員可以編寫以二進(jìn)制方式進(jìn)行狀態(tài)更改的代碼。事情發(fā)生了或沒有發(fā)生。借助微服務(wù),世界狀況分布在不同的服務(wù)器上?,F(xiàn)在,更改應(yīng)用程序狀態(tài)需要同時(shí)更新不同的數(shù)據(jù)庫(kù)。這引入了一種可能性,即一個(gè)數(shù)據(jù)庫(kù)將成功更新,而其他數(shù)據(jù)庫(kù)可能已關(guān)閉,從而使您陷入不一致的中間狀態(tài)。但是,由于服務(wù)是橫向可伸縮性的唯一解決方案,因此開發(fā)人員別無(wú)選擇。
在服務(wù)之間分配狀態(tài)的根本問題是,對(duì)外部服務(wù)的每次調(diào)用都是可用性骰子。開發(fā)人員當(dāng)然可以選擇忽略代碼中的問題,并假定他們調(diào)用的每個(gè)外部依賴關(guān)系將始終成功。但是,如果忽略它,則意味著這些下游依賴項(xiàng)之一可能會(huì)在沒有警告的情況下關(guān)閉應(yīng)用程序。結(jié)果,開發(fā)人員被迫改編他們現(xiàn)有的整體時(shí)代代碼,以添加檢查以檢查操作是否在事務(wù)中間失敗的檢查。在下面的代碼中,開發(fā)人員必須不斷從即席myDB存儲(chǔ)中檢索最后記錄的狀態(tài),以避免潛在的競(jìng)爭(zhēng)情況。不幸的是,即使采用這種實(shí)施方式,仍然存在競(jìng)賽條件。如果在不更新myDB的情況下更改了帳戶狀態(tài),則存在不一致的余地。
public void transferWithoutTemporal(
String fromId,
String toId,
String referenceId,
double amount,) {
boolean withdrawDonePreviously = myDB.getWithdrawState(referenceId);
if (!withdrawDonePreviously) {
account.withdraw(fromAccountId, referenceId, amount);
myDB.setWithdrawn(referenceId);
}
boolean depositDonePreviously = myDB.getDepositState(referenceId);
if (!depositDonePreviously) {
account.deposit(toAccountId, referenceId, amount);
myDB.setDeposited(referenceId);
}}
不幸的是,編寫無(wú)錯(cuò)誤的代碼是不可能的,并且通常,代碼越復(fù)雜,發(fā)生錯(cuò)誤的可能性就越大。 如您所料,處理“中間”的代碼不僅復(fù)雜,而且令人費(fèi)解。 某些可靠性總比沒有強(qiáng)。因此,開發(fā)人員被迫編寫此固有的錯(cuò)誤代碼來(lái)維持最終用戶的體驗(yàn)。 這不僅耗費(fèi)了開發(fā)人員的時(shí)間和精力,而且使雇主付出了很多錢。 盡管微服務(wù)非常適合擴(kuò)展,但它們卻以開發(fā)人員的樂趣和生產(chǎn)力以及應(yīng)用程序的可靠性為代價(jià)。
數(shù)以百萬(wàn)計(jì)的開發(fā)人員每天都在浪費(fèi)時(shí)間來(lái)重新發(fā)明一種最創(chuàng)新的輪子,即可靠性樣板代碼。 當(dāng)前使用微服務(wù)的方法根本不能反映現(xiàn)代應(yīng)用程序?qū)煽啃院涂缮炜s性的要求。
因此,現(xiàn)在是我們向您介紹我們的解決方案的部分。需要說明的是,Stack Overflow并不認(rèn)可這一點(diǎn)。而且我們還沒有說它是完美的。我們想分享我們的想法,并聽聽您的想法。有什么比Stack更好的地方獲得改進(jìn)代碼反饋的呢?
直到今天,還沒有一種解決方案使開發(fā)人員能夠使用微服務(wù)而不會(huì)遇到我上面提到的這些問題。您可以測(cè)試和模擬故障狀態(tài),編寫代碼以預(yù)測(cè)故障,但是這些問題仍然會(huì)發(fā)生。我們相信Temporal解決了這個(gè)問題。 Temporal是一個(gè)開源(麻省理工學(xué)院,沒有惡作?。┑?,有狀態(tài)的,微服務(wù)編排運(yùn)行時(shí)。
Temporal具有兩個(gè)主要組件:由您選擇的數(shù)據(jù)庫(kù)提供支持的有狀態(tài)后端層,以及受支持的語(yǔ)言之一的客戶端框架。應(yīng)用程序是使用客戶端框架和簡(jiǎn)單的舊代碼構(gòu)建的,該代碼在代碼運(yùn)行時(shí)自動(dòng)將狀態(tài)更改保持在后端。您可以自由使用在構(gòu)建任何其他應(yīng)用程序時(shí)將依賴的依賴項(xiàng),庫(kù)和構(gòu)建鏈。需要明確的是,后端本身是高度分布式的,因此這不是J2EE 2.0的情況。實(shí)際上,后端的分布式性質(zhì)正是實(shí)現(xiàn)幾乎無(wú)限水平縮放的原因。 Temporal旨在為應(yīng)用程序?qū)犹峁┮恢滦?,?jiǎn)單性和可靠性,就像Docker,Kubernetes和無(wú)服務(wù)器對(duì)基礎(chǔ)架構(gòu)所做的那樣。
Temporal為編排微服務(wù)提供了許多高度可靠的機(jī)制,但是最重要的是狀態(tài)保存。狀態(tài)保存是一種臨時(shí)功能,它使用事件源自動(dòng)將正在運(yùn)行的應(yīng)用程序中的所有有狀態(tài)更改持久化。就是說,如果運(yùn)行您的Temporal工作流程功能的計(jì)算機(jī)崩潰了,該代碼將自動(dòng)在另一臺(tái)計(jì)算機(jī)上恢復(fù),就像從未發(fā)生崩潰一樣。這甚至包括局部變量,線程和其他特定于應(yīng)用程序的狀態(tài)。比喻,了解此功能的工作原理的最佳方法是。作為當(dāng)今的開發(fā)人員,您很可能依賴版本控制SVN(它是OG Git)來(lái)跟蹤對(duì)代碼所做的更改。關(guān)于SVN的事情是,它不會(huì)在您進(jìn)行每次更改后就對(duì)應(yīng)用程序的全面狀態(tài)進(jìn)行快照。 SVN的工作方式是僅存儲(chǔ)新文件,然后引用現(xiàn)有文件,從而避免重復(fù)它們。對(duì)于運(yùn)行應(yīng)用程序的有狀態(tài)歷史記錄,Temporal類似于SVN(再次大致類推)。只要您的代碼修改了應(yīng)用程序狀態(tài),Temporal就會(huì)以容錯(cuò)的方式自動(dòng)存儲(chǔ)所做的更改(而不是結(jié)果)。這意味著Temporal不僅可以還原崩潰的應(yīng)用程序,還可以將它們回滾,分叉等等。結(jié)果是,開發(fā)人員不再需要在基礎(chǔ)服務(wù)器可能發(fā)生故障的前提下構(gòu)建應(yīng)用程序。
作為開發(fā)人員,此功能就像從鍵入每個(gè)字母后手動(dòng)保存(ctrl-s)文檔到使用Google文檔在云中自動(dòng)保存一樣。不僅僅是您不再手動(dòng)保存文件,而且不再有與該文檔關(guān)聯(lián)的機(jī)器。狀態(tài)保存意味著開發(fā)人員編寫的微服務(wù)最初所編寫的繁瑣的樣板代碼要少得多。這也意味著不再需要臨時(shí)基礎(chǔ)結(jié)構(gòu)(如獨(dú)立隊(duì)列,緩存和數(shù)據(jù)庫(kù))。這樣可以減少操作負(fù)擔(dān)和添加新功能的開銷。這也使新員工的入職變得更加容易,因?yàn)樗麄儾辉傩枰黾踊靵y且特定于域的狀態(tài)管理代碼。
狀態(tài)保存也以另一種形式出現(xiàn):“耐用計(jì)時(shí)器”。耐用計(jì)時(shí)器是開發(fā)人員通過Workflow.sleep命令利用的容錯(cuò)機(jī)制。通常,Workflow.sleep函數(shù)與語(yǔ)言本機(jī)sleep命令完全一樣。但是,有了Workflow.sleep,無(wú)論多長(zhǎng)時(shí)間,您都可以安全地睡眠任何時(shí)間。有許多Temporal用戶,其工作流程休眠了數(shù)周甚至數(shù)年。為此,Temporal服務(wù)在基礎(chǔ)數(shù)據(jù)存儲(chǔ)區(qū)中保留持久性計(jì)時(shí)器,并跟蹤何時(shí)需要恢復(fù)相應(yīng)的代碼。同樣,即使底層服務(wù)器崩潰(或您只是將其關(guān)閉),當(dāng)打算觸發(fā)計(jì)時(shí)器時(shí),代碼也會(huì)在可用的計(jì)算機(jī)上恢復(fù)。睡眠工作流不會(huì)消耗資源,因此您可以擁有數(shù)百萬(wàn)個(gè)睡眠工作流,而開銷卻可以忽略不計(jì)。這看起來(lái)似乎非常抽象,所以這里是一個(gè)有效的臨時(shí)代碼示例:
public class SubscriptionWorkflowImpl implements SubscriptionWorkflow {
private final SubscriptionActivities activities =
Workflow.newActivityStub(SubscriptionActivities.class);
public void execute(String customerId) {
activities.onboardToFreeTrial(customerId);
try {
Workflow.sleep(Duration.ofDays(180));
activities.upgradeFromTrialToPaid(customerId);
while (true) {
Workflow.sleep(Duration.ofDays(30));
activities.chargeMonthlyFee(customerId);
}
} catch (CancellationException e) {
activities.processSubscriptionCancellation(customerId);
}
}
}
除了保留狀態(tài)外,Temporal還提供了一套用于構(gòu)建可靠應(yīng)用程序的機(jī)制?;顒?dòng)功能是從工作流中調(diào)用的,但是活動(dòng)中運(yùn)行的代碼不是有狀態(tài)的。盡管它們不是有狀態(tài)的,但活動(dòng)具有自動(dòng)重試,超時(shí)和心跳的功能。活動(dòng)對(duì)于封裝可能失敗的代碼非常有用。例如,假設(shè)您的應(yīng)用程序依賴于通常不可用的銀行的API。對(duì)于傳統(tǒng)的應(yīng)用程序,您將需要使用大量try / catch語(yǔ)句,重試邏輯和超時(shí)來(lái)包裝調(diào)用bank API的所有代碼。但是,如果您在活動(dòng)中調(diào)用銀行API,那么所有這些東西都是開箱即用的,這意味著如果調(diào)用失敗,則將自動(dòng)重試該活動(dòng)。重試非常好,但是有時(shí)不可靠的服務(wù)是您擁有的服務(wù),因此您希望避免DDoSing。因此,活動(dòng)調(diào)用還支持超時(shí),該超時(shí)由持久計(jì)時(shí)器支持。這意味著您可以在重試嘗試之間讓活動(dòng)等待數(shù)小時(shí),數(shù)天或數(shù)周。這對(duì)于需要成功但無(wú)需擔(dān)心代碼執(zhí)行速度如何的代碼特別有用。
Temporal的另一個(gè)強(qiáng)大方面是它對(duì)正在運(yùn)行的應(yīng)用程序提供的可見性。 可見性API提供了類似于SQL的界面,可從任何工作流程(正在運(yùn)行或以其他方式)查詢?cè)獢?shù)據(jù)。 也可以直接在工作流程中定義和更新自定義元數(shù)據(jù)值。 可見性API非常適合Temporal操作員和開發(fā)人員,尤其是在開發(fā)過程中進(jìn)行調(diào)試時(shí)。 可見性甚至支持將批處理操作應(yīng)用于查詢結(jié)果。 例如,您可以向與您的創(chuàng)建時(shí)間查詢>昨天匹配的所有工作流發(fā)送終止信號(hào)。 Temporal還支持同步獲取功能,該功能使開發(fā)人員可以在運(yùn)行的實(shí)例中獲取本地工作流變量的值。 這有點(diǎn)像您的IDE中的調(diào)試器適用于生產(chǎn)應(yīng)用。 例如,可以在以下代碼的運(yùn)行實(shí)例中獲得greeting的值:
public static class GreetingWorkflowImpl implements GreetingWorkflow {
private String greeting;
@Override
public void createGreeting(String name) {
greeting = "Hello " + name + "!";
Workflow.sleep(Duration.ofSeconds(2));
greeting = "Bye " + name + "!";
} @Override
public String queryGreeting() {
return greeting;
}
}
結(jié)論
微服務(wù)很棒,但是開發(fā)人員和企業(yè)為使用微服務(wù)付出的代價(jià)是生產(chǎn)力和可靠性。 Temporal旨在通過提供一種為開發(fā)人員支付微服務(wù)稅的環(huán)境來(lái)解決此問題。 狀態(tài)保留,自動(dòng)重試失敗的呼叫以及開箱即用的可見性只是Temporal提供的使開發(fā)微服務(wù)合理化的一些基本要素。
作者介紹
熱門博客推薦