雪崩效应

常见容错方案

  • 超时
  • 限流
  • 仓壁模式
  • 断路器模式
断路器三态转换

使用Sentinel实现容错

什么是Sentinel

https://github.com/alibaba/Sentinel

轻量级的流量控制、熔断降级 Java 库

pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

搭建Sentinel控制台

下载控制台

https://github.com/alibaba/Sentinel/releases

项目整合sentinel控制台
application.yml
spring:
cloud:
sentinel:
filter:
# 打开/关闭掉对Spring MVC端点的保护
enabled: true
transport:
# 指定sentinel 控制台的地址
dashboard: localhost:8080

流控规则

直接

  • 快速失败

  • Warm Up

  • 排队等待

  • 直接拒绝:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任何规则的阈值后,新的请求就会立即拒绝,拒绝方式为抛出FlowException . 这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

  • Warm Up:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处理低水平的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值的上限,给系统一个预热的时间,避免冷系统被压垮。通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:

  • 均速排队:(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式后严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法。该方式的作用如下图所示:

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

降级规则

热点规则详解

@GetMapping("test-hot")
@SentinelResource("hot")
public String testHot(
@RequestParam(required = false) String a,
@RequestParam(required = false) String b
) {
return a + " " + b;
}

在sentinel控制台配置热点规则

系统规则详解

授权规则

使用代码配置流控规则

@GetMapping("test-add-flow-rule")
public String testHot() {
this.initFlowQpsRule();
return "success";
} private void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule("/shares/1");
// set limit qps to 20
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}

sentinel与控制台通信原理剖析

sentinel控制台相关配置

sentinel Api详解

@GetMapping("/test-sentinel-api")
public String testSentinelAPI(
@RequestParam(required = false) String a) { String resourceName = "test-sentinel-api";
ContextUtil.enter(resourceName, "test-wfw"); // 定义一个sentinel保护的资源,名称是test-sentinel-api
Entry entry = null;
try { entry = SphU.entry(resourceName);
// 被保护的业务逻辑
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("a不能为空");
}
return a;
}
// 如果被保护的资源被限流或者降级了,就会抛BlockException
catch (BlockException e) {
log.warn("限流,或者降级了", e);
return "限流,或者降级了";
} catch (IllegalArgumentException e2) {
// 统计IllegalArgumentException【发生的次数、发生占比...】
Tracer.trace(e2);
return "参数非法!";
} finally {
if (entry != null) {
// 退出entry
entry.exit();
}
ContextUtil.exit();
}
}

@SentinelResource注解详解

@GetMapping("/test-sentinel-resource")
@SentinelResource(
value = "test-sentinel-api",
blockHandler = "block",
blockHandlerClass = TestControllerBlockHandlerClass.class,
fallback = "fallback"
)
public String testSentinelResource(@RequestParam(required = false) String a) {
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("a cannot be blank.");
}
return a;
} /**
* 1.5 处理降级
* - sentinel 1.6 可以处理Throwable
*
* @param a
* @return
*/
public String fallback(String a) {
return "限流,或者降级了 fallback";
}
TestControllerBlockHandlerClass
@Slf4j
public class TestControllerBlockHandlerClass {
/**
* 处理限流或者降级
*
* @param a
* @param e
* @return
*/
public static String block(String a, BlockException e) {
log.warn("限流,或者降级了 block", e);
return "限流,或者降级了 block";
}
}

RestTemplate整合sentinel

// 在spring容器中,创建一个对象,类型RestTemplate;名称/ID是:restTemplate
// <bean id="restTemplate" class="xxx.RestTemplate"/>
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
template.setInterceptors(
Collections.singletonList(
new TestRestTemplateTokenRelayInterceptor()
)
);
return template;
}
@Autowired
private RestTemplate restTemplate; @GetMapping("/test-rest-template-sentinel/{userId}")
public UserDTO test(@PathVariable Integer userId) {
return this.restTemplate
.getForObject(
"http://user-center/users/{userId}",
UserDTO.class, userId);
}

关闭@SentinelRestTemplate注解

resttemplate:
sentinel:
# 设置成false,表示关闭@SentinelRestTemplate注解
enabled: true

Feign整合sentinel

为feign整合sentinel
feign:
sentinel: # 为feign整合sentinel
enabled: true

代码验证

不能获取异常
@FeignClient(name = "user-center",
fallback = UserCenterFeignClientFallback.class,
)
public interface UserCenterFeignClient {
/**
* http://user-center/users/{id}
*
* @param id
* @return
*/
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
private final ShareMapper shareMapper;
private final UserCenterFeignClient userCenterFeignClient; public ShareDTO findById(Integer id) {
// 获取分享详情
Share share = this.shareMapper.selectByPrimaryKey(id);
// 发布人id
Integer userId = share.getUserId(); // 1. 代码不可读
// 2. 复杂的url难以维护:https://user-center/s?ie={ie}&f={f}&rsv_bp=1&rsv_idx=1&tn=baidu&wd=a&rsv_pq=c86459bd002cfbaa&rsv_t=edb19hb%2BvO%2BTySu8dtmbl%2F9dCK%2FIgdyUX%2BxuFYuE0G08aHH5FkeP3n3BXxw&rqlang=cn&rsv_enter=1&rsv_sug3=1&rsv_sug2=0&inputT=611&rsv_sug4=611
// 3. 难以相应需求的变化,变化很没有幸福感
// 4. 编程体验不统一
UserDTO userDTO = this.userCenterFeignClient.findById(userId); ShareDTO shareDTO = new ShareDTO();
// 消息的装配
BeanUtils.copyProperties(share, shareDTO);
shareDTO.setWxNickname(userDTO.getWxNickname());
return shareDTO;
}

UserCenterFeignClientFallback

@Component
public class UserCenterFeignClientFallback implements UserCenterFeignClient {
@Override
public UserDTO findById(Integer id) {
UserDTO userDTO = new UserDTO();
userDTO.setWxNickname("流控/降级返回的用户");
return userDTO;
}
}
可以捕获异常
@FeignClient(name = "user-center",
fallbackFactory = UserCenterFeignClientFallbackFactory.class
)
public interface UserCenterFeignClient {
/**
* http://user-center/users/{id}
*
* @param id
* @return
*/
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
private final ShareMapper shareMapper;
private final UserCenterFeignClient userCenterFeignClient; public ShareDTO findById(Integer id) {
// 获取分享详情
Share share = this.shareMapper.selectByPrimaryKey(id);
// 发布人id
Integer userId = share.getUserId(); // 1. 代码不可读
// 2. 复杂的url难以维护:https://user-center/s?ie={ie}&f={f}&rsv_bp=1&rsv_idx=1&tn=baidu&wd=a&rsv_pq=c86459bd002cfbaa&rsv_t=edb19hb%2BvO%2BTySu8dtmbl%2F9dCK%2FIgdyUX%2BxuFYuE0G08aHH5FkeP3n3BXxw&rqlang=cn&rsv_enter=1&rsv_sug3=1&rsv_sug2=0&inputT=611&rsv_sug4=611
// 3. 难以相应需求的变化,变化很没有幸福感
// 4. 编程体验不统一
UserDTO userDTO = this.userCenterFeignClient.findById(userId); ShareDTO shareDTO = new ShareDTO();
// 消息的装配
BeanUtils.copyProperties(share, shareDTO);
shareDTO.setWxNickname(userDTO.getWxNickname());
return shareDTO;
}

UserCenterFeignClientFallbackFactory

@Component
@Slf4j
public class UserCenterFeignClientFallbackFactory implements FallbackFactory<UserCenterFeignClient> {
@Override
public UserCenterFeignClient create(Throwable cause) {
return new UserCenterFeignClient() {
@Override
public UserDTO findById(Integer id) {
log.warn("远程调用被限流/降级了", cause);
UserDTO userDTO = new UserDTO();
userDTO.setWxNickname("流控/降级返回的用户");
return userDTO;
}
};
}
}

sentinel使用总结

sentinel规则持久化01-拉模式

拉模式架构

原理简述

  • FileRefreshableDataSource 定时从指定文件中读取规则JSON文件【图中的本地文件】,如果发现文件发生变化,就更新规则缓存。
  • FileWritableDataSource 接收控制台规则推送,并根据配置,修改规则JSON文件【图中的本地文件】。

编写

3.1 加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>
写代码
FileDataSourceInit
/**
* 拉模式规则持久化
*
* @author itmuch.com
*/
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
// TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json"; this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath); // 流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
// 将可读数据源注册至FlowRuleManager
// 这样当规则文件发生变化时,就会更新规则到内存
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
// 将可写数据源注册至transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS); // 降级规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS); // 系统规则
ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS); // 授权规则
ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS); // 热点参数规则
ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
} private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<FlowRule>>() {
}
);
private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<DegradeRule>>() {
}
);
private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<SystemRule>>() {
}
); private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<AuthorityRule>>() {
}
); private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<ParamFlowRule>>() {
}
); private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
} private void createFileIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
} private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}

配置

在项目的 resources/META-INF/services 目录下创建文件,名为 com.alibaba.csp.sentinel.init.InitFunc ,内容为:

# 改成上面FileDataSourceInit的包名类名全路径即可。
com.itmuch.contentcenter.FileDataSourceInit

优缺点分析

优点
  • 简单易懂
  • 没有多余依赖(比如配置中心、缓存等)
缺点
  • 由于规则是用 FileRefreshableDataSource 定时更新的,所以规则更新会有延迟。如果FileRefreshableDataSource定时时间过大,可能长时间延迟;如果FileRefreshableDataSource过小,又会影响性能;
  • 规则存储在本地文件,如果有一天需要迁移微服务,那么需要把规则文件一起迁移,否则规则会丢失。

sentinel规则持久化-推模式

推模式架构图

原理简述

  • 控制台推送规则:

    1. 将规则推送到Nacos或其他远程配置中心
    2. Sentinel客户端链接Nacos,获取规则配置;并监听Nacos配置变化,如发生变化,就更新本地缓存(从而让本地缓存总是和Nacos一致)
  • 控制台监听Nacos配置变化,如发生变化就更新本地缓存(从而让控制台本地缓存总是和Nacos一致)

微服务改造

加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
添加配置
spring:
cloud:
sentinel:
datasource:
# 名称随意
flow:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
# 规则类型,取值见:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
degrade:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade
system:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-system-rules
groupId: SENTINEL_GROUP
rule-type: system
authority:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-authority-rules
groupId: SENTINEL_GROUP
rule-type: authority
param-flow:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: SENTINEL_GROUP
rule-type: param-flow
推模式改造详情请查看如下地址

https://www.imooc.com/article/289464

在生产环境使用

https://help.aliyun.com/document_detail/90323.html

集群流控

扩展sentinel01-错误页优化

@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
ErrorMsg msg = null;
if (ex instanceof FlowException) {
msg = ErrorMsg.builder()
.status(100)
.msg("限流了")
.build();
} else if (ex instanceof DegradeException) {
msg = ErrorMsg.builder()
.status(101)
.msg("降级了")
.build();
} else if (ex instanceof ParamFlowException) {
msg = ErrorMsg.builder()
.status(102)
.msg("热点参数限流")
.build();
} else if (ex instanceof SystemBlockException) {
msg = ErrorMsg.builder()
.status(103)
.msg("系统规则(负载/...不满足要求)")
.build();
} else if (ex instanceof AuthorityException) {
msg = ErrorMsg.builder()
.status(104)
.msg("授权规则不通过")
.build();
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
// spring mvc自带的json操作工具,叫jackson
new ObjectMapper()
.writeValue(
response.getWriter(),
msg
);
}
} @Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ErrorMsg {
private Integer status;
private String msg;
}

扩展sentinel02-实现区分来源

限流规则里的来源值要和请求参数origin的值相等,限流规则才生效。

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 从请求参数中获取名为 origin 的参数并返回
// 如果获取不到origin参数,那么就抛异常 String origin = request.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must be specified");
}
return origin;
}
}

扩展sentinel03-RESTfulUrl

扩展sentinel04-通过现象看本质

配置项总结

https://www.imooc.com/article/289562

最新文章

  1. SSIS Design6:利用数据流
  2. js自动更新时间+星期
  3. Atitit cms wordpress get_post &#160;返回的WP_Post 规范 标准化
  4. 通过Class获取标签,兼容的几种思路
  5. Theano深度学习结构分析
  6. csharp:Dapper Sample
  7. 在eclipse中配置一个简单的spring入门项目
  8. 对OpenGL的GLFrame框架进行的扩展截至2014年11月29日
  9. ZOJ 2677 Oil Deal(最大生成树)
  10. svn教程
  11. css选择器优化
  12. [&lt;DDGuessYouLIkeModel 0x7c99faf0&gt; setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key star.
  13. Java泛型介绍!!!
  14. 从头开始学JavaScript (八)——变量
  15. npm 常用命令详解
  16. 利用angular给节点添加样式
  17. 几分钟搞定redis存储session共享——设计实现
  18. go语言指针理解
  19. vue富文本编辑器
  20. java 桥接模式

热门文章

  1. cf - 429D
  2. Nginx作为负载均衡——实战演练
  3. 今天我的jupyter notebook打不开了,报错原因&#39;No module named &#39;zmq.eventloop&#39;
  4. python 面向对象的内置方法
  5. prometheus和zabbix的对比
  6. HBase的安装、配置与实践
  7. CQBZOJ 【重庆市NOIP模拟赛】避难向导
  8. 团队第一次作业(By七个小矮人)
  9. 个人第四次作业--Alpha项目测试
  10. 异想家Ubuntu安装的软件