Dubbo_負(fù)載均衡算法_第1頁(yè)
Dubbo_負(fù)載均衡算法_第2頁(yè)
Dubbo_負(fù)載均衡算法_第3頁(yè)
Dubbo_負(fù)載均衡算法_第4頁(yè)
Dubbo_負(fù)載均衡算法_第5頁(yè)
已閱讀5頁(yè),還剩11頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、負(fù)載均衡算法在集群負(fù)載均衡時(shí),Dubbo提供了 4種均衡策略,女口: Random LoadBalanee(隨機(jī)均衡算 法)、RoundRobin LoadBalanee權(quán)重輪循均衡算法卜LeastAetion LoadBalanee(最少活躍調(diào)用 數(shù)均衡算法)、ConsistentHash LoadBalance(致性Hash均衡算法)。缺省時(shí)為 Random隨機(jī)調(diào) 用。這四種算法的原理簡(jiǎn)要介紹如下:1、RoundRobin LoadBalanceRound-Robin既是輪詢算法,是按照公約后的權(quán)重設(shè)置輪詢比率,即權(quán)重輪詢算法(Weighted Round-Robin),它是基于輪詢算法改

2、進(jìn)而來的。這里之所以寫RoundRobin是為了跟Dubbo中的內(nèi)容保持一致。輪詢調(diào)度算法的原理是:每一次把來自用戶的請(qǐng)求輪流分配給內(nèi)部中的服務(wù)器。女口:從1開始,一直到N(其中,N是內(nèi)部服務(wù)器的總個(gè)數(shù)),然后重新開始循環(huán)。該算法的優(yōu)點(diǎn):其簡(jiǎn)潔性,它無需記錄當(dāng)前所有連接的狀態(tài),所以它是一種無狀態(tài)調(diào)度。該算法的缺點(diǎn):輪詢調(diào)度算法假設(shè)所有服務(wù)器的處理性能都相同,不關(guān)心每臺(tái)服務(wù)器的當(dāng)前連接數(shù)和響應(yīng)速度。當(dāng)請(qǐng)求服務(wù)間隔時(shí)間變化比較大時(shí),輪詢調(diào)度算法容易導(dǎo)致服務(wù)器間的負(fù)載不平衡。所以此種均衡算法適合于服務(wù)器組中的所有服務(wù)器都有相同的軟硬件配置并且平均服 務(wù)請(qǐng)求相對(duì)均衡的情況。但是,在實(shí)際情況中,可能并不

3、是這種情況。由于每臺(tái)服務(wù)器的配置、安裝的業(yè)務(wù)應(yīng)用等不同,其處理能力會(huì)不一樣。 所以,我們根據(jù)服務(wù)器的不同處理能力,給每個(gè)服務(wù)器分配不同的權(quán)值,使其能夠接受相應(yīng)權(quán)值數(shù)的服務(wù)請(qǐng)求。權(quán)重輪詢調(diào)度算法流程假設(shè)有一組服務(wù)器 S = SO, S1,Sn1, W(Si)表示服務(wù)器Si的權(quán)值,一個(gè)指示變量 i表示上一次選擇的服務(wù)器,指示變量cw表示當(dāng)前調(diào)度的權(quán)值,max(S)表示集合S中所有服務(wù)器的最大權(quán)值,gcd(S)表示集合S中所有服務(wù)器權(quán)值的最大公約數(shù)。變量i初始化為-1 ,cw初始化為零。其算法如下:while (true) i = (i + 1) mod n;if (i = 0) cw = cw -

4、 gcd(S);if (cw <= 0) cw = max(S); if (cw = 0) return NULL;if (W(Si) >= cw)return Si;3:1:1:1。這種算法的邏輯實(shí)現(xiàn)如圖 2所示,圖中我們假定四臺(tái)服務(wù)器的處理能力為負(fù)載均衡算法一權(quán)重輪詢調(diào)度客戶端路山器費(fèi)載溝衝器圖1權(quán)重輪詢調(diào)度實(shí)現(xiàn)邏輯圖示由于權(quán)重輪詢調(diào)度算法考慮到了不同服務(wù)器的處理能力, 所以這種均衡算法能確保高性 能的服務(wù)器得到更多的使用率, 避免低性能的服務(wù)器負(fù)載過重。 所以,在實(shí)際應(yīng)用中比較常 見。2、ConsistentHash LoadBalance一致性Hash,相同參數(shù)的請(qǐng)求總是發(fā)

5、到同一個(gè)提供者。一:一致性Hash算法可以解決服務(wù)提供者的增加、移除及掛掉時(shí)的情況,能盡可能小的改變已存在 key映射關(guān)系,盡可能的滿足單調(diào)性的要求。二:一致性Hash通過構(gòu)建虛擬節(jié)點(diǎn),能盡可能避免分配失衡,具有很好的平衡性。一致性Hash下面就來按照 5個(gè)步驟簡(jiǎn)單講講 consistent hash算法的基本原理。因?yàn)?以下資料來自于互聯(lián)網(wǎng),現(xiàn)說明幾點(diǎn):一、下面例子中的對(duì)象就相當(dāng)于Client發(fā)的請(qǐng)求,cache相當(dāng)于服務(wù)提供者。環(huán)形hash空間考慮通常的hash算法都是將 value映射到一個(gè) 32為的key值,也即是02A32-1次 方的數(shù)值空間;我們可以將這個(gè)空間想象成一個(gè)首 (0)尾

6、(2A32-1)相接的圓環(huán),如下面圖2所 示的那樣。把對(duì)象映射到hash空間接下來考慮 4個(gè)對(duì)象 object1object4,通過 hash函數(shù)計(jì)算出的hash值key在環(huán)上的分布如圖3所示。hash(objectl) = keyl;hash(object4) = key4;把cache映射到hash空間Consistent hashing的基本思想就是將對(duì)象和cache都映射到同一個(gè) hash數(shù)值空間中,并且使用相同的hash算法。假設(shè)當(dāng)前有 A,B和C共3臺(tái)cache,那么其映射結(jié)果將如圖4所示,他們?cè)?hash空間中,以對(duì)應(yīng)的 hash值排列。hash(cache A) = key A

7、;hash(cache C) = key C;圖4 cache和對(duì)象的 key值分布說到這里,順便提一下 cache的hash計(jì)算,一般的方法可以使用cache機(jī)器的IP地址或者機(jī)器名作為hash輸入。把對(duì)象映射到cache現(xiàn)在cache和對(duì)象都已經(jīng)通過同一個(gè)hash算法映射到 hash數(shù)值空間中了,接下來要考慮的就是如何將對(duì)象映射到cache上面了。在這個(gè)環(huán)形空間中,如果沿著順時(shí)針方向從對(duì)象的key值出發(fā),直到遇見一個(gè) cache ,那么就將該對(duì)象存儲(chǔ)在這個(gè)cache上,因?yàn)閷?duì)象和cache的hash值是固定的,因此這個(gè)cache必然是唯一和確定的。這樣不就找到了對(duì)象和cache的映射方法了

8、嗎!依然繼續(xù)上面的例子(參見圖4),那么根據(jù)上面的方法,對(duì)象 objectl將被存儲(chǔ)到cache A 上;object2 和 object3 對(duì)應(yīng)到 cache C ; object4 對(duì)應(yīng)到 cache B ;考察cache 的變動(dòng)前面講過,一致性Hash算法可以解決服務(wù)提供者的增加、移除及掛掉時(shí)的情況,能盡 可能小的改變已存在 key映射關(guān)系,盡可能的滿足單調(diào)性的要求。移除cache考慮假設(shè) cache B掛掉了,根據(jù)上面講到的映射方法,這時(shí)受影響的將僅是那些沿cache B逆時(shí)針遍歷直到下一個(gè)cache ( cache C)之間的對(duì)象,也即是本來映射到cacheB上的那些對(duì)象。因此這里僅

9、需要變動(dòng)對(duì)象object4,將其重新映射到cache C上即可;參見圖 5 。添力口 cache再考慮添加一臺(tái)新的cache D的情況,假設(shè)在這個(gè)環(huán)形hash空間中,cache D被映射在對(duì)象 object2和object3之間。這時(shí)受影響的將僅是那些沿cache D逆時(shí)針遍歷直到下一個(gè)cache ( cache B)之間的對(duì)象(它們是也本來映射到cache C上對(duì)象的一部分),將這些對(duì)象重新映射到cache D上即可。因此這里僅需要變動(dòng)對(duì)象object2,將其重新映射到cache D上;參見圖 6。山a ICaclie CC3GtW 0.CatfieD圖6 添加cache D后的映射關(guān)系虛擬

10、節(jié)點(diǎn)考慮Hash算法的另一個(gè)指標(biāo)是平衡性(Balanee),定義如下:平衡性是指哈希的結(jié)果能夠盡可能分布到所有的緩沖中去,這樣可以使得所有的緩沖空間都得到利用。hash算法并不是保證絕對(duì)的平衡,如果cache較少的話,對(duì)象并不能被均勻的映射到cache上,比如在上面的例子中,僅部署 cache A和cache C的情況下,在 4個(gè)對(duì)象中, cache A 僅存儲(chǔ)了 objectl ,而 cache C 則存儲(chǔ)了 object2、 object3 和 object4 ;分布是 很不均衡的。為了解決這種情況,consistent hashing弓I入了 "虛擬節(jié)點(diǎn)”的概念,它可以如下定義

11、:"虛擬節(jié)點(diǎn)” (virtual node )是實(shí)際節(jié)點(diǎn)在 hash空間的復(fù)制品(replica ), 一實(shí)際 個(gè)節(jié)點(diǎn)對(duì)應(yīng)了若干個(gè)“虛擬節(jié)點(diǎn)”,這個(gè)對(duì)應(yīng)個(gè)數(shù)也成為“復(fù)制個(gè)數(shù)”,“虛擬節(jié)點(diǎn)”在 hash 空間中以hash值排列。仍以僅部署 cache A和cache C的情況為例,在圖 5中我們已經(jīng)看到,cache分布并不均勻?,F(xiàn)在我們引入虛擬節(jié)點(diǎn),并設(shè)置“復(fù)制個(gè)數(shù)”為2,這就意味著一共會(huì)存在4個(gè)“虛擬節(jié)點(diǎn)” ,cache A1, cache A2 代表了 cache A ; cache C1, cache C2 代表了 cache C ; 假設(shè)一種比較理想的情況,參見圖 7。此時(shí),對(duì)

12、象到“虛擬節(jié)點(diǎn)”的映射關(guān)系為:objec1->cache A2 ; objec2->cache A1 ; objec3->cache C1 ; objec4->cache C2 ; 因此對(duì)象 object1和object2 都被映射到了 cache A上,而 object3和object4 映射 到了 cache C上;平衡性有了很大提高。引入“虛擬節(jié)點(diǎn)”后,映射關(guān)系就從對(duì)象-> 節(jié)點(diǎn)轉(zhuǎn)換到了 對(duì)象-> 虛擬節(jié)點(diǎn)。 查詢物體所在cache時(shí)的映射關(guān)系如圖 8所示。圖8查詢對(duì)象所在 cache“虛擬節(jié)點(diǎn)”的 hash計(jì)算可以采用對(duì)應(yīng)節(jié)點(diǎn)的IP地址加數(shù)字后綴的方

13、式。例如假設(shè)cache A 的 IP 地址為 41。引入“虛擬節(jié)點(diǎn)”前,計(jì)算cache A 的hash 值:Hash( “ 41”);引入“虛擬節(jié)點(diǎn)”后,計(jì)算“虛擬節(jié)”點(diǎn)cache A1 和 cache A2 的 hash 值Hash( “ 41#1/ cache A1Hash( “ 41#2/ cache A23、Random LoadBalance 與 LeastAction LoadBalanceRandom LoadBalanee 與 LeastAction LoadBalanee 算法比較

14、簡(jiǎn)單,可以參照Dubbo 文檔中的給的描述及后面代碼附錄。Dubbo文檔截圖如下圖9所示:負(fù)裁均衡可嘆自行曠展戾裁塩衙黃略,飆:負(fù)裁均循護(hù).密Random LoadSalsnCQ柱一彳槪面上碰屋葩枇草高,但調(diào)便璽魁犬卅殆他均勻rr且抜樨華便用祝重左也憶撓均勻,肖和于吐花調(diào)壘擺浜者枚垂RouEidRcliin LoadQalancE舍血撥扯約啟的惶垂宦雖輪猶比宰*存芯慢的提卑者星祖語(yǔ)求可樹比如:革二臺(tái)機(jī)券很輻、但曼輕,當(dāng)告康訓(xùn)到鎖二臺(tái)時(shí)議去左珈復(fù)而夏N號(hào)有佶家卻慕在訓(xùn)到第二臺(tái)上LeastActive LoadBalance億少酒置調(diào)吊埶f目?jī)諣?wèi)躍數(shù)的陋機(jī),活躍埶指詭用前右計(jì)數(shù)琵。悵唱m拒快者收到

15、更少詩(shī)求*因?yàn)橐猜奈赵栁羰勒{(diào)用前后計(jì)數(shù)差會(huì)越丸*CarwirtontHath LoatIBahnco一敦性卜朋h,羽同養(yǎng)迪能請(qǐng)求爼導(dǎo)左針同一垠盟盍"*當(dāng)來一臺(tái)提快酋畦科,匣農(nóng)忘征謖提供肯前謫求,基亍圭擬節(jié)點(diǎn),平離劉基匂提棋吉r不嘗引握劇理寅動(dòng)算法豊見 I ittp - on v.'l(ij.iC'!ia ji j .-. it Ccnsniont_ha8hirc*諛譽(yù)F.圖第一A夢(mèng)埶H少PSD軍童驚軀 倩酉潔屈itbtn 側(cè)刑訛rk中產(chǎn)"I齡h a ri minis' :iiiifr="Ci 1' >缺總用儲(chǔ)0怡童撫節(jié)賓,如

16、累矍俺£k ffi0£ffi<tlutoo parameter Key= hash nodes1 alLe= 320 />圖9負(fù)載均衡算法4、附錄1、RandomLoadBalanee算法public class RandomLoadBalanee extends AbstractLoadBalanee publicstatic final String NAME = "random"private final Random random = new Random();protected <T> Invoker<T>

17、doSelect(List<Invoker<T>> invokers, URL url, Invocation in vocati on) int length = invokers.size(); / 總個(gè)數(shù)int totalWeight = 0; / 總權(quán)重boolean sameWeight = true; / 權(quán)重是否都一樣 for (int i = 0; i < length; i+) int weight = getWeight(i nvokers.get(i), i nvocati on);totalWeight += weight; / 累計(jì)總權(quán)重

18、if (sameWeight && i > 0&& weight != getWeight(i nvokers.get(i - 1), in vocatio n) sameWeight = false; /計(jì)算所有權(quán)重是否一樣if (totalWeight > 0 && ! sameWeight) /如果權(quán)重不相同且權(quán)重大于0則按總權(quán)重?cái)?shù)隨機(jī)int offset = ran dom .n ext In t(totalWeight);/并確定隨機(jī)值落在哪個(gè)片斷上for (int i = 0; i < length; i+) off

19、set -= getWeight(i nvokers.get(i), i nvocati on);if (offset < 0) return in vokers.get(i);/如果權(quán)重相同或權(quán)重為0則均等隨機(jī)return in vokers.get( ra ndom .n ext In t(le ngth);2、RoundRobinLoadBalanee 算法public class Roun dRob in LoadBala neeexte nds AbstractLoadBala nee public static final Stri ng NAME= "rou nd

20、robi n"sequences =private finalCon curre ntMapvStr ing,AtomicPositivel nteger>new Con curre ntHashMapvStri ng, AtomicPositivel nteger>();private finalCon curre ntMapvStri ng,AtomicPositive In teger>weightSeque nces=new Con curre ntHashMapvStri ng, AtomicPositivel nteger>();protected

21、 <T> In voker<T> doSelect(List< In voker<T>> in vokers, URL url,In vocati on in vocati on) Stri ng key = in vokers.get(0).getUrl().getServiceKey() +"."+in vocatio n.getMethodName();int length = invokers.size();/ 總個(gè)數(shù)int maxWeight = 0;/ 最大權(quán)重int minWeight = Integer.MAX

22、_VALUE / 最小權(quán)重for (int i = 0; i < length; i+) int weight = getWeight(invokers.get(i), invocation);/累計(jì)最大權(quán)重/累計(jì)最小權(quán)重/權(quán)重不一樣weightSeque nces.get(key);new AtomicPositivel nteger();maxWeight = Math.max(maxWeight, weight);min Weight = Math.min (min Weight, weight);if (maxWeight > 0 && minWeight

23、< maxWeight) AtomicPositive In teger weightSeque nee =if (weightSequenee =null ) weightSeque nces.putlfAbse nt(key.weightSeque nee =weightSeque nces.get(key);int curre ntWeight = weightSeque nce.getA ndl ncreme nt() % maxWeight;List< In voker<T>> weightl nvokers =new ArrayList< In

24、voker<T>>();for (Invoker<T> invoker : invokers) / 篩選權(quán)重大于當(dāng)前權(quán)重基數(shù)的In vokerif (getWeight(invoker, invocation) > currentWeight) weightI nvokers.add(i nvoker);int weightLength = weightInvokers.size();if (weightLength = 1) retur nweightI nvokers.get(O);else if (weightLength > 1) in vok

25、ers = weightI nvokers;len gth = in vokers.size();AtomicPositiveInteger sequenee =sequences .get(key);if (sequenee = null ) sequences .putIfAbsent(key,new AtomicPositiveInteger();sequenee =sequences .get(key);/取模輪循retur nin vokers.get(seque nce.getA ndln creme nt() % len gth);3、LeastActionLoadBalanee

26、 算法public class LeastActiveLoadBala neeexte nds AbstractLoadBala nee public static final Stri ng NAME= "leastactive" ;private finalRan dom ran dom = new Ran dom();protected <T> In voker<T> doSelect(List< In voker<T>> in vokers, URL url,In vocati on in vocati on) /總個(gè)

27、數(shù)int length = invokers.size();int leastActive = -1; int leastCount = 0; int leastIndexs = int totalWeight = 0;int firstWeight = 0;/最小的活躍數(shù)/相同最小活躍數(shù)的個(gè)數(shù)new int length;/相同最小活躍數(shù)的下標(biāo)/ 總權(quán)重/第一個(gè)權(quán)重,用于于計(jì)算是否相同boolean sameWeight = true ; / 是否所有權(quán)重相同for (int i = 0; i < length; i+) In voker<T> in voker = in

28、vokers.get(i);int active = RpcStatus.getStatus (in voker.getUrl(),in vocatio n. getMethodName().getActive();/ 活躍數(shù)int weight =in voker.getUrl().getMethodParameter(i nvocati on. getMethodName(),Con sta nts.WEIGHT_KE Y, Con sta nts.DEFAULT_WEIGHT; 權(quán)重/發(fā)現(xiàn)更小的活躍if (leastActive = -1 | active < leastActiv

29、e) 數(shù),重新開始leastActive = active;/記錄最小活躍數(shù)leastCou nt = 1;/重新統(tǒng)計(jì)相冋最小活躍數(shù)的個(gè)數(shù)least In dexs0 = i;/重新記錄最小活躍數(shù)下標(biāo)totalWeight = weight;/重新累計(jì)總權(quán)重firstWeight = weight;/記錄第一個(gè)權(quán)重sameWeight =true ; /還原權(quán)重相同標(biāo)識(shí)else if (active =leastActive) /累計(jì)相同最小的活躍數(shù)leastI ndexsleastCou nt +=i;/累計(jì)相同最小活躍數(shù)下標(biāo)totalWeight += weight;/累計(jì)總權(quán)重/判斷所有

30、權(quán)重是否一樣if (sameWeight && i > 0&& weight != firstWeight) sameWeight =false ;/ assert(leastCou nt > 0)if (leastCount = 1) /如果只有一個(gè)最小則直接返回retur nin vokers.get(leastI ndexs0);if (! sameWeight && totalWeight > 0) /如果權(quán)重不相同且權(quán)重大于0則按總權(quán)重?cái)?shù)隨機(jī)int offsetWeight = random .n ext In t(t

31、otalWeight); /并確定隨機(jī)值落在哪個(gè)片斷上for (int i = 0; i < leastCount; i+) int leastI ndex = leastI ndexsi;offsetWeight -= getWeight(i nvokers.get(leastl ndex).in vocati on);if (offsetweight <= 0)retur n in vokers.get(least In dex);/如果權(quán)重相同或權(quán)重為0則均等隨機(jī)returninvokers.get(leastIndexsrandom .nextInt(leastCount)

32、;4、ConsistentHashLoadBalanee算法public class Con siste ntHashLoadBala neeexte nds AbstraetLoadBala nee private finalCon eurre ntMapvStri ng. Con siste ntHashSelector<?>>selectors=new Con eurre ntHashMapvStri ng. Con siste ntHashSelector<?>>();SuppressWar nings("u nchecked"

33、)Overrideprotected <T> In voker<T> doSelect(List< In voker<T>> in vokers, URL url,In vocati on in vocati on) Stri ng key = in vokers.get(0).getUrl().getServiceKey() +"."+in vocatio n.getMethodName();int identityHashCode = System.identityHashCode(invokers);Con siste

34、ntHashSelector<T> selector = (Con siste ntHashSelector<T>)selectors .get(key);if (selector = null | selector.getIdentityHashCode。!=identityHashCode) selectors .put(key, new Con siste ntHashSelector<T>(i nvokers,in vocatio n.getMethodName(), ide ntityHashCode);selector = (Con siste

35、ntHashSelector<T>)selectors .get(key);retur nselector.select(i nvocati on);privatestaticfinalclassCon siste ntHashSelector<T> privatefinalTreeMapvL ong,In voker<T>>virtualI nvokersprivatefinalintreplicaNumberprivatefinalintide ntityHashCodeprivatefinalint argume ntl ndexpublicCo

36、n siste ntHashSelector(List<l nvoker<T>>in vokers,Stri ng methodName,int identityHashCode) this . virtualInvokers= new TreeMap<Long, Invoker<T>>();this . identityHashCode = System.identityHashCode(invokers);URL url = in vokers.get(0).getUrl();this . replicaNumber = url.getMet

37、hodParameter(methodName,"hash .n odes", 160);Strin g in dex =Con sta nts.COMMA_SPLIT_PATTERNsplit(url.getMethodParameter(methodName,"hash.arguments", "0");argumentlndex = new int index. length ; for (int i = 0; i < index.length ; i +) argume ntl ndexi = In teger.pars

38、e Int (in dexi);for (Invoker<T> invoker : invokers) for (int i = 0; i <replicaNumber / 4; i+) byte digest = md5(invoker.getUrl().toFullString() + i);for ( int h = 0; h < 4; h+) long m = hash(digest, h); virtualI nvokers.put(m, i nvoker);public int getIde ntityHashCode() retur n ide ntityHashCode ;publicIn voker<T> select(I nvocati on in vocati on) String key = toKey(invocation.getArguments();byte digest = md5(ke

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論