Spring Cloud学习教程之Zuul统一异常处理与回退

2025-05-29 0 79

前言

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;

}

}

}

  启动项目后,我们通过网关访问一下试试:

Spring Cloud学习教程之Zuul统一异常处理与回退

二、关于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 得到如下结果:

Spring Cloud学习教程之Zuul统一异常处理与回退

当我们访问:http://localhost:8088/order-server/sleep/1 就得到如下结果:

Spring Cloud学习教程之Zuul统一异常处理与回退

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对快网idc的支持。

原文链接:http://www.cnblogs.com/niechen/p/8856551.html

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 Spring Cloud学习教程之Zuul统一异常处理与回退 https://www.kuaiidc.com/111884.html

相关文章

发表评论
暂无评论