之前的笔记里面学习过普通springboot项目接入soul网关的时候,只要有@SoulSpringMvcClient
注解的接口,就会被注册到soul-admin的divide插件的selector和rule里面去。soul网关不仅通过divide插件进行请求代理,还可以对业务的机器进行探活。
假如说网关没有探活功能,那么当用户的请求过来之后,会直接转发给相关业务方。这个时候假如业务方的机器因为请求压力太大挂掉了,但是网关还是把大量的请求直接传过来了,业务方的机器还是会因为有大量的请求一直起不来。或者是业务方的某一台机器有问题不能正常服务,如果网关直接转发用户请求,可能会把请求转到不能服务的那台机器上去了,这样的话对用户来说体验更好一点。所以,网关有必要对业务方的机器进行探活。
我们来学习一下soul网关是如何进行服务(upstream)探活的。
SelectorServiceImpl
有一个属性是UpstreamCheckService
类型的,我们先来看下UpstreamCheckService
的几个属性:
Map<String, List<DivideUpstream>>
类型的UPSTREAM_MAP属性,是把selectorName与服务节点列表的映射缓存在了内存中。直接读写这个内存中的变量,效率很高- boolean类型的check属性,表示是否需要进行探活。默认true
- int类型的scheduledTime属性,表示每隔多少秒进行一下探活。默认10s
- 还有一些操作数据库的mapper
public class UpstreamCheckService {
private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = Maps.newConcurrentMap();
@Value("${soul.upstream.check:true}")
private boolean check;
@Value("${soul.upstream.scheduledTime:10}")
private int scheduledTime;
private final SelectorMapper selectorMapper;
private final ApplicationEventPublisher eventPublisher;
private final PluginMapper pluginMapper;
private final SelectorConditionMapper selectorConditionMapper;
……
……
……
UpstreamCheckService
里面最关键的,就是public void setup()
方法,该方法上面有一个@PostConstruct
注解。
该注解使用的时候需要注意:
- 被注解的方法不得有参数
- 被注解的方法返回值为void
- 被注解方法不得抛出已检查异常
- 被注解方法必须是非静态方法
- 被注解的方法只会被执行一次
被@PostConstruct
注解的方法,在对象加载完依赖注入后会被执行。与@PostConstruct
对应的是@PreDestroy
,被它注解的方法,在对象消亡之前执行。
我们来看下这个被@PostConstruct
注解的setUp方法里面主要干了什么:
- 从数据库中取出divide插件的数据
- 如果插件数据不为空,则取出divide插件的selector数据。并且解析出selector里面的upstream,放到内存缓存中。
- 创建一个
ScheduledThreadPoolExecutor
线程池,来进行周期性任务。周期为10s。
@PostConstruct
public void setup() {
PluginDO pluginDO = pluginMapper.selectByName(PluginEnum.DIVIDE.getName());
if (pluginDO != null) {
List<SelectorDO> selectorDOList = selectorMapper.findByPluginId(pluginDO.getId());
for (SelectorDO selectorDO : selectorDOList) {
List<DivideUpstream> divideUpstreams = GsonUtils.getInstance().fromList(selectorDO.getHandle(), DivideUpstream.class);
if (CollectionUtils.isNotEmpty(divideUpstreams)) {
UPSTREAM_MAP.put(selectorDO.getName(), divideUpstreams);
}
}
}
if (check) {
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), SoulThreadFactory.create("scheduled-upstream-task", false))
.scheduleWithFixedDelay(this::scheduled, 10, scheduledTime, TimeUnit.SECONDS);
}
}
周期性任务的主要功能就是进行探活检查,具体逻辑为:
- 如果当前内存缓存UPSTREAM_MAP里面有数据,则遍历每项数据进行以下检查
- 对每一个upstream,如果它是ip,则进行socket连接来探活;如果不是ip,则调用java本身的
isReachable
方法来探活 - 对于不存活的upstream,则从内存缓存中剔除它,也从数据库的selector里的handle里的upstream里删除它。并且使用
ApplicationEventPublisher
发布这个数据更新事件。那么数据的更新就会被同步到网关中去。网关进行请求代理的时候,自然也不会转发到故障节点去。 - 对于所有upstream都不存活的selector,将它从内存缓存UPSTREAM_MAP里面删除
画图举例来说明。假如说有一个服务A,有三个节点server1, server2, server3,一开始三个节点是好的。那么soul-admin会向这三个节点进行探活。
当其中server1节点挂掉之后,soul-admin进行探活的时候得知它挂了,那么下次进行探活的时候就提出server1,不会对它进行探活。
当server1节点重启恢复之后,由于我在之前的笔记里面写到的,节点启动的时候会向soul-admin注册,soul-admin里面的服务A的upstream又变成了3个。
本文学习了soul网关的探活机制,总体来说还是比较简单易懂。
2020-02-01补充:
后来看了一下,发现soul-plugin-divide里面也有upstream探活机制UpstreamCacheManager
,不过默认是关闭状态。
UpstreamCacheManager
里面有两个属性,UPSTREAM_MAP
里面存储的是admin同步过来的upstream,UPSTREAM_MAP_TEMP
里面存储的是探活机制探到的存活的upstream。不过因为
soul-plugin-divide里面的探活机制默认是关闭的,所以UPSTREAM_MAP
和UPSTREAM_MAP_TEMP
中存储的数据一样的。
public final class UpstreamCacheManager {
private static final UpstreamCacheManager INSTANCE = new UpstreamCacheManager();
private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = Maps.newConcurrentMap();
private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP_TEMP = Maps.newConcurrentMap();
……
……
……
如果soul-plugin-divide的探活机制开启的话,默认是每30s进行一次探活。