## 一、远程服务之间异常该如何传递?
* 如果是单体应用,控制层调用服务层代码,服务层调用持久层代码。异常在下层拦截转换成自定义异常向上层抛出,从而实现异常的传递。控制层或全局配置统一异常处理,将异常转换为前端用户可以理解的信息。
* 微服务应用之间的调用是使用HTTP协议来互相访问的,那么该如何有效的传递异常信息的渠道呢?
* 渠道一:HTTP的状态码,如:200表示请求成功,400表示非系统异常(用户输入参数等),500表示系统内部异常等等。
* 渠道二:HTTP的Response Body,在Response Body中包含异常信息。
## 二、远程服务异常传递的问题演示
为了让为大家更直观的感受,我们来做一个实验,感受一下远程服务异常传递问题。

* 调整**服务提供者**aservice-sms的代码,加入服务降级的功能。我们人为制造一个一个程序异常:被除数为0。所以产生服务降级,会执行commonFallbackMethod方法。
~~~
public AjaxResponse commonFallbackMethod() {
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR,
"系统繁忙,请稍后再试!");
}
~~~
代码中的error方法实际上做了几件事情:
1. 设置 AjaxResponse.isOk = false
2. 设置 AjaxResponse.code = 500 (基于CustomExceptionType.SYSTEM\_ERROR。我自定义的异常分类)
3. 设置 AjaxResponse.message = "系统繁忙,请稍后再试!"

* 在**服务调用者**aservice-rbac我们把smsService(FeignClient)远程服务调用的结果AjaxResponse打印出来

* 然后访问aservice-rbac的“/sysuser/pwd/reset”服务,结果如下:
* 密码修改成功了,即:数据库操作成功了。即:`sysUserMapper.updateByPrimaryKeySelective`操作成功。

* 但是远程服务短信发送失败了(因为我们在aservice-sms被调用接口中定义了程序异常:被除数为0,并且执行fallback函数)。服务提供者aservice-sms的fallback返回的数据如下:

这显然不是我们希望看到的结果。如果只是发短信失败还不是非常要紧,如果是购物网站,订单服务成功了,账务服务失败了,这个影响就大了!我们期望看到的结果是:**要么都成功,要么都失败**!
## 三、重点理解一下服务提供者的响应数据
问自己几个问题:

* 这个数据是运行时异常么?不是,它只是数据,起不到数据库事务回滚回滚的作用。我们要非常明确的一点是:**只有运行时异常才会导致数据库事务回滚,业务异常数据是不会导致数据库事务回滚的。**
* 这个数据中的code:500是Http状态码么?也不是,500代表的是远程系统服务运行出现异常,**是我自己定义的**。当然你可以认为2或者3表示远程服务运行异常,但这样不好,谁能记住呢?(我自定义的AjaxResponse的code字段的含义与HTTP状态码含义一致,好记!)
* 这条数据响应的实际HTTP状态码是什么?是200-299其中的一个,因为我们成功的接收到了远程服务fallback函数响应的数据。成功的HTTP请求状态码都是200-299。
所以远程服务降级之后返回的结果是:
* HTTP协议的正常响应结果(200-299,HTTP协议规范)
* 业务上的异常数据(AjaxResponse.code = 500,我自己根据HTTP协议规范定义的业务结果状态码)
也就是说,我们介绍了服务异常传递的两个渠道:一是HTTP状态码,二是HTTP的Response Body。**目前我们只能使用第二种渠道传递异常!**
## 四、使用HTTP的Response Body传递异常(最简单的方式)
所以针对以上的异常传递不到位导致的问题,最简单的处理方式就是:我们在接收到远程服务的响应结果Response Body(对于我们的项目是AjaxResponse)后,判断其内部的状态信息。如果状态信息是业务失败,throw new 自定义异常抛出,触发数据库回滚!
1. AjaxResponse.isOk = false
2. AjaxResponse.code = 500
3. AjaxResponse.message = "系统繁忙,请稍后再试!"

## 五、使用HTTP状态码传递异常(优化方式,符合RESTful风格)
目前很多应用都采用RESTful风格的接口,特点就是
* 看HTTP方法就知道动作,如:GET表示查询、POST表示修改、DELETE表示删除
* 看URL就知道操作的资源。比如:用GET请求/dogs资源,是查询所有的狗狗数据。
* 看HTTP返回的状态码,就知道动作的结果。如:200表示成功、400表示一些输入参数错误等、500表示系统内部错误。如果严格遵照这一项,**我们有必要让HTTP响应结果的状态码与业务的运行结果AjaxResponse的code统一!**
方法就是实现ResponseBodyAdvice接口:对项目的所有的Controller的JSON类型数据响应结果进行二次封装,然后再返回给服务调用端端。

* 统一数据响应格式为AjaxResponse(可以自定义)。
* 指定HTTP协议状态码status code = 业务运行结果AjaxResponse.code,前提是自定义的AjaxResponse的code字段的含义与HTTP状态码含义一致。
~~~
response.setStatusCode(HttpStatus.valueOf(
((AjaxResponse) body).getCode())
);
~~~
有了这样一层封装,**服务调用端就能根据HTTP状态码判断服务提供者的响应数据是否异常**。完整实现如下:
~~~
@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//return returnType.hasMethodAnnotation(ResponseBody.class);
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
//对于JSON类型的响应数据
if(selectedContentType.equalsTypeAndSubtype(
MediaType.APPLICATION_JSON)){
if(body instanceof AjaxResponse){
//如果Controller返回值body的数据类型是AjaxResponse(body instanceof AjaxResponse)
//就将body直接返回
response.setStatusCode(HttpStatus.valueOf(
((AjaxResponse) body).getCode()) //将业务异常状态码赋值给HTTP状态码
);
return body;
}else{
//如果Controller返回值body的数据类型不是AjaxResponse,
//就将body封装为AjaxResponse类型返回,总之要统一数据响应的类型
AjaxResponse ajaxResponse = AjaxResponse.success(body);
response.setStatusCode(HttpStatus.valueOf(
ajaxResponse.getCode()) //将业务异常状态码赋值给HTTP状态码
);
return AjaxResponse.success(body);
}
}
return body;
}
}
~~~
- 文档简介
- 模块与代码分支说明
- dongbb-cloud项目核心架构
- 微服务架构进化论
- SpringBoot与Cloud选型兼容
- Spring Cloud组件的选型
- 单体应用拆分微服务
- 单体应用与微服务对比
- 微服务设计拆分原则
- 新建父工程及子模块框架
- 通用微服务初始化模块构建
- 持久层模块单独拆分
- 拆分rbac权限管理微服务
- Hello-microservice
- 构建eureka服务注册中心
- 向服务注册中心注册服务
- 第一个微服务调用
- 远程服务调用
- HttpClient远程服务调用
- RestTemplate远程服务调用
- RestTemplate多实例负载均衡
- Ribbon调用流程源码解析
- Ribbon负载均衡策略源码解析
- Ribbon重试机制与饥饿加载
- Ribbon自定义负载均衡策略
- Feign与OpenFeign
- Feign设计原理源码解析
- Feign请求压缩与超时等配置
- 服务注册与发现
- 白话服务注册与发现
- DiscoveryClient服务发现
- Eureka集群环境构建(linux)
- Eureka集群多网卡环境ip设置
- Eureka集群服务注册与安全认证
- Eureka自我保护与健康检查
- 主流服务注册中心对比(含nacos)
- zookeeper概念及功能简介
- zookeeper-linux集群安装
- zookeeper服务注册与发现
- consul概念及功能介绍
- consul-linux集群安装
- consul服务注册与发现
- 通用-auatator导致401问题
- 分布式配置中心-apollo
- 服务配置中心概念及使用场景
- apollo概念功能简介
- apollo架构详解
- apollo分布式部署之Portal
- apollo分布式部署之环境区分
- apollo项目权限管理实战
- apollo-java客户端基础
- apollo与SpringCloud服务集成
- apollo实例配置热更新
- apollo命名空间与集群
- apollo灰度发布(日志热更新为例)
- SpringCloudConfig配置中心
- config-git配置文件仓库
- config配置中心搭建与测试
- config客户端基础
- config配置安全认证
- config客户端配置刷新
- config配置中心高可用
- BUS消息总线
- bus消息总线简介
- docker安装rabbitMQ
- 基于rabbitMQ的消息总线
- bus实现批量配置刷新
- alibaba-nacos
- nacos介绍与单机部署
- nacos集群部署方式(linux)
- nacos服务注册与发现
- nacos服务注册中心详解
- nacos客户端配置加载
- nacos客户端配置刷新
- nacos服务配置隔离与共享
- nacos配置Beta发布
- 服务熔断降级hystrix
- 服务降级&熔断&限流
- Hystrix集成并实现服务熔断
- Jemter模拟触发服务熔断
- Hystrix服务降级fallback
- Hystrix结合Feign服务降级
- 远程服务调用异常传递的问题
- Hystrix-Feign异常拦截与处理
- Hystrix-DashBoard单服务监控
- Hystrix-dashboard集群监控
- 分布式系统流量卫兵sentinel
- sentinel简介与安装
- 客户端集成与实时监控
- 实战流控规则-QPS限流
- 实战流控规则-线程数限流
- 实战流控规则-关联限流
- 实战流控规则-链路限流
- 实战流控效果-WarmUp
- 实战流控效果-匀速排队
- BlockException处理
- 实战熔断降级-RT
- 实战熔断降级-异常数与比例
- DegradeException处理
- 注解与异常的归纳总结
- Feign降级及异常传递拦截
- 动态规则nacos集中存储
- 热点参数限流
- 系统自适应限流
- 微服务网关-GateWay
- 还有必要学习Zuul么?
- 简介与非阻塞异步IO模型
- GateWay概念与流程
- 新建一个GateWay项目
- 通用Predicate的使用
- 自定义PredicateFactory
- 编码方式构建静态路由
- Filter过滤器介绍与使用
- 自定义过滤器Filter
- 网关请求转发负载均衡
- 结合nacos实现动态路由配置
- 整合Sentinel实现资源限流
- 跨域访问配置
- 网关层面全局异常处理
- 微服务网关安全认证-JWT篇
- Gateway-JWT认证鉴权流程
- 登录认证JWT令牌颁发
- 全局过滤器实现JWT鉴权
- 微服务自身内部的权限管理
