sentinel集成 自定义 origin (ip限流)限流 IP 黑白名单 熔断降级

sentinel手册
spring:
  cloud:
    sentinel:
      # 取消控制台懒加载
      eager: true
      # sentinel 地址
      transport:
        dashboard: 192.168.1.22:8858
        port: 8719
      #将sentinel配置信息持久化到nacos 熔断规则
      datasource:
        ds1:
          nacos:
            server-addr: 52.83.239.30:8848
            dataId: gateway-server-flow
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow 
            ## flow 流控规则 degrade 降级规则 system 系统规则 authority 授权规则 param-flow 热点规则
限流规则
  [
    {
      "resource": "/swagger-ui.html", // 资源名称
      "limitApp": "192.168.1.25", // 来源应用 default 不区分调用者
      "grade": 1, // 阀值类型,0表示线程数,1表示QPS
      "count": 1, // 单机阀值
      "strategy": 0, // 流控模式,0表示直接,1表示关联,2表示链路
      "controlBehavior": 0, // 流控效果,0表示快速失败,1表示Warm UP,2表示排队等待
      "clusterMode": false // 是否集群
    }
  ]
  // 192.168.1.25来源的请求 qps 为 1(每秒的查询数1次) 
降级规则
  [
    {
      "resource": "/swagger-ui.html", // 资源名称
      "count": 3, // 最大Rt(ms)(平均响应时间 ) 比例阀值 异常数 
      "grade": 0, // 熔断策略 0 RT 1 异常比例 2 异常数
      "timeWindow": 10, // 熔断时长S 10秒后回复
    }
  ]
  // 一秒内进来n个请求,平均响应时长均超过rt。对此接口进行熔断降级。10秒后恢复
热点规则

黑白名单
  [
    {
      "resource": "/swagger-ui.html",  // 资源名称
      "limitApp": "192.168.1.25,127.0.0.1", //  对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
      "strategy": 1 // 限流模式 0 白名单 1 黑名单
    }
  ]
  // 192.168.1.25 和 127.0.0.1 的来源禁止请求

关于 limitApp 及自定义异常处理

WebMVC 设置来源 limitApp

开发包
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
重写RequestOriginParser 设置来源
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import com.cloud.common.web.utils.IpUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 刘志强
 * @date 2020/9/24 16:51
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 从header中获取名为 origin 的参数并返回
        String ip = IpUtils.getIpAddr(request);
        return ip;
    }
}
覆盖 DefaultUrlBlockHandler 异常处理器处理器
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cloud.common.core.result.AjaxResult;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyUrlBlockHandler implements UrlBlockHandler {
    public MyUrlBlockHandler() {
    }

    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
        if (AuthorityException.isBlockException(ex)) {
            writer(request,response,AjaxResult.error("被sentinel拦截"));
        }
    }

    public static boolean writer(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AjaxResult ajaxResult) {
        PrintWriter writer = null;
        String originHeader = httpServletRequest.getHeader("Origin");
        httpServletResponse.setHeader("Access-Control-Allow-Origin", originHeader);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.addHeader("Vary", "Origin");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try {
            JSONObject jsonObject = (JSONObject) JSON.toJSON(ajaxResult);
            writer = httpServletResponse.getWriter();
            writer.append(jsonObject.toString());
        } catch (IOException e) {
            System.out.println("response error" + e);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
        return false;
    }
}
将异常处理器添加至WebCallbackManager中
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 刘志强
 * @date 2020/9/27 09:16
 */

@Configuration
public class SentinelConfig {

    @Bean
    public MyUrlBlockHandler webCallbackManager() {
        MyUrlBlockHandler myUrlBlockHandler = new MyUrlBlockHandler();
        WebCallbackManager.setUrlBlockHandler(myUrlBlockHandler);
        return myUrlBlockHandler;
    }
}

WebFlux 设置来源 limitApp (Gateway 也可以这样写)

开发包
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
取消SentinelWebFluxFilter 及异常处理器 自定过滤器,重写SentinelWebFluxFilter过滤器 及异常处理器
取消默认的过滤器
spring:
  cloud:
    sentinel:
      filter:
        enabled: false
重写SentinelWebFluxFilter过滤器
import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

/**
 * @author 刘志强
 * @date 2020/9/25 15:31
 */
@Configuration
public class SentinelConfig {

    /**
     * 自定义Sentinel过滤器
     * @return
     */
    @Bean
    @Order(-2)
    public WebFilter sentinelWebFluxFilter()
    {
        return new MySentinelWebFluxFilter();
    }

    /**
     * 自定义异常处理器
     * @return
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public WebExceptionHandler sentinelBlockExceptionHandler() {
        return new MySentinelBlockExceptionHandler();
    }
}

import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.adapter.reactor.ContextConfig;
import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig;
import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer;
import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter;
import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager;
import java.util.Optional;

import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;


/**
 * 自定义过滤器
 * @author admin
 */
public class MySentinelWebFluxFilter extends SentinelWebFluxFilter {
    private static final String EMPTY_ORIGIN = "";

    public MySentinelWebFluxFilter() {
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).transform(this.buildSentinelTransformer(exchange));
    }

    private SentinelReactorTransformer<Void> buildSentinelTransformer(ServerWebExchange exchange) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String path = exchange.getRequest().getPath().value();
        String finalPath = (String)Optional.ofNullable(WebFluxCallbackManager.getUrlCleaner()).map((f) -> {
            return (String)f.apply(exchange, path);
        }).orElse(path);
//        String origin = (String)Optional.ofNullable(WebFluxCallbackManager.getRequestOriginParser()).map((f) -> {
//            return (String)f.apply(exchange);
//        }).orElse("");
        String origin = getIpAddress(serverHttpRequest);
        return new SentinelReactorTransformer(new EntryConfig(finalPath, EntryType.IN, new ContextConfig(finalPath, origin)));
    }


    public static String getIpAddress(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }
}
自定义异常处理器

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;

import java.nio.charset.StandardCharsets;

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

/**
 * @author admin
 */
public class MySentinelBlockExceptionHandler implements WebExceptionHandler {


    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{\"status\":429,\"message\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }
        if (!BlockException.isBlockException(ex)) {
            return Mono.error(ex);
        }
        return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
    }

    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}
  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Sentinel熔断降级限流是一种用于保护应用程序的机制,它可以防止应用程序不断执行可能失败的操作,并快速拒绝可能导致错误的调用。熔断降级规则是熔断降级机制的核心属性之一。\[1\] 熔断降级规则的配置可以通过在应用程序中设置相关属性来实现。例如,在Spring Cloud中,可以通过配置`spring.cloud.sentinel.transport.dashboard`属性来指定Sentinel控制台的地址,以及通过`management.endpoints.web.exposure.include`属性来开放管理端点。\[2\] 熔断降级机制的作用是在感知到下游服务的资源出现不稳定状态时,暂时切断对下游服务的调用,而不是一直阻塞等待服务响应。这样可以防止级联失败导致的雪崩效应,并保证系统的可用性。在微服务架构下,熔断降级机制尤为重要,可以保护后端不会过载。\[3\] 熔断降级规则包含一些核心属性,可以根据具体需求进行配置。这些属性包括但不限于超时时间、异常比例、最小请求数等。当资源被熔断降级后,在降级时间窗口内,对该资源的调用会自动返回降级数据。当检测到该节点的服务调用响应正常后,熔断机制会恢复调用链路。\[3\] 总结来说,Sentinel熔断降级限流是一种保护应用程序的机制,通过设置熔断降级规则来防止应用程序执行可能失败的操作,并在下游服务资源不稳定时暂时切断对下游服务的调用。这样可以防止级联失败导致的雪崩效应,并保证系统的可用性。熔断降级规则包含一些核心属性,可以根据具体需求进行配置。\[1\]\[3\] #### 引用[.reference_title] - *1* *3* [sentinel 限流熔断神器详细介绍](https://blog.csdn.net/a745233700/article/details/122733366)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Sentinel服务流控、熔断降级](https://blog.csdn.net/m0_49183244/article/details/124273538)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值