基于Vue+SpringCloudAlibaba微服务电商项目实战-023:整合sentinel实现服务保护

1 基于sentinel实现服务保护效果演示

今日课程任务

  1. 微服务电商项目如何实现接口限流
  2. 目前主流限流框架实现方案有哪些
  3. Api接口限流常用算法有哪些
  4. 基于RedisSetNx实现Api接口的限流
  5. 基于谷歌的guava实现Api接口的限流
  6. 服务集群如何整合guavaApi接口的限流
  7. 基于sentinel实现微服务接口服务保护
  8. sentinel整合sentinel控制台

2 为什么需要在网关服务中实现限流

秒杀频率限制 在秒杀服务中setnx命令根据手机号码实现限流
缺点:对redis的访问压力大、无法通过页面形式实现控制

限流目的:保护服务
例如限制1w访问量,超过1w的请求走服务降级返回友好的提示给客户端(“当前用户访问人数过多请稍后重试”)。

接口设置阈值:在上线之前服务会做压力测试,测试服务最大支撑阈值。
注意:限流代码配置统一在请求入口(nginx、网关)中实现,不同的服务走不同的限流策略。

  1. 基于nginx+lua实现限流,对开发者学习成本比较高,一般运维负责。
  2. 基于微服务网关实现限流,对开发者学习成本比较低,java开发工程师负责。
    限流的实现方案:基于nginx+lua结合实现限流、基于微服务网关实现限流(整合guava、redisSetNx、sentinel实现Api接口的限流)

3 微服务网关中实现秒杀服务转发

限流算法:令牌桶、令牌桶、滑动窗口算法
配置网关服务application.yml

server:
  port: 81
spring:
  application:
    name: mayikt-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      locator:
        enabled: true
      routes:
        - id: mayikt-seckill
          uri: lb://mayikt-seckill
          predicates:
            - Path=/mayikt-seckill/**
          filters:
            - SwaggerHeaderFilter
            - StripPrefix=1
      x-forwarded:
        enabled: false

测试效果:
在这里插入图片描述

4 基于ratelimiter实现微服务限流

引入依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

限制接口每秒只有一个请求进行访问(初始版本)

@Component
public class SeckillFilter implements GlobalFilter, Ordered {
    // 创建一个限流器,参数代表每秒生成1个令牌数(用户限流率设置 每秒限制1个请求)
    private RateLimiter rateLimiter = RateLimiter.create(1);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = response.getHeaders();
        header.add("Content-Type", "application/json; charset=UTF-8");
        boolean tryAcquire = rateLimiter.tryAcquire(0, TimeUnit.SECONDS);
        if(!tryAcquire){
            JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
            DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        // 放行转发到秒杀服务
        return chain.filter(exchange);
    }

    /**
     * 设置优先级,数值越小越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }
    private JSONObject setResultErrorMsg(String msg) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "500");
        jsonObject.put("msg", msg);
        return jsonObject;
    }
}

效果测试:
在这里插入图片描述

5 ratelimit配置根据用户频率限流

SeckillFilter

@Component
public class SeckillFilter implements GlobalFilter, Ordered {
    // 创建一个限流器,参数代表每秒生成1个令牌数(用户限流率设置 每秒限制1个请求)
//    private RateLimiter rateLimiter = RateLimiter.create(1);
    LoadingCache<String, RateLimiter> ipRequestCaches = CacheBuilder.newBuilder()
            .maximumSize(1000)// 设置缓存个数
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .build(new CacheLoader<String, RateLimiter>() {
                @Override
                public RateLimiter load(String s) throws Exception {
                    return RateLimiter.create(1);// (限流每秒1个令牌响应)
                }
            });

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = response.getHeaders();
        header.add("Content-Type", "application/json; charset=UTF-8");
        RequestPath path = request.getPath();
        // 根据用户请求地址进行设置限流(可以看做对不同用户进行限定)不同用户每秒可以访问一次
        RateLimiter rateLimiter = ipRequestCaches.get(request.getURI().toString());
        boolean tryAcquire = rateLimiter.tryAcquire();
        if (!tryAcquire) {
            JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
            DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
            return response.writeWith(Mono.just(buffer));
        }

        // 放行转发到秒杀服务
        return chain.filter(exchange);
    }

    /**
     * 设置优先级,数值越小越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }
    private JSONObject setResultErrorMsg(String msg) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "500");
        jsonObject.put("msg", msg);
        return jsonObject;
    }
}

网关集群如何实现ratelimit限流
如果guava在微服务网关中实现集群如何配置限流策略

根据网关平均值配置,例如nginx配置两个网关集群,nginx配置流量权重为3、7,限制整个服务1qps,网关分配配置生成令牌速率为0.3和0.7。

6 微服务网关中整合sentinel实现限流

引入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

Sentinel整合微服务有代码和注解两种方式,@SentinelResource("")只适用于微服务接口上(网关不会通过接口传参数)。

项目启动的时候初始化配置 项目启动的时候向sentinel内存中创建限流规则

@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GateWayConstant.GETORDER_SECKILL_KEY);
        // QPS控制在1以内
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        log.info(">>>限流服务接口配置加载成功>>>");
    }
}

SeckillFilter

@Component
public class SeckillFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = response.getHeaders();
        header.add("Content-Type", "application/json; charset=UTF-8");
        Entry entry = null;
        try {
            entry = SphU.entry(GateWayConstant.GETORDER_SECKILL_KEY);
            // 执行我们服务需要保护的业务逻辑
            return chain.filter(exchange);
        } catch (Exception e) {
            e.printStackTrace();
            JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
            DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
            return response.writeWith(Mono.just(buffer));
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }

    private JSONObject setResultErrorMsg(String msg) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "500");
        jsonObject.put("msg", msg);
        return jsonObject;
    }
}

效果测试:
在这里插入图片描述

7 sentinel整合控制台实现界面管理

启动Sentinel控制台

java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719  -jar D:\devtool\sentinel\sentinel-dashboard.jar

application.yml新增配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8718
      eager: true

过滤器SeckillFilter

@Component
public class SeckillFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = response.getHeaders();
        header.add("Content-Type", "application/json; charset=UTF-8");
        Entry entry = null;
        try {
            entry = SphU.entry(GateWayConstant.GETORDER_SECKILL_KEY);
            // 执行我们服务需要保护的业务逻辑
            return chain.filter(exchange);
        } catch (Exception e) {
            e.printStackTrace();
            JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
            DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
            return response.writeWith(Mono.just(buffer));
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }

    private JSONObject setResultErrorMsg(String msg) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "500");
        jsonObject.put("msg", msg);
        return jsonObject;
    }
}

启动加载限流规则

@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource(GateWayConstant.GETORDER_SECKILL_KEY);
        // QPS控制在1以内
        rule1.setCount(1);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
        log.info(">>>限流服务接口配置加载成功>>>");
    }
}

流控制规则也可手动控制台新增,注意资源名与过滤器中
GateWayConstant.GETORDER_SECKILL_KEY一定要保持一致。

测试效果:
在这里插入图片描述

8 微服务网关中如何使用sentinel注解版本

以上过滤器配置缺陷:

  1. 对所有服务实现限流
  2. 没有根据参数实现限流
  3. 没有采用注解的形式

sentinel注解版本是一般写在服务接口上,如何整合进网关服务?
在网关中新增一个service接口,里面写一个伪秒杀方法(方法参数和秒杀服务参数保持一致)。

过滤器优化解决上述问题

@Component
public class SeckillService {

    @SentinelResource(value = "seckill", blockHandler = "getSeckillQpsException")
    public String seckill(String userPhone, String seckillId) throws Exception {
        return "success";
    }

    public String getSeckillQpsException(String userPhone, String seckillId, BlockException e) throws Exception {
        throw new RuntimeException();
    }
}
@Component
public class SeckillFilter implements GlobalFilter {

    @Autowired
    private SeckillService seckillService;
    @Value("${gateway.seckill.intercept.url}")
    private List<String> gatewaySeckillInterceptUrl;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = response.getHeaders();
        header.add("Content-Type", "application/json; charset=UTF-8");
        RequestPath path = request.getPath();
        // 排除请求
        boolean resultExist = existSeckillUrl(path.value());
        if (!resultExist) {
            // 直接放行
            return chain.filter(exchange);
        }
        try {
            String userPhone = getParam(request.getURI().getQuery(), "userPhone");
            String seckillId = getParam(request.getURI().getQuery(), "seckillId");
            seckillService.seckill(userPhone, seckillId);
            return chain.filter(exchange);
        } catch (Exception e) {
            JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
            DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
            return response.writeWith(Mono.just(buffer));
        }
    }

    public static String getParam(String url, String name) {
        String params = url.substring(url.indexOf("?") + 1, url.length());
        Map<String, String> split = Splitter.on("&").withKeyValueSeparator("=").split(params);
        return split.get(name);
    }

    public boolean existSeckillUrl(String url) {
        return gatewaySeckillInterceptUrl.contains(url);
    }

    private JSONObject setResultErrorMsg(String msg) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "500");
        jsonObject.put("msg", msg);
        return jsonObject;
    }
}

application.yml新增配置

###配置哪些接口需要限流
gateway:
  seckill:
    intercept:
      url: /spike

在这里插入图片描述
微服务网关如何获取请求参数信息
控制台热点流量配置即可。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值