ribbon是netflix發布的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將netflix的中間層服務連接在一起。ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出load balancer(簡稱lb)后面所有的機器,ribbon會自動的幫助你基于某種規則(如簡單輪詢,隨即連接等)去連接這些機器。我們也很容易使用ribbon實現自定義的負載均衡算法。
說起負載均衡一般都會想到服務端的負載均衡,常用產品包括lbs硬件或云服務、nginx等,都是耳熟能詳的產品。
而spring cloud提供了讓服務調用端具備負載均衡能力的ribbon,通過和eureka的緊密結合,不用在服務集群內再架設負載均衡服務,很大程度簡化了服務集群內的架構。
具體也不想多寫虛的介紹,反正哪里都能看得到相關的介紹。
直接開擼代碼,通過代碼來看ribbon是如何實現的。
配置
詳解:
1.ribbonautoconfiguration配置生成ribbonloadbalancerclient實例。
代碼位置:
spring-cloud-netflix-core-1.3.5.release.jar
org.springframework.cloud.netflix.ribbon
ribbonautoconfiguration.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@configuration @conditionalonclass ({ iclient. class , resttemplate. class , asyncresttemplate. class , ribbon. class }) @ribbonclients @autoconfigureafter (name = "org.springframework.cloud.netflix.eureka.eurekaclientautoconfiguration" ) @autoconfigurebefore ({loadbalancerautoconfiguration. class , asyncloadbalancerautoconfiguration. class }) @enableconfigurationproperties (ribboneagerloadproperties. class ) public class ribbonautoconfiguration { // 略 @bean @conditionalonmissingbean (loadbalancerclient. class ) public loadbalancerclient loadbalancerclient() { return new ribbonloadbalancerclient(springclientfactory()); } // 略 } |
先看配置條件項,ribbonautoconfiguration配置必須在loadbalancerautoconfiguration配置前執行,因為在loadbalancerautoconfiguration配置中會使用ribbonloadbalancerclient實例。
ribbonloadbalancerclient繼承自loadbalancerclient接口,是負載均衡客戶端,也是負載均衡策略的調用方。
2.loadbalancerinterceptorconfig配置生成:
1).負載均衡攔截器loadbalancerinterceptor實例
包含:
loadbalancerclient實現類的ribbonloadbalancerclient實例
負載均衡的請求創建工廠loadbalancerrequestfactory:實例
2).resttemplate自定義的resttemplatecustomizer實例
代碼位置:
spring-cloud-commons-1.2.4.release.jar
org.springframework.cloud.client.loadbalancer
loadbalancerautoconfiguration.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@configuration @conditionalonclass (resttemplate. class ) @conditionalonbean (loadbalancerclient. class ) @enableconfigurationproperties (loadbalancerretryproperties. class ) public class loadbalancerautoconfiguration { // 略 @bean @conditionalonmissingbean public loadbalancerrequestfactory loadbalancerrequestfactory( loadbalancerclient loadbalancerclient) { return new loadbalancerrequestfactory(loadbalancerclient, transformers); } @configuration @conditionalonmissingclass ( "org.springframework.retry.support.retrytemplate" ) static class loadbalancerinterceptorconfig { @bean public loadbalancerinterceptor ribboninterceptor( loadbalancerclient loadbalancerclient, loadbalancerrequestfactory requestfactory) { return new loadbalancerinterceptor(loadbalancerclient, requestfactory); } @bean @conditionalonmissingbean public resttemplatecustomizer resttemplatecustomizer( final loadbalancerinterceptor loadbalancerinterceptor) { return new resttemplatecustomizer() { @override public void customize(resttemplate resttemplate) { list<clienthttprequestinterceptor> list = new arraylist<>( resttemplate.getinterceptors()); list.add(loadbalancerinterceptor); resttemplate.setinterceptors(list); } }; } } // 略 } |
先看配置條件項:
要求在項目環境中必須要有resttemplate類。
要求必須要有loadbalancerclient接口的實現類的實例,也就是上一步生成的ribbonloadbalancerclient。
3.通過上面一步創建的resttemplatecustomizer配置所有resttemplate實例,就是將負載均衡攔截器設置給resttemplate實例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
@configuration @conditionalonclass (resttemplate. class ) @conditionalonbean (loadbalancerclient. class ) @enableconfigurationproperties (loadbalancerretryproperties. class ) public class loadbalancerautoconfiguration { // 略 @bean public smartinitializingsingleton loadbalancedresttemplateinitializer( final list<resttemplatecustomizer> customizers) { return new smartinitializingsingleton() { @override public void aftersingletonsinstantiated() { for (resttemplate resttemplate : loadbalancerautoconfiguration. this .resttemplates) { for (resttemplatecustomizer customizer : customizers) { customizer.customize(resttemplate); } } } }; } // 略 @configuration @conditionalonmissingclass ( "org.springframework.retry.support.retrytemplate" ) static class loadbalancerinterceptorconfig { @bean public loadbalancerinterceptor ribboninterceptor( loadbalancerclient loadbalancerclient, loadbalancerrequestfactory requestfactory) { return new loadbalancerinterceptor(loadbalancerclient, requestfactory); } @bean @conditionalonmissingbean public resttemplatecustomizer resttemplatecustomizer( final loadbalancerinterceptor loadbalancerinterceptor) { return new resttemplatecustomizer() { @override public void customize(resttemplate resttemplate) { list<clienthttprequestinterceptor> list = new arraylist<>( resttemplate.getinterceptors()); list.add(loadbalancerinterceptor); resttemplate.setinterceptors(list); } }; } } // 略 } |
resttemplate.setinterceptors(list)這個地方就是注入負載均衡攔截器的地方loadbalancerinterceptor。
從這個地方實際上也可以猜出來,resttemplate可以通過注入的攔截器來構建相應的請求實現負載均衡。
也能看出來可以自定義攔截器實現其他目的。
4.ribbonclientconfiguration配置生成zoneawareloadbalancer實例
代碼位置:
spring-cloud-netflix-core-1.3.5.release.jar
org.springframework.cloud.netflix.ribbon
ribbonclientconfiguration.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@suppresswarnings ( "deprecation" ) @configuration @enableconfigurationproperties //order is important here, last should be the default, first should be optional // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 @import ({okhttpribbonconfiguration. class , restclientribbonconfiguration. class , httpclientribbonconfiguration. class }) public class ribbonclientconfiguration { // 略 @bean @conditionalonmissingbean public iloadbalancer ribbonloadbalancer(iclientconfig config, serverlist<server> serverlist, serverlistfilter<server> serverlistfilter, irule rule, iping ping, serverlistupdater serverlistupdater) { if ( this .propertiesfactory.isset(iloadbalancer. class , name)) { return this .propertiesfactory.get(iloadbalancer. class , config, name); } return new zoneawareloadbalancer<>(config, rule, ping, serverlist, serverlistfilter, serverlistupdater); } // 略 } |
zoneawareloadbalancer繼承自iloadbalancer接口,該接口有一個方法:
1
2
3
4
5
6
7
8
|
/** * choose a server from load balancer. * * @param key an object that the load balancer may use to determine which server to return. null if * the load balancer does not use this parameter. * @return server chosen */ public server chooseserver(object key); |
zoneawareloadbalancer就是一個具體的負載均衡實現類,也是默認的負載均衡類,通過對chooseserver方法的實現選取某個服務實例。
攔截&請求
1.使用resttemplate進行get、post等各種請求,都是通過doexecute方法實現
代碼位置:
spring-web-4.3.12.release.jar
org.springframework.web.client
resttemplate.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class resttemplate extends interceptinghttpaccessor implements restoperations { // 略 protected <t> t doexecute(uri url, httpmethod method, requestcallback requestcallback, responseextractor<t> responseextractor) throws restclientexception { assert .notnull(url, "'url' must not be null" ); assert .notnull(method, "'method' must not be null" ); clienthttpresponse response = null ; try { clienthttprequest request = createrequest(url, method); if (requestcallback != null ) { requestcallback.dowithrequest(request); } response = request.execute(); handleresponse(url, method, response); if (responseextractor != null ) { return responseextractor.extractdata(response); } else { return null ; } } catch (ioexception ex) { string resource = url.tostring(); string query = url.getrawquery(); resource = (query != null ? resource.substring( 0 , resource.indexof( '?' )) : resource); throw new resourceaccessexception( "i/o error on " + method.name() + " request for \"" + resource + "\": " + ex.getmessage(), ex); } finally { if (response != null ) { response.close(); } } } // 略 } |
支持的各種http請求方法最終都是調用doexecute方法,該方法內調用創建方法創建請求實例,并執行請求得到響應對象。
2.生成請求實例創建工廠
上一步代碼中,調用createrequest方法創建請求實例,這個方法是定義在父類中。
先整理出主要的繼承關系:
createrequest方法實際是定義在httpaccessor抽象類中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public abstract class httpaccessor { private clienthttprequestfactory requestfactory = new simpleclienthttprequestfactory(); public void setrequestfactory(clienthttprequestfactory requestfactory) { assert .notnull(requestfactory, "clienthttprequestfactory must not be null" ); this .requestfactory = requestfactory; } public clienthttprequestfactory getrequestfactory() { return this .requestfactory; } protected clienthttprequest createrequest(uri url, httpmethod method) throws ioexception { clienthttprequest request = getrequestfactory().createrequest(url, method); if (logger.isdebugenabled()) { logger.debug( "created " + method.name() + " request for \"" + url + "\"" ); } return request; } } |
在createrequest方法中調用getrequestfactory方法獲得請求實例創建工廠,實際上getrequestfactory并不是當前httpaccessor類中定義的,而是在子類interceptinghttpaccessor中定義的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public abstract class interceptinghttpaccessor extends httpaccessor { private list<clienthttprequestinterceptor> interceptors = new arraylist<clienthttprequestinterceptor>(); public void setinterceptors(list<clienthttprequestinterceptor> interceptors) { this .interceptors = interceptors; } public list<clienthttprequestinterceptor> getinterceptors() { return interceptors; } @override public clienthttprequestfactory getrequestfactory() { clienthttprequestfactory delegate = super .getrequestfactory(); if (!collectionutils.isempty(getinterceptors())) { return new interceptingclienthttprequestfactory(delegate, getinterceptors()); } else { return delegate; } } } |
在這里做了個小動作,首先還是通過httpaccessor類創建并獲得simpleclienthttprequestfactory工廠,這個工廠主要就是在沒有攔截器的時候創建基本請求實例。
其次,在有攔截器注入的情況下,創建interceptingclienthttprequestfactory工廠,該工廠就是創建帶攔截器的請求實例,因為注入了負載均衡攔截器,所以這里就從interceptingclienthttprequestfactory工廠創建。
3.通過工廠創建請求實例
創建實例就看工廠的createrequest方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class interceptingclienthttprequestfactory extends abstractclienthttprequestfactorywrapper { private final list<clienthttprequestinterceptor> interceptors; public interceptingclienthttprequestfactory(clienthttprequestfactory requestfactory, list<clienthttprequestinterceptor> interceptors) { super (requestfactory); this .interceptors = (interceptors != null ? interceptors : collections.<clienthttprequestinterceptor>emptylist()); } @override protected clienthttprequest createrequest(uri uri, httpmethod httpmethod, clienthttprequestfactory requestfactory) { return new interceptingclienthttprequest(requestfactory, this .interceptors, uri, httpmethod); } } |
就是new了個interceptingclienthttprequest實例,并且把攔截器、基本請求實例創建工廠注進去。
4.請求實例調用配置階段注入的負載均衡攔截器的攔截方法intercept
可從第1步看出,創建完請求實例后,通過執行請求實例的execute方法執行請求。
1
2
3
4
5
|
clienthttprequest request = createrequest(url, method); if (requestcallback != null ) { requestcallback.dowithrequest(request); } response = request.execute(); |
實際請求實例是interceptingclienthttprequest,execute實際是在它的父類中。
類定義位置:
spring-web-4.3.12.release.jar
org.springframework.http.client
interceptingclienthttprequest.class
看一下它們的繼承關系。
在execute方法中實際調用了子類實現的executeinternal方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public abstract class abstractclienthttprequest implements clienthttprequest { private final httpheaders headers = new httpheaders(); private boolean executed = false ; @override public final httpheaders getheaders() { return ( this .executed ? httpheaders.readonlyhttpheaders( this .headers) : this .headers); } @override public final outputstream getbody() throws ioexception { assertnotexecuted(); return getbodyinternal( this .headers); } @override public final clienthttpresponse execute() throws ioexception { assertnotexecuted(); clienthttpresponse result = executeinternal( this .headers); this .executed = true ; return result; } protected void assertnotexecuted() { assert .state(! this .executed, "clienthttprequest already executed" ); } protected abstract outputstream getbodyinternal(httpheaders headers) throws ioexception; protected abstract clienthttpresponse executeinternal(httpheaders headers) throws ioexception; } |
其實就是interceptingclienthttprequest類的executeinternal方法,其中,又調用了一個執行器interceptingrequestexecution的execute,通關判斷如果有攔截器注入進來過,就調用攔截器的intercept方法。
這里的攔截器實際上就是在配置階段注入進resttemplate實例的負載均衡攔截器loadbalancerinterceptor實例,可參考上面配置階段的第2步。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class interceptingclienthttprequest extends abstractbufferingclienthttprequest { // 略 @override protected final clienthttpresponse executeinternal(httpheaders headers, byte [] bufferedoutput) throws ioexception { interceptingrequestexecution requestexecution = new interceptingrequestexecution(); return requestexecution.execute( this , bufferedoutput); } private class interceptingrequestexecution implements clienthttprequestexecution { private final iterator<clienthttprequestinterceptor> iterator; public interceptingrequestexecution() { this .iterator = interceptors.iterator(); } @override public clienthttpresponse execute(httprequest request, byte [] body) throws ioexception { if ( this .iterator.hasnext()) { clienthttprequestinterceptor nextinterceptor = this .iterator.next(); return nextinterceptor.intercept(request, body, this ); } else { clienthttprequest delegate = requestfactory.createrequest(request.geturi(), request.getmethod()); for (map.entry<string, list<string>> entry : request.getheaders().entryset()) { list<string> values = entry.getvalue(); for (string value : values) { delegate.getheaders().add(entry.getkey(), value); } } if (body.length > 0 ) { streamutils.copy(body, delegate.getbody()); } return delegate.execute(); } } } } |
5.負載均衡攔截器調用負載均衡客戶端
在負載均衡攔截器loadbalancerinterceptor類的intercept方法中,又調用了負載均衡客戶端loadbalancerclient實現類的execute方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class loadbalancerinterceptor implements clienthttprequestinterceptor { private loadbalancerclient loadbalancer; private loadbalancerrequestfactory requestfactory; public loadbalancerinterceptor(loadbalancerclient loadbalancer, loadbalancerrequestfactory requestfactory) { this .loadbalancer = loadbalancer; this .requestfactory = requestfactory; } public loadbalancerinterceptor(loadbalancerclient loadbalancer) { // for backwards compatibility this (loadbalancer, new loadbalancerrequestfactory(loadbalancer)); } @override public clienthttpresponse intercept( final httprequest request, final byte [] body, final clienthttprequestexecution execution) throws ioexception { final uri originaluri = request.geturi(); string servicename = originaluri.gethost(); assert .state(servicename != null , "request uri does not contain a valid hostname: " + originaluri); return this .loadbalancer.execute(servicename, requestfactory.createrequest(request, body, execution)); } } |
在配置階段的第1步,可以看到實現類是ribbonloadbalancerclient。
6.負載均衡客戶端調用負載均衡策略選取目標服務實例并發起請求
在ribbonloadbalancerclient的第一個execute方法以及getserver方法中可以看到,實際上是通過iloadbalancer的負載均衡器實現類作的chooseserver方法選取一個服務,交給接下來的請求對象發起一個請求。
這里的負載均衡實現類默認是zoneawareloadbalancer區域感知負載均衡器實例,其內部通過均衡策略選擇一個服務。
zoneawareloadbalancer的創建可以參考配置階段的第4步。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
public class ribbonloadbalancerclient implements loadbalancerclient { @override public <t> t execute(string serviceid, loadbalancerrequest<t> request) throws ioexception { iloadbalancer loadbalancer = getloadbalancer(serviceid); server server = getserver(loadbalancer); if (server == null ) { throw new illegalstateexception( "no instances available for " + serviceid); } ribbonserver ribbonserver = new ribbonserver(serviceid, server, issecure(server, serviceid), serverintrospector(serviceid).getmetadata(server)); return execute(serviceid, ribbonserver, request); } @override public <t> t execute(string serviceid, serviceinstance serviceinstance, loadbalancerrequest<t> request) throws ioexception { server server = null ; if (serviceinstance instanceof ribbonserver) { server = ((ribbonserver)serviceinstance).getserver(); } if (server == null ) { throw new illegalstateexception( "no instances available for " + serviceid); } ribbonloadbalancercontext context = this .clientfactory .getloadbalancercontext(serviceid); ribbonstatsrecorder statsrecorder = new ribbonstatsrecorder(context, server); try { t returnval = request.apply(serviceinstance); statsrecorder.recordstats(returnval); return returnval; } // catch ioexception and rethrow so resttemplate behaves correctly catch (ioexception ex) { statsrecorder.recordstats(ex); throw ex; } catch (exception ex) { statsrecorder.recordstats(ex); reflectionutils.rethrowruntimeexception(ex); } return null ; } // 略 protected server getserver(iloadbalancer loadbalancer) { if (loadbalancer == null ) { return null ; } return loadbalancer.chooseserver( "default" ); // todo: better handling of key } protected iloadbalancer getloadbalancer(string serviceid) { return this .clientfactory.getloadbalancer(serviceid); } public static class ribbonserver implements serviceinstance { private final string serviceid; private final server server; private final boolean secure; private map<string, string> metadata; public ribbonserver(string serviceid, server server) { this (serviceid, server, false , collections.<string, string> emptymap()); } public ribbonserver(string serviceid, server server, boolean secure, map<string, string> metadata) { this .serviceid = serviceid; this .server = server; this .secure = secure; this .metadata = metadata; } // 略 } } |
代碼擼完,總結下。
普通使用resttemplate請求其他服務時,內部使用的就是常規的http請求實例發送請求。
為resttemplate增加了@loanbalanced 注解后,實際上通過配置,為resttemplate注入負載均衡攔截器,讓負載均衡器選擇根據其對應的策略選擇合適的服務后,再發送請求。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/kongxianghai/p/8445030.html