文章目录
- 1.简单介绍
- 2.使用示例
- 3.主要实现原理和组成部分
- 4.动态规则数据源
本篇文章主要介绍熔断限流框架Sentinel的使用示例、组成原理和动态规则数据源的实现原理。
1.简单介绍
阿里的熔断限流框架Sentinel基于滑动时间窗口实现熔断限流管控的,支持多样的管控场景:
- 流量管控;
- 熔断降级;
- 系统负载管控;
- 热点参数限流等。
其中使用最多的就是流量管控和熔断降级两个功能。Sentinel是基于 槽实现 和 规则 来完成具体的管控场景,规则被对应的 Manager管理器 以 静态成员属性 的形式维护在类中,槽实现需要获取规则时则直接根据资源名称去 规则管理器 中获取。下面是官方功能宣传图的简化版:
除了绿色部分的功能特性外,Sentinel也支持动态规则配置,目前支持Zookeeper、Nacos和Apollo三个动态数配置源。其中还有个控制台,控制台可以完成数据监控和动态规则配置,但是不可持久化,且不推荐在生产环境使用,介绍意义不大,因此控制台可以忽略。
2.使用示例
下面是实现熔断降级的简单示例:
/** 模拟调用类 */
public class TestService {
private final Random random = new Random();
public void test() {
if (random.nextInt(10) <= 5) {
throw new RuntimeException("随机数小于等于5");
}
}
}
/** 模拟测试类 */
@RunWith(MockitoJUnitRunner.class)
public class TestServiceMockTest {
@Spy
private TestService testService;
@Test
public void testDegrade() throws InterruptedException {
DegradeRule degradeRule = new DegradeRule();
// 设置资源名称
degradeRule.setResource("test");
// 设置故障数量
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 数量大于5
degradeRule.setCount(5);
// 熔断降级时间
degradeRule.setTimeWindow(1);
// 统计时间窗口
degradeRule.setStatIntervalMs(400);
// 以上规则配置生效场景:
// 当在400ms内,程序抛故障数量>5,则熔断降级1s,之后半开闭,直到程序正常
DegradeRuleManager.setRulesForResource("test", Collections.singleton(degradeRule));
for (int i = 0;i < 30;i ++) {
Entry entry = null;
try {
entry = SphU.entry("test");
testService.test();
} catch (Exception e) {
if (e instanceof BlockException) {
System.out.println(i + " blocked,execute degrade method");
} else {
// 抛出异常需要正常追踪
Tracer.trace(e);
System.out.println(e.getMessage());
}
} finally {
if (entry != null) {
entry.exit();
}
}
Thread.sleep(10);
}
}
}
流量管控就不做过多介绍了,实现方式和熔断降级类似,只是配置的规则不同。可以从上述例子看到主要内容就三部分:
- 制定规则内容并加载到对应的规则管理器中,这部分需要开发者针对不同的资源制定不同的规则;
- 使用Sentine开放出来的静态方法完成对被管控方法的监听,熔断降级使用SphU-Entr-Tracer进行监听,Tracer需要追踪异常抛出情况;
- 如果Sentinel触发了熔断限流,则会抛出BlockException,熔断降级是DegradeException,开发者可自行编写熔断降级后的逻辑。
从上面这个例子可以看出Sentinel最基本的使用方式是非常简单,很容易接入。
3.主要实现原理和组成部分
仅从最常使用的熔断降级和流量管控两个方面简单的分析一下执行流程和其中的重要部分:
- SphU:Sentinel对外提供的静态方法,触发熔断限流后直接抛出异常。与其对应的还有SphO,触发熔断限流后将返回true或false,使用方法在两个类的注解中都有简单案例;
- Sph:静态方法实际调用的功能对象,默认使用CtSph实现类,Env类有该实现类的静态常量,平时使用的也都是Env类的静态常量;
- Context:Sentinel统计调用方法时的上下文信息,包括当前调用点、统计信息及入口信息;
- Entry:入口类,每次调用静态方法都会返回Entry类,保存了当前资源调用的信息;
- ProcessorSlotChain:Sentinel使用不同的槽来实现不同的处理逻辑,最外层便使用了槽列表来管理不同的槽实现,并对其排序控制调用顺序;
- FlowSlot:流量管控的槽实现,会直接从FlowRuleManager中获取资源名称对应的流量规则FlowRule,使用FlowRule进行规则判断;
- DegradeSlot:熔断降级的槽实现,会从DegradeRuleManager中获取资源对应的断路器CircuitBreaker列表,CircuitBreaker由DegradeRule转换生成的。
FlowRuleManager和DegradeRuleManager维护对应的流量规则FlowRule和熔断降级规则DegradeRule使用的是内部静态成员对象,代码如下:
/** 熔断降级规则管理器 */
public final class DegradeRuleManager {
/** 由熔断限流规则对象生成的断路器对象,key=资源名称,value=对应的断路器列表 */
private static volatile Map<String, List<CircuitBreaker>> circuitBreakers = new HashMap<>();
/** 管理器保存的熔断降级规则对象,key=资源名称,value=熔断降级规则列表 */
private static volatile Map<String, Set<DegradeRule>> ruleMap = new HashMap<>();
}
/** 流量管控规则管理器 */
public class FlowRuleManager {
/** 管理器保存的流量管控规则对象,key=资源名称,value=流量管控规则列表 */
private static final Map<String, List<FlowRule>> flowRules = new ConcurrentHashMap<String, List<FlowRule>>();
}
初始化规则和需要更新规则时,则是通过Manager暴露出去的方法新增修改这些静态成员对象,以达到新增规则和更新规则的目的。DegradeRuleManager中使用volatile修饰静态成员对象,是因为由DegradeRule转换成CircuitBreaker需要经过一系列的计算,同时修改两个对象的内容。而FlowRuleManager只需要更新一个静态成员对象,因此无需使用volatile修饰。
4.动态规则数据源
由上面的内容我们可以得知,Sentinel的各种规则是被保存在对应Manger管理器的静态成员属性中的,在Sentinel运行判断时会直接从静态成员属性中获取对应的规则对象。也就是说如果要实现动态规则更改,直接修改对应Manger管理器的静态成员属性即可,动态规则数据源实现原理如下:
动态数据源的实现分为两个部分:
- 启动时初始化规则;
- 数据源数据修改后的监听更新。
数据转换器: 从动态数据源拉取数据后,需要把数据转换为Sentinel对应的规则对象,如流量管控的规则对象FlowRule;熔断降级的规则对象DegradeRule。动态数据源的格式可自定义,只需要实现对应的数据转换器即可。
以下是不借助Spring容器,使用Sentinel-Zookeeper包的ReadableDataSource实现类,以Zookeeper为数据源的代码示例:
@RunWith(MockitoJUnitRunner.class)
public class ZookeeperDataSourceMockTest {
@Test
public void test() {
// 使用的Zookeeper当远程数据源,确认地址和路径
String serverAddr = "localhost:2181";
String path = "xx/sentinelConfig.properties";
// 获得节点的数据后需要对数据进行解析,具体格式没做强制要求
// 如果是纯Json格式官方有实现类JsonConverter
Converter<String, List<DegradeRule>> converter = new LocalConverter();
// 实例化Zookeeper的数据源,里面会自动连接拉取数据
ReadableDataSource<String, List<DegradeRule>> zookeeperDataSource =
new ZookeeperDataSource<>(serverAddr, path, converter);
// 这一步是为了将数据源的数据注册到规则管理器中
DegradeRuleManager.register2Property(zookeeperDataSource.getProperty());
// 测试打印代码
Thread t = new Thread(() -> {
while (true) {
try {
System.out.println("rules:" + JSONArray.toJSONString(DegradeRuleManager.getRules()));
Thread.sleep(3000);
System.out.println("-----------------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static class LocalConverter implements Converter<String, List<DegradeRule>> {
@Override
public List<DegradeRule> convert(String source) {
// 将返回的string对象转成需要的规则列表,这是省略,可自定义需要的规则属性
return null;
}
}
}
上面是一个以Zookeeper为数据源实现的简单例子,功能及流程如下:
- 确定Zookeeper服务器和获取数据节点的路径;
- 实现节点数据转换成对应规则对象的转换器,官方支持JSON和XML格式;
- 实例化Zookeeper数据源;
- 将数据源属性注册到对应的规则管理器中。
完成上面的四步,就可以正常的从远程数据源拉取并监听节点数据,此时就可以在远程动态的修改管理动态规则数据了,十分便捷简。生产推荐使用远程数据源的方式,不推荐使用控制台方式。
上面的代码依赖于下面的maven:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
本篇文章先为深入Sentinel打个基础,先了解Sentinel的大致轮廓及重要组成部分的原理,后续再分析更深入的部分。