漫談荤懂!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

<article class="syl-page-article syl-device-pc tt-article-content font_m" style="box-sizing: border-box; display: block; font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; line-height: 1.75; margin-bottom: 24px; font-size: 16px; color: rgb(34, 34, 34); overflow-wrap: break-word; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">

本文要點

  • 微服務(wù)遷移不是一個小更改趋急。你必須搞清楚它是否真的能解決你的問題,否則你可能會創(chuàng)建一個會殺死你的势誊、亂糟糟的實體。
  • 單體有不同類型谣蠢,其中一些可能是有效的粟耻,足以滿足業(yè)務(wù)需求。單體不是一個應(yīng)該被殺死的敵人眉踱。
  • 微服務(wù)關(guān)乎獨立部署挤忙。有一些分解和增量更改模式可以幫助你評估并遷移到微服務(wù)架構(gòu)。
  • 當(dāng)你開始使用微服務(wù)時谈喳,你會意識到隨之而來的是一系列非常復(fù)雜的挑戰(zhàn)册烈。所以不應(yīng)該將微服務(wù)作為默認(rèn)選擇。你得仔細(xì)考慮它們是否適合你。

在倫敦 QCon 大會上赏僧,我談到了 單體分解模式以及我們?nèi)绾芜_(dá)成微服務(wù) 大猛。我喜歡把它們比作令人討厭的水母,因為它們是一種亂糟糟的實體淀零,會刺痛甚至可能殺死我們挽绩。這在通常的企業(yè)微服務(wù)遷移中很常見。

許多組織正在經(jīng)歷某種數(shù)字化轉(zhuǎn)型驾中。隨便看下當(dāng)前的任何數(shù)字化轉(zhuǎn)型唉堪,我們都會發(fā)現(xiàn)微服務(wù)的身影。我們知道肩民,數(shù)字化轉(zhuǎn)型是一件大事唠亚,因為現(xiàn)在任何機場候機室都有大型 IT 咨詢公司的廣告推銷數(shù)字化轉(zhuǎn)型,包括德勤持痰、DXC灶搜、埃森哲等公司。微服務(wù)非常流行共啃。

不過占调,在談及微服務(wù)時,我關(guān)注的是結(jié)果移剪,而不是我們用來實現(xiàn)它們的技術(shù)究珊。我們選擇微服務(wù)架構(gòu)的原因有很多,但我反復(fù)提到的一個原因是其獨立部署的屬性纵苛。有一個功能剿涮,一個我們想要改變系統(tǒng)行為的更改。我們想要盡快實現(xiàn)這個更改攻人。

漫談取试!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 1:微服務(wù)方法示意圖

將微服務(wù)架構(gòu)與單體做下比較。我們認(rèn)為怀吻,單體是一個單一的瞬浓、無法透視地塊,我們無法對它作出任何更改蓬坡。單體被認(rèn)為是我們生活中最糟糕的東西猿棉,是難以擺脫的沉重負(fù)擔(dān)。我認(rèn)為這非常不公平屑咳。最終萨赁,“單體”一詞在過去兩三年里取代了我們之前使用的“遺留問題(legacy)”一詞。這是一個根本性問題兆龙,因為有些人開始將單體視為遺留問題杖爽,是需要移除的東西。我認(rèn)為這非常不合適。

單體的類型

單體有多種形式和規(guī)模慰安。在討論單體應(yīng)用程序時腋寨,我主要是將單體作為部署單元來討論⌒喊铮考慮下經(jīng)典的單體精置,它是將所有代碼打包在單個進(jìn)程中。它可能是 Tomcat 中的一個 WAR 文件锣杂,也可能是一個基于 PHP 的應(yīng)用程序脂倦,所有代碼都打包在一個可部署單元中,該單元會與數(shù)據(jù)庫通信元莫。

這種單體類型可以看作是一個簡單的分布式系統(tǒng)赖阻。分布式系統(tǒng)是由多臺通過非本地網(wǎng)絡(luò)相互通信的計算機組成的系統(tǒng)。在這種情況下踱蠢,所有的代碼都打包在一個進(jìn)程中火欧,重要的是,所有數(shù)據(jù)都保存在于一個運行在不同機器上的大型數(shù)據(jù)庫中茎截。把所有數(shù)據(jù)都放在一個數(shù)據(jù)庫中苇侵,將來會給我們帶來很多痛苦。

漫談企锌!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 2:模塊化單體

我們還可以考慮下單進(jìn)程單體的一種變體榆浓,稱為模塊化單體。這種模塊化單體使用了關(guān)于結(jié)構(gòu)化編程的前沿思想(誕生于 20 世紀(jì) 70 年代初撕攒,幾十年后陡鹃,我們中的一些人仍在努力掌握這些思想!)抖坪。如圖 2 所示萍鲸,我們將單進(jìn)程單體應(yīng)用程序分解為模塊。如果我們正確地劃分了模塊邊界擦俐,我們就可以獨立地處理每個模塊脊阴。但是,本質(zhì)上蚯瞧,部署過程仍然是靜態(tài)鏈接的方法:我們必須鏈接好所有模塊才能進(jìn)行部署嘿期。比如一個 Ruby 應(yīng)用程序,它有許多 GEM 文件状知、NuGet 包括通過 Maven 組裝的 JAR 文件組成。

雖然我們?nèi)匀皇菃误w部署孽查,但模塊化單體有一些顯著的好處饥悴。把代碼分解成模塊確實可以讓我們在一定程度上獨立地完成工作。它可以方便不同的團(tuán)隊一起工作,并處理系統(tǒng)的不同方面西设。我認(rèn)為這是一個被嚴(yán)重低估的選項瓣铣。這其中存在的問題是,人們往往不善于定義模塊邊界——更確切地說贷揽,即使他們擅長定義模塊邊界棠笑,他們也不擅長保持這些邊界。遺憾的是禽绪,結(jié)構(gòu)化編程或模塊化的概念往往會遭遇“泥球”問題蓖救。

對于我服務(wù)過的許多組織來說,使用模塊化單體比使用微服務(wù)架構(gòu)會更好印屁。在過去的三年里循捺,我對我一半的客戶說過:“微服務(wù)不適合你⌒廴耍“有些客戶甚至聽了我的話从橘。對于它們中的許多來說,一種可以定義模塊邊界的好方法就足以滿足它們的需要础钠。他們可以得到一個比較簡單的分布式系統(tǒng)恰力,以及一定程度上獨立、自主地工作旗吁。

漫談踩萎!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 3:模塊化單體的一種變體

模塊化單體也有變體。圖 3 看起來有點奇怪阵漏,但這是我多次提出的建議驻民,特別是對于初創(chuàng)公司,我通常認(rèn)為履怯,他們最好不要著急上微服務(wù)回还。如圖 3 所示,我們使用了模塊化單體叹洲,并將后臺單個的整體數(shù)據(jù)庫進(jìn)行了分解柠硕,這樣就可以單獨存儲和管理每個模塊的數(shù)據(jù)。

雖然這看起來很奇怪运提,但歸根結(jié)底這是一種對沖架構(gòu)蝗柔。人們認(rèn)識到,分解單體架構(gòu)時最困難的工作之一是處理數(shù)據(jù)層民泵。如果我們能提前設(shè)計好與這些模塊相關(guān)聯(lián)的獨立數(shù)據(jù)庫癣丧,以后遷移到單獨的微服務(wù)就會更容易。如果我正在處理模塊 C栈妆,我對與模塊 C 關(guān)聯(lián)的數(shù)據(jù)具有完全的所有權(quán)和控制權(quán)胁编。當(dāng)模塊 C 變成一個單獨的服務(wù)時厢钧,遷移它應(yīng)該會更容易。

當(dāng)我還在 ThoughtWorks 工作時嬉橙,我的一位老同事 Peter Gillard-Moss 第一次向我展示了這種模式早直。這是他為我們正在開發(fā)的一個內(nèi)部系統(tǒng)設(shè)計的。他說市框,“我覺得這能行霞扬。我們不確定我們是否想要提供服務(wù),所以也許它應(yīng)該是一個單體枫振∮髌裕”

我說,“試一試蒋得〖都埃看看會發(fā)生什么《钛茫“大約 6 年過去了饮焦,去年我和 Peter 談過,ThoughtWorks 仍然沒有改變架構(gòu)窍侧。它仍然運行得很歡快县踢。他們讓不同的人處理不同的模塊,即使是在這個級別上將數(shù)據(jù)分離開來伟件,也給他們帶來了巨大的好處硼啤。

圖 4:分布式單體

現(xiàn)在,我們來看看最糟糕的單體——分布式單體斧账。我們的應(yīng)用程序代碼現(xiàn)在運行在彼此通信的獨立進(jìn)程上谴返。不管出于什么原因,我們都必須將整個系統(tǒng)作為一個單元同步部署咧织。經(jīng)常嗓袱,這種情況的出現(xiàn)是因為我們弄錯了服務(wù)邊界。我們將業(yè)務(wù)邏輯胡亂地放在了不同的層上习绢。我們沒有遵從關(guān)于耦合和內(nèi)聚的要點渠抹,現(xiàn)在,我們的結(jié)賬邏輯分布在服務(wù)棧中 15 個不同的地方闪萄。我們要做任何工作梧却,都必須協(xié)調(diào)多個團(tuán)隊。如果組織中存在大量的橫切更改败去,通常表明組織邊界或服務(wù)邊界定義的不對放航。

分布式單體的問題在于,它本質(zhì)上是一個更加分布式的系統(tǒng)圆裕,但是對于所有相關(guān)的設(shè)計广鳍、運行和操作挑戰(zhàn)缺菌,我們?nèi)匀恍枰獑误w需要的那些協(xié)調(diào)活動。我想在線部署搜锰,但我不能。我必須等你完成更改耿战,但你也完不成蛋叼,因為你在等別人。現(xiàn)在剂陡,我們一致同意:“好吧狈涮,7 月 5 日,我們將一起上線鸭栖。每個人都準(zhǔn)備好了嗎歌馍?三、二晕鹊、一松却,部署〗埃“當(dāng)然晓锻,一切都很順利。對于這類系統(tǒng)飞几,我們從來沒有遇到過任何問題砚哆。

如果一個組織有一位全職的發(fā)布協(xié)調(diào)經(jīng)理或這方面的其他職位,那么他們可能有一個分布式單體屑墨。協(xié)調(diào)分布式系統(tǒng)的同步部署一點都不好玩躁锁。我們最終會付出更高的更改成本。部署的范圍會大很多卵史,可能會有更多的地方出錯战转。這種難以避免的協(xié)調(diào)活動,不只存在于發(fā)布活動中程腹,而且存在于一般部署活動中匣吊。

稍微看下精益生產(chǎn)的內(nèi)容就會發(fā)現(xiàn),減少交接是優(yōu)化生產(chǎn)力的關(guān)鍵寸潦。等待別人為我做點什么只會產(chǎn)生浪費色鸳。這會導(dǎo)致生產(chǎn)力瓶頸。為了更快地交付軟件见转,減少交接和協(xié)調(diào)非常關(guān)鍵命雀。遺憾的是,分布式單體往往會創(chuàng)造出不得不進(jìn)行協(xié)調(diào)的環(huán)境斩箫。

有時吏砂,我們的問題不在于服務(wù)邊界在哪里撵儿。有時,它完全是始于我們開發(fā)軟件的方式狐血。有些人從根本上誤解了發(fā)布序列淀歇。發(fā)布序列一直被認(rèn)為是一種治療性的發(fā)布技術(shù),而不是一種進(jìn)取性的活動匈织。我們會選擇像發(fā)布序列這樣的東西來幫助組織轉(zhuǎn)向持續(xù)交付浪默。發(fā)布序列的概念以定期為基礎(chǔ),也許每四周缀匕,軟件的所有部分已經(jīng)準(zhǔn)備就緒纳决。如果軟件還沒有準(zhǔn)備好,它就會被推遲到下一個發(fā)布序列乡小。對許多組織來說阔加,這是向前邁出了一步。我們應(yīng)該縮小發(fā)布序列之間的間隔满钟,最終完全消除胜榔。然而,有太多的組織在采用了發(fā)布序列就再也沒有繼續(xù)前進(jìn)湃番。

當(dāng)若干團(tuán)隊都朝著同一個發(fā)布序列而努力時苗分,所有已經(jīng)準(zhǔn)備好的軟件都會在這個發(fā)布序列中交付——突然之間,我們會一次性部署大量的服務(wù)牵辣。這是真正的問題所在摔癣。當(dāng)實踐發(fā)布序列時,最重要的一件事是纬向,至少要將這些發(fā)布序列分解择浊,使它們成為團(tuán)隊發(fā)布序列。允許不同的團(tuán)隊安排自己的發(fā)布序列逾条。最終琢岩,我們應(yīng)該拋棄這些序列。它們應(yīng)該只是邁向持續(xù)交付的一個步驟师脂。

遺憾的是担孔,一些營銷敏捷的優(yōu)秀成果已經(jīng)將發(fā)布序列作為交付軟件的最終方式。我們知道他們已經(jīng)這么做了吃警,因為許多公司組織里掛著的 SAFe 圖解上都印著“發(fā)布序列”的字樣糕篇。這不是好事。不管是對于 SAFe酌心,還是你遇到的任何其他問題拌消,發(fā)布序列始終都是一種補救技術(shù),是自行車的輔助輪安券。我們應(yīng)該向著持續(xù)交付繼續(xù)前進(jìn)。問題是,如果我們使用這些發(fā)布序列時間太長镊折,最終的架構(gòu)就會是一個分布式單體,因為我們已經(jīng)習(xí)慣了將所有服務(wù)部署在一起铝阐。要注意這一點。這可能不會在一夜之間發(fā)生铐拐。我們可以從支持獨立部署的架構(gòu)開始饰迹,但如果我們使用發(fā)布序列太久,我們的架構(gòu)就會開始圍繞這些發(fā)布實踐聚合在一起余舶。

歸根結(jié)底,分布式單體是一個問題锹淌,因為它同時具有分布式系統(tǒng)的所有復(fù)雜性和單個部署單元的缺點匿值。我們應(yīng)該跨越它,尋找更好的工作方式赂摆。分布式單體是一件很棘手的事情挟憔,關(guān)于如何處理這種情況的建議已經(jīng)有很多。有時烟号,正確的答案是將其合并回單進(jìn)程單體绊谭。但是,如果現(xiàn)如今我們有一個分布式單體汪拥,最好的辦法是弄清楚為什么我們會有這樣的單體达传,并且在添加任何新服務(wù)之前,著手讓架構(gòu)中的某些部分可以獨立部署迫筑。在這種情況下宪赶,添加新服務(wù)很可能會增加我們開展工作的難度。

如何將單體遷移到微服務(wù)架構(gòu)

我們使用微服務(wù)架構(gòu)是因為它具有獨立部署的特性脯燃。我們希望能夠在不改變其他任何東西的情況下將服務(wù)的更改部署到產(chǎn)品中搂妻。這是微服務(wù)的黃金法則。在演講或文章中辕棚,這似乎很容易欲主。在現(xiàn)實生活中,要做到這一點要困難得多逝嚎,尤其是考慮到大多數(shù)人并非從零開始扁瓢。絕大多數(shù)人都覺得他們的系統(tǒng)太大了,想把它分成更小的部分补君。他們想知道從哪里開始涤妒。

領(lǐng)域驅(qū)動設(shè)計(DDD)有一些很好的方法可以幫助我們找出服務(wù)邊界。當(dāng)與研究微服務(wù)遷移的組織合作時赚哗,我們通常是從在現(xiàn)有的單體應(yīng)用程序架構(gòu)上執(zhí)行 DDD 建模練習(xí)開始她紫。我們這樣做是為了弄清楚單體應(yīng)用內(nèi)部發(fā)生了什么硅堆,并從業(yè)務(wù)域的角度確定工作單元。

盡管單體看起來像一個巨大的盒子贿讹,但當(dāng)我們應(yīng)用 DDD渐逃,并將邏輯模型投射到該單體上時,我們意識到民褂,其內(nèi)部被組織成訂單管理茄菊、PDF 渲染、客戶端通知等內(nèi)容赊堪。雖然代碼可能沒有圍繞這些概念進(jìn)行組織面殖,但從用戶或業(yè)務(wù)領(lǐng)域模型的角度來看,這些概念存在于代碼中哭廉。這些業(yè)務(wù)領(lǐng)域的邊界(DDD 中通常稱為“有界上下文”)就成為我們分解的單元脊僚,原因我這里就不展開討論了。

漫談遵绰!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 5:找出單體中的分解和依賴項單元

首先要做的是問下從哪里開始辽幌,什么事情可以優(yōu)先處理,我們的工作單元是什么椿访。在圖 5 所示的初始單體中乌企,我們有訂單管理、發(fā)票和通知成玫。DDD 建模練習(xí)將使我們了解它們之間的關(guān)系加酵。但愿我們能得出一個有向無環(huán)圖,來描述這些不同功能之間的依賴關(guān)系哭当。(如果我們得到是一個依賴關(guān)系的循環(huán)圖虽画,我們就得做更多的工作。)我們可以看到荣病,在這個單體中码撰,有很多東西都依賴于向客戶發(fā)送通知的能力。那似乎是領(lǐng)域的核心部分个盆。

檢查點:對于我遇到的問題脖岛,微服務(wù)是合適的解決方案嗎?

我們可以開始問問題了颊亮,比如我們應(yīng)該先提取什么柴梆。我可以完全從這個角度來看問題。我們可能會看到终惑,通知被很多功能使用——如果微服務(wù)更好绍在,那么提取系統(tǒng)中很多部分都在使用的東西將使更多的東西變得更好。也許我們應(yīng)該從那里開始。但是偿渡,看看所有的入站依賴關(guān)系臼寄。因為有太多的部分需要通知功能,所以我們很難將其從現(xiàn)有的單體架構(gòu)中剝離出來溜宽。在這個單體系統(tǒng)中吉拳,像結(jié)賬或訂單管理之類的概念似乎更加獨立。它們可能是更容易分解的東西适揉。決定從哪一部分開始留攒,從根本上講是一種漸進(jìn)式分解方法。

首先要記住嫉嘀,單體并不是敵人炼邀。我希望大家都好好地思考一下。人們將任何單體系統(tǒng)都視為問題剪侮。在過去幾年里拭宁,我看到的最令人擔(dān)憂的事情之一是,微服務(wù)現(xiàn)在似乎成了許多人的默認(rèn)選擇票彪。

有人可能記得一句老話:“沒有人會因為購買 IBM 產(chǎn)品而被解雇〔皇ǎ”意思是降铸,因為其他人都在買 IBM 產(chǎn)品,你也可以買——如果你買的東西不適合你摇零,那也不是你的錯推掸,因為大家都在這么做。你沒必要冒險∽そ觯現(xiàn)在每個人都在做微服務(wù)谅畅,我們也面臨同樣的問題。每個人都吵著要做微服務(wù)噪服。這對我很有好處:我寫關(guān)于該主題的書毡泻,但對你可能不是好事。

從根本上說粘优,這取決于我們想要解決什么問題仇味。我們想要達(dá)到而在當(dāng)前的架構(gòu)下無法達(dá)到的目標(biāo)是什么?也許微服務(wù)是答案雹顺,或者其他什么東西才是答案丹墨。理解我們想要達(dá)到的目標(biāo)至關(guān)重要,否則嬉愧,我們將很難確定如何遷移我們的系統(tǒng)贩挣。我們正在做的事情將改變我們分解系統(tǒng)的方式,以及我們?nèi)绾未_定工作的優(yōu)先級。

微服務(wù)遷移不像一個開關(guān)王财,沒有開/關(guān)切換卵迂。這更像是轉(zhuǎn)動一個旋鈕。在采用微服務(wù)的過程中搪搏,我們轉(zhuǎn)動一下旋鈕狭握,增加一兩個服務(wù)。我們想看看一個服務(wù)如何發(fā)揮作用疯溺,它是否提供了我們需要的東西论颅,是否解決了我們的問題。如果是囱嫩,而且我們也滿意恃疯,我們就可以繼續(xù)轉(zhuǎn)動旋鈕。

不過墨闲,我看到很多人都會轉(zhuǎn)動旋鈕今妄,增加 500 項服務(wù),然后插上耳機鸳碧,檢查音量盾鳞。這是讓鼓膜破裂的好方法。我們不知道我們將要面對什么問題瞻离,那些問題在開發(fā)人員的筆記本電腦上碰不到腾仅。它們會在生產(chǎn)環(huán)境中出現(xiàn)。當(dāng)我們從提供一個單體系統(tǒng)轉(zhuǎn)為一次性提供 500 個服務(wù)時套利,所有的問題都會同時出現(xiàn)推励。不管我們最后是提供一項、兩項還是五項服務(wù)肉迫,還是像 Monzo 那樣擁有 800 項或 1500 項服務(wù)验辞,我們都必須從一個小轉(zhuǎn)變開始。我們需要選擇一些服務(wù)來啟動遷移喊衫。讓它們在生產(chǎn)環(huán)境中運行跌造,積累經(jīng)驗,并盡快把這種經(jīng)驗付諸實踐族购。通過逐步調(diào)整鼻听,以漸進(jìn)的方式創(chuàng)建和發(fā)布新的微服務(wù),我們可以更好地發(fā)現(xiàn)和處理出現(xiàn)的問題联四。每個項目將要面對的問題都會有所不同撑碴,這取決于許多不同的因素。

我們想要從單體系統(tǒng)中提取一些功能朝墩,讓它與單體系統(tǒng)的剩余部分通信并集成醉拓,并且要盡快完成伟姐。我們不想再進(jìn)行大爆炸式的重寫了。我們過去是每年向用戶發(fā)布軟件亿卤,因為有一個為期 12 個月的窗口期愤兵,所以我們可以這樣說:“現(xiàn)有的系統(tǒng)太糟糕了,現(xiàn)在已經(jīng)無法使用了排吴,但是我們還有 12 個月的時間來發(fā)布下一個版本秆乳。如果我們努力,完全可以重寫系統(tǒng)钻哩,我們不會再犯過去犯過的錯誤屹堰,現(xiàn)有的功能一個都不會少,而且還會有更多的新功能街氢,一切都會很好扯键。”

當(dāng)每年發(fā)布一次軟件時珊肃,我們從來沒有那樣做荣刑。當(dāng)人們期望每月、每周或每天發(fā)布軟件時伦乔,我不知道該如何證明其合理性厉亏。套用 Martin Fowler 的話來說,“ 如果你要進(jìn)行大爆炸式的重寫烈和,你唯一能確定的就是大爆炸爱只。 ”我喜歡動作片中的爆炸場面,但不喜歡我的 IT 項目里出現(xiàn)這種情況斥杜。我們需要從不同的角度思考如何做出這些更改虱颗。

部署來自單體的第一個微服務(wù)

我是架構(gòu)增量演進(jìn)的忠實擁護(hù)者沥匈。我們不應(yīng)該認(rèn)為我們的架構(gòu)是一成不變的蔗喂。我們需要有一些模式來幫助我們以漸進(jìn)的方式向微服務(wù)轉(zhuǎn)變。

我們首先看下應(yīng)用程序模式 Strangler Fig高帖,它以一種植物命名缰儿,這種植物在樹冠上生根,然后卷須向下纏繞在樹干上散址。絞殺榕(strangler fig)靠自身無法爬到林冠層以獲得足夠的陽光乖阵,所以它不像普通樹木一樣從一棵小樹苗慢慢長大,而是包裹在現(xiàn)有的植物體上预麸。它依賴于現(xiàn)有的樹的高度和力量瞪浸。隨著時間的推移,這些絞殺榕成長起來吏祸,變得越來越大对蒲,能夠獨立生存了。如果下面的樹死了,腐爛了蹈矮,就只剩下絞殺榕和一根空心的柱子砰逻。這些東西看起來就像蠟滴在其他樹上——看起來真的很令人不安。

但是作為應(yīng)用程序遷移策略的一種模式泛鸟,這種思想是有用的蝠咆。我們找一個現(xiàn)有的系統(tǒng)(它完成我們想要它做的所有事情,即現(xiàn)有的單體應(yīng)用程序)北滥,然后開始圍繞它封裝出我們的新系統(tǒng)刚操。在這里,就是我們的微服務(wù)架構(gòu)碑韵。實現(xiàn) Strangler Fig 應(yīng)用程序有兩個關(guān)鍵赡茸。第一個是資產(chǎn)捕獲,即確定把哪些功能遷移到微服務(wù)架構(gòu)的過程祝闻。然后我們需要進(jìn)行轉(zhuǎn)接占卧。以前對于單體應(yīng)用程序的調(diào)用得轉(zhuǎn)接到新功能上。如果功能沒有遷移联喘,調(diào)用就不需要轉(zhuǎn)接华蜒;非常簡單。

有些人對如何轉(zhuǎn)移功能感到困惑豁遭。如果我們真的夠幸運的話叭喜,也許可以簡單地復(fù)制代碼。如果結(jié)賬服務(wù)的代碼在單體代碼庫中一個叫“結(jié)賬”的漂亮盒子中蓖谢,我們就可以剪切并粘貼到新服務(wù)中捂蕴。我認(rèn)為,如果代碼庫是這種狀態(tài)闪幽,那你可能不需要任何幫助啥辨。更大的可能是,我們將不得不快速瀏覽系統(tǒng)盯腌,設(shè)法收集所有與結(jié)賬相關(guān)的代碼溉知。我們可能會做一些重構(gòu)前的活動。也許我們可以重用這些代碼腕够,但在這種情況下级乍,那將是復(fù)制粘貼,而不是剪切粘貼帚湘。我們想把這個功能留在這個單體應(yīng)用中玫荣,原因我將在后面討論。更常見的情況是大诸,人們會進(jìn)行一些重寫捅厂。

實現(xiàn) Strangler Fig 的方法有很多種材诽。讓我們來看一種簡單的方法。

假設(shè)我們有一個基于 HTTP 的單體系統(tǒng)恒傻。這可能是一個無頭應(yīng)用程序脸侥。我們可以在用戶界面的后臺使用 API Boundary 攔截調(diào)用。我們需要的是可以將調(diào)用重定向的東西盈厘,因此睁枕,我們將使用某種 HTTP 代理。對于這類架構(gòu)沸手,HTTP 協(xié)議非常有效外遇,這是因為它非常適合透明的重定向調(diào)用。通過 HTTP 發(fā)起的調(diào)用可以被轉(zhuǎn)接到許多不同的地方契吉。有很多軟件可以幫你做到這一點跳仿,而且非常簡單。

漫談捐晶!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 6:HTTP 代理攔截對單體的調(diào)用菲语,增加了一個網(wǎng)絡(luò)躍點

首先要做的是,在上游流量和下游單體系統(tǒng)之間放置一個代理惑灵,別的什么都不用做山上。我們將把這個代理部署到生產(chǎn)環(huán)境中。此時英支,它還沒有轉(zhuǎn)接任何調(diào)用佩憾。我們可以看下它在生產(chǎn)環(huán)境中是否有效。我們要擔(dān)心的一件事是網(wǎng)絡(luò)質(zhì)量干花,因為我們增加了一個網(wǎng)絡(luò)躍點妄帘。通常是直接調(diào)用單體系統(tǒng),但現(xiàn)在通過我們的代理池凄。在這種情況下抡驼,延遲是殺手。通過代理轉(zhuǎn)接只會給現(xiàn)有的調(diào)用增加幾毫秒的開銷——少于 10 毫秒就很棒修赞。如果額外增加一個網(wǎng)絡(luò)躍點增加了 200 毫秒的延遲婶恼,我們就需要暫停微服務(wù)遷移桑阶,因為我們還有其他需要首先解決的大問題柏副。

準(zhǔn)備好代理之后,我們接下來將處理新的結(jié)賬服務(wù)蚣录。我們將其部署到生產(chǎn)環(huán)境中割择。即使它功能還不全,也沒什么問題萎河,因為它還沒有被使用荔泳。我們要在腦海中將部署到生產(chǎn)環(huán)境和使用這兩個概念分開蕉饼。開始采用微服務(wù)后,我們希望定期地將功能部署到生產(chǎn)環(huán)境中玛歌,以確保我們的部署機制能夠正常工作昧港。在添加功能時,我們可以單獨測試新服務(wù)支子。我們還沒有把它發(fā)布給用戶创肥,但它已經(jīng)在生產(chǎn)環(huán)境中了。我們可以將它連接到我們的儀表板上值朋,確保日志聚合正常叹侄,或者做其他我們想做的事。

關(guān)鍵是我們只對一個服務(wù)進(jìn)行操作昨登。我們甚至可以將那個服務(wù)的提取過程分解為許多小步驟:創(chuàng)建服務(wù)框架趾代、實現(xiàn)方法、在生產(chǎn)環(huán)境中測試它丰辣,然后部署發(fā)布版本撒强。準(zhǔn)備就緒之后,當(dāng)我們認(rèn)為新實現(xiàn)已經(jīng)等同于舊系統(tǒng)時笙什,我們只需重新配置代理尿褪,將調(diào)用從舊的單體功能轉(zhuǎn)到新的微服務(wù)。

事到如今得湘,你可能認(rèn)為現(xiàn)在應(yīng)該從這個單體中刪除舊功能杖玲。先別這么做!如果新創(chuàng)建的微服務(wù)在生產(chǎn)環(huán)境中出現(xiàn)問題淘正,我們有一種非嘲诼恚快的補救技術(shù):我們只需還原代理配置,將流量轉(zhuǎn)到原功能所在的單體上鸿吆。不過囤采,要想實現(xiàn)這一點,我們必須考慮數(shù)據(jù)的作用——這點我們稍后討論惩淳。

我們希望蕉毯,將這個功能提取成微服務(wù)是一種真正的重構(gòu),改變代碼的結(jié)構(gòu)而不是行為思犁。在功能上代虾,微服務(wù)應(yīng)該等同于單體中的同一功能。我們應(yīng)該能夠在它們之間切換激蹲,直到微服務(wù)正常工作為止棉磨。

如果我們想要保留切換的能力,那么在遷移完成学辱、我們不再需要這種切換能力之前乘瓤,我們就不應(yīng)該添加新的功能或更改現(xiàn)有的功能环形。

在很多情況下,這項簡單的 Strangler Fig 技術(shù)都出奇的好衙傀。這個例子使用了 HTTP抬吟,但是我也看過使用 FTP 的情況。我已經(jīng)用消息攔截器做到了這一點统抬。我在上傳固定文件時就這么做了:我們插入固定文件拗军,在新服務(wù)中剔除我們希望去掉的內(nèi)容,然后把剩下的內(nèi)容傳遞下去蓄喇。

漫談发侵!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 7:微服務(wù)架構(gòu)的有向無環(huán)圖

使用“抽象分支“模式來逐步完成單體遷移

Strangler Fig 對于結(jié)賬或訂單管理等功能非常有效,這些功能在我們的調(diào)用堆棧中處于更高的位置妆偏,如圖 7 所示的依賴關(guān)系圖刃鳄。但是,進(jìn)入單體系統(tǒng)的調(diào)用沒有哪個是為了獲得像忠誠獎勵積分或給客戶發(fā)送通知這樣的能力钱骂。進(jìn)入單體的調(diào)用是“下訂單”或“付款”叔锐。只是作為這些操作的副作用,我們可能會獎勵積分或發(fā)送電子郵件见秽。因此愉烙,我們無法在單體系統(tǒng)的外圍攔截對忠誠獎勵積分或通知的調(diào)用。那是在單體系統(tǒng)內(nèi)部完成的解取。

假設(shè)我們要把通知功能提取出來步责。我們必須提取這塊功能,并用一種增量的方式攔截這些入站鏈接禀苦,這樣我們就不會破壞系統(tǒng)的其余部分蔓肯。

一種名為“抽象分支”的技術(shù)可以很好地完成這項工作。在基于主干的開發(fā)環(huán)境中振乏,抽象分支是一種經(jīng)常討論的模式蔗包,這是一種很好的軟件開發(fā)方式。在這種情況下中慧邮,抽象分支作為一種模式也很有用调限。我們在現(xiàn)有的單體系統(tǒng)中創(chuàng)建了一個空間,同一功能的兩種實現(xiàn)可以在其中共存误澳。在很多方面耻矮,這是 里氏替換原則 的一個例子。這是對完全相同的抽象做的一個獨立實現(xiàn)脓匿。對于本例淘钟,我們將從現(xiàn)有代碼中提取通知功能宦赠。

漫談陪毡!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 8:用于遷移到一個微服務(wù)的抽象分支

通知代碼散布在我們的系統(tǒng)中米母。我們要做的第一件事是為新服務(wù)收集所有這些代碼。我們將把服務(wù)隱藏在抽象點后面毡琉。我們希望結(jié)賬代碼和訂單代碼通過一個明確的抽象點來訪問這個功能铁瞒。起初,我們有一個通知抽象的實現(xiàn)——它封裝了單體中當(dāng)前所有與通知相關(guān)的功能桅滋。我們的所有調(diào)用——到 SMTP 庫慧耍、到 Twilio、發(fā)送 SMS——都被打包到這個實現(xiàn)中丐谋。

此時芍碧,我們所做的只是在代碼中創(chuàng)建了一個很好的抽象點。我們可以停了号俐。我們已經(jīng)厘清了我們的代碼庫泌豆,并使其更容易測試,這已經(jīng)是改進(jìn)了吏饿。這是一種很好的老式重構(gòu)踪危。我們也創(chuàng)造了一個機會來更改結(jié)賬或訂單使用的通知功能的實現(xiàn)。我們可以用幾天或幾周的時間來完成這項重構(gòu)工作猪落,同時做一些其他的事情贞远,比如實際發(fā)布特性。

接下來笨忌,我們開始創(chuàng)建通知服務(wù)的新實現(xiàn)蓝仲。這可以分成兩個部分。我們已經(jīng)在單體中實現(xiàn)了新接口官疲,但這只是調(diào)用另一部分(新建的通知微服務(wù))的客戶端代碼杂曲。部署這些實現(xiàn)很安全,因為它們還沒有被使用袁余。我們更頻繁地集成代碼擎勘,減少合并工作,并確保一切工作正常颖榜。

一旦單體內(nèi)部調(diào)用新服務(wù)的代碼和單體外部的通知服務(wù)可以正常工作棚饵,我們所需要做的就是切換我們正在使用的抽象實現(xiàn)。我們可以使用特性開關(guān)掩完、文本文件噪漾、專用工具,或者任何我們希望使用的方式且蓬。我們還沒有刪除舊功能欣硼,所以如果有問題,我們可以輕松切回舊功能恶阴。同樣诈胜,這個服務(wù)的遷移被分解成許多小步驟豹障,我們試圖通過所有這些步驟盡快將其部署到生產(chǎn)環(huán)境。

一切工作正常之后焦匈,我們就可以選擇清理代碼了血公。如果不再需要這個功能,我們可以刪除其特性標(biāo)識缓熟,甚或刪除舊代碼±勰В現(xiàn)在刪除舊代碼很容易了,因為我們已經(jīng)花了一些時間將所有代碼整理好够滑。我們刪除了那個類垦写,它消失了。我們把單體變小了彰触,每個人都對自己感到滿意梯澜。

并行運行驗證微服務(wù)遷移

就代碼重構(gòu)而言,我強烈推薦 Michael Feathers 的著作《 修改代碼的藝術(shù) 》渴析。他對遺留代碼的定義是沒有測試代碼的代碼晚伙。關(guān)于如何在不破壞現(xiàn)有系統(tǒng)的情況下,在代碼庫中找出并創(chuàng)建這些抽象俭茧,這本書提供了很多好主意咆疗。即使你不使用微服務(wù),僅僅創(chuàng)建這個抽象點就可能會使你的代碼處于更好母债、更可測試的狀態(tài)午磁。

我已經(jīng)強調(diào)過,不要太早刪除舊實現(xiàn)毡们。保留兩種實現(xiàn)有很多好處迅皇。它為我們?nèi)绾尾渴鸷蜕暇€軟件提供了有趣的方法。當(dāng)調(diào)用進(jìn)入抽象點時衙熔,它可以觸發(fā)對這兩個實現(xiàn)的調(diào)用登颓。這叫做并行運行。這可以幫助我們確保新的微服務(wù)實現(xiàn)功能上等價红氯。我們運行該功能的兩個副本框咙,然后比較結(jié)果。

要做這個比較痢甘,只需運行這兩個實現(xiàn)并比較結(jié)果喇嘱。我們必須指定其中之一作為真相來源,因為我們不希望把兩者串聯(lián)起來:例如塞栅,在發(fā)送通知時者铜,我們只想發(fā)送一封郵件,結(jié)果卻發(fā)了兩封。并行運行是一種實用而直接的實時比較作烟,不僅是功能等價性的比較愉粤,而且包含可接受的非功能等價性比較。我們不僅要測試是否創(chuàng)建了正確的電子郵件俗壹,并將其發(fā)送到正確的虛擬 SMTP 服務(wù)器科汗,而且還要測試新服務(wù)的響應(yīng)速度是否同樣快藻烤,或者錯誤率在可接受范圍之內(nèi)绷雏。

通常,我們信任舊的功能實現(xiàn)怖亭,并使用其結(jié)果涎显。我們將它們并行運行一段時間,如果新實現(xiàn)提供了可接受的結(jié)果兴猩,我們最終將處理掉舊的期吓。

GitHub 可以幫我們做這件事。他們創(chuàng)建了一個名為 GitHub Scientist 地庫倾芝,這是一個很小的 Ruby 庫讨勤,用于封裝不同的抽象并對它們進(jìn)行評分。在重構(gòu)應(yīng)用程序中的關(guān)鍵代碼路徑時晨另,我們可以使用它來進(jìn)行實時比較潭千。GitHub Scientist 已經(jīng)被移植到了很多不同的語言上,令人費解的是借尿,Perl 有三種不同的移植:顯然刨晴,在 Perl 社區(qū),并行運行是一件很重要的事情路翻。關(guān)于如何在應(yīng)用程序內(nèi)部并行運行狈癞,已經(jīng)有很多很好的建議。

將部署與發(fā)布分離:根本性的改變

從根本上說茂契,我們需要將部署的概念與發(fā)布的概念分離開來蝶桶。傳統(tǒng)上,我們認(rèn)為這兩種活動是一回事掉冶,部署軟件和向生產(chǎn)環(huán)境用戶發(fā)布軟件是一回事莫瞬。這就是為什么每個人都害怕生產(chǎn)環(huán)境會發(fā)生什么事情,這就是生產(chǎn)環(huán)境成為一個封閉環(huán)境的原因郭蕉。

我們可以把這兩個概念分開疼邀。將某樣?xùn)|西部署到生產(chǎn)環(huán)境中與將它發(fā)布給我們的用戶是不一樣的。這個想法是人們現(xiàn)在所說的“漸進(jìn)式交付”的基礎(chǔ)召锈,這是一個涵蓋了一系列不同技術(shù)的總稱旁振,包括金絲雀發(fā)布、藍(lán)/綠部署、抹黑啟動等拐袜。我們可以快速推出軟件吉嚣,但不必向任何客戶公開。我們可以把它放到生產(chǎn)環(huán)境中蹬铺,在那里測試尝哆,然后自己承受出現(xiàn)的任何問題。

如果我們將部署與發(fā)布分開甜攀,那么部署的風(fēng)險就會小很多秋泄。我們就會更加勇敢地進(jìn)行更改。我們將能夠更頻繁地發(fā)布规阀,而且發(fā)布的風(fēng)險將更低恒序。

RedMonk 聯(lián)合創(chuàng)始人 James Governor 在公司的博客上對漸進(jìn)式交付做了很好的 闡述 。該文探討了漸進(jìn)式交付谁撼,其中最重要的結(jié)論是芦倒,主動部署與主動發(fā)布不是一回事牡借,并且你可以控制發(fā)布活動如何發(fā)生。

用微服務(wù)方法遷移簡單的數(shù)據(jù)訪問

我們將現(xiàn)有的單體應(yīng)用程序和數(shù)據(jù)鎖定在系統(tǒng)中,如圖 9 所示疆瑰。我們已經(jīng)決定提取結(jié)賬功能掘猿,但是它需要訪問數(shù)據(jù)樊销。

漫談此蜈!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 9:從新服務(wù)訪問舊數(shù)據(jù)

選項一是直接訪問單體的數(shù)據(jù)。如果我們?nèi)匀辉跍y試并在單體中的結(jié)賬功能和微服務(wù)里的結(jié)賬功能之間進(jìn)行切換袄秩,我們會希望這兩種實現(xiàn)之間具有數(shù)據(jù)兼容性和一致性阵翎,這種方式可以保證這一點。這在短時間內(nèi)是可以接受的之剧,但它違背了數(shù)據(jù)庫的黃金規(guī)則之一:不共享數(shù)據(jù)庫郭卫。這不是可以長期依賴的東西,因為它會導(dǎo)致根本性的耦合問題背稼。我們希望保持獨立部署的能力贰军。

[圖片上傳失敗...(image-ab64fc-1614067307743)]

圖 10:從新服務(wù)直接訪問舊數(shù)據(jù)

如圖 10 所示,我們有一個 Shipping 服務(wù)和數(shù)據(jù)庫蟹肘,我們允許其他人訪問我們的數(shù)據(jù)词疼。我們已經(jīng)向外部公開了內(nèi)部實現(xiàn)細(xì)節(jié)。這使得 Shipping 服務(wù)的開發(fā)人員很難知道哪些內(nèi)容可以安全地更改帘腹。哪些數(shù)據(jù)要共享贰盗,哪些數(shù)據(jù)要隱藏,并沒有做區(qū)分阳欲。

20 世紀(jì) 70 年代舵盈,David Parnas 提出了“信息隱藏”的概念陋率,我們就是以此為基礎(chǔ)考慮模塊分解。我們希望在模塊或微服務(wù)的邊界內(nèi)隱藏盡可能多的信息秽晚。如果我們創(chuàng)建一個定義良好的服務(wù)接口來共享數(shù)據(jù)瓦糟,而不是直接公開數(shù)據(jù)庫,那么這個接口就讓 Shipping 服務(wù)的開發(fā)人員可以明確知道這個契約以及他們可以向外界公開什么赴蝇。只要遵守該契約菩浙,開發(fā)人員就可以在 Shipping 服務(wù)中做任何他們想做的事情。也就是說句伶,這些服務(wù)可以獨立演進(jìn)和開發(fā)劲蜻。不要直接訪問數(shù)據(jù)庫,除非是在極其特殊的情況下熄阻。

拋開直接訪問斋竞,我們有兩種選擇:要么訪問別人的數(shù)據(jù)倔约,要么保存自己的數(shù)據(jù)秃殉。對于這個例子,假如我們已經(jīng)確定新開發(fā)的結(jié)賬微服務(wù)已經(jīng)足夠好浸剩,可以作為我們的真相來源钾军。

此時,如果我們想要使用別人的數(shù)據(jù)绢要,那么這可能意味著數(shù)據(jù)屬于單體吏恭,我們必須向單體請求數(shù)據(jù)。我們在單體上創(chuàng)建某種顯式的服務(wù)接口(在我們的示例中是一個 API)重罪,通過它獲取我們想要的數(shù)據(jù)樱哼。

漫談!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 11:新開發(fā)的微服務(wù)使用單體顯式提供的一個服務(wù)接口

我們是結(jié)賬服務(wù)剿配,而不是訂單服務(wù)搅幅,但我們可能需要訂單數(shù)據(jù)。訂單功能存在于單體中呼胚,因此我們將從那里獲取數(shù)據(jù)茄唐。這樣來說,我們需要在單體上定義服務(wù)接口以公開不同的數(shù)據(jù)集蝇更,而且沪编,在這樣做時,我們可以看到其他實體從單體中顯現(xiàn)出來年扩。我們可能會發(fā)現(xiàn)蚁廓,訂單服務(wù)正等待著從單體中噴發(fā)出來,就像《異形》中的異形幼體厨幻,在電影中相嵌,單體是由 John Hurt 扮演的挽荠,它會死去。

另一種選擇是保存服務(wù)自己的數(shù)據(jù)——在本例中平绩,是單體數(shù)據(jù)庫中的結(jié)賬數(shù)據(jù)圈匆。至此,我們必須把數(shù)據(jù)移到一個結(jié)賬數(shù)據(jù)庫捏雌,這真的很難跃赚。從現(xiàn)有系統(tǒng)(尤其是關(guān)系數(shù)據(jù)庫)中提取數(shù)據(jù)會帶來很多麻煩。我們將看一個簡單的例子性湿,看看它帶來的挑戰(zhàn)纬傲。我將帶大家深入了解一下,如何處理連接肤频。

最后的挑戰(zhàn):處理連接操作

漫談叹括!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 12:在線銷售光盤的單體

圖 12 描述了一個現(xiàn)有的、在線銷售光盤的單體應(yīng)用程序宵荒。(你可以看出我這個例子已經(jīng)用了多久了汁雷。)Catalog 功能知道某些東西多少錢,并將信息存儲在 Line Items 表中报咳。Finance 功能管理我們的財務(wù)交易侠讯,并將數(shù)據(jù)存儲在 Ledger 表中。我們要做的其中一件事是暑刃,生成一個每周銷量前 10 的專輯列表厢漩。在這種情況下,我們需要做一個簡單的連接操作岩臣。我們從 Ledger 表上查出 10 個最暢銷的溜嗜。我們根據(jù)行和其他東西來限制這個查詢。這樣我們就能得到 ID 列表了架谎。

漫談炸宵!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 13:在線銷售光盤的微服務(wù)架構(gòu)

在進(jìn)入微服務(wù)領(lǐng)域后,我們需要在應(yīng)用層執(zhí)行連接操作狐树。我們從 Finance 數(shù)據(jù)庫中提取財務(wù)交易數(shù)據(jù)焙压。關(guān)于我們出售的物品的信息則存在于 Catalog 數(shù)據(jù)庫中。為了生成銷量前 10 名列表抑钟,我們必須從 Ledger 表中提取最暢銷商品的 ID涯曲,然后轉(zhuǎn)到 Catalog 微服務(wù),查詢所銷售商品的信息在塔。我們過去在關(guān)系層中執(zhí)行的連接操作轉(zhuǎn)移到了應(yīng)用程序?qū)印?/p>

延遲可能會變得令人震驚』眉現(xiàn)在,我不是做一個一次往返的連接操作蛔溃,而是要調(diào)用 Finance 服務(wù)獲取銷量前 10 的 ID绰沥,然后調(diào)用另一個 Catalog 服務(wù)請求者 10 個 ID 的信息篱蝇,然后 Catalog 服務(wù)從 Catalog 數(shù)據(jù)庫中取得這些 ID,然后我們才得到響應(yīng)徽曲。圖 13 說明了這個過程零截。

漫談!如何簡單明了通過分解和增量更改將單體遷移到微服務(wù)

圖 14:微服務(wù)架構(gòu)會導(dǎo)致更多的躍點和延遲

我們還沒有涉及到像缺乏數(shù)據(jù)完整性這樣的問題(在這種情況下秃臣,關(guān)系型數(shù)據(jù)庫如何實現(xiàn)引用完整性)涧衙。

小結(jié)

如果你想深入研究諸如處理延遲和數(shù)據(jù)一致性之類的問題,我在《從單體到微服務(wù)》一書中進(jìn)行了深入的闡述奥此。

無論你是否決定繼續(xù)自己的微服務(wù)遷移之旅弧哎,我都建議你仔細(xì)考慮下,自己正在做什么以及為什么要這樣做稚虎。不要把注意力都放在創(chuàng)建微服務(wù)上撤嫩。相反,你要清楚自己想要達(dá)到的結(jié)果蠢终。你認(rèn)為微服務(wù)會帶來什么結(jié)果序攘?專注于這一點——你可能會發(fā)現(xiàn),你可以在不進(jìn)入復(fù)雜的微服務(wù)世界的情況下實現(xiàn)同樣的結(jié)果蜕径。

以上就是有關(guān)微服務(wù)的學(xué)習(xí)筆記两踏,希望可以對大家學(xué)習(xí)微服務(wù)有幫助败京,喜歡的小伙伴可以幫忙轉(zhuǎn)發(fā)+關(guān)注兜喻,感謝大家!

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赡麦,一起剝皮案震驚了整個濱河市朴皆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泛粹,老刑警劉巖遂铡,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晶姊,居然都是意外死亡扒接,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門们衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钾怔,“玉大人,你說我怎么就攤上這事蒙挑∽谡欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵忆蚀,是天一觀的道長矾利。 經(jīng)常有香客問我姑裂,道長,這世上最難降的妖魔是什么男旗? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任舶斧,我火速辦了婚禮,結(jié)果婚禮上察皇,老公的妹妹穿的比我還像新娘捧毛。我一直安慰自己,他們只是感情好让网,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布呀忧。 她就那樣靜靜地躺著,像睡著了一般溃睹。 火紅的嫁衣襯著肌膚如雪而账。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天因篇,我揣著相機與錄音泞辐,去河邊找鬼。 笑死竞滓,一個胖子當(dāng)著我的面吹牛咐吼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播商佑,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锯茄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了茶没?” 一聲冷哼從身側(cè)響起肌幽,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抓半,沒想到半個月后喂急,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡笛求,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年廊移,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片探入。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡狡孔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出新症,到底是詐尸還是另有隱情步氏,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布徒爹,位于F島的核電站荚醒,受9級特大地震影響芋类,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜界阁,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一侯繁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泡躯,春花似錦贮竟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至写穴,卻和暖如春惰拱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啊送。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工偿短, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人馋没。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓昔逗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親篷朵。 傳聞我的和親對象是個殘疾皇子勾怒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容