总结:
每一个插件都自定了一个 spring boot starter。soul-bootstrap 中引入 starter 即可添加插件。
soul-bootstrap 启动时就会通过这些 starter 将插件 bean 初始化,并装载到上下文。
soul-bootstrap
通过soul-spring-boot-starter-gateway
这个starter来启动网关,soul-spring-boot-starter-gateway
依赖soul-web
,soul-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请求进行相应的转发。
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 加上:
org.springframework.boot
spring-boot-starter-websocket
bootstrap的application.yml 配上:
soul :
file:
enabled: true
corss:
enabled: true
dubbo :
parameter: multi
sync:
websocket :
urls: ws://localhost:9095/websocket
配置数据的发送
Soul-admin -----> Soul-web
WebsocketDataChangedListener
类监听插件的改动、选择器的改动、规则的改动、用户权限的改动以及元数据的改动。
监听到配置的变更之后,便会通过建立好的 websocket
连接将变更的数据发送给soul-web
Soul bootstrap 处理
同步数据的starter:soul-spring-boot-starter-sync-data-websocket
的配置类WebsocketSyncDataConfiguration
初始化bean
时,创建了 WebsocketSyncDataService
用于同步数据(WebsocketSyncDataService
的构造函数中创建了SoulWebsocketClient
来处理websocket
数据)
soul-sync-data-websocket
是同步数据的具体操作。其中有很多 Handler
,顶级接口DataHandler
中定了一个handle()
方法,抽象类 AbstractDataHandler
实现了 DataHandler
接口,并封装了很多抽象方法,包括 覆盖数据,刷新数据REFRESH,更新数据UPDATE,删除数据DELETE,并实现了 handle()
方法。其中 WebsocketDataHandler
用来处理 web socket
协议推送过来的数据。
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
数据的变化,实现监听并处理变化的数据.
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));
}
https://blog.csdn.net/wu2304211/article/details/113286454
目前主流的负载方案分为两种:
1.集中式负载均衡,比如 Nginx
2.客户端负载均衡,客户端根据请求情况做负载,Ribbon 就属于这一种
https://blog.csdn.net/wu2304211/article/details/113409479
Hystrix 由 Netflix 发布的针对微服务分布式系统的熔断保护中间件
HystrixPlugin 前置插件
doExecute()方法中的断路器是判断熔断的关键:
command.isCircuitBreakerOpen()
https://blog.csdn.net/wu2304211/article/details/113449333
令牌桶算法
https://blog.csdn.net/wu2304211/article/details/113449333#_57