023:整合sentinel实现服务保护
1 基于sentinel实现服务保护效果演示
今日课程任务
- 微服务电商项目如何实现接口限流
- 目前主流限流框架实现方案有哪些
- Api接口限流常用算法有哪些
- 基于RedisSetNx实现Api接口的限流
- 基于谷歌的guava实现Api接口的限流
- 服务集群如何整合guavaApi接口的限流
- 基于sentinel实现微服务接口服务保护
- sentinel整合sentinel控制台
2 为什么需要在网关服务中实现限流
秒杀频率限制 在秒杀服务中setnx命令根据手机号码实现限流
缺点:对redis的访问压力大、无法通过页面形式实现控制
限流目的:保护服务
例如限制1w访问量,超过1w的请求走服务降级返回友好的提示给客户端(“当前用户访问人数过多请稍后重试”)。
接口设置阈值:在上线之前服务会做压力测试,测试服务最大支撑阈值。
注意:限流代码配置统一在请求入口(nginx、网关)中实现,不同的服务走不同的限流策略。
- 基于nginx+lua实现限流,对开发者学习成本比较高,一般运维负责。
- 基于微服务网关实现限流,对开发者学习成本比较低,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注解版本
以上过滤器配置缺陷:
- 对所有服务实现限流
- 没有根据参数实现限流
- 没有采用注解的形式
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
微服务网关如何获取请求参数信息
控制台热点流量配置即可。