前言
zuul 是netflix 提供的一個開源組件,致力于在云平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。也有很多公司使用它來作為網關的重要組成部分,碰巧今年公司的架構組決定自研一個網關產品,集動態路由,動態權限,限流配額等功能為一體,為其他部門的項目提供統一的外網調用管理,最終形成產品(這方面阿里其實已經有成熟的網關產品了,但是不太適用于個性化的配置,也沒有集成權限和限流降級)。
本文主要給大家介紹了關于spring cloud zuul統一異常處理與回退的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
一、filter中統一異常處理
其實在springcloud的edgware sr2版本中對于zuulfilter中的錯誤有統一的處理,但是在實際開發當中對于錯誤的響應方式,我想每個團隊都有自己的處理規范。那么如何做到自定義的異常處理呢?
我們可以先參考一下springcloud提供的senderrorfilter:
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
/* * copyright 2013-2015 the original author or authors. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ package org.springframework.cloud.netflix.zuul.filters.post; import javax.servlet.requestdispatcher; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.beans.factory.annotation.value; import org.springframework.cloud.netflix.zuul.util.zuulruntimeexception; import org.springframework.util.reflectionutils; import org.springframework.util.stringutils; import com.netflix.zuul.zuulfilter; import com.netflix.zuul.context.requestcontext; import com.netflix.zuul.exception.zuulexception; import static org.springframework.cloud.netflix.zuul.filters.support.filterconstants.error_type; import static org.springframework.cloud.netflix.zuul.filters.support.filterconstants.send_error_filter_order; /** * error {@link zuulfilter} that forwards to /error (by default) if {@link requestcontext#getthrowable()} is not null. * * @author spencer gibb */ //todo: move to error package in edgware public class senderrorfilter extends zuulfilter { private static final log log = logfactory.getlog(senderrorfilter. class ); protected static final string send_error_filter_ran = "senderrorfilter.ran" ; @value ( "${error.path:/error}" ) private string errorpath; @override public string filtertype() { return error_type; } @override public int filterorder() { return send_error_filter_order; } @override public boolean shouldfilter() { requestcontext ctx = requestcontext.getcurrentcontext(); // only forward to errorpath if it hasn't been forwarded to already return ctx.getthrowable() != null && !ctx.getboolean(send_error_filter_ran, false ); } @override public object run() { try { requestcontext ctx = requestcontext.getcurrentcontext(); zuulexception exception = findzuulexception(ctx.getthrowable()); httpservletrequest request = ctx.getrequest(); request.setattribute( "javax.servlet.error.status_code" , exception.nstatuscode); log.warn( "error during filtering" , exception); request.setattribute( "javax.servlet.error.exception" , exception); if (stringutils.hastext(exception.errorcause)) { request.setattribute( "javax.servlet.error.message" , exception.errorcause); } requestdispatcher dispatcher = request.getrequestdispatcher( this .errorpath); if (dispatcher != null ) { ctx.set(send_error_filter_ran, true ); if (!ctx.getresponse().iscommitted()) { ctx.setresponsestatuscode(exception.nstatuscode); dispatcher.forward(request, ctx.getresponse()); } } } catch (exception ex) { reflectionutils.rethrowruntimeexception(ex); } return null ; } zuulexception findzuulexception(throwable throwable) { if (throwable.getcause() instanceof zuulruntimeexception) { // this was a failure initiated by one of the local filters return (zuulexception) throwable.getcause().getcause(); } if (throwable.getcause() instanceof zuulexception) { // wrapped zuul exception return (zuulexception) throwable.getcause(); } if (throwable instanceof zuulexception) { // exception thrown by zuul lifecycle return (zuulexception) throwable; } // fallback, should never get here return new zuulexception(throwable, httpservletresponse.sc_internal_server_error, null ); } public void seterrorpath(string errorpath) { this .errorpath = errorpath; } } |
在這里我們可以找到幾個關鍵點:
1)在上述代碼中,我們可以發現filter已經將相關的錯誤信息放到request當中了:
request.setattribute("javax.servlet.error.status_code", exception.nstatuscode);
request.setattribute("javax.servlet.error.exception", exception);
request.setattribute("javax.servlet.error.message", exception.errorcause);
2)錯誤處理完畢后,會轉發到 xxx/error的地址來處理
那么我們可以來做個試驗,我們在gateway-service項目模塊里,創建一個會拋出異常的filter:
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
|
package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.netflix.zuul.zuulfilter; import lombok.extern.slf4j.slf4j; import org.springframework.stereotype.component; @component @slf4j public class myzuulfilter extends zuulfilter { @override public string filtertype() { return "post" ; } @override public int filterorder() { return 9 ; } @override public boolean shouldfilter() { return true ; } @override public object run() { log.info( "run error test ..." ); throw new runtimeexception(); // return null; } } |
緊接著我們定義一個控制器,來做錯誤處理:
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
|
package com.hzgj.lyrk.springcloud.gateway.server.filter; import org.springframework.http.httpstatus; import org.springframework.http.responseentity; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.restcontroller; import javax.servlet.http.httpservletrequest; @restcontroller public class errorhandler { @getmapping (value = "/error" ) public responseentity<errorbean> error(httpservletrequest request) { string message = request.getattribute( "javax.servlet.error.message" ).tostring(); errorbean errorbean = new errorbean(); errorbean.setmessage(message); errorbean.setreason( "程序出錯" ); return new responseentity<>(errorbean, httpstatus.bad_gateway); } private static class errorbean { private string message; private string reason; public string getmessage() { return message; } public void setmessage(string message) { this .message = message; } public string getreason() { return reason; } public void setreason(string reason) { this .reason = reason; } } } |
啟動項目后,我們通過網關訪問一下試試:
二、關于zuul回退的問題
1、關于zuul的超時問題:
這個問題網上有很多解決方案,但是我還要貼一下源代碼,請關注這個類 abstractribboncommand,在這個類里集成了hystrix與ribbon。
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
/* * copyright 2013-2016 the original author or authors. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. * */ package org.springframework.cloud.netflix.zuul.filters.route.support; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cloud.netflix.ribbon.ribbonclientconfiguration; import org.springframework.cloud.netflix.ribbon.ribbonhttpresponse; import org.springframework.cloud.netflix.ribbon.support.abstractloadbalancingclient; import org.springframework.cloud.netflix.ribbon.support.contextawarerequest; import org.springframework.cloud.netflix.zuul.filters.zuulproperties; import org.springframework.cloud.netflix.zuul.filters.route.ribboncommand; import org.springframework.cloud.netflix.zuul.filters.route.ribboncommandcontext; import org.springframework.cloud.netflix.zuul.filters.route.zuulfallbackprovider; import org.springframework.cloud.netflix.zuul.filters.route.fallbackprovider; import org.springframework.http.client.clienthttpresponse; import com.netflix.client.abstractloadbalancerawareclient; import com.netflix.client.clientrequest; import com.netflix.client.config.defaultclientconfigimpl; import com.netflix.client.config.iclientconfig; import com.netflix.client.config.iclientconfigkey; import com.netflix.client.http.httpresponse; import com.netflix.config.dynamicintproperty; import com.netflix.config.dynamicpropertyfactory; import com.netflix.hystrix.hystrixcommand; import com.netflix.hystrix.hystrixcommandgroupkey; import com.netflix.hystrix.hystrixcommandkey; import com.netflix.hystrix.hystrixcommandproperties; import com.netflix.hystrix.hystrixcommandproperties.executionisolationstrategy; import com.netflix.hystrix.hystrixthreadpoolkey; import com.netflix.zuul.constants.zuulconstants; import com.netflix.zuul.context.requestcontext; /** * @author spencer gibb */ public abstract class abstractribboncommand<lbc extends abstractloadbalancerawareclient<rq, rs>, rq extends clientrequest, rs extends httpresponse> extends hystrixcommand<clienthttpresponse> implements ribboncommand { private static final log logger = logfactory.getlog(abstractribboncommand. class ); protected final lbc client; protected ribboncommandcontext context; protected zuulfallbackprovider zuulfallbackprovider; protected iclientconfig config; public abstractribboncommand(lbc client, ribboncommandcontext context, zuulproperties zuulproperties) { this ( "default" , client, context, zuulproperties); } public abstractribboncommand(string commandkey, lbc client, ribboncommandcontext context, zuulproperties zuulproperties) { this (commandkey, client, context, zuulproperties, null ); } public abstractribboncommand(string commandkey, lbc client, ribboncommandcontext context, zuulproperties zuulproperties, zuulfallbackprovider fallbackprovider) { this (commandkey, client, context, zuulproperties, fallbackprovider, null ); } public abstractribboncommand(string commandkey, lbc client, ribboncommandcontext context, zuulproperties zuulproperties, zuulfallbackprovider fallbackprovider, iclientconfig config) { this (getsetter(commandkey, zuulproperties, config), client, context, fallbackprovider, config); } protected abstractribboncommand(setter setter, lbc client, ribboncommandcontext context, zuulfallbackprovider fallbackprovider, iclientconfig config) { super (setter); this .client = client; this .context = context; this .zuulfallbackprovider = fallbackprovider; this .config = config; } protected static hystrixcommandproperties.setter createsetter(iclientconfig config, string commandkey, zuulproperties zuulproperties) { int hystrixtimeout = gethystrixtimeout(config, commandkey); return hystrixcommandproperties.setter().withexecutionisolationstrategy( zuulproperties.getribbonisolationstrategy()).withexecutiontimeoutinmilliseconds(hystrixtimeout); } protected static int gethystrixtimeout(iclientconfig config, string commandkey) { int ribbontimeout = getribbontimeout(config, commandkey); dynamicpropertyfactory dynamicpropertyfactory = dynamicpropertyfactory.getinstance(); int defaulthystrixtimeout = dynamicpropertyfactory.getintproperty( "hystrix.command.default.execution.isolation.thread.timeoutinmilliseconds" , 0 ).get(); int commandhystrixtimeout = dynamicpropertyfactory.getintproperty( "hystrix.command." + commandkey + ".execution.isolation.thread.timeoutinmilliseconds" , 0 ).get(); int hystrixtimeout; if (commandhystrixtimeout > 0 ) { hystrixtimeout = commandhystrixtimeout; } else if (defaulthystrixtimeout > 0 ) { hystrixtimeout = defaulthystrixtimeout; } else { hystrixtimeout = ribbontimeout; } if (hystrixtimeout < ribbontimeout) { logger.warn( "the hystrix timeout of " + hystrixtimeout + "ms for the command " + commandkey + " is set lower than the combination of the ribbon read and connect timeout, " + ribbontimeout + "ms." ); } return hystrixtimeout; } protected static int getribbontimeout(iclientconfig config, string commandkey) { int ribbontimeout; if (config == null ) { ribbontimeout = ribbonclientconfiguration.default_read_timeout + ribbonclientconfiguration.default_connect_timeout; } else { int ribbonreadtimeout = gettimeout(config, commandkey, "readtimeout" , iclientconfigkey.keys.readtimeout, ribbonclientconfiguration.default_read_timeout); int ribbonconnecttimeout = gettimeout(config, commandkey, "connecttimeout" , iclientconfigkey.keys.connecttimeout, ribbonclientconfiguration.default_connect_timeout); int maxautoretries = gettimeout(config, commandkey, "maxautoretries" , iclientconfigkey.keys.maxautoretries, defaultclientconfigimpl.default_max_auto_retries); int maxautoretriesnextserver = gettimeout(config, commandkey, "maxautoretriesnextserver" , iclientconfigkey.keys.maxautoretriesnextserver, defaultclientconfigimpl.default_max_auto_retries_next_server); ribbontimeout = (ribbonreadtimeout + ribbonconnecttimeout) * (maxautoretries + 1 ) * (maxautoretriesnextserver + 1 ); } return ribbontimeout; } private static int gettimeout(iclientconfig config, string commandkey, string property, iclientconfigkey<integer> configkey, int defaultvalue) { dynamicpropertyfactory dynamicpropertyfactory = dynamicpropertyfactory.getinstance(); return dynamicpropertyfactory.getintproperty(commandkey + "." + config.getnamespace() + "." + property, config.get(configkey, defaultvalue)).get(); } @deprecated //todo remove in 2.0.x protected static setter getsetter( final string commandkey, zuulproperties zuulproperties) { return getsetter(commandkey, zuulproperties, null ); } protected static setter getsetter( final string commandkey, zuulproperties zuulproperties, iclientconfig config) { // @formatter:off setter commandsetter = setter.withgroupkey(hystrixcommandgroupkey.factory.askey( "ribboncommand" )) .andcommandkey(hystrixcommandkey.factory.askey(commandkey)); final hystrixcommandproperties.setter setter = createsetter(config, commandkey, zuulproperties); if (zuulproperties.getribbonisolationstrategy() == executionisolationstrategy.semaphore){ final string name = zuulconstants.zuul_eureka + commandkey + ".semaphore.maxsemaphores" ; // we want to default to semaphore-isolation since this wraps // 2 others commands that are already thread isolated final dynamicintproperty value = dynamicpropertyfactory.getinstance() .getintproperty(name, zuulproperties.getsemaphore().getmaxsemaphores()); setter.withexecutionisolationsemaphoremaxconcurrentrequests(value.get()); } else if (zuulproperties.getthreadpool().isuseseparatethreadpools()) { final string threadpoolkey = zuulproperties.getthreadpool().getthreadpoolkeyprefix() + commandkey; commandsetter.andthreadpoolkey(hystrixthreadpoolkey.factory.askey(threadpoolkey)); } return commandsetter.andcommandpropertiesdefaults(setter); // @formatter:on } @override protected clienthttpresponse run() throws exception { final requestcontext context = requestcontext.getcurrentcontext(); rq request = createrequest(); rs response; boolean retryableclient = this .client instanceof abstractloadbalancingclient && ((abstractloadbalancingclient) this .client).isclientretryable((contextawarerequest)request); if (retryableclient) { response = this .client.execute(request, config); } else { response = this .client.executewithloadbalancer(request, config); } context.set( "ribbonresponse" , response); // explicitly close the httpresponse if the hystrix command timed out to // release the underlying http connection held by the response. // if ( this .isresponsetimedout()) { if (response != null ) { response.close(); } } return new ribbonhttpresponse(response); } @override protected clienthttpresponse getfallback() { if (zuulfallbackprovider != null ) { return getfallbackresponse(); } return super .getfallback(); } protected clienthttpresponse getfallbackresponse() { if (zuulfallbackprovider instanceof fallbackprovider) { throwable cause = getfailedexecutionexception(); cause = cause == null ? getexecutionexception() : cause; if (cause == null ) { zuulfallbackprovider.fallbackresponse(); } else { return ((fallbackprovider) zuulfallbackprovider).fallbackresponse(cause); } } return zuulfallbackprovider.fallbackresponse(); } public lbc getclient() { return client; } public ribboncommandcontext getcontext() { return context; } protected abstract rq createrequest() throws exception; } |
請注意:getribbontimeout方法與gethystrixtimeout方法,其中這兩個方法 commandkey的值為路由的名稱,比如說我們訪問:http://localhost:8088/order-server/xxx來訪問order-server服務, 那么commandkey 就為order-server
根據源代碼,我們先設置gateway-server的超時參數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#全局的ribbon設置 ribbon: connecttimeout: 3000 readtimeout: 3000 hystrix: command: default : execution: isolation: thread: timeoutinmilliseconds: 3000 zuul: host: connecttimeoutmillis: 10000 |
當然也可以單獨為order-server設置ribbon的超時參數:order-server.ribbon.xxxx=xxx , 為了演示zuul中的回退效果,我在這里把hystrix超時時間設置短一點。當然最好不要將hystrix默認的超時時間設置的比ribbon的超時時間短,源碼里遇到此情況已經給與我們警告了。
那么我們在order-server下添加如下方法:
1
2
3
4
5
|
@getmapping ( "/sleep/{sleeptime}" ) public string sleep( @pathvariable long sleeptime) throws interruptedexception { timeunit.seconds.sleep(sleeptime); return "success" ; } |
2、zuul的回退方法
我們可以實現zuulfallbackprovider接口,實現代碼:
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
|
package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.google.common.collect.immutablemap; import com.google.gson.gsonbuilder; import org.springframework.cloud.netflix.zuul.filters.route.zuulfallbackprovider; import org.springframework.http.httpheaders; import org.springframework.http.httpstatus; import org.springframework.http.mediatype; import org.springframework.http.client.clienthttpresponse; import org.springframework.stereotype.component; import java.io.bytearrayinputstream; import java.io.ioexception; import java.io.inputstream; import java.time.localdatetime; import java.time.localtime; @component public class fallbackhandler implements zuulfallbackprovider { @override public string getroute() { //代表所有的路由都適配該設置 return "*" ; } @override public clienthttpresponse fallbackresponse() { return new clienthttpresponse() { @override public httpstatus getstatuscode() throws ioexception { return httpstatus.ok; } @override public int getrawstatuscode() throws ioexception { return 200 ; } @override public string getstatustext() throws ioexception { return "ok" ; } @override public void close() { } @override public inputstream getbody() throws ioexception { string result = new gsonbuilder().create().tojson(immutablemap.of( "errorcode" , 500 , "content" , "請求失敗" , "time" , localdatetime.now())); return new bytearrayinputstream(result.getbytes()); } @override public httpheaders getheaders() { httpheaders headers = new httpheaders(); headers.setcontenttype(mediatype.application_json); return headers; } }; } } |
此時我們訪問:http://localhost:8088/order-server/sleep/6 得到如下結果:
當我們訪問:http://localhost:8088/order-server/sleep/1 就得到如下結果:
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:http://www.cnblogs.com/niechen/p/8856551.html