Soul网关总结01

1 插件调用链

总结:

​ 每一个插件都自定了一个 spring boot starter。soul-bootstrap 中引入 starter 即可添加插件。

​ soul-bootstrap 启动时就会通过这些 starter 将插件 bean 初始化,并装载到上下文。

soul-bootstrap通过soul-spring-boot-starter-gateway这个starter来启动网关,soul-spring-boot-starter-gateway依赖soul-websoul-web的自动配置类:SoulConfiguration中初始化了 webHandler, dispatcherHandler, pluginDataSubscriber

soul-bootstrap 引入了 webflux 依赖来实现反应式编程,初始化了一个 反应式 web 服务器:

		/**
     * 反应式web 服务器
     * @return the netty reactive web server factory
     */
    @Bean
    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
        NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();
        webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer());
        return webServerFactory;
    }

soul-web 从bean工厂加载所有插件并初始化 SoulWebHandler:

/**
 * 从bean工厂加载插件并初始化 SoulWebHandler.
 */
@Bean("webHandler")
public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
    List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
    final List<SoulPlugin> soulPlugins = pluginList.stream()
            .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
    soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
    // 初始化 SoulWebHandler 并将插件加载到本地内存
    return new SoulWebHandler(soulPlugins);
}

加载上下文中的所有插件,根据每个插件Order的大小,形成了一个插件链:

GlobalPlugin 				(默认不跳过) --- 有execute()方法
SignPlugin 					(默认不跳过) --- 有doExecute()方法
WafPlugin 					(默认不跳过) --- 有doExecute()方法
RateLimiterPlugin 	(默认不跳过) --- 有doExecute()方法
HystrixPlugin 			(默认不跳过) --- 有doExecute()方法
Resilience4JPlugin	(默认不跳过) --- 有doExecute()方法

DividePlugin				(只匹配RPC类型为 http 的请求)  ---  有execute()方法
SpringCloudPlugin		(【跳过】因为RPC类型不匹配,此插件的rpc类型=springcloud)
WebClientPlugin		 (只匹配RPC类型为 http 或 springcloud 的请求)  ---  有execute()方法
WebSocketPlugin		 (【跳过】因为RPC类型不匹配,此插件的rpc类型=websocket)---  有doExecute()方法
BodyParamPlugin		  (【跳过】只有dubbo、sofa、tars 这几个Plugin里有用到) ---  有doExecute()方法
SofaPlugin					(【跳过】因为RPC类型不匹配,此插件的rpc类型=sofa)		 ---  有doExecute()方法
AlibabaDubboPlugin	(【跳过】因为RPC类型不匹配,此插件的rpc类型=dubbo)		---  有doExecute()方法
MonitorPlugin				(默认不跳过)	---  有doExecute()方法
SofaResponsePlugin	(【跳过】因为RPC类型不匹配,此插件的rpc类型=sofa)---有execute()方法
WebClientResponsePlugin(只匹配RPC类型为 http 或 springcloud 的请求)---有execute()方法
DubboResponsePlugin	(【跳过】因为RPC类型不匹配,此插件的rpc类型=dubbo)---有execute()方法

SoulWebHandler(响应式服务的入口) ,Web 请求进来会走这个插件链,根据请求 RPC 类型找到对应的插件来处理当前请求。

				@Override
        public Mono<Void> execute(final ServerWebExchange exchange) {
            return Mono.defer(() -> {
                if (this.index < plugins.size()) {
                    SoulPlugin plugin = plugins.get(this.index++);
                    // 根据 rpc 类型判断是否跳过
                    Boolean skip = plugin.skip(exchange);
                    if (skip) {
                        return this.execute(exchange);
                    }
                    return plugin.execute(exchange, this);
                }
                return Mono.empty();
            });
        }

请求到达某个具体的插件,插件会查找 选择器 和 规则,是否有匹配的规则,如果可以匹配上,再进一步处理。比如 限流插件 RateLimiterPlugin,根据soul-admin中配置的令牌桶算法的参数:令牌桶容量 和 令牌生成速率,来对请求进行限流处理。

protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
    final String handle = rule.getHandle();
    final RateLimiterHandle limiterHandle = GsonUtils.getInstance().fromJson(handle, RateLimiterHandle.class);
  	// 请求是否被拒绝(是否有匹配的规则、令牌生成速率、令牌桶容量)
    return redisRateLimiter.isAllowed(rule.getId(), limiterHandle.getReplenishRate(), limiterHandle.getBurstCapacity())
            .flatMap(response -> {
                if (!response.isAllowed()) {
                    exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                    Object error = SoulResultWrap.error(SoulResultEnum.TOO_MANY_REQUESTS.getCode(), SoulResultEnum.TOO_MANY_REQUESTS.getMsg(), null);
                    return WebFluxResultUtils.result(exchange, error);
                }
                return chain.execute(exchange);
            });
}

再比如说:

HTTP 请求(RPC类型为:http),经过一些前置插件后,HTTP请求会匹配上 Divide插件,然后匹配 选择器和规则,对HTTP请求进行相应的转发。

2 数据同步

Websocket
zookeeper
HTTP 长轮询
Nacos

开启数据同步:

soul-admin 配置文件 application.yml 中指定与网关的数据同步策略(默认采用 websocket)。然后引入对应的 starter,比如websocket 同步数据。 soul-bootstrap 的配置文件application-local.yml中也要相应配置

soul-admin的application.yml 配上:

  sync:
    websocket:
      enabled: true

soul-admin的pom 加上:

<dependency>
    <groupId>org.springframework.boot</groupId>
	  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

bootstrap的application.yml 配上:

soul :
    file:
      enabled: true
    corss:
      enabled: true
    dubbo :
      parameter: multi
    sync:
        websocket :
             urls: ws://localhost:9095/websocket

2.1 Websocket 数据同步

https://blog.csdn.net/wu2304211/article/details/112915059

配置数据的发送

Soul-admin -----> Soul-web

WebsocketDataChangedListener类监听插件的改动、选择器的改动、规则的改动、用户权限的改动以及元数据的改动。

监听到配置的变更之后,便会通过建立好的 websocket连接将变更的数据发送给soul-web

Soul bootstrap 处理

  1. 同步数据的starter:soul-spring-boot-starter-sync-data-websocket的配置类WebsocketSyncDataConfiguration初始化bean时,创建了 WebsocketSyncDataService 用于同步数据(WebsocketSyncDataService的构造函数中创建了SoulWebsocketClient来处理websocket数据)

  2. soul-sync-data-websocket 是同步数据的具体操作。其中有很多 Handler,顶级接口DataHandler中定了一个handle()方法,抽象类 AbstractDataHandler实现了 DataHandler 接口,并封装了很多抽象方法,包括 覆盖数据,刷新数据REFRESH,更新数据UPDATE,删除数据DELETE,并实现了 handle() 方法。其中 WebsocketDataHandler 用来处理 web socket协议推送过来的数据。

2.2 Zookeeper 数据同步

https://blog.csdn.net/wu2304211/article/details/112976150

依赖 zookeeper 的 watch 机制,soul-web 会监听配置的节点,soul-admin 在启动的时候,会将数据全量写入 zookeeper,后续数据发生变更时,会增量更新 zookeeper 的节点,与此同时,soul-bootstrap中引入的soul-spring-boot-starter-sync-data-zookeeper 会监听配置信息的节点,一旦有信息变更时,会更新本地缓存。

Soul-admin 发送更新数据到zk:

ZookeeperDataChangedListener 类负责监听数据的变化,并通过zkClient将数据的变化增量更新到 zookeeper的节点.

bootstrap处理:

starter: soul-spring-boot-starter-sync-data-zookeeper 中的配置类( ZookeeperSyncDataConfiguration):

配置类中初始化了ZookeeperSyncDataService对象

ZookeeperSyncDataService 会监听 zookeeper中选择器、规则、元数据、用户权限等数据的变更。如何监听?----通过zookeeper 客户端订阅zookeeper数据的变化,实现监听并处理变化的数据.

2.3 HTTP 长轮询同步数据

https://blog.csdn.net/wu2304211/article/details/113013490

Soul-bootstrap 的HttpSyncDataService 中创建了一个线程,这个线程会通过 HttpLongPollingTask 任务来长轮询 soul-admin/configs/listener接口)。

Soul-bootstrap 的请求到了 soul-admin 之后,首先,soul-admin将长轮询请求任务 LongPollingClient 扔到 BlocingQueue 中,并且开启调度任务ScheduledThreadPoolExecutor,60s 后执行,这样做的目的是 60s 后将该长轮询请求移出BlocingQueue队列(超时响应机制),(soul-bootstrap请求soul-admin/configs/listener接口时候,也可以设置超时时间)

BlocingQueue,初始大小为 1024 ,用来存放「长轮询请求任务 LongPollingClient」:

	/**
	 * 初始化 Http长轮询数据变化监听器
     * @param httpSyncProperties the HttpSyncProperties
     */
    public HttpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
        // 初始大小为 1024 
        this.clients = new ArrayBlockingQueue<>(1024);
        // 创建了一个带延迟队列的线程池(核心线程数 1)来将 长轮询请求 移出队列BlocingQueue:
        this.scheduler = new ScheduledThreadPoolExecutor(1,
                SoulThreadFactory.create("long-polling", true));
        this.httpSyncProperties = httpSyncProperties;
    }

Servlet3.0的异步机制,异步响应数据:

	// 处理soul-bootstrap过来的请求	
	public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {

        // compare group md5
        List<ConfigGroupEnum> changedGroup = compareChangedGroup(request);
        String clientIp = getRemoteIp(request);

        // 如果配置数据有改动,则立即响应
        if (CollectionUtils.isNotEmpty(changedGroup)) {
            this.generateResponse(response, changedGroup);
            log.info("send response with the changed group, ip={}, group={}", clientIp, changedGroup);
            return;
        }

        // 监听配置数据的变化
        final AsyncContext asyncContext = request.startAsync();

        // AsyncContext.settimeout() does not timeout properly, so you have to control it yourself
        asyncContext.setTimeout(0L);

        // 调度任务 60 秒后会将请求移出BlocingQueue队列
        scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
    }

img

3 负载均衡

https://blog.csdn.net/wu2304211/article/details/113286454

目前主流的负载方案分为两种:

1.集中式负载均衡,比如 Nginx

2.客户端负载均衡,客户端根据请求情况做负载,Ribbon 就属于这一种

4 容错

https://blog.csdn.net/wu2304211/article/details/113409479

Hystrix 由 Netflix 发布的针对微服务分布式系统的熔断保护中间件

HystrixPlugin 前置插件

​ doExecute()方法中的断路器是判断熔断的关键:

command.isCircuitBreakerOpen()

4.1 Hystrix 总结

在这里插入图片描述

5 限流

https://blog.csdn.net/wu2304211/article/details/113449333

5.1 RateLimiterPlugin 限流插件

img

令牌桶算法

  • 速率:令牌桶填充令牌的速率。等同于:可以匀速处理 QPS=令牌填充速率 的请求。
  • 容量:令牌桶可以保存的最大令牌数。等同于:一秒内可以执行的最大请求数。(可以应对突发大流量)

5.2 限流总结

https://blog.csdn.net/wu2304211/article/details/113449333#_57

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值