1. spring cloud . spring boot . alibaba . sentinel 版本
<spring-boot.version>2.3.5.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
sentinel-dashboard 1.8.0
2. 项目依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
3. sentinel 官网地址
https://github.com/alibaba/Sentinel
4. 配置微服务间统一限流降级处理
1. 配置服务间调用开关及发生降级后异常处理
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return xxxxxSentinelFeign.builder();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new xxxxxUrlBlockHandler();
}
}
2. 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 支持自动降级注入
public final class xxxxxSentinelFeign {
private xxxxxSentinelFeign() {
}
public static xxxxSentinelFeign.Builder builder() {
return new xxxxxSentinelFeign.Builder();
}
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignContext feignContext;
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public xxxxxSentinelFeign.Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// using reflect get fallback and fallbackFactory properties from
// FeignClientFactoryBean because FeignClientFactoryBean is a package
// level class, we can not use it in our package
Object feignClientFactoryBean = xxxxSentinelFeign.Builder.this.applicationContext
.getBean("&" + target.type().getName());
Class fallback = (Class) getFieldValue(feignClientFactoryBean, "fallback");
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, "fallbackFactory");
String beanName = (String) getFieldValue(feignClientFactoryBean, "contextId");
if (!StringUtils.hasText(beanName)) {
beanName = (String) getFieldValue(feignClientFactoryBean, "name");
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// check fallback and fallbackFactory properties
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
return new xxxxSentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(beanName, "fallbackFactory",
fallbackFactory, FallbackFactory.class);
return new xxxxSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
return new xxxxxSentinelInvocationHandler(target, dispatch);
}
private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
Object fallbackInstance = feignContext.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s", type, fallbackType, name));
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name));
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
private Object getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
field.setAccessible(true);
try {
return field.get(instance);
}
catch (IllegalAccessException e) {
// ignore
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignContext.class);
}
}
}
3. 降级后异常处理
public class xxxxxSentinelInvocationHandler implements InvocationHandler {
public static final String EQUALS = "equals";
public static final String HASH_CODE = "hashCode";
public static final String TO_STRING = "toString";
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
xxxxxxSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
xxxxxxSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if (EQUALS.equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
}
catch (IllegalArgumentException e) {
return false;
}
}
else if (HASH_CODE.equals(method.getName())) {
return hashCode();
}
else if (TO_STRING.equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
}
else {
String resourceName = methodMetadata.template().method().toUpperCase() + ":" + hardCodedTarget.url()
+ methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}
catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex),
args);
return fallbackResult;
}
catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
}
catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
}else {
// 若是XResponse类型 执行自动降级返回 XResponse
if (XResponse.class == method.getReturnType()) {
log.error("feign 服务间调用异常", ex);
//不能返回 XResponse , 涉及业务处理。 直接抛出异常,在全局异常中处理
// return XResponse.builder()
// .data(Boolean.FALSE)
// .msg("服务请求超时,请稍后重试")
// .httpStatus(HttpStatus.SERVICE_UNAVAILABLE)
// .build();
throw new FeginServierException("系统繁忙,请稍后重试");
}else {
throw ex;
}
}
}
finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
}
else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SentinelInvocationHandler) {
xxxxSentinelInvocationHandler other = (xxxxSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}
4. 服务间流控降级自定义返回
public class xxxxxUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
log.error("sentinel 降级 资源名称{}", e.getRule().getResource(), e);
response.setContentType(ContentType.JSON.toString());
response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
response.setCharacterEncoding("UTF-8");
response.getWriter().print(JSONUtil.toJsonStr(XResponse.builder()
.data(Boolean.FALSE)
.msg("服务请求超时,请稍后重试")
.httpStatus(HttpStatus.SERVICE_UNAVAILABLE)
.build()
.toResponseEntity()));
}
}
5. 网关集成流控
#######将上面4步操作集成公共的依赖, 网关与各个微服务集成 xxx-common-sentinel##########
<!--断路器网关依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.xxxx</groupId>
<artifactId>xxxx-common-sentinel</artifactId>
<version>1.0.0</version>
</dependency>
- 5.1 网关配置限流降级
@Configuration
public class GatewayConfiguration {
@PostConstruct
public void doInit() {
GatewayCallbackManager.setBlockHandler(new GateWayBlockRequestHandler());
}
}
- 5.2 服务返回 一般返回自定义的返回体 sentinel 不能根据自定义的返回体进行异常降级 需自已处理
@Component
public class GatewayBlockFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
// get match route id
Route route = (Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
String id = route.getId();
// 500 error -> degrade
if (originalResponse.getRawStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value() ) {
Entry entry = null;
try {
// 0 token
entry = SphU.entry(id, EntryType.OUT);
Tracer.trace(new RuntimeException("INTERNAL_SERVER_ERROR"));
} catch (BlockException e) {
log.error(e.getMessage(), e);
} finally {
if (entry != null){
entry.close();
}
}
}
return super.writeWith(body);
}
};
// replace response with decorator
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
6. sentinel 规则持久化 - 控制台(1.8.0)
-
6.1 在sentinel 官网下最新控制台源码
image.png -
6.2 将控制台源码拿到自己项目中部署
image.png -
6.3 修改持久化规则,在源码的test 目录中找到 nacos , 将nacos 目录拷贝到 源码的rule 目录下,完善流控降级的规则 。
image.png
image.png -
6.4 在自己微服务中添加sentinel对nacos 依赖 ( 可以添加到上面第四步 xxx-common-sentinel 公共的依赖里面 )
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
-
6.5 在网关配置里面将 sentinel 拉去配置的地址配置 (nacos)
image.png -
6.6 在所有服务依赖的配置文件中添加 其他规则配置拉取地址, 如在 application-dev.yml中
image.png -
6.7 默认网关启动在sentinel 控制台会跟普通服务显示一样 需要添加启动项
@SpringCloudApplication
public class XXXGatewayApplication {
public static void main(String[] args) {
System.setProperty("csp.sentinel.app.type", "1");
SpringApplication.run(XXXGatewayApplication.class, args);
}
}
网友评论