说明
Sentinel官方文档写的不是很详细,坑比较多。本人主要是改了Sentinel的控制台release版本源码,测试环境又使用的是我改源码后打包的jar,有两个功能支持不是很好(参数限流和黑白名单授权),如果有其他问题,请联系我。但是基本上95%的工作都已经被我封装了,大家只需不到5%的工作量,当前熔断、降级支持Hystrix方案,所以以前的代码可以几乎不动(有少量问题下面会说)。
Sentinel控制台:
http://192.168.128.36:8081/#/dashboard/metric/gateway-server
用户名/密码:myyshop
引入pom包
引入下述包即可使用sentinel
com.myyshop.framework
sentinel-spring-boot-starter
1.0.0-RELEASE
其他基础包引用版本升级和现有影响请严格按照Confluence->基础组件变更记录->变更时间
为2020-10-14修改,Confluence地址为:
http://192.168.128.17:8090/pages/viewpage.action?pageId=3768400
Nacos配置
需要引入 global-public-share.yml 共享配置,这样即可开启 Sentinel 和 Nacos 持久化
spring:
cloud:
config:
server-addr: 192.168.128.20
file-extension: yml
group: uaa-center-server
namespace: 1c53b59e-9f3d-44a4-b2d7-5faaea25b3c6 # dev 环境的命名空间
extension-configs:
- data-id: global-public-share.yml
group: share
refresh: true
配置Sentinel控制台链路
bootstrap-dev.yml.example 全部配置如下:
spring:
cloud:
sentinel:
transport:
client-ip: 192.168.128.41 #如果docker 在20上就配置成20。prod 和 test不需要指定该参数
port: 8733 #这个端口配置不能跟其他服务重复
bootstrap-prod.yml.example 和 bootstrap-test.yml.example 跟上述dev配置类似,只是不需要指定client-ip,端口跟上面一样
下面注意两点:
- client-ip 看自己负责微服务所在开发环境ip(参照下图client-ip)
- port 去 端口总览 -> sentinel端口 找自己的负责微服务端口,端口总览地址点击
注: 如果还看不懂的,参照uaa-center-server服务下bootstrap-dev.yml.example、bootstrap-prod.yml.example 和 bootstrap-test.yml.example
使用Feign+Hystrix处理熔断、降级
用过Hystrix的都知道熔断、降级需要指定具体降级类(也就是fallback),同时加入@Component注解,否则项目启动报错。当前Sentinel兼容Hystrix的降级类。而现在大家代码里的fallback根本形同虚设,不管任何异常都不会走进去,当然造成这个现象也有一些历史原因的,本来要用Hystrix的,后面又决定不用了,原因是Hystrix已经闭源了,开源社区已经不活跃的组件,已经没有使用的必要。然后想着后面用Sentinel这个功能就会生效。
目前大家基本都是使用Feign调接口,使用方法跟大家现有Feign一样,几乎不需要改动,害怕大家看不懂,直接贴个例子:
接口端
@FeignClient(value= ServiceNameConstants.DS_USER_PLATFORM, fallbackFactory = DsUserFeignClientFallback.class)
public interface DsUserFeignClient {
@GetMapping("/api/users/mail/{mail}")
Result findUserByMail(@RequestParam("mail") @PathVariable("mail") String mail);
}
Fallback类
必须加@Component注解,否则项目不能启动, 这点Hystrix跟Sentinel是一样的
@Component //注意:必须加@Component注解,否则项目不能启动
public class DsUserFeignClientFallback implements FallbackFactory {
@Override
public DsUserFeignClient create(Throwable throwable) {
return new DsUserFeignClient() {
@Override
public Result findUserByMail(String mail) {
log.error("通过邮箱查询用户异常:{}", mail, throwable);
throw new BusinessException(BusinessExceptionEnum.BUSINESS_CALLBACK_SERVICE_ERROR.getCode()
, BusinessExceptionEnum.BUSINESS_CALLBACK_SERVICE_ERROR.getMsg() + ServiceNameConstants.DS_USER_PLATFORM + ", 接口方法名:findUserByMail");
}
};
}
}
这地方有个坑,原因是我们现在把服务调用Feign去拆出一个API项目出来(加注解项目会报错,当然本人有解决方案),但是本人不建议拆个服务出来,具体原因看下面的 其他 标题, 会详细分析。
Feign的fallback与fallbackFactory区别
使用Sentinel处理熔断、降级
本着对大家代码改动最小考虑,以前接口代码怎么样,现在还是一样,以前Feign调用fallback怎么处理(就是上面那个方案),现在还是一样。本人已经对Sentinel相关异常统一进行封装返回,一旦遇到Sentinel相关异常,则会默认抛出我的包装返回。
当然,在实际业务开发中,业务场景千奇百怪,虽然我封装的Sentinel默认返回处理适应大部分场景,但是还是有些业务场景需要指定自己的Sentinel异常。可以在需要特定Sentinel异常的接口加入@SentinelResource 注解,可以参照以下两个例子:
例子1:
- value 指定resource名称,这个必须加,而且尽量不要跟,其他资源名重复
- blockHandler 指定Sentinel异常返回的方法,参数必须跟接口一致+BlockException参数 ,需要注意的是,方法必须是跟接口在同一个类中
- defaultFallback 指定接口提供方的异常 或 BlockException 异常(这个可以替代Hystrix的fallback的功能),需要注意的是,方法必须是跟接口在同一个类中
@SentinelResource(value = "/uaa/hello", blockHandler = "exceptionHandler2", defaultFallback = "fallback2")
@GetMapping(value = "/hello")
public String abcd(@RequestParam(value = "name") String name, int age){
log.info("name={}",name);
log.info("age={}",age);
String content = nacosProviderClient.abc(name, age);
return "我是feign: " + content;
}
public String exceptionHandler2(String name, int age, BlockException ex) {.
ex.printStackTrace();
return "Oops, error occurred at "+ name + ","+ age;
}
public String fallback2(Throwable e){
log.info("进入sentinelResource注解测试,进入fallback2,参数b={}", e.getMessage());
return "defaultFallback";
}
例子2:
- value 同上
- fallbackClass 指定异常返回的类(跟例子1不同的是,上面的异常方法必须在本类,而fallbackClass 可以指定其它类)
- blockHandler & defaultFallback 跟例子1解释基本一样,不同的是,异常方法必须写在 fallbackClass 指定的类中
@GetMapping(value = "/abcd")
@SentinelResource(value = "abcd", fallbackClass = DefaultBlockFallbackHandler.class, defaultFallback = "defaultFallback")
public String abcd(String name){
if(name.equals("abc")){
throw new IllegalArgumentException("adffdgdfg");
}
if(name.equals("bbb")){
try {
Thread.sleep(900);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "aaaaaaasdjkl" + name;
}
以上两个例子大家可以根据自己业务实际情况,定制自己的异常,也可以不做任何处理,会默认走我封装的返回。此外,我还提供了一个通用异常类(例子2上面那个DefaultBlockFallbackHandler就是我对大家封装的一个通用异常),大家可以按需引用(不引用不会生效),通用异常类在sentinel-spring-boot-starter基础包中:
public class DefaultBlockFallbackHandler {
public static String defaultFallback(Throwable e){
if(e instanceof FlowException){
FlowException ex = (FlowException) e;
log.error("defaultFallback FlowException,资源信息={}", JSON.toJSONString(ex.getRule()));
throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_FLOWEXCEPTION);
} else if(e instanceof DegradeException){
DegradeException ex = (DegradeException) e;
log.error("defaultFallback DegradeException,资源信息={}", JSON.toJSONString(ex.getRule()));
throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_DEGRADEEXCEPTION);
} else if(e instanceof ParamFlowException){
ParamFlowException ex = (ParamFlowException) e;
log.error("defaultFallback ParamFlowException,资源名={},参数={},资源信息={}", ex.getResourceName(), ex.getLimitParam(), JSON.toJSONString(ex.getRule()));
throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_PARAMFLOWEXCEPTION.getCode(), BusinessExceptionEnum.BUSINESS_PARAMFLOWEXCEPTION.getMsg() + e.getMessage());
} else if(e instanceof AuthorityException){
AuthorityException ex = (AuthorityException) e;
log.error("defaultFallback AuthorityException,资源信息={}", JSON.toJSONString(ex.getRule()));
throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_AUTHORITYEXCEPTION);
}
throw new SeataTrBusinessException(999, "defaultFallback, error = " + e.getClass().getName() + ": " + e.getMessage());
}
}
无论是Feign+Hystrix方案还是Sentinel方案都需要大家根据自身业务需要去选型,也可以组合用。基本做到大家最少改动,就算大家现在代码什么都不改(除了Feign的fallback要加@Component注解外),也会走我封装的默认处理(默认会抛出异常)。但是还是那句话,结合自身业务场景去用,而不是一味的懒而不做处理(参照我上面Fallback类那块例子按自身业务去返回什么),默认走我抛出的异常。这时调用方就需要跟接口提供方协调代码异常是抛出BusinessException 还是 SeataTrBusinessException(这两种异常下面会进行说明),而不是什么都不处理或简单处理,线上接口出错,接口提供方和接口调用方互相指责
BusinessException 和 SeataTrBusinessException
说明:BusinessException 和 SeataTrBusinessException内部代码几乎一样,只是一个request state返回200,一个返回500
接口提供方代码异常无非就两种场景:
- 第一种场景是接口提供方抛出异常,调用方不捕获跟着抛出(SeataTrBusinessException)
- 第二种场景是接口提供方不抛异常(返回的是一个Result.fail对象),接口调用方拿到Result对象,去判断success是否是false,如果是false,进行相应业务错误处理,否则是true拿着接口提供方返回的数据继续执行后续业务代码(BusinessException )
SeataTrBusinessException:接口提供方异常会被抛出,这时调用方不处理(没有走feign fallback或者try cache捕获),也会跟着往上抛而出错。这种适合上面第一种场景。
BusinessException :接口提供方异常不会抛出,调用方拿到的是Result.fail(success=false)的对象,这时调用方可以根据success去判断接下来怎么处理。这种适合上面第二种场景
注意:SeataTrBusinessException如果在分布式事务场景下,只要一方服务出现任何异常(包括调用服务超时),其他服务事务跟着会回滚。而BusinessException只会回滚那个报错的服务事务。
其他
另外我想说的是,不建议大家把服务拆分出一个API项目出来,就比如拿上面的Feign fallback为例,如果服务拆分出来,会导致你不加@Component注解,服务可以调用,但是熔断、降级失效;加了这个注解,你连你自己的服务因为报错都启动不起来。(当然这个我也有解决方案)
Dubbo服务间调用默认是一种二进制TCP协议,它本身就需要握手来传输数据,所以导致他是有状态的,这就是为什么dubbo服务调用是调用对方的service,协议设计上没有足够的前瞻性,不适合做 service-mesh。但是熟悉dubbo的人肯定会想用dubbo这种调用方式,但是这种方式本身就不适合服务间调用(具体可以看dubbo协议介绍)。
而Spring Cloud的走的是HTTP协议,HTTP协议本身就是无状态的(对同一个url请求没有上下文关系),就算服务提供方的服务本身有问题,我调用方最多包一层处理就好了,也总比我依赖你的API POM包,导致我调用方服务都运行不起来好。
所以我的建议是Feign调用就在自身服务里,不要单独拆个API项目给别人用。
链路监控
Sentinel提供链路监控功能,能监控每个请求,或者某个微服务的QPS或响应时间(该功能需要添加网关),所以能充当一部分监控功能。