高并发系统关注的问题
高并发系统关注的问题
- 独立部署,把高并发系统(例如:秒杀系统)放在单独的模块中
- 秒杀链接加密,例如,可以给秒杀商品加随机码,只有随机码正确,才进行后面的逻辑,随机码只有在到了秒杀时间才返回给用户
- 库存预热,例如,把商品放在redis中,通过信号量来决定是否能扣减
- 动静分离,例如,静态内容直接在nginx中处理
- 恶意请求拦截,例如,登录拦截器,风控系统
- 流量错峰,例如,验证码
- 限流熔断降级,例如,使用sentinel
- 消息队列,秒杀成功的请求放入队列进行处理
- 1~6,8保证快,7保证稳
熔断、降级、限流
文档
-
Sentinel官方网站:https://sentinelguard.io/
-
与SpringCoud整合:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
-
与主流框架的适配:https://github.com/alibaba/Sentinel/wiki/主流框架的适配#feign
-
与Feign整合:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
sentinel配置
Hystrix与Sentinel
基本配置
pom
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
yaml
spring:
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
port: 8719 #指定客户端监控 API 的端口(默认是 8719)
feign:
sentinel:
enabled: true # 激活Sentinel对Feign的支持
management:
endpoints:
web:
exposure:
include: *
main
@EnableDiscoveryClient
可视化界面:
持久化
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
port: 8719
datasource: #添加Nacos数据源配置
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
json样例:
[{
"resource": "/rateLimit/byUrl",
"IimitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}]
解释:
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数, 1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。
例子
定义资源
官网:https://sentinelguard.io/zh-cn/docs/quick-start.html
基于代码
// 1.5.0 版本开始可以直接利用 try-with-resources 特性
try (Entry entry = SphU.entry("HelloWorld")) {
// 被保护的逻辑
System.out.println("hello world");
} catch (BlockException ex) {
// 处理被流控的逻辑
System.out.println("blocked!");
}
基于注解
使用@SentinelResource注解可以定义一个资源,例如
@SentinelResource("HelloWorld")
public void helloWorld() {
// 资源中的逻辑
System.out.println("hello world");
}
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
value
:资源名称,必需项(不能为空)entryType
:entry 类型,可选项(默认为EntryType.OUT
)blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。fallback
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
defaultFallback
(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所以类型的异常(除了exceptionsToIgnore
里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
示例:
- 分别使用注解和try-with-resource定义资源:
- 在sentinel控制台能够对这两个资源进行操作:
RestTemplate
@RestController
@Slf4j
public class CircleBreakerController{
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback") //没有配置
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")//若blockHandler和fallback 都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。
public CommonResult<Payment> fallback(@PathVariable Long id){
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
Feign
Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 2 个步骤:
-
配置文件打开 Sentinel 对 Feign 的支持:
feign.sentinel.enabled=true
-
加入
spring-cloud-starter-openfeign
依赖使 Sentinel starter 中的自动化配置类生效:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
这是一个 FeignClient 的简单使用示例:
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class)
public interface EchoService {
@GetMapping("/echo/{str}")
String echo(@PathVariable("str") String str);
}
//必须加入容器
@Component
class EchoServiceFallback implements EchoService {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
}
Gateway
官网:
- 跟 Sentinel Starter 配合使用:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#spring-cloud-gateway-支持(引入pom)
- 网关限流:https://github.com/alibaba/Sentinel/wiki/网关限流(详细使用)
配置:
1、pom引入:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
2、配置文件
spring.cloud.sentinel.transport.port=8379
spring.cloud.sentinel.transport.dashboard=localhost:8080
3、可以在 GatewayCallbackManager
注册回调进行定制:
-
setBlockHandler
:注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为BlockRequestHandler
。默认实现为DefaultBlockRequestHandler
,当被限流时会返回类似于下面的错误信息:Blocked by Sentinel: FlowException
。
例如:
- 自定义限流后的提示信息:
@Configuration public class MyGatewayCallbackManager { public MyGatewayCallbackManager() { GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) { String jsonResp = "自定义限流返回信息"; Mono<ServerResponse> body = ServerResponse.ok().body(Mono.just(jsonResp), String.class); return body; } }); } }