單體應(yīng)用 VS 微服務(wù)
讓我們先從運(yùn)維的真實(shí)場景出發(fā),來看一下單體應(yīng)用存在的問題。這里先分享兩個真實(shí)的生產(chǎn)案例:
案例一是某核心業(yè)務(wù)系統(tǒng),所有的業(yè)務(wù)邏輯代碼都打包在同一個WAR包里部署,運(yùn)行了將近幾百個同構(gòu)的實(shí)例在虛擬機(jī)上。某次因?yàn)閼?yīng)用包中的一個功能模塊出現(xiàn)異常,導(dǎo)致實(shí)例掛起,整個應(yīng)用都不能用了。因?yàn)樗且粋單體,所以盡管有幾百個實(shí)例在運(yùn)行,但是這幾百個實(shí)例都是異常的。業(yè)務(wù)系統(tǒng)是經(jīng)過多年建設(shè)起來的,排查起來也很復(fù)雜,最終整個業(yè)務(wù)系統(tǒng)癱瘓了近六個小時才恢復(fù)。同時,因?yàn)橛卸鄠前臺系統(tǒng)也調(diào)用了這個后臺系統(tǒng),導(dǎo)致所有要調(diào)用的前臺系統(tǒng)也都全部癱瘓了。設(shè)想一下,如果這個場景使用的是微服務(wù)架構(gòu),每個微服務(wù)都是獨(dú)立部署的,那影響的也只是有異常的微服務(wù)和其他相關(guān)聯(lián)的服務(wù),而不會導(dǎo)致整個業(yè)務(wù)系統(tǒng)都不能使用。
另外的案例是一個客服系統(tǒng),這個系統(tǒng)有一個特點(diǎn),早上八點(diǎn)的時候會有大量的客服登錄。這個登錄點(diǎn)是全天中業(yè)務(wù)并發(fā)量最高的時間點(diǎn),登錄時系統(tǒng)需要讀取一些客戶信息,加載到內(nèi)存。后來一到早上客服登錄時,系統(tǒng)經(jīng)常出現(xiàn)內(nèi)存溢出,進(jìn)而導(dǎo)致整個客服系統(tǒng)都用不了。當(dāng)時系統(tǒng)應(yīng)對這種場景做架構(gòu)冗余時,并不是根據(jù)單獨(dú)的業(yè)務(wù)按需進(jìn)行擴(kuò)展,而是按照單體應(yīng)用的長板進(jìn)行冗余。比如說早上八點(diǎn)并發(fā)量最高,單點(diǎn)登陸模塊業(yè)務(wù)需求非常大,為適應(yīng)這個時間點(diǎn)這個模塊的業(yè)務(wù)壓力,系統(tǒng)會由原來的八個實(shí)例擴(kuò)展到十六個實(shí)例,這時的擴(kuò)展是基于整個系統(tǒng)的。但事實(shí)上,在其他時間段,這個單點(diǎn)登陸模塊基本不使用,且監(jiān)控的數(shù)據(jù)顯示,主機(jī)的資源使用情況基本在40%以下,造成了很大的資源浪費(fèi)。所以在一個大型的業(yè)務(wù)系統(tǒng)中,每個服務(wù)的并發(fā)壓力不一樣,如果都按壓力最大的模塊進(jìn)行整體擴(kuò)展,就會造成資源的浪費(fèi),而在微服務(wù)的模式下,每個服務(wù)都是按自身壓力進(jìn)行擴(kuò)展的,就可以有效的提高資源利用率。
從這兩個例子中,我們可以看出,單體應(yīng)用存在如下兩個問題:一個是橫向擴(kuò)展時需要整體擴(kuò)展,資源分配最大化,不能按需擴(kuò)展和分配資源;另一個是如果單體中有一個業(yè)務(wù)模塊出現(xiàn)問題,就會是全局性災(zāi)難,因?yàn)樗袠I(yè)務(wù)跑在同一個實(shí)例中,發(fā)生異常時不具備故障隔離性,會影響整個業(yè)務(wù)系統(tǒng),整個入口都會存在問題。
因此,我們當(dāng)時考慮把綜合業(yè)務(wù)拆分,進(jìn)行更好的資源分配和故障隔離。
下面我們看一下單體應(yīng)用和微服務(wù)的對比,如表所示。這里從微服務(wù)帶來的好處和額外的復(fù)雜性來講。
微服務(wù)的好處:
1.局部修改,局部更新。當(dāng)運(yùn)維對一個單體應(yīng)用進(jìn)行修改時,可能要先把整個包給停了,然后再去修改,而微服務(wù)只需逐步修改和更新即可;
2.故障隔離,非全局。單體應(yīng)用是跑在一起,所以只要一個模塊有問題,其他就都會有問題。而微服務(wù)的故障隔離性、業(yè)務(wù)可持續(xù)性都非常高;
資源利用率高。單體應(yīng)用的資源利用率低,而使用微服務(wù),可以按需分配資源,資源利用率會非常高。
微服務(wù)帶來的復(fù)雜性:
1.微服務(wù)間較強(qiáng)的依賴關(guān)系管理。以前單體應(yīng)用是跑在一起,無依賴關(guān)系管理,如果拆成微服務(wù)依賴關(guān)系該如何處理,比如說某個微服務(wù)更新了會不會對整個系統(tǒng)造成影響。
2.部署復(fù)雜。單體應(yīng)用是集中式的,就一個單體跑在一起,部署和管理的時候非常簡單,而微服務(wù)是一個網(wǎng)狀分布的,有很多服務(wù)需要維護(hù)和管理,對它進(jìn)行部署和維護(hù)的時候則比較復(fù)雜。
3.如何更好地利用資源。單體應(yīng)用在資源分配時是整體分配,擴(kuò)展時也是整體擴(kuò)展,數(shù)量可控,而在使用微服務(wù)的情況下,需要為每一個微服務(wù)按需分配資源,那么該為每個微服務(wù)分配多少資源,啟動多少個實(shí)例呢,這也是非常大的問題。
4.監(jiān)控管理難。以前我們用Java,就是一個單體應(yīng)用,監(jiān)控和管理非常簡單,因?yàn)樗褪且粋1,但是使用微服務(wù)它就是N個,監(jiān)控管理變得非常復(fù)雜。另外是微服務(wù)之間還有一個協(xié)作的問題。
基于容器構(gòu)建微服務(wù)架構(gòu)
使用微服務(wù),第一步是要構(gòu)建一個一體化的DevOps平臺,如圖。如果你不使用DevOps做微服務(wù)的話,整個環(huán)境會變得非常的亂、非常的糟糕。它會給你的整個開發(fā)、測試和運(yùn)維增加很多成本,所以第一步我們是提高DevOps的能力,能夠把它的開發(fā)、部署和維護(hù)進(jìn)行很完美的結(jié)合,才可以說我們真正能夠享受到微服務(wù)架構(gòu)的福利。
容器的出現(xiàn)給微服務(wù)提供了一個完美的環(huán)境,因?yàn)槲覀兛梢裕?/div>
1.基于容器做標(biāo)準(zhǔn)化構(gòu)建和持續(xù)集成、持續(xù)交付等。
2.基于標(biāo)準(zhǔn)工具對部署在微服務(wù)里面的容器做服務(wù)發(fā)現(xiàn)和管理。
3.透過容器的編排工具對容器進(jìn)行自動化的伸縮管理、自動化的運(yùn)維管理。
所以說,容器的出現(xiàn)和微服務(wù)的發(fā)展是非常相關(guān)的,它們共同發(fā)展,形成了一個非常好的生態(tài)圈。下面詳細(xì)講下DevOps的各個模塊。
持續(xù)集成與持續(xù)發(fā)布
持續(xù)集成的關(guān)鍵是完全的自動化,讀取源代碼、編譯、連接、測試,整個創(chuàng)建過程自動完成。我們來看一下如何用Docker、Maven、Jenkins完成持續(xù)集成。
如圖所示。首先是開發(fā)人員把程序代碼更新后上傳到Git,然后其他的事情都將由Jenkins自動完成。那Jenkins這邊發(fā)生什么了呢?Git在接收到用戶更新的代碼后,會把消息和任務(wù)傳遞給Jenkins,然后Jenkins會自動構(gòu)建一個任務(wù),下載Maven相關(guān)的軟件包。下載完成后,就開始利用Maven Build新的項(xiàng)目包,然后重建Maven容器,構(gòu)建新的Image并Push到Docker私有庫中。然后刪除正在運(yùn)行的Docker容器,再基于新的鏡像重新把Docker容器拉起來,自動完成集成測試。整個過程都是自動的,這樣就簡化了原本復(fù)雜的集成工作,一天可以集成一次,甚至是多次。
依賴關(guān)系管理
前面講到,當(dāng)微服務(wù)多的時候,依賴關(guān)系管理也會比較復(fù)雜,現(xiàn)在比較流行的是基于消費(fèi)者驅(qū)動的契約管理。在開發(fā)一個微服務(wù)時,并不需要另外一個微服務(wù)開發(fā)完后再做集成測試,而是使用契約的方式。契約通過提供標(biāo)準(zhǔn)化的輸出,說明請求的內(nèi)容、回復(fù)的內(nèi)容、交換的數(shù)據(jù),開發(fā)微服務(wù)時符合契約里這些條件即可。
如圖所示,微服務(wù)A通過模擬與微服務(wù)B的交互,將交互內(nèi)容保存在契約里,而微服務(wù)B開發(fā)時需滿足這個契約里的條件,這樣就不需要A和B完全完成了才能做測試。當(dāng)很多微服務(wù)與微服務(wù)B關(guān)聯(lián)時,每個微服務(wù)通過契約告訴微服務(wù)B請求的內(nèi)容、正確的響應(yīng)和請示的數(shù)據(jù),然后微服務(wù)B通過契約模擬這個測試過程,而其他的微服務(wù)則需要滿足這個契約。當(dāng)微服務(wù)進(jìn)行升級時,也是要先滿足所有契約,這樣微服務(wù)間的關(guān)系就可以更好的進(jìn)行管理。
典型的微服務(wù)架構(gòu)
典型的微服務(wù)架構(gòu)模型,采用的是Kubernetes框架。把業(yè)務(wù)系統(tǒng)拆分成很多的微服務(wù),然后通過服務(wù)注冊的方式去發(fā)現(xiàn)整個生產(chǎn)環(huán)境中所有的微服務(wù),通過負(fù)載均衡組件進(jìn)行分發(fā),再用服務(wù)調(diào)度去進(jìn)行彈性伸縮,而客戶端則只需要通過API網(wǎng)關(guān)訪問微服務(wù)。除此之外,微服務(wù)的運(yùn)維也很重要。開發(fā)是實(shí)現(xiàn)功能性需求,而在實(shí)際的生產(chǎn)環(huán)境中,我們更應(yīng)該關(guān)心非功能性的需求。因?yàn)榧词构δ軐?shí)現(xiàn)了,跑到生產(chǎn)上卻不能用,功能開發(fā)再完美也沒有用。
服務(wù)發(fā)現(xiàn)與負(fù)載均衡
服務(wù)發(fā)現(xiàn)與負(fù)載均衡使用的是Kubernetes的架構(gòu),如圖。每一個微服務(wù)都有一個IP和PORT,當(dāng)調(diào)用一個微服務(wù)時,只需要知道微服務(wù)的IP,而不需要關(guān)心容器的IP,也不需要關(guān)心pod的IP。雖然每個pod也有IP和PORT,但當(dāng)一個pod啟動時,就會把pod的IP和PORT注冊到服務(wù)發(fā)現(xiàn)模塊,再進(jìn)行負(fù)載均衡。所以當(dāng)多個pod啟動時,對于用戶來說還是只需要知道service的IP,不需要知道后端啟動了多少pod、IP是多少,這就解決了網(wǎng)絡(luò)的問題。
日志集中式管理
以前單體的情況下,單體的數(shù)量少,日志數(shù)量也相應(yīng)比較少,而在微服務(wù)架構(gòu)下,因?yàn)椴鸱殖闪撕芏辔⒎⻊?wù),相應(yīng)的日志會非常多且散,這種情況下需要對日志進(jìn)行集中的管理。我們可以在每個容器里跑日志監(jiān)控,把所有日志采集進(jìn)行集中管理和存儲,再通過簡易操作的UI界面進(jìn)行索引和查詢。
監(jiān)控管理
然后就是監(jiān)控方面了。微服務(wù)的量是非常大的,這個時候如何有效地監(jiān)控是極其重要的。我們剛開始做監(jiān)控的時候,有幾百個實(shí)例對同一個關(guān)鍵字進(jìn)行監(jiān)控,出故障后會收到幾百條短信,因?yàn)槊恳粋實(shí)例都會發(fā)一條短信。這時候嚴(yán)重的致命性的報(bào)警就會看不到,因?yàn)槭謾C(jī)信息已經(jīng)爆炸了,所以要對報(bào)警進(jìn)行分級,精確告警,最重要的是盡量讓故障在發(fā)生之前滅亡。因此,在做監(jiān)控時要對故障提前進(jìn)行判斷,先自動化處理,再看是否需要人為處理,然后通過人為的干預(yù),有效的把故障在發(fā)生之前進(jìn)行滅亡。
但如果所有事情都靠人為去處理,這個量也是非常大的,所以對故障進(jìn)行自動化隔離和自動化處理也很重要。我們在寫自動化故障處理的時候研究了很多常見的故障,寫了很多算法去判斷,精確到所有的故障,這樣基本的常見的故障和可以策劃處理的故障都可以自動化處理掉。之前沒有出現(xiàn)的故障,出現(xiàn)之后我們就會去研究是否可以做成自動化處理。如果生產(chǎn)上做的不精確,對生產(chǎn)會是災(zāi)難性的,所以我們對生產(chǎn)的故障自動化處理也做了很多研究。
七牛的微服務(wù)架構(gòu)實(shí)踐
前面講了很多運(yùn)維,下面來了解一下Docker和微服務(wù)在七牛云中的實(shí)踐,以七牛的文件處理服務(wù)架構(gòu)演變?yōu)槔?/div>
文件處理服務(wù)是指,用戶把原圖存到七牛的云存儲之后,然后使用文件處理服務(wù),就可以把它變成自己想要的服務(wù),比如剪裁、縮放和旋轉(zhuǎn)等,如圖。
過去為適應(yīng)不同的場景,用戶需要針對同一圖片自行處理后上傳所有版本,但如果使用七牛的文件處理服務(wù),則只需提供原圖就可以了,其他版本的圖片都可以通過七牛進(jìn)行處理。如圖所示,我們把一張?jiān)瓐D放進(jìn)去之后,只需要在原圖后面加一個問號和參數(shù),就可以呈現(xiàn)出想要的方式,比如說加水印、旋轉(zhuǎn)、縮放和剪裁。
文件處理服務(wù)的架構(gòu)在早期是非常笨拙的,如圖所示。FopGate是業(yè)務(wù)的入口,通過work config靜態(tài)配置實(shí)際進(jìn)行計(jì)算的各種worker集群的信息,里面包含了圖片處理、視頻處理、文檔處理等各種處理實(shí)例。集群信息在入口配置中寫死了,后端配置變更會很不靈活,因?yàn)槭庆o態(tài)配置,突發(fā)請求情況下,應(yīng)對可能不及時,且FopGate成為流量穿透的組件(業(yè)務(wù)的指令流與數(shù)據(jù)流混雜在一起),比如說要讀一張圖片,這張圖片會經(jīng)過FopGate導(dǎo)過來。
于是,我們自己組建了一個叫Discovery的服務(wù),由它進(jìn)行負(fù)載均衡和服務(wù)發(fā)現(xiàn),用于集群中worker信息的自動發(fā)現(xiàn),每個worker被添加進(jìn)集群都會主動注冊自己,F(xiàn)opGate從Discovery獲取集群信息,完成對請求的負(fù)載均衡。同時在每個計(jì)算節(jié)點(diǎn)上新增Agent組件,用于向Discovery組件上報(bào)心跳和節(jié)點(diǎn)信息,并緩存處理后的結(jié)果數(shù)據(jù)(將數(shù)據(jù)流從入口分離),另外也負(fù)責(zé)節(jié)點(diǎn)內(nèi)的請求負(fù)載均衡(實(shí)例可能會有多個)。此時業(yè)務(wù)入口只需負(fù)責(zé)分發(fā)指令流,但仍然需要對請求做節(jié)點(diǎn)級別的負(fù)載均衡。這是第二個階段,如圖。
在第三個階段,我們把文件處理架構(gòu)遷移到了容器平臺上,取消了業(yè)務(wù)的Discovery服務(wù),轉(zhuǎn)由平臺自身的服務(wù)發(fā)現(xiàn)功能。每個Agent無需和Discovery維護(hù)心跳,也不再需要上報(bào)節(jié)點(diǎn)信息,每個Agent對應(yīng)一個計(jì)算worker,并按工種獨(dú)立成Service,比如ImageService、VideoService,由于后端只有一個worker,因此也不需要有節(jié)點(diǎn)內(nèi)的負(fù)載均衡邏輯,業(yè)務(wù)入口無需負(fù)載均衡,只需請求容器平臺提供的入口地址即可,如圖所示。
上一個階段中,每個Agent仍然和計(jì)算實(shí)例綁定在一起,而這么做其實(shí)只是為了方便業(yè)務(wù)的無痛遷移,因?yàn)锳gent本身的代碼會有一些邏輯上的假設(shè)。在第四階段中,如圖,我們進(jìn)一步分離了Agent和worker,Agent獨(dú)立成一個Service,所有的worker按工種獨(dú)立成Service。這么分離的目的在于,Agent可能會有文件內(nèi)容緩存,屬于有狀態(tài)的服務(wù),而所有的worker是真正干活、無狀態(tài)的服務(wù)。分離之后的好處在于,worker的數(shù)量可以隨時調(diào)整和伸縮,而不影響Agent中攜帶的狀態(tài)。可以看到,相比于最早的架構(gòu),業(yè)務(wù)方只需集中精力開發(fā)業(yè)務(wù)本身,而無需重復(fù)造輪子,實(shí)現(xiàn)各種復(fù)雜的服務(wù)發(fā)現(xiàn)和各種負(fù)載均衡的代碼。另外,自從部署到容器平臺之后,平臺的調(diào)度器會自動根據(jù)節(jié)點(diǎn)的資源消耗狀況做實(shí)例的遷移,這樣使得計(jì)算集群中每個節(jié)點(diǎn)的資源消耗更加均衡。
踩過的那些坑
圖所示的是單體應(yīng)用訪問后端數(shù)據(jù)庫使用的數(shù)據(jù)庫連接池,連接池里的連接是可以復(fù)用的,而單體中所有的業(yè)務(wù)模塊都可以調(diào)用同一個池里的連接。這個數(shù)據(jù)庫連接池可以進(jìn)行動態(tài)的伸縮,但它連接到后端數(shù)據(jù)庫的連接是有上限的。拆分成微服務(wù)之后,可能每一個微服務(wù)都要創(chuàng)建一個數(shù)據(jù)庫連接池,微服務(wù)間的數(shù)據(jù)庫連接池并不能共享。當(dāng)對微服務(wù)進(jìn)行彈性伸縮時,并不知道它會彈性到什么程度,這時就可能會對后端數(shù)據(jù)庫造成連接風(fēng)暴,比如原本只有五千個長連接,但是由于彈性伸縮(可能根據(jù)前臺業(yè)務(wù)情況進(jìn)行了非常大的伸縮),會對數(shù)據(jù)庫發(fā)生連接風(fēng)暴,對數(shù)據(jù)庫造成非常大的壓力。
為了確保不會因某個服務(wù)的連接風(fēng)暴把數(shù)據(jù)庫拖垮,對其他服務(wù)造成影響,我們做了一些優(yōu)化。首先是優(yōu)化了我們的一些彈性算法,其次是限定了容器彈性伸縮的范圍,因?yàn)闆]有限制的伸縮,可能會對后端數(shù)據(jù)庫造成壓力。另外一個是降低連接池初始化的大小,比如初始化設(shè)定為10,它往數(shù)據(jù)庫里面的長連接就是100個,如果設(shè)定為1,往數(shù)據(jù)庫里面長連接就只有10個,如果太多則可能會對數(shù)據(jù)庫造成壓力。還有就是數(shù)據(jù)庫端進(jìn)行一些限定,當(dāng)連接達(dá)到一定數(shù)量就進(jìn)行預(yù)警,限制彈性擴(kuò)展。
我在傳統(tǒng)企業(yè)工作了七八年,我們一直在探討單體應(yīng)用如何拆分成微服務(wù)。其實(shí)對傳統(tǒng)企業(yè)來講,他們的單體是非常大的,而且內(nèi)部的耦合性是非常強(qiáng)的,內(nèi)部調(diào)用非常多。如果我們冒冒失失地將單體拆分成微服務(wù),會占用非常大的網(wǎng)絡(luò)傳輸。如果在拆成微服務(wù)的時候,沒有進(jìn)行很好的架構(gòu)設(shè)計(jì),拆成微服務(wù)并不是享受它帶來的好處,而是災(zāi)難。所以在拆的時候,比如說前端、后端的拆分、耦合性不是特別強(qiáng)的拆分,我們提倡先跑到容器里面,看一下運(yùn)維容器的時候有什么坑,然后解決這些坑,然后不斷完善。我們可以把耦合性不是那么高的拆成大的塊,然后把信息密集型的進(jìn)行拆分,慢慢拆分成我們理想的架構(gòu)。這是一個循序漸進(jìn)的過程,不可一蹴而就。
以上是微服務(wù)的一些設(shè)計(jì)準(zhǔn)則,例如設(shè)計(jì)提倡最小化功能,小到不能再小。這時需要每一個服務(wù)標(biāo)準(zhǔn)化的提供出來,有一個標(biāo)準(zhǔn)化的接口和其他微服務(wù)進(jìn)行通信。還需要是異步通信,如果是同步會造成堵塞。每個微服務(wù)都要有獨(dú)立的數(shù)據(jù)存儲。此外,服務(wù)還需要是無狀態(tài)的,因?yàn)槿绻袪顟B(tài),彈性伸縮時可能會有數(shù)據(jù)丟失,可以用一些其他的方法處理這些有狀態(tài)的數(shù)據(jù)。
核心關(guān)注:拓步ERP系統(tǒng)平臺是覆蓋了眾多的業(yè)務(wù)領(lǐng)域、行業(yè)應(yīng)用,蘊(yùn)涵了豐富的ERP管理思想,集成了ERP軟件業(yè)務(wù)管理理念,功能涉及供應(yīng)鏈、成本、制造、CRM、HR等眾多業(yè)務(wù)領(lǐng)域的管理,全面涵蓋了企業(yè)關(guān)注ERP管理系統(tǒng)的核心領(lǐng)域,是眾多中小企業(yè)信息化建設(shè)首選的ERP管理軟件信賴品牌。
轉(zhuǎn)載請注明出處:拓步ERP資訊網(wǎng)http://www.ezxoed.cn/
本文標(biāo)題:從運(yùn)維的角度看微服務(wù)和容器
本文網(wǎng)址:http://www.ezxoed.cn/html/support/11121820033.html
關(guān)鍵詞標(biāo)簽:
從運(yùn)維的角度看微服務(wù)和容器,運(yùn)維 微服務(wù) 容器,ERP,ERP系統(tǒng),ERP軟件,ERP系統(tǒng)軟件,ERP管理系統(tǒng),ERP管理軟件,進(jìn)銷存軟件,財(cái)務(wù)軟件,倉庫管理軟件,生產(chǎn)管理軟件,企業(yè)管理軟件,拓步,拓步ERP,拓步軟件,免費(fèi)ERP,免費(fèi)ERP軟件,免費(fèi)ERP系統(tǒng),ERP軟件免費(fèi)下載,ERP系統(tǒng)免費(fèi)下載,免費(fèi)ERP軟件下載,免費(fèi)進(jìn)銷存軟件,免費(fèi)進(jìn)銷存,免費(fèi)財(cái)務(wù)軟件,免費(fèi)倉庫管理軟件,免費(fèi)下載,
本文轉(zhuǎn)自:e-works制造業(yè)信息化門戶網(wǎng)
本文來源于互聯(lián)網(wǎng),拓步ERP資訊網(wǎng)本著傳播知識、有益學(xué)習(xí)和研究的目的進(jìn)行的轉(zhuǎn)載,為網(wǎng)友免費(fèi)提供,并盡力標(biāo)明作者與出處,如有著作權(quán)人或出版方提出異議,本站將立即刪除。如果您對文章轉(zhuǎn)載有任何疑問請告之我們,以便我們及時糾正。聯(lián)系方式:QQ:10877846 Tel:0755-26405298。(請勿發(fā)郵件,由于垃圾郵件眾多,有可能會被當(dāng)作垃圾郵件處理掉,同時現(xiàn)在很用郵件處理事務(wù)了,郵件處理時效期為3天,如急件請直接QQ聯(lián)系。)
上一篇:
沒有了!
相關(guān)文章