Soul源码阅读系列(一)Soul网关初探

本篇文章主要内容如下:

  • Soul是什么
  • 如何在本地运行Soul
  • Soul进行压测

Soul 是什么

Soul是什么?它可不是灵魂交友软件!

引用Soul的官网,它是这样描述Soul的:

这是一个异步的,高性能的,跨语言的,响应式的API网关。我希望能够有一样东西像灵魂一样,保护您的微服务。参考了KongSpring-Cloud-Gateway等优秀的网关后,站在巨人的肩膀上,Soul由此诞生!

Soul是一个网关,它的特点如下:

  • 支持各种语言(http协议),支持 dubbospring cloud协议。
  • 插件化设计思想,插件热插拔,易扩展。
  • 灵活的流量筛选,能满足各种流量控制。
  • 内置丰富的插件支持,鉴权,限流,熔断,防火墙等等。
  • 流量配置动态化,性能极高,网关消耗在 1~2ms
  • 支持集群部署,支持 A/B Test,蓝绿发布。

来自官网的一张架构图

1

从上面的架构图可以看出,Soul网关与语言无关,各种语言(Java,PHP,.NET等)都可以接入到网关中。它通过对不同插件的支持实现各种功能(监控,认证,限流,熔断,不同用户接入等)。在后台管理系统(soul-admin)就可以灵活配置各种流量。

如何在本地运行 Soul

好了,知道了Soul是一个网关,那么接下来就看看怎么使用它。 通过案例演示的方式比直接了解各个概念的方式更能激发兴趣,所以开始play it吧!

  1. 从官网拉取项目源码 git clone git@github.com:dromara/soul.git,当前最新版本是2.2.1

  2. 创建并切换分支git checkout -b myLearn,就在自己的在本地跑,直接用master分支也行。

  3. 使用IDEA打开项目,然后本地编译一下,确保没有错。

    mvn clean install
    

    第一次编译会很慢,需要下载依赖。当然,也可以跳过相关测试和注释,这样会快一点。

    mvn clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
    
  4. 启动Soul的后台管理地址,就是项目源码中的soul-admin模块,这是一个SpringBoot项目,只需要修改一下数据库的地址就可以运行了。项目会自动创建对应的库和表。

    2

    项目启动后的登录地址是http://localhost:9095/,默认用户名是admin,密码是123456。后台界面如下:

    3

    主要模块有插件列表和系统管理,在插件列表中可以对各个插件进行管理,每个插件都可以添加多个选择器,每个选择器都可以添加多条规则。实际这就是Soul根据请求的URL去匹配规则:插件->选择器->规则,这个后面再细说。

  5. 启动Soul的核心模块soul-bootstrap,这是网关的核心处理逻辑。不要怕它,这个模块本身不复杂,目录结构如下:

    4

    启动成功后,就可以访问这个网关了http://127.0.0.1:9195/,返回信息如下:

    {"code":-107,"message":"Can not find selector, please check your configuration!","data":null}
    

    因为还没有接入业务系统,所以没有相关返回值,上面展示的信息是Soul网关没有找到相应的选择器,返回的一个提示信息

  6. 通过上述步骤,就成功的搭建起Soul网关服务了,后面就是在自己的业务系统上使用网关。使用例子可以参考soul-examples模块。

  7. 本文运行soul-examples下面的 http服务,结合divde插件,发起http请求到soul网关,体验http代理。模块目录及配置信息如下:

    5

    配置文件中有关soul的配置我们后面再详细解释,现在只需要知道这个服务就是一个很普通的Spring Boot项目。其中的一个接口信息如下:

    6

    运行SoulTestHttpApplication,启动这个项目。成功之后,通过postman进行访问:

    7

    上面就是一个普通的http请求,直接请求业务系统的后端服务,现在通过Soul网关来访问该服务。

    8

这个localhost:9195地址就是网关的地址,/http是业务系统在网关中的名称。那么,现在的请求就是先通过Soul网关,再由网关转发到实际的请求接口。

对 Soul 进行压测

成功在本地运行Soul网关之后,我们最后再来看看Soul网关的性能如何。在windows平台可以通过SuperBenchmarker工具进行压测。压测设置的参数是200个请求,32个并发,100秒内执行完。

9

然后再来看一下直连的情况:

10

可以看到 直连的RPS(3916.9)还是大于经过网关转发后的RPS(1526.3 ),毕竟多了网关这一层的转发。

小结一下,本篇文章介绍了Soul网关的特点和架构设计。然后,拉取soul源码,在本地运行测试案例。最后,对soul网关进行了简单的压测。

参考文章:

Soul网关中的Sentinel插件

本篇文章分析的是Sentinel插件,它也可以提供熔断和限流的功能。

操作前准备:启动soul-adminsoul网关,soul-examples-http测试用例。

Sentinel功能演示

首先在soul网关中添加Sentinel插件,引入依赖:

        <!-- soul sentinel plugin start-->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-plugin-sentinel</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- soul sentinel plugin end-->

然后需要在soul-admin中依次开启插件,添加选择器,添加规则:

规则字段解析:

  • degrade count: 熔断阈值;
  • whether to open the degrade (1 or 0): 是否开启熔断;
  • degrade type: 熔断策略,支持秒级 RT/秒级异常比例/分钟级异常数;
  • control behavior: 流控策略(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流;
  • grade count:限流阈值;
  • whether control behavior is enabled (1 or 0): 是否开启限流;
  • grade type: 限流类型,QPS 或线程数模式。

上述准备工作完成后,就能进行测试了。开启限流,设置流控阈值为1,然后多点几次 postman,即会发生限流现象:

Sentinel原理分析

SentinelPlugin 继承于模板抽象类 AbstractSoulPlugin,所以 doExecutor 是其执行真正功能逻辑的代码:

    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
		//省略了其他代码
        SentinelHandle sentinelHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), SentinelHandle.class);
        //限流或降级执行类 SentinelReactorTransformer
        //责任链继续执行
        return chain.execute(exchange).transform(new SentinelReactorTransformer<>(resourceName)).doOnSuccess(v -> {
            //不成功,则抛出降级异常
            if (exchange.getResponse().getStatusCode() != HttpStatus.OK) {
                HttpStatus status = exchange.getResponse().getStatusCode();
                exchange.getResponse().setStatusCode(null);
                throw new SentinelFallbackException(status);
            }
        }).onErrorResume(throwable -> sentinelFallbackHandler.fallback(exchange, UriUtils.createUri(sentinelHandle.getFallbackUri()), throwable));
    }

限流或降级执行类是 SentinelReactorTransformer,来自于Sentinel插件。集成Sentinel插件就是以上代码,使用起来还是比较简单。

Sentinel规则在admin修改后,会同步更新到SentinelRuleHandle

public void handlerRule(final RuleData ruleData) {
        SentinelHandle sentinelHandle = GsonUtils.getInstance().fromJson(ruleData.getHandle(), SentinelHandle.class);

        //更新限流规则
        List<FlowRule> flowRules = FlowRuleManager.getRules()
        if (sentinelHandle.getFlowRuleEnable() == Constants.SENTINEL_ENABLE_FLOW_RULE) {
			//省略了其他代码
        }
        FlowRuleManager.loadRules(flowRules);
    
        //更新熔断降级规则
        List<DegradeRule> degradeRules = DegradeRuleManager.getRules()
        if (sentinelHandle.getDegradeRuleEnable() == Constants.SENTINEL_ENABLE_DEGRADE_RULE) {
		//
        }
        DegradeRuleManager.loadRules(degradeRules);
    }

小结,本文结合实际案例演示了Sentinel插件,跟踪了Sentinel插件源码。

Soul网关中的Resilience4j插件

本篇文章分析的是Resilience4j插件,它可以提供熔断和限流的功能。

操作前准备:启动soul-adminsoul网关,soul-examples-http测试用例。

Resilience4j功能演示

要在soul网关使用Resilience4j插件,需要引入依赖:

        <!-- soul resilience4j plugin start-->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-plugin-resilience4j</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- soul resilience4j plugin end-->

然后需要在soul-admin中依次开启插件,添加选择器,添加规则:

规则字段解析:

  • token filling period(limitRefreshPeriod): 刷新令牌的时间间隔,单位ms,默认值:500。
  • token filling number(limitForPeriod): 每次刷新令牌的数量,默认值:50。
  • control behavior timeout(timeoutDurationRate): 熔断超时时间,单位ms,默认值:30000。
  • circuit enable(circuitEnable): 是否开启熔断,0:关闭,1:开启,默认值:0。
  • circuit timeout (timeoutDuration): 熔断超时时间,单位ms,默认值:30000。
  • fallback uri(fallbackUri): 降级处理的uri
  • sliding window size(slidingWindowSize): 滑动窗口大小,默认值:100。
  • sliding window type(slidingWindowType): 滑动窗口类型,0:基于计数,1:基于时间,默认值:0。
  • enabled error minimum calculation threshold(minimumNumberOfCalls): 开启熔断的最小请求数,超过这个请求数才开启熔断统计,默认值:100。
  • degrade opening duration(waitIntervalFunctionInOpenState): 熔断器开启持续时间,单位ms,默认值:10。
  • half open threshold(permittedNumberOfCallsInHalfOpenState): 半开状态下的环形缓冲区大小,必须达到此数量才会计算失败率,默认值:10。
  • degrade failure rate(failureRateThreshold):错误率百分比,达到这个阈值,熔断器才会开启,默认值50。

上述准备工作完成后,就能进行测试了。每次刷新令牌的数量调成了只有一个,刷新时间要 100s,所以只要多点几次 postman,即会发生限流现象:

Resilience4j原理分析

Resilience4JPlugin 继承于模板抽象类 AbstractSoulPlugin,所以 doExecutor 是其执行真正功能逻辑的代码:

    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
		//省略了其他代码
        //是否开启熔断
        if (resilience4JHandle.getCircuitEnable() == 1) {
            return combined(exchange, chain, rule);
        }
        //限流处理
        return rateLimiter(exchange, chain, rule);
    }

主要功能是判断是否开启熔断,如果是,就进入组合模式(即处理熔断又处理限流);否则,只做限流处理。

  • 组合模式
    private Mono<Void> combined(final ServerWebExchange exchange, final SoulPluginChain chain, final RuleData rule) {
        //设置管理后台配置的规则
        Resilience4JConf conf = Resilience4JBuilder.build(rule);
        return combinedExecutor.run(
            	//执行责任链后续操作
                chain.execute(exchange).doOnSuccess(v -> {
                    //省略了其他代码
                }), 
            fallback(combinedExecutor, exchange, conf.getFallBackUri()), //降级处理
            conf); // Resilience4J相关配置
    }

combinedExecutor.run()方法负责执行真实的逻辑:先执行熔断的功能,后执行限流的功能。再处理超时,错误信息,最后进行降级处理。

    public <T> Mono<T> run(final Mono<T> run, final Function<Throwable, Mono<T>> fallback, final Resilience4JConf resilience4JConf) {

        //链式调用
        Mono<T> to = run.transformDeferred(CircuitBreakerOperator.of(circuitBreaker))//先处理熔断
                .transformDeferred(RateLimiterOperator.of(rateLimiter))//后限流
                .timeout(resilience4JConf.getTimeLimiterConfig().getTimeoutDuration())//超时处理
                .doOnError(/*...*/);//错误处理
        if (fallback != null) {
            to = to.onErrorResume(fallback);//降级处理
        }
        return to;
    }
  • 限流模式
    private Mono<Void> rateLimiter(final ServerWebExchange exchange, final SoulPluginChain chain, final RuleData rule) {
        return ratelimiterExecutor.run(
                chain.execute(exchange),  //责任链继续执行
            fallback(ratelimiterExecutor, exchange, null), //降级处理
            Resilience4JBuilder.build(rule))//规则配置
                .onErrorResume(throwable -> ratelimiterExecutor.withoutFallback(exchange, throwable));
    }

限流的功能主要在ratelimiterExecutor.run()中,负责生成限流器并执行。

    @Override
    public <T> Mono<T> run(final Mono<T> toRun, final Function<Throwable, Mono<T>> fallback, final Resilience4JConf conf) {
        //限流器
        RateLimiter rateLimiter = Resilience4JRegistryFactory.rateLimiter(conf.getId(), conf.getRateLimiterConfig());
        Mono<T> to = toRun.transformDeferred(RateLimiterOperator.of(rateLimiter));//执行限流功能
        if (fallback != null) { //降级
            return to.onErrorResume(fallback);
        }
        return to;
    }

小结,本文结合实际案例演示了Resilience4j插件,并分析了组合模式和限流模式的执行原理。