摘要:集群系統(tǒng)中的單個(gè)計(jì)算機(jī)通常稱為節(jié)點(diǎn),通常通過局域網(wǎng)連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負(fù)責(zé)了。于是,小周和就變成了協(xié)作開發(fā)。都是為了項(xiàng)目正常運(yùn)行以及迭代。
一、前言
只有光頭才能變強(qiáng)
認(rèn)識(shí)我的朋友可能都知道我這陣子去實(shí)習(xí)啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)...
所以,這篇主要來講講SpringCloud的一些基礎(chǔ)的知識(shí)。(我就是現(xiàn)學(xué)現(xiàn)賣了,主要當(dāng)做我學(xué)習(xí)SpringCloud的筆記吧!)當(dāng)然了,我的水平是有限的,可能會(huì)有一些理解錯(cuò)的的概念/知識(shí)點(diǎn),還請(qǐng)大家不吝在評(píng)論區(qū)指正啊~~
SpringCloud GitHub Demo(看完文章的同學(xué)可以自己練手玩玩):
https://github.com/ZhongFuCheng3y/msc-Demo
項(xiàng)目結(jié)構(gòu)圖:
二、集群/分布式/微服務(wù)/SOA是什么?像我這種技術(shù)小白,看到這些詞(集群/分布式/微服務(wù)/SOA)的時(shí)候,感覺就是遙不可及的(高大尚的技術(shù)!!)。就好像剛學(xué)Java面向?qū)ο蟮臅r(shí)候,在論壇上翻閱資料的時(shí)候,無意看到"面向切面編程",也認(rèn)為這是遙不可及的(高大尚的技術(shù)!!)。
但真正接觸到"面向切面編程"的時(shí)候,發(fā)現(xiàn)原來就是如此啊,也沒什么大不了的。只不過當(dāng)時(shí)被它的名字給唬住了...
不知道各位在剛接觸這些名字集群/分布式/微服務(wù)/SOA的時(shí)候,有沒有被唬住了呢??
下面我就簡單說說這些名詞的意思
2.1什么是集群以下內(nèi)容來源維基百科:
計(jì)算機(jī)集群簡稱集群是一種計(jì)算機(jī)系統(tǒng),它通過一組松散集成的計(jì)算機(jī)軟件和/或硬件連接起來高度緊密地協(xié)作完成計(jì)算工作。在某種意義上,他們可以被看作是一臺(tái)計(jì)算機(jī)。集群系統(tǒng)中的單個(gè)計(jì)算機(jī)通常稱為節(jié)點(diǎn),通常通過局域網(wǎng)連接,但也有其它的可能連接方式。集群計(jì)算機(jī)通常用來改進(jìn)單個(gè)計(jì)算機(jī)的計(jì)算速度和/或可靠性。一般情況下集群計(jì)算機(jī)比單個(gè)計(jì)算機(jī),比如工作站或超級(jí)計(jì)算機(jī)性能價(jià)格比要高得多
集群技術(shù)特點(diǎn):
通過多臺(tái)計(jì)算機(jī)完成同一個(gè)工作,達(dá)到更高的效率。
兩機(jī)或多機(jī)內(nèi)容、工作過程等完全一樣。如果一臺(tái)死機(jī),另一臺(tái)可以起作用。
在維基百科上說得也挺明白的了,我來舉個(gè)例子吧。
小周在公司寫Java程序,但公司業(yè)務(wù)在發(fā)展,一個(gè)Java開發(fā)者可能忙不過來,小周有的時(shí)候也得請(qǐng)個(gè)假呀。于是請(qǐng)了3y過去一起做Java開發(fā)。平時(shí)小周和3y就寫Java程序,但3y可能有事要回學(xué)校一趟。沒事,公司還有小周做Java開發(fā)呢,公司開發(fā)還能繼續(xù)運(yùn)作。
3y跟小周都是做Java開發(fā)。
3y來了,小周的工作可以分擔(dān)一些。
3y請(qǐng)假了,還有小周在呢。
我寫了一個(gè)910便利網(wǎng)發(fā)布到服務(wù)器去了,現(xiàn)在越來越多的人訪問了,訪問有點(diǎn)慢,怎么辦???很簡單,(只有充錢才能變強(qiáng)),加配置吧(加cpu,加內(nèi)存)。升級(jí)完配置之后,訪問人數(shù)越來越多,于是發(fā)現(xiàn)又不禁用啦,在這臺(tái)機(jī)器上加配置已經(jīng)解決不了了,怎么辦???很簡單,(只有充錢才能變強(qiáng)),我再買一臺(tái)服務(wù)器,將910便利網(wǎng)也發(fā)布到新買的這臺(tái)服務(wù)器上去。
特點(diǎn):
這兩臺(tái)服務(wù)器都是運(yùn)行同一個(gè)系統(tǒng)--->910便利網(wǎng)
好處:
本來只有一臺(tái)機(jī)器處理訪問,現(xiàn)在有兩臺(tái)機(jī)器處理訪問了,分擔(dān)了壓力。
如果其中一臺(tái)忘記繳費(fèi)了,暫時(shí)用不了了。沒關(guān)系,還有另一臺(tái)可以用呢。
集群:同一個(gè)業(yè)務(wù),部署在多個(gè)服務(wù)器上(不同的服務(wù)器運(yùn)行同樣的代碼,干同一件事)
2.2什么是分布式以下內(nèi)容來源維基百科:
分布式系統(tǒng)是一組計(jì)算機(jī),通過網(wǎng)絡(luò)相互連接傳遞消息與通信后并協(xié)調(diào)它們的行為而形成的系統(tǒng)。組件之間彼此進(jìn)行交互以實(shí)現(xiàn)一個(gè)共同的目標(biāo)。
我也來舉個(gè)例子來說明一下吧:
現(xiàn)在公司有小周和3y一起做Java開發(fā),做Java開發(fā)一般jQuery,AJAX都能寫一點(diǎn),所以這些活都由我們來干。可是呢,3y對(duì)前端不是很熟,有的時(shí)候調(diào)試半天都調(diào)不出來。老板認(rèn)為3y是真的菜!于是讓小周專門來處理前端的事情。這樣3y就高興了,可以專心寫自己的Java,前端就專門交由小周負(fù)責(zé)了。于是,小周和3y就變成了協(xié)作開發(fā)。
3y對(duì)前端不熟(能寫出來),但在調(diào)試的時(shí)候可能會(huì)花費(fèi)很多時(shí)間
小周來專門做前端的事,3y可以專心寫自己的Java程序了。
都是為了項(xiàng)目正常運(yùn)行以及迭代。
我的910便利網(wǎng)已經(jīng)部署到兩臺(tái)服務(wù)器去了,但是越來越多的人去訪問。現(xiàn)在也逐漸承受不住啦。那現(xiàn)在怎么辦啊??那繼續(xù)充錢變強(qiáng)??作為一個(gè)理智的我,肯定得想想是哪里有問題。現(xiàn)在910便利網(wǎng)的模塊有好幾個(gè),全都丟在同一個(gè)Tomcat里邊。
其實(shí)有些模塊的訪問是很低的(比如后臺(tái)管理),那我可不可以這樣做:將每個(gè)模塊抽取獨(dú)立出來,訪問量大的模塊用好的服務(wù)器裝著,沒啥人訪問的模塊用差的服務(wù)器裝著。這樣的好處是:一、資源合理利用了(沒人訪問的模塊用性能差的服務(wù)器,訪問量大的模塊多帶帶提升性能就好了)。二、耦合度降低了:每個(gè)模塊獨(dú)立出來,各干各的事(專業(yè)的人做專業(yè)的事),便于擴(kuò)展
特點(diǎn):
將910便利網(wǎng)的功能拆分,模塊之間獨(dú)立,在使用的時(shí)候再將這些獨(dú)立的模塊組合起來就是一個(gè)系統(tǒng)了。
好處:
模塊之間獨(dú)立,各做各的事,便于擴(kuò)展,復(fù)用性高
高吞吐量。某個(gè)任務(wù)需要一個(gè)機(jī)器運(yùn)行10個(gè)小時(shí),將該任務(wù)用10臺(tái)機(jī)器的分布式跑(將這個(gè)任務(wù)拆分成10個(gè)小任務(wù)),可能2個(gè)小時(shí)就跑完了
分布式:一個(gè)業(yè)務(wù)分拆多個(gè)子業(yè)務(wù),部署在不同的服務(wù)器上(不同的服務(wù)器,運(yùn)行不同的代碼,為了同一個(gè)目的)
2.3集群/分布式集群和分布式并不沖突,可以有分布式集群
現(xiàn)在3y的公司規(guī)模變大了,有5個(gè)小伙子寫Java,4個(gè)小伙子寫前端,2個(gè)小伙子做測試,1個(gè)小伙子做DBA。
Java,前端,測試,DBA的關(guān)系看作是分布式的
5個(gè)Java看作是集群的(前端,測試同理)...
2.4分布式/微服務(wù)/SOA其實(shí)我認(rèn)為分布式/微服務(wù)/SOA這三個(gè)概念是差不多的,了解了其中的一個(gè),然后將自己的理解往上面套就好了。沒必要細(xì)分每個(gè)的具體概念~~(當(dāng)然了,我很期待有大佬可以在評(píng)論區(qū)留言說下自己的看法哈)
參考資料:
分布式與集群的區(qū)別是什么?https://www.zhihu.com/question/20004877
分布式、集群、微服務(wù)、SOA 之間的區(qū)別https://blog.csdn.net/heatdeath/article/details/79038795
三、CAP理論從上面所講的分布式概念我們已經(jīng)知道,分布式簡單理解就是:一個(gè)業(yè)務(wù)分拆多個(gè)子業(yè)務(wù),部署在不同的服務(wù)器上
一般來說,一個(gè)子業(yè)務(wù)我們稱為節(jié)點(diǎn)。
如果你接觸過一些分布式的基礎(chǔ)概念,那肯定會(huì)聽過CAP這個(gè)理論。就比如說:你學(xué)了MySQL的InnoDB存儲(chǔ)引擎相關(guān)知識(shí),你肯定聽過ACID!
首先,我們來看一下CAP分別代表的是什么意思:
C:數(shù)據(jù)一致性(consistency)
所有節(jié)點(diǎn)擁有數(shù)據(jù)的最新版本
A:可用性(availability)
數(shù)據(jù)具備高可用性
P:分區(qū)容錯(cuò)性(partition-tolerance)
容忍網(wǎng)絡(luò)出現(xiàn)分區(qū),分區(qū)之間網(wǎng)絡(luò)不可達(dá)。
下面有三個(gè)節(jié)點(diǎn)(它們是集群的),此時(shí)三個(gè)節(jié)點(diǎn)都能夠相互通信:
由于我們的系統(tǒng)是分布式的,節(jié)點(diǎn)之間的通信是通過網(wǎng)絡(luò)來進(jìn)行的。只要是分布式系統(tǒng),那很有可能會(huì)出現(xiàn)一種情況:因?yàn)橐恍?strong>故障,使得有些節(jié)點(diǎn)之間不連通了,整個(gè)網(wǎng)絡(luò)就分成了幾塊區(qū)域。
數(shù)據(jù)就散布在了這些不連通的區(qū)域中,這就叫分區(qū)
現(xiàn)在出現(xiàn)了網(wǎng)絡(luò)分區(qū)后,此時(shí)有一個(gè)請(qǐng)求過來了,想要注冊(cè)一個(gè)賬戶。
此時(shí)我們節(jié)點(diǎn)一和節(jié)點(diǎn)三是不可通信的,這就有了抉擇:
如果允許當(dāng)前用戶注冊(cè)一個(gè)賬戶,此時(shí)注冊(cè)的記錄數(shù)據(jù)只會(huì)在節(jié)點(diǎn)一和節(jié)點(diǎn)二或者節(jié)點(diǎn)二和節(jié)點(diǎn)三同步,因?yàn)楣?jié)點(diǎn)一和節(jié)點(diǎn)三的記錄不能同步的。
這種情況其實(shí)就是選擇了可用性(availability),拋棄了數(shù)據(jù)一致性(consistency)
如果不允許當(dāng)前用戶注冊(cè)一個(gè)賬戶(就是要等到節(jié)點(diǎn)一和節(jié)點(diǎn)三恢復(fù)通信)。節(jié)點(diǎn)一和節(jié)點(diǎn)三一旦恢復(fù)通信,我們就可以保證節(jié)點(diǎn)擁有的數(shù)據(jù)是最新版本。
這種情況其實(shí)就是拋棄了可用性(availability),選擇了數(shù)據(jù)一致性(consistency)
3.1再次梳理一下CAP理論一般我們說的分布式系統(tǒng),P:分區(qū)容錯(cuò)性(partition-tolerance)這個(gè)是必需的,這是客觀存在的。
CAP是無法完全兼顧的,從上面的例子也可以看出,我們可以選AP,也可以選CP。但是,要注意的是:不是說選了AP,C就完全拋棄了。不是說選了CP,A就完全拋棄了!
在CAP理論中,C所表示的一致性是強(qiáng)一致性(每個(gè)節(jié)點(diǎn)的數(shù)據(jù)都是最新版本),其實(shí)一致性還有其他級(jí)別的:
弱一致性:弱一致性是相對(duì)于強(qiáng)一致性而言,它不保證總能得到最新的值;
最終一致性(eventual consistency):放寬對(duì)時(shí)間的要求,在被調(diào)完成操作響應(yīng)后的某個(gè)時(shí)間點(diǎn),被調(diào)多個(gè)節(jié)點(diǎn)的數(shù)據(jù)最終達(dá)成一致
可用性的值域可以定義成0到100%的連續(xù)區(qū)間。
所以,CAP理論定義的其實(shí)是在容忍網(wǎng)絡(luò)分區(qū)的條件下,“強(qiáng)一致性”和“極致可用性”無法同時(shí)達(dá)到。
參考資料:
CAP理論中的P到底是個(gè)什么意思?https://www.zhihu.com/question/54105974
淺談分布式系統(tǒng)的基本問題:可用性與一致性:https://m.aliyun.com/yunqi/articles/2709
分布式系統(tǒng)的CAP理論:http://www.hollischuang.com/archives/666
為什么CAP理論在舍棄P的情況下,可以有完美的CA?https://www.zhihu.com/question/285878189
不懂點(diǎn)CAP理論,你好意思說你是做分布式的嗎?http://www.yunweipai.com/archives/8432.html
擴(kuò)展閱讀:
淺談分布式事務(wù):https://m.aliyun.com/yunqi/articles/230242
四、SpringCloud就是這么簡單相信大家讀到這里,對(duì)分布式/微服務(wù)已經(jīng)有一定的了解了,其實(shí)單從概念來說,是非常容易理解的。只是很可能被它的名字給唬住了。
下面我就來講講SpringCloud最基礎(chǔ)的知識(shí)~
4.1為什么需要SpringCloud?前面也講了,從分布式/微服務(wù)的角度而言:就是把我們一大的項(xiàng)目,分解成多個(gè)小的模塊。這些小的模塊組合起來,完成功能。
舉個(gè)可能不太恰當(dāng)?shù)睦?現(xiàn)實(shí)可能不會(huì)這么拆分,但意思到位就好了):
拆分出多個(gè)模塊以后,就會(huì)出現(xiàn)各種各樣的問題,而SpringCloud提供了一整套的解決方案!
注:這些模塊是獨(dú)立成一個(gè)子系統(tǒng)的(不同主機(jī))。
SpringCloud的基礎(chǔ)功能:
服務(wù)治理: Spring Cloud Eureka
客戶端負(fù)載均衡: Spring Cloud Ribbon
服務(wù)容錯(cuò)保護(hù): Spring Cloud Hystrix
聲明式服務(wù)調(diào)用: Spring Cloud Feign
API網(wǎng)關(guān)服務(wù):Spring Cloud Zuul
分布式配置中心: Spring Cloud Config
SpringCloud的高級(jí)功能(本文不講):
消息總線: Spring Cloud Bus
消息驅(qū)動(dòng)的微服務(wù): Spring Cloud Stream
分布式服務(wù)跟蹤: Spring Cloud Sleuth
五、引出Eureka那會(huì)出現(xiàn)什么問題呢??首當(dāng)其沖的就是子系統(tǒng)之間的通訊問題。子系統(tǒng)與子系統(tǒng)之間不是在同一個(gè)環(huán)境下,那就需要遠(yuǎn)程調(diào)用。遠(yuǎn)程調(diào)用可能就會(huì)想到httpClient,WebService等等這些技術(shù)來實(shí)現(xiàn)。
既然是遠(yuǎn)程調(diào)用,就必須知道ip地址,我們可能有以下的場景。
功能實(shí)現(xiàn)一:A服務(wù)需要調(diào)用B服務(wù)
在A服務(wù)的代碼里面調(diào)用B服務(wù),顯式通過IP地址調(diào)用:http://123.123.123.123:8888/java3y/3
功能實(shí)現(xiàn)二:A服務(wù)調(diào)用B服務(wù),B服務(wù)調(diào)用C服務(wù),C服務(wù)調(diào)用D服務(wù)
在A服務(wù)的代碼里面調(diào)用B服務(wù),顯式通過IP地址調(diào)用:http://123.123.123.123:8888/java3y/3,(同樣地)B->C,C->D
功能實(shí)現(xiàn)三:D服務(wù)調(diào)用B服務(wù),B服務(wù)調(diào)用C服務(wù)
在D服務(wù)的代碼里面調(diào)用B服務(wù),顯式通過IP地址調(diào)用:http://123.123.123.123:8888/java3y/3,(同樣地)B->C
.....等等等等
萬一,我們B服務(wù)的IP地址變了,想想會(huì)出現(xiàn)什么問題:A服務(wù),D服務(wù)(等等)需要手動(dòng)更新B服務(wù)的地址
在服務(wù)多的情況下,手動(dòng)來維護(hù)這些靜態(tài)配置就是噩夢!
為了解決微服務(wù)架構(gòu)中的服務(wù)實(shí)例維護(hù)問題(ip地址), 產(chǎn)生了大量的服務(wù)治理框架和產(chǎn)品。 這些框架和產(chǎn)品的實(shí)現(xiàn)都圍繞著服務(wù)注冊(cè)與服務(wù)發(fā)現(xiàn)機(jī)制來完成對(duì)微服務(wù)應(yīng)用實(shí)例的自動(dòng)化管理。
在SpringCloud中我們的服務(wù)治理框架一般使用的就是Eureka。
我們的問題:
現(xiàn)在有A、B、C、D四個(gè)服務(wù),它們之間會(huì)互相調(diào)用(而且IP地址很可能會(huì)發(fā)生變化),一旦某個(gè)服務(wù)的IP地址變了,那服務(wù)中的代碼要跟著變,手動(dòng)維護(hù)這些靜態(tài)配置(IP)非常麻煩!
Eureka是這樣解決上面所說的情況的:
創(chuàng)建一個(gè)E服務(wù),將A、B、C、D四個(gè)服務(wù)的信息都注冊(cè)到E服務(wù)上,E服務(wù)維護(hù)這些已經(jīng)注冊(cè)進(jìn)來的信息
A、B、C、D四個(gè)服務(wù)都可以拿到Eureka(服務(wù)E)那份注冊(cè)清單。A、B、C、D四個(gè)服務(wù)互相調(diào)用不再通過具體的IP地址,而是通過服務(wù)名來調(diào)用!
拿到注冊(cè)清單--->注冊(cè)清單上有服務(wù)名--->自然就能夠拿到服務(wù)具體的位置了(IP)。
其實(shí)簡單來說就是:代碼中通過服務(wù)名找到對(duì)應(yīng)的IP地址(IP地址會(huì)變,但服務(wù)名一般不會(huì)變)
5.1Eureka細(xì)節(jié)Eureka專門用于給其他服務(wù)注冊(cè)的稱為Eureka Server(服務(wù)注冊(cè)中心),其余注冊(cè)到Eureka Server的服務(wù)稱為Eureka Client。
在Eureka Server一般我們會(huì)這樣配置:
register-with-eureka: false #false表示不向注冊(cè)中心注冊(cè)自己。 fetch-registry: false #false表示自己端就是注冊(cè)中心,我的職責(zé)就是維護(hù)服務(wù)實(shí)例,并不需要去檢索服務(wù)
Eureka Client分為服務(wù)提供者和服務(wù)消費(fèi)者。
但很可能,某服務(wù)既是服務(wù)提供者又是服務(wù)消費(fèi)者。
如果在網(wǎng)上看到SpringCloud的某個(gè)服務(wù)配置沒有"注冊(cè)"到Eureka-Server也不用過于驚訝(但是它是可以獲取Eureka服務(wù)清單的)
很可能只是作者把該服務(wù)認(rèn)作為單純的服務(wù)消費(fèi)者,單純的服務(wù)消費(fèi)者無需對(duì)外提供服務(wù),也就無須注冊(cè)到Eureka中了
eureka: client: register-with-eureka: false # 當(dāng)前微服務(wù)不注冊(cè)到eureka中(消費(fèi)端) service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
下面是Eureka的治理機(jī)制:
服務(wù)提供者
服務(wù)注冊(cè):啟動(dòng)的時(shí)候會(huì)通過發(fā)送REST請(qǐng)求的方式將自己注冊(cè)到Eureka Server上,同時(shí)帶上了自身服務(wù)的一些元數(shù)據(jù)信息。
服務(wù)續(xù)約:在注冊(cè)完服務(wù)之后,服務(wù)提供者會(huì)維護(hù)一個(gè)心跳用來持續(xù)告訴Eureka Server: "我還活著 ” 、
服務(wù)下線:當(dāng)服務(wù)實(shí)例進(jìn)行正常的關(guān)閉操作時(shí),它會(huì)觸發(fā)一個(gè)服務(wù)下線的REST請(qǐng)求給Eureka Server, 告訴服務(wù)注冊(cè)中心:“我要下線了 ”。
服務(wù)消費(fèi)者
獲取服務(wù):當(dāng)我們啟動(dòng)服務(wù)消費(fèi)者的時(shí)候,它會(huì)發(fā)送一個(gè)REST請(qǐng)求給服務(wù)注冊(cè)中心,來獲取上面注冊(cè)的服務(wù)清單
服務(wù)調(diào)用:服務(wù)消費(fèi)者在獲取服務(wù)清單后,通過服務(wù)名可以獲得具體提供服務(wù)的實(shí)例名和該實(shí)例的元數(shù)據(jù)信息。在進(jìn)行服務(wù)調(diào)用的時(shí)候,優(yōu)先訪問同處一個(gè)Zone中的服務(wù)提供方。
Eureka Server(服務(wù)注冊(cè)中心):
失效剔除:默認(rèn)每隔一段時(shí)間(默認(rèn)為60秒) 將當(dāng)前清單中超時(shí)(默認(rèn)為90秒)沒有續(xù)約的服務(wù)剔除出去。
自我保護(hù):。EurekaServer 在運(yùn)行期間,會(huì)統(tǒng)計(jì)心跳失敗的比例在15分鐘之內(nèi)是否低于85%(通常由于網(wǎng)絡(luò)不穩(wěn)定導(dǎo)致)。 Eureka Server會(huì)將當(dāng)前的實(shí)例注冊(cè)信息保護(hù)起來, 讓這些實(shí)例不會(huì)過期,盡可能保護(hù)這些注冊(cè)信息。
最后,我們就有了這張圖:
舉個(gè)例子:
3y跟女朋友去東站的東方寶泰逛街,但不知道東方寶泰有什么好玩的。于是就去物業(yè)搜了一下東方寶泰商戶清單,發(fā)現(xiàn)一樓有優(yōu)衣庫,二樓有星巴克,三樓有麥當(dāng)勞。
在優(yōu)衣庫旁邊,有新開張的KFC,在墻壁打上了很大的標(biāo)識(shí)“歡迎KFC入駐東方寶泰”。
商家們需要定時(shí)交物業(yè)費(fèi)給物業(yè)。
物業(yè)維持東方寶泰的穩(wěn)定性。如果某個(gè)商家不想在東方寶泰運(yùn)營了,告訴了物業(yè)。物業(yè)自然就會(huì)將其在東方寶泰商戶清單去除。
優(yōu)秀博文:
Spring Cloud Eureka詳解:https://blog.csdn.net/sunhuiliang85/article/details/76222517
《Spring Cloud Netflix》 -- 服務(wù)注冊(cè)和服務(wù)發(fā)現(xiàn)-Eureka 的使用:https://zhuanlan.zhihu.com/p/26472547
微服務(wù)架構(gòu):Eureka參數(shù)配置項(xiàng)詳解:https://www.cnblogs.com/fangfuhai/p/7070325.html
六、引出RestTemplate和Ribbon通過Eureka服務(wù)治理框架,我們可以通過服務(wù)名來獲取具體的服務(wù)實(shí)例的位置了(IP)。一般在使用SpringCloud的時(shí)候不需要自己手動(dòng)創(chuàng)建HttpClient來進(jìn)行遠(yuǎn)程調(diào)用。
可以使用Spring封裝好的RestTemplate工具類,使用起來很簡單:
// 傳統(tǒng)的方式,直接顯示寫死IP是不好的! //private static final String REST_URL_PREFIX = "http://localhost:8001"; // 服務(wù)實(shí)例名 private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT"; /** * 使用 使用restTemplate訪問restful接口非常的簡單粗暴無腦。 (url, requestMap, * ResponseBean.class)這三個(gè)參數(shù)分別代表 REST請(qǐng)求地址、請(qǐng)求參數(shù)、HTTP響應(yīng)轉(zhuǎn)換被轉(zhuǎn)換成的對(duì)象類型。 */ @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/consumer/dept/add") public boolean add(Dept dept) { return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class); }
為了實(shí)現(xiàn)服務(wù)的高可用,我們可以將服務(wù)提供者集群。比如說,現(xiàn)在一個(gè)秒殺系統(tǒng)設(shè)計(jì)出來了,準(zhǔn)備上線了。在11月11號(hào)時(shí)為了能夠支持高并發(fā),我們開多臺(tái)機(jī)器來支持并發(fā)量。
現(xiàn)在想要這三個(gè)秒殺系統(tǒng)合理攤分用戶的請(qǐng)求(專業(yè)來說就是負(fù)載均衡),可能你會(huì)想到nginx。
其實(shí)SpringCloud也支持的負(fù)載均衡功能,只不過它是客戶端的負(fù)載均衡,這個(gè)功能實(shí)現(xiàn)就是Ribbon!
負(fù)載均衡又區(qū)分了兩種類型:
客戶端負(fù)載均衡(Ribbon)
服務(wù)實(shí)例的清單在客戶端,客戶端進(jìn)行負(fù)載均衡算法分配。
(從上面的知識(shí)我們已經(jīng)知道了:客戶端可以從Eureka Server中得到一份服務(wù)清單,在發(fā)送請(qǐng)求時(shí)通過負(fù)載均衡算法,在多個(gè)服務(wù)器之間選擇一個(gè)進(jìn)行訪問)
服務(wù)端負(fù)載均衡(Nginx)
服務(wù)實(shí)例的清單在服務(wù)端,服務(wù)器進(jìn)行負(fù)載均衡算法分配
所以,我們的圖可以畫成這樣:
6.1Ribbon細(xì)節(jié)Ribbon是支持負(fù)載均衡,默認(rèn)的負(fù)載均衡策略是輪詢,我們也是可以根據(jù)自己實(shí)際的需求自定義負(fù)載均衡策略的。
@Configuration public class MySelfRule { @Bean public IRule myRule() { //return new RandomRule();// Ribbon默認(rèn)是輪詢,我自定義為隨機(jī) //return new RoundRobinRule();// Ribbon默認(rèn)是輪詢,我自定義為隨機(jī) return new RandomRule_ZY();// 我自定義為每臺(tái)機(jī)器5次 } }
實(shí)現(xiàn)起來也很簡單:繼承AbstractLoadBalancerRule類,重寫public Server choose(ILoadBalancer lb, Object key)即可。
SpringCloud 在CAP理論是選擇了AP的,在Ribbon中還可以配置重試機(jī)制的(有興趣的同學(xué)可以去搜搜)~
舉個(gè)例子:
3y跟女朋友過了幾個(gè)月,又去東方寶泰了。由于記性不好,又去物業(yè)那弄了一份東方寶泰商戶清單。
這次看到東方寶泰又開了一間麥當(dāng)勞,一間在二樓,一間在三樓。原來生意太好了,為了能提高用戶體驗(yàn),在二樓多開了一間麥當(dāng)勞。
這時(shí),3y問女朋友:“去哪間麥當(dāng)勞比較好?要不我們拋硬幣決定?”3y女朋友說:”你是不是傻,肯定哪間近去哪間啊“
優(yōu)秀博文:
擼一擼Spring Cloud Ribbon的原理-負(fù)載均衡策略:https://www.cnblogs.com/kongxianghai/p/8477781.html
七、引出Hystrix到目前為止,我們的服務(wù)看起來好像挺好的了:能夠根據(jù)服務(wù)名來遠(yuǎn)程調(diào)用其他的服務(wù),可以實(shí)現(xiàn)客戶端的負(fù)載均衡。
但是,如果我們?cè)?strong>調(diào)用多個(gè)遠(yuǎn)程服務(wù)時(shí),某個(gè)服務(wù)出現(xiàn)延遲,會(huì)怎么樣??
在高并發(fā)的情況下,由于單個(gè)服務(wù)的延遲,可能導(dǎo)致所有的請(qǐng)求都處于延遲狀態(tài),甚至在幾秒鐘就使服務(wù)處于負(fù)載飽和的狀態(tài),資源耗盡,直到不可用,最終導(dǎo)致這個(gè)分布式系統(tǒng)都不可用,這就是“雪崩”。
針對(duì)上述問題, Spring Cloud Hystrix實(shí)現(xiàn)了斷路器、線程隔離等一系列服務(wù)保護(hù)功能。
Fallback(失敗快速返回):當(dāng)某個(gè)服務(wù)單元發(fā)生故障(類似用電器發(fā)生短路)之后,通過斷路器的故障監(jiān)控(類似熔斷保險(xiǎn)絲), 向調(diào)用方返回一個(gè)錯(cuò)誤響應(yīng), 而不是長時(shí)間的等待。這樣就不會(huì)使得線程因調(diào)用故障服務(wù)被長時(shí)間占用不釋放,避免了故障在分布式系統(tǒng)中的蔓延。
資源/依賴隔離(線程池隔離):它會(huì)為每一個(gè)依賴服務(wù)創(chuàng)建一個(gè)獨(dú)立的線程池,這樣就算某個(gè)依賴服務(wù)出現(xiàn)延遲過高的情況,也只是對(duì)該依賴服務(wù)的調(diào)用產(chǎn)生影響, 而不會(huì)拖慢其他的依賴服務(wù)。
Hystrix提供幾個(gè)熔斷關(guān)鍵參數(shù):滑動(dòng)窗口大小(20)、 熔斷器開關(guān)間隔(5s)、錯(cuò)誤率(50%)
每當(dāng)20個(gè)請(qǐng)求中,有50%失敗時(shí),熔斷器就會(huì)打開,此時(shí)再調(diào)用此服務(wù),將會(huì)直接返回失敗,不再調(diào)遠(yuǎn)程服務(wù)。
直到5s鐘之后,重新檢測該觸發(fā)條件,判斷是否把熔斷器關(guān)閉,或者繼續(xù)打開。
Hystrix還有請(qǐng)求合并、請(qǐng)求緩存這樣強(qiáng)大的功能,在此我就不具體說明了,有興趣的同學(xué)可繼續(xù)深入學(xué)習(xí)~
7.1Hystrix儀表盤Hystrix儀表盤:它主要用來實(shí)時(shí)監(jiān)控Hystrix的各項(xiàng)指標(biāo)信息。通過Hystrix Dashboard反饋的實(shí)時(shí)信息,可以幫助我們快速發(fā)現(xiàn)系統(tǒng)中存在的問題,從而及時(shí)地采取應(yīng)對(duì)措施。
啟動(dòng)時(shí)的頁面:
監(jiān)控單服務(wù)的頁面:
我們現(xiàn)在的服務(wù)是這樣的:
除了可以開啟單個(gè)實(shí)例的監(jiān)控頁面之外,還有一個(gè)監(jiān)控端點(diǎn) /turbine.stream 是對(duì)集群使用的。 從端點(diǎn)的命名中,可以引入Turbine, 通過它來匯集監(jiān)控信息,并將聚合后的信息提供給 HystrixDashboard 來集中展示和監(jiān)控。
舉個(gè)例子:
3y和女朋友決定去萬達(dá)玩,去到萬達(dá)的停車場發(fā)現(xiàn)在負(fù)一層已經(jīng)大大寫上“負(fù)一層已停滿,請(qǐng)下負(fù)二層,負(fù)二層空余停車位還有100個(gè)!”
這時(shí),3y就跟女朋友說:“萬達(dá)停車場是做得挺好的,如果它沒有直接告知我負(fù)一層已滿,可能我就去負(fù)一層找位置了,要是一堆人跑去負(fù)一層但都找不到車位的話,恐怕就塞死了”。3y接著說:“看停車位的狀態(tài)也做得不錯(cuò),在停車位上頭有一個(gè)感應(yīng)(監(jiān)控),如果是紅色就代表已被停了,如果是綠色就說明停車位是空的”。
3y女朋友不屑的說:“你話是真的多”
參考資料:
Hystrix ,為什么說它是每個(gè)系統(tǒng)不可或缺的開源框架?https://zhuanlan.zhihu.com/p/34304136
深入理解Hystrix之文檔翻譯:https://zhuanlan.zhihu.com/p/28523060
談?wù)勎覍?duì)服務(wù)熔斷、服務(wù)降級(jí)的理解:https://blog.csdn.net/guwei9111986/article/details/51649240
Hystrix幾篇文章《青芒》:https://segmentfault.com/u/yedge/articles
八、引出Feign上面已經(jīng)介紹了Ribbon和Hystrix了,可以發(fā)現(xiàn)的是:他倆作為基礎(chǔ)工具類框架廣泛地應(yīng)用在各個(gè)微服務(wù)的實(shí)現(xiàn)中。我們會(huì)發(fā)現(xiàn)對(duì)這兩個(gè)框架的使用幾乎是同時(shí)出現(xiàn)的。
為了簡化我們的開發(fā),Spring Cloud Feign出現(xiàn)了!它基于 Netflix Feign 實(shí)現(xiàn),整合了 Spring Cloud Ribbon 與 Spring Cloud Hystrix, 除了整合這兩者的強(qiáng)大功能之外,它還提
供了聲明式的服務(wù)調(diào)用(不再通過RestTemplate)。
Feign是一種聲明式、模板化的HTTP客戶端。在Spring Cloud中使用Feign, 我們可以做到使用HTTP請(qǐng)求遠(yuǎn)程服務(wù)時(shí)能與調(diào)用本地方法一樣的編碼體驗(yàn),開發(fā)者完全感知不到這是遠(yuǎn)程方法,更感知不到這是個(gè)HTTP請(qǐng)求。
下面就簡單看看Feign是怎么優(yōu)雅地實(shí)現(xiàn)遠(yuǎn)程調(diào)用的:
服務(wù)綁定:
// value --->指定調(diào)用哪個(gè)服務(wù) // fallbackFactory--->熔斷器的降級(jí)提示 @FeignClient(value = "MICROSERVICECLOUD-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class) public interface DeptClientService { // 采用Feign我們可以使用SpringMVC的注解來對(duì)服務(wù)進(jìn)行綁定! @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) public Dept get(@PathVariable("id") long id); @RequestMapping(value = "/dept/list", method = RequestMethod.GET) public Listlist(); @RequestMapping(value = "/dept/add", method = RequestMethod.POST) public boolean add(Dept dept); }
Feign中使用熔斷器:
/** * Feign中使用斷路器 * 這里主要是處理異常出錯(cuò)的情況(降級(jí)/熔斷時(shí)服務(wù)不可用,fallback就會(huì)找到這里來) */ @Component // 不要忘記添加,不要忘記添加 public class DeptClientServiceFallbackFactory implements FallbackFactory{ @Override public DeptClientService create(Throwable throwable) { return new DeptClientService() { @Override public Dept get(long id) { return new Dept().setDeptno(id).setDname("該ID:" + id + "沒有沒有對(duì)應(yīng)的信息,Consumer客戶端提供的降級(jí)信息,此刻服務(wù)Provider已經(jīng)關(guān)閉") .setDb_source("no this database in MySQL"); } @Override public List list() { return null; } @Override public boolean add(Dept dept) { return false; } }; } }
調(diào)用:
九、引出Zuul基于上面的學(xué)習(xí),我們現(xiàn)在的架構(gòu)很可能會(huì)設(shè)計(jì)成這樣:
這樣的架構(gòu)會(huì)有兩個(gè)比較麻煩的問題:
路由規(guī)則與服務(wù)實(shí)例的維護(hù)間題:外層的負(fù)載均衡(nginx)需要維護(hù)所有的服務(wù)實(shí)例清單(圖上的OpenService)
簽名校驗(yàn)、 登錄校驗(yàn)冗余問題:為了保證對(duì)外服務(wù)的安全性, 我們?cè)诜?wù)端實(shí)現(xiàn)的微服務(wù)接口,往往都會(huì)有一定的權(quán)限校驗(yàn)機(jī)制,但我們的服務(wù)是獨(dú)立的,我們不得不在這些應(yīng)用中都實(shí)現(xiàn)這樣一套校驗(yàn)邏輯,這就會(huì)造成校驗(yàn)邏輯的冗余。
還是畫個(gè)圖來理解一下吧:
每個(gè)服務(wù)都有自己的IP地址,Nginx想要正確請(qǐng)求轉(zhuǎn)發(fā)到服務(wù)上,就必須維護(hù)著每個(gè)服務(wù)實(shí)例的地址!
更是災(zāi)難的是:這些服務(wù)實(shí)例的IP地址還有可能會(huì)變,服務(wù)之間的劃分也很可能會(huì)變。
http://123.123.123.123 http://123.123.123.124 http://123.123.123.125 http://123.123.123.126 http://123.123.123.127
購物車和訂單模塊都需要用戶登錄了才可以正常訪問,基于現(xiàn)在的架構(gòu),只能在購物車和訂單模塊都編寫校驗(yàn)邏輯,這無疑是冗余的代碼。
為了解決上面這些常見的架構(gòu)問題,API網(wǎng)關(guān)的概念應(yīng)運(yùn)而生。在SpringCloud中了提供了基于Netfl ix Zuul實(shí)現(xiàn)的API網(wǎng)關(guān)組件Spring Cloud Zuul。
Spring Cloud Zuul是這樣解決上述兩個(gè)問題的:
SpringCloud Zuul通過與SpringCloud Eureka進(jìn)行整合,將自身注冊(cè)為Eureka服務(wù)治理下的應(yīng)用,同時(shí)從Eureka中獲得了所有其他微服務(wù)的實(shí)例信息。外層調(diào)用都必須通過API網(wǎng)關(guān),使得將維護(hù)服務(wù)實(shí)例的工作交給了服務(wù)治理框架自動(dòng)完成。
在API網(wǎng)關(guān)服務(wù)上進(jìn)行統(tǒng)一調(diào)用來對(duì)微服務(wù)接口做前置過濾,以實(shí)現(xiàn)對(duì)微服務(wù)接口的攔截和校驗(yàn)。
Zuul天生就擁有線程隔離和斷路器的自我保護(hù)功能,以及對(duì)服務(wù)調(diào)用的客戶端負(fù)載均衡功能。也就是說:Zuul也是支持Hystrix和Ribbon。
關(guān)于Zuul還有很多知識(shí)點(diǎn)(由于篇幅問題,這里我就不細(xì)說了):
路由匹配(動(dòng)態(tài)路由)
過濾器實(shí)現(xiàn)(動(dòng)態(tài)過濾器)
默認(rèn)會(huì)過濾掉Cookie與敏感的HTTP頭信息(額外配置)
9.1可能對(duì)Zuul的疑問Zuul支持Ribbon和Hystrix,也能夠?qū)崿F(xiàn)客戶端的負(fù)載均衡。我們的Feign不也是實(shí)現(xiàn)客戶端的負(fù)載均衡和Hystrix的嗎?既然Zuul已經(jīng)能夠?qū)崿F(xiàn)了,那我們的Feign還有必要嗎?
或者可以這樣理解:
zuul是對(duì)外暴露的唯一接口相當(dāng)于路由的是controller的請(qǐng)求,而Ribbonhe和Fegin路由了service的請(qǐng)求
zuul做最外層請(qǐng)求的負(fù)載均衡 ,而Ribbon和Fegin做的是系統(tǒng)內(nèi)部各個(gè)微服務(wù)的service的調(diào)用的負(fù)載均衡
有了Zuul,還需要Nginx嗎?他倆可以一起使用嗎?
我的理解:Zuul和Nginx是可以一起使用的(畢竟我們的Zuul也是可以搭成集群來實(shí)現(xiàn)高可用的),要不要一起使用得看架構(gòu)的復(fù)雜度了(業(yè)務(wù))~~~
參考資料:
微服務(wù)與API網(wǎng)關(guān)(上): 為什么需要API網(wǎng)關(guān)?:http://blog.didispace.com/hzf-ms-apigateway-1/
談?wù)?API 網(wǎng)關(guān):https://www.jianshu.com/p/b52a2773e75f
談?wù)勎⒎?wù)中的 API 網(wǎng)關(guān)(API Gateway):https://www.cnblogs.com/savorboard/p/api-gateway.html
API網(wǎng)關(guān)性能比較:NGINX vs. ZUUL vs. Spring Cloud Gateway :http://www.360doc.com/content/18/0208/05/46368139_728502763.shtml
談API網(wǎng)關(guān)的背景、架構(gòu)以及落地方案:http://www.infoq.com/cn/news/2016/07/API-background-architecture-floo
zuul和nginx:https://zhuanlan.zhihu.com/p/37385481
十、引出SpringCloud Config隨著業(yè)務(wù)的擴(kuò)展,我們的服務(wù)會(huì)越來越多,越來越多。每個(gè)服務(wù)都有自己的配置文件。
既然是配置文件,給我們配置的東西,那難免會(huì)有些改動(dòng)的。
比如我們的Demo中,每個(gè)服務(wù)都寫上相同的配置文件。萬一我們有一天,配置文件中的密碼需要更換了,那就得三個(gè)都要重新更改。
在分布式系統(tǒng)中,某一個(gè)基礎(chǔ)服務(wù)信息變更,都很可能會(huì)引起一系列的更新和重啟
Spring Cloud Config項(xiàng)目是一個(gè)解決分布式系統(tǒng)的配置管理方案。它包含了Client和Server兩個(gè)部分,server提供配置文件的存儲(chǔ)、以接口的形式將配置文件的內(nèi)容提供出去,client通過接口獲取數(shù)據(jù)、并依據(jù)此數(shù)據(jù)初始化自己的應(yīng)用。
簡單來說,使用Spring Cloud Config就是將配置文件放到統(tǒng)一的位置管理(比如GitHub),客戶端通過接口去獲取這些配置文件。
在GitHub上修改了某個(gè)配置文件,應(yīng)用加載的就是修改后的配置文件。
SpringCloud Config其他的知識(shí):
在SpringCloud Config的服務(wù)端, 對(duì)于配置倉庫的默認(rèn)實(shí)現(xiàn)采用了Git,我們也可以配置SVN。
配置文件內(nèi)的信息加密和解密
修改了配置文件,希望不用重啟來動(dòng)態(tài)刷新配置,配合Spring Cloud Bus 使用~
使用SpringCloud Config可能的疑問:application.yml和 bootstrap.yml區(qū)別
https://www.cnblogs.com/BlogNetSpace/p/8469033.html
總結(jié)本文主要寫了SpringCloud的基礎(chǔ)知識(shí),希望大家看完能有所幫助~
SpringCloud的資料也很多,我整理一些我認(rèn)為比較好,想要深入的同學(xué)不妨看看下邊的資源~~~
SpringCloud系列文章參考資料:
史上最簡單的 SpringCloud 教程 | 終章https://blog.csdn.net/forezp/article/details/70148833
Spring Cloud基礎(chǔ)教程《程序員DD》http://blog.didispace.com/Spring-Cloud%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/
Spring Cloud 系列文章《純潔的微笑》:http://www.ityouknow.com/spring-cloud.html
SpringCloud系列文章:https://www.cnblogs.com/woshimrf/tag/SpringCloud/
SpringCloud系列文章《狂小白》:https://www.cnblogs.com/huangjuncong/tag/SpringCloud/
SpringCloud官方文檔:http://projects.spring.io/spring-cloud/
Spring Cloud 中文文檔:https://springcloud.cc/spring-cloud-dalston.html#_appendix_compendium_of_configuration_properties
參考書籍:
《SpringCloud 微服務(wù)實(shí)戰(zhàn)》
SpringCloud GitHub Demo(看完文章的同學(xué)可以自己練手玩玩,寫好了ReadMe了):
https://github.com/ZhongFuCheng3y/msc-Demo
如果想看更多的原創(chuàng)技術(shù)文章,歡迎大家關(guān)注我的微信公眾號(hào):Java3y。Java技術(shù)群討論:742919422。公眾號(hào)還有海量的視頻資源哦,關(guān)注即可免費(fèi)領(lǐng)取。
可能感興趣的鏈接:
文章的目錄導(dǎo)航(微信公眾號(hào)端):https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
文章的目錄導(dǎo)航(PC端):http://www.zhongfucheng.bitcron.com/post/shou-ji/pcduan-wen-zhang-dao-hang
海量精美腦圖:http://www.zhongfucheng.bitcron.com/post/shou-ji/nao-tu-da-quan
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/40080.html
摘要:文本已收錄至我的倉庫,歡迎回顧上一篇大型網(wǎng)站系統(tǒng)與中間件讀書筆記一這周周末讀了第四章,現(xiàn)在過來做做筆記,希望能幫助到大家。沒錯(cuò),我們通過肯定是可以完成兩個(gè)系統(tǒng)之間的通信的問題的。 前言 只有光頭才能變強(qiáng)。文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y 回顧上一篇: 《大型網(wǎng)站系統(tǒng)與Java中間件》讀書筆記(一)...
摘要:另一個(gè)用戶請(qǐng)求過來,負(fù)載均衡器指派這個(gè)請(qǐng)求到服務(wù)器。這樣就平攤了請(qǐng)求這種方式就叫做輪詢策略還有很多種,就看你想怎么實(shí)現(xiàn)了,反正這個(gè)邏輯的代碼放在負(fù)載均衡器上。 前言 只有光頭才能變強(qiáng)。文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y 這本書買了一段時(shí)間了,之前在杭州沒帶過去,現(xiàn)在讀完第三章,來做做筆記 showI...
摘要:集群系統(tǒng)中的單個(gè)計(jì)算機(jī)通常稱為節(jié)點(diǎn),通常通過局域網(wǎng)連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負(fù)責(zé)了。于是,小周和就變成了協(xié)作開發(fā)。都是為了項(xiàng)目正常運(yùn)行以及迭代。 一、前言 只有光頭才能變強(qiáng) 認(rèn)識(shí)我的朋友可能都知道我這陣子去實(shí)習(xí)啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
摘要:前言只有光頭才能變強(qiáng)沒錯(cuò),這篇主要跟大家一起入門機(jī)器學(xué)習(xí)。所以我們可以總結(jié)出人工智能機(jī)器學(xué)習(xí)深度學(xué)習(xí)之間的關(guān)系是這樣的機(jī)器學(xué)習(xí),是實(shí)現(xiàn)人工智能的重要方法。機(jī)器學(xué)習(xí)資源,可關(guān)注我的公眾號(hào),回復(fù)機(jī)器學(xué)習(xí)即可領(lǐng)取。有周志華機(jī)器學(xué)習(xí)電子版。 前言 只有光頭才能變強(qiáng) 沒錯(cuò),這篇主要跟大家一起入門機(jī)器學(xué)習(xí)。作為一個(gè)開發(fā)者,人工智能肯定是聽過的。作為一個(gè)開發(fā)面試者,肯定也會(huì)見過機(jī)器學(xué)習(xí)這個(gè)崗位(反正...
閱讀 1804·2023-04-26 02:32
閱讀 567·2021-11-18 13:12
閱讀 2446·2021-10-20 13:48
閱讀 2515·2021-10-14 09:43
閱讀 3825·2021-10-11 10:58
閱讀 3483·2021-09-30 10:00
閱讀 2932·2019-08-30 15:53
閱讀 3487·2019-08-30 15:53